@react-aria/datepicker 3.0.0-nightly.3173 → 3.0.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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.3173+fe4148bea",
3
+ "version": "3.0.0-rc.0",
4
4
  "description": "Spectrum UI components in React",
5
5
  "license": "Apache-2.0",
6
6
  "main": "dist/main.js",
@@ -18,20 +18,20 @@
18
18
  },
19
19
  "dependencies": {
20
20
  "@babel/runtime": "^7.6.2",
21
- "@internationalized/message": "3.0.6-nightly.3173+fe4148bea",
22
- "@internationalized/number": "3.0.6-nightly.3173+fe4148bea",
23
- "@react-aria/focus": "3.0.0-nightly.1474+fe4148bea",
24
- "@react-aria/i18n": "3.0.0-nightly.1474+fe4148bea",
25
- "@react-aria/interactions": "3.0.0-nightly.1474+fe4148bea",
26
- "@react-aria/label": "3.0.0-nightly.1474+fe4148bea",
27
- "@react-aria/spinbutton": "3.0.0-nightly.1474+fe4148bea",
28
- "@react-aria/utils": "3.0.0-nightly.1474+fe4148bea",
29
- "@react-stately/datepicker": "3.0.0-nightly.3173+fe4148bea",
30
- "@react-types/button": "3.4.5-nightly.3173+fe4148bea",
31
- "@react-types/calendar": "3.0.0-nightly.3173+fe4148bea",
32
- "@react-types/datepicker": "3.0.0-nightly.3173+fe4148bea",
33
- "@react-types/dialog": "3.3.5-nightly.3173+fe4148bea",
34
- "@react-types/shared": "3.0.0-nightly.1474+fe4148bea"
21
+ "@internationalized/message": "^3.0.6",
22
+ "@internationalized/number": "^3.1.0",
23
+ "@react-aria/focus": "^3.5.5",
24
+ "@react-aria/i18n": "^3.3.9",
25
+ "@react-aria/interactions": "^3.8.4",
26
+ "@react-aria/label": "^3.2.5",
27
+ "@react-aria/spinbutton": "^3.0.6",
28
+ "@react-aria/utils": "^3.12.0",
29
+ "@react-stately/datepicker": "3.0.0-rc.0",
30
+ "@react-types/button": "^3.4.5",
31
+ "@react-types/calendar": "3.0.0-rc.0",
32
+ "@react-types/datepicker": "3.0.0-rc.0",
33
+ "@react-types/dialog": "^3.3.5",
34
+ "@react-types/shared": "^3.12.0"
35
35
  },
36
36
  "peerDependencies": {
37
37
  "react": "^16.8.0 || ^17.0.0-rc.1",
@@ -40,5 +40,5 @@
40
40
  "publishConfig": {
41
41
  "access": "public"
42
42
  },
43
- "gitHead": "fe4148beaf7f63a1ea486aecc139747a666f8c3a"
43
+ "gitHead": "6a503b715e0dbbf92038cd7f08b1bcdde4c78e82"
44
44
  }
package/src/index.ts CHANGED
@@ -14,4 +14,10 @@ export {useDatePicker} from './useDatePicker';
14
14
  export {useDateSegment} from './useDateSegment';
15
15
  export {useDateField, useTimeField} from './useDateField';
16
16
  export {useDateRangePicker} from './useDateRangePicker';
17
- export * 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,19 +38,25 @@ interface DateFieldAria {
37
38
 
38
39
  // Data that is passed between useDateField and useDateSegment.
39
40
  interface HookData {
41
+ ariaLabel: string,
40
42
  ariaLabelledBy: string,
41
43
  ariaDescribedBy: string,
42
44
  focusManager: FocusManager
43
45
  }
44
46
 
45
- export const hookData = new WeakMap<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'
@@ -63,22 +70,47 @@ export function useDateField<T extends DateValue>(props: DateFieldProps<T>, stat
63
70
  }
64
71
  });
