@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
JavaScript
;
'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]);
}