@react-aria/datepicker 3.8.1 → 3.9.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/ar-AE.main.js +21 -0
- package/dist/ar-AE.main.js.map +1 -0
- package/dist/ar-AE.mjs +23 -0
- package/dist/ar-AE.module.js +23 -0
- package/dist/ar-AE.module.js.map +1 -0
- package/dist/bg-BG.main.js +21 -0
- package/dist/bg-BG.main.js.map +1 -0
- package/dist/bg-BG.mjs +23 -0
- package/dist/bg-BG.module.js +23 -0
- package/dist/bg-BG.module.js.map +1 -0
- package/dist/cs-CZ.main.js +21 -0
- package/dist/cs-CZ.main.js.map +1 -0
- package/dist/cs-CZ.mjs +23 -0
- package/dist/cs-CZ.module.js +23 -0
- package/dist/cs-CZ.module.js.map +1 -0
- package/dist/da-DK.main.js +21 -0
- package/dist/da-DK.main.js.map +1 -0
- package/dist/da-DK.mjs +23 -0
- package/dist/da-DK.module.js +23 -0
- package/dist/da-DK.module.js.map +1 -0
- package/dist/de-DE.main.js +21 -0
- package/dist/de-DE.main.js.map +1 -0
- package/dist/de-DE.mjs +23 -0
- package/dist/de-DE.module.js +23 -0
- package/dist/de-DE.module.js.map +1 -0
- package/dist/el-GR.main.js +21 -0
- package/dist/el-GR.main.js.map +1 -0
- package/dist/el-GR.mjs +23 -0
- package/dist/el-GR.module.js +23 -0
- package/dist/el-GR.module.js.map +1 -0
- package/dist/en-US.main.js +21 -0
- package/dist/en-US.main.js.map +1 -0
- package/dist/en-US.mjs +23 -0
- package/dist/en-US.module.js +23 -0
- package/dist/en-US.module.js.map +1 -0
- package/dist/es-ES.main.js +21 -0
- package/dist/es-ES.main.js.map +1 -0
- package/dist/es-ES.mjs +23 -0
- package/dist/es-ES.module.js +23 -0
- package/dist/es-ES.module.js.map +1 -0
- package/dist/et-EE.main.js +21 -0
- package/dist/et-EE.main.js.map +1 -0
- package/dist/et-EE.mjs +23 -0
- package/dist/et-EE.module.js +23 -0
- package/dist/et-EE.module.js.map +1 -0
- package/dist/fi-FI.main.js +21 -0
- package/dist/fi-FI.main.js.map +1 -0
- package/dist/fi-FI.mjs +23 -0
- package/dist/fi-FI.module.js +23 -0
- package/dist/fi-FI.module.js.map +1 -0
- package/dist/fr-FR.main.js +21 -0
- package/dist/fr-FR.main.js.map +1 -0
- package/dist/fr-FR.mjs +23 -0
- package/dist/fr-FR.module.js +23 -0
- package/dist/fr-FR.module.js.map +1 -0
- package/dist/he-IL.main.js +21 -0
- package/dist/he-IL.main.js.map +1 -0
- package/dist/he-IL.mjs +23 -0
- package/dist/he-IL.module.js +23 -0
- package/dist/he-IL.module.js.map +1 -0
- package/dist/hr-HR.main.js +21 -0
- package/dist/hr-HR.main.js.map +1 -0
- package/dist/hr-HR.mjs +23 -0
- package/dist/hr-HR.module.js +23 -0
- package/dist/hr-HR.module.js.map +1 -0
- package/dist/hu-HU.main.js +21 -0
- package/dist/hu-HU.main.js.map +1 -0
- package/dist/hu-HU.mjs +23 -0
- package/dist/hu-HU.module.js +23 -0
- package/dist/hu-HU.module.js.map +1 -0
- package/dist/import.mjs +137 -714
- package/dist/it-IT.main.js +21 -0
- package/dist/it-IT.main.js.map +1 -0
- package/dist/it-IT.mjs +23 -0
- package/dist/it-IT.module.js +23 -0
- package/dist/it-IT.module.js.map +1 -0
- package/dist/ja-JP.main.js +21 -0
- package/dist/ja-JP.main.js.map +1 -0
- package/dist/ja-JP.mjs +23 -0
- package/dist/ja-JP.module.js +23 -0
- package/dist/ja-JP.module.js.map +1 -0
- package/dist/ko-KR.main.js +21 -0
- package/dist/ko-KR.main.js.map +1 -0
- package/dist/ko-KR.mjs +23 -0
- package/dist/ko-KR.module.js +23 -0
- package/dist/ko-KR.module.js.map +1 -0
- package/dist/lt-LT.main.js +21 -0
- package/dist/lt-LT.main.js.map +1 -0
- package/dist/lt-LT.mjs +23 -0
- package/dist/lt-LT.module.js +23 -0
- package/dist/lt-LT.module.js.map +1 -0
- package/dist/lv-LV.main.js +21 -0
- package/dist/lv-LV.main.js.map +1 -0
- package/dist/lv-LV.mjs +23 -0
- package/dist/lv-LV.module.js +23 -0
- package/dist/lv-LV.module.js.map +1 -0
- package/dist/main.js +137 -713
- package/dist/main.js.map +1 -1
- package/dist/module.js +137 -714
- package/dist/module.js.map +1 -1
- package/dist/nb-NO.main.js +21 -0
- package/dist/nb-NO.main.js.map +1 -0
- package/dist/nb-NO.mjs +23 -0
- package/dist/nb-NO.module.js +23 -0
- package/dist/nb-NO.module.js.map +1 -0
- package/dist/nl-NL.main.js +21 -0
- package/dist/nl-NL.main.js.map +1 -0
- package/dist/nl-NL.mjs +23 -0
- package/dist/nl-NL.module.js +23 -0
- package/dist/nl-NL.module.js.map +1 -0
- package/dist/pl-PL.main.js +21 -0
- package/dist/pl-PL.main.js.map +1 -0
- package/dist/pl-PL.mjs +23 -0
- package/dist/pl-PL.module.js +23 -0
- package/dist/pl-PL.module.js.map +1 -0
- package/dist/pt-BR.main.js +21 -0
- package/dist/pt-BR.main.js.map +1 -0
- package/dist/pt-BR.mjs +23 -0
- package/dist/pt-BR.module.js +23 -0
- package/dist/pt-BR.module.js.map +1 -0
- package/dist/pt-PT.main.js +21 -0
- package/dist/pt-PT.main.js.map +1 -0
- package/dist/pt-PT.mjs +23 -0
- package/dist/pt-PT.module.js +23 -0
- package/dist/pt-PT.module.js.map +1 -0
- package/dist/ro-RO.main.js +21 -0
- package/dist/ro-RO.main.js.map +1 -0
- package/dist/ro-RO.mjs +23 -0
- package/dist/ro-RO.module.js +23 -0
- package/dist/ro-RO.module.js.map +1 -0
- package/dist/ru-RU.main.js +21 -0
- package/dist/ru-RU.main.js.map +1 -0
- package/dist/ru-RU.mjs +23 -0
- package/dist/ru-RU.module.js +23 -0
- package/dist/ru-RU.module.js.map +1 -0
- package/dist/sk-SK.main.js +21 -0
- package/dist/sk-SK.main.js.map +1 -0
- package/dist/sk-SK.mjs +23 -0
- package/dist/sk-SK.module.js +23 -0
- package/dist/sk-SK.module.js.map +1 -0
- package/dist/sl-SI.main.js +21 -0
- package/dist/sl-SI.main.js.map +1 -0
- package/dist/sl-SI.mjs +23 -0
- package/dist/sl-SI.module.js +23 -0
- package/dist/sl-SI.module.js.map +1 -0
- package/dist/sr-SP.main.js +21 -0
- package/dist/sr-SP.main.js.map +1 -0
- package/dist/sr-SP.mjs +23 -0
- package/dist/sr-SP.module.js +23 -0
- package/dist/sr-SP.module.js.map +1 -0
- package/dist/sv-SE.main.js +21 -0
- package/dist/sv-SE.main.js.map +1 -0
- package/dist/sv-SE.mjs +23 -0
- package/dist/sv-SE.module.js +23 -0
- package/dist/sv-SE.module.js.map +1 -0
- package/dist/tr-TR.main.js +21 -0
- package/dist/tr-TR.main.js.map +1 -0
- package/dist/tr-TR.mjs +23 -0
- package/dist/tr-TR.module.js +23 -0
- package/dist/tr-TR.module.js.map +1 -0
- package/dist/types.d.ts +5 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/uk-UA.main.js +21 -0
- package/dist/uk-UA.main.js.map +1 -0
- package/dist/uk-UA.mjs +23 -0
- package/dist/uk-UA.module.js +23 -0
- package/dist/uk-UA.module.js.map +1 -0
- package/dist/zh-CN.main.js +21 -0
- package/dist/zh-CN.main.js.map +1 -0
- package/dist/zh-CN.mjs +23 -0
- package/dist/zh-CN.module.js +23 -0
- package/dist/zh-CN.module.js.map +1 -0
- package/dist/zh-TW.main.js +21 -0
- package/dist/zh-TW.main.js.map +1 -0
- package/dist/zh-TW.mjs +23 -0
- package/dist/zh-TW.module.js +23 -0
- package/dist/zh-TW.module.js.map +1 -0
- package/package.json +19 -17
- package/src/useDateField.ts +46 -17
- package/src/useDatePicker.ts +17 -9
- package/src/useDateRangePicker.ts +41 -12
- package/src/useDisplayNames.ts +6 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-aria/datepicker",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.9.1",
|
|
4
4
|
"description": "Spectrum UI components in React",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"main": "dist/main.js",
|
|
@@ -22,21 +22,23 @@
|
|
|
22
22
|
"url": "https://github.com/adobe/react-spectrum"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@internationalized/date": "^3.5.
|
|
26
|
-
"@internationalized/number": "^3.
|
|
27
|
-
"@internationalized/string": "^3.
|
|
28
|
-
"@react-aria/focus": "^3.
|
|
29
|
-
"@react-aria/
|
|
30
|
-
"@react-aria/
|
|
31
|
-
"@react-aria/
|
|
32
|
-
"@react-aria/
|
|
33
|
-
"@react-aria/
|
|
34
|
-
"@react-
|
|
35
|
-
"@react-
|
|
36
|
-
"@react-
|
|
37
|
-
"@react-types/
|
|
38
|
-
"@react-types/
|
|
39
|
-
"@react-types/
|
|
25
|
+
"@internationalized/date": "^3.5.1",
|
|
26
|
+
"@internationalized/number": "^3.5.0",
|
|
27
|
+
"@internationalized/string": "^3.2.0",
|
|
28
|
+
"@react-aria/focus": "^3.16.0",
|
|
29
|
+
"@react-aria/form": "^3.0.1",
|
|
30
|
+
"@react-aria/i18n": "^3.10.0",
|
|
31
|
+
"@react-aria/interactions": "^3.20.1",
|
|
32
|
+
"@react-aria/label": "^3.7.4",
|
|
33
|
+
"@react-aria/spinbutton": "^3.6.1",
|
|
34
|
+
"@react-aria/utils": "^3.23.0",
|
|
35
|
+
"@react-stately/datepicker": "^3.9.1",
|
|
36
|
+
"@react-stately/form": "^3.0.0",
|
|
37
|
+
"@react-types/button": "^3.9.1",
|
|
38
|
+
"@react-types/calendar": "^3.4.3",
|
|
39
|
+
"@react-types/datepicker": "^3.7.1",
|
|
40
|
+
"@react-types/dialog": "^3.5.7",
|
|
41
|
+
"@react-types/shared": "^3.22.0",
|
|
40
42
|
"@swc/helpers": "^0.5.0"
|
|
41
43
|
},
|
|
42
44
|
"peerDependencies": {
|
|
@@ -46,5 +48,5 @@
|
|
|
46
48
|
"publishConfig": {
|
|
47
49
|
"access": "public"
|
|
48
50
|
},
|
|
49
|
-
"gitHead": "
|
|
51
|
+
"gitHead": "86b38c87868ce7f262e0df905e5ac4eb2653791d"
|
|
50
52
|
}
|
package/src/useDateField.ts
CHANGED
|
@@ -13,23 +13,24 @@
|
|
|
13
13
|
import {AriaDateFieldProps as AriaDateFieldPropsBase, AriaTimeFieldProps, DateValue, TimeValue} from '@react-types/datepicker';
|
|
14
14
|
import {createFocusManager, FocusManager} from '@react-aria/focus';
|
|
15
15
|
import {DateFieldState, TimeFieldState} from '@react-stately/datepicker';
|
|
16
|
-
import {DOMAttributes, GroupDOMAttributes, KeyboardEvent} from '@react-types/shared';
|
|
16
|
+
import {DOMAttributes, GroupDOMAttributes, KeyboardEvent, ValidationResult} from '@react-types/shared';
|
|
17
17
|
import {filterDOMProps, mergeProps, useDescription, useFormReset} from '@react-aria/utils';
|
|
18
|
-
import {
|
|
18
|
+
import {InputHTMLAttributes, RefObject, useEffect, useMemo, useRef} from 'react';
|
|
19
19
|
// @ts-ignore
|
|
20
20
|
import intlMessages from '../intl/*.json';
|
|
21
21
|
import {useDatePickerGroup} from './useDatePickerGroup';
|
|
22
22
|
import {useField} from '@react-aria/label';
|
|
23
23
|
import {useFocusWithin} from '@react-aria/interactions';
|
|
24
|
+
import {useFormValidation} from '@react-aria/form';
|
|
24
25
|
import {useLocalizedStringFormatter} from '@react-aria/i18n';
|
|
25
26
|
|
|
26
27
|
// Allows this hook to also be used with TimeField
|
|
27
|
-
export interface AriaDateFieldOptions<T extends DateValue> extends Omit<AriaDateFieldPropsBase<T>, 'value' | 'defaultValue' | 'onChange' | 'minValue' | 'maxValue' | 'placeholderValue'> {
|
|
28
|
+
export interface AriaDateFieldOptions<T extends DateValue> extends Omit<AriaDateFieldPropsBase<T>, 'value' | 'defaultValue' | 'onChange' | 'minValue' | 'maxValue' | 'placeholderValue' | 'validate'> {
|
|
28
29
|
/** A ref for the hidden input element for HTML form submission. */
|
|
29
30
|
inputRef?: RefObject<HTMLInputElement>
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
export interface DateFieldAria {
|
|
33
|
+
export interface DateFieldAria extends ValidationResult {
|
|
33
34
|
/** Props for the field's visible label element, if any. */
|
|
34
35
|
labelProps: DOMAttributes,
|
|
35
36
|
/** Props for the field grouping element. */
|
|
@@ -63,25 +64,32 @@ export const focusManagerSymbol = '__focusManager_' + Date.now();
|
|
|
63
64
|
* Each part of a date value is displayed in an individually editable segment.
|
|
64
65
|
*/
|
|
65
66
|
export function useDateField<T extends DateValue>(props: AriaDateFieldOptions<T>, state: DateFieldState, ref: RefObject<Element>): DateFieldAria {
|
|
67
|
+
let {isInvalid, validationErrors, validationDetails} = state.displayValidation;
|
|
66
68
|
let {labelProps, fieldProps, descriptionProps, errorMessageProps} = useField({
|
|
67
69
|
...props,
|
|
68
|
-
labelElementType: 'span'
|
|
70
|
+
labelElementType: 'span',
|
|
71
|
+
isInvalid,
|
|
72
|
+
errorMessage: props.errorMessage || validationErrors
|
|
69
73
|
});
|
|
70
74
|
|
|
75
|
+
let valueOnFocus = useRef<DateValue | null>(null);
|
|
71
76
|
let {focusWithinProps} = useFocusWithin({
|
|
72
77
|
...props,
|
|
73
|
-
|
|
78
|
+
onFocusWithin(e) {
|
|
79
|
+
valueOnFocus.current = state.value;
|
|
80
|
+
props.onFocus?.(e);
|
|
81
|
+
},
|
|
82
|
+
onBlurWithin: (e) => {
|
|
74
83
|
state.confirmPlaceholder();
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
props.onBlur(e);
|
|
84
|
+
if (state.value !== valueOnFocus.current) {
|
|
85
|
+
state.commitValidation();
|
|
78
86
|
}
|
|
87
|
+
props.onBlur?.(e);
|
|
79
88
|
},
|
|
80
|
-
onFocusWithin: props.onFocus,
|
|
81
89
|
onFocusWithinChange: props.onFocusChange
|
|
82
90
|
});
|
|
83
91
|
|
|
84
|
-
let stringFormatter = useLocalizedStringFormatter(intlMessages);
|
|
92
|
+
let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-aria/datepicker');
|
|
85
93
|
let message = state.maxGranularity === 'hour' ? 'selectedTimeDescription' : 'selectedDateDescription';
|
|
86
94
|
let field = state.maxGranularity === 'hour' ? 'time' : 'date';
|
|
87
95
|
let description = state.value ? stringFormatter.format(message, {[field]: state.formatValue({month: 'long'})}) : '';
|
|
@@ -131,6 +139,28 @@ export function useDateField<T extends DateValue>(props: AriaDateFieldOptions<T>
|
|
|
131
139
|
}, [focusManager]);
|
|
132
140
|
|
|
133
141
|
useFormReset(props.inputRef, state.value, state.setValue);
|
|
142
|
+
useFormValidation({
|
|
143
|
+
...props,
|
|
144
|
+
focus() {
|
|
145
|
+
focusManager.focusFirst();
|
|
146
|
+
}
|
|
147
|
+
}, state, props.inputRef);
|
|
148
|
+
|
|
149
|
+
let inputProps: InputHTMLAttributes<HTMLInputElement> = {
|
|
150
|
+
type: 'hidden',
|
|
151
|
+
name: props.name,
|
|
152
|
+
value: state.value?.toString() || ''
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
if (props.validationBehavior === 'native') {
|
|
156
|
+
// Use a hidden <input type="text"> rather than <input type="hidden">
|
|
157
|
+
// so that an empty value blocks HTML form submission when the field is required.
|
|
158
|
+
inputProps.type = 'text';
|
|
159
|
+
inputProps.hidden = true;
|
|
160
|
+
inputProps.required = props.isRequired;
|
|
161
|
+
// Ignore react warning.
|
|
162
|
+
inputProps.onChange = () => {};
|
|
163
|
+
}
|
|
134
164
|
|
|
135
165
|
let domProps = filterDOMProps(props);
|
|
136
166
|
return {
|
|
@@ -152,13 +182,12 @@ export function useDateField<T extends DateValue>(props: AriaDateFieldOptions<T>
|
|
|
152
182
|
}
|
|
153
183
|
}
|
|
154
184
|
}),
|
|
155
|
-
inputProps
|
|
156
|
-
type: 'hidden',
|
|
157
|
-
name: props.name,
|
|
158
|
-
value: state.value?.toString() || ''
|
|
159
|
-
},
|
|
185
|
+
inputProps,
|
|
160
186
|
descriptionProps,
|
|
161
|
-
errorMessageProps
|
|
187
|
+
errorMessageProps,
|
|
188
|
+
isInvalid,
|
|
189
|
+
validationErrors,
|
|
190
|
+
validationDetails
|
|
162
191
|
};
|
|
163
192
|
}
|
|
164
193
|
|
package/src/useDatePicker.ts
CHANGED
|
@@ -16,10 +16,11 @@ 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 {DOMAttributes, GroupDOMAttributes, KeyboardEvent} from '@react-types/shared';
|
|
19
|
+
import {DOMAttributes, GroupDOMAttributes, KeyboardEvent, ValidationResult} from '@react-types/shared';
|
|
20
20
|
import {filterDOMProps, mergeProps, useDescription, useId} from '@react-aria/utils';
|
|
21
21
|
// @ts-ignore
|
|
22
22
|
import intlMessages from '../intl/*.json';
|
|
23
|
+
import {privateValidationStateProp} from '@react-stately/form';
|
|
23
24
|
import {RefObject, useMemo} from 'react';
|
|
24
25
|
import {roleSymbol} from './useDateField';
|
|
25
26
|
import {useDatePickerGroup} from './useDatePickerGroup';
|
|
@@ -27,7 +28,7 @@ import {useField} from '@react-aria/label';
|
|
|
27
28
|
import {useFocusWithin} from '@react-aria/interactions';
|
|
28
29
|
import {useLocale, useLocalizedStringFormatter} from '@react-aria/i18n';
|
|
29
30
|
|
|
30
|
-
export interface DatePickerAria {
|
|
31
|
+
export interface DatePickerAria extends ValidationResult {
|
|
31
32
|
/** Props for the date picker's visible label element, if any. */
|
|
32
33
|
labelProps: DOMAttributes,
|
|
33
34
|
/** Props for the grouping element containing the date field and button. */
|
|
@@ -54,11 +55,14 @@ export function useDatePicker<T extends DateValue>(props: AriaDatePickerProps<T>
|
|
|
54
55
|
let buttonId = useId();
|
|
55
56
|
let dialogId = useId();
|
|
56
57
|
let fieldId = useId();
|
|
57
|
-
let stringFormatter = useLocalizedStringFormatter(intlMessages);
|
|
58
|
+
let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-aria/datepicker');
|
|
58
59
|
|
|
60
|
+
let {isInvalid, validationErrors, validationDetails} = state.displayValidation;
|
|
59
61
|
let {labelProps, fieldProps, descriptionProps, errorMessageProps} = useField({
|
|
60
62
|
...props,
|
|
61
|
-
labelElementType: 'span'
|
|
63
|
+
labelElementType: 'span',
|
|
64
|
+
isInvalid,
|
|
65
|
+
errorMessage: props.errorMessage || validationErrors
|
|
62
66
|
});
|
|
63
67
|
|
|
64
68
|
let groupProps = useDatePickerGroup(state, ref);
|
|
@@ -119,8 +123,6 @@ export function useDatePicker<T extends DateValue>(props: AriaDatePickerProps<T>
|
|
|
119
123
|
'aria-describedby': ariaDescribedBy,
|
|
120
124
|
value: state.value,
|
|
121
125
|
onChange: state.setValue,
|
|
122
|
-
minValue: props.minValue,
|
|
123
|
-
maxValue: props.maxValue,
|
|
124
126
|
placeholderValue: props.placeholderValue,
|
|
125
127
|
hideTimeZone: props.hideTimeZone,
|
|
126
128
|
hourCycle: props.hourCycle,
|
|
@@ -129,7 +131,9 @@ export function useDatePicker<T extends DateValue>(props: AriaDatePickerProps<T>
|
|
|
129
131
|
isDisabled: props.isDisabled,
|
|
130
132
|
isReadOnly: props.isReadOnly,
|
|
131
133
|
isRequired: props.isRequired,
|
|
132
|
-
|
|
134
|
+
validationBehavior: props.validationBehavior,
|
|
135
|
+
// DatePicker owns the validation state for the date field.
|
|
136
|
+
[privateValidationStateProp]: state,
|
|
133
137
|
autoFocus: props.autoFocus,
|
|
134
138
|
name: props.name
|
|
135
139
|
},
|
|
@@ -143,6 +147,7 @@ export function useDatePicker<T extends DateValue>(props: AriaDatePickerProps<T>
|
|
|
143
147
|
'aria-labelledby': `${buttonId} ${labelledBy}`,
|
|
144
148
|
'aria-describedby': ariaDescribedBy,
|
|
145
149
|
'aria-expanded': state.isOpen,
|
|
150
|
+
isDisabled: props.isDisabled || props.isReadOnly,
|
|
146
151
|
onPress: () => state.setOpen(true)
|
|
147
152
|
},
|
|
148
153
|
dialogProps: {
|
|
@@ -160,7 +165,10 @@ export function useDatePicker<T extends DateValue>(props: AriaDatePickerProps<T>
|
|
|
160
165
|
isDateUnavailable: props.isDateUnavailable,
|
|
161
166
|
defaultFocusedValue: state.dateValue ? undefined : props.placeholderValue,
|
|
162
167
|
isInvalid: state.isInvalid,
|
|
163
|
-
errorMessage: props.errorMessage
|
|
164
|
-
}
|
|
168
|
+
errorMessage: typeof props.errorMessage === 'function' ? props.errorMessage(state.displayValidation) : (props.errorMessage || state.displayValidation.validationErrors.join(' '))
|
|
169
|
+
},
|
|
170
|
+
isInvalid,
|
|
171
|
+
validationErrors,
|
|
172
|
+
validationDetails
|
|
165
173
|
};
|
|
166
174
|
}
|
|
@@ -15,19 +15,20 @@ 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 {
|
|
18
|
+
import {DEFAULT_VALIDATION_RESULT, mergeValidation, privateValidationStateProp} from '@react-stately/form';
|
|
19
|
+
import {DOMAttributes, GroupDOMAttributes, KeyboardEvent, ValidationResult} from '@react-types/shared';
|
|
19
20
|
import {filterDOMProps, mergeProps, useDescription, useId} from '@react-aria/utils';
|
|
20
21
|
import {focusManagerSymbol, roleSymbol} from './useDateField';
|
|
21
22
|
// @ts-ignore
|
|
22
23
|
import intlMessages from '../intl/*.json';
|
|
23
24
|
import {RangeCalendarProps} from '@react-types/calendar';
|
|
24
|
-
import {RefObject, useMemo} from 'react';
|
|
25
|
+
import {RefObject, useMemo, useRef} from 'react';
|
|
25
26
|
import {useDatePickerGroup} from './useDatePickerGroup';
|
|
26
27
|
import {useField} from '@react-aria/label';
|
|
27
28
|
import {useFocusWithin} from '@react-aria/interactions';
|
|
28
29
|
import {useLocale, useLocalizedStringFormatter} from '@react-aria/i18n';
|
|
29
30
|
|
|
30
|
-
export interface DateRangePickerAria {
|
|
31
|
+
export interface DateRangePickerAria extends ValidationResult {
|
|
31
32
|
/** Props for the date range picker's visible label element, if any. */
|
|
32
33
|
labelProps: DOMAttributes,
|
|
33
34
|
/** Props for the grouping element containing the date fields and button. */
|
|
@@ -54,10 +55,13 @@ export interface DateRangePickerAria {
|
|
|
54
55
|
* users to enter or select a date and time range.
|
|
55
56
|
*/
|
|
56
57
|
export function useDateRangePicker<T extends DateValue>(props: AriaDateRangePickerProps<T>, state: DateRangePickerState, ref: RefObject<Element>): DateRangePickerAria {
|
|
57
|
-
let stringFormatter = useLocalizedStringFormatter(intlMessages);
|
|
58
|
+
let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-aria/datepicker');
|
|
59
|
+
let {isInvalid, validationErrors, validationDetails} = state.displayValidation;
|
|
58
60
|
let {labelProps, fieldProps, descriptionProps, errorMessageProps} = useField({
|
|
59
61
|
...props,
|
|
60
|
-
labelElementType: 'span'
|
|
62
|
+
labelElementType: 'span',
|
|
63
|
+
isInvalid,
|
|
64
|
+
errorMessage: props.errorMessage || validationErrors
|
|
61
65
|
});
|
|
62
66
|
|
|
63
67
|
let labelledBy = fieldProps['aria-labelledby'] || fieldProps.id;
|
|
@@ -92,8 +96,6 @@ export function useDateRangePicker<T extends DateValue>(props: AriaDateRangePick
|
|
|
92
96
|
[focusManagerSymbol]: focusManager,
|
|
93
97
|
[roleSymbol]: 'presentation',
|
|
94
98
|
'aria-describedby': ariaDescribedBy,
|
|
95
|
-
minValue: props.minValue,
|
|
96
|
-
maxValue: props.maxValue,
|
|
97
99
|
placeholderValue: props.placeholderValue,
|
|
98
100
|
hideTimeZone: props.hideTimeZone,
|
|
99
101
|
hourCycle: props.hourCycle,
|
|
@@ -102,7 +104,7 @@ export function useDateRangePicker<T extends DateValue>(props: AriaDateRangePick
|
|
|
102
104
|
isDisabled: props.isDisabled,
|
|
103
105
|
isReadOnly: props.isReadOnly,
|
|
104
106
|
isRequired: props.isRequired,
|
|
105
|
-
|
|
107
|
+
validationBehavior: props.validationBehavior
|
|
106
108
|
};
|
|
107
109
|
|
|
108
110
|
let domProps = filterDOMProps(props);
|
|
@@ -115,6 +117,9 @@ export function useDateRangePicker<T extends DateValue>(props: AriaDateRangePick
|
|
|
115
117
|
onFocusWithinChange: props.onFocusChange
|
|
116
118
|
});
|
|
117
119
|
|
|
120
|
+
let startFieldValidation = useRef(DEFAULT_VALIDATION_RESULT);
|
|
121
|
+
let endFieldValidation = useRef(DEFAULT_VALIDATION_RESULT);
|
|
122
|
+
|
|
118
123
|
return {
|
|
119
124
|
groupProps: mergeProps(domProps, groupProps, fieldProps, descProps, focusWithinProps, {
|
|
120
125
|
role: 'group' as const,
|
|
@@ -153,6 +158,7 @@ export function useDateRangePicker<T extends DateValue>(props: AriaDateRangePick
|
|
|
153
158
|
'aria-labelledby': `${buttonId} ${labelledBy}`,
|
|
154
159
|
'aria-describedby': ariaDescribedBy,
|
|
155
160
|
'aria-expanded': state.isOpen,
|
|
161
|
+
isDisabled: props.isDisabled || props.isReadOnly,
|
|
156
162
|
onPress: () => state.setOpen(true)
|
|
157
163
|
},
|
|
158
164
|
dialogProps: {
|
|
@@ -165,14 +171,34 @@ export function useDateRangePicker<T extends DateValue>(props: AriaDateRangePick
|
|
|
165
171
|
value: state.value?.start,
|
|
166
172
|
onChange: start => state.setDateTime('start', start),
|
|
167
173
|
autoFocus: props.autoFocus,
|
|
168
|
-
name: props.startName
|
|
174
|
+
name: props.startName,
|
|
175
|
+
[privateValidationStateProp]: {
|
|
176
|
+
realtimeValidation: state.realtimeValidation,
|
|
177
|
+
displayValidation: state.displayValidation,
|
|
178
|
+
updateValidation(e) {
|
|
179
|
+
startFieldValidation.current = e;
|
|
180
|
+
state.updateValidation(mergeValidation(e, endFieldValidation.current));
|
|
181
|
+
},
|
|
182
|
+
resetValidation: state.resetValidation,
|
|
183
|
+
commitValidation: state.commitValidation
|
|
184
|
+
}
|
|
169
185
|
},
|
|
170
186
|
endFieldProps: {
|
|
171
187
|
...endFieldProps,
|
|
172
188
|
...commonFieldProps,
|
|
173
189
|
value: state.value?.end,
|
|
174
190
|
onChange: end => state.setDateTime('end', end),
|
|
175
|
-
name: props.endName
|
|
191
|
+
name: props.endName,
|
|
192
|
+
[privateValidationStateProp]: {
|
|
193
|
+
realtimeValidation: state.realtimeValidation,
|
|
194
|
+
displayValidation: state.displayValidation,
|
|
195
|
+
updateValidation(e) {
|
|
196
|
+
endFieldValidation.current = e;
|
|
197
|
+
state.updateValidation(mergeValidation(startFieldValidation.current, e));
|
|
198
|
+
},
|
|
199
|
+
resetValidation: state.resetValidation,
|
|
200
|
+
commitValidation: state.commitValidation
|
|
201
|
+
}
|
|
176
202
|
},
|
|
177
203
|
descriptionProps,
|
|
178
204
|
errorMessageProps,
|
|
@@ -188,7 +214,10 @@ export function useDateRangePicker<T extends DateValue>(props: AriaDateRangePick
|
|
|
188
214
|
allowsNonContiguousRanges: props.allowsNonContiguousRanges,
|
|
189
215
|
defaultFocusedValue: state.dateRange ? undefined : props.placeholderValue,
|
|
190
216
|
isInvalid: state.isInvalid,
|
|
191
|
-
errorMessage: props.errorMessage
|
|
192
|
-
}
|
|
217
|
+
errorMessage: typeof props.errorMessage === 'function' ? props.errorMessage(state.displayValidation) : (props.errorMessage || state.displayValidation.validationErrors.join(' '))
|
|
218
|
+
},
|
|
219
|
+
isInvalid,
|
|
220
|
+
validationErrors,
|
|
221
|
+
validationDetails
|
|
193
222
|
};
|
|
194
223
|
}
|
package/src/useDisplayNames.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// @ts-ignore
|
|
14
14
|
import intlMessages from '../intl/*.json';
|
|
15
15
|
import {LocalizedStringDictionary} from '@internationalized/string';
|
|
16
|
-
import {useLocale} from '@react-aria/i18n';
|
|
16
|
+
import {useLocale, useLocalizedStringDictionary} from '@react-aria/i18n';
|
|
17
17
|
import {useMemo} from 'react';
|
|
18
18
|
|
|
19
19
|
type Field = Intl.DateTimeFormatPartTypes;
|
|
@@ -24,6 +24,7 @@ interface DisplayNames {
|
|
|
24
24
|
/** @private */
|
|
25
25
|
export function useDisplayNames(): DisplayNames {
|
|
26
26
|
let {locale} = useLocale();
|
|
27
|
+
let dictionary = useLocalizedStringDictionary(intlMessages, '@react-aria/datepicker');
|
|
27
28
|
return useMemo(() => {
|
|
28
29
|
// Try to use Intl.DisplayNames if possible. It may be supported in browsers, but not support the dateTimeField
|
|
29
30
|
// type as that was only added in v2. https://github.com/tc39/intl-displaynames-v2
|
|
@@ -31,18 +32,18 @@ export function useDisplayNames(): DisplayNames {
|
|
|
31
32
|
// @ts-ignore
|
|
32
33
|
return new Intl.DisplayNames(locale, {type: 'dateTimeField'});
|
|
33
34
|
} catch (err) {
|
|
34
|
-
return new DisplayNamesPolyfill(locale);
|
|
35
|
+
return new DisplayNamesPolyfill(locale, dictionary);
|
|
35
36
|
}
|
|
36
|
-
}, [locale]);
|
|
37
|
+
}, [locale, dictionary]);
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
class DisplayNamesPolyfill implements DisplayNames {
|
|
40
41
|
private locale: string;
|
|
41
42
|
private dictionary: LocalizedStringDictionary<Field, string>;
|
|
42
43
|
|
|
43
|
-
constructor(locale: string) {
|
|
44
|
+
constructor(locale: string, dictionary: LocalizedStringDictionary<Field, string>) {
|
|
44
45
|
this.locale = locale;
|
|
45
|
-
this.dictionary =
|
|
46
|
+
this.dictionary = dictionary;
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
of(field: Field): string {
|