{"version":3,"file":"use-scroller.mjs","names":[],"sources":["../../src/use-scroller/use-scroller.ts"],"sourcesContent":["import { RefCallback, useCallback, useEffect, useRef, useState } from 'react';\n\nexport interface UseScrollerOptions {\n  /** Amount of pixels to scroll when calling scroll functions, `200` by default */\n  scrollAmount?: number;\n\n  /** Determines whether content can be scrolled by dragging with mouse, `true` by default */\n  draggable?: boolean;\n\n  /** Called when scroll state changes (canScrollStart or canScrollEnd) */\n  onScrollStateChange?: (state: UseScrollerScrollState) => void;\n}\n\nexport interface UseScrollerScrollState {\n  /** Whether content can be scrolled towards the start (left in LTR, right in RTL) */\n  canScrollStart: boolean;\n\n  /** Whether content can be scrolled towards the end (right in LTR, left in RTL) */\n  canScrollEnd: boolean;\n}\n\nexport interface UseScrollerReturnValue<T extends HTMLElement = HTMLDivElement> {\n  /** Ref callback to attach to the scrollable container element */\n  ref: RefCallback<T | null>;\n\n  /** Whether content can be scrolled towards the start */\n  canScrollStart: boolean;\n\n  /** Whether content can be scrolled towards the end */\n  canScrollEnd: boolean;\n\n  /** Scrolls towards the start direction */\n  scrollStart: () => void;\n\n  /** Scrolls towards the end direction */\n  scrollEnd: () => void;\n\n  /** `true` if the user is currently dragging the content */\n  isDragging: boolean;\n\n  /** Props to spread on the scrollable container for drag functionality */\n  dragHandlers: {\n    onMouseDown: (e: React.MouseEvent) => void;\n    onMouseMove: (e: React.MouseEvent) => void;\n    onMouseUp: () => void;\n    onMouseLeave: () => void;\n  };\n}\n\nexport function useScroller<T extends HTMLElement = HTMLDivElement>(\n  options: UseScrollerOptions = {}\n): UseScrollerReturnValue<T> {\n  const { scrollAmount = 200, draggable = true, onScrollStateChange } = options;\n\n  const containerRef = useRef<T | null>(null);\n\n  const [canScrollStart, setCanScrollStart] = useState(false);\n  const [canScrollEnd, setCanScrollEnd] = useState(false);\n  const [isDragging, setIsDragging] = useState(false);\n\n  const isDraggingRef = useRef(false);\n  const hasDraggedRef = useRef(false);\n  const startX = useRef(0);\n  const scrollLeftStart = useRef(0);\n\n  const onScrollStateChangeRef = useRef(onScrollStateChange);\n  onScrollStateChangeRef.current = onScrollStateChange;\n\n  const updateScrollState = useCallback(() => {\n    const container = containerRef.current;\n    if (container) {\n      const { scrollLeft, scrollWidth, clientWidth } = container;\n      const isRtl = getComputedStyle(container).direction === 'rtl';\n\n      let newCanScrollStart: boolean;\n      let newCanScrollEnd: boolean;\n\n      if (isRtl) {\n        newCanScrollStart = scrollLeft < -1;\n        newCanScrollEnd = scrollLeft > -(scrollWidth - clientWidth) + 1;\n      } else {\n        newCanScrollStart = scrollLeft > 1;\n        newCanScrollEnd = scrollLeft < scrollWidth - clientWidth - 1;\n      }\n\n      setCanScrollStart(newCanScrollStart);\n      setCanScrollEnd(newCanScrollEnd);\n\n      onScrollStateChangeRef.current?.({\n        canScrollStart: newCanScrollStart,\n        canScrollEnd: newCanScrollEnd,\n      });\n    }\n  }, []);\n\n  useEffect(() => {\n    updateScrollState();\n    const container = containerRef.current;\n    if (container) {\n      container.addEventListener('scroll', updateScrollState);\n      const resizeObserver = new ResizeObserver(updateScrollState);\n      resizeObserver.observe(container);\n      return () => {\n        container.removeEventListener('scroll', updateScrollState);\n        resizeObserver.disconnect();\n      };\n    }\n    return undefined;\n  }, [updateScrollState]);\n\n  const scroll = useCallback(\n    (direction: 'start' | 'end') => {\n      const container = containerRef.current;\n      if (container) {\n        const isRtl = getComputedStyle(container).direction === 'rtl';\n        const amount = scrollAmount;\n        const scrollBy = direction === 'end' ? amount : -amount;\n        const adjustedScrollBy = isRtl ? -scrollBy : scrollBy;\n\n        container.scrollBy({\n          left: adjustedScrollBy,\n          behavior: 'smooth',\n        });\n      }\n    },\n    [scrollAmount]\n  );\n\n  const scrollStart = useCallback(() => scroll('start'), [scroll]);\n  const scrollEnd = useCallback(() => scroll('end'), [scroll]);\n\n  const handleMouseDown = useCallback(\n    (event: React.MouseEvent) => {\n      if (!draggable) {\n        return;\n      }\n      const container = containerRef.current;\n      if (container) {\n        isDraggingRef.current = true;\n        hasDraggedRef.current = false;\n        setIsDragging(true);\n        startX.current = event.pageX - container.offsetLeft;\n        scrollLeftStart.current = container.scrollLeft;\n        container.style.cursor = 'grabbing';\n        container.style.userSelect = 'none';\n      }\n    },\n    [draggable]\n  );\n\n  const handleMouseMove = useCallback((event: React.MouseEvent) => {\n    if (!isDraggingRef.current) {\n      return;\n    }\n    event.preventDefault();\n    const container = containerRef.current;\n    if (container) {\n      const x = event.pageX - container.offsetLeft;\n      const walk = x - startX.current;\n      if (Math.abs(walk) > 5) {\n        hasDraggedRef.current = true;\n      }\n      container.scrollLeft = scrollLeftStart.current - walk;\n    }\n  }, []);\n\n  const handleMouseUp = useCallback(() => {\n    const wasDragged = hasDraggedRef.current;\n    isDraggingRef.current = false;\n    hasDraggedRef.current = false;\n    setIsDragging(false);\n    const container = containerRef.current;\n    if (container) {\n      container.style.cursor = '';\n      container.style.userSelect = '';\n\n      if (wasDragged) {\n        const suppressClick = (event: MouseEvent) => {\n          event.stopPropagation();\n          event.preventDefault();\n          container.removeEventListener('click', suppressClick, true);\n        };\n        container.addEventListener('click', suppressClick, true);\n      }\n    }\n  }, []);\n\n  const handleMouseLeave = useCallback(() => {\n    if (isDraggingRef.current) {\n      handleMouseUp();\n    }\n  }, [handleMouseUp]);\n\n  const assignRef: RefCallback<T | null> = useCallback(\n    (node) => {\n      containerRef.current = node;\n      if (node) {\n        updateScrollState();\n      }\n    },\n    [updateScrollState]\n  );\n\n  return {\n    ref: assignRef,\n    canScrollStart,\n    canScrollEnd,\n    scrollStart,\n    scrollEnd,\n    isDragging,\n    dragHandlers: {\n      onMouseDown: handleMouseDown,\n      onMouseMove: handleMouseMove,\n      onMouseUp: handleMouseUp,\n      onMouseLeave: handleMouseLeave,\n    },\n  };\n}\n\nexport namespace useScroller {\n  export type Options = UseScrollerOptions;\n  export type ReturnValue<T extends HTMLElement = HTMLDivElement> = UseScrollerReturnValue<T>;\n  export type ScrollState = UseScrollerScrollState;\n}\n"],"mappings":"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"}