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