65
72
 
66
- let formatter = useDateFormatter(state.getFormatOptions({month: 'long'}));
67
- let descProps = useDescription(state.value ? formatter.format(state.dateValue) : null);
73
+ let formatMessage = useMessageFormatter(intlMessages);
74
+ let message = state.maxGranularity === 'hour' ? 'selectedTimeDescription' : 'selectedDateDescription';
75
+ let field = state.maxGranularity === 'hour' ? 'time' : 'date';
76
+ let description = state.value ? formatMessage(message, {[field]: state.formatValue({month: 'long'})}) : '';
77
+ let descProps = useDescription(description);
68
78
 
69
- let segmentLabelledBy = fieldProps['aria-labelledby'] || fieldProps.id;
70
- let describedBy = [descProps['aria-describedby'], fieldProps['aria-describedby']].filter(Boolean).join(' ') || undefined;
79
+ // If within a date picker or date range picker, the date field will have role="presentation" and an aria-describedby
80
+ // will be passed in that references the value (e.g. entire range). Otherwise, add the field's value description.
81
+ let describedBy = props[roleSymbol] === 'presentation'
82
+ ? fieldProps['aria-describedby']
83
+ : [descProps['aria-describedby'], fieldProps['aria-describedby']].filter(Boolean).join(' ') || undefined;
71
84
  let propsFocusManager = props[focusManagerSymbol];
72
85
  let focusManager = useMemo(() => propsFocusManager || createFocusManager(ref), [propsFocusManager, ref]);
73
86
 
87
+ // Pass labels and other information to segments.
74
88
  hookData.set(state, {
75
- ariaLabelledBy: segmentLabelledBy,
89
+ ariaLabel: props['aria-label'],
90
+ ariaLabelledBy: [props['aria-labelledby'], labelProps.id].filter(Boolean).join(' ') || undefined,
76
91
  ariaDescribedBy: describedBy,
77
92
  focusManager
78
93
  });
79
94
 
80
95
  let autoFocusRef = useRef(props.autoFocus);
81
96
 
