@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.
@@ -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
- let {locale, direction} = useLocale();
34
+ let {locale} = 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
 
@@ -115,31 +114,13 @@ export function useDateSegment(segment: DateSegment, state: DatePickerFieldState
115
114
  }
116
115
 
117
116
  switch (e.key) {
118
- case 'ArrowLeft':
119
- e.preventDefault();
120
- e.stopPropagation();
121
- if (direction === 'rtl') {
122
- focusManager.focusNext({tabbable: true});
123
- } else {
124
- focusManager.focusPrevious({tabbable: true});
125
- }
126
- break;
127
- case 'ArrowRight':
128
- e.preventDefault();
129
- e.stopPropagation();
130
- if (direction === 'rtl') {
131
- focusManager.focusPrevious({tabbable: true});
132
- } else {
133
- focusManager.focusNext({tabbable: true});
134
- }
135
- break;
136
117
  case 'Enter':
137
118
  e.preventDefault();
138
119
  e.stopPropagation();
139
120
  if (segment.isPlaceholder && !state.isReadOnly) {
140
121
  state.confirmPlaceholder(segment.type);
141
122
  }
142
- focusManager.focusNext({tabbable: true});
123
+ focusManager.focusNext();
143
124
  break;
144
125
  case 'Tab':
145
126
  break;
@@ -185,7 +166,7 @@ export function useDateSegment(segment: DateSegment, state: DatePickerFieldState
185
166
  } else {
186
167
  break;
187
168
  }
188
- focusManager.focusNext({tabbable: true});
169
+ focusManager.focusNext();
189
170
  break;
190
171
  case 'day':
191
172
  case 'hour':
@@ -234,7 +215,7 @@ export function useDateSegment(segment: DateSegment, state: DatePickerFieldState
234
215
  if (Number(numberValue + '0') > segment.maxValue || newValue.length >= String(segment.maxValue).length) {
235
216
  enteredKeys.current = '';
236
217
  if (shouldSetValue) {
237
- focusManager.focusNext({tabbable: true});
218
+ focusManager.focusNext();
238
219
  }
239
220
  } else {
240
221
  enteredKeys.current = newValue;
@@ -251,9 +232,11 @@ export function useDateSegment(segment: DateSegment, state: DatePickerFieldState
251
232
  // Safari requires that a selection is set or it won't fire input events.
252
233
  // Since usePress disables text selection, this won't happen by default.
253
234
  ref.current.style.webkitUserSelect = 'text';
235
+ ref.current.style.userSelect = 'text';
254
236
  let selection = window.getSelection();
255
237
  selection.collapse(ref.current);
256
- ref.current.style.webkitUserSelect = '';
238
+ ref.current.style.webkitUserSelect = 'none';
239
+ ref.current.style.userSelect = 'none';
257
240
  };
258
241
 
259
242
  let compositionRef = useRef('');
@@ -301,22 +284,6 @@ export function useDateSegment(segment: DateSegment, state: DatePickerFieldState
301
284
  }
302
285
  });
303
286
 
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
287
  // For Android: prevent selection on long press.
321
288
  useEvent(ref, 'selectstart', e => {
322
289
  e.preventDefault();
@@ -341,6 +308,14 @@ export function useDateSegment(segment: DateSegment, state: DatePickerFieldState
341
308
  let id = useId();
342
309
  let isEditable = !state.isDisabled && !state.isReadOnly && segment.isEditable;
343
310
 
311
+ // Prepend the label passed from the field to each segment name.
312
+ // This is needed because VoiceOver on iOS does not announce groups.
313
+ let name = segment.type === 'literal' ? '' : displayNames.of(segment.type);
314
+ let labelProps = useLabels({
315
+ 'aria-label': (ariaLabel ? ariaLabel + ' ' : '') + name,
316
+ 'aria-labelledby': ariaLabelledBy
317
+ });
318
+
344
319
  // Literal segments should not be visible to screen readers. We don't really need any of the above,
345
320
  // but the rules of hooks mean hooks cannot be conditional so we have to put this condition here.
346
321
  if (segment.type === 'literal') {
@@ -352,12 +327,10 @@ export function useDateSegment(segment: DateSegment, state: DatePickerFieldState
352
327
  }
353
328
 
354
329
  return {
355
- segmentProps: mergeProps(spinButtonProps, pressProps, {
330
+ segmentProps: mergeProps(spinButtonProps, labelProps, {
356
331
  id,
357
332
  ...touchPropOverrides,
358
333
  'aria-invalid': state.validationState === 'invalid' ? 'true' : undefined,
359
- 'aria-label': displayNames.of(segment.type),
360
- 'aria-labelledby': `${ariaLabelledBy} ${id}`,
361
334
  'aria-describedby': ariaDescribedBy,
362
335
  'aria-placeholder': segment.isPlaceholder ? segment.text : undefined,
363
336
  'aria-readonly': state.isReadOnly || !segment.isEditable ? 'true' : undefined,
@@ -373,7 +346,16 @@ export function useDateSegment(segment: DateSegment, state: DatePickerFieldState
373
346
  onKeyDown,
374
347
  onFocus,
375
348
  style: {
376
- caretColor: 'transparent'
349
+ caretColor: 'transparent',
350
+ userSelect: 'none',
351
+ WebkitUserSelect: 'none'
352
+ },
353
+ // Prevent pointer events from reaching useDatePickerGroup, and allow native browser behavior to focus the segment.
354
+ onPointerDown(e) {
355
+ e.stopPropagation();
356
+ },
357
+ onMouseDown(e) {
358
+ e.stopPropagation();
377
359
  }
378
360
  })
379
361
  };
@@ -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(() => {