@react-aria/datepicker 3.0.0-nightly.3134 → 3.0.0-nightly.3156

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-aria/datepicker",
3
- "version": "3.0.0-nightly.3134+6fa5706ef",
3
+ "version": "3.0.0-nightly.3156+2c25922e2",
4
4
  "description": "Spectrum UI components in React",
5
5
  "license": "Apache-2.0",
6
6
  "main": "dist/main.js",
@@ -18,19 +18,20 @@
18
18
  },
19
19
  "dependencies": {
20
20
  "@babel/runtime": "^7.6.2",
21
- "@internationalized/message": "3.0.6-nightly.3134+6fa5706ef",
22
- "@internationalized/number": "3.0.6-nightly.3134+6fa5706ef",
23
- "@react-aria/focus": "3.0.0-nightly.1438+6fa5706ef",
24
- "@react-aria/i18n": "3.0.0-nightly.1438+6fa5706ef",
25
- "@react-aria/interactions": "3.0.0-nightly.1438+6fa5706ef",
26
- "@react-aria/label": "3.0.0-nightly.1438+6fa5706ef",
27
- "@react-aria/spinbutton": "3.0.0-nightly.1438+6fa5706ef",
28
- "@react-aria/utils": "3.0.0-nightly.1438+6fa5706ef",
29
- "@react-stately/datepicker": "3.0.0-nightly.3134+6fa5706ef",
30
- "@react-types/button": "3.4.4-nightly.3134+6fa5706ef",
31
- "@react-types/datepicker": "3.0.0-nightly.3134+6fa5706ef",
32
- "@react-types/dialog": "3.3.4-nightly.3134+6fa5706ef",
33
- "@react-types/shared": "3.0.0-nightly.1438+6fa5706ef"
21
+ "@internationalized/message": "3.0.6-nightly.3156+2c25922e2",
22
+ "@internationalized/number": "3.0.6-nightly.3156+2c25922e2",
23
+ "@react-aria/focus": "3.0.0-nightly.1460+2c25922e2",
24
+ "@react-aria/i18n": "3.0.0-nightly.1460+2c25922e2",
25
+ "@react-aria/interactions": "3.0.0-nightly.1460+2c25922e2",
26
+ "@react-aria/label": "3.0.0-nightly.1460+2c25922e2",
27
+ "@react-aria/spinbutton": "3.0.0-nightly.1460+2c25922e2",
28
+ "@react-aria/utils": "3.0.0-nightly.1460+2c25922e2",
29
+ "@react-stately/datepicker": "3.0.0-nightly.3156+2c25922e2",
30
+ "@react-types/button": "3.4.4-nightly.3156+2c25922e2",
31
+ "@react-types/calendar": "3.0.0-nightly.3156+2c25922e2",
32
+ "@react-types/datepicker": "3.0.0-nightly.3156+2c25922e2",
33
+ "@react-types/dialog": "3.3.4-nightly.3156+2c25922e2",
34
+ "@react-types/shared": "3.0.0-nightly.1460+2c25922e2"
34
35
  },
35
36
  "peerDependencies": {
36
37
  "react": "^16.8.0 || ^17.0.0-rc.1",
@@ -39,5 +40,5 @@
39
40
  "publishConfig": {
40
41
  "access": "public"
41
42
  },
42
- "gitHead": "6fa5706efb7bb7d8c322b8f756c451378ca962ce"
43
+ "gitHead": "2c25922e23ec51bd487b7838adb48a60c558dc36"
43
44
  }
package/src/index.ts CHANGED
@@ -10,8 +10,8 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- export * from './useDatePicker';
14
- export * from './useDateSegment';
15
- export * from './useDateField';
16
- export * from './useDateRangePicker';
13
+ export {useDatePicker} from './useDatePicker';
14
+ export {useDateSegment} from './useDateSegment';
15
+ export {useDateField, useTimeField} from './useDateField';
16
+ export {useDateRangePicker} from './useDateRangePicker';
17
17
  export * from './useDisplayNames';
