-
Notifications
You must be signed in to change notification settings - Fork 26
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve Slider Scrolling #8321
base: master
Are you sure you want to change the base?
Improve Slider Scrolling #8321
Changes from all commits
d3c9254
2cb3a63
a2b28c4
13b6a96
acfd83e
bac0010
8106cde
0f5ced1
4c2148e
49c44d3
80a47d7
7c10dce
7f6ae32
52eb133
45394bc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -2,14 +2,15 @@ import { Slider as AntdSlider, type SliderSingleProps } from "antd"; | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
import type { SliderRangeProps } from "antd/lib/slider"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { clamp } from "libs/utils"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import _ from "lodash"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import type { WheelEventHandler } from "react"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { useCallback, useEffect, useRef, useState } from "react"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const DEFAULT_WHEEL_FACTOR = 0.02; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const DEFAULT_STEP = 1; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
type SliderProps = (SliderSingleProps | SliderRangeProps) & { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
wheelFactor?: number; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
onWheelDisabled?: boolean; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
onResetToDefault?: () => void; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const getDiffPerSliderStep = ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -23,6 +24,8 @@ const getDiffPerSliderStep = ( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const getWheelStepFromEvent = (step: number, deltaY: number, wheelStep: number) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const absDeltaY = Math.abs(deltaY); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (absDeltaY === 0 || step === 0) return 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Make sure that result is a multiple of step | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return step * Math.round((wheelStep * deltaY) / Math.abs(deltaY) / step); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -32,6 +35,7 @@ export function Slider(props: SliderProps) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
min, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
max, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
onChange, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
onResetToDefault, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
value, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
range, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
defaultValue, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -40,49 +44,78 @@ export function Slider(props: SliderProps) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
step, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
disabled, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} = props; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const isFocused = useRef(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const sliderRef = useRef<HTMLDivElement>(null); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const handleWheelEvent = useCallback( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
(event: { preventDefault: () => void; deltaY: number }) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
onWheelDisabled || | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
value == null || | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
min == null || | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
max == null || | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
!isFocused.current || | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
onChange == null | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
event.preventDefault(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const diff = getWheelStepFromEvent(ensuredStep, event.deltaY, wheelStep); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// differentiate between single value and range slider | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (range === false || range == null) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const newValue = value - diff; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const clampedNewValue = clamp(min, newValue, max); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
onChange(clampedNewValue); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} else if (range === true || typeof range === "object") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const newLowerValue = Math.round(value[0] + diff); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const newUpperValue = Math.round(value[1] - diff); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const clampedNewLowerValue = clamp(min, newLowerValue, Math.min(newUpperValue, max)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const clampedNewUpperValue = clamp(newLowerValue, newUpperValue, max); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
onChange([clampedNewLowerValue, clampedNewUpperValue]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
[value, min, max, onChange, range, onWheelDisabled], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Reacts onWheel is passive by default, this means that it can't preventDefault. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Thus we need to add the event listener manually. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// (See https://github.com/facebook/react/pull/19654) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const sliderElement = sliderRef.current; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (sliderElement) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
sliderElement.addEventListener("wheel", handleWheelEvent, { passive: false }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
sliderElement.removeEventListener("wheel", handleWheelEvent); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}, [handleWheelEvent]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was a little surprised about this solution. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (min == null || max == null || onChange == null || value == null || disabled) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return <AntdSlider {...props} />; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const sliderRange = max - min; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const ensuredStep = step || DEFAULT_STEP; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
let handleWheelEvent: WheelEventHandler<HTMLDivElement> = _.noop; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
let handleDoubleClick: React.MouseEventHandler<HTMLDivElement> = _.noop; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const wheelStep = getDiffPerSliderStep(sliderRange, wheelFactor, ensuredStep); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
handleDoubleClick = (event) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const handleDoubleClick: React.MouseEventHandler<HTMLDivElement> = (event) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
event.target instanceof HTMLElement && | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
event.target.className.includes("ant-slider-handle") && | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
defaultValue != null | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (onResetToDefault != null) onResetToDefault(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// @ts-ignore Argument of type 'number | number[]' is not assignable to parameter of type 'number'. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
//TypeScript doesn't understand that onChange always takes the type of defaultValue. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
onChange(defaultValue); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
else onChange(defaultValue); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+99
to
109
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix else branch placement. The const handleDoubleClick: React.MouseEventHandler<HTMLDivElement> = (event) => {
if (
event.target instanceof HTMLElement &&
event.target.className.includes("ant-slider-handle") &&
defaultValue != null
- )
- if (onResetToDefault != null) onResetToDefault();
+ ) {
+ if (onResetToDefault != null) {
+ onResetToDefault();
+ } else {
// @ts-ignore Argument of type 'number | number[]' is not assignable to parameter of type 'number'.
//TypeScript doesn't understand that onChange always takes the type of defaultValue.
- else onChange(defaultValue);
+ onChange(defaultValue);
+ }
+ }
}; 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// differentiate between single value and range slider | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (range === false || range == null) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (!onWheelDisabled) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
handleWheelEvent = (event) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const newValue = value - getWheelStepFromEvent(ensuredStep, event.deltaY, wheelStep); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const clampedNewValue = clamp(min, newValue, max); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
onChange(clampedNewValue); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} else if (range === true || typeof range === "object") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (!onWheelDisabled) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
handleWheelEvent = (event) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const diff = getWheelStepFromEvent(ensuredStep, event.deltaY, wheelStep); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const newLowerValue = Math.round(value[0] + diff); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const newUpperValue = Math.round(value[1] - diff); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const clampedNewLowerValue = clamp(min, newLowerValue, Math.min(newUpperValue, max)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const clampedNewUpperValue = clamp(newLowerValue, newUpperValue, max); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
onChange([clampedNewLowerValue, clampedNewUpperValue]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div onWheel={handleWheelEvent} onDoubleClick={handleDoubleClick} style={{ flexGrow: 1 }}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<div | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ref={sliderRef} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
onDoubleClick={handleDoubleClick} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
style={{ flexGrow: 1, touchAction: "none" }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
onFocus={() => (isFocused.current = true)} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
onBlur={() => (isFocused.current = false)} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<AntdSlider {...props} /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
111
to
121
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Update focus handlers to use setState. Update the focus handlers to use setState for consistency with the earlier suggestion to use useState. return (
<div
ref={sliderRef}
onDoubleClick={handleDoubleClick}
style={{ flexGrow: 1, touchAction: "none" }}
- onFocus={() => (isFocused.current = true)}
- onBlur={() => (isFocused.current = false)}
+ onFocus={() => setIsFocused(true)}
+ onBlur={() => setIsFocused(false)}
>
<AntdSlider {...props} />
</div>
); 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Use useState for focus management to prevent race conditions.
Using
useRef
for focus state could lead to race conditions in concurrent mode. Consider usinguseState
instead.📝 Committable suggestion