@cdx-ui/primitives 0.0.1-beta.170 → 0.0.1-beta.172
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/accordion/Accordion.js +59 -0
- package/lib/commonjs/accordion/Accordion.js.map +1 -0
- package/lib/commonjs/accordion/AccordionItem.js +68 -0
- package/lib/commonjs/accordion/AccordionItem.js.map +1 -0
- package/lib/commonjs/accordion/AccordionPanel.js +71 -0
- package/lib/commonjs/accordion/AccordionPanel.js.map +1 -0
- package/lib/commonjs/accordion/AccordionTrigger.js +122 -0
- package/lib/commonjs/accordion/AccordionTrigger.js.map +1 -0
- package/lib/commonjs/accordion/context.js +26 -0
- package/lib/commonjs/accordion/context.js.map +1 -0
- package/lib/commonjs/accordion/index.js +59 -0
- package/lib/commonjs/accordion/index.js.map +1 -0
- package/lib/commonjs/accordion/types.js +6 -0
- package/lib/commonjs/accordion/types.js.map +1 -0
- package/lib/commonjs/accordion/utils.js +24 -0
- package/lib/commonjs/accordion/utils.js.map +1 -0
- package/lib/commonjs/index.js +12 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/slider/SliderRail.js +35 -9
- package/lib/commonjs/slider/SliderRail.js.map +1 -1
- package/lib/commonjs/slider/SliderRoot.js +155 -97
- package/lib/commonjs/slider/SliderRoot.js.map +1 -1
- package/lib/commonjs/slider/SliderThumb.js +182 -68
- package/lib/commonjs/slider/SliderThumb.js.map +1 -1
- package/lib/commonjs/slider/context.js +4 -1
- package/lib/commonjs/slider/context.js.map +1 -1
- package/lib/commonjs/slider/index.js +36 -0
- package/lib/commonjs/slider/index.js.map +1 -1
- package/lib/commonjs/slider/useSliderPointer.js +1 -1
- package/lib/commonjs/slider/useSliderPointer.js.map +1 -1
- package/lib/commonjs/slider/utils.js +68 -0
- package/lib/commonjs/slider/utils.js.map +1 -1
- package/lib/module/accordion/Accordion.js +55 -0
- package/lib/module/accordion/Accordion.js.map +1 -0
- package/lib/module/accordion/AccordionItem.js +64 -0
- package/lib/module/accordion/AccordionItem.js.map +1 -0
- package/lib/module/accordion/AccordionPanel.js +67 -0
- package/lib/module/accordion/AccordionPanel.js.map +1 -0
- package/lib/module/accordion/AccordionTrigger.js +118 -0
- package/lib/module/accordion/AccordionTrigger.js.map +1 -0
- package/lib/module/accordion/context.js +20 -0
- package/lib/module/accordion/context.js.map +1 -0
- package/lib/module/accordion/index.js +8 -0
- package/lib/module/accordion/index.js.map +1 -0
- package/lib/module/accordion/types.js +4 -0
- package/lib/module/accordion/types.js.map +1 -0
- package/lib/module/accordion/utils.js +19 -0
- package/lib/module/accordion/utils.js.map +1 -0
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/slider/SliderRail.js +35 -9
- package/lib/module/slider/SliderRail.js.map +1 -1
- package/lib/module/slider/SliderRoot.js +157 -99
- package/lib/module/slider/SliderRoot.js.map +1 -1
- package/lib/module/slider/SliderThumb.js +185 -71
- package/lib/module/slider/SliderThumb.js.map +1 -1
- package/lib/module/slider/context.js +1 -0
- package/lib/module/slider/context.js.map +1 -1
- package/lib/module/slider/index.js +2 -2
- package/lib/module/slider/index.js.map +1 -1
- package/lib/module/slider/useSliderPointer.js +1 -1
- package/lib/module/slider/useSliderPointer.js.map +1 -1
- package/lib/module/slider/utils.js +63 -0
- package/lib/module/slider/utils.js.map +1 -1
- package/lib/typescript/accordion/Accordion.d.ts +8 -0
- package/lib/typescript/accordion/Accordion.d.ts.map +1 -0
- package/lib/typescript/accordion/AccordionItem.d.ts +8 -0
- package/lib/typescript/accordion/AccordionItem.d.ts.map +1 -0
- package/lib/typescript/accordion/AccordionPanel.d.ts +7 -0
- package/lib/typescript/accordion/AccordionPanel.d.ts.map +1 -0
- package/lib/typescript/accordion/AccordionTrigger.d.ts +6 -0
- package/lib/typescript/accordion/AccordionTrigger.d.ts.map +1 -0
- package/lib/typescript/accordion/context.d.ts +6 -0
- package/lib/typescript/accordion/context.d.ts.map +1 -0
- package/lib/typescript/accordion/index.d.ts +7 -0
- package/lib/typescript/accordion/index.d.ts.map +1 -0
- package/lib/typescript/accordion/types.d.ts +50 -0
- package/lib/typescript/accordion/types.d.ts.map +1 -0
- package/lib/typescript/accordion/utils.d.ts +4 -0
- package/lib/typescript/accordion/utils.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +1 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/slider/SliderRail.d.ts.map +1 -1
- package/lib/typescript/slider/SliderRoot.d.ts +2 -2
- package/lib/typescript/slider/SliderRoot.d.ts.map +1 -1
- package/lib/typescript/slider/SliderThumb.d.ts.map +1 -1
- package/lib/typescript/slider/context.d.ts +53 -9
- package/lib/typescript/slider/context.d.ts.map +1 -1
- package/lib/typescript/slider/index.d.ts +4 -4
- package/lib/typescript/slider/index.d.ts.map +1 -1
- package/lib/typescript/slider/types.d.ts +44 -8
- package/lib/typescript/slider/types.d.ts.map +1 -1
- package/lib/typescript/slider/utils.d.ts +8 -0
- package/lib/typescript/slider/utils.d.ts.map +1 -1
- package/package.json +5 -3
- package/src/accordion/Accordion.tsx +72 -0
- package/src/accordion/AccordionItem.tsx +70 -0
- package/src/accordion/AccordionPanel.tsx +72 -0
- package/src/accordion/AccordionTrigger.tsx +120 -0
- package/src/accordion/context.tsx +22 -0
- package/src/accordion/index.ts +21 -0
- package/src/accordion/types.ts +56 -0
- package/src/accordion/utils.ts +22 -0
- package/src/index.ts +1 -0
- package/src/slider/SliderRail.tsx +56 -8
- package/src/slider/SliderRoot.tsx +255 -136
- package/src/slider/SliderThumb.tsx +240 -88
- package/src/slider/context.tsx +53 -9
- package/src/slider/index.ts +9 -2
- package/src/slider/types.ts +49 -8
- package/src/slider/useSliderPointer.ts +1 -1
- package/src/slider/utils.ts +76 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { forwardRef, useCallback, useMemo, useRef, type ComponentRef } from 'react';
|
|
1
|
+
import { forwardRef, useCallback, useEffect, useMemo, useRef, type ComponentRef } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
PanResponder,
|
|
4
4
|
Platform,
|
|
5
5
|
Pressable,
|
|
6
6
|
View,
|
|
7
|
+
type AccessibilityActionEvent,
|
|
7
8
|
type GestureResponderEvent,
|
|
8
9
|
type LayoutChangeEvent,
|
|
9
10
|
type PressableProps,
|
|
@@ -15,10 +16,32 @@ import { useHover, usePress } from '@react-native-aria/interactions';
|
|
|
15
16
|
import { Slot } from '../slot';
|
|
16
17
|
import { assignRef } from '../utils/assignRef';
|
|
17
18
|
import { dataAttributes } from '../utils/dataAttributes';
|
|
18
|
-
import { useSliderContext } from './context';
|
|
19
|
+
import { SliderThumbProvider, useSliderContext } from './context';
|
|
19
20
|
import type { InterfaceSliderThumbProps } from './types';
|
|
20
21
|
import { getPointerCoords, useWebSliderDrag } from './useSliderPointer';
|
|
21
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
clamp,
|
|
24
|
+
formatSliderValue,
|
|
25
|
+
getThumbTravel,
|
|
26
|
+
percentToThumbOffset,
|
|
27
|
+
valueToPercent,
|
|
28
|
+
} from './utils';
|
|
29
|
+
|
|
30
|
+
const STEP_KEYS = new Set(['ArrowUp', 'ArrowRight', 'ArrowDown', 'ArrowLeft', 'Home', 'End']);
|
|
31
|
+
|
|
32
|
+
function getDefaultThumbAccessibilityLabel(
|
|
33
|
+
index: number,
|
|
34
|
+
thumbCount: number,
|
|
35
|
+
groupAccessibilityLabel?: string,
|
|
36
|
+
): string | undefined {
|
|
37
|
+
if (thumbCount === 2) {
|
|
38
|
+
return index === 0 ? 'Minimum' : 'Maximum';
|
|
39
|
+
}
|
|
40
|
+
if (thumbCount > 2) {
|
|
41
|
+
return `Value ${String(index + 1)} of ${String(thumbCount)}`;
|
|
42
|
+
}
|
|
43
|
+
return groupAccessibilityLabel;
|
|
44
|
+
}
|
|
22
45
|
|
|
23
46
|
export interface SliderThumbProps extends InterfaceSliderThumbProps {}
|
|
24
47
|
|
|
@@ -26,7 +49,10 @@ export const SliderThumb = forwardRef<ComponentRef<typeof Pressable>, SliderThum
|
|
|
26
49
|
(
|
|
27
50
|
{
|
|
28
51
|
asChild,
|
|
52
|
+
index = 0,
|
|
29
53
|
style,
|
|
54
|
+
accessibilityLabel: accessibilityLabelProp,
|
|
55
|
+
accessibilityValue: accessibilityValueProp,
|
|
30
56
|
onLayout,
|
|
31
57
|
onHoverIn,
|
|
32
58
|
onHoverOut,
|
|
@@ -40,32 +66,52 @@ export const SliderThumb = forwardRef<ComponentRef<typeof Pressable>, SliderThum
|
|
|
40
66
|
ref,
|
|
41
67
|
) => {
|
|
42
68
|
const {
|
|
43
|
-
|
|
69
|
+
values,
|
|
70
|
+
min,
|
|
71
|
+
max,
|
|
72
|
+
step,
|
|
44
73
|
orientation,
|
|
45
74
|
isDisabled,
|
|
46
|
-
|
|
75
|
+
thumbCount,
|
|
76
|
+
groupAccessibilityLabel,
|
|
77
|
+
accessibilityValueProp: rootAccessibilityValueProp,
|
|
47
78
|
trackLayout,
|
|
48
79
|
thumbSize,
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
80
|
+
getThumbPercent,
|
|
81
|
+
getThumbMinValue,
|
|
82
|
+
getThumbMaxValue,
|
|
83
|
+
setThumbValue,
|
|
84
|
+
setThumbValueFromRatio,
|
|
85
|
+
setDraggingIndex,
|
|
86
|
+
setHoveredIndex,
|
|
87
|
+
setFocusedIndex,
|
|
53
88
|
setThumbSize,
|
|
54
|
-
|
|
89
|
+
registerThumb,
|
|
90
|
+
isThumbDragging,
|
|
91
|
+
thumbRef: deprecatedThumbRef,
|
|
55
92
|
} = useSliderContext();
|
|
56
93
|
|
|
94
|
+
const value = values[index] ?? min;
|
|
95
|
+
const percent = getThumbPercent(index);
|
|
96
|
+
const isDragging = isThumbDragging(index);
|
|
97
|
+
const thumbRef = useRef<View>(null);
|
|
98
|
+
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
return registerThumb(index);
|
|
101
|
+
}, [index, registerThumb]);
|
|
102
|
+
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
if (index === 0) {
|
|
105
|
+
deprecatedThumbRef.current = thumbRef.current;
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
57
109
|
const percentRef = useRef(percent);
|
|
58
110
|
percentRef.current = percent;
|
|
59
111
|
|
|
60
112
|
const dragStartRatio = useRef(0);
|
|
61
113
|
const lastRatioRef = useRef(percent / 100);
|
|
62
114
|
|
|
63
|
-
// The native PanResponder is created once (stable across renders) so an
|
|
64
|
-
// in-flight gesture is never handed a fresh internal `gestureState`
|
|
65
|
-
// mid-drag. In controlled mode `setValueFromRatio`/`trackLayout` change
|
|
66
|
-
// identity on every value update, so the handlers read the current values
|
|
67
|
-
// through refs instead of closing over them (which would force the
|
|
68
|
-
// `useMemo` to re-create the responder on every move and reset `dx`).
|
|
69
115
|
const orientationRef = useRef(orientation);
|
|
70
116
|
orientationRef.current = orientation;
|
|
71
117
|
const trackLayoutRef = useRef(trackLayout);
|
|
@@ -74,10 +120,12 @@ export const SliderThumb = forwardRef<ComponentRef<typeof Pressable>, SliderThum
|
|
|
74
120
|
thumbSizeRef.current = thumbSize;
|
|
75
121
|
const isDisabledRef = useRef(isDisabled);
|
|
76
122
|
isDisabledRef.current = isDisabled;
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
|
|
123
|
+
const indexRef = useRef(index);
|
|
124
|
+
indexRef.current = index;
|
|
125
|
+
const setThumbValueFromRatioRef = useRef(setThumbValueFromRatio);
|
|
126
|
+
setThumbValueFromRatioRef.current = setThumbValueFromRatio;
|
|
127
|
+
const setDraggingIndexRef = useRef(setDraggingIndex);
|
|
128
|
+
setDraggingIndexRef.current = setDraggingIndex;
|
|
81
129
|
|
|
82
130
|
const { hoverProps, isHovered } = useHover();
|
|
83
131
|
const { focusProps, isFocused } = useFocus();
|
|
@@ -90,10 +138,24 @@ export const SliderThumb = forwardRef<ComponentRef<typeof Pressable>, SliderThum
|
|
|
90
138
|
pressProps: Pick<PressableProps, 'onPressIn' | 'onPressOut'>;
|
|
91
139
|
};
|
|
92
140
|
|
|
141
|
+
const setRatioForThumb = useCallback(
|
|
142
|
+
(ratio: number, complete?: boolean) => {
|
|
143
|
+
setThumbValueFromRatio(index, ratio, complete);
|
|
144
|
+
},
|
|
145
|
+
[index, setThumbValueFromRatio],
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
const setDraggingForThumb = useCallback(
|
|
149
|
+
(dragging: boolean) => {
|
|
150
|
+
setDraggingIndex(dragging ? index : null);
|
|
151
|
+
},
|
|
152
|
+
[index, setDraggingIndex],
|
|
153
|
+
);
|
|
154
|
+
|
|
93
155
|
const { startDrag } = useWebSliderDrag({
|
|
94
156
|
isDisabled,
|
|
95
|
-
setIsDragging,
|
|
96
|
-
setValueFromRatio,
|
|
157
|
+
setIsDragging: setDraggingForThumb,
|
|
158
|
+
setValueFromRatio: setRatioForThumb,
|
|
97
159
|
trackLayout,
|
|
98
160
|
orientation,
|
|
99
161
|
thumbSize,
|
|
@@ -108,7 +170,7 @@ export const SliderThumb = forwardRef<ComponentRef<typeof Pressable>, SliderThum
|
|
|
108
170
|
if (isDisabledRef.current) {
|
|
109
171
|
return;
|
|
110
172
|
}
|
|
111
|
-
|
|
173
|
+
setDraggingIndexRef.current(indexRef.current);
|
|
112
174
|
dragStartRatio.current = percentRef.current / 100;
|
|
113
175
|
lastRatioRef.current = dragStartRatio.current;
|
|
114
176
|
},
|
|
@@ -126,15 +188,15 @@ export const SliderThumb = forwardRef<ComponentRef<typeof Pressable>, SliderThum
|
|
|
126
188
|
const delta = isHorizontal ? gestureState.dx : -gestureState.dy;
|
|
127
189
|
const nextRatio = clamp(dragStartRatio.current + delta / travel, 0, 1);
|
|
128
190
|
lastRatioRef.current = nextRatio;
|
|
129
|
-
|
|
191
|
+
setThumbValueFromRatioRef.current(indexRef.current, nextRatio);
|
|
130
192
|
},
|
|
131
193
|
onPanResponderRelease: () => {
|
|
132
|
-
|
|
133
|
-
|
|
194
|
+
setDraggingIndexRef.current(null);
|
|
195
|
+
setThumbValueFromRatioRef.current(indexRef.current, lastRatioRef.current, true);
|
|
134
196
|
},
|
|
135
197
|
onPanResponderTerminate: () => {
|
|
136
|
-
|
|
137
|
-
|
|
198
|
+
setDraggingIndexRef.current(null);
|
|
199
|
+
setThumbValueFromRatioRef.current(indexRef.current, lastRatioRef.current, true);
|
|
138
200
|
},
|
|
139
201
|
}),
|
|
140
202
|
[],
|
|
@@ -149,13 +211,15 @@ export const SliderThumb = forwardRef<ComponentRef<typeof Pressable>, SliderThum
|
|
|
149
211
|
: trackLayout.height;
|
|
150
212
|
const offset = percentToThumbOffset(percent, trackSize, thumbSize);
|
|
151
213
|
|
|
214
|
+
const zIndex = isDragging ? 3 : 2;
|
|
215
|
+
|
|
152
216
|
if (orientation === 'vertical') {
|
|
153
217
|
return {
|
|
154
218
|
position: 'absolute',
|
|
155
219
|
left: '50%',
|
|
156
220
|
bottom: offset,
|
|
157
221
|
marginLeft: -thumbSize / 2,
|
|
158
|
-
zIndex
|
|
222
|
+
zIndex,
|
|
159
223
|
};
|
|
160
224
|
}
|
|
161
225
|
|
|
@@ -164,9 +228,9 @@ export const SliderThumb = forwardRef<ComponentRef<typeof Pressable>, SliderThum
|
|
|
164
228
|
top: '50%',
|
|
165
229
|
left: offset,
|
|
166
230
|
transform: [{ translateY: -thumbSize / 2 }],
|
|
167
|
-
zIndex
|
|
231
|
+
zIndex,
|
|
168
232
|
};
|
|
169
|
-
}, [orientation, percent, thumbSize, trackLayout]);
|
|
233
|
+
}, [isDragging, orientation, percent, thumbSize, trackLayout]);
|
|
170
234
|
|
|
171
235
|
const handleLayout = useCallback(
|
|
172
236
|
(event: LayoutChangeEvent) => {
|
|
@@ -175,9 +239,7 @@ export const SliderThumb = forwardRef<ComponentRef<typeof Pressable>, SliderThum
|
|
|
175
239
|
if (measured > 0) {
|
|
176
240
|
setThumbSize(measured);
|
|
177
241
|
}
|
|
178
|
-
|
|
179
|
-
onLayout(event);
|
|
180
|
-
}
|
|
242
|
+
onLayout?.(event);
|
|
181
243
|
},
|
|
182
244
|
[onLayout, orientation, setThumbSize],
|
|
183
245
|
);
|
|
@@ -195,72 +257,162 @@ export const SliderThumb = forwardRef<ComponentRef<typeof Pressable>, SliderThum
|
|
|
195
257
|
[isDisabled, startDrag],
|
|
196
258
|
);
|
|
197
259
|
|
|
198
|
-
const
|
|
260
|
+
const thumbMin = getThumbMinValue(index);
|
|
261
|
+
const thumbMax = getThumbMaxValue(index);
|
|
262
|
+
|
|
263
|
+
const accessibilityValue = useMemo(() => {
|
|
264
|
+
const thumbPercent = valueToPercent(value, min, max);
|
|
265
|
+
const defaults = {
|
|
266
|
+
min: 0,
|
|
267
|
+
max: 100,
|
|
268
|
+
now: Math.round(thumbPercent),
|
|
269
|
+
text: formatSliderValue(value),
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
const mergedRoot = rootAccessibilityValueProp
|
|
273
|
+
? { ...defaults, ...rootAccessibilityValueProp }
|
|
274
|
+
: defaults;
|
|
275
|
+
if (accessibilityValueProp === undefined) {
|
|
276
|
+
return mergedRoot;
|
|
277
|
+
}
|
|
278
|
+
return { ...mergedRoot, ...accessibilityValueProp };
|
|
279
|
+
}, [accessibilityValueProp, max, min, rootAccessibilityValueProp, value]);
|
|
280
|
+
|
|
281
|
+
const resolvedAccessibilityLabel =
|
|
282
|
+
accessibilityLabelProp ??
|
|
283
|
+
getDefaultThumbAccessibilityLabel(index, thumbCount, groupAccessibilityLabel);
|
|
199
284
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
285
|
+
const handleKeyDown = useCallback(
|
|
286
|
+
(event: { nativeEvent: { key?: string }; preventDefault?: () => void }) => {
|
|
287
|
+
if (isDisabled) {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
const key = event.nativeEvent.key;
|
|
291
|
+
if (!key || !STEP_KEYS.has(key)) {
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
if (Platform.OS === 'web') {
|
|
295
|
+
event.preventDefault?.();
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
let next = value;
|
|
299
|
+
if (key === 'ArrowUp' || key === 'ArrowRight') {
|
|
300
|
+
next = value + step;
|
|
301
|
+
} else if (key === 'ArrowDown' || key === 'ArrowLeft') {
|
|
302
|
+
next = value - step;
|
|
303
|
+
} else if (key === 'Home') {
|
|
304
|
+
next = thumbMin;
|
|
305
|
+
} else if (key === 'End') {
|
|
306
|
+
next = thumbMax;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
setThumbValue(index, next, true);
|
|
310
|
+
},
|
|
311
|
+
[index, isDisabled, setThumbValue, step, thumbMax, thumbMin, value],
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
const handleAccessibilityAction = useCallback(
|
|
315
|
+
(event: AccessibilityActionEvent) => {
|
|
316
|
+
if (isDisabled) {
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
if (event.nativeEvent.actionName === 'increment') {
|
|
320
|
+
setThumbValue(index, value + step, true);
|
|
321
|
+
} else if (event.nativeEvent.actionName === 'decrement') {
|
|
322
|
+
setThumbValue(index, value - step, true);
|
|
323
|
+
}
|
|
324
|
+
},
|
|
325
|
+
[index, isDisabled, setThumbValue, step, value],
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
const thumbContextValue = useMemo(
|
|
329
|
+
() => ({
|
|
330
|
+
index,
|
|
331
|
+
value,
|
|
332
|
+
percent,
|
|
333
|
+
isDragging,
|
|
334
|
+
isHovered,
|
|
335
|
+
isFocused,
|
|
336
|
+
thumbRef,
|
|
337
|
+
}),
|
|
338
|
+
[index, isDragging, isFocused, isHovered, percent, value],
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
const isWeb = Platform.OS === 'web';
|
|
206
342
|
const Comp = asChild ? Slot : isWeb ? Pressable : View;
|
|
207
343
|
|
|
208
344
|
const setThumbRef = useCallback(
|
|
209
345
|
(node: ComponentRef<typeof Pressable> | null) => {
|
|
210
346
|
thumbRef.current = node;
|
|
347
|
+
if (index === 0) {
|
|
348
|
+
deprecatedThumbRef.current = node;
|
|
349
|
+
}
|
|
211
350
|
assignRef(ref, node);
|
|
212
351
|
},
|
|
213
|
-
[
|
|
352
|
+
[deprecatedThumbRef, index, ref],
|
|
214
353
|
);
|
|
215
354
|
|
|
216
|
-
const webInteractionProps
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
355
|
+
const webInteractionProps =
|
|
356
|
+
Platform.OS === 'web'
|
|
357
|
+
? ({
|
|
358
|
+
onPointerDown: handlePointerDown as unknown as PressableProps['onPointerDown'],
|
|
359
|
+
onHoverIn: composeEventHandlers(
|
|
360
|
+
onHoverIn,
|
|
361
|
+
composeEventHandlers(hoverProps.onHoverIn, () => setHoveredIndex(index)),
|
|
362
|
+
),
|
|
363
|
+
onHoverOut: composeEventHandlers(
|
|
364
|
+
onHoverOut,
|
|
365
|
+
composeEventHandlers(hoverProps.onHoverOut, () => setHoveredIndex(null)),
|
|
366
|
+
),
|
|
367
|
+
onPressIn: composeEventHandlers(onPressIn, pressableProps.onPressIn),
|
|
368
|
+
onPressOut: composeEventHandlers(onPressOut, pressableProps.onPressOut),
|
|
369
|
+
disabled: isDisabled,
|
|
370
|
+
tabIndex: isDisabled ? -1 : 0,
|
|
371
|
+
onKeyDown: handleKeyDown,
|
|
372
|
+
role: 'slider',
|
|
373
|
+
'aria-valuemin': thumbMin,
|
|
374
|
+
'aria-valuemax': thumbMax,
|
|
375
|
+
'aria-valuenow': value,
|
|
376
|
+
'aria-orientation': orientation,
|
|
377
|
+
'aria-disabled': isDisabled,
|
|
378
|
+
} as PressableProps)
|
|
379
|
+
: ({} as PressableProps);
|
|
231
380
|
|
|
232
381
|
return (
|
|
233
|
-
<
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
composeEventHandlers(
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
composeEventHandlers(
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
382
|
+
<SliderThumbProvider value={thumbContextValue}>
|
|
383
|
+
<Comp
|
|
384
|
+
ref={setThumbRef}
|
|
385
|
+
{...(props as PressableProps)}
|
|
386
|
+
{...(isWeb ? webInteractionProps : panResponder.panHandlers)}
|
|
387
|
+
onLayout={handleLayout}
|
|
388
|
+
onFocus={composeEventHandlers(
|
|
389
|
+
composeEventHandlers(onFocus as (...args: unknown[]) => unknown, focusProps.onFocus),
|
|
390
|
+
composeEventHandlers(focusRingProps.onFocus, () => setFocusedIndex(index)),
|
|
391
|
+
)}
|
|
392
|
+
onBlur={composeEventHandlers(
|
|
393
|
+
composeEventHandlers(onBlur as (...args: unknown[]) => unknown, focusProps.onBlur),
|
|
394
|
+
composeEventHandlers(focusRingProps.onBlur, () => setFocusedIndex(null)),
|
|
395
|
+
)}
|
|
396
|
+
style={[thumbStyle, style]}
|
|
397
|
+
accessibilityRole="adjustable"
|
|
398
|
+
accessibilityLabel={resolvedAccessibilityLabel}
|
|
399
|
+
accessibilityValue={accessibilityValue}
|
|
400
|
+
accessibilityActions={[{ name: 'increment' }, { name: 'decrement' }]}
|
|
401
|
+
onAccessibilityAction={handleAccessibilityAction}
|
|
402
|
+
accessibilityState={{ disabled: isDisabled }}
|
|
403
|
+
{...dataAttributes({
|
|
404
|
+
slot: 'slider-thumb',
|
|
405
|
+
hover: isHovered,
|
|
406
|
+
focus: isFocused,
|
|
407
|
+
focusVisible: isFocusVisible,
|
|
408
|
+
active: isPressed || isDragging,
|
|
409
|
+
dragging: isDragging,
|
|
410
|
+
disabled: isDisabled,
|
|
411
|
+
})}
|
|
412
|
+
>
|
|
413
|
+
{children}
|
|
414
|
+
</Comp>
|
|
415
|
+
</SliderThumbProvider>
|
|
264
416
|
);
|
|
265
417
|
},
|
|
266
418
|
);
|
package/src/slider/context.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { RefObject } from 'react';
|
|
2
2
|
import type { View } from 'react-native';
|
|
3
3
|
import { createContext } from '@cdx-ui/utils';
|
|
4
|
-
import type { SliderOrientation } from './types';
|
|
4
|
+
import type { SliderAccessibilityValue, SliderOrientation } from './types';
|
|
5
5
|
|
|
6
6
|
export interface SliderTrackLayout {
|
|
7
7
|
readonly width: number;
|
|
@@ -11,28 +11,72 @@ export interface SliderTrackLayout {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export interface SliderContextValue {
|
|
14
|
-
readonly
|
|
14
|
+
readonly values: readonly number[];
|
|
15
15
|
readonly min: number;
|
|
16
16
|
readonly max: number;
|
|
17
17
|
readonly step: number;
|
|
18
|
-
readonly percent: number;
|
|
19
18
|
readonly orientation: SliderOrientation;
|
|
20
19
|
readonly isDisabled: boolean;
|
|
21
|
-
readonly
|
|
22
|
-
readonly
|
|
23
|
-
readonly
|
|
20
|
+
readonly draggingIndex: number | null;
|
|
21
|
+
readonly hoveredIndex: number | null;
|
|
22
|
+
readonly focusedIndex: number | null;
|
|
24
23
|
readonly trackLayout: SliderTrackLayout | null;
|
|
25
24
|
readonly thumbSize: number;
|
|
26
|
-
readonly
|
|
27
|
-
readonly
|
|
25
|
+
readonly thumbCount: number;
|
|
26
|
+
readonly groupAccessibilityLabel?: string;
|
|
27
|
+
readonly accessibilityValueProp?: SliderAccessibilityValue;
|
|
28
|
+
readonly getThumbPercent: (index: number) => number;
|
|
29
|
+
readonly getThumbMinValue: (index: number) => number;
|
|
30
|
+
readonly getThumbMaxValue: (index: number) => number;
|
|
31
|
+
readonly setThumbValue: (index: number, value: number, complete?: boolean) => void;
|
|
32
|
+
readonly setThumbValueFromRatio: (index: number, ratio: number, complete?: boolean) => void;
|
|
33
|
+
readonly setNearestThumbValueFromRatio: (ratio: number, complete?: boolean) => void;
|
|
28
34
|
readonly setTrackLayout: (layout: SliderTrackLayout) => void;
|
|
29
35
|
readonly setThumbSize: (size: number) => void;
|
|
36
|
+
readonly setDraggingIndex: (index: number | null) => void;
|
|
37
|
+
readonly setHoveredIndex: (index: number | null) => void;
|
|
38
|
+
readonly setFocusedIndex: (index: number | null) => void;
|
|
39
|
+
readonly getThumbValueLabel: (index: number) => string;
|
|
40
|
+
readonly registerThumb: (index: number) => () => void;
|
|
41
|
+
readonly isThumbDragging: (index: number) => boolean;
|
|
42
|
+
/** @deprecated Use `values[0]` */
|
|
43
|
+
readonly value: number;
|
|
44
|
+
/** @deprecated Use `getThumbPercent(0)` */
|
|
45
|
+
readonly percent: number;
|
|
46
|
+
/** @deprecated Use `draggingIndex !== null` */
|
|
47
|
+
readonly isDragging: boolean;
|
|
48
|
+
/** @deprecated Use `getThumbValueLabel(0)` */
|
|
49
|
+
readonly getValueLabel: () => string;
|
|
50
|
+
/** @deprecated Use `setThumbValue(0, …)` */
|
|
51
|
+
readonly setValue: (value: number, complete?: boolean) => void;
|
|
52
|
+
/** @deprecated Use `setNearestThumbValueFromRatio` */
|
|
53
|
+
readonly setValueFromRatio: (ratio: number, complete?: boolean) => void;
|
|
54
|
+
/** @deprecated Use per-thumb context `thumbRef` */
|
|
55
|
+
readonly thumbRef: RefObject<View | null>;
|
|
56
|
+
/** @deprecated Use `hoveredIndex !== null` */
|
|
57
|
+
readonly isHovered: boolean;
|
|
58
|
+
/** @deprecated Use `focusedIndex !== null` */
|
|
59
|
+
readonly isFocused: boolean;
|
|
60
|
+
/** @deprecated Use `setDraggingIndex` */
|
|
30
61
|
readonly setIsDragging: (dragging: boolean) => void;
|
|
62
|
+
/** @deprecated Use `setHoveredIndex` */
|
|
31
63
|
readonly setIsHovered: (hovered: boolean) => void;
|
|
64
|
+
/** @deprecated Use `setFocusedIndex` */
|
|
32
65
|
readonly setIsFocused: (focused: boolean) => void;
|
|
33
|
-
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface SliderThumbContextValue {
|
|
69
|
+
readonly index: number;
|
|
70
|
+
readonly value: number;
|
|
71
|
+
readonly percent: number;
|
|
72
|
+
readonly isDragging: boolean;
|
|
73
|
+
readonly isHovered: boolean;
|
|
74
|
+
readonly isFocused: boolean;
|
|
34
75
|
readonly thumbRef: RefObject<View | null>;
|
|
35
76
|
}
|
|
36
77
|
|
|
37
78
|
export const [SliderProvider, useSliderContext] =
|
|
38
79
|
createContext<SliderContextValue>('SliderContext');
|
|
80
|
+
|
|
81
|
+
export const [SliderThumbProvider, useSliderThumbContext] =
|
|
82
|
+
createContext<SliderThumbContextValue>('SliderThumbContext');
|
package/src/slider/index.ts
CHANGED
|
@@ -4,15 +4,22 @@ export type {
|
|
|
4
4
|
ISliderThumbProps,
|
|
5
5
|
SliderAccessibilityValue,
|
|
6
6
|
SliderOrientation,
|
|
7
|
+
SliderRangeValueProps,
|
|
8
|
+
SliderSingleValueProps,
|
|
7
9
|
} from './types';
|
|
8
|
-
export type { SliderContextValue, SliderTrackLayout } from './context';
|
|
9
|
-
export { useSliderContext } from './context';
|
|
10
|
+
export type { SliderContextValue, SliderThumbContextValue, SliderTrackLayout } from './context';
|
|
11
|
+
export { useSliderContext, useSliderThumbContext } from './context';
|
|
10
12
|
export { SliderRoot, type SliderRootProps } from './SliderRoot';
|
|
11
13
|
export { SliderRail, type SliderRailProps } from './SliderRail';
|
|
12
14
|
export { SliderThumb, type SliderThumbProps } from './SliderThumb';
|
|
13
15
|
export {
|
|
16
|
+
arraysEqual,
|
|
17
|
+
denormalizeSliderValue,
|
|
18
|
+
getClosestValueIndex,
|
|
19
|
+
getFillExtent,
|
|
14
20
|
getMarkerPositionStyle,
|
|
15
21
|
getMarkerStepValues,
|
|
22
|
+
normalizeSliderValue,
|
|
16
23
|
percentToFillExtent,
|
|
17
24
|
SLIDER_MARKER_SIZE,
|
|
18
25
|
valueToPercent,
|
package/src/slider/types.ts
CHANGED
|
@@ -5,30 +5,67 @@ export type SliderOrientation = 'horizontal' | 'vertical';
|
|
|
5
5
|
|
|
6
6
|
export type SliderAccessibilityValue = NonNullable<ViewProps['accessibilityValue']>;
|
|
7
7
|
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
readonly onChangeComplete?: (value: number) => void;
|
|
8
|
+
export type SliderValue = number | readonly number[];
|
|
9
|
+
|
|
10
|
+
interface SliderBaseProps extends Omit<ViewProps, 'children' | 'accessibilityValue'> {
|
|
11
|
+
/** Minimum selectable value. @default `0` */
|
|
13
12
|
readonly min?: number;
|
|
13
|
+
/** Maximum selectable value. @default `100` */
|
|
14
14
|
readonly max?: number;
|
|
15
|
+
/**
|
|
16
|
+
* Increment used to snap value updates and generate automatic `Slider.MarkerGroup` marks.
|
|
17
|
+
* @default `1`
|
|
18
|
+
*/
|
|
15
19
|
readonly step?: number;
|
|
20
|
+
/** Disables pointer, keyboard, and screen reader interaction. @default `false` */
|
|
16
21
|
readonly isDisabled?: boolean;
|
|
22
|
+
/** Slider axis. @default `'horizontal'` */
|
|
17
23
|
readonly orientation?: SliderOrientation;
|
|
18
24
|
/**
|
|
19
|
-
* Screen reader label when there is no visible label.
|
|
25
|
+
* Screen reader label for the slider group when there is no visible label.
|
|
20
26
|
*/
|
|
21
27
|
readonly accessibilityLabel?: string;
|
|
22
28
|
/**
|
|
23
|
-
* Screen reader value
|
|
24
|
-
* `text`
|
|
29
|
+
* Screen reader value override. Merged with derived per-thumb values when fields are omitted;
|
|
30
|
+
* `text` replaces the formatted announcement. Native numeric values are normalized to 0–100.
|
|
25
31
|
*/
|
|
26
32
|
readonly accessibilityValue?: SliderAccessibilityValue;
|
|
27
33
|
readonly children?: ReactNode;
|
|
28
34
|
}
|
|
29
35
|
|
|
36
|
+
export interface SliderSingleValueProps {
|
|
37
|
+
readonly value?: number;
|
|
38
|
+
/** Initial uncontrolled value. Defaults to `min`. */
|
|
39
|
+
readonly defaultValue?: number;
|
|
40
|
+
/** Called while the value changes. */
|
|
41
|
+
readonly onChange?: (value: number) => void;
|
|
42
|
+
/** Called when a pointer, keyboard, or screen reader interaction completes. */
|
|
43
|
+
readonly onChangeComplete?: (value: number) => void;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface SliderRangeValueProps {
|
|
47
|
+
readonly value?: readonly number[];
|
|
48
|
+
/** Initial uncontrolled thumb values. */
|
|
49
|
+
readonly defaultValue?: readonly number[];
|
|
50
|
+
/** Called while any thumb value changes. */
|
|
51
|
+
readonly onChange?: (value: number[]) => void;
|
|
52
|
+
/** Called when a pointer, keyboard, or screen reader interaction completes. */
|
|
53
|
+
readonly onChangeComplete?: (value: number[]) => void;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type InterfaceSliderProps = SliderBaseProps &
|
|
57
|
+
(SliderSingleValueProps | SliderRangeValueProps);
|
|
58
|
+
|
|
30
59
|
export type ISliderProps = InterfaceSliderProps;
|
|
31
60
|
|
|
61
|
+
/** Wider internal props for the root primitive implementation. */
|
|
62
|
+
export interface SliderRootImplementationProps extends SliderBaseProps {
|
|
63
|
+
readonly value?: number | readonly number[];
|
|
64
|
+
readonly defaultValue?: number | readonly number[];
|
|
65
|
+
readonly onChange?: ((value: number) => void) | ((value: number[]) => void);
|
|
66
|
+
readonly onChangeComplete?: ((value: number) => void) | ((value: number[]) => void);
|
|
67
|
+
}
|
|
68
|
+
|
|
32
69
|
export interface InterfaceSliderRailProps extends ViewProps {
|
|
33
70
|
readonly asChild?: boolean;
|
|
34
71
|
readonly children?: ReactNode;
|
|
@@ -38,6 +75,10 @@ export type ISliderRailProps = InterfaceSliderRailProps;
|
|
|
38
75
|
|
|
39
76
|
export interface InterfaceSliderThumbProps extends PressableProps {
|
|
40
77
|
readonly asChild?: boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Zero-based thumb index. Defaults to `0` for single-value sliders.
|
|
80
|
+
*/
|
|
81
|
+
readonly index?: number;
|
|
41
82
|
readonly children?: ReactNode;
|
|
42
83
|
}
|
|
43
84
|
|
|
@@ -77,8 +77,8 @@ export function useWebSliderDrag({
|
|
|
77
77
|
}
|
|
78
78
|
draggingRef.current = false;
|
|
79
79
|
dragContextRef.current = null;
|
|
80
|
-
setIsDragging(false);
|
|
81
80
|
setValueFromRatio(lastRatioRef.current, true);
|
|
81
|
+
setIsDragging(false);
|
|
82
82
|
}, [setIsDragging, setValueFromRatio]);
|
|
83
83
|
|
|
84
84
|
const startDrag = useCallback(
|