@@ -2,14 +2,15 @@ import { Slider as AntdSlider, type SliderSingleProps } from "antd";
2
2
import type { SliderRangeProps } from "antd/lib/slider" ;
3
3
import { clamp } from "libs/utils" ;
4
4
import _ from "lodash" ;
5
- import type { WheelEventHandler } from "react" ;
5
+ import { useCallback , useEffect , useRef , useState } from "react" ;
6
6
7
7
const DEFAULT_WHEEL_FACTOR = 0.02 ;
8
8
const DEFAULT_STEP = 1 ;
9
9
10
10
type SliderProps = ( SliderSingleProps | SliderRangeProps ) & {
11
11
wheelFactor ?: number ;
12
12
onWheelDisabled ?: boolean ;
13
+ onResetToDefault ?: ( ) => void ;
13
14
} ;
14
15
15
16
const getDiffPerSliderStep = (
@@ -23,6 +24,8 @@ const getDiffPerSliderStep = (
23
24
} ;
24
25
25
26
const getWheelStepFromEvent = ( step : number , deltaY : number , wheelStep : number ) => {
27
+ const absDeltaY = Math . abs ( deltaY ) ;
28
+ if ( absDeltaY === 0 || step === 0 ) return 0 ;
26
29
// Make sure that result is a multiple of step
27
30
return step * Math . round ( ( wheelStep * deltaY ) / Math . abs ( deltaY ) / step ) ;
28
31
} ;
@@ -32,6 +35,7 @@ export function Slider(props: SliderProps) {
32
35
min,
33
36
max,
34
37
onChange,
38
+ onResetToDefault,
35
39
value,
36
40
range,
37
41
defaultValue,
@@ -40,49 +44,81 @@ export function Slider(props: SliderProps) {
40
44
step,
41
45
disabled,
42
46
} = props ;
47
+ const isFocused = useRef ( false ) ;
48
+ const sliderRef = useRef < HTMLDivElement > ( null ) ;
49
+
50
+ const handleWheelEvent = useCallback (
51
+ ( event : { preventDefault : ( ) => void ; deltaY : number } ) => {
52
+ if (
53
+ onWheelDisabled ||
54
+ value == null ||
55
+ min == null ||
56
+ max == null ||
57
+ ! isFocused . current ||
58
+ onChange == null
59
+ )
60
+ return ;
61
+ event . preventDefault ( ) ;
62
+ const diff = getWheelStepFromEvent ( ensuredStep , event . deltaY , wheelStep ) ;
63
+ // differentiate between single value and range slider
64
+ if ( range === false || range == null ) {
65
+ const newValue = value - diff ;
66
+ const clampedNewValue = clamp ( min , newValue , max ) ;
67
+ onChange ( clampedNewValue ) ;
68
+ } else if ( range === true || typeof range === "object" ) {
69
+ const newLowerValue = Math . round ( value [ 0 ] + diff ) ;
70
+ const newUpperValue = Math . round ( value [ 1 ] - diff ) ;
71
+ const clampedNewLowerValue = clamp ( min , newLowerValue , Math . min ( newUpperValue , max ) ) ;
72
+ const clampedNewUpperValue = clamp ( newLowerValue , newUpperValue , max ) ;
73
+ onChange ( [ clampedNewLowerValue , clampedNewUpperValue ] ) ;
74
+ }
75
+ } ,
76
+ [ value , min , max , onChange , range , onWheelDisabled ] ,
77
+ ) ;
78
+
79
+ // Reacts onWheel is passive by default, this means that it can't preventDefault.
80
+ // Thus we need to add the event listener manually.
81
+ // (See https://github.com/facebook/react/pull/19654)
82
+ useEffect ( ( ) => {
83
+ const sliderElement = sliderRef . current ;
84
+ if ( sliderElement ) {
85
+ sliderElement . addEventListener ( "wheel" , handleWheelEvent , { passive : false } ) ;
86
+ return ( ) => {
87
+ sliderElement . removeEventListener ( "wheel" , handleWheelEvent ) ;
88
+ } ;
89
+ }
90
+ } , [ handleWheelEvent ] ) ;
91
+
43
92
if ( min == null || max == null || onChange == null || value == null || disabled )
44
93
return < AntdSlider { ...props } /> ;
45
94
const sliderRange = max - min ;
46
95
const ensuredStep = step || DEFAULT_STEP ;
47
- let handleWheelEvent : WheelEventHandler < HTMLDivElement > = _ . noop ;
48
- let handleDoubleClick : React . MouseEventHandler < HTMLDivElement > = _ . noop ;
96
+
49
97
const wheelStep = getDiffPerSliderStep ( sliderRange , wheelFactor , ensuredStep ) ;
50
98
51
- handleDoubleClick = ( event ) => {
99
+ const handleDoubleClick : React . MouseEventHandler < HTMLDivElement > = ( event ) => {
52
100
if (
53
101
event . target instanceof HTMLElement &&
54
102
event . target . className . includes ( "ant-slider-handle" ) &&
55
103
defaultValue != null
56
104
)
57
- // @ts -ignore Argument of type 'number | number[]' is not assignable to parameter of type 'number'.
58
- //TypeScript doesn't understand that onChange always takes the type of defaultValue.
59
- onChange ( defaultValue ) ;
105
+ if ( onResetToDefault != null ) {
106
+ onResetToDefault ( ) ;
107
+ } else {
108
+ // @ts -ignore Argument of type 'number | number[]' is not assignable to parameter of type 'number'.
109
+ //TypeScript doesn't understand that onChange always takes the type of defaultValue.
110
+ onChange ( defaultValue ) ;
111
+ }
60
112
} ;
61
113
62
- // differentiate between single value and range slider
63
- if ( range === false || range == null ) {
64
- if ( ! onWheelDisabled ) {
65
- handleWheelEvent = ( event ) => {
66
- const newValue = value - getWheelStepFromEvent ( ensuredStep , event . deltaY , wheelStep ) ;
67
- const clampedNewValue = clamp ( min , newValue , max ) ;
68
- onChange ( clampedNewValue ) ;
69
- } ;
70
- }
71
- } else if ( range === true || typeof range === "object" ) {
72
- if ( ! onWheelDisabled ) {
73
- handleWheelEvent = ( event ) => {
74
- const diff = getWheelStepFromEvent ( ensuredStep , event . deltaY , wheelStep ) ;
75
- const newLowerValue = Math . round ( value [ 0 ] + diff ) ;
76
- const newUpperValue = Math . round ( value [ 1 ] - diff ) ;
77
- const clampedNewLowerValue = clamp ( min , newLowerValue , Math . min ( newUpperValue , max ) ) ;
78
- const clampedNewUpperValue = clamp ( newLowerValue , newUpperValue , max ) ;
79
- onChange ( [ clampedNewLowerValue , clampedNewUpperValue ] ) ;
80
- } ;
81
- }
82
- }
83
-
84
114
return (
85
- < div onWheel = { handleWheelEvent } onDoubleClick = { handleDoubleClick } style = { { flexGrow : 1 } } >
115
+ < div
116
+ ref = { sliderRef }
117
+ onDoubleClick = { handleDoubleClick }
118
+ style = { { flexGrow : 1 , touchAction : "none" , userSelect : isFocused ? "none" : "auto" } }
119
+ onFocus = { ( ) => ( isFocused . current = true ) }
120
+ onBlur = { ( ) => ( isFocused . current = false ) }
121
+ >
86
122
< AntdSlider { ...props } />
87
123
</ div >
88
124
) ;
0 commit comments