{"version":3,"file":"use-scroller.cjs","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 < 0;\n        newCanScrollEnd = scrollLeft > -(scrollWidth - clientWidth);\n      } else {\n        newCanScrollStart = scrollLeft > 0;\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":";;;AAiDA,SAAgB,YACd,UAA8B,EAAE,EACL;CAC3B,MAAM,EAAE,eAAe,KAAK,YAAY,MAAM,wBAAwB;CAEtE,MAAM,gBAAA,GAAA,MAAA,QAAgC,KAAK;CAE3C,MAAM,CAAC,gBAAgB,sBAAA,GAAA,MAAA,UAA8B,MAAM;CAC3D,MAAM,CAAC,cAAc,oBAAA,GAAA,MAAA,UAA4B,MAAM;CACvD,MAAM,CAAC,YAAY,kBAAA,GAAA,MAAA,UAA0B,MAAM;CAEnD,MAAM,iBAAA,GAAA,MAAA,QAAuB,MAAM;CACnC,MAAM,iBAAA,GAAA,MAAA,QAAuB,MAAM;CACnC,MAAM,UAAA,GAAA,MAAA,QAAgB,EAAE;CACxB,MAAM,mBAAA,GAAA,MAAA,QAAyB,EAAE;CAEjC,MAAM,0BAAA,GAAA,MAAA,QAAgC,oBAAoB;AAC1D,wBAAuB,UAAU;CAEjC,MAAM,qBAAA,GAAA,MAAA,mBAAsC;EAC1C,MAAM,YAAY,aAAa;AAC/B,MAAI,WAAW;GACb,MAAM,EAAE,YAAY,aAAa,gBAAgB;GACjD,MAAM,QAAQ,iBAAiB,UAAU,CAAC,cAAc;GAExD,IAAI;GACJ,IAAI;AAEJ,OAAI,OAAO;AACT,wBAAoB,aAAa;AACjC,sBAAkB,aAAa,EAAE,cAAc;UAC1C;AACL,wBAAoB,aAAa;AACjC,sBAAkB,aAAa,cAAc,cAAc;;AAG7D,qBAAkB,kBAAkB;AACpC,mBAAgB,gBAAgB;AAEhC,0BAAuB,UAAU;IAC/B,gBAAgB;IAChB,cAAc;IACf,CAAC;;IAEH,EAAE,CAAC;AAEN,EAAA,GAAA,MAAA,iBAAgB;AACd,qBAAmB;EACnB,MAAM,YAAY,aAAa;AAC/B,MAAI,WAAW;AACb,aAAU,iBAAiB,UAAU,kBAAkB;GACvD,MAAM,iBAAiB,IAAI,eAAe,kBAAkB;AAC5D,kBAAe,QAAQ,UAAU;AACjC,gBAAa;AACX,cAAU,oBAAoB,UAAU,kBAAkB;AAC1D,mBAAe,YAAY;;;IAI9B,CAAC,kBAAkB,CAAC;CAEvB,MAAM,UAAA,GAAA,MAAA,cACH,cAA+B;EAC9B,MAAM,YAAY,aAAa;AAC/B,MAAI,WAAW;GACb,MAAM,QAAQ,iBAAiB,UAAU,CAAC,cAAc;GACxD,MAAM,SAAS;GACf,MAAM,WAAW,cAAc,QAAQ,SAAS,CAAC;GACjD,MAAM,mBAAmB,QAAQ,CAAC,WAAW;AAE7C,aAAU,SAAS;IACjB,MAAM;IACN,UAAU;IACX,CAAC;;IAGN,CAAC,aAAa,CACf;CAED,MAAM,eAAA,GAAA,MAAA,mBAAgC,OAAO,QAAQ,EAAE,CAAC,OAAO,CAAC;CAChE,MAAM,aAAA,GAAA,MAAA,mBAA8B,OAAO,MAAM,EAAE,CAAC,OAAO,CAAC;CAE5D,MAAM,mBAAA,GAAA,MAAA,cACH,UAA4B;AAC3B,MAAI,CAAC,UACH;EAEF,MAAM,YAAY,aAAa;AAC/B,MAAI,WAAW;AACb,iBAAc,UAAU;AACxB,iBAAc,UAAU;AACxB,iBAAc,KAAK;AACnB,UAAO,UAAU,MAAM,QAAQ,UAAU;AACzC,mBAAgB,UAAU,UAAU;AACpC,aAAU,MAAM,SAAS;AACzB,aAAU,MAAM,aAAa;;IAGjC,CAAC,UAAU,CACZ;CAED,MAAM,mBAAA,GAAA,MAAA,cAA+B,UAA4B;AAC/D,MAAI,CAAC,cAAc,QACjB;AAEF,QAAM,gBAAgB;EACtB,MAAM,YAAY,aAAa;AAC/B,MAAI,WAAW;GAEb,MAAM,OADI,MAAM,QAAQ,UAAU,aACjB,OAAO;AACxB,OAAI,KAAK,IAAI,KAAK,GAAG,EACnB,eAAc,UAAU;AAE1B,aAAU,aAAa,gBAAgB,UAAU;;IAElD,EAAE,CAAC;CAEN,MAAM,iBAAA,GAAA,MAAA,mBAAkC;EACtC,MAAM,aAAa,cAAc;AACjC,gBAAc,UAAU;AACxB,gBAAc,UAAU;AACxB,gBAAc,MAAM;EACpB,MAAM,YAAY,aAAa;AAC/B,MAAI,WAAW;AACb,aAAU,MAAM,SAAS;AACzB,aAAU,MAAM,aAAa;AAE7B,OAAI,YAAY;IACd,MAAM,iBAAiB,UAAsB;AAC3C,WAAM,iBAAiB;AACvB,WAAM,gBAAgB;AACtB,eAAU,oBAAoB,SAAS,eAAe,KAAK;;AAE7D,cAAU,iBAAiB,SAAS,eAAe,KAAK;;;IAG3D,EAAE,CAAC;CAEN,MAAM,oBAAA,GAAA,MAAA,mBAAqC;AACzC,MAAI,cAAc,QAChB,gBAAe;IAEhB,CAAC,cAAc,CAAC;AAYnB,QAAO;EACL,MAAA,GAAA,MAAA,cAVC,SAAS;AACR,gBAAa,UAAU;AACvB,OAAI,KACF,oBAAmB;KAGvB,CAAC,kBAAkB,CACpB;EAIC;EACA;EACA;EACA;EACA;EACA,cAAc;GACZ,aAAa;GACb,aAAa;GACb,WAAW;GACX,cAAc;GACf;EACF"}