diff --git a/packages/react/src/slider/control/SliderControl.test.tsx b/packages/react/src/slider/control/SliderControl.test.tsx index 07d4982407..9eef837772 100644 --- a/packages/react/src/slider/control/SliderControl.test.tsx +++ b/packages/react/src/slider/control/SliderControl.test.tsx @@ -12,7 +12,7 @@ const testRootContext: SliderRootContext = { disabled: false, getFingerState: () => ({ value: 0, - percentageValue: 0, + valueRescaled: 0, percentageValues: [0], thumbIndex: 0, }), diff --git a/packages/react/src/slider/control/useSliderControl.ts b/packages/react/src/slider/control/useSliderControl.ts index fc865654c5..ef6fd13269 100644 --- a/packages/react/src/slider/control/useSliderControl.ts +++ b/packages/react/src/slider/control/useSliderControl.ts @@ -7,14 +7,41 @@ import { useForkRef } from '../../utils/useForkRef'; import { useEventCallback } from '../../utils/useEventCallback'; import { focusThumb, - trackFinger, validateMinimumDistance, + type FingerPosition, type useSliderRoot, } from '../root/useSliderRoot'; import { useFieldControlValidation } from '../../field/control/useFieldControlValidation'; const INTENTIONAL_DRAG_COUNT_THRESHOLD = 2; +function trackFinger( + event: TouchEvent | PointerEvent | React.PointerEvent, + touchIdRef: React.RefObject, +): FingerPosition | null { + // The event is TouchEvent + if (touchIdRef.current !== undefined && (event as TouchEvent).changedTouches) { + const touchEvent = event as TouchEvent; + for (let i = 0; i < touchEvent.changedTouches.length; i += 1) { + const touch = touchEvent.changedTouches[i]; + if (touch.identifier === touchIdRef.current) { + return { + x: touch.clientX, + y: touch.clientY, + }; + } + } + + return null; + } + + // The event is PointerEvent + return { + x: (event as PointerEvent).clientX, + y: (event as PointerEvent).clientY, + }; +} + export function useSliderControl( parameters: useSliderControl.Parameters, ): useSliderControl.ReturnValue { @@ -42,12 +69,11 @@ export function useSliderControl( // 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 + /** + * The difference between the value at the finger origin and the value at + * the center of the thumb scaled down to fit the range [0, 1] + */ const offsetRef = React.useRef(0); const handleTouchMove = useEventCallback((nativeEvent: TouchEvent | PointerEvent) => { @@ -209,8 +235,7 @@ export function useSliderControl( // 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 as HTMLElement)) { - offsetRef.current = - percentageValues[finger.thumbIndex] / 100 - finger.percentageValue; + offsetRef.current = percentageValues[finger.thumbIndex] / 100 - finger.valueRescaled; } else { setValue(finger.value, finger.percentageValues, finger.thumbIndex, event.nativeEvent); } diff --git a/packages/react/src/slider/indicator/SliderIndicator.test.tsx b/packages/react/src/slider/indicator/SliderIndicator.test.tsx index 49ac011d9c..63114597bb 100644 --- a/packages/react/src/slider/indicator/SliderIndicator.test.tsx +++ b/packages/react/src/slider/indicator/SliderIndicator.test.tsx @@ -12,7 +12,7 @@ const testRootContext: SliderRootContext = { disabled: false, getFingerState: () => ({ value: 0, - percentageValue: 0, + valueRescaled: 0, percentageValues: [0], thumbIndex: 0, }), diff --git a/packages/react/src/slider/root/useSliderRoot.ts b/packages/react/src/slider/root/useSliderRoot.ts index cfe3f6960d..7da886c635 100644 --- a/packages/react/src/slider/root/useSliderRoot.ts +++ b/packages/react/src/slider/root/useSliderRoot.ts @@ -17,7 +17,6 @@ import { useFieldRootContext } from '../../field/root/FieldRootContext'; import { useFieldControlValidation } from '../../field/control/useFieldControlValidation'; import { asc } from '../utils/asc'; import { getSliderValue } from '../utils/getSliderValue'; -import { percentToValue } from '../utils/percentToValue'; import { replaceArrayItemAtIndex } from '../utils/replaceArrayItemAtIndex'; import { roundValueToStep } from '../utils/roundValueToStep'; import { ThumbMetadata } from '../thumb/useSliderThumb'; @@ -104,33 +103,6 @@ export function validateMinimumDistance( return Math.min(...distances) >= step * minStepsBetweenValues; } -export function trackFinger( - event: TouchEvent | PointerEvent | React.PointerEvent, - touchIdRef: React.RefObject, -): FingerPosition | null { - // The event is TouchEvent - if (touchIdRef.current !== undefined && (event as TouchEvent).changedTouches) { - const touchEvent = event as TouchEvent; - for (let i = 0; i < touchEvent.changedTouches.length; i += 1) { - const touch = touchEvent.changedTouches[i]; - if (touch.identifier === touchIdRef.current) { - return { - x: touch.clientX, - y: touch.clientY, - }; - } - } - - return null; - } - - // The event is PointerEvent - return { - x: (event as PointerEvent).clientX, - y: (event as PointerEvent).clientY, - }; -} - /** */ export function useSliderRoot(parameters: useSliderRoot.Parameters): useSliderRoot.ReturnValue { @@ -211,13 +183,13 @@ export function useSliderRoot(parameters: useSliderRoot.Parameters): useSliderRo const range = Array.isArray(valueUnwrapped); const values = React.useMemo(() => { - return (range ? valueUnwrapped.slice().sort(asc) : [valueUnwrapped]).map((val) => - val == null ? min : clamp(val, min, max), - ); + if (!range) { + return [clamp(valueUnwrapped as number, min, max)]; + } + return valueUnwrapped.slice().sort(asc); }, [max, min, range, valueUnwrapped]); function initializePercentageValues() { - // console.log('initializePercentageValues'); const vals = []; for (let i = 0; i < values.length; i += 1) { vals.push(valueToPercent(values[i], min, max)); @@ -228,7 +200,6 @@ export function useSliderRoot(parameters: useSliderRoot.Parameters): useSliderRo const [percentageValues, setPercentageValues] = React.useState( initializePercentageValues, ); - // console.log('percentageValues', percentageValues); const setValue = useEventCallback( ( @@ -263,14 +234,7 @@ export function useSliderRoot(parameters: useSliderRoot.Parameters): useSliderRo const handleInputChange = useEventCallback( (valueInput: number, index: number, event: React.KeyboardEvent | React.ChangeEvent) => { - const newValue = getSliderValue({ - valueInput, - min, - max, - index, - range, - values, - }); + const newValue = getSliderValue(valueInput, index, min, max, range, values); if (range) { focusThumb(index, sliderRef); @@ -310,10 +274,11 @@ export function useSliderRoot(parameters: useSliderRoot.Parameters): useSliderRo */ shouldCaptureThumbIndex: boolean = false, /** - * The pixel distance between the finger origin and the center of the thumb. + * The difference between the value at the finger origin and the value at + * the center of the thumb scaled down to fit the range [0, 1] */ offset: number = 0, - ) => { + ): FingerState | null => { if (fingerPosition == null) { return null; } @@ -329,26 +294,26 @@ export function useSliderRoot(parameters: useSliderRoot.Parameters): useSliderRo const { width, height, bottom, left } = sliderControl.getBoundingClientRect(); - // percent is a value between 0 and 1, e.g. "41%" is `0.41` - const valueRescaled = isVertical + // the value at the finger origin scaled down to fit the range [0, 1] + let valueRescaled = isVertical ? (bottom - fingerPosition.y) / height + offset : (fingerPosition.x - left) / width + offset * (isRtl ? -1 : 1); - let percentageValue = Math.min(valueRescaled, 1); + valueRescaled = Math.min(valueRescaled, 1); if (isRtl && !isVertical) { - percentageValue = 1 - percentageValue; + valueRescaled = 1 - valueRescaled; } - let newValue = percentToValue(percentageValue, min, max); + let newValue = (max - min) * valueRescaled + min; newValue = roundValueToStep(newValue, step, min); newValue = clamp(newValue, min, max); if (!range) { return { value: newValue, - percentageValue, - percentageValues: [percentageValue * 100], + valueRescaled, + percentageValues: [valueRescaled * 100], thumbIndex: 0, }; } @@ -368,11 +333,11 @@ export function useSliderRoot(parameters: useSliderRoot.Parameters): useSliderRo return { value: replaceArrayItemAtIndex(values, closestThumbIndex, newValue), - percentageValue, + valueRescaled, percentageValues: replaceArrayItemAtIndex( percentageValues, closestThumbIndex, - percentageValue * 100, + valueRescaled * 100, ), thumbIndex: closestThumbIndex, }; @@ -467,10 +432,17 @@ export function useSliderRoot(parameters: useSliderRoot.Parameters): useSliderRo ); } -export type FingerPosition = { +export interface FingerPosition { x: number; y: number; -}; +} + +interface FingerState { + value: number | number[]; + valueRescaled: number; + percentageValues: number[]; + thumbIndex: number; +} export namespace useSliderRoot { export type Orientation = 'horizontal' | 'vertical'; @@ -589,12 +561,7 @@ export namespace useSliderRoot { fingerPosition: FingerPosition | null, shouldCaptureThumbIndex?: boolean, offset?: number, - ) => { - value: number | number[]; - percentageValue: number; - percentageValues: number[]; - thumbIndex: number; - } | null; + ) => FingerState | null; /** * Callback to invoke change handlers after internal value state is updated. */ diff --git a/packages/react/src/slider/thumb/SliderThumb.test.tsx b/packages/react/src/slider/thumb/SliderThumb.test.tsx index 86e58c3368..fa7b44a92f 100644 --- a/packages/react/src/slider/thumb/SliderThumb.test.tsx +++ b/packages/react/src/slider/thumb/SliderThumb.test.tsx @@ -12,7 +12,7 @@ const testRootContext: SliderRootContext = { disabled: false, getFingerState: () => ({ value: 0, - percentageValue: 0, + valueRescaled: 0, percentageValues: [0], thumbIndex: 0, }), diff --git a/packages/react/src/slider/thumb/useSliderThumb.ts b/packages/react/src/slider/thumb/useSliderThumb.ts index c7b697ee21..86db210a24 100644 --- a/packages/react/src/slider/thumb/useSliderThumb.ts +++ b/packages/react/src/slider/thumb/useSliderThumb.ts @@ -143,14 +143,7 @@ export function useSliderThumb(parameters: useSliderThumb.Parameters): useSlider } setTouched(true); commitValidation( - getSliderValue({ - valueInput: thumbValue, - min, - max, - index, - range: sliderValues.length > 1, - values: sliderValues, - }), + getSliderValue(thumbValue, index, min, max, sliderValues.length > 1, sliderValues), ); }, onKeyDown(event: React.KeyboardEvent) { @@ -215,9 +208,7 @@ export function useSliderThumb(parameters: useSliderThumb.Parameters): useSlider } }, ref: mergedThumbRef, - style: { - ...getThumbStyle(), - }, + style: getThumbStyle(), tabIndex: externalTabIndex ?? (disabled ? undefined : 0), }); }, diff --git a/packages/react/src/slider/track/SliderTrack.test.tsx b/packages/react/src/slider/track/SliderTrack.test.tsx index f8e75b411e..aca1fb5d5b 100644 --- a/packages/react/src/slider/track/SliderTrack.test.tsx +++ b/packages/react/src/slider/track/SliderTrack.test.tsx @@ -12,7 +12,7 @@ const testRootContext: SliderRootContext = { disabled: false, getFingerState: () => ({ value: 0, - percentageValue: 0, + valueRescaled: 0, percentageValues: [0], thumbIndex: 0, }), diff --git a/packages/react/src/slider/utils/getSliderValue.ts b/packages/react/src/slider/utils/getSliderValue.ts index b031f84d92..f82ef09aa5 100644 --- a/packages/react/src/slider/utils/getSliderValue.ts +++ b/packages/react/src/slider/utils/getSliderValue.ts @@ -1,18 +1,14 @@ import { clamp } from '../../utils/clamp'; import { replaceArrayItemAtIndex } from './replaceArrayItemAtIndex'; -interface GetSliderValueParameters { - valueInput: number; - index: number; - min: number; - max: number; - range: boolean; - values: readonly number[]; -} - -export function getSliderValue(params: GetSliderValueParameters) { - const { valueInput, index, min, max, range, values } = params; - +export function getSliderValue( + valueInput: number, + index: number, + min: number, + max: number, + range: boolean, + values: readonly number[], +) { let newValue: number | number[] = valueInput; newValue = clamp(newValue, min, max); diff --git a/packages/react/src/slider/utils/percentToValue.ts b/packages/react/src/slider/utils/percentToValue.ts deleted file mode 100644 index cc12d737b7..0000000000 --- a/packages/react/src/slider/utils/percentToValue.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function percentToValue(percent: number, min: number, max: number) { - return (max - min) * percent + min; -} diff --git a/packages/react/src/slider/value/SliderValue.test.tsx b/packages/react/src/slider/value/SliderValue.test.tsx index c060d813d8..ed960c2601 100644 --- a/packages/react/src/slider/value/SliderValue.test.tsx +++ b/packages/react/src/slider/value/SliderValue.test.tsx @@ -14,7 +14,7 @@ const testRootContext: SliderRootContext = { disabled: false, getFingerState: () => ({ value: 0, - percentageValue: 0, + valueRescaled: 0, percentageValues: [0], thumbIndex: 0, }),