@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.
@@ -1 +1 @@
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/useDatePicker.ts","packages/@react-aria/datepicker/src/packages/@react-aria/datepicker/src/useDateRangePicker.ts","packages/@react-aria/datepicker/src/packages/@react-aria/datepicker/src/useDateField.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/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 * from './useDisplayNames';\n"],"names":[],"version":3,"file":"types.d.ts.map"}
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-nightly.3175+91ca94fe5",
3
+ "version": "3.0.0-rc.1",
4
4
  "description": "Spectrum UI components in React",
5
5
  "license": "Apache-2.0",
6
6
  "main": "dist/main.js",
@@ -18,27 +18,27 @@
18
18
  },
19
19
  "dependencies": {
20
20
  "@babel/runtime": "^7.6.2",
21
- "@internationalized/message": "3.0.6-nightly.3175+91ca94fe5",
22
- "@internationalized/number": "3.0.6-nightly.3175+91ca94fe5",
23
- "@react-aria/focus": "3.0.0-nightly.1476+91ca94fe5",
24
- "@react-aria/i18n": "3.0.0-nightly.1476+91ca94fe5",
25
- "@react-aria/interactions": "3.0.0-nightly.1476+91ca94fe5",
26
- "@react-aria/label": "3.0.0-nightly.1476+91ca94fe5",
27
- "@react-aria/spinbutton": "3.0.0-nightly.1476+91ca94fe5",
28
- "@react-aria/utils": "3.0.0-nightly.1476+91ca94fe5",
29
- "@react-stately/datepicker": "3.0.0-nightly.3175+91ca94fe5",
30
- "@react-types/button": "3.4.5-nightly.3175+91ca94fe5",
31
- "@react-types/calendar": "3.0.0-nightly.3175+91ca94fe5",
32
- "@react-types/datepicker": "3.0.0-nightly.3175+91ca94fe5",
33
- "@react-types/dialog": "3.3.5-nightly.3175+91ca94fe5",
34
- "@react-types/shared": "3.0.0-nightly.1476+91ca94fe5"
21
+ "@internationalized/message": "^3.0.7",
22
+ "@internationalized/number": "^3.1.1",
23
+ "@react-aria/focus": "^3.6.0",
24
+ "@react-aria/i18n": "^3.4.0",
25
+ "@react-aria/interactions": "^3.9.0",
26
+ "@react-aria/label": "^3.3.0",
27
+ "@react-aria/spinbutton": "^3.1.0",
28
+ "@react-aria/utils": "^3.13.0",
29
+ "@react-stately/datepicker": "3.0.0-rc.1",
30
+ "@react-types/button": "^3.5.0",
31
+ "@react-types/calendar": "3.0.0-rc.1",
32
+ "@react-types/datepicker": "3.0.0-rc.1",
33
+ "@react-types/dialog": "^3.4.0",
34
+ "@react-types/shared": "^3.13.0"
35
35
  },
36
36
  "peerDependencies": {
37
- "react": "^16.8.0 || ^17.0.0-rc.1",
38
- "react-dom": "^16.8.0 || ^17.0.0-rc.1"
37
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0",
38
+ "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
39
39
  },
40
40
  "publishConfig": {
41
41
  "access": "public"
42
42
  },
43
- "gitHead": "91ca94fe52840b7a32b961ec08208f5fbdf65697"
43
+ "gitHead": "8f921ec5094e7c2b3c301bcb6133372e35a2052b"
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 * from './useDisplayNames';
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';
@@ -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 {DatePickerFieldState} from '@react-stately/datepicker';
16
- import {focusManagerSymbol} from './useDateRangePicker';
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
- import {mergeProps, useDescription} from '@react-aria/utils';
19
- import {useDateFormatter} from '@react-aria/i18n';
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 DateFieldProps<T extends DateValue> extends Omit<AriaDatePickerProps<T>, 'value' | 'defaultValue' | 'onChange' | 'minValue' | 'maxValue' | 'placeholderValue'> {}
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,48 +38,78 @@ 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<DatePickerFieldState, HookData>();
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: DateFieldProps<T>, state: DatePickerFieldState, ref: RefObject<HTMLElement>): DateFieldAria {
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'
56
63
  });
57
64
 
58
- let groupProps = useDatePickerGroup(state, ref);
59
-
60
65
  let {focusWithinProps} = useFocusWithin({
61
66
  onBlurWithin() {
62
67
  state.confirmPlaceholder();
63
68
  }
64
69
  });
65
70
 
