UNPKG

@base-ui-components/react

Version:

Base UI is a library of headless ('unstyled') React components and low-level hooks. You gain complete control over your app's CSS and accessibility features.

143 lines (140 loc) 6.78 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.useScrollAreaScrollbar = useScrollAreaScrollbar; var React = _interopRequireWildcard(require("react")); var _ScrollAreaRootContext = require("../root/ScrollAreaRootContext"); var _mergeReactProps = require("../../utils/mergeReactProps"); var _getOffset = require("../utils/getOffset"); var _ScrollAreaRootCssVars = require("../root/ScrollAreaRootCssVars"); var _ScrollAreaScrollbarCssVars = require("./ScrollAreaScrollbarCssVars"); function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } function useScrollAreaScrollbar(params) { const { orientation } = params; const { dir, scrollbarYRef, scrollbarXRef, viewportRef, thumbYRef, thumbXRef, handlePointerDown, handlePointerUp, rootId, thumbSize } = (0, _ScrollAreaRootContext.useScrollAreaRootContext)(); React.useEffect(() => { const viewportEl = viewportRef.current; const scrollbarEl = orientation === 'vertical' ? scrollbarYRef.current : scrollbarXRef.current; if (!scrollbarEl) { return undefined; } function handleWheel(event) { if (!viewportEl || !scrollbarEl || event.ctrlKey) { return; } event.preventDefault(); if (orientation === 'vertical') { if (viewportEl.scrollTop === 0 && event.deltaY < 0) { return; } } else if (viewportEl.scrollLeft === 0 && event.deltaX < 0) { return; } if (orientation === 'vertical') { if (viewportEl.scrollTop === viewportEl.scrollHeight - viewportEl.clientHeight && event.deltaY > 0) { return; } } else if (viewportEl.scrollLeft === viewportEl.scrollWidth - viewportEl.clientWidth && event.deltaX > 0) { return; } if (orientation === 'vertical') { viewportEl.scrollTop += event.deltaY; } else { viewportEl.scrollLeft += event.deltaX; } } scrollbarEl.addEventListener('wheel', handleWheel, { passive: false }); return () => { scrollbarEl.removeEventListener('wheel', handleWheel); }; }, [orientation, scrollbarXRef, scrollbarYRef, viewportRef]); const getScrollbarProps = React.useCallback((externalProps = {}) => (0, _mergeReactProps.mergeReactProps)(externalProps, { ...(rootId && { 'data-id': `${rootId}-scrollbar` }), onPointerDown(event) { // Ignore clicks on thumb if (event.currentTarget !== event.target) { return; } if (!viewportRef.current) { return; } // Handle Y-axis (vertical) scroll if (thumbYRef.current && scrollbarYRef.current && orientation === 'vertical') { const thumbYOffset = (0, _getOffset.getOffset)(thumbYRef.current, 'margin', 'y'); const scrollbarYOffset = (0, _getOffset.getOffset)(scrollbarYRef.current, 'padding', 'y'); const thumbHeight = thumbYRef.current.offsetHeight; const trackRectY = scrollbarYRef.current.getBoundingClientRect(); const clickY = event.clientY - trackRectY.top - thumbHeight / 2 - scrollbarYOffset + thumbYOffset / 2; const scrollableContentHeight = viewportRef.current.scrollHeight; const viewportHeight = viewportRef.current.clientHeight; const maxThumbOffsetY = scrollbarYRef.current.offsetHeight - thumbHeight - scrollbarYOffset - thumbYOffset; const scrollRatioY = clickY / maxThumbOffsetY; const newScrollTop = scrollRatioY * (scrollableContentHeight - viewportHeight); viewportRef.current.scrollTop = newScrollTop; } if (thumbXRef.current && scrollbarXRef.current && orientation === 'horizontal') { const thumbXOffset = (0, _getOffset.getOffset)(thumbXRef.current, 'margin', 'x'); const scrollbarXOffset = (0, _getOffset.getOffset)(scrollbarXRef.current, 'padding', 'x'); const thumbWidth = thumbXRef.current.offsetWidth; const trackRectX = scrollbarXRef.current.getBoundingClientRect(); const clickX = event.clientX - trackRectX.left - thumbWidth / 2 - scrollbarXOffset + thumbXOffset / 2; const scrollableContentWidth = viewportRef.current.scrollWidth; const viewportWidth = viewportRef.current.clientWidth; const maxThumbOffsetX = scrollbarXRef.current.offsetWidth - thumbWidth - scrollbarXOffset - thumbXOffset; const scrollRatioX = clickX / maxThumbOffsetX; let newScrollLeft; if (dir === 'rtl') { // In RTL, invert the scroll direction newScrollLeft = (1 - scrollRatioX) * (scrollableContentWidth - viewportWidth); // Adjust for browsers that use negative scrollLeft in RTL if (viewportRef.current.scrollLeft <= 0) { newScrollLeft = -newScrollLeft; } } else { newScrollLeft = scrollRatioX * (scrollableContentWidth - viewportWidth); } viewportRef.current.scrollLeft = newScrollLeft; } handlePointerDown(event); }, onPointerUp: handlePointerUp, style: { position: 'absolute', touchAction: 'none', ...(orientation === 'vertical' && { top: 0, bottom: `var(${_ScrollAreaRootCssVars.ScrollAreaRootCssVars.scrollAreaCornerHeight})`, [dir === 'rtl' ? 'left' : 'right']: 0, [_ScrollAreaScrollbarCssVars.ScrollAreaScrollbarCssVars.scrollAreaThumbHeight]: `${thumbSize.height}px` }), ...(orientation === 'horizontal' && { [dir === 'rtl' ? 'right' : 'left']: 0, [dir === 'rtl' ? 'left' : 'right']: `var(${_ScrollAreaRootCssVars.ScrollAreaRootCssVars.scrollAreaCornerWidth})`, bottom: 0, [_ScrollAreaScrollbarCssVars.ScrollAreaScrollbarCssVars.scrollAreaThumbWidth]: `${thumbSize.width}px` }) } }), [rootId, handlePointerUp, orientation, dir, thumbSize.height, thumbSize.width, viewportRef, thumbYRef, scrollbarYRef, thumbXRef, scrollbarXRef, handlePointerDown]); return React.useMemo(() => ({ getScrollbarProps }), [getScrollbarProps]); }