diff --git a/src/components/RangeSlider/RangeSlider.stories.tsx b/src/components/RangeSlider/RangeSlider.stories.tsx index 7d620af4d..50b62d760 100644 --- a/src/components/RangeSlider/RangeSlider.stories.tsx +++ b/src/components/RangeSlider/RangeSlider.stories.tsx @@ -69,12 +69,12 @@ export const Default: Story = ({ disabled, showDomainLabels, showSelectedRange, - springOnRelease, min, max, dragHandleAttachment, readonly, debounceInterval, + animated, }: DefaultProps) => { const [val, setVal] = useState(value); @@ -100,12 +100,12 @@ export const Default: Story = ({ readonly={readonly} showDomainLabels={showDomainLabels} showSelectedRange={showSelectedRange} - springOnRelease={springOnRelease} + animated={animated} min={min} max={max} debounceInterval={debounceInterval} - onDebounceChange={newVal => { - action('onDebounceChange')(newVal); + onChange={newVal => { + action('onChange')(newVal); setVal(Math.round(newVal)); }} dragHandleAttachment={dragHandleAttachment} @@ -126,9 +126,9 @@ Default.args = { showDomainLabels: false, showHandleLabels: true, showSelectedRange: true, - springOnRelease: true, dragHandleAttachment: 'value', debounceInterval: 10, + animated: true, }; type RatingProps = Omit & { @@ -140,7 +140,6 @@ export const Rating: Story = ({ disabled, showDomainLabels, showSelectedRange, - springOnRelease, min, max, }: RatingProps) => { @@ -154,7 +153,6 @@ export const Rating: Story = ({ disabled={disabled} showDomainLabels={showDomainLabels} showSelectedRange={showSelectedRange} - springOnRelease={springOnRelease} min={min} max={max} onChange={newVal => { @@ -177,7 +175,6 @@ Rating.args = { disabled: false, showDomainLabels: false, showSelectedRange: false, - springOnRelease: true, min: 0, max: 5, }; diff --git a/src/components/RangeSlider/RangeSlider.tsx b/src/components/RangeSlider/RangeSlider.tsx index 5d526ef64..60c2d30db 100644 --- a/src/components/RangeSlider/RangeSlider.tsx +++ b/src/components/RangeSlider/RangeSlider.tsx @@ -215,7 +215,8 @@ export const RangeSlider = ({ showSelectedRange = true, showHandleLabels = true, - springOnRelease = true, + springOnRelease, + animated = true, debounceInterval = 8, onDrag, @@ -238,8 +239,13 @@ export const RangeSlider = ({ 'From FoundryUI RangerSlider: onDrag callback is deprecated. Instead, use onChange or onDebounceChange.', ); } - - const snapToValue = dragHandleAttachment === 'value'; + if (springOnRelease !== undefined) { + animated = springOnRelease; + // eslint-disable-next-line no-console + console.warn( + 'From FoundryUI RangeSlider: springOnRelease is deprecated. Instead, use animated.', + ); + } const { prefersReducedMotion } = useAccessibilityPreferences(); const isInitializing = useRef(true); @@ -320,7 +326,6 @@ export const RangeSlider = ({ to: { dragHandleX: 0 }, friction: 13, tension: 100, - immediate: prefersReducedMotion, })); const handleSlideRailClick = useCallback( @@ -372,31 +377,36 @@ export const RangeSlider = ({ processedValues, ], ); + const handleSlideRailClickWithAnalytics = (e: any) => { if (readonly) return; handleEventWithAnalytics('RangeSlider', handleSlideRailClick, 'onClick', e, containerProps); }; const bind = useDrag( - ({ down, movement: [deltaX] }) => { + ({ down: isDragging, movement: [deltaX] }) => { if (readonly) return; const delta = (deltaX / sliderBounds.width) * domain; valueBuffer.current = clamp(delta, min, max); - setDraggedHandle(down ? 0 : -1); + setDraggedHandle(isDragging ? 0 : -1); handleDrag(valueBuffer.current); - let animate = true; - if (prefersReducedMotion) animate = false; - if (!snapToValue) animate = springOnRelease ? !down : false; - - springRef.start({ - // Should handle follow value or drag gesture? - dragHandleX: snapToValue || !down ? (pixelPositions ?? [0])[0] : deltaX, - - immediate: !animate, - config: { friction: 13, tension: 100 }, - }); + if (dragHandleAttachment === 'mouse') { + if (isDragging) { + // constantly follow mouse during drag + springRef.start({ + dragHandleX: deltaX, + immediate: true, + }); + } else { + // after drag release, spring to value + springRef.start({ + dragHandleX: pixelPositions[0], + immediate: prefersReducedMotion || !animated, + }); + } + } }, { initial: [(pixelPositions ?? [0])[0], 0], @@ -411,23 +421,38 @@ export const RangeSlider = ({ }, ); - // Snap to value on initial load and when pixelPositions changes (on click) + // Once sliderBounds are read, set initial position useEffect(() => { - if (draggedHandle >= 0) return; - - if (sliderBounds.x) { + if (isInitializing.current && sliderBounds.width) { springRef.start({ dragHandleX: pixelPositions[0], - - immediate: prefersReducedMotion || isInitializing.current, - config: { friction: 13, tension: 100 }, + immediate: true, onResolve: () => { - if (isInitializing) isInitializing.current = false; + isInitializing.current = false; }, }); } - }, [springRef, pixelPositions, draggedHandle, prefersReducedMotion, sliderBounds]); + }, [springRef, sliderBounds, isInitializing, pixelPositions]); + // For snap to value, listen to changes in value and always animate to value. Also listens to clicks + useEffect(() => { + if (dragHandleAttachment === 'value' || draggedHandle === -1) { + springRef.start({ + dragHandleX: pixelPositions[0], + immediate: prefersReducedMotion || !animated, + }); + } + }, [ + dragHandleAttachment, + springRef, + pixelPositions, + prefersReducedMotion, + sliderBounds, + animated, + draggedHandle, + ]); + + // Dispose of debounce timers useEffect(() => { return () => { debouncedOnChange.cancel(); diff --git a/src/components/RangeSlider/types.ts b/src/components/RangeSlider/types.ts index 833620a61..3bebe0f44 100644 --- a/src/components/RangeSlider/types.ts +++ b/src/components/RangeSlider/types.ts @@ -64,8 +64,8 @@ export type RangeSliderProps = { showDomainLabels?: boolean; showSelectedRange?: boolean; showHandleLabels?: boolean; + animated?: boolean; - springOnRelease?: boolean; /** Debounce interval (in ms) before calling `onDebounceChange`. */ debounceInterval?: number; @@ -88,6 +88,8 @@ export type RangeSliderProps = { */ dragHandleAttachment?: 'value' | 'mouse'; + /** @deprecated use `animated` instead. */ + springOnRelease?: boolean; /** @deprecated use onChange or onChangeDebounce instead. */ onDrag?: (val: number) => void; /** @deprecated do not use. */