@react-aria/datepicker 3.0.0-nightly.3168 → 3.0.0-nightly.3180

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.3168+2fabc1ac0",
3
+ "version": "3.0.0-nightly.3180+0bba35ae3",
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-nightly.3168+2fabc1ac0",
22
- "@internationalized/number": "3.0.6-nightly.3168+2fabc1ac0",
23
- "@react-aria/focus": "3.0.0-nightly.1469+2fabc1ac0",
24
- "@react-aria/i18n": "3.0.0-nightly.1469+2fabc1ac0",
25
- "@react-aria/interactions": "3.0.0-nightly.1469+2fabc1ac0",
26
- "@react-aria/label": "3.0.0-nightly.1469+2fabc1ac0",
27
- "@react-aria/spinbutton": "3.0.0-nightly.1469+2fabc1ac0",
28
- "@react-aria/utils": "3.0.0-nightly.1469+2fabc1ac0",
29
- "@react-stately/datepicker": "3.0.0-nightly.3168+2fabc1ac0",
30
- "@react-types/button": "3.4.5-nightly.3168+2fabc1ac0",
31
- "@react-types/calendar": "3.0.0-nightly.3168+2fabc1ac0",
32
- "@react-types/datepicker": "3.0.0-nightly.3168+2fabc1ac0",
33
- "@react-types/dialog": "3.3.5-nightly.3168+2fabc1ac0",
34
- "@react-types/shared": "3.0.0-nightly.1469+2fabc1ac0"
21
+ "@internationalized/message": "3.0.6-nightly.3180+0bba35ae3",
22
+ "@internationalized/number": "3.0.6-nightly.3180+0bba35ae3",
23
+ "@react-aria/focus": "3.0.0-nightly.1481+0bba35ae3",
24
+ "@react-aria/i18n": "3.0.0-nightly.1481+0bba35ae3",
25
+ "@react-aria/interactions": "3.0.0-nightly.1481+0bba35ae3",
26
+ "@react-aria/label": "3.0.0-nightly.1481+0bba35ae3",
27
+ "@react-aria/spinbutton": "3.0.0-nightly.1481+0bba35ae3",
28
+ "@react-aria/utils": "3.0.0-nightly.1481+0bba35ae3",
29
+ "@react-stately/datepicker": "3.0.0-nightly.3180+0bba35ae3",
30
+ "@react-types/button": "3.4.5-nightly.3180+0bba35ae3",
31
+ "@react-types/calendar": "3.0.0-nightly.3180+0bba35ae3",
32
+ "@react-types/datepicker": "3.0.0-nightly.3180+0bba35ae3",
33
+ "@react-types/dialog": "3.3.5-nightly.3180+0bba35ae3",
34
+ "@react-types/shared": "3.0.0-nightly.1481+0bba35ae3"
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": "2fabc1ac07aa6fd75e7111e571e27787d8a44314"
43
+ "gitHead": "0bba35ae36b5d220570385215860d3ca3b549656"
44
44
  }
@@ -12,11 +12,9 @@
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';
17
16
  import {HTMLAttributes, RefObject, useEffect, useMemo, useRef} from 'react';
18
17
  import {mergeProps, useDescription} from '@react-aria/utils';
19
- import {useDateFormatter} from '@react-aria/i18n';
20
18
  import {useDatePickerGroup} from './useDatePickerGroup';
21
19
  import {useField} from '@react-aria/label';
22
20
  import {useFocusWithin} from '@react-aria/interactions';
