{"version":3,"file":"use-scroll-into-view.mjs","names":[],"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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"}