Skip to content

Commit acaacd8

Browse files
committed
fix(slider): fix thumb not draggable
fix #357
1 parent b0517dd commit acaacd8

File tree

3 files changed

+74
-70
lines changed

3 files changed

+74
-70
lines changed

src/Slider/Slider.stories.tsx

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ComponentMeta } from '@storybook/react';
22
import React from 'react';
3-
import { ScrollView, Slider } from 'react95';
3+
import { ScrollView, Slider, SliderOnChangeHandler } from 'react95';
44
import styled from 'styled-components';
55

66
const Wrapper = styled.div`
@@ -41,6 +41,10 @@ export default {
4141
} as ComponentMeta<typeof Slider>;
4242

4343
export function Default() {
44+
const [state, setState] = React.useState(0);
45+
46+
const onChange: SliderOnChangeHandler = (_, newValue) => setState(newValue);
47+
4448
return (
4549
<div className='row'>
4650
<div className='col'>
@@ -66,7 +70,8 @@ export function Default() {
6670
min={0}
6771
max={6}
6872
step={1}
69-
defaultValue={0}
73+
value={state}
74+
onChange={onChange}
7075
marks={[
7176
{ value: 0, label: '0°C' },
7277
{ value: 2, label: '2°C' },

src/Slider/Slider.tsx

+44-68
Original file line numberDiff line numberDiff line change
@@ -17,31 +17,31 @@ import {
1717
createHatchedBackground
1818
} from '../common';
1919
import useControlledOrUncontrolled from '../common/hooks/useControlledOrUncontrolled';
20+
import useEventCallback from '../common/hooks/useEventCallback';
2021
import useForkRef from '../common/hooks/useForkRef';
2122
import { useIsFocusVisible } from '../common/hooks/useIsFocusVisible';
2223
import { clamp, getSize, roundValueToStep } from '../common/utils';
2324
import { StyledScrollView } from '../ScrollView/ScrollView';
2425
import { CommonStyledProps } from '../types';
2526

27+
export type SliderOnChangeHandler = (
28+
event:
29+
| MouseEvent
30+
| React.KeyboardEvent<HTMLSpanElement>
31+
| React.MouseEvent<HTMLDivElement>
32+
| TouchEvent,
33+
value: number
34+
) => void;
35+
2636
type SliderProps = {
2737
defaultValue?: number;
2838
disabled?: boolean;
2939
marks?: boolean | { label?: string; value: number }[];
3040
max?: number;
3141
min?: number;
3242
name?: string;
33-
onChange?: (
34-
event:
35-
| MouseEvent
36-
| React.KeyboardEvent<HTMLSpanElement>
37-
| React.MouseEvent<HTMLDivElement>
38-
| TouchEvent,
39-
value: number
40-
) => void;
41-
onChangeCommitted?: (
42-
event: MouseEvent | React.KeyboardEvent<HTMLSpanElement> | TouchEvent,
43-
value: number
44-
) => void;
43+
onChange?: SliderOnChangeHandler;
44+
onChangeCommitted?: SliderOnChangeHandler;
4545
onMouseDown?: (event: React.MouseEvent<HTMLDivElement>) => void;
4646
orientation?: 'horizontal' | 'vertical';
4747
size?: string | number;
@@ -330,21 +330,20 @@ const Slider = forwardRef<HTMLDivElement, SliderProps>(
330330
const handleFocusRef = useForkRef(focusVisibleRef, sliderRef);
331331
const handleRef = useForkRef(ref, handleFocusRef);
332332

333-
const handleFocus = useCallback(
333+
const handleFocus = useEventCallback(
334334
(event: React.FocusEvent<HTMLSpanElement>) => {
335335
if (isFocusVisible(event)) {
336336
setFocusVisible(true);
337337
}
338-
},
339-
[isFocusVisible]
338+
}
340339
);
341340

342-
const handleBlur = useCallback(() => {
341+
const handleBlur = useEventCallback(() => {
343342
if (focusVisible !== false) {
344343
setFocusVisible(false);
345344
onBlurVisible();
346345
}
347-
}, [focusVisible, onBlurVisible]);
346+
});
348347

349348
const touchId = useRef<number>();
350349

@@ -363,7 +362,7 @@ const Slider = forwardRef<HTMLDivElement, SliderProps>(
363362
[marksProp, max, min, step]
364363
);
365364

366-
const handleKeyDown = useCallback(
365+
const handleKeyDown = useEventCallback(
367366
(event: React.KeyboardEvent<HTMLSpanElement>) => {
368367
const tenPercents = (max - min) / 10;
369368
const marksValues = marks.map(mark => mark.value);
@@ -422,17 +421,7 @@ const Slider = forwardRef<HTMLDivElement, SliderProps>(
422421

423422
onChange?.(event, newValue);
424423
onChangeCommitted?.(event, newValue);
425-
},
426-
[
427-
marks,
428-
max,
429-
min,
430-
onChange,
431-
onChangeCommitted,
432-
setValueState,
433-
step,
434-
valueDerived
435-
]
424+
}
436425
);
437426

438427
const getNewValue = useCallback(
@@ -464,7 +453,7 @@ const Slider = forwardRef<HTMLDivElement, SliderProps>(
464453
[marks, max, min, step, vertical]
465454
);
466455

467-
const handleTouchMove = useCallback(
456+
const handleTouchMove = useEventCallback(
468457
(event: MouseEvent | TouchEvent) => {
469458
const finger = trackFinger(event, touchId.current);
470459

@@ -478,11 +467,10 @@ const Slider = forwardRef<HTMLDivElement, SliderProps>(
478467
setFocusVisible(true);
479468

480469
onChange?.(event, newValue);
481-
},
482-
[getNewValue, onChange, setValueState]
470+
}
483471
);
484472

485-
const handleTouchEnd = useCallback(
473+
const handleTouchEnd = useEventCallback(
486474
(event: MouseEvent | TouchEvent) => {
487475
const finger = trackFinger(event, touchId.current);
488476

@@ -501,11 +489,10 @@ const Slider = forwardRef<HTMLDivElement, SliderProps>(
501489
doc.removeEventListener('mouseup', handleTouchEnd);
502490
doc.removeEventListener('touchmove', handleTouchMove);
503491
doc.removeEventListener('touchend', handleTouchEnd);
504-
},
505-
[getNewValue, handleTouchMove, onChangeCommitted]
492+
}
506493
);
507494

508-
const handleMouseDown = useCallback(
495+
const handleMouseDown = useEventCallback(
509496
(event: React.MouseEvent<HTMLDivElement>) => {
510497
// TODO should we also pass event together with new value to callbacks? (same thing with other input components)
511498
onMouseDown?.(event);
@@ -524,43 +511,32 @@ const Slider = forwardRef<HTMLDivElement, SliderProps>(
524511
const doc = ownerDocument(sliderRef.current);
525512
doc.addEventListener('mousemove', handleTouchMove);
526513
doc.addEventListener('mouseup', handleTouchEnd);
527-
},
528-
[
529-
getNewValue,
530-
handleTouchEnd,
531-
handleTouchMove,
532-
onChange,
533-
onMouseDown,
534-
setValueState
535-
]
514+
}
536515
);
537516

538-
const handleTouchStart = useCallback(
539-
(event: TouchEvent) => {
540-
// Workaround as Safari has partial support for touchAction: 'none'.
541-
event.preventDefault();
542-
const touch = event.changedTouches[0];
543-
if (touch != null) {
544-
// A number that uniquely identifies the current finger in the touch session.
545-
touchId.current = touch.identifier;
546-
}
517+
const handleTouchStart = useEventCallback((event: TouchEvent) => {
518+
// Workaround as Safari has partial support for touchAction: 'none'.
519+
event.preventDefault();
520+
const touch = event.changedTouches[0];
521+
if (touch != null) {
522+
// A number that uniquely identifies the current finger in the touch session.
523+
touchId.current = touch.identifier;
524+
}
547525

548-
thumbRef.current?.focus();
549-
setFocusVisible(true);
526+
thumbRef.current?.focus();
527+
setFocusVisible(true);
550528

551-
const finger = trackFinger(event, touchId.current);
552-
if (finger) {
553-
const newValue = getNewValue(finger);
554-
setValueState(newValue);
555-
onChange?.(event, newValue);
556-
}
529+
const finger = trackFinger(event, touchId.current);
530+
if (finger) {
531+
const newValue = getNewValue(finger);
532+
setValueState(newValue);
533+
onChange?.(event, newValue);
534+
}
557535

558-
const doc = ownerDocument(sliderRef.current);
559-
doc.addEventListener('touchmove', handleTouchMove);
560-
doc.addEventListener('touchend', handleTouchEnd);
561-
},
562-
[getNewValue, handleTouchEnd, handleTouchMove, onChange, setValueState]
563-
);
536+
const doc = ownerDocument(sliderRef.current);
537+
doc.addEventListener('touchmove', handleTouchMove);
538+
doc.addEventListener('touchend', handleTouchEnd);
539+
});
564540

565541
useEffect(() => {
566542
const { current: slider } = sliderRef;

src/common/hooks/useEventCallback.ts

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import * as React from 'react';
2+
3+
const useEnhancedEffect =
4+
typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect;
5+
6+
/**
7+
* https://github.com/facebook/react/issues/14099#issuecomment-440013892
8+
*/
9+
export default function useEventCallback<Args extends unknown[], Return>(
10+
fn: (...args: Args) => Return
11+
): (...args: Args) => Return {
12+
const ref = React.useRef(fn);
13+
useEnhancedEffect(() => {
14+
ref.current = fn;
15+
});
16+
return React.useCallback(
17+
(...args: Args) =>
18+
// @ts-expect-error hide `this`
19+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
20+
(0, ref.current!)(...args),
21+
[]
22+
);
23+
}

0 commit comments

Comments
 (0)