UNPKG

@mantine/hooks

Version:

A collection of 50+ hooks for state and UI management

1 lines 11.1 kB
{"version":3,"file":"use-scroll-into-view.cjs","names":["useReducedMotion"],"sources":["../../src/use-scroll-into-view/use-scroll-into-view.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\nimport { useReducedMotion } from '../use-reduced-motion/use-reduced-motion';\nimport { useWindowEvent } from '../use-window-event/use-window-event';\n\ninterface UseScrollIntoViewAnimation {\n /** Target element alignment relatively to parent based on current axis */\n alignment?: 'start' | 'end' | 'center';\n}\n\nexport interface UseScrollIntoViewOptions {\n /** Callback fired after scroll */\n onScrollFinish?: () => void;\n\n /** Callback fired when scroll animation is canceled by user interaction */\n onScrollCancel?: () => void;\n\n /** Duration of scroll in milliseconds */\n duration?: number;\n\n /** Axis of scroll */\n axis?: 'x' | 'y';\n\n /** Custom mathematical easing function */\n easing?: (t: number) => number;\n\n /** Additional distance between nearest edge and element */\n offset?: number;\n\n /** Indicator if animation may be interrupted by user scrolling */\n cancelable?: boolean;\n\n /** Prevents content jumping in scrolling lists with multiple targets */\n isList?: boolean;\n}\n\nexport interface UseScrollIntoViewReturnValue<\n Target extends HTMLElement = any,\n Parent extends HTMLElement | null = null,\n> {\n scrollableRef: React.RefObject<Parent | null>;\n targetRef: React.RefObject<Target | null>;\n scrollIntoView: (params?: UseScrollIntoViewAnimation) => void;\n cancel: () => void;\n scrolling: boolean;\n}\n\nexport function useScrollIntoView<\n Target extends HTMLElement = any,\n Parent extends HTMLElement | null = null,\n>({\n duration = 1250,\n axis = 'y',\n onScrollFinish,\n onScrollCancel,\n easing = easeInOutQuad,\n offset = 0,\n cancelable = true,\n isList = false,\n}: UseScrollIntoViewOptions = {}): UseScrollIntoViewReturnValue<Target, Parent> {\n const frameID = useRef(0);\n const startTime = useRef(0);\n const shouldStop = useRef(false);\n const [scrolling, setScrolling] = useState(false);\n\n const scrollableRef = useRef<Parent | null>(null);\n const targetRef = useRef<Target | null>(null);\n\n const reducedMotion = useReducedMotion();\n\n const cancel = (): void => {\n if (frameID.current) {\n cancelAnimationFrame(frameID.current);\n frameID.current = 0;\n setScrolling(false);\n }\n };\n\n const scrollIntoView = useCallback(\n ({ alignment = 'start' }: UseScrollIntoViewAnimation = {}) => {\n shouldStop.current = false;\n\n if (frameID.current) {\n cancel();\n }\n\n const start = getScrollStart({ parent: scrollableRef.current, axis }) ?? 0;\n\n const change =\n getRelativePosition({\n parent: scrollableRef.current,\n target: targetRef.current,\n axis,\n alignment,\n offset,\n isList,\n }) - (scrollableRef.current ? 0 : start);\n\n setScrolling(true);\n\n function animateScroll() {\n if (startTime.current === 0) {\n startTime.current = performance.now();\n }\n\n const now = performance.now();\n const elapsed = now - startTime.current;\n\n // Easing timing progress\n const t = reducedMotion || duration === 0 ? 1 : elapsed / duration;\n\n const distance = start + change * easing(t);\n\n setScrollParam({\n parent: scrollableRef.current,\n axis,\n distance,\n });\n\n if (!shouldStop.current && t < 1) {\n frameID.current = requestAnimationFrame(animateScroll);\n } else {\n if (shouldStop.current) {\n typeof onScrollCancel === 'function' && onScrollCancel();\n } else {\n typeof onScrollFinish === 'function' && onScrollFinish();\n }\n startTime.current = 0;\n frameID.current = 0;\n setScrolling(false);\n cancel();\n }\n }\n animateScroll();\n },\n [axis, duration, easing, isList, offset, onScrollFinish, onScrollCancel, reducedMotion]\n );\n\n const handleStop = () => {\n if (cancelable) {\n shouldStop.current = true;\n }\n };\n\n /**\n * Detection of one of these events stops scroll animation\n * wheel - mouse wheel / touch pad\n * touchmove - any touchable device\n */\n\n useWindowEvent('wheel', handleStop, {\n passive: true,\n });\n\n useWindowEvent('touchmove', handleStop, {\n passive: true,\n });\n\n // Cleanup requestAnimationFrame\n useEffect(() => cancel, []);\n\n return {\n scrollableRef,\n targetRef,\n scrollIntoView,\n cancel,\n scrolling,\n };\n}\n\n// ---------------------------------------------------\n// Helpers\n// ---------------------------------------------------\n\nfunction easeInOutQuad(t: number) {\n return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;\n}\n\nfunction getRelativePosition({ axis, target, parent, alignment, offset, isList }: any): number {\n if (!target || (!parent && typeof document === 'undefined')) {\n return 0;\n }\n const isCustomParent = !!parent;\n const parentElement = parent || document.body;\n const parentPosition = parentElement.getBoundingClientRect();\n const targetPosition = target.getBoundingClientRect();\n\n const getDiff = (property: 'top' | 'left'): number =>\n targetPosition[property] - parentPosition[property];\n\n if (axis === 'y') {\n const diff = getDiff('top');\n\n if (diff === 0) {\n return 0;\n }\n\n if (alignment === 'start') {\n const distance = diff - offset;\n const shouldScroll = distance <= targetPosition.height * (isList ? 0 : 1) || !isList;\n\n return shouldScroll ? distance : 0;\n }\n\n const parentHeight = isCustomParent ? parentPosition.height : window.innerHeight;\n\n if (alignment === 'end') {\n const distance = diff + offset - parentHeight + targetPosition.height;\n const shouldScroll = distance >= -targetPosition.height * (isList ? 0 : 1) || !isList;\n\n return shouldScroll ? distance : 0;\n }\n\n if (alignment === 'center') {\n return diff - parentHeight / 2 + targetPosition.height / 2;\n }\n\n return 0;\n }\n\n if (axis === 'x') {\n const diff = getDiff('left');\n\n if (diff === 0) {\n return 0;\n }\n\n if (alignment === 'start') {\n const distance = diff - offset;\n const shouldScroll = distance <= targetPosition.width || !isList;\n\n return shouldScroll ? distance : 0;\n }\n\n const parentWidth = isCustomParent ? parentPosition.width : window.innerWidth;\n\n if (alignment === 'end') {\n const distance = diff + offset - parentWidth + targetPosition.width;\n const shouldScroll = distance >= -targetPosition.width || !isList;\n\n return shouldScroll ? distance : 0;\n }\n\n if (alignment === 'center') {\n return diff - parentWidth / 2 + targetPosition.width / 2;\n }\n\n return 0;\n }\n\n return 0;\n}\n\nfunction getScrollStart({ axis, parent }: any) {\n if (!parent && typeof document === 'undefined') {\n return 0;\n }\n\n const method = axis === 'y' ? 'scrollTop' : 'scrollLeft';\n\n if (parent) {\n return parent[method];\n }\n\n const { body, documentElement } = document;\n\n // While one of it has a value the second is equal 0\n return body[method] + documentElement[method];\n}\n\nfunction setScrollParam({ axis, parent, distance }: any) {\n if (!parent && typeof document === 'undefined') {\n return;\n }\n\n const method = axis === 'y' ? 'scrollTop' : 'scrollLeft';\n\n if (parent) {\n parent[method] = distance;\n } else {\n const { body, documentElement } = document;\n body[method] = distance;\n documentElement[method] = distance;\n }\n}\n\nexport namespace useScrollIntoView {\n export type Options = UseScrollIntoViewOptions;\n export type ReturnValue<\n Target extends HTMLElement,\n Parent extends HTMLElement | null,\n > = UseScrollIntoViewReturnValue<Target, Parent>;\n}\n"],"mappings":"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"}