@react-aria/datepicker 3.0.0-nightly.3175 → 3.0.0-rc.1
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/dist/main.js +321 -294
- package/dist/main.js.map +1 -1
- package/dist/module.js +322 -282
- package/dist/module.js.map +1 -1
- package/dist/types.d.ts +46 -44
- package/dist/types.d.ts.map +1 -1
- package/package.json +18 -18
- package/src/index.ts +7 -1
- package/src/useDateField.ts +49 -21
- package/src/useDatePicker.ts +15 -8
- package/src/useDatePickerGroup.ts +34 -5
- package/src/useDateRangePicker.ts +22 -17
- package/src/useDateSegment.ts +32 -50
- package/src/useDisplayNames.ts +1 -0
package/src/useDateSegment.ts
CHANGED
|
@@ -10,17 +10,16 @@
|
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {
|
|
14
|
-
import {getScrollParent, isIOS, isMac, mergeProps, scrollIntoView, useEvent, useId} from '@react-aria/utils';
|
|
13
|
+
import {DateFieldState, DateSegment} from '@react-stately/datepicker';
|
|
14
|
+
import {getScrollParent, isIOS, isMac, mergeProps, scrollIntoView, useEvent, useId, useLabels} from '@react-aria/utils';
|
|
15
15
|
import {hookData} from './useDateField';
|
|
16
16
|
import {NumberParser} from '@internationalized/number';
|
|
17
17
|
import React, {HTMLAttributes, RefObject, useMemo, useRef} from 'react';
|
|
18
18
|
import {useDateFormatter, useFilter, useLocale} from '@react-aria/i18n';
|
|
19
19
|
import {useDisplayNames} from './useDisplayNames';
|
|
20
|
-
import {usePress} from '@react-aria/interactions';
|
|
21
20
|
import {useSpinButton} from '@react-aria/spinbutton';
|
|
22
21
|
|
|
23
|
-
interface DateSegmentAria {
|
|
22
|
+
export interface DateSegmentAria {
|
|
24
23
|
/** Props for the segment element. */
|
|
25
24
|
segmentProps: HTMLAttributes<HTMLDivElement>
|
|
26
25
|
}
|
|
@@ -30,11 +29,11 @@ interface DateSegmentAria {
|
|
|
30
29
|
* A date segment displays an individual unit of a date and time, and allows users to edit
|
|
31
30
|
* the value by typing or using the arrow keys to increment and decrement.
|
|
32
31
|
*/
|
|
33
|
-
export function useDateSegment(segment: DateSegment, state:
|
|
32
|
+
export function useDateSegment(segment: DateSegment, state: DateFieldState, ref: RefObject<HTMLElement>): DateSegmentAria {
|
|
34
33
|
let enteredKeys = useRef('');
|
|
35
|
-
let {locale
|
|
34
|
+
let {locale} = useLocale();
|
|
36
35
|
let displayNames = useDisplayNames();
|
|
37
|
-
let {ariaLabelledBy, ariaDescribedBy, focusManager} = hookData.get(state);
|
|
36
|
+
let {ariaLabel, ariaLabelledBy, ariaDescribedBy, focusManager} = hookData.get(state);
|
|
38
37
|
|
|
39
38
|
let textValue = segment.text;
|
|
40
39
|
let options = useMemo(() => state.dateFormatter.resolvedOptions(), [state.dateFormatter]);
|
|
@@ -48,7 +47,7 @@ export function useDateSegment(segment: DateSegment, state: DatePickerFieldState
|
|
|
48
47
|
if (segment.type === 'month') {
|
|
49
48
|
let monthTextValue = monthDateFormatter.format(state.dateValue);
|
|
50
49
|
textValue = monthTextValue !== textValue ? `${textValue} – ${monthTextValue}` : monthTextValue;
|
|
51
|
-
} else if (segment.type === 'hour'
|
|
50
|
+
} else if (segment.type === 'hour') {
|
|
52
51
|
textValue = hourDateFormatter.format(state.dateValue);
|
|
53
52
|
}
|
|
54
53
|
|
|
@@ -115,31 +114,13 @@ export function useDateSegment(segment: DateSegment, state: DatePickerFieldState
|
|
|
115
114
|
}
|
|
116
115
|
|
|
117
116
|
switch (e.key) {
|
|
118
|
-
case 'ArrowLeft':
|
|
119
|
-
e.preventDefault();
|
|
120
|
-
e.stopPropagation();
|
|
121
|
-
if (direction === 'rtl') {
|
|
122
|
-
focusManager.focusNext({tabbable: true});
|
|
123
|
-
} else {
|
|
124
|
-
focusManager.focusPrevious({tabbable: true});
|
|
125
|
-
}
|
|
126
|
-
break;
|
|
127
|
-
case 'ArrowRight':
|
|
128
|
-
e.preventDefault();
|
|
129
|
-
e.stopPropagation();
|
|
130
|
-
if (direction === 'rtl') {
|
|
131
|
-
focusManager.focusPrevious({tabbable: true});
|
|
132
|
-
} else {
|
|
133
|
-
focusManager.focusNext({tabbable: true});
|
|
134
|
-
}
|
|
135
|
-
break;
|
|
136
117
|
case 'Enter':
|
|
137
118
|
e.preventDefault();
|
|
138
119
|
e.stopPropagation();
|
|
139
120
|
if (segment.isPlaceholder && !state.isReadOnly) {
|
|
140
121
|
state.confirmPlaceholder(segment.type);
|
|
141
122
|
}
|
|
142
|
-
focusManager.focusNext(
|
|
123
|
+
focusManager.focusNext();
|
|
143
124
|
break;
|
|
144
125
|
case 'Tab':
|
|
145
126
|
break;
|
|
@@ -185,7 +166,7 @@ export function useDateSegment(segment: DateSegment, state: DatePickerFieldState
|
|
|
185
166
|
} else {
|
|
186
167
|
break;
|
|
187
168
|
}
|
|
188
|
-
focusManager.focusNext(
|
|
169
|
+
focusManager.focusNext();
|
|
189
170
|
break;
|
|
190
171
|
case 'day':
|
|
191
172
|
case 'hour':
|
|
@@ -234,7 +215,7 @@ export function useDateSegment(segment: DateSegment, state: DatePickerFieldState
|
|
|
234
215
|
if (Number(numberValue + '0') > segment.maxValue || newValue.length >= String(segment.maxValue).length) {
|
|
235
216
|
enteredKeys.current = '';
|
|
236
217
|
if (shouldSetValue) {
|
|
237
|
-
focusManager.focusNext(
|
|
218
|
+
focusManager.focusNext();
|
|
238
219
|
}
|
|
239
220
|
} else {
|
|
240
221
|
enteredKeys.current = newValue;
|
|
@@ -251,9 +232,11 @@ export function useDateSegment(segment: DateSegment, state: DatePickerFieldState
|
|
|
251
232
|
// Safari requires that a selection is set or it won't fire input events.
|
|
252
233
|
// Since usePress disables text selection, this won't happen by default.
|
|
253
234
|
ref.current.style.webkitUserSelect = 'text';
|
|
235
|
+
ref.current.style.userSelect = 'text';
|
|
254
236
|
let selection = window.getSelection();
|
|
255
237
|
selection.collapse(ref.current);
|
|
256
|
-
ref.current.style.webkitUserSelect = '';
|
|
238
|
+
ref.current.style.webkitUserSelect = 'none';
|
|
239
|
+
ref.current.style.userSelect = 'none';
|
|
257
240
|
};
|
|
258
241
|
|
|
259
242
|
let compositionRef = useRef('');
|
|
@@ -301,22 +284,6 @@ export function useDateSegment(segment: DateSegment, state: DatePickerFieldState
|
|
|
301
284
|
}
|
|
302
285
|
});
|
|
303
286
|
|
|
304
|
-
// Focus on mouse down/touch up to match native textfield behavior.
|
|
305
|
-
// usePress handles canceling text selection.
|
|
306
|
-
let {pressProps} = usePress({
|
|
307
|
-
preventFocusOnPress: true,
|
|
308
|
-
onPressStart: (e) => {
|
|
309
|
-
if (e.pointerType === 'mouse') {
|
|
310
|
-
e.target.focus();
|
|
311
|
-
}
|
|
312
|
-
},
|
|
313
|
-
onPress(e) {
|
|
314
|
-
if (e.pointerType !== 'mouse') {
|
|
315
|
-
e.target.focus();
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
});
|
|
319
|
-
|
|
320
287
|
// For Android: prevent selection on long press.
|
|
321
288
|
useEvent(ref, 'selectstart', e => {
|
|
322
289
|
e.preventDefault();
|
|
@@ -341,6 +308,14 @@ export function useDateSegment(segment: DateSegment, state: DatePickerFieldState
|
|
|
341
308
|
let id = useId();
|
|
342
309
|
let isEditable = !state.isDisabled && !state.isReadOnly && segment.isEditable;
|
|
343
310
|
|
|
311
|
+
// Prepend the label passed from the field to each segment name.
|
|
312
|
+
// This is needed because VoiceOver on iOS does not announce groups.
|
|
313
|
+
let name = segment.type === 'literal' ? '' : displayNames.of(segment.type);
|
|
314
|
+
let labelProps = useLabels({
|
|
315
|
+
'aria-label': (ariaLabel ? ariaLabel + ' ' : '') + name,
|
|
316
|
+
'aria-labelledby': ariaLabelledBy
|
|
317
|
+
});
|
|
318
|
+
|
|
344
319
|
// Literal segments should not be visible to screen readers. We don't really need any of the above,
|
|
345
320
|
// but the rules of hooks mean hooks cannot be conditional so we have to put this condition here.
|
|
346
321
|
if (segment.type === 'literal') {
|
|
@@ -352,12 +327,10 @@ export function useDateSegment(segment: DateSegment, state: DatePickerFieldState
|
|
|
352
327
|
}
|
|
353
328
|
|
|
354
329
|
return {
|
|
355
|
-
segmentProps: mergeProps(spinButtonProps,
|
|
330
|
+
segmentProps: mergeProps(spinButtonProps, labelProps, {
|
|
356
331
|
id,
|
|
357
332
|
...touchPropOverrides,
|
|
358
333
|
'aria-invalid': state.validationState === 'invalid' ? 'true' : undefined,
|
|
359
|
-
'aria-label': displayNames.of(segment.type),
|
|
360
|
-
'aria-labelledby': `${ariaLabelledBy} ${id}`,
|
|
361
334
|
'aria-describedby': ariaDescribedBy,
|
|
362
335
|
'aria-placeholder': segment.isPlaceholder ? segment.text : undefined,
|
|
363
336
|
'aria-readonly': state.isReadOnly || !segment.isEditable ? 'true' : undefined,
|
|
@@ -373,7 +346,16 @@ export function useDateSegment(segment: DateSegment, state: DatePickerFieldState
|
|
|
373
346
|
onKeyDown,
|
|
374
347
|
onFocus,
|
|
375
348
|
style: {
|
|
376
|
-
caretColor: 'transparent'
|
|
349
|
+
caretColor: 'transparent',
|
|
350
|
+
userSelect: 'none',
|
|
351
|
+
WebkitUserSelect: 'none'
|
|
352
|
+
},
|
|
353
|
+
// Prevent pointer events from reaching useDatePickerGroup, and allow native browser behavior to focus the segment.
|
|
354
|
+
onPointerDown(e) {
|
|
355
|
+
e.stopPropagation();
|
|
356
|
+
},
|
|
357
|
+
onMouseDown(e) {
|
|
358
|
+
e.stopPropagation();
|
|
377
359
|
}
|
|
378
360
|
})
|
|
379
361
|
};
|