@mantine/hooks
Version:
A collection of 50+ hooks for state and UI management
1 lines • 7.57 kB
Source Map (JSON)
{"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"}