{"version":3,"file":"use-headroom.cjs","names":["useScrollDirection","useWindowScroll"],"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  useIsomorphicEffect(() => {\n    if (isFixed(scrollPosition, fixedAt)) {\n      onFixEvent();\n    }\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,QAAQ;AACnD,KAAI,qBAAqB,CAAC,qBAAqB,SAAS;AACtD,uBAAqB,UAAU;AAC/B,WAAS;YACA,CAAC,qBAAqB,iBAAiB,CAAC,qBAAqB,SAAS;AAC/E,uBAAqB,UAAU;AAC/B,WAAS;YACA,CAAC,qBAAqB,CAAC,iBAAiB,qBAAqB,SAAS;AAC/E,uBAAqB,UAAU;AAC/B,eAAa;;;AA6BjB,SAAgB,YAAY,EAC1B,UAAU,GACV,iBAAiB,KACjB,OACA,OACA,cACoB,EAAE,EAA0B;CAChD,MAAM,wBAAA,GAAA,MAAA,QAA8B,MAAM;CAE1C,MAAM,gBADkBA,6BAAAA,oBAAoB,KACF;CAC1C,MAAM,CAAC,EAAE,GAAG,oBAAoBC,0BAAAA,iBAAiB;CAEjD,MAAM,cAAA,GAAA,MAAA,sBAAkC,SAAS,CAAC;CAClD,MAAM,kBAAA,GAAA,MAAA,sBAAsC,aAAa,CAAC;CAC1D,MAAM,cAAA,GAAA,MAAA,sBAAkC,SAAS,CAAC;AAElD,+BAAA,0BAA0B;AACxB,qBACE,gBACA,SACA,sBACA,eACA,YACA,eACD;IACA;EAAC;EAAgB;EAAS;EAAc,CAAC;AAE5C,+BAAA,0BAA0B;AACxB,MAAI,QAAQ,gBAAgB,QAAQ,CAClC,aAAY;IAEb,CAAC,gBAAgB,QAAQ,CAAC;CAG7B,MAAM,iBAAiB,QAAQ,gBAAgB,QAAQ;CACvD,MAAM,kBAAA,GAAA,MAAA,QAAwB,eAAe;CAC7C,MAAM,6BAAA,GAAA,MAAA,QAAmC,eAAe;CACxD,MAAM,gCAAA,GAAA,MAAA,QAAsC,iBAAiB,IAAI,EAAE;CACnE,MAAM,wBAAA,GAAA,MAAA,QAA8B,cAAc;AAMlD,KAAI,eAAe,YAAY,gBAAgB;AAC7C,iBAAe,UAAU;AAEzB,MAAI,CAAC,gBAAgB;AACnB,6BAA0B,UAAU;AACpC,gCAA6B,UAAU;SAClC;AACL,6BAA0B,UAAU;AACpC,gCAA6B,UAAU;;AAGzC,uBAAqB,UAAU;;AAKjC,KAAI,CAAC,kBAAkB,qBAAqB,YAAY,eAAe;EACrE,MAAM,kBAAkB,KAAK,IAAI,iBAAiB,0BAA0B,QAAQ;EACpF,MAAM,qBAAqB,qBAAqB,UAC5C,KAAK,IAAI,6BAA6B,UAAU,kBAAkB,gBAAgB,EAAE,GACpF,KAAK,IAAI,6BAA6B,UAAU,kBAAkB,gBAAgB,EAAE;AAExF,uBAAqB,UAAU;AAC/B,4BAA0B,UAAU;AACpC,+BAA6B,UAAU;;CAGzC,IAAI;AAEJ,KAAI,eACF,kBAAiB;MACZ;EACL,MAAM,cAAc,KAAK,IAAI,iBAAiB,0BAA0B,QAAQ;AAEhF,MAAI,cACF,kBAAiB,KAAK,IACpB,6BAA6B,UAAU,cAAc,gBACrD,EACD;MAED,kBAAiB,KAAK,IACpB,6BAA6B,UAAU,cAAc,gBACrD,EACD;;AAIL,QAAO;EAAE,QAAQ,iBAAiB;EAAG;EAAgB"}