Skip to content

Commit a93831d

Browse files
authored
feat: add activeHandleRender (#981)
* docs: update demo * feat: add activeHandleRender * chore: tmp unlock * 10.6.0-0 * chore: reorder to avoid break change * 10.6.0-1 * chore: fix logic * 10.6.0-2 * 10.6.0-3 * chore: opt drag * 10.6.0-4 * fix: blur miss * chore: cleanup * test: coverage
1 parent ac66c54 commit a93831d

File tree

8 files changed

+175
-57
lines changed

8 files changed

+175
-57
lines changed

docs/examples/multiple.tsx

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,38 @@ function log(value) {
1212
console.log(value);
1313
}
1414

15-
export default () => (
16-
<div>
17-
<div style={style}>
18-
<Slider
19-
range
20-
defaultValue={[0, 10, 30]}
21-
onChange={log}
22-
styles={{
23-
tracks: {
24-
background: `linear-gradient(to right, blue, red)`,
25-
},
26-
track: {
27-
background: 'transparent',
28-
},
29-
}}
30-
/>
15+
const NodeWrapper = ({ children }: { children: React.ReactElement }) => {
16+
return <div>{React.cloneElement(children, {}, <div>TOOLTIP</div>)}</div>;
17+
};
18+
19+
export default () => {
20+
const [value, setValue] = React.useState([0, 5, 8]);
21+
22+
return (
23+
<div>
24+
<div style={style}>
25+
<Slider
26+
range
27+
// defaultValue={[0, 10, 30]}
28+
// onChange={log}
29+
min={0}
30+
max={10}
31+
value={value}
32+
onChange={(nextValue) => {
33+
// console.log('>>>', nextValue);
34+
setValue(nextValue as any);
35+
}}
36+
activeHandleRender={(node) => <NodeWrapper>{node}</NodeWrapper>}
37+
styles={{
38+
tracks: {
39+
background: `linear-gradient(to right, blue, red)`,
40+
},
41+
track: {
42+
background: 'transparent',
43+
},
44+
}}
45+
/>
46+
</div>
3147
</div>
32-
</div>
33-
);
48+
);
49+
};

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "rc-slider",
3-
"version": "10.5.0",
3+
"version": "10.6.0-4",
44
"description": "Slider UI component for React",
55
"keywords": [
66
"react",
@@ -35,7 +35,7 @@
3535
"docs:deploy": "gh-pages -d .doc",
3636
"lint": "eslint src/ --ext .ts,.tsx,.jsx,.js,.md",
3737
"now-build": "npm run docs:build",
38-
"prepublishOnly": "npm run compile && np --yolo --no-publish",
38+
"prepublishOnly": "npm run compile && np --yolo --no-publish --any-branch",
3939
"prettier": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\"",
4040
"start": "dumi dev",
4141
"test": "rc-test"

src/Handles/Handle.tsx

Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,23 @@ interface RenderProps {
1212
dragging: boolean;
1313
}
1414

15-
export interface HandleProps {
15+
export interface HandleProps
16+
extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onFocus' | 'onMouseEnter'> {
1617
prefixCls: string;
1718
style?: React.CSSProperties;
1819
value: number;
1920
valueIndex: number;
2021
dragging: boolean;
2122
onStartMove: OnStartMove;
2223
onOffsetChange: (value: number | 'min' | 'max', valueIndex: number) => void;
23-
onFocus?: (e: React.FocusEvent<HTMLDivElement>) => void;
24-
onBlur?: (e: React.FocusEvent<HTMLDivElement>) => void;
25-
render?: (origin: React.ReactElement<HandleProps>, props: RenderProps) => React.ReactElement;
24+
onFocus: (e: React.FocusEvent<HTMLDivElement>, index: number) => void;
25+
onMouseEnter: (e: React.MouseEvent<HTMLDivElement>, index: number) => void;
26+
render?: (
27+
origin: React.ReactElement<React.HTMLAttributes<HTMLDivElement>>,
28+
props: RenderProps,
29+
) => React.ReactElement;
2630
onChangeComplete?: () => void;
31+
mock?: boolean;
2732
}
2833

2934
const Handle = React.forwardRef<HTMLDivElement, HandleProps>((props, ref) => {
@@ -37,6 +42,8 @@ const Handle = React.forwardRef<HTMLDivElement, HandleProps>((props, ref) => {
3742
dragging,
3843
onOffsetChange,
3944
onChangeComplete,
45+
onFocus,
46+
onMouseEnter,
4047
...restProps
4148
} = props;
4249
const {
@@ -63,6 +70,14 @@ const Handle = React.forwardRef<HTMLDivElement, HandleProps>((props, ref) => {
6370
}
6471
};
6572

73+
const onInternalFocus = (e: React.FocusEvent<HTMLDivElement>) => {
74+
onFocus?.(e, valueIndex);
75+
};
76+
77+
const onInternalMouseEnter = (e: React.MouseEvent<HTMLDivElement>) => {
78+
onMouseEnter(e, valueIndex);
79+
};
80+
6681
// =========================== Keyboard ===========================
6782
const onKeyDown: React.KeyboardEventHandler<HTMLDivElement> = (e) => {
6883
if (!disabled && keyboard) {
@@ -131,13 +146,36 @@ const Handle = React.forwardRef<HTMLDivElement, HandleProps>((props, ref) => {
131146
const positionStyle = getDirectionStyle(direction, value, min, max);
132147

133148
// ============================ Render ============================
149+
let divProps: React.HtmlHTMLAttributes<HTMLDivElement> = {};
150+
151+
if (valueIndex !== null) {
152+
divProps = {
153+
tabIndex: disabled ? null : getIndex(tabIndex, valueIndex),
154+
role: 'slider',
155+
'aria-valuemin': min,
156+
'aria-valuemax': max,
157+
'aria-valuenow': value,
158+
'aria-disabled': disabled,
159+
'aria-label': getIndex(ariaLabelForHandle, valueIndex),
160+
'aria-labelledby': getIndex(ariaLabelledByForHandle, valueIndex),
161+
'aria-valuetext': getIndex(ariaValueTextFormatterForHandle, valueIndex)?.(value),
162+
'aria-orientation': direction === 'ltr' || direction === 'rtl' ? 'horizontal' : 'vertical',
163+
onMouseDown: onInternalStartMove,
164+
onTouchStart: onInternalStartMove,
165+
onFocus: onInternalFocus,
166+
onMouseEnter: onInternalMouseEnter,
167+
onKeyDown,
168+
onKeyUp: handleKeyUp,
169+
};
170+
}
171+
134172
let handleNode = (
135173
<div
136174
ref={ref}
137175
className={cls(
138176
handlePrefixCls,
139177
{
140-
[`${handlePrefixCls}-${valueIndex + 1}`]: range,
178+
[`${handlePrefixCls}-${valueIndex + 1}`]: valueIndex !== null && range,
141179
[`${handlePrefixCls}-dragging`]: dragging,
142180
},
143181
classNames.handle,
@@ -147,20 +185,7 @@ const Handle = React.forwardRef<HTMLDivElement, HandleProps>((props, ref) => {
147185
...style,
148186
...styles.handle,
149187
}}
150-
onMouseDown={onInternalStartMove}
151-
onTouchStart={onInternalStartMove}
152-
onKeyDown={onKeyDown}
153-
onKeyUp={handleKeyUp}
154-
tabIndex={disabled ? null : getIndex(tabIndex, valueIndex)}
155-
role="slider"
156-
aria-valuemin={min}
157-
aria-valuemax={max}
158-
aria-valuenow={value}
159-
aria-disabled={disabled}
160-
aria-label={getIndex(ariaLabelForHandle, valueIndex)}
161-
aria-labelledby={getIndex(ariaLabelledByForHandle, valueIndex)}
162-
aria-valuetext={getIndex(ariaValueTextFormatterForHandle, valueIndex)?.(value)}
163-
aria-orientation={direction === 'ltr' || direction === 'rtl' ? 'horizontal' : 'vertical'}
188+
{...divProps}
164189
{...restProps}
165190
/>
166191
);

src/Handles/index.tsx

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ export interface HandlesProps {
1313
onFocus?: (e: React.FocusEvent<HTMLDivElement>) => void;
1414
onBlur?: (e: React.FocusEvent<HTMLDivElement>) => void;
1515
handleRender?: HandleProps['render'];
16+
/**
17+
* When config `activeHandleRender`,
18+
* it will render another hidden handle for active usage.
19+
* This is useful for accessibility or tooltip usage.
20+
*/
21+
activeHandleRender?: HandleProps['render'];
1622
draggingIndex: number;
1723
onChangeComplete?: () => void;
1824
}
@@ -29,7 +35,9 @@ const Handles = React.forwardRef<HandlesRef, HandlesProps>((props, ref) => {
2935
onOffsetChange,
3036
values,
3137
handleRender,
38+
activeHandleRender,
3239
draggingIndex,
40+
onFocus,
3341
...restProps
3442
} = props;
3543
const handlesRef = React.useRef<Record<number, HTMLDivElement>>({});
@@ -40,6 +48,30 @@ const Handles = React.forwardRef<HandlesRef, HandlesProps>((props, ref) => {
4048
},
4149
}));
4250

51+
// =========================== Active ===========================
52+
const [activeIndex, setActiveIndex] = React.useState<number>(-1);
53+
54+
const onHandleFocus = (e: React.FocusEvent<HTMLDivElement>, index: number) => {
55+
setActiveIndex(index);
56+
onFocus?.(e);
57+
};
58+
59+
const onHandleMouseEnter = (e: React.MouseEvent<HTMLDivElement>, index: number) => {
60+
setActiveIndex(index);
61+
};
62+
63+
// =========================== Render ===========================
64+
// Handle Props
65+
const handleProps = {
66+
prefixCls,
67+
onStartMove,
68+
onOffsetChange,
69+
render: handleRender,
70+
onFocus: onHandleFocus,
71+
onMouseEnter: onHandleMouseEnter,
72+
...restProps,
73+
};
74+
4375
return (
4476
<>
4577
{values.map<React.ReactNode>((value, index) => (
@@ -52,17 +84,27 @@ const Handles = React.forwardRef<HandlesRef, HandlesProps>((props, ref) => {
5284
}
5385
}}
5486
dragging={draggingIndex === index}
55-
prefixCls={prefixCls}
5687
style={getIndex(style, index)}
5788
key={index}
5889
value={value}
5990
valueIndex={index}
60-
onStartMove={onStartMove}
61-
onOffsetChange={onOffsetChange}
62-
render={handleRender}
63-
{...restProps}
91+
{...handleProps}
6492
/>
6593
))}
94+
95+
{activeHandleRender && (
96+
<Handle
97+
key="a11y"
98+
{...handleProps}
99+
value={values[activeIndex]}
100+
valueIndex={null}
101+
dragging={draggingIndex !== -1}
102+
render={activeHandleRender}
103+
style={{ pointerEvents: 'none' }}
104+
tabIndex={null}
105+
aria-hidden
106+
/>
107+
)}
66108
</>
67109
);
68110
});

src/Slider.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ export interface SliderProps<ValueType = number | number[]> {
9393

9494
// Components
9595
handleRender?: HandlesProps['handleRender'];
96+
activeHandleRender?: HandlesProps['handleRender'];
9697

9798
// Accessibility
9899
tabIndex?: number | number[];
@@ -158,6 +159,7 @@ const Slider = React.forwardRef<SliderRef, SliderProps<number | number[]>>((prop
158159

159160
// Components
160161
handleRender,
162+
activeHandleRender,
161163

162164
// Accessibility
163165
tabIndex = 0,
@@ -289,12 +291,13 @@ const Slider = React.forwardRef<SliderRef, SliderProps<number | number[]>>((prop
289291
};
290292

291293
const finishChange = () => {
292-
onAfterChange?.(getTriggerValue(rawValuesRef.current));
294+
const finishValue = getTriggerValue(rawValuesRef.current);
295+
onAfterChange?.(finishValue);
293296
warning(
294297
!onAfterChange,
295298
'[rc-slider] `onAfterChange` is deprecated. Please use `onChangeComplete` instead.',
296299
);
297-
onChangeComplete?.(getTriggerValue(rawValuesRef.current));
300+
onChangeComplete?.(finishValue);
298301
};
299302

300303
const [draggingIndex, draggingValue, cacheValues, onStartDrag] = useDrag(
@@ -335,6 +338,7 @@ const Slider = React.forwardRef<SliderRef, SliderProps<number | number[]>>((prop
335338
onBeforeChange?.(getTriggerValue(cloneNextValues));
336339
triggerChange(cloneNextValues);
337340
if (e) {
341+
handlesRef.current.focus(valueIndex);
338342
onStartDrag(e, valueIndex, cloneNextValues);
339343
}
340344
}
@@ -543,6 +547,7 @@ const Slider = React.forwardRef<SliderRef, SliderProps<number | number[]>>((prop
543547
onFocus={onFocus}
544548
onBlur={onBlur}
545549
handleRender={handleRender}
550+
activeHandleRender={activeHandleRender}
546551
onChangeComplete={finishChange}
547552
/>
548553

src/hooks/useDrag.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useEvent } from 'rc-util';
12
import * as React from 'react';
23
import type { Direction, OnStartMove } from '../interface';
34
import type { OffsetValues } from './useOffset';
@@ -18,7 +19,12 @@ function useDrag(
1819
triggerChange: (values: number[]) => void,
1920
finishChange: () => void,
2021
offsetValues: OffsetValues,
21-
): [number, number, number[], OnStartMove] {
22+
): [
23+
draggingIndex: number,
24+
draggingValue: number,
25+
returnValues: number[],
26+
onStartMove: OnStartMove,
27+
] {
2228
const [draggingValue, setDraggingValue] = React.useState(null);
2329
const [draggingIndex, setDraggingIndex] = React.useState(-1);
2430
const [cacheValues, setCacheValues] = React.useState(rawValues);
@@ -27,7 +33,7 @@ function useDrag(
2733
const mouseMoveEventRef = React.useRef<(event: MouseEvent) => void>(null);
2834
const mouseUpEventRef = React.useRef<(event: MouseEvent) => void>(null);
2935

30-
React.useEffect(() => {
36+
React.useLayoutEffect(() => {
3137
if (draggingIndex === -1) {
3238
setCacheValues(rawValues);
3339
}
@@ -55,7 +61,7 @@ function useDrag(
5561
}
5662
};
5763

58-
const updateCacheValue = (valueIndex: number, offsetPercent: number) => {
64+
const updateCacheValue = useEvent((valueIndex: number, offsetPercent: number) => {
5965
// Basic point offset
6066

6167
if (valueIndex === -1) {
@@ -87,11 +93,7 @@ function useDrag(
8793

8894
flushValues(next.values, next.value);
8995
}
90-
};
91-
92-
// Resolve closure
93-
const updateCacheValueRef = React.useRef(updateCacheValue);
94-
updateCacheValueRef.current = updateCacheValue;
96+
});
9597

9698
const onStartMove: OnStartMove = (e, valueIndex, startValues?: number[]) => {
9799
e.stopPropagation();
@@ -133,7 +135,7 @@ function useDrag(
133135
default:
134136
offSetPercent = offsetX / width;
135137
}
136-
updateCacheValueRef.current(valueIndex, offSetPercent);
138+
updateCacheValue(valueIndex, offSetPercent);
137139
};
138140

139141
// End

tests/Range.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -511,12 +511,12 @@ describe('Range', () => {
511511
expect(handleFocus).toBeCalled();
512512
});
513513

514-
it('blur', () => {
514+
it('blur()', () => {
515515
const handleBlur = jest.fn();
516516
const { container } = render(<Slider range min={0} max={20} onBlur={handleBlur} />);
517517
container.getElementsByClassName('rc-slider-handle')[0].focus();
518518
container.getElementsByClassName('rc-slider-handle')[0].blur();
519-
expect(handleBlur).toBeCalled();
519+
expect(handleBlur).toHaveBeenCalled();
520520
});
521521
});
522522

0 commit comments

Comments
 (0)