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.

244 lines (236 loc) 8.6 kB
"use strict"; 'use client'; Object.defineProperty(exports, "__esModule", { value: true }); exports.useSliderControl = useSliderControl; var React = _interopRequireWildcard(require("react")); var _mergeReactProps = require("../../utils/mergeReactProps"); var _owner = require("../../utils/owner"); var _useForkRef = require("../../utils/useForkRef"); var _useEventCallback = require("../../utils/useEventCallback"); var _useSliderRoot = require("../root/useSliderRoot"); var _useFieldControlValidation = require("../../field/control/useFieldControlValidation"); 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; } const INTENTIONAL_DRAG_COUNT_THRESHOLD = 2; function useSliderControl(parameters) { const { areValuesEqual, disabled, dragging, getFingerNewValue, handleValueChange, onValueCommitted, minStepsBetweenValues, percentageValues, registerSliderControl, rootRef: externalRef, setActive, setDragging, setValueState, step, thumbRefs } = parameters; const { commitValidation } = (0, _useFieldControlValidation.useFieldControlValidation)(); const controlRef = React.useRef(null); const handleRootRef = (0, _useForkRef.useForkRef)(externalRef, registerSliderControl, controlRef); // A number that uniquely identifies the current finger in the touch session. const touchIdRef = React.useRef(null); const moveCountRef = React.useRef(0); // offset distance between: // 1. pointerDown coordinates and // 2. the exact intersection of the center of the thumb and the track const offsetRef = React.useRef(0); const handleTouchMove = (0, _useEventCallback.useEventCallback)(nativeEvent => { const finger = (0, _useSliderRoot.trackFinger)(nativeEvent, touchIdRef); if (!finger) { return; } moveCountRef.current += 1; // Cancel move in case some other element consumed a pointerup event and it was not fired. // @ts-ignore buttons doesn't not exists on touch event if (nativeEvent.type === 'pointermove' && nativeEvent.buttons === 0) { // eslint-disable-next-line @typescript-eslint/no-use-before-define handleTouchEnd(nativeEvent); return; } const newFingerValue = getFingerNewValue({ finger, move: true, offset: offsetRef.current }); if (!newFingerValue) { return; } const { newValue, activeIndex } = newFingerValue; (0, _useSliderRoot.focusThumb)({ sliderRef: controlRef, activeIndex, setActive }); if ((0, _useSliderRoot.validateMinimumDistance)(newValue, step, minStepsBetweenValues)) { setValueState(newValue); if (!dragging && moveCountRef.current > INTENTIONAL_DRAG_COUNT_THRESHOLD) { setDragging(true); } if (handleValueChange && !areValuesEqual(newValue)) { handleValueChange(newValue, activeIndex, nativeEvent); } } }); const handleTouchEnd = (0, _useEventCallback.useEventCallback)(nativeEvent => { const finger = (0, _useSliderRoot.trackFinger)(nativeEvent, touchIdRef); setDragging(false); if (!finger) { return; } const newFingerValue = getFingerNewValue({ finger, move: true }); if (!newFingerValue) { return; } setActive(-1); commitValidation(newFingerValue.newValue); if (onValueCommitted) { onValueCommitted(newFingerValue.newValue, nativeEvent); } touchIdRef.current = null; // eslint-disable-next-line @typescript-eslint/no-use-before-define stopListening(); }); const handleTouchStart = (0, _useEventCallback.useEventCallback)(nativeEvent => { if (disabled) { return; } const touch = nativeEvent.changedTouches[0]; if (touch != null) { touchIdRef.current = touch.identifier; } const finger = (0, _useSliderRoot.trackFinger)(nativeEvent, touchIdRef); if (finger !== false) { const newFingerValue = getFingerNewValue({ finger }); if (!newFingerValue) { return; } const { newValue, activeIndex } = newFingerValue; (0, _useSliderRoot.focusThumb)({ sliderRef: controlRef, activeIndex, setActive }); setValueState(newValue); if (handleValueChange && !areValuesEqual(newValue)) { handleValueChange(newValue, activeIndex, nativeEvent); } } moveCountRef.current = 0; const doc = (0, _owner.ownerDocument)(controlRef.current); doc.addEventListener('touchmove', handleTouchMove, { passive: true }); doc.addEventListener('touchend', handleTouchEnd, { passive: true }); }); const stopListening = (0, _useEventCallback.useEventCallback)(() => { offsetRef.current = 0; const doc = (0, _owner.ownerDocument)(controlRef.current); doc.removeEventListener('pointermove', handleTouchMove); doc.removeEventListener('pointerup', handleTouchEnd); doc.removeEventListener('touchmove', handleTouchMove); doc.removeEventListener('touchend', handleTouchEnd); }); React.useEffect(() => { const { current: sliderControl } = controlRef; if (!sliderControl) { return () => stopListening(); } sliderControl.addEventListener('touchstart', handleTouchStart, { passive: true }); return () => { sliderControl.removeEventListener('touchstart', handleTouchStart); stopListening(); }; }, [stopListening, handleTouchStart, controlRef]); React.useEffect(() => { if (disabled) { stopListening(); } }, [disabled, stopListening]); const getRootProps = React.useCallback((externalProps = {}) => { return (0, _mergeReactProps.mergeReactProps)(externalProps, { onPointerDown(event) { if (disabled) { return; } if (event.defaultPrevented) { return; } // Only handle left clicks if (event.button !== 0) { return; } // Avoid text selection event.preventDefault(); const finger = (0, _useSliderRoot.trackFinger)(event, touchIdRef); if (finger !== false) { const newFingerValue = getFingerNewValue({ finger }); if (!newFingerValue) { return; } const { newValue, activeIndex, newPercentageValue } = newFingerValue; (0, _useSliderRoot.focusThumb)({ sliderRef: controlRef, activeIndex, setActive }); // if the event lands on a thumb, don't change the value, just get the // percentageValue difference represented by the distance between the click origin // and the coordinates of the value on the track area if (thumbRefs.current.includes(event.target)) { const targetThumbIndex = event.target.getAttribute('data-index'); const offset = percentageValues[Number(targetThumbIndex)] / 100 - newPercentageValue; offsetRef.current = offset; } else { setValueState(newValue); if (handleValueChange && !areValuesEqual(newValue)) { handleValueChange(newValue, activeIndex, event); } } } moveCountRef.current = 0; const doc = (0, _owner.ownerDocument)(controlRef.current); doc.addEventListener('pointermove', handleTouchMove, { passive: true }); doc.addEventListener('pointerup', handleTouchEnd); }, ref: handleRootRef }); }, [areValuesEqual, disabled, getFingerNewValue, handleRootRef, handleTouchMove, handleTouchEnd, handleValueChange, percentageValues, setActive, setValueState, thumbRefs]); return React.useMemo(() => ({ getRootProps }), [getRootProps]); }