@react-aria/slider 3.8.4 → 3.9.0

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.
@@ -1,275 +0,0 @@
1
- import {AriaSliderThumbProps} from '@react-types/slider';
2
- import {clamp, focusWithoutScrolling, mergeProps, useFormReset, useGlobalListeners} from '@react-aria/utils';
3
- import {DOMAttributes, RefObject} from '@react-types/shared';
4
- import {getSliderThumbId, sliderData} from './utils';
5
- import React, {ChangeEvent, InputHTMLAttributes, LabelHTMLAttributes, useCallback, useEffect, useRef} from 'react';
6
- import {SliderState} from '@react-stately/slider';
7
- import {useFocusable, useKeyboard, useMove} from '@react-aria/interactions';
8
- import {useLabel} from '@react-aria/label';
9
- import {useLocale} from '@react-aria/i18n';
10
-
11
- export interface SliderThumbAria {
12
- /** Props for the root thumb element; handles the dragging motion. */
13
- thumbProps: DOMAttributes,
14
-
15
- /** Props for the visually hidden range input element. */
16
- inputProps: InputHTMLAttributes<HTMLInputElement>,
17
-
18
- /** Props for the label element for this thumb (optional). */
19
- labelProps: LabelHTMLAttributes<HTMLLabelElement>,
20
-
21
- /** Whether this thumb is currently being dragged. */
22
- isDragging: boolean,
23
- /** Whether the thumb is currently focused. */
24
- isFocused: boolean,
25
- /** Whether the thumb is disabled. */
26
- isDisabled: boolean
27
- }
28
-
29
- export interface AriaSliderThumbOptions extends AriaSliderThumbProps {
30
- /** A ref to the track element. */
31
- trackRef: RefObject<Element | null>,
32
- /** A ref to the thumb input element. */
33
- inputRef: RefObject<HTMLInputElement | null>
34
- }
35
-
36
- /**
37
- * Provides behavior and accessibility for a thumb of a slider component.
38
- *
39
- * @param opts Options for this Slider thumb.
40
- * @param state Slider state, created via `useSliderState`.
41
- */
42
- export function useSliderThumb(
43
- opts: AriaSliderThumbOptions,
44
- state: SliderState
45
- ): SliderThumbAria {
46
- let {
47
- index = 0,
48
- isRequired,
49
- validationState,
50
- isInvalid,
51
- trackRef,
52
- inputRef,
53
- orientation = state.orientation,
54
- name,
55
- form
56
- } = opts;
57
-
58
- let isDisabled = opts.isDisabled || state.isDisabled;
59
- let isVertical = orientation === 'vertical';
60
-
61
- let {direction} = useLocale();
62
- let {addGlobalListener, removeGlobalListener} = useGlobalListeners();
63
-
64
- let data = sliderData.get(state)!;
65
- const {labelProps, fieldProps} = useLabel({
66
- ...opts,
67
- id: getSliderThumbId(state, index),
68
- 'aria-labelledby': `${data.id} ${opts['aria-labelledby'] ?? ''}`.trim()
69
- });
70
-
71
- const value = state.values[index];
72
-
73
- const focusInput = useCallback(() => {
74
- if (inputRef.current) {
75
- focusWithoutScrolling(inputRef.current);
76
- }
77
- }, [inputRef]);
78
-
79
- const isFocused = state.focusedThumb === index;
80
-
81
- useEffect(() => {
82
- if (isFocused) {
83
- focusInput();
84
- }
85
- }, [isFocused, focusInput]);
86
-
87
- let reverseX = direction === 'rtl';
88
- let currentPosition = useRef<number>(null);
89
-
90
- let {keyboardProps} = useKeyboard({
91
- onKeyDown(e) {
92
- let {
93
- getThumbMaxValue,
94
- getThumbMinValue,
95
- decrementThumb,
96
- incrementThumb,
97
- setThumbValue,
98
- setThumbDragging,
99
- pageSize
100
- } = state;
101
- // these are the cases that useMove or useSlider don't handle
102
- if (!/^(PageUp|PageDown|Home|End)$/.test(e.key)) {
103
- e.continuePropagation();
104
- return;
105
- }
106
- // same handling as useMove, stopPropagation to prevent useSlider from handling the event as well.
107
- e.preventDefault();
108
- // remember to set this so that onChangeEnd is fired
109
- setThumbDragging(index, true);
110
- switch (e.key) {
111
- case 'PageUp':
112
- incrementThumb(index, pageSize);
113
- break;
114
- case 'PageDown':
115
- decrementThumb(index, pageSize);
116
- break;
117
- case 'Home':
118
- setThumbValue(index, getThumbMinValue(index));
119
- break;
120
- case 'End':
121
- setThumbValue(index, getThumbMaxValue(index));
122
- break;
123
- }
124
- setThumbDragging(index, false);
125
- }
126
- });
127
-
128
- let {moveProps} = useMove({
129
- onMoveStart() {
130
- currentPosition.current = null;
131
- state.setThumbDragging(index, true);
132
- },
133
- onMove({deltaX, deltaY, pointerType, shiftKey}) {
134
- const {
135
- getThumbPercent,
136
- setThumbPercent,
137
- decrementThumb,
138
- incrementThumb,
139
- step,
140
- pageSize
141
- } = state;
142
- if (!trackRef.current) {
143
- return;
144
- }
145
- let {width, height} = trackRef.current.getBoundingClientRect();
146
- let size = isVertical ? height : width;
147
-
148
- if (currentPosition.current == null) {
149
- currentPosition.current = getThumbPercent(index) * size;
150
- }
151
- if (pointerType === 'keyboard') {
152
- if ((deltaX > 0 && reverseX) || (deltaX < 0 && !reverseX) || deltaY > 0) {
153
- decrementThumb(index, shiftKey ? pageSize : step);
154
- } else {
155
- incrementThumb(index, shiftKey ? pageSize : step);
156
- }
157
- } else {
158
- let delta = isVertical ? deltaY : deltaX;
159
- if (isVertical || reverseX) {
160
- delta = -delta;
161
- }
162
-
163
- currentPosition.current += delta;
164
- setThumbPercent(index, clamp(currentPosition.current / size, 0, 1));
165
- }
166
- },
167
- onMoveEnd() {
168
- state.setThumbDragging(index, false);
169
- }
170
- });
171
-
172
- // Immediately register editability with the state
173
- state.setThumbEditable(index, !isDisabled);
174
-
175
- const {focusableProps} = useFocusable(
176
- mergeProps(opts, {
177
- onFocus: () => state.setFocusedThumb(index),
178
- onBlur: () => state.setFocusedThumb(undefined)
179
- }),
180
- inputRef
181
- );
182
-
183
- let currentPointer = useRef<number | undefined>(undefined);
184
- let onDown = (id?: number) => {
185
- focusInput();
186
- currentPointer.current = id;
187
- state.setThumbDragging(index, true);
188
-
189
- addGlobalListener(window, 'mouseup', onUp, false);
190
- addGlobalListener(window, 'touchend', onUp, false);
191
- addGlobalListener(window, 'pointerup', onUp, false);
192
-
193
- };
194
-
195
- let onUp = (e) => {
196
- let id = e.pointerId ?? e.changedTouches?.[0].identifier;
197
- if (id === currentPointer.current) {
198
- focusInput();
199
- state.setThumbDragging(index, false);
200
- removeGlobalListener(window, 'mouseup', onUp, false);
201
- removeGlobalListener(window, 'touchend', onUp, false);
202
- removeGlobalListener(window, 'pointerup', onUp, false);
203
- }
204
- };
205
-
206
- let thumbPosition = state.getThumbPercent(index);
207
- if (isVertical || direction === 'rtl') {
208
- thumbPosition = 1 - thumbPosition;
209
- }
210
-
211
- let interactions = !isDisabled ? mergeProps(
212
- keyboardProps,
213
- moveProps,
214
- {
215
- onMouseDown: (e: React.MouseEvent) => {
216
- if (e.button !== 0 || e.altKey || e.ctrlKey || e.metaKey) {
217
- return;
218
- }
219
- onDown();
220
- },
221
- onPointerDown: (e: React.PointerEvent) => {
222
- if (e.button !== 0 || e.altKey || e.ctrlKey || e.metaKey) {
223
- return;
224
- }
225
- onDown(e.pointerId);
226
- },
227
- onTouchStart: (e: React.TouchEvent) => {onDown(e.changedTouches[0].identifier);}
228
- }
229
- ) : {};
230
-
231
- useFormReset(inputRef, state.defaultValues[index], (v) => {
232
- state.setThumbValue(index, v);
233
- });
234
-
235
- // We install mouse handlers for the drag motion on the thumb div, but
236
- // not the key handler for moving the thumb with the slider. Instead,
237
- // we focus the range input, and let the browser handle the keyboard
238
- // interactions; we then listen to input's onChange to update state.
239
- return {
240
- inputProps: mergeProps(focusableProps, fieldProps, {
241
- type: 'range',
242
- tabIndex: !isDisabled ? 0 : undefined,
243
- min: state.getThumbMinValue(index),
244
- max: state.getThumbMaxValue(index),
245
- step: state.step,
246
- value: value,
247
- name,
248
- form,
249
- disabled: isDisabled,
250
- 'aria-orientation': orientation,
251
- 'aria-valuetext': state.getThumbValueLabel(index),
252
- 'aria-required': isRequired || undefined,
253
- 'aria-invalid': isInvalid || validationState === 'invalid' || undefined,
254
- 'aria-errormessage': opts['aria-errormessage'],
255
- 'aria-describedby': [data['aria-describedby'], opts['aria-describedby']].filter(Boolean).join(' '),
256
- 'aria-details': [data['aria-details'], opts['aria-details']].filter(Boolean).join(' '),
257
- onChange: (e: ChangeEvent<HTMLInputElement>) => {
258
- state.setThumbValue(index, parseFloat(e.target.value));
259
- }
260
- }),
261
- thumbProps: {
262
- ...interactions,
263
- style: {
264
- position: 'absolute',
265
- [isVertical ? 'top' : 'left']: `${thumbPosition * 100}%`,
266
- transform: 'translate(-50%, -50%)',
267
- touchAction: 'none'
268
- }
269
- },
270
- labelProps,
271
- isDragging: state.isThumbDragging(index),
272
- isDisabled,
273
- isFocused
274
- };
275
- }
package/src/utils.ts DELETED
@@ -1,18 +0,0 @@
1
- import {SliderState} from '@react-stately/slider';
2
-
3
- interface SliderData {
4
- id: string,
5
- 'aria-describedby'?: string,
6
- 'aria-details'?: string
7
- }
8
-
9
- export const sliderData: WeakMap<SliderState, SliderData> = new WeakMap<SliderState, SliderData>();
10
-
11
- export function getSliderThumbId(state: SliderState, index: number): string {
12
- let data = sliderData.get(state);
13
- if (!data) {
14
- throw new Error('Unknown slider state');
15
- }
16
-
17
- return `${data.id}-${index}`;
18
- }