@react-aria/datepicker 3.0.0-alpha.4 → 3.0.0-alpha.5
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 +417 -281
- package/dist/main.js.map +1 -1
- package/dist/module.js +409 -275
- package/dist/module.js.map +1 -1
- package/dist/types.d.ts +67 -28
- package/dist/types.d.ts.map +1 -1
- package/package.json +10 -9
- package/src/index.ts +4 -4
- package/src/useDateField.ts +77 -21
- package/src/useDatePicker.ts +52 -10
- package/src/useDatePickerGroup.ts +34 -11
- package/src/useDateRangePicker.ts +66 -18
- package/src/useDateSegment.ts +55 -36
package/src/useDateSegment.ts
CHANGED
|
@@ -10,28 +10,31 @@
|
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {getScrollParent, isIOS, isMac, mergeProps, scrollIntoView, useEvent, useId} from '@react-aria/utils';
|
|
17
|
-
import {labelIds} from './useDateField';
|
|
13
|
+
import {DateFieldState, DateSegment} from '@react-stately/datepicker';
|
|
14
|
+
import {getScrollParent, isIOS, isMac, mergeProps, scrollIntoView, useEvent, useId, useLabels} from '@react-aria/utils';
|
|
15
|
+
import {hookData} from './useDateField';
|
|
18
16
|
import {NumberParser} from '@internationalized/number';
|
|
19
17
|
import React, {HTMLAttributes, RefObject, useMemo, useRef} from 'react';
|
|
20
18
|
import {useDateFormatter, useFilter, useLocale} from '@react-aria/i18n';
|
|
21
19
|
import {useDisplayNames} from './useDisplayNames';
|
|
22
|
-
import {useFocusManager} from '@react-aria/focus';
|
|
23
20
|
import {usePress} from '@react-aria/interactions';
|
|
24
21
|
import {useSpinButton} from '@react-aria/spinbutton';
|
|
25
22
|
|
|
26
23
|
interface DateSegmentAria {
|
|
24
|
+
/** Props for the segment element. */
|
|
27
25
|
segmentProps: HTMLAttributes<HTMLDivElement>
|
|
28
26
|
}
|
|
29
27
|
|
|
30
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Provides the behavior and accessibility implementation for a segment in a date field.
|
|
30
|
+
* A date segment displays an individual unit of a date and time, and allows users to edit
|
|
31
|
+
* the value by typing or using the arrow keys to increment and decrement.
|
|
32
|
+
*/
|
|
33
|
+
export function useDateSegment(segment: DateSegment, state: DateFieldState, ref: RefObject<HTMLElement>): DateSegmentAria {
|
|
31
34
|
let enteredKeys = useRef('');
|
|
32
35
|
let {locale, direction} = useLocale();
|
|
33
36
|
let displayNames = useDisplayNames();
|
|
34
|
-
let focusManager =
|
|
37
|
+
let {ariaLabel, ariaLabelledBy, ariaDescribedBy, focusManager} = hookData.get(state);
|
|
35
38
|
|
|
36
39
|
let textValue = segment.text;
|
|
37
40
|
let options = useMemo(() => state.dateFormatter.resolvedOptions(), [state.dateFormatter]);
|
|
@@ -45,7 +48,7 @@ export function useDateSegment<T extends DateValue>(props: DatePickerProps<T> &
|
|
|
45
48
|
if (segment.type === 'month') {
|
|
46
49
|
let monthTextValue = monthDateFormatter.format(state.dateValue);
|
|
47
50
|
textValue = monthTextValue !== textValue ? `${textValue} – ${monthTextValue}` : monthTextValue;
|
|
48
|
-
} else if (segment.type === 'hour'
|
|
51
|
+
} else if (segment.type === 'hour') {
|
|
49
52
|
textValue = hourDateFormatter.format(state.dateValue);
|
|
50
53
|
}
|
|
51
54
|
|
|
@@ -54,9 +57,9 @@ export function useDateSegment<T extends DateValue>(props: DatePickerProps<T> &
|
|
|
54
57
|
textValue,
|
|
55
58
|
minValue: segment.minValue,
|
|
56
59
|
maxValue: segment.maxValue,
|
|
57
|
-
isDisabled:
|
|
58
|
-
isReadOnly:
|
|
59
|
-
isRequired:
|
|
60
|
+
isDisabled: state.isDisabled,
|
|
61
|
+
isReadOnly: state.isReadOnly || !segment.isEditable,
|
|
62
|
+
isRequired: state.isRequired,
|
|
60
63
|
onIncrement: () => {
|
|
61
64
|
enteredKeys.current = '';
|
|
62
65
|
state.increment(segment.type);
|
|
@@ -86,7 +89,7 @@ export function useDateSegment<T extends DateValue>(props: DatePickerProps<T> &
|
|
|
86
89
|
let parser = useMemo(() => new NumberParser(locale, {maximumFractionDigits: 0}), [locale]);
|
|
87
90
|
|
|
88
91
|
let backspace = () => {
|
|
89
|
-
if (parser.isValidPartialNumber(segment.text) && !
|
|
92
|
+
if (parser.isValidPartialNumber(segment.text) && !state.isReadOnly && !segment.isPlaceholder) {
|
|
90
93
|
let newValue = segment.text.slice(0, -1);
|
|
91
94
|
let parsed = parser.parse(newValue);
|
|
92
95
|
if (newValue.length === 0 || parsed === 0) {
|
|
@@ -116,27 +119,27 @@ export function useDateSegment<T extends DateValue>(props: DatePickerProps<T> &
|
|
|
116
119
|
e.preventDefault();
|
|
117
120
|
e.stopPropagation();
|
|
118
121
|
if (direction === 'rtl') {
|
|
119
|
-
focusManager.focusNext();
|
|
122
|
+
focusManager.focusNext({tabbable: true});
|
|
120
123
|
} else {
|
|
121
|
-
focusManager.focusPrevious();
|
|
124
|
+
focusManager.focusPrevious({tabbable: true});
|
|
122
125
|
}
|
|
123
126
|
break;
|
|
124
127
|
case 'ArrowRight':
|
|
125
128
|
e.preventDefault();
|
|
126
129
|
e.stopPropagation();
|
|
127
130
|
if (direction === 'rtl') {
|
|
128
|
-
focusManager.focusPrevious();
|
|
131
|
+
focusManager.focusPrevious({tabbable: true});
|
|
129
132
|
} else {
|
|
130
|
-
focusManager.focusNext();
|
|
133
|
+
focusManager.focusNext({tabbable: true});
|
|
131
134
|
}
|
|
132
135
|
break;
|
|
133
136
|
case 'Enter':
|
|
134
137
|
e.preventDefault();
|
|
135
138
|
e.stopPropagation();
|
|
136
|
-
if (segment.isPlaceholder && !
|
|
139
|
+
if (segment.isPlaceholder && !state.isReadOnly) {
|
|
137
140
|
state.confirmPlaceholder(segment.type);
|
|
138
141
|
}
|
|
139
|
-
focusManager.focusNext();
|
|
142
|
+
focusManager.focusNext({tabbable: true});
|
|
140
143
|
break;
|
|
141
144
|
case 'Tab':
|
|
142
145
|
break;
|
|
@@ -167,7 +170,7 @@ export function useDateSegment<T extends DateValue>(props: DatePickerProps<T> &
|
|
|
167
170
|
}, [amPmFormatter]);
|
|
168
171
|
|
|
169
172
|
let onInput = (key: string) => {
|
|
170
|
-
if (
|
|
173
|
+
if (state.isDisabled || state.isReadOnly) {
|
|
171
174
|
return;
|
|
172
175
|
}
|
|
173
176
|
|
|
@@ -182,7 +185,7 @@ export function useDateSegment<T extends DateValue>(props: DatePickerProps<T> &
|
|
|
182
185
|
} else {
|
|
183
186
|
break;
|
|
184
187
|
}
|
|
185
|
-
focusManager.focusNext();
|
|
188
|
+
focusManager.focusNext({tabbable: true});
|
|
186
189
|
break;
|
|
187
190
|
case 'day':
|
|
188
191
|
case 'hour':
|
|
@@ -231,7 +234,7 @@ export function useDateSegment<T extends DateValue>(props: DatePickerProps<T> &
|
|
|
231
234
|
if (Number(numberValue + '0') > segment.maxValue || newValue.length >= String(segment.maxValue).length) {
|
|
232
235
|
enteredKeys.current = '';
|
|
233
236
|
if (shouldSetValue) {
|
|
234
|
-
focusManager.focusNext();
|
|
237
|
+
focusManager.focusNext({tabbable: true});
|
|
235
238
|
}
|
|
236
239
|
} else {
|
|
237
240
|
enteredKeys.current = newValue;
|
|
@@ -261,7 +264,7 @@ export function useDateSegment<T extends DateValue>(props: DatePickerProps<T> &
|
|
|
261
264
|
switch (e.inputType) {
|
|
262
265
|
case 'deleteContentBackward':
|
|
263
266
|
case 'deleteContentForward':
|
|
264
|
-
if (parser.isValidPartialNumber(segment.text) && !
|
|
267
|
+
if (parser.isValidPartialNumber(segment.text) && !state.isReadOnly) {
|
|
265
268
|
backspace();
|
|
266
269
|
}
|
|
267
270
|
break;
|
|
@@ -328,8 +331,6 @@ export function useDateSegment<T extends DateValue>(props: DatePickerProps<T> &
|
|
|
328
331
|
'aria-valuenow': null
|
|
329
332
|
} : {};
|
|
330
333
|
|
|
331
|
-
let {ariaLabelledBy, ariaDescribedBy} = labelIds.get(state);
|
|
332
|
-
|
|
333
334
|
// Only apply aria-describedby to the first segment, unless the field is invalid. This avoids it being
|
|
334
335
|
// read every time the user navigates to a new segment.
|
|
335
336
|
let firstSegment = useMemo(() => state.segments.find(s => s.isEditable), [state.segments]);
|
|
@@ -337,20 +338,35 @@ export function useDateSegment<T extends DateValue>(props: DatePickerProps<T> &
|
|
|
337
338
|
ariaDescribedBy = undefined;
|
|
338
339
|
}
|
|
339
340
|
|
|
340
|
-
let id = useId(
|
|
341
|
-
let isEditable = !
|
|
341
|
+
let id = useId();
|
|
342
|
+
let isEditable = !state.isDisabled && !state.isReadOnly && segment.isEditable;
|
|
343
|
+
|
|
344
|
+
// Prepend the label passed from the field to each segment name.
|
|
345
|
+
// This is needed because VoiceOver on iOS does not announce groups.
|
|
346
|
+
let name = segment.type === 'literal' ? '' : displayNames.of(segment.type);
|
|
347
|
+
let labelProps = useLabels({
|
|
348
|
+
'aria-label': (ariaLabel ? ariaLabel + ' ' : '') + name,
|
|
349
|
+
'aria-labelledby': ariaLabelledBy
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
// Literal segments should not be visible to screen readers. We don't really need any of the above,
|
|
353
|
+
// but the rules of hooks mean hooks cannot be conditional so we have to put this condition here.
|
|
354
|
+
if (segment.type === 'literal') {
|
|
355
|
+
return {
|
|
356
|
+
segmentProps: {
|
|
357
|
+
'aria-hidden': true
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
|
|
342
362
|
return {
|
|
343
|
-
segmentProps: mergeProps(spinButtonProps, pressProps, {
|
|
363
|
+
segmentProps: mergeProps(spinButtonProps, pressProps, labelProps, {
|
|
344
364
|
id,
|
|
345
365
|
...touchPropOverrides,
|
|
346
|
-
'aria-controls': props['aria-controls'],
|
|
347
|
-
// 'aria-haspopup': props['aria-haspopup'], // deprecated in ARIA 1.2
|
|
348
366
|
'aria-invalid': state.validationState === 'invalid' ? 'true' : undefined,
|
|
349
|
-
'aria-label': segment.type !== 'literal' ? displayNames.of(segment.type) : undefined,
|
|
350
|
-
'aria-labelledby': `${ariaLabelledBy} ${id}`,
|
|
351
367
|
'aria-describedby': ariaDescribedBy,
|
|
352
368
|
'aria-placeholder': segment.isPlaceholder ? segment.text : undefined,
|
|
353
|
-
'aria-readonly':
|
|
369
|
+
'aria-readonly': state.isReadOnly || !segment.isEditable ? 'true' : undefined,
|
|
354
370
|
contentEditable: isEditable,
|
|
355
371
|
suppressContentEditableWarning: isEditable,
|
|
356
372
|
spellCheck: isEditable ? 'false' : undefined,
|
|
@@ -358,10 +374,13 @@ export function useDateSegment<T extends DateValue>(props: DatePickerProps<T> &
|
|
|
358
374
|
autoCorrect: isEditable ? 'off' : undefined,
|
|
359
375
|
// Capitalization was changed in React 17...
|
|
360
376
|
[parseInt(React.version, 10) >= 17 ? 'enterKeyHint' : 'enterkeyhint']: isEditable ? 'next' : undefined,
|
|
361
|
-
inputMode:
|
|
362
|
-
tabIndex:
|
|
377
|
+
inputMode: state.isDisabled || segment.type === 'dayPeriod' || !isEditable ? undefined : 'numeric',
|
|
378
|
+
tabIndex: state.isDisabled ? undefined : 0,
|
|
363
379
|
onKeyDown,
|
|
364
|
-
onFocus
|
|
380
|
+
onFocus,
|
|
381
|
+
style: {
|
|
382
|
+
caretColor: 'transparent'
|
|
383
|
+
}
|
|
365
384
|
})
|
|
366
385
|
};
|
|
367
386
|
}
|