97
+ // When used within a date picker or date range picker, the field gets role="presentation"
98
+ // rather than role="group". Since the date picker/date range picker already has a role="group"
99
+ // with a label and description, and the segments are already labeled by this as well, this
100
+ // avoids very verbose duplicate announcements.
101
+ let fieldDOMProps: HTMLAttributes<HTMLElement>;
102
+ if (props[roleSymbol] === 'presentation') {
103
+ fieldDOMProps = {
104
+ role: 'presentation'
105
+ };
106
+ } else {
107
+ fieldDOMProps = mergeProps(fieldProps, {
108
+ role: 'group',
109
+ 'aria-disabled': props.isDisabled || undefined,
110
+ 'aria-describedby': describedBy
111
+ });
112
+ }
113
+
82
114
  useEffect(() => {
83
115
  if (autoFocusRef.current) {
84
116
  focusManager.focusFirst();
@@ -86,6 +118,7 @@ export function useDateField<T extends DateValue>(props: DateFieldProps<T>, stat
86
118
  autoFocusRef.current = false;
87
119
  }, [focusManager]);
88
120
 
121
+ let domProps = filterDOMProps(props);
89
122
  return {
90
123
  labelProps: {
91
124
  ...labelProps,
@@ -93,11 +126,7 @@ export function useDateField<T extends DateValue>(props: DateFieldProps<T>, stat
93
126
  focusManager.focusFirst();
94
127
  }
95
128
  },
96
- fieldProps: mergeProps(fieldProps, descProps, groupProps, focusWithinProps, {
97
- role: 'group',
98
- 'aria-disabled': props.isDisabled || undefined,
99
- 'aria-describedby': describedBy
100
- }),
129
+ fieldProps: mergeProps(domProps, fieldDOMProps, groupProps, focusWithinProps),
101
130
  descriptionProps,
102
131
  errorMessageProps
103
132
  };
@@ -108,6 +137,6 @@ export function useDateField<T extends DateValue>(props: DateFieldProps<T>, stat
108
137
  * A time field allows users to enter and edit time values using a keyboard.
109
138
  * Each part of a time value is displayed in an individually editable segment.
110
139
  */
111
- export function useTimeField<T extends TimeValue>(props: AriaTimeFieldProps<T>, state: DatePickerFieldState, ref: RefObject<HTMLElement>): DateFieldAria {
140
+ export function useTimeField<T extends TimeValue>(props: AriaTimeFieldProps<T>, state: DateFieldState, ref: RefObject<HTMLElement>): DateFieldAria {
112
141
  return useDateField(props, state, ref);
113
142
  }
@@ -16,15 +16,16 @@ import {AriaDialogProps} from '@react-types/dialog';
16
16
  import {CalendarProps} from '@react-types/calendar';
17
17
  import {createFocusManager} from '@react-aria/focus';
18
18
  import {DatePickerState} from '@react-stately/datepicker';
19
+ import {filterDOMProps, mergeProps, useDescription, useId} from '@react-aria/utils';
19
20
  import {HTMLAttributes, RefObject} from 'react';
20
21
  // @ts-ignore
21
22
  import intlMessages from '../intl/*.json';
22
- import {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,14 @@ export function useDatePicker<T extends DateValue>(props: AriaDatePickerProps<T>
62
63
  let labelledBy = fieldProps['aria-labelledby'] || fieldProps.id;
63
64
 
64
65
  let {locale} = useLocale();
65
- let 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);
67
71
 
68
72
  return {
69
- groupProps: mergeProps(groupProps, descProps, {
73
+ groupProps: mergeProps(domProps, groupProps, fieldProps, descProps, {
70
74
  role: 'group',
71
75
  'aria-disabled': props.isDisabled || null,
72
76
  'aria-labelledby': labelledBy,
@@ -81,6 +85,8 @@ export function useDatePicker<T extends DateValue>(props: AriaDatePickerProps<T>
81
85
  },
82
86
  fieldProps: {
83
87
  ...fieldProps,
88
+ [roleSymbol]: 'presentation',
89
+ 'aria-describedby': ariaDescribedBy,
84
90
  value: state.value,
85
91
  onChange: state.setValue,
86
92
  minValue: props.minValue,
@@ -120,7 +126,9 @@ export function useDatePicker<T extends DateValue>(props: AriaDatePickerProps<T>
120
126
  isDisabled: props.isDisabled,
121
127
  isReadOnly: props.isReadOnly,
122
128
  isDateUnavailable: props.isDateUnavailable,
123
- defaultFocusedValue: state.dateValue ? undefined : props.placeholderValue
129
+ defaultFocusedValue: state.dateValue ? undefined : props.placeholderValue,
130
+ validationState: state.validationState,
131
+ errorMessage: props.errorMessage
124
132
  }
125
133
  };
126
134
  }
@@ -1,14 +1,14 @@
1
- import {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,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();
@@ -92,6 +90,8 @@ export function useDateRangePicker<T extends DateValue>(props: AriaDateRangePick
92
90
  let focusManager = useMemo(() => createFocusManager(ref), [ref]);
93
91
  let commonFieldProps = {
94
92
  [focusManagerSymbol]: focusManager,
93
+ [roleSymbol]: 'presentation',
94
+ 'aria-describedby': ariaDescribedBy,
95
95
  minValue: props.minValue,
96
96
  maxValue: props.maxValue,
97
97
  placeholderValue: props.placeholderValue,
@@ -104,8 +104,10 @@ export function useDateRangePicker<T extends DateValue>(props: AriaDateRangePick
104
104
  validationState: state.validationState
105
105
  };
106
106
 
107
+ let domProps = filterDOMProps(props);
108
+
107
109
  return {
108
- groupProps: mergeProps(groupProps, fieldProps, descProps, focusWithinProps, {
110
+ groupProps: mergeProps(domProps, groupProps, fieldProps, descProps, focusWithinProps, {
109
111
  role: 'group',
110
112
  'aria-disabled': props.isDisabled || null,
111
113
  'aria-describedby': ariaDescribedBy
@@ -133,7 +135,6 @@ export function useDateRangePicker<T extends DateValue>(props: AriaDateRangePick
133
135
  startFieldProps: {
134
136
  ...startFieldProps,
135
137
  ...commonFieldProps,
136
- 'aria-describedby': fieldProps['aria-describedby'],
137
138
  value: state.value?.start,
138
139
  onChange: start => state.setDateTime('start', start),
139
140
  autoFocus: props.autoFocus
@@ -141,7 +142,6 @@ export function useDateRangePicker<T extends DateValue>(props: AriaDateRangePick
141
142
  endFieldProps: {
142
143
  ...endFieldProps,
143
144
  ...commonFieldProps,
144
- 'aria-describedby': fieldProps['aria-describedby'],
145
145
  value: state.value?.end,
146
146
  onChange: end => state.setDateTime('end', end)
147
147
  },
@@ -157,7 +157,9 @@ export function useDateRangePicker<T extends DateValue>(props: AriaDateRangePick
157
157
  isReadOnly: props.isReadOnly,
158
158
  isDateUnavailable: props.isDateUnavailable,
159
159
  allowsNonContiguousRanges: props.allowsNonContiguousRanges,
160
- defaultFocusedValue: state.dateRange ? undefined : props.placeholderValue
160
+ defaultFocusedValue: state.dateRange ? undefined : props.placeholderValue,
161
+ validationState: state.validationState,
162
+ errorMessage: props.errorMessage
161
163
  }
162
164
  };
163
165
  }
@@ -10,17 +10,16 @@
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';
18
18
  import {useDateFormatter, useFilter, useLocale} from '@react-aria/i18n';
19
19
  import {useDisplayNames} from './useDisplayNames';
20
- import {usePress} from '@react-aria/interactions';
21
20
  import {useSpinButton} from '@react-aria/spinbutton';
22
21
 
23
- interface DateSegmentAria {
22
+ export interface DateSegmentAria {
24
23
  /** Props for the segment element. */
25
24
  segmentProps: HTMLAttributes<HTMLDivElement>
26
25
  }
@@ -30,11 +29,11 @@ interface DateSegmentAria {
30
29
  * A date segment displays an individual unit of a date and time, and allows users to edit
31
30
  * the value by typing or using the arrow keys to increment and decrement.
32
31
  */
33
- export function useDateSegment(segment: DateSegment, state: DatePickerFieldState, ref: RefObject<HTMLElement>): DateSegmentAria {
32
+ export function useDateSegment(segment: DateSegment, state: DateFieldState, ref: RefObject<HTMLElement>): DateSegmentAria {
34
33
  let enteredKeys = useRef('');
35
34
  let {locale, direction} = useLocale();
36
35
  let displayNames = useDisplayNames();
37
- let {ariaLabelledBy, ariaDescribedBy, focusManager} = hookData.get(state);
36
+ let {ariaLabel, ariaLabelledBy, ariaDescribedBy, focusManager} = hookData.get(state);
38
37
 
39
38
  let textValue = segment.text;
40
39
  let options = useMemo(() => state.dateFormatter.resolvedOptions(), [state.dateFormatter]);
@@ -48,7 +47,7 @@ export function useDateSegment(segment: DateSegment, state: DatePickerFieldState
48
47
  if (segment.type === 'month') {
49
48
  let monthTextValue = monthDateFormatter.format(state.dateValue);
50
49
  textValue = monthTextValue !== textValue ? `${textValue} – ${monthTextValue}` : monthTextValue;
51
- } else if (segment.type === 'hour' || segment.type === 'dayPeriod') {
50
+ } else if (segment.type === 'hour') {
52
51
  textValue = hourDateFormatter.format(state.dateValue);
53
52
  }
54
53
 
@@ -301,22 +300,6 @@ export function useDateSegment(segment: DateSegment, state: DatePickerFieldState
301
300
  }
302
301
  });
303
302
 
304
- // Focus on mouse down/touch up to match native textfield behavior.
305
- // usePress handles canceling text selection.
306
- let {pressProps} = usePress({
307
- preventFocusOnPress: true,
308
- onPressStart: (e) => {
309
- if (e.pointerType === 'mouse') {
310
- e.target.focus();
311
- }
312
- },
313
- onPress(e) {
314
- if (e.pointerType !== 'mouse') {
315
- e.target.focus();
316
- }
317
- }
318
- });
319
-
320
303
  // For Android: prevent selection on long press.
321
304
  useEvent(ref, 'selectstart', e => {
322
305
  e.preventDefault();
@@ -341,6 +324,14 @@ export function useDateSegment(segment: DateSegment, state: DatePickerFieldState
341
324
  let id = useId();
342
325
  let isEditable = !state.isDisabled && !state.isReadOnly && segment.isEditable;
343
326
 
327
+ // Prepend the label passed from the field to each segment name.
328
+ // This is needed because VoiceOver on iOS does not announce groups.
329
+ let name = segment.type === 'literal' ? '' : displayNames.of(segment.type);
330
+ let labelProps = useLabels({
331
+ 'aria-label': (ariaLabel ? ariaLabel + ' ' : '') + name,
332
+ 'aria-labelledby': ariaLabelledBy
333
+ });
334
+
344
335
  // Literal segments should not be visible to screen readers. We don't really need any of the above,
345
336
  // but the rules of hooks mean hooks cannot be conditional so we have to put this condition here.
346
337
  if (segment.type === 'literal') {
@@ -352,12 +343,10 @@ export function useDateSegment(segment: DateSegment, state: DatePickerFieldState
352
343
  }
353
344
 
354
345
  return {
355
- segmentProps: mergeProps(spinButtonProps, pressProps, {
346
+ segmentProps: mergeProps(spinButtonProps, labelProps, {
356
347
  id,
357
348
  ...touchPropOverrides,
358
349
  'aria-invalid': state.validationState === 'invalid' ? 'true' : undefined,
359
- 'aria-label': displayNames.of(segment.type),
360
- 'aria-labelledby': `${ariaLabelledBy} ${id}`,
361
350
  'aria-describedby': ariaDescribedBy,
362
351
  'aria-placeholder': segment.isPlaceholder ? segment.text : undefined,
363
352
  'aria-readonly': state.isReadOnly || !segment.isEditable ? 'true' : undefined,
@@ -373,7 +362,16 @@ export function useDateSegment(segment: DateSegment, state: DatePickerFieldState
373
362
  onKeyDown,
374
363
  onFocus,
375
364
  style: {
376
- caretColor: 'transparent'
365
+ caretColor: 'transparent',
366
+ userSelect: 'none',
367
+ WebkitUserSelect: 'none'
368
+ },
369
+ // Prevent pointer events from reaching useDatePickerGroup, and allow native browser behavior to focus the segment.
370
+ onPointerDown(e) {
371
+ e.stopPropagation();
372
+ },
373
+ onMouseDown(e) {
374
+ e.stopPropagation();
377
375
  }
378
376
  })
379
377
  };
@@ -21,6 +21,7 @@ interface DisplayNames {
21
21
  of(field: Field): string
22
22
  }
23
23
 
24
+ /** @private */
24
25
  export function useDisplayNames(): DisplayNames {
25
26
  let {locale} = useLocale();
26
27
  return useMemo(() => {