@@ -37,19 +35,25 @@ interface DateFieldAria {
37
35
 
38
36
  // Data that is passed between useDateField and useDateSegment.
39
37
  interface HookData {
38
+ ariaLabel: string,
40
39
  ariaLabelledBy: string,
41
40
  ariaDescribedBy: string,
42
41
  focusManager: FocusManager
43
42
  }
44
43
 
45
- export const hookData = new WeakMap<DatePickerFieldState, HookData>();
44
+ export const hookData = new WeakMap<DateFieldState, HookData>();
45
+
46
+ // Private props that we pass from useDatePicker/useDateRangePicker.
47
+ // Ideally we'd use a Symbol for this, but React doesn't support them: https://github.com/facebook/react/issues/7552
48
+ export const roleSymbol = '__role_' + Date.now();
49
+ export const focusManagerSymbol = '__focusManager_' + Date.now();
46
50
 
47
51
  /**
48
52
  * Provides the behavior and accessibility implementation for a date field component.
49
53
  * A date field allows users to enter and edit date and time values using a keyboard.
50
54
  * Each part of a date value is displayed in an individually editable segment.
51
55
  */
52
- export function useDateField<T extends DateValue>(props: DateFieldProps<T>, state: DatePickerFieldState, ref: RefObject<HTMLElement>): DateFieldAria {
56
+ export function useDateField<T extends DateValue>(props: DateFieldProps<T>, state: DateFieldState, ref: RefObject<HTMLElement>): DateFieldAria {
53
57
  let {labelProps, fieldProps, descriptionProps, errorMessageProps} = useField({
54
58
  ...props,
55
59
  labelElementType: 'span'
@@ -63,22 +67,43 @@ export function useDateField<T extends DateValue>(props: DateFieldProps<T>, stat
63
67
  }
64
68
  });
65
69
 
66
- let formatter = useDateFormatter(state.getFormatOptions({month: 'long'}));
67
- let descProps = useDescription(state.value ? formatter.format(state.dateValue) : null);
70
+ let descProps = useDescription(state.formatValue({month: 'long'}));
68
71
 
69
- let segmentLabelledBy = fieldProps['aria-labelledby'] || fieldProps.id;
70
- let describedBy = [descProps['aria-describedby'], fieldProps['aria-describedby']].filter(Boolean).join(' ') || undefined;
72
+ // If within a date picker or date range picker, the date field will have role="presentation" and an aria-describedby
73
+ // will be passed in that references the value (e.g. entire range). Otherwise, add the field's value description.
74
+ let describedBy = props[roleSymbol] === 'presentation'
75
+ ? fieldProps['aria-describedby']
76
+ : [descProps['aria-describedby'], fieldProps['aria-describedby']].filter(Boolean).join(' ') || undefined;
71
77
  let propsFocusManager = props[focusManagerSymbol];
72
78
  let focusManager = useMemo(() => propsFocusManager || createFocusManager(ref), [propsFocusManager, ref]);
73
79
 
80
+ // Pass labels and other information to segments.
74
81
  hookData.set(state, {
75
- ariaLabelledBy: segmentLabelledBy,
82
+ ariaLabel: props['aria-label'],
83
+ ariaLabelledBy: [props['aria-labelledby'], labelProps.id].filter(Boolean).join(' ') || undefined,
76
84
  ariaDescribedBy: describedBy,
77
85
  focusManager
78
86
  });
79
87
 
80
88
  let autoFocusRef = useRef(props.autoFocus);
81
89
 
90
+ // When used within a date picker or date range picker, the field gets role="presentation"
91
+ // rather than role="group". Since the date picker/date range picker already has a role="group"
92
+ // with a label and description, and the segments are already labeled by this as well, this
93
+ // avoids very verbose duplicate announcements.
94
+ let fieldDOMProps: HTMLAttributes<HTMLElement>;
95
+ if (props[roleSymbol] === 'presentation') {
96
+ fieldDOMProps = {
97
+ role: 'presentation'
98
+ };
99
+ } else {
100
+ fieldDOMProps = mergeProps(fieldProps, {
101
+ role: 'group',
102
+ 'aria-disabled': props.isDisabled || undefined,
103
+ 'aria-describedby': describedBy
104
+ });
105
+ }
106
+
82
107
  useEffect(() => {
83
108
  if (autoFocusRef.current) {
84
109
  focusManager.focusFirst();
@@ -93,11 +118,7 @@ export function useDateField<T extends DateValue>(props: DateFieldProps<T>, stat
93
118
  focusManager.focusFirst();
94
119
  }
95
120
  },
96
- fieldProps: mergeProps(fieldProps, descProps, groupProps, focusWithinProps, {
97
- role: 'group',
98
- 'aria-disabled': props.isDisabled || undefined,
99
- 'aria-describedby': describedBy
100
- }),
121
+ fieldProps: mergeProps(fieldDOMProps, groupProps, focusWithinProps),
101
122
  descriptionProps,
102
123
  errorMessageProps
103
124
  };
@@ -108,6 +129,6 @@ export function useDateField<T extends DateValue>(props: DateFieldProps<T>, stat
108
129
  * A time field allows users to enter and edit time values using a keyboard.
109
130
  * Each part of a time value is displayed in an individually editable segment.
110
131
  */
111
- export function useTimeField<T extends TimeValue>(props: AriaTimeFieldProps<T>, state: DatePickerFieldState, ref: RefObject<HTMLElement>): DateFieldAria {
132
+ export function useTimeField<T extends TimeValue>(props: AriaTimeFieldProps<T>, state: DateFieldState, ref: RefObject<HTMLElement>): DateFieldAria {
112
133
  return useDateField(props, state, ref);
113
134
  }
@@ -20,6 +20,7 @@ import {HTMLAttributes, RefObject} from 'react';
20
20
  // @ts-ignore
21
21
  import intlMessages from '../intl/*.json';
22
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';
@@ -66,7 +67,7 @@ export function useDatePicker<T extends DateValue>(props: AriaDatePickerProps<T>
66
67
  let ariaDescribedBy = [descProps['aria-describedby'], fieldProps['aria-describedby']].filter(Boolean).join(' ') || undefined;
67
68
 
68
69
  return {
69
- groupProps: mergeProps(groupProps, descProps, {
70
+ groupProps: mergeProps(groupProps, fieldProps, descProps, {
70
71
  role: 'group',
71
72
  'aria-disabled': props.isDisabled || null,
72
73
  'aria-labelledby': labelledBy,
@@ -81,6 +82,8 @@ export function useDatePicker<T extends DateValue>(props: AriaDatePickerProps<T>
81
82
  },
82
83
  fieldProps: {
83
84
  ...fieldProps,
85
+ [roleSymbol]: 'presentation',
86
+ 'aria-describedby': ariaDescribedBy,
84
87
  value: state.value,
85
88
  onChange: state.setValue,
86
89
  minValue: props.minValue,
@@ -119,7 +122,7 @@ export function useDatePicker<T extends DateValue>(props: AriaDatePickerProps<T>
119
122
  maxValue: props.maxValue,
120
123
  isDisabled: props.isDisabled,
121
124
  isReadOnly: props.isReadOnly,
122
- isDateDisabled: props.isDateDisabled,
125
+ isDateUnavailable: props.isDateUnavailable,
123
126
  defaultFocusedValue: state.dateValue ? undefined : props.placeholderValue
124
127
  }
125
128
  };
@@ -1,14 +1,14 @@
1
- import {DatePickerFieldState, DatePickerState, DateRangePickerState} from '@react-stately/datepicker';
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 | DatePickerFieldState, ref: RefObject<HTMLElement>) {
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,10 +15,11 @@ 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 {focusManagerSymbol, roleSymbol} from './useDateField';
18
19
  import {HTMLAttributes, RefObject, useMemo} from 'react';
19
20
  // @ts-ignore
20
21
  import intlMessages from '../intl/*.json';
21
- import {mergeProps, useDescription, useId, useLabels} from '@react-aria/utils';
22
+ import {mergeProps, useDescription, useId} 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';
@@ -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
@@ -68,15 +65,15 @@ export function useDateRangePicker<T extends DateValue>(props: AriaDateRangePick
68
65
  let description = state.formatValue(locale, {month: 'long'});
69
66
  let descProps = useDescription(description);
70
67
 
71
- let startFieldProps = useLabels({
68
+ let startFieldProps = {
72
69
  'aria-label': formatMessage('startDate'),
73
70
  'aria-labelledby': labelledBy
74
- });
71
+ };
75
72
 
76
- let endFieldProps = useLabels({
73
+ let endFieldProps = {
77
74
  'aria-label': formatMessage('endDate'),
78
75
  'aria-labelledby': labelledBy
79
- });
76
+ };
80
77
 
81
78
  let buttonId = useId();
82
79
  let dialogId = useId();
@@ -92,6 +89,8 @@ export function useDateRangePicker<T extends DateValue>(props: AriaDateRangePick
92
89
  let focusManager = useMemo(() => createFocusManager(ref), [ref]);
93
90
  let commonFieldProps = {
94
91
  [focusManagerSymbol]: focusManager,
92
+ [roleSymbol]: 'presentation',
93
+ 'aria-describedby': ariaDescribedBy,
95
94
  minValue: props.minValue,
96
95
  maxValue: props.maxValue,
97
96
  placeholderValue: props.placeholderValue,
@@ -133,7 +132,6 @@ export function useDateRangePicker<T extends DateValue>(props: AriaDateRangePick
133
132
  startFieldProps: {
134
133
  ...startFieldProps,
135
134
  ...commonFieldProps,
136
- 'aria-describedby': fieldProps['aria-describedby'],
137
135
  value: state.value?.start,
138
136
  onChange: start => state.setDateTime('start', start),
139
137
  autoFocus: props.autoFocus
@@ -141,7 +139,6 @@ export function useDateRangePicker<T extends DateValue>(props: AriaDateRangePick
141
139
  endFieldProps: {
142
140
  ...endFieldProps,
143
141
  ...commonFieldProps,
144
- 'aria-describedby': fieldProps['aria-describedby'],
145
142
  value: state.value?.end,
146
143
  onChange: end => state.setDateTime('end', end)
147
144
  },
@@ -155,7 +152,8 @@ export function useDateRangePicker<T extends DateValue>(props: AriaDateRangePick
155
152
  maxValue: props.maxValue,
156
153
  isDisabled: props.isDisabled,
157
154
  isReadOnly: props.isReadOnly,
158
- isDateDisabled: props.isDateDisabled,
155
+ isDateUnavailable: props.isDateUnavailable,
156
+ allowsNonContiguousRanges: props.allowsNonContiguousRanges,
159
157
  defaultFocusedValue: state.dateRange ? undefined : props.placeholderValue
160
158
  }
161
159
  };
@@ -10,8 +10,8 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import {DatePickerFieldState, DateSegment} from '@react-stately/datepicker';
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';
@@ -30,11 +30,11 @@ interface DateSegmentAria {
30
30
  * A date segment displays an individual unit of a date and time, and allows users to edit
31
31
  * the value by typing or using the arrow keys to increment and decrement.
32
32
  */
33
- export function useDateSegment(segment: DateSegment, state: DatePickerFieldState, ref: RefObject<HTMLElement>): DateSegmentAria {
33
+ export function useDateSegment(segment: DateSegment, state: DateFieldState, ref: RefObject<HTMLElement>): DateSegmentAria {
34
34
  let enteredKeys = useRef('');
35
35
  let {locale, direction} = useLocale();
36
36
  let displayNames = useDisplayNames();
37
- let {ariaLabelledBy, ariaDescribedBy, focusManager} = hookData.get(state);
37
+ let {ariaLabel, ariaLabelledBy, ariaDescribedBy, focusManager} = hookData.get(state);
38
38
 
39
39
  let textValue = segment.text;
40
40
  let options = useMemo(() => state.dateFormatter.resolvedOptions(), [state.dateFormatter]);
@@ -48,7 +48,7 @@ export function useDateSegment(segment: DateSegment, state: DatePickerFieldState
48
48
  if (segment.type === 'month') {
49
49
  let monthTextValue = monthDateFormatter.format(state.dateValue);
50
50
  textValue = monthTextValue !== textValue ? `${textValue} – ${monthTextValue}` : monthTextValue;
51
- } else if (segment.type === 'hour' || segment.type === 'dayPeriod') {
51
+ } else if (segment.type === 'hour') {
52
52
  textValue = hourDateFormatter.format(state.dateValue);
53
53
  }
54
54
 
@@ -341,6 +341,14 @@ export function useDateSegment(segment: DateSegment, state: DatePickerFieldState
341
341
  let id = useId();
342
342
  let isEditable = !state.isDisabled && !state.isReadOnly && segment.isEditable;
343
343
 
344
+ // Prepend the label passed from the field to each segment name.
345
+ // This is needed because VoiceOver on iOS does not announce groups.
346
+ let name = segment.type === 'literal' ? '' : displayNames.of(segment.type);
347
+ let labelProps = useLabels({
348
+ 'aria-label': (ariaLabel ? ariaLabel + ' ' : '') + name,
349
+ 'aria-labelledby': ariaLabelledBy
350
+ });
351
+
344
352
  // Literal segments should not be visible to screen readers. We don't really need any of the above,
345
353
  // but the rules of hooks mean hooks cannot be conditional so we have to put this condition here.
346
354
  if (segment.type === 'literal') {
@@ -352,12 +360,10 @@ export function useDateSegment(segment: DateSegment, state: DatePickerFieldState
352
360
  }
353
361
 
354
362
  return {
355
- segmentProps: mergeProps(spinButtonProps, pressProps, {
363
+ segmentProps: mergeProps(spinButtonProps, pressProps, labelProps, {
356
364
  id,
357
365
  ...touchPropOverrides,
358
366
  'aria-invalid': state.validationState === 'invalid' ? 'true' : undefined,
359
- 'aria-label': displayNames.of(segment.type),
360
- 'aria-labelledby': `${ariaLabelledBy} ${id}`,
361
367
  'aria-describedby': ariaDescribedBy,
362
368
  'aria-placeholder': segment.isPlaceholder ? segment.text : undefined,
363
369
  'aria-readonly': state.isReadOnly || !segment.isEditable ? 'true' : undefined,