66
- let formatter = useDateFormatter(state.getFormatOptions({month: 'long'}));
67
- let descProps = useDescription(state.value ? formatter.format(state.dateValue) : null);
71
+ let formatMessage = useMessageFormatter(intlMessages);
72
+ let message = state.maxGranularity === 'hour' ? 'selectedTimeDescription' : 'selectedDateDescription';
73
+ let field = state.maxGranularity === 'hour' ? 'time' : 'date';
74
+ let description = state.value ? formatMessage(message, {[field]: state.formatValue({month: 'long'})}) : '';
75
+ let descProps = useDescription(description);
68
76
 
69
- let segmentLabelledBy = fieldProps['aria-labelledby'] || fieldProps.id;
70
- let describedBy = [descProps['aria-describedby'], fieldProps['aria-describedby']].filter(Boolean).join(' ') || undefined;
77
+ // If within a date picker or date range picker, the date field will have role="presentation" and an aria-describedby
78
+ // will be passed in that references the value (e.g. entire range). Otherwise, add the field's value description.
79
+ let describedBy = props[roleSymbol] === 'presentation'
80
+ ? fieldProps['aria-describedby']
81
+ : [descProps['aria-describedby'], fieldProps['aria-describedby']].filter(Boolean).join(' ') || undefined;
71
82
  let propsFocusManager = props[focusManagerSymbol];
72
83
  let focusManager = useMemo(() => propsFocusManager || createFocusManager(ref), [propsFocusManager, ref]);
84
+ let groupProps = useDatePickerGroup(state, ref, props[roleSymbol] === 'presentation');
73
85
 
86
+ // Pass labels and other information to segments.
74
87
  hookData.set(state, {
75
- ariaLabelledBy: segmentLabelledBy,
88
+ ariaLabel: props['aria-label'],
89
+ ariaLabelledBy: [props['aria-labelledby'], labelProps.id].filter(Boolean).join(' ') || undefined,
76
90
  ariaDescribedBy: describedBy,
77
91
  focusManager
78
92
  });
79
93
 
80
94
  let autoFocusRef = useRef(props.autoFocus);
81
95
 
