@react-aria/datepicker 3.0.0-nightly.3173 → 3.0.0-rc.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.
- package/dist/main.js +286 -263
- package/dist/main.js.map +1 -1
- package/dist/module.js +287 -251
- 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 +16 -16
- package/src/index.ts +7 -1
- package/src/useDateField.ts +48 -19
- package/src/useDatePicker.ts +13 -5
- package/src/useDatePickerGroup.ts +3 -3
- package/src/useDateRangePicker.ts +17 -15
- package/src/useDateSegment.ts +25 -27
- package/src/useDisplayNames.ts +1 -0
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"mappings":";;;;;;
|
|
1
|
+
{"mappings":"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","sources":["packages/@react-aria/datepicker/src/packages/@react-aria/datepicker/src/useDatePickerGroup.ts","packages/@react-aria/datepicker/src/packages/@react-aria/datepicker/src/useDateField.ts","packages/@react-aria/datepicker/src/packages/@react-aria/datepicker/src/useDatePicker.ts","packages/@react-aria/datepicker/src/packages/@react-aria/datepicker/src/useDisplayNames.ts","packages/@react-aria/datepicker/src/packages/@react-aria/datepicker/src/useDateSegment.ts","packages/@react-aria/datepicker/src/packages/@react-aria/datepicker/src/useDateRangePicker.ts","packages/@react-aria/datepicker/src/packages/@react-aria/datepicker/src/index.ts","packages/@react-aria/datepicker/src/index.ts"],"sourcesContent":[null,null,null,null,null,null,null,"/*\n * Copyright 2020 Adobe. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nexport {useDatePicker} from './useDatePicker';\nexport {useDateSegment} from './useDateSegment';\nexport {useDateField, useTimeField} from './useDateField';\nexport {useDateRangePicker} from './useDateRangePicker';\nexport {useDisplayNames} from './useDisplayNames';\n\nexport type {AriaDatePickerProps, AriaDateRangePickerProps} from '@react-types/datepicker';\nexport type {AriaDateFieldProps, DateFieldAria} from './useDateField';\nexport type {DatePickerAria} from './useDatePicker';\nexport type {DateRangePickerAria} from './useDateRangePicker';\nexport type {DateSegmentAria} from './useDateSegment';\n"],"names":[],"version":3,"file":"types.d.ts.map"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-aria/datepicker",
|
|
3
|
-
"version": "3.0.0-
|
|
3
|
+
"version": "3.0.0-rc.0",
|
|
4
4
|
"description": "Spectrum UI components in React",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"main": "dist/main.js",
|
|
@@ -18,20 +18,20 @@
|
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@babel/runtime": "^7.6.2",
|
|
21
|
-
"@internationalized/message": "3.0.6
|
|
22
|
-
"@internationalized/number": "3.0
|
|
23
|
-
"@react-aria/focus": "3.
|
|
24
|
-
"@react-aria/i18n": "3.
|
|
25
|
-
"@react-aria/interactions": "3.
|
|
26
|
-
"@react-aria/label": "3.
|
|
27
|
-
"@react-aria/spinbutton": "3.0.
|
|
28
|
-
"@react-aria/utils": "3.
|
|
29
|
-
"@react-stately/datepicker": "3.0.0-
|
|
30
|
-
"@react-types/button": "3.4.5
|
|
31
|
-
"@react-types/calendar": "3.0.0-
|
|
32
|
-
"@react-types/datepicker": "3.0.0-
|
|
33
|
-
"@react-types/dialog": "3.3.5
|
|
34
|
-
"@react-types/shared": "3.
|
|
21
|
+
"@internationalized/message": "^3.0.6",
|
|
22
|
+
"@internationalized/number": "^3.1.0",
|
|
23
|
+
"@react-aria/focus": "^3.5.5",
|
|
24
|
+
"@react-aria/i18n": "^3.3.9",
|
|
25
|
+
"@react-aria/interactions": "^3.8.4",
|
|
26
|
+
"@react-aria/label": "^3.2.5",
|
|
27
|
+
"@react-aria/spinbutton": "^3.0.6",
|
|
28
|
+
"@react-aria/utils": "^3.12.0",
|
|
29
|
+
"@react-stately/datepicker": "3.0.0-rc.0",
|
|
30
|
+
"@react-types/button": "^3.4.5",
|
|
31
|
+
"@react-types/calendar": "3.0.0-rc.0",
|
|
32
|
+
"@react-types/datepicker": "3.0.0-rc.0",
|
|
33
|
+
"@react-types/dialog": "^3.3.5",
|
|
34
|
+
"@react-types/shared": "^3.12.0"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
37
|
"react": "^16.8.0 || ^17.0.0-rc.1",
|
|
@@ -40,5 +40,5 @@
|
|
|
40
40
|
"publishConfig": {
|
|
41
41
|
"access": "public"
|
|
42
42
|
},
|
|
43
|
-
"gitHead": "
|
|
43
|
+
"gitHead": "6a503b715e0dbbf92038cd7f08b1bcdde4c78e82"
|
|
44
44
|
}
|
package/src/index.ts
CHANGED
|
@@ -14,4 +14,10 @@ export {useDatePicker} from './useDatePicker';
|
|
|
14
14
|
export {useDateSegment} from './useDateSegment';
|
|
15
15
|
export {useDateField, useTimeField} from './useDateField';
|
|
16
16
|
export {useDateRangePicker} from './useDateRangePicker';
|
|
17
|
-
export
|
|
17
|
+
export {useDisplayNames} from './useDisplayNames';
|
|
18
|
+
|
|
19
|
+
export type {AriaDatePickerProps, AriaDateRangePickerProps} from '@react-types/datepicker';
|
|
20
|
+
export type {AriaDateFieldProps, DateFieldAria} from './useDateField';
|
|
21
|
+
export type {DatePickerAria} from './useDatePicker';
|
|
22
|
+
export type {DateRangePickerAria} from './useDateRangePicker';
|
|
23
|
+
export type {DateSegmentAria} from './useDateSegment';
|
package/src/useDateField.ts
CHANGED
|
@@ -12,19 +12,20 @@
|
|
|
12
12
|
|
|
13
13
|
import {AriaDatePickerProps, AriaTimeFieldProps, DateValue, TimeValue} from '@react-types/datepicker';
|
|
14
14
|
import {createFocusManager, FocusManager} from '@react-aria/focus';
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
15
|
+
import {DateFieldState} from '@react-stately/datepicker';
|
|
16
|
+
import {filterDOMProps, mergeProps, useDescription} from '@react-aria/utils';
|
|
17
17
|
import {HTMLAttributes, RefObject, useEffect, useMemo, useRef} from 'react';
|
|
18
|
-
|
|
19
|
-
import
|
|
18
|
+
// @ts-ignore
|
|
19
|
+
import intlMessages from '../intl/*.json';
|
|
20
20
|
import {useDatePickerGroup} from './useDatePickerGroup';
|
|
21
21
|
import {useField} from '@react-aria/label';
|
|
22
22
|
import {useFocusWithin} from '@react-aria/interactions';
|
|
23
|
+
import {useMessageFormatter} from '@react-aria/i18n';
|
|
23
24
|
|
|
24
25
|
// Allows this hook to also be used with TimeField
|
|
25
|
-
interface
|
|
26
|
+
export interface AriaDateFieldProps<T extends DateValue> extends Omit<AriaDatePickerProps<T>, 'value' | 'defaultValue' | 'onChange' | 'minValue' | 'maxValue' | 'placeholderValue'> {}
|
|
26
27
|
|
|
27
|
-
interface DateFieldAria {
|
|
28
|
+
export interface DateFieldAria {
|
|
28
29
|
/** Props for the field's visible label element, if any. */
|
|
29
30
|
labelProps: HTMLAttributes<HTMLElement>,
|
|
30
31
|
/** Props for the field grouping element. */
|
|
@@ -37,19 +38,25 @@ interface DateFieldAria {
|
|
|
37
38
|
|
|
38
39
|
// Data that is passed between useDateField and useDateSegment.
|
|
39
40
|
interface HookData {
|
|
41
|
+
ariaLabel: string,
|
|
40
42
|
ariaLabelledBy: string,
|
|
41
43
|
ariaDescribedBy: string,
|
|
42
44
|
focusManager: FocusManager
|
|
43
45
|
}
|
|
44
46
|
|
|
45
|
-
export const hookData = new WeakMap<
|
|
47
|
+
export const hookData = new WeakMap<DateFieldState, HookData>();
|
|
48
|
+
|
|
49
|
+
// Private props that we pass from useDatePicker/useDateRangePicker.
|
|
50
|
+
// Ideally we'd use a Symbol for this, but React doesn't support them: https://github.com/facebook/react/issues/7552
|
|
51
|
+
export const roleSymbol = '__role_' + Date.now();
|
|
52
|
+
export const focusManagerSymbol = '__focusManager_' + Date.now();
|
|
46
53
|
|
|
47
54
|
/**
|
|
48
55
|
* Provides the behavior and accessibility implementation for a date field component.
|
|
49
56
|
* A date field allows users to enter and edit date and time values using a keyboard.
|
|
50
57
|
* Each part of a date value is displayed in an individually editable segment.
|
|
51
58
|
*/
|
|
52
|
-
export function useDateField<T extends DateValue>(props:
|
|
59
|
+
export function useDateField<T extends DateValue>(props: AriaDateFieldProps<T>, state: DateFieldState, ref: RefObject<HTMLElement>): DateFieldAria {
|
|
53
60
|
let {labelProps, fieldProps, descriptionProps, errorMessageProps} = useField({
|
|
54
61
|
...props,
|
|
55
62
|
labelElementType: 'span'
|
|
@@ -63,22 +70,47 @@ export function useDateField<T extends DateValue>(props: DateFieldProps<T>, stat
|
|
|
63
70
|
}
|
|
64
71
|
});
|
|
65
72
|
|
|
66
|
-
let
|
|
67
|
-
let
|
|
73
|
+
let formatMessage = useMessageFormatter(intlMessages);
|
|
74
|
+
let message = state.maxGranularity === 'hour' ? 'selectedTimeDescription' : 'selectedDateDescription';
|
|
75
|
+
let field = state.maxGranularity === 'hour' ? 'time' : 'date';
|
|
76
|
+
let description = state.value ? formatMessage(message, {[field]: state.formatValue({month: 'long'})}) : '';
|
|
77
|
+
let descProps = useDescription(description);
|
|
68
78
|
|
|
69
|
-
|
|
70
|
-
|
|
79
|
+
// If within a date picker or date range picker, the date field will have role="presentation" and an aria-describedby
|
|
80
|
+
// will be passed in that references the value (e.g. entire range). Otherwise, add the field's value description.
|
|
81
|
+
let describedBy = props[roleSymbol] === 'presentation'
|
|
82
|
+
? fieldProps['aria-describedby']
|
|
83
|
+
: [descProps['aria-describedby'], fieldProps['aria-describedby']].filter(Boolean).join(' ') || undefined;
|
|
71
84
|
let propsFocusManager = props[focusManagerSymbol];
|
|
72
85
|
let focusManager = useMemo(() => propsFocusManager || createFocusManager(ref), [propsFocusManager, ref]);
|
|
73
86
|
|
|
87
|
+
// Pass labels and other information to segments.
|
|
74
88
|
hookData.set(state, {
|
|
75
|
-
|
|
89
|
+
ariaLabel: props['aria-label'],
|
|
90
|
+
ariaLabelledBy: [props['aria-labelledby'], labelProps.id].filter(Boolean).join(' ') || undefined,
|
|
76
91
|
ariaDescribedBy: describedBy,
|
|
77
92
|
focusManager
|
|
78
93
|
});
|
|
79
94
|
|
|
80
95
|
let autoFocusRef = useRef(props.autoFocus);
|
|
81
96
|
|
|
97
|
+
// When used within a date picker or date range picker, the field gets role="presentation"
|
|
98
|
+
// rather than role="group". Since the date picker/date range picker already has a role="group"
|
|
99
|
+
// with a label and description, and the segments are already labeled by this as well, this
|
|
100
|
+
// avoids very verbose duplicate announcements.
|
|
101
|
+
let fieldDOMProps: HTMLAttributes<HTMLElement>;
|
|
102
|
+
if (props[roleSymbol] === 'presentation') {
|
|
103
|
+
fieldDOMProps = {
|
|
104
|
+
role: 'presentation'
|
|
105
|
+
};
|
|
106
|
+
} else {
|
|
107
|
+
fieldDOMProps = mergeProps(fieldProps, {
|
|
108
|
+
role: 'group',
|
|
109
|
+
'aria-disabled': props.isDisabled || undefined,
|
|
110
|
+
'aria-describedby': describedBy
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
82
114
|
useEffect(() => {
|
|
83
115
|
if (autoFocusRef.current) {
|
|
84
116
|
focusManager.focusFirst();
|
|
@@ -86,6 +118,7 @@ export function useDateField<T extends DateValue>(props: DateFieldProps<T>, stat
|
|
|
86
118
|
autoFocusRef.current = false;
|
|
87
119
|
}, [focusManager]);
|
|
88
120
|
|
|
121
|
+
let domProps = filterDOMProps(props);
|
|
89
122
|
return {
|
|
90
123
|
labelProps: {
|
|
91
124
|
...labelProps,
|
|
@@ -93,11 +126,7 @@ export function useDateField<T extends DateValue>(props: DateFieldProps<T>, stat
|
|
|
93
126
|
focusManager.focusFirst();
|
|
94
127
|
}
|
|
95
128
|
},
|
|
96
|
-
fieldProps: mergeProps(
|
|
97
|
-
role: 'group',
|
|
98
|
-
'aria-disabled': props.isDisabled || undefined,
|
|
99
|
-
'aria-describedby': describedBy
|
|
100
|
-
}),
|
|
129
|
+
fieldProps: mergeProps(domProps, fieldDOMProps, groupProps, focusWithinProps),
|
|
101
130
|
descriptionProps,
|
|
102
131
|
errorMessageProps
|
|
103
132
|
};
|
|
@@ -108,6 +137,6 @@ export function useDateField<T extends DateValue>(props: DateFieldProps<T>, stat
|
|
|
108
137
|
* A time field allows users to enter and edit time values using a keyboard.
|
|
109
138
|
* Each part of a time value is displayed in an individually editable segment.
|
|
110
139
|
*/
|
|
111
|
-
export function useTimeField<T extends TimeValue>(props: AriaTimeFieldProps<T>, state:
|
|
140
|
+
export function useTimeField<T extends TimeValue>(props: AriaTimeFieldProps<T>, state: DateFieldState, ref: RefObject<HTMLElement>): DateFieldAria {
|
|
112
141
|
return useDateField(props, state, ref);
|
|
113
142
|
}
|
package/src/useDatePicker.ts
CHANGED
|
@@ -16,15 +16,16 @@ import {AriaDialogProps} from '@react-types/dialog';
|
|
|
16
16
|
import {CalendarProps} from '@react-types/calendar';
|
|
17
17
|
import {createFocusManager} from '@react-aria/focus';
|
|
18
18
|
import {DatePickerState} from '@react-stately/datepicker';
|
|
19
|
+
import {filterDOMProps, mergeProps, useDescription, useId} from '@react-aria/utils';
|
|
19
20
|
import {HTMLAttributes, RefObject} from 'react';
|
|
20
21
|
// @ts-ignore
|
|
21
22
|
import intlMessages from '../intl/*.json';
|
|
22
|
-
import {
|
|
23
|
+
import {roleSymbol} from './useDateField';
|
|
23
24
|
import {useDatePickerGroup} from './useDatePickerGroup';
|
|
24
25
|
import {useField} from '@react-aria/label';
|
|
25
26
|
import {useLocale, useMessageFormatter} from '@react-aria/i18n';
|
|
26
27
|
|
|
27
|
-
interface DatePickerAria {
|
|
28
|
+
export interface DatePickerAria {
|
|
28
29
|
/** Props for the date picker's visible label element, if any. */
|
|
29
30
|
labelProps: HTMLAttributes<HTMLElement>,
|
|
30
31
|
/** Props for the grouping element containing the date field and button. */
|
|
@@ -62,11 +63,14 @@ export function useDatePicker<T extends DateValue>(props: AriaDatePickerProps<T>
|
|
|
62
63
|
let labelledBy = fieldProps['aria-labelledby'] || fieldProps.id;
|
|
63
64
|
|
|
64
65
|
let {locale} = useLocale();
|
|
65
|
-
let
|
|
66
|
+
let date = state.formatValue(locale, {month: 'long'});
|
|
67
|
+
let description = date ? formatMessage('selectedDateDescription', {date}) : '';
|
|
68
|
+
let descProps = useDescription(description);
|
|
66
69
|
let ariaDescribedBy = [descProps['aria-describedby'], fieldProps['aria-describedby']].filter(Boolean).join(' ') || undefined;
|
|
70
|
+
let domProps = filterDOMProps(props);
|
|
67
71
|
|
|
68
72
|
return {
|
|
69
|
-
groupProps: mergeProps(groupProps, descProps, {
|
|
73
|
+
groupProps: mergeProps(domProps, groupProps, fieldProps, descProps, {
|
|
70
74
|
role: 'group',
|
|
71
75
|
'aria-disabled': props.isDisabled || null,
|
|
72
76
|
'aria-labelledby': labelledBy,
|
|
@@ -81,6 +85,8 @@ export function useDatePicker<T extends DateValue>(props: AriaDatePickerProps<T>
|
|
|
81
85
|
},
|
|
82
86
|
fieldProps: {
|
|
83
87
|
...fieldProps,
|
|
88
|
+
[roleSymbol]: 'presentation',
|
|
89
|
+
'aria-describedby': ariaDescribedBy,
|
|
84
90
|
value: state.value,
|
|
85
91
|
onChange: state.setValue,
|
|
86
92
|
minValue: props.minValue,
|
|
@@ -120,7 +126,9 @@ export function useDatePicker<T extends DateValue>(props: AriaDatePickerProps<T>
|
|
|
120
126
|
isDisabled: props.isDisabled,
|
|
121
127
|
isReadOnly: props.isReadOnly,
|
|
122
128
|
isDateUnavailable: props.isDateUnavailable,
|
|
123
|
-
defaultFocusedValue: state.dateValue ? undefined : props.placeholderValue
|
|
129
|
+
defaultFocusedValue: state.dateValue ? undefined : props.placeholderValue,
|
|
130
|
+
validationState: state.validationState,
|
|
131
|
+
errorMessage: props.errorMessage
|
|
124
132
|
}
|
|
125
133
|
};
|
|
126
134
|
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {DateFieldState, DatePickerState, DateRangePickerState} from '@react-stately/datepicker';
|
|
2
2
|
import {getFocusableTreeWalker} from '@react-aria/focus';
|
|
3
3
|
import {KeyboardEvent} from '@react-types/shared';
|
|
4
4
|
import {mergeProps} from '@react-aria/utils';
|
|
5
5
|
import {RefObject} from 'react';
|
|
6
6
|
import {usePress} from '@react-aria/interactions';
|
|
7
7
|
|
|
8
|
-
export function useDatePickerGroup(state: DatePickerState | DateRangePickerState |
|
|
8
|
+
export function useDatePickerGroup(state: DatePickerState | DateRangePickerState | DateFieldState, ref: RefObject<HTMLElement>) {
|
|
9
9
|
// Open the popover on alt + arrow down
|
|
10
10
|
let onKeyDown = (e: KeyboardEvent) => {
|
|
11
|
-
if (e.altKey && e.key === 'ArrowDown' && 'setOpen' in state) {
|
|
11
|
+
if (e.altKey && (e.key === 'ArrowDown' || e.key === 'ArrowUp') && 'setOpen' in state) {
|
|
12
12
|
e.preventDefault();
|
|
13
13
|
e.stopPropagation();
|
|
14
14
|
state.setOpen(true);
|
|
@@ -15,17 +15,18 @@ import {AriaDatePickerProps, AriaDateRangePickerProps, DateValue} from '@react-t
|
|
|
15
15
|
import {AriaDialogProps} from '@react-types/dialog';
|
|
16
16
|
import {createFocusManager} from '@react-aria/focus';
|
|
17
17
|
import {DateRangePickerState} from '@react-stately/datepicker';
|
|
18
|
+
import {filterDOMProps, mergeProps, useDescription, useId} from '@react-aria/utils';
|
|
19
|
+
import {focusManagerSymbol, roleSymbol} from './useDateField';
|
|
18
20
|
import {HTMLAttributes, RefObject, useMemo} from 'react';
|
|
19
21
|
// @ts-ignore
|
|
20
22
|
import intlMessages from '../intl/*.json';
|
|
21
|
-
import {mergeProps, useDescription, useId, useLabels} from '@react-aria/utils';
|
|
22
23
|
import {RangeCalendarProps} from '@react-types/calendar';
|
|
23
24
|
import {useDatePickerGroup} from './useDatePickerGroup';
|
|
24
25
|
import {useField} from '@react-aria/label';
|
|
25
26
|
import {useFocusWithin} from '@react-aria/interactions';
|
|
26
27
|
import {useLocale, useMessageFormatter} from '@react-aria/i18n';
|
|
27
28
|
|
|
28
|
-
interface DateRangePickerAria {
|
|
29
|
+
export interface DateRangePickerAria {
|
|
29
30
|
/** Props for the date range picker's visible label element, if any. */
|
|
30
31
|
labelProps: HTMLAttributes<HTMLElement>,
|
|
31
32
|
/** Props for the grouping element containing the date fields and button. */
|
|
@@ -46,10 +47,6 @@ interface DateRangePickerAria {
|
|
|
46
47
|
calendarProps: RangeCalendarProps<DateValue>
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
// Used to pass the focus manager to the date fields.
|
|
50
|
-
// Ideally we'd use a Symbol for this, but React doesn't support them: https://github.com/facebook/react/issues/7552
|
|
51
|
-
export const focusManagerSymbol = '__focusManager_' + Date.now();
|
|
52
|
-
|
|
53
50
|
/**
|
|
54
51
|
* Provides the behavior and accessibility implementation for a date picker component.
|
|
55
52
|
* A date range picker combines two DateFields and a RangeCalendar popover to allow
|
|
@@ -65,18 +62,19 @@ export function useDateRangePicker<T extends DateValue>(props: AriaDateRangePick
|
|
|
65
62
|
let labelledBy = fieldProps['aria-labelledby'] || fieldProps.id;
|
|
66
63
|
|
|
67
64
|
let {locale} = useLocale();
|
|
68
|
-
let
|
|
65
|
+
let range = state.formatValue(locale, {month: 'long'});
|
|
66
|
+
let description = range ? formatMessage('selectedRangeDescription', {startDate: range.start, endDate: range.end}) : '';
|
|
69
67
|
let descProps = useDescription(description);
|
|
70
68
|
|
|
71
|
-
let startFieldProps =
|
|
69
|
+
let startFieldProps = {
|
|
72
70
|
'aria-label': formatMessage('startDate'),
|
|
73
71
|
'aria-labelledby': labelledBy
|
|
74
|
-
}
|
|
72
|
+
};
|
|
75
73
|
|
|
76
|
-
let endFieldProps =
|
|
74
|
+
let endFieldProps = {
|
|
77
75
|
'aria-label': formatMessage('endDate'),
|
|
78
76
|
'aria-labelledby': labelledBy
|
|
79
|
-
}
|
|
77
|
+
};
|
|
80
78
|
|
|
81
79
|
let buttonId = useId();
|
|
82
80
|
let dialogId = useId();
|
|
@@ -92,6 +90,8 @@ export function useDateRangePicker<T extends DateValue>(props: AriaDateRangePick
|
|
|
92
90
|
let focusManager = useMemo(() => createFocusManager(ref), [ref]);
|
|
93
91
|
let commonFieldProps = {
|
|
94
92
|
[focusManagerSymbol]: focusManager,
|
|
93
|
+
[roleSymbol]: 'presentation',
|
|
94
|
+
'aria-describedby': ariaDescribedBy,
|
|
95
95
|
minValue: props.minValue,
|
|
96
96
|
maxValue: props.maxValue,
|
|
97
97
|
placeholderValue: props.placeholderValue,
|
|
@@ -104,8 +104,10 @@ export function useDateRangePicker<T extends DateValue>(props: AriaDateRangePick
|
|
|
104
104
|
validationState: state.validationState
|
|
105
105
|
};
|
|
106
106
|
|
|
107
|
+
let domProps = filterDOMProps(props);
|
|
108
|
+
|
|
107
109
|
return {
|
|
108
|
-
groupProps: mergeProps(groupProps, fieldProps, descProps, focusWithinProps, {
|
|
110
|
+
groupProps: mergeProps(domProps, groupProps, fieldProps, descProps, focusWithinProps, {
|
|
109
111
|
role: 'group',
|
|
110
112
|
'aria-disabled': props.isDisabled || null,
|
|
111
113
|
'aria-describedby': ariaDescribedBy
|
|
@@ -133,7 +135,6 @@ export function useDateRangePicker<T extends DateValue>(props: AriaDateRangePick
|
|
|
133
135
|
startFieldProps: {
|
|
134
136
|
...startFieldProps,
|
|
135
137
|
...commonFieldProps,
|
|
136
|
-
'aria-describedby': fieldProps['aria-describedby'],
|
|
137
138
|
value: state.value?.start,
|
|
138
139
|
onChange: start => state.setDateTime('start', start),
|
|
139
140
|
autoFocus: props.autoFocus
|
|
@@ -141,7 +142,6 @@ export function useDateRangePicker<T extends DateValue>(props: AriaDateRangePick
|
|
|
141
142
|
endFieldProps: {
|
|
142
143
|
...endFieldProps,
|
|
143
144
|
...commonFieldProps,
|
|
144
|
-
'aria-describedby': fieldProps['aria-describedby'],
|
|
145
145
|
value: state.value?.end,
|
|
146
146
|
onChange: end => state.setDateTime('end', end)
|
|
147
147
|
},
|
|
@@ -157,7 +157,9 @@ export function useDateRangePicker<T extends DateValue>(props: AriaDateRangePick
|
|
|
157
157
|
isReadOnly: props.isReadOnly,
|
|
158
158
|
isDateUnavailable: props.isDateUnavailable,
|
|
159
159
|
allowsNonContiguousRanges: props.allowsNonContiguousRanges,
|
|
160
|
-
defaultFocusedValue: state.dateRange ? undefined : props.placeholderValue
|
|
160
|
+
defaultFocusedValue: state.dateRange ? undefined : props.placeholderValue,
|
|
161
|
+
validationState: state.validationState,
|
|
162
|
+
errorMessage: props.errorMessage
|
|
161
163
|
}
|
|
162
164
|
};
|
|
163
165
|
}
|
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
34
|
let {locale, direction} = 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
|
|
|
@@ -301,22 +300,6 @@ export function useDateSegment(segment: DateSegment, state: DatePickerFieldState
|
|
|
301
300
|
}
|
|
302
301
|
});
|
|
303
302
|
|
|
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
303
|
// For Android: prevent selection on long press.
|
|
321
304
|
useEvent(ref, 'selectstart', e => {
|
|
322
305
|
e.preventDefault();
|
|
@@ -341,6 +324,14 @@ export function useDateSegment(segment: DateSegment, state: DatePickerFieldState
|
|
|
341
324
|
let id = useId();
|
|
342
325
|
let isEditable = !state.isDisabled && !state.isReadOnly && segment.isEditable;
|
|
343
326
|
|
|
327
|
+
// Prepend the label passed from the field to each segment name.
|
|
328
|
+
// This is needed because VoiceOver on iOS does not announce groups.
|
|
329
|
+
let name = segment.type === 'literal' ? '' : displayNames.of(segment.type);
|
|
330
|
+
let labelProps = useLabels({
|
|
331
|
+
'aria-label': (ariaLabel ? ariaLabel + ' ' : '') + name,
|
|
332
|
+
'aria-labelledby': ariaLabelledBy
|
|
333
|
+
});
|
|
334
|
+
|
|
344
335
|
// Literal segments should not be visible to screen readers. We don't really need any of the above,
|
|
345
336
|
// but the rules of hooks mean hooks cannot be conditional so we have to put this condition here.
|
|
346
337
|
if (segment.type === 'literal') {
|
|
@@ -352,12 +343,10 @@ export function useDateSegment(segment: DateSegment, state: DatePickerFieldState
|
|
|
352
343
|
}
|
|
353
344
|
|
|
354
345
|
return {
|
|
355
|
-
segmentProps: mergeProps(spinButtonProps,
|
|
346
|
+
segmentProps: mergeProps(spinButtonProps, labelProps, {
|
|
356
347
|
id,
|
|
357
348
|
...touchPropOverrides,
|
|
358
349
|
'aria-invalid': state.validationState === 'invalid' ? 'true' : undefined,
|
|
359
|
-
'aria-label': displayNames.of(segment.type),
|
|
360
|
-
'aria-labelledby': `${ariaLabelledBy} ${id}`,
|
|
361
350
|
'aria-describedby': ariaDescribedBy,
|
|
362
351
|
'aria-placeholder': segment.isPlaceholder ? segment.text : undefined,
|
|
363
352
|
'aria-readonly': state.isReadOnly || !segment.isEditable ? 'true' : undefined,
|
|
@@ -373,7 +362,16 @@ export function useDateSegment(segment: DateSegment, state: DatePickerFieldState
|
|
|
373
362
|
onKeyDown,
|
|
374
363
|
onFocus,
|
|
375
364
|
style: {
|
|
376
|
-
caretColor: 'transparent'
|
|
365
|
+
caretColor: 'transparent',
|
|
366
|
+
userSelect: 'none',
|
|
367
|
+
WebkitUserSelect: 'none'
|
|
368
|
+
},
|
|
369
|
+
// Prevent pointer events from reaching useDatePickerGroup, and allow native browser behavior to focus the segment.
|
|
370
|
+
onPointerDown(e) {
|
|
371
|
+
e.stopPropagation();
|
|
372
|
+
},
|
|
373
|
+
onMouseDown(e) {
|
|
374
|
+
e.stopPropagation();
|
|
377
375
|
}
|
|
378
376
|
})
|
|
379
377
|
};
|