{"version":3,"file":"use-headroom.mjs","names":[],"sources":["../../src/use-headroom/use-headroom.ts"],"sourcesContent":["import { useEffectEvent, useRef } from 'react';\nimport { useIsomorphicEffect } from '../use-isomorphic-effect/use-isomorphic-effect';\nimport { useScrollDirection } from '../use-scroll-direction/use-scroll-direction';\nimport { useWindowScroll } from '../use-window-scroll/use-window-scroll';\n\nexport { useScrollDirection } from '../use-scroll-direction/use-scroll-direction';\n\nexport const isFixed = (current: number, fixedAt: number) => current <= fixedAt;\nexport const isPinned = (current: number, previous: number) => current <= previous;\nexport const isReleased = (current: number, previous: number, fixedAt: number) =>\n  !isPinned(current, previous) && !isFixed(current, fixedAt);\n\nexport const isPinnedOrReleased = (\n  current: number,\n  fixedAt: number,\n  isCurrentlyPinnedRef: React.RefObject<boolean>,\n  isScrollingUp: boolean,\n  onPin?: () => void,\n  onRelease?: () => void\n) => {\n  const isInFixedPosition = isFixed(current, fixedAt);\n  if (isInFixedPosition && !isCurrentlyPinnedRef.current) {\n    isCurrentlyPinnedRef.current = true;\n    onPin?.();\n  } else if (!isInFixedPosition && isScrollingUp && !isCurrentlyPinnedRef.current) {\n    isCurrentlyPinnedRef.current = true;\n    onPin?.();\n  } else if (!isInFixedPosition && !isScrollingUp && isCurrentlyPinnedRef.current) {\n    isCurrentlyPinnedRef.current = false;\n    onRelease?.();\n  }\n};\n\nexport interface UseHeadroomInput {\n  /** Number in px at which element should be fixed */\n  fixedAt?: number;\n\n  /** Number of px to scroll to fully reveal or hide the element, 100 by default */\n  scrollDistance?: number;\n\n  /** Called when element is pinned */\n  onPin?: () => void;\n\n  /** Called when element is at fixed position */\n  onFix?: () => void;\n\n  /** Called when element is unpinned */\n  onRelease?: () => void;\n}\n\nexport interface UseHeadroomReturnValue {\n  /** True when the element is at least partially visible */\n  pinned: boolean;\n\n  /** Reveal progress: 0 = fully hidden, 1 = fully visible */\n  scrollProgress: number;\n}\n\nexport function useHeadroom({\n  fixedAt = 0,\n  scrollDistance = 100,\n  onPin,\n  onFix,\n  onRelease,\n}: UseHeadroomInput = {}): UseHeadroomReturnValue {\n  const isCurrentlyPinnedRef = useRef(false);\n  const scrollDirection = useScrollDirection();\n  const isScrollingUp = scrollDirection === 'up';\n  const [{ y: scrollPosition }] = useWindowScroll();\n\n  const onPinEvent = useEffectEvent(() => onPin?.());\n  const onReleaseEvent = useEffectEvent(() => onRelease?.());\n  const onFixEvent = useEffectEvent(() => onFix?.());\n\n  useIsomorphicEffect(() => {\n    isPinnedOrReleased(\n      scrollPosition,\n      fixedAt,\n      isCurrentlyPinnedRef,\n      isScrollingUp,\n      onPinEvent,\n      onReleaseEvent\n    );\n  }, [scrollPosition, fixedAt, isScrollingUp]);\n\n  const wasFixedRef = useRef(false);\n\n  useIsomorphicEffect(() => {\n    const currentlyInFixedZone = isFixed(scrollPosition, fixedAt);\n    if (currentlyInFixedZone && !wasFixedRef.current) {\n      onFixEvent();\n    }\n    wasFixedRef.current = currentlyInFixedZone;\n  }, [scrollPosition, fixedAt]);\n\n  // Refs for scroll-progress tracking. Mutated during render (safe for refs).\n  const currentlyFixed = isFixed(scrollPosition, fixedAt);\n  const prevIsFixedRef = useRef(currentlyFixed);\n  const directionChangeScrollYRef = useRef(scrollPosition);\n  const progressAtDirectionChangeRef = useRef(currentlyFixed ? 1 : 0);\n  const prevIsScrollingUpRef = useRef(isScrollingUp);\n\n  // Detect fixed-zone transitions first. When leaving the fixed zone the baseline\n  // is anchored at fixedAt (not the current scroll position) so the delta is measured\n  // from where the element was last fully visible, regardless of how scroll position\n  // was initialised on the first render.\n  if (prevIsFixedRef.current !== currentlyFixed) {\n    prevIsFixedRef.current = currentlyFixed;\n\n    if (!currentlyFixed) {\n      directionChangeScrollYRef.current = fixedAt;\n      progressAtDirectionChangeRef.current = 1;\n    } else {\n      directionChangeScrollYRef.current = scrollPosition;\n      progressAtDirectionChangeRef.current = 1;\n    }\n\n    prevIsScrollingUpRef.current = isScrollingUp;\n  }\n\n  // When scroll direction changes outside the fixed zone, save the current progress\n  // so the next direction accumulates from that point (handles partial reveals).\n  if (!currentlyFixed && prevIsScrollingUpRef.current !== isScrollingUp) {\n    const transitionDelta = Math.abs(scrollPosition - directionChangeScrollYRef.current);\n    const transitionProgress = prevIsScrollingUpRef.current\n      ? Math.min(progressAtDirectionChangeRef.current + transitionDelta / scrollDistance, 1)\n      : Math.max(progressAtDirectionChangeRef.current - transitionDelta / scrollDistance, 0);\n\n    prevIsScrollingUpRef.current = isScrollingUp;\n    directionChangeScrollYRef.current = scrollPosition;\n    progressAtDirectionChangeRef.current = transitionProgress;\n  }\n\n  let scrollProgress: number;\n\n  if (currentlyFixed) {\n    scrollProgress = 1;\n  } else {\n    const scrollDelta = Math.abs(scrollPosition - directionChangeScrollYRef.current);\n\n    if (isScrollingUp) {\n      scrollProgress = Math.min(\n        progressAtDirectionChangeRef.current + scrollDelta / scrollDistance,\n        1\n      );\n    } else {\n      scrollProgress = Math.max(\n        progressAtDirectionChangeRef.current - scrollDelta / scrollDistance,\n        0\n      );\n    }\n  }\n\n  return { pinned: scrollProgress > 0, scrollProgress };\n}\n\nexport namespace useHeadroom {\n  export type Input = UseHeadroomInput;\n  export type ReturnValue = UseHeadroomReturnValue;\n}\n"],"mappings":";;;;;;AAOA,MAAa,WAAW,SAAiB,YAAoB,WAAW;AAKxE,MAAa,sBACX,SACA,SACA,sBACA,eACA,OACA,cACG;CACH,MAAM,oBAAoB,QAAQ,SAAS,OAAO;CAClD,IAAI,qBAAqB,CAAC,qBAAqB,SAAS;EACtD,qBAAqB,UAAU;EAC/B,QAAQ;CACV,OAAO,IAAI,CAAC,qBAAqB,iBAAiB,CAAC,qBAAqB,SAAS;EAC/E,qBAAqB,UAAU;EAC/B,QAAQ;CACV,OAAO,IAAI,CAAC,qBAAqB,CAAC,iBAAiB,qBAAqB,SAAS;EAC/E,qBAAqB,UAAU;EAC/B,YAAY;CACd;AACF;AA2BA,SAAgB,YAAY,EAC1B,UAAU,GACV,iBAAiB,KACjB,OACA,OACA,cACoB,CAAC,GAA2B;CAChD,MAAM,uBAAuB,OAAO,KAAK;CAEzC,MAAM,gBADkB,mBACY,MAAM;CAC1C,MAAM,CAAC,EAAE,GAAG,oBAAoB,gBAAgB;CAEhD,MAAM,aAAa,qBAAqB,QAAQ,CAAC;CACjD,MAAM,iBAAiB,qBAAqB,YAAY,CAAC;CACzD,MAAM,aAAa,qBAAqB,QAAQ,CAAC;CAEjD,0BAA0B;EACxB,mBACE,gBACA,SACA,sBACA,eACA,YACA,cACF;CACF,GAAG;EAAC;EAAgB;EAAS;CAAa,CAAC;CAE3C,MAAM,cAAc,OAAO,KAAK;CAEhC,0BAA0B;EACxB,MAAM,uBAAuB,QAAQ,gBAAgB,OAAO;EAC5D,IAAI,wBAAwB,CAAC,YAAY,SACvC,WAAW;EAEb,YAAY,UAAU;CACxB,GAAG,CAAC,gBAAgB,OAAO,CAAC;CAG5B,MAAM,iBAAiB,QAAQ,gBAAgB,OAAO;CACtD,MAAM,iBAAiB,OAAO,cAAc;CAC5C,MAAM,4BAA4B,OAAO,cAAc;CACvD,MAAM,+BAA+B,OAAO,iBAAiB,IAAI,CAAC;CAClE,MAAM,uBAAuB,OAAO,aAAa;CAMjD,IAAI,eAAe,YAAY,gBAAgB;EAC7C,eAAe,UAAU;EAEzB,IAAI,CAAC,gBAAgB;GACnB,0BAA0B,UAAU;GACpC,6BAA6B,UAAU;EACzC,OAAO;GACL,0BAA0B,UAAU;GACpC,6BAA6B,UAAU;EACzC;EAEA,qBAAqB,UAAU;CACjC;CAIA,IAAI,CAAC,kBAAkB,qBAAqB,YAAY,eAAe;EACrE,MAAM,kBAAkB,KAAK,IAAI,iBAAiB,0BAA0B,OAAO;EACnF,MAAM,qBAAqB,qBAAqB,UAC5C,KAAK,IAAI,6BAA6B,UAAU,kBAAkB,gBAAgB,CAAC,IACnF,KAAK,IAAI,6BAA6B,UAAU,kBAAkB,gBAAgB,CAAC;EAEvF,qBAAqB,UAAU;EAC/B,0BAA0B,UAAU;EACpC,6BAA6B,UAAU;CACzC;CAEA,IAAI;CAEJ,IAAI,gBACF,iBAAiB;MACZ;EACL,MAAM,cAAc,KAAK,IAAI,iBAAiB,0BAA0B,OAAO;EAE/E,IAAI,eACF,iBAAiB,KAAK,IACpB,6BAA6B,UAAU,cAAc,gBACrD,CACF;OAEA,iBAAiB,KAAK,IACpB,6BAA6B,UAAU,cAAc,gBACrD,CACF;CAEJ;CAEA,OAAO;EAAE,QAAQ,iBAAiB;EAAG;CAAe;AACtD"}