96
+ // When used within a date picker or date range picker, the field gets role="presentation"
97
+ // rather than role="group". Since the date picker/date range picker already has a role="group"
98
+ // with a label and description, and the segments are already labeled by this as well, this
99
+ // avoids very verbose duplicate announcements.
100
+ let fieldDOMProps: HTMLAttributes<HTMLElement>;
101
+ if (props[roleSymbol] === 'presentation') {
102
+ fieldDOMProps = {
103
+ role: 'presentation'
104
+ };
105
+ } else {
106
+ fieldDOMProps = mergeProps(fieldProps, {
107
+ role: 'group',
108
+ 'aria-disabled': props.isDisabled || undefined,
109
+ 'aria-describedby': describedBy
110
+ });
111
+ }
112
+
82
113
  useEffect(() => {
83
114
  if (autoFocusRef.current) {
84
115
  focusManager.focusFirst();
@@ -86,6 +117,7 @@ export function useDateField<T extends DateValue>(props: DateFieldProps<T>, stat
86
117
  autoFocusRef.current = false;
87
118
  }, [focusManager]);
88
119
 
120
+ let domProps = filterDOMProps(props);
89
121
  return {
90
122
  labelProps: {
91
123
  ...labelProps,
@@ -93,11 +125,7 @@ export function useDateField<T extends DateValue>(props: DateFieldProps<T>, stat
93
125
  focusManager.focusFirst();
94
126
  }
95
127
  },
96
- fieldProps: mergeProps(fieldProps, descProps, groupProps, focusWithinProps, {
97
- role: 'group',
98
- 'aria-disabled': props.isDisabled || undefined,
99
- 'aria-describedby': describedBy
100
- }),
128
+ fieldProps: mergeProps(domProps, fieldDOMProps, groupProps, focusWithinProps),
101
129
  descriptionProps,
102
130
  errorMessageProps
103
131
  };
@@ -108,6 +136,6 @@ export function useDateField<T extends DateValue>(props: DateFieldProps<T>, stat
108
136
  * A time field allows users to enter and edit time values using a keyboard.
109
137
  * Each part of a time value is displayed in an individually editable segment.
110
138
  */
111
- export function useTimeField<T extends TimeValue>(props: AriaTimeFieldProps<T>, state: DatePickerFieldState, ref: RefObject<HTMLElement>): DateFieldAria {
139
+ export function useTimeField<T extends TimeValue>(props: AriaTimeFieldProps<T>, state: DateFieldState, ref: RefObject<HTMLElement>): DateFieldAria {
112
140
  return useDateField(props, state, ref);
113
141
  }
@@ -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 {HTMLAttributes, RefObject} from 'react';
19
+ import {filterDOMProps, mergeProps, useDescription, useId} from '@react-aria/utils';
20
+ import {HTMLAttributes, RefObject, useMemo} from 'react';
20
21
  // @ts-ignore
21
22
  import intlMessages from '../intl/*.json';
22
- import {mergeProps, useDescription, useId} from '@react-aria/utils';
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,15 @@ 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 descProps = useDescription(state.formatValue(locale, {month: 'long'}));
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);
71
+ let focusManager = useMemo(() => createFocusManager(ref), [ref]);
67
72
 
68
73
  return {
69
- groupProps: mergeProps(groupProps, descProps, {
74
+ groupProps: mergeProps(domProps, groupProps, fieldProps, descProps, {
70
75
  role: 'group',
71
76
  'aria-disabled': props.isDisabled || null,
72
77
  'aria-labelledby': labelledBy,
@@ -75,12 +80,13 @@ export function useDatePicker<T extends DateValue>(props: AriaDatePickerProps<T>
75
80
  labelProps: {
76
81
  ...labelProps,
77
82
  onClick: () => {
78
- let focusManager = createFocusManager(ref);
79
83
  focusManager.focusFirst();
80
84
  }
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,
@@ -100,7 +106,6 @@ export function useDatePicker<T extends DateValue>(props: AriaDatePickerProps<T>
100
106
  buttonProps: {
101
107
  ...descProps,
102
108
  id: buttonId,
103
- excludeFromTabOrder: true,
104
109
  'aria-haspopup': 'dialog',
105
110
  'aria-label': formatMessage('calendar'),
106
111
  'aria-labelledby': `${labelledBy} ${buttonId}`,
@@ -120,7 +125,9 @@ export function useDatePicker<T extends DateValue>(props: AriaDatePickerProps<T>
120
125
  isDisabled: props.isDisabled,
121
126
  isReadOnly: props.isReadOnly,
122
127
  isDateUnavailable: props.isDateUnavailable,
123
- defaultFocusedValue: state.dateValue ? undefined : props.placeholderValue
128
+ defaultFocusedValue: state.dateValue ? undefined : props.placeholderValue,
129
+ validationState: state.validationState,
130
+ errorMessage: props.errorMessage
124
131
  }
125
132
  };
126
133
  }
@@ -1,18 +1,47 @@
1
- import {DatePickerFieldState, DatePickerState, DateRangePickerState} from '@react-stately/datepicker';
2
- import {getFocusableTreeWalker} from '@react-aria/focus';
1
+ import {createFocusManager, getFocusableTreeWalker} from '@react-aria/focus';
2
+ import {DateFieldState, DatePickerState, DateRangePickerState} from '@react-stately/datepicker';
3
3
  import {KeyboardEvent} from '@react-types/shared';
4
4
  import {mergeProps} from '@react-aria/utils';
5
- import {RefObject} from 'react';
5
+ import {RefObject, useMemo} from 'react';
6
+ import {useLocale} from '@react-aria/i18n';
6
7
  import {usePress} from '@react-aria/interactions';
7
8
 
8
- export function useDatePickerGroup(state: DatePickerState | DateRangePickerState | DatePickerFieldState, ref: RefObject<HTMLElement>) {
9
+ export function useDatePickerGroup(state: DatePickerState | DateRangePickerState | DateFieldState, ref: RefObject<HTMLElement>, disableArrowNavigation?: boolean) {
10
+ let {direction} = useLocale();
11
+ let focusManager = useMemo(() => createFocusManager(ref), [ref]);
12
+
9
13
  // Open the popover on alt + arrow down
10
14
  let onKeyDown = (e: KeyboardEvent) => {
11
- if (e.altKey && e.key === 'ArrowDown' && 'setOpen' in state) {
15
+ if (e.altKey && (e.key === 'ArrowDown' || e.key === 'ArrowUp') && 'setOpen' in state) {
12
16
  e.preventDefault();
13
17
  e.stopPropagation();
14
18
  state.setOpen(true);
15
19
  }
20
+
21
+ if (disableArrowNavigation) {
22
+ return;
23
+ }
24
+
25
+ switch (e.key) {
26
+ case 'ArrowLeft':
27
+ e.preventDefault();
28
+ e.stopPropagation();
29
+ if (direction === 'rtl') {
30
+ focusManager.focusNext();
31
+ } else {
32
+ focusManager.focusPrevious();
33
+ }
34
+ break;
35
+ case 'ArrowRight':
36
+ e.preventDefault();
37
+ e.stopPropagation();
38
+ if (direction === 'rtl') {
39
+ focusManager.focusPrevious();
40
+ } else {
41
+ focusManager.focusNext();
42
+ }
43
+ break;
44
+ }
16
45
  };
17
46
 
18
47
  // Focus the first placeholder segment from the end on mouse down/touch up in the field.
@@ -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 description = state.formatValue(locale, {month: 'long'});
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 = useLabels({
69
+ let startFieldProps = {
72
70
  'aria-label': formatMessage('startDate'),
73
71
  'aria-labelledby': labelledBy
74
- });
72
+ };
75
73
 
76
- let endFieldProps = useLabels({
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();
@@ -89,9 +87,15 @@ export function useDateRangePicker<T extends DateValue>(props: AriaDateRangePick
89
87
  });
90
88
 
91
89
  let ariaDescribedBy = [descProps['aria-describedby'], fieldProps['aria-describedby']].filter(Boolean).join(' ') || undefined;
92
- let focusManager = useMemo(() => createFocusManager(ref), [ref]);
90
+ let focusManager = useMemo(() => createFocusManager(ref, {
91
+ // Exclude the button from the focus manager.
92
+ accept: element => element.id !== buttonId
93
+ }), [ref, buttonId]);
94
+
93
95
  let commonFieldProps = {
94
96
  [focusManagerSymbol]: focusManager,
97
+ [roleSymbol]: 'presentation',
98
+ 'aria-describedby': ariaDescribedBy,
95
99
  minValue: props.minValue,
96
100
  maxValue: props.maxValue,
97
101
  placeholderValue: props.placeholderValue,
@@ -104,8 +108,10 @@ export function useDateRangePicker<T extends DateValue>(props: AriaDateRangePick
104
108
  validationState: state.validationState
105
109
  };
106
110
 
111
+ let domProps = filterDOMProps(props);
112
+
107
113
  return {
108
- groupProps: mergeProps(groupProps, fieldProps, descProps, focusWithinProps, {
114
+ groupProps: mergeProps(domProps, groupProps, fieldProps, descProps, focusWithinProps, {
109
115
  role: 'group',
110
116
  'aria-disabled': props.isDisabled || null,
111
117
  'aria-describedby': ariaDescribedBy
@@ -119,7 +125,6 @@ export function useDateRangePicker<T extends DateValue>(props: AriaDateRangePick
119
125
  buttonProps: {
120
126
  ...descProps,
121
127
  id: buttonId,
122
- excludeFromTabOrder: true,
123
128
  'aria-haspopup': 'dialog',
124
129
  'aria-label': formatMessage('calendar'),
125
130
  'aria-labelledby': `${labelledBy} ${buttonId}`,
@@ -133,7 +138,6 @@ export function useDateRangePicker<T extends DateValue>(props: AriaDateRangePick
133
138
  startFieldProps: {
134
139
  ...startFieldProps,
135
140
  ...commonFieldProps,
136
- 'aria-describedby': fieldProps['aria-describedby'],
137
141
  value: state.value?.start,
138
142
  onChange: start => state.setDateTime('start', start),
139
143
  autoFocus: props.autoFocus
@@ -141,7 +145,6 @@ export function useDateRangePicker<T extends DateValue>(props: AriaDateRangePick
141
145
  endFieldProps: {
142
146
  ...endFieldProps,
143
147
  ...commonFieldProps,
144
- 'aria-describedby': fieldProps['aria-describedby'],
145
148
  value: state.value?.end,
146
149
  onChange: end => state.setDateTime('end', end)
147
150
  },
@@ -157,7 +160,9 @@ export function useDateRangePicker<T extends DateValue>(props: AriaDateRangePick
157
160
  isReadOnly: props.isReadOnly,
158
161
  isDateUnavailable: props.isDateUnavailable,
159
162
  allowsNonContiguousRanges: props.allowsNonContiguousRanges,
160
- defaultFocusedValue: state.dateRange ? undefined : props.placeholderValue
163
+ defaultFocusedValue: state.dateRange ? undefined : props.placeholderValue,
164
+ validationState: state.validationState,
165
+ errorMessage: props.errorMessage
161
166
  }
162
167
  };
163
168
  }