@@ -10,10 +10,11 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import {AriaDatePickerProps, DateValue} from '@react-types/datepicker';
14
- import {createFocusManager} from '@react-aria/focus';
13
+ import {AriaDatePickerProps, AriaTimeFieldProps, DateValue, TimeValue} from '@react-types/datepicker';
14
+ import {createFocusManager, FocusManager} from '@react-aria/focus';
15
15
  import {DatePickerFieldState} from '@react-stately/datepicker';
16
- import {HTMLAttributes, LabelHTMLAttributes, RefObject} from 'react';
16
+ import {focusManagerSymbol} from './useDateRangePicker';
17
+ import {HTMLAttributes, RefObject, useEffect, useMemo, useRef} from 'react';
17
18
  import {mergeProps, useDescription} from '@react-aria/utils';
18
19
  import {useDateFormatter} from '@react-aria/i18n';
19
20
  import {useDatePickerGroup} from './useDatePickerGroup';
@@ -24,7 +25,9 @@ import {useFocusWithin} from '@react-aria/interactions';
24
25
  interface DateFieldProps<T extends DateValue> extends Omit<AriaDatePickerProps<T>, 'value' | 'defaultValue' | 'onChange' | 'minValue' | 'maxValue' | 'placeholderValue'> {}
25
26
 
26
27
  interface DateFieldAria {
27
- labelProps: LabelHTMLAttributes<HTMLLabelElement>,
28
+ /** Props for the field's visible label element, if any. */
29
+ labelProps: HTMLAttributes<HTMLElement>,
30
+ /** Props for the field grouping element. */
28
31
  fieldProps: HTMLAttributes<HTMLElement>,
29
32
  /** Props for the description element, if any. */
30
33
  descriptionProps: HTMLAttributes<HTMLElement>,
@@ -32,8 +35,20 @@ interface DateFieldAria {
32
35
  errorMessageProps: HTMLAttributes<HTMLElement>
33
36
  }
34
37
 
35
- export const labelIds = new WeakMap<DatePickerFieldState, {ariaLabelledBy: string, ariaDescribedBy: string}>();
38
+ // Data that is passed between useDateField and useDateSegment.
39
+ interface HookData {
40
+ ariaLabelledBy: string,
41
+ ariaDescribedBy: string,
42
+ focusManager: FocusManager
43
+ }
44
+
45
+ export const hookData = new WeakMap<DatePickerFieldState, HookData>();
36
46
 
47
+ /**
48
+ * Provides the behavior and accessibility implementation for a date field component.
49
+ * A date field allows users to enter and edit date and time values using a keyboard.
50
+ * Each part of a date value is displayed in an individually editable segment.
51
+ */
37
52
  export function useDateField<T extends DateValue>(props: DateFieldProps<T>, state: DatePickerFieldState, ref: RefObject<HTMLElement>): DateFieldAria {
38
53
  let {labelProps, fieldProps, descriptionProps, errorMessageProps} = useField({
39
54
  ...props,
@@ -53,17 +68,28 @@ export function useDateField<T extends DateValue>(props: DateFieldProps<T>, stat
53
68
 
54
69
  let segmentLabelledBy = fieldProps['aria-labelledby'] || fieldProps.id;
55
70
  let describedBy = [descProps['aria-describedby'], fieldProps['aria-describedby']].filter(Boolean).join(' ') || undefined;
71
+ let propsFocusManager = props[focusManagerSymbol];
72
+ let focusManager = useMemo(() => propsFocusManager || createFocusManager(ref), [propsFocusManager, ref]);
56
73
 
57
- labelIds.set(state, {
74
+ hookData.set(state, {
58
75
  ariaLabelledBy: segmentLabelledBy,
59
- ariaDescribedBy: describedBy
76
+ ariaDescribedBy: describedBy,
77
+ focusManager
60
78
  });
61
79
 
80
+ let autoFocusRef = useRef(props.autoFocus);
81
+
82
+ useEffect(() => {
83
+ if (autoFocusRef.current) {
84
+ focusManager.focusFirst();
85
+ }
86
+ autoFocusRef.current = false;
87
+ }, [focusManager]);
88
+
62
89
  return {
63
90
  labelProps: {
64
91
  ...labelProps,
65
92
  onClick: () => {
66
- let focusManager = createFocusManager(ref);
67
93
  focusManager.focusFirst();
68
94
  }
69
95
  },
@@ -76,3 +102,12 @@ export function useDateField<T extends DateValue>(props: DateFieldProps<T>, stat
76
102
  errorMessageProps
77
103
  };
78
104
  }
105
+
106
+ /**
107
+ * Provides the behavior and accessibility implementation for a time field component.
108
+ * A time field allows users to enter and edit time values using a keyboard.
109
+ * Each part of a time value is displayed in an individually editable segment.
110
+ */
111
+ export function useTimeField<T extends TimeValue>(props: AriaTimeFieldProps<T>, state: DatePickerFieldState, ref: RefObject<HTMLElement>): DateFieldAria {
112
+ return useDateField(props, state, ref);
113
+ }
@@ -13,9 +13,10 @@
13
13
  import {AriaButtonProps} from '@react-types/button';
14
14
  import {AriaDatePickerProps, DateValue} from '@react-types/datepicker';
15
15
  import {AriaDialogProps} from '@react-types/dialog';
16
+ import {CalendarProps} from '@react-types/calendar';
16
17
  import {createFocusManager} from '@react-aria/focus';
17
18
  import {DatePickerState} from '@react-stately/datepicker';
18
- import {HTMLAttributes, LabelHTMLAttributes, RefObject} from 'react';
19
+ import {HTMLAttributes, RefObject} from 'react';
19
20
  // @ts-ignore
20
21
  import intlMessages from '../intl/*.json';
21
22
  import {mergeProps, useDescription, useId} from '@react-aria/utils';
@@ -23,19 +24,30 @@ import {useDatePickerGroup} from './useDatePickerGroup';
23
24
  import {useField} from '@react-aria/label';
24
25
  import {useLocale, useMessageFormatter} from '@react-aria/i18n';
25
26
 
26
- interface DatePickerAria<T extends DateValue> {
27
+ interface DatePickerAria {
28
+ /** Props for the date picker's visible label element, if any. */
29
+ labelProps: HTMLAttributes<HTMLElement>,
30
+ /** Props for the grouping element containing the date field and button. */
27
31
  groupProps: HTMLAttributes<HTMLElement>,
28
- labelProps: LabelHTMLAttributes<HTMLLabelElement>,
29
- fieldProps: AriaDatePickerProps<T>,
32
+ /** Props for the date field. */
33
+ fieldProps: AriaDatePickerProps<DateValue>,
34
+ /** Props for the popover trigger button. */
35
+ buttonProps: AriaButtonProps,
30
36
  /** Props for the description element, if any. */
31
37
  descriptionProps: HTMLAttributes<HTMLElement>,
32
38
  /** Props for the error message element, if any. */
33
39
  errorMessageProps: HTMLAttributes<HTMLElement>,
34
- buttonProps: AriaButtonProps,
35
- dialogProps: AriaDialogProps
40
+ /** Props for the popover dialog. */
41
+ dialogProps: AriaDialogProps,
42
+ /** Props for the calendar within the popover dialog. */
43
+ calendarProps: CalendarProps<DateValue>
36
44
  }
37
45
 
38
- export function useDatePicker<T extends DateValue>(props: AriaDatePickerProps<T>, state: DatePickerState, ref: RefObject<HTMLElement>): DatePickerAria<T> {
46
+ /**
47
+ * Provides the behavior and accessibility implementation for a date picker component.
48
+ * A date picker combines a DateField and a Calendar popover to allow users to enter or select a date and time value.
49
+ */
50
+ export function useDatePicker<T extends DateValue>(props: AriaDatePickerProps<T>, state: DatePickerState, ref: RefObject<HTMLElement>): DatePickerAria {
39
51
  let buttonId = useId();
40
52
  let dialogId = useId();
41
53
  let formatMessage = useMessageFormatter(intlMessages);
@@ -67,7 +79,22 @@ export function useDatePicker<T extends DateValue>(props: AriaDatePickerProps<T>
67
79
  focusManager.focusFirst();
68
80
  }
69
81
  },
70
- fieldProps,
82
+ fieldProps: {
83
+ ...fieldProps,
84
+ value: state.value,
85
+ onChange: state.setValue,
86
+ minValue: props.minValue,
87
+ maxValue: props.maxValue,
88
+ placeholderValue: props.placeholderValue,
89
+ hideTimeZone: props.hideTimeZone,
90
+ hourCycle: props.hourCycle,
91
+ granularity: props.granularity,
92
+ isDisabled: props.isDisabled,
93
+ isReadOnly: props.isReadOnly,
94
+ isRequired: props.isRequired,
95
+ validationState: state.validationState,
96
+ autoFocus: props.autoFocus
97
+ },
71
98
  descriptionProps,
72
99
  errorMessageProps,
73
100
  buttonProps: {
@@ -77,11 +104,23 @@ export function useDatePicker<T extends DateValue>(props: AriaDatePickerProps<T>
77
104
  'aria-haspopup': 'dialog',
78
105
  'aria-label': formatMessage('calendar'),
79
106
  'aria-labelledby': `${labelledBy} ${buttonId}`,
80
- 'aria-describedby': ariaDescribedBy
107
+ 'aria-describedby': ariaDescribedBy,
108
+ onPress: () => state.setOpen(true)
81
109
  },
82
110
  dialogProps: {
83
111
  id: dialogId,
84
112
  'aria-labelledby': `${labelledBy} ${buttonId}`
113
+ },
114
+ calendarProps: {
115
+ autoFocus: true,
116
+ value: state.dateValue,
117
+ onChange: state.setDateValue,
118
+ minValue: props.minValue,
119
+ maxValue: props.maxValue,
120
+ isDisabled: props.isDisabled,
121
+ isReadOnly: props.isReadOnly,
122
+ isDateDisabled: props.isDateDisabled,
123
+ defaultFocusedValue: state.dateValue ? undefined : props.placeholderValue
85
124
  }
86
125
  };
87
126
  }
@@ -1,4 +1,5 @@
1
1
  import {DatePickerFieldState, DatePickerState, DateRangePickerState} from '@react-stately/datepicker';
2
+ import {getFocusableTreeWalker} from '@react-aria/focus';
2
3
  import {KeyboardEvent} from '@react-types/shared';
3
4
  import {mergeProps} from '@react-aria/utils';
4
5
  import {RefObject} from 'react';
@@ -16,15 +17,37 @@ export function useDatePickerGroup(state: DatePickerState | DateRangePickerState
16
17
 
17
18
  // Focus the first placeholder segment from the end on mouse down/touch up in the field.
18
19
  let focusLast = () => {
19
- let elements = ref.current.querySelectorAll('[tabindex="0"]');
20
- let index = elements.length - 1;
21
- while (index >= 0 && elements[index].getAttribute('aria-placeholder')) {
22
- index--;
20
+ // Try to find the segment prior to the element that was clicked on.
21
+ let target = window.event?.target as HTMLElement;
22
+ let walker = getFocusableTreeWalker(ref.current, {tabbable: true});
23
+ if (target) {
24
+ walker.currentNode = target;
25
+ target = walker.previousNode() as HTMLElement;
23
26
  }
24
- index = Math.min(index + 1, elements.length - 1);
25
- let element = elements[index] as HTMLElement;
26
- if (element) {
27
- element.focus();
27
+
28
+ // If no target found, find the last element from the end.
29
+ if (!target) {
30
+ let last: HTMLElement;
31
+ do {
32
+ last = walker.lastChild() as HTMLElement;
33
+ if (last) {
34
+ target = last;
35
+ }
36
+ } while (last);
37
+ }
38
+
39
+ // Now go backwards until we find an element that is not a placeholder.
40
+ while (target?.getAttribute('aria-placeholder')) {
41
+ let prev = walker.previousNode() as HTMLElement;
42
+ if (prev && prev.getAttribute('aria-placeholder')) {
43
+ target = prev;
44
+ } else {
45
+ break;
46
+ }
47
+ }
48
+
49
+ if (target) {
50
+ target.focus();
28
51
  }
29
52
  };
30
53
 
@@ -15,29 +15,47 @@ 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 {HTMLAttributes, LabelHTMLAttributes, RefObject} from 'react';
18
+ import {HTMLAttributes, RefObject, useMemo} from 'react';
19
19
  // @ts-ignore
20
20
  import intlMessages from '../intl/*.json';
21
21
  import {mergeProps, useDescription, useId, useLabels} from '@react-aria/utils';
22
+ import {RangeCalendarProps} from '@react-types/calendar';
22
23
  import {useDatePickerGroup} from './useDatePickerGroup';
23
24
  import {useField} from '@react-aria/label';
24
25
  import {useFocusWithin} from '@react-aria/interactions';
25
26
  import {useLocale, useMessageFormatter} from '@react-aria/i18n';
26
27
 
27
- interface DateRangePickerAria<T extends DateValue> {
28
- labelProps: LabelHTMLAttributes<HTMLLabelElement>,
28
+ interface DateRangePickerAria {
29
+ /** Props for the date range picker's visible label element, if any. */
30
+ labelProps: HTMLAttributes<HTMLElement>,
31
+ /** Props for the grouping element containing the date fields and button. */
29
32
  groupProps: HTMLAttributes<HTMLElement>,
30
- startFieldProps: AriaDatePickerProps<T>,
31
- endFieldProps: AriaDatePickerProps<T>,
33
+ /** Props for the start date field. */
34
+ startFieldProps: AriaDatePickerProps<DateValue>,
35
+ /** Props for the end date field. */
36
+ endFieldProps: AriaDatePickerProps<DateValue>,
37
+ /** Props for the popover trigger button. */
38
+ buttonProps: AriaButtonProps,
32
39
  /** Props for the description element, if any. */
33
40
  descriptionProps: HTMLAttributes<HTMLElement>,
34
41
  /** Props for the error message element, if any. */
35
42
  errorMessageProps: HTMLAttributes<HTMLElement>,
36
- buttonProps: AriaButtonProps,
37
- dialogProps: AriaDialogProps
43
+ /** Props for the popover dialog. */
44
+ dialogProps: AriaDialogProps,
45
+ /** Props for the range calendar within the popover dialog. */
46
+ calendarProps: RangeCalendarProps<DateValue>
38
47
  }
39
48
 
40
- export function useDateRangePicker<T extends DateValue>(props: AriaDateRangePickerProps<T>, state: DateRangePickerState, ref: RefObject<HTMLElement>): DateRangePickerAria<T> {
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
+ /**
54
+ * Provides the behavior and accessibility implementation for a date picker component.
55
+ * A date range picker combines two DateFields and a RangeCalendar popover to allow
56
+ * users to enter or select a date and time range.
57
+ */
58
+ export function useDateRangePicker<T extends DateValue>(props: AriaDateRangePickerProps<T>, state: DateRangePickerState, ref: RefObject<HTMLElement>): DateRangePickerAria {
41
59
  let formatMessage = useMessageFormatter(intlMessages);
42
60
  let {labelProps, fieldProps, descriptionProps, errorMessageProps} = useField({
43
61
  ...props,
@@ -71,6 +89,20 @@ export function useDateRangePicker<T extends DateValue>(props: AriaDateRangePick
71
89
  });
72
90
 
73
91
  let ariaDescribedBy = [descProps['aria-describedby'], fieldProps['aria-describedby']].filter(Boolean).join(' ') || undefined;
92
+ let focusManager = useMemo(() => createFocusManager(ref), [ref]);
93
+ let commonFieldProps = {
94
+ [focusManagerSymbol]: focusManager,
95
+ minValue: props.minValue,
96
+ maxValue: props.maxValue,
97
+ placeholderValue: props.placeholderValue,
98
+ hideTimeZone: props.hideTimeZone,
99
+ hourCycle: props.hourCycle,
100
+ granularity: props.granularity,
101
+ isDisabled: props.isDisabled,
102
+ isReadOnly: props.isReadOnly,
103
+ isRequired: props.isRequired,
104
+ validationState: state.validationState
105
+ };
74
106
 
75
107
  return {
76
108
  groupProps: mergeProps(groupProps, fieldProps, descProps, focusWithinProps, {
@@ -81,7 +113,6 @@ export function useDateRangePicker<T extends DateValue>(props: AriaDateRangePick
81
113
  labelProps: {
82
114
  ...labelProps,
83
115
  onClick: () => {
84
- let focusManager = createFocusManager(ref);
85
116
  focusManager.focusFirst();
86
117
  }
87
118
  },
@@ -92,7 +123,8 @@ export function useDateRangePicker<T extends DateValue>(props: AriaDateRangePick
92
123
  'aria-haspopup': 'dialog',
93
124
  'aria-label': formatMessage('calendar'),
94
125
  'aria-labelledby': `${labelledBy} ${buttonId}`,
95
- 'aria-describedby': ariaDescribedBy
126
+ 'aria-describedby': ariaDescribedBy,
127
+ onPress: () => state.setOpen(true)
96
128
  },
97
129
  dialogProps: {
98
130
  id: dialogId,
@@ -100,13 +132,31 @@ export function useDateRangePicker<T extends DateValue>(props: AriaDateRangePick
100
132
  },
101
133
  startFieldProps: {
102
134
  ...startFieldProps,
103
- 'aria-describedby': fieldProps['aria-describedby']
135
+ ...commonFieldProps,
136
+ 'aria-describedby': fieldProps['aria-describedby'],
137
+ value: state.value?.start,
138
+ onChange: start => state.setDateTime('start', start),
139
+ autoFocus: props.autoFocus
104
140
  },
105
141
  endFieldProps: {
106
142
  ...endFieldProps,
107
- 'aria-describedby': fieldProps['aria-describedby']
143
+ ...commonFieldProps,
144
+ 'aria-describedby': fieldProps['aria-describedby'],
145
+ value: state.value?.end,
146
+ onChange: end => state.setDateTime('end', end)
108
147
  },
109
148
  descriptionProps,
110
- errorMessageProps
149
+ errorMessageProps,
150
+ calendarProps: {
151
+ autoFocus: true,
152
+ value: state.dateRange,
153
+ onChange: state.setDateRange,
154
+ minValue: props.minValue,
155
+ maxValue: props.maxValue,
156
+ isDisabled: props.isDisabled,
157
+ isReadOnly: props.isReadOnly,
158
+ isDateDisabled: props.isDateDisabled,
159
+ defaultFocusedValue: state.dateRange ? undefined : props.placeholderValue
160
+ }
111
161
  };
112
162
  }
@@ -11,27 +11,30 @@
11
11
  */
12
12
 
13
13
  import {DatePickerFieldState, DateSegment} from '@react-stately/datepicker';
14
- import {DatePickerProps, DateValue} from '@react-types/datepicker';
15
- import {DOMProps} from '@react-types/shared';
16
14
  import {getScrollParent, isIOS, isMac, mergeProps, scrollIntoView, useEvent, useId} from '@react-aria/utils';
17
- import {labelIds} from './useDateField';
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
- export function useDateSegment<T extends DateValue>(props: DatePickerProps<T> & DOMProps, segment: DateSegment, state: DatePickerFieldState, ref: RefObject<HTMLElement>): DateSegmentAria {
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: DatePickerFieldState, ref: RefObject<HTMLElement>): DateSegmentAria {
31
34
  let enteredKeys = useRef('');
32
35
  let {locale, direction} = useLocale();
33
36
  let displayNames = useDisplayNames();
34
- let focusManager = useFocusManager();
37
+ let {ariaLabelledBy, ariaDescribedBy, focusManager} = hookData.get(state);
35
38
 
36
39
  let textValue = segment.text;
37
40
  let options = useMemo(() => state.dateFormatter.resolvedOptions(), [state.dateFormatter]);
@@ -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: props.isDisabled,
58
- isReadOnly: props.isReadOnly || !segment.isEditable,
59
- isRequired: props.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) && !props.isReadOnly && !segment.isPlaceholder) {
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 && !props.isReadOnly) {
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 (props.isDisabled || props.isReadOnly) {
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) && !props.isReadOnly) {
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,29 @@ export function useDateSegment<T extends DateValue>(props: DatePickerProps<T> &
337
338
  ariaDescribedBy = undefined;
338
339
  }
339
340
 
340
- let id = useId(props.id);
341
- let isEditable = !props.isDisabled && !props.isReadOnly && segment.isEditable;
341
+ let id = useId();
342
+ let isEditable = !state.isDisabled && !state.isReadOnly && segment.isEditable;
343
+
344
+ // Literal segments should not be visible to screen readers. We don't really need any of the above,
345
+ // but the rules of hooks mean hooks cannot be conditional so we have to put this condition here.
346
+ if (segment.type === 'literal') {
347
+ return {
348
+ segmentProps: {
349
+ 'aria-hidden': true
350
+ }
351
+ };
352
+ }
353
+
342
354
  return {
343
355
  segmentProps: mergeProps(spinButtonProps, pressProps, {
344
356
  id,
345
357
  ...touchPropOverrides,
346
- 'aria-controls': props['aria-controls'],
347
- // 'aria-haspopup': props['aria-haspopup'], // deprecated in ARIA 1.2
348
358
  'aria-invalid': state.validationState === 'invalid' ? 'true' : undefined,
349
- 'aria-label': segment.type !== 'literal' ? displayNames.of(segment.type) : undefined,
359
+ 'aria-label': displayNames.of(segment.type),
350
360
  'aria-labelledby': `${ariaLabelledBy} ${id}`,
351
361
  'aria-describedby': ariaDescribedBy,
352
362
  'aria-placeholder': segment.isPlaceholder ? segment.text : undefined,
353
- 'aria-readonly': props.isReadOnly || !segment.isEditable ? 'true' : undefined,
363
+ 'aria-readonly': state.isReadOnly || !segment.isEditable ? 'true' : undefined,
354
364
  contentEditable: isEditable,
355
365
  suppressContentEditableWarning: isEditable,
356
366
  spellCheck: isEditable ? 'false' : undefined,
@@ -358,10 +368,13 @@ export function useDateSegment<T extends DateValue>(props: DatePickerProps<T> &
358
368
  autoCorrect: isEditable ? 'off' : undefined,
359
369
  // Capitalization was changed in React 17...
360
370
  [parseInt(React.version, 10) >= 17 ? 'enterKeyHint' : 'enterkeyhint']: isEditable ? 'next' : undefined,
361
- inputMode: props.isDisabled || segment.type === 'dayPeriod' || !isEditable ? undefined : 'numeric',
362
- tabIndex: props.isDisabled ? undefined : 0,
371
+ inputMode: state.isDisabled || segment.type === 'dayPeriod' || !isEditable ? undefined : 'numeric',
372
+ tabIndex: state.isDisabled ? undefined : 0,
363
373
  onKeyDown,
364
- onFocus
374
+ onFocus,
375
+ style: {
376
+ caretColor: 'transparent'
377
+ }
365
378
  })
366
379
  };
367
380
  }