@react-aria/calendar 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/calendar",
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,15 +18,15 @@
18
18
  },
19
19
  "dependencies": {
20
20
  "@babel/runtime": "^7.6.2",
21
- "@internationalized/date": "3.0.0-nightly.3168+2fabc1ac0",
22
- "@react-aria/i18n": "3.0.0-nightly.1469+2fabc1ac0",
23
- "@react-aria/interactions": "3.0.0-nightly.1469+2fabc1ac0",
24
- "@react-aria/live-announcer": "3.0.0-nightly.1469+2fabc1ac0",
25
- "@react-aria/utils": "3.0.0-nightly.1469+2fabc1ac0",
26
- "@react-stately/calendar": "3.0.0-nightly.1469+2fabc1ac0",
27
- "@react-types/button": "3.4.5-nightly.3168+2fabc1ac0",
28
- "@react-types/calendar": "3.0.0-nightly.3168+2fabc1ac0",
29
- "@react-types/shared": "3.0.0-nightly.1469+2fabc1ac0",
21
+ "@internationalized/date": "3.0.0-nightly.3180+0bba35ae3",
22
+ "@react-aria/i18n": "3.0.0-nightly.1481+0bba35ae3",
23
+ "@react-aria/interactions": "3.0.0-nightly.1481+0bba35ae3",
24
+ "@react-aria/live-announcer": "3.0.0-nightly.1481+0bba35ae3",
25
+ "@react-aria/utils": "3.0.0-nightly.1481+0bba35ae3",
26
+ "@react-stately/calendar": "3.0.0-nightly.1481+0bba35ae3",
27
+ "@react-types/button": "3.4.5-nightly.3180+0bba35ae3",
28
+ "@react-types/calendar": "3.0.0-nightly.3180+0bba35ae3",
29
+ "@react-types/shared": "3.0.0-nightly.1481+0bba35ae3",
30
30
  "date-fns": "^1.30.1"
31
31
  },
32
32
  "peerDependencies": {
@@ -36,5 +36,5 @@
36
36
  "publishConfig": {
37
37
  "access": "public"
38
38
  },
39
- "gitHead": "2fabc1ac07aa6fd75e7111e571e27787d8a44314"
39
+ "gitHead": "0bba35ae36b5d220570385215860d3ca3b549656"
40
40
  }
@@ -20,6 +20,7 @@ import {DOMProps} from '@react-types/shared';
20
20
  import intlMessages from '../intl/*.json';
21
21
  import {mergeProps, useDescription, useId, useUpdateEffect} from '@react-aria/utils';
22
22
  import {useMessageFormatter} from '@react-aria/i18n';
23
+ import {useRef} from 'react';
23
24
 
24
25
  export function useCalendarBase(props: CalendarPropsBase & DOMProps, state: CalendarState | RangeCalendarState): CalendarAria {
25
26
  let formatMessage = useMessageFormatter(intlMessages);
@@ -49,6 +50,21 @@ export function useCalendarBase(props: CalendarPropsBase & DOMProps, state: Cale
49
50
  // Label the child grid elements by the group element if it is labelled.
50
51
  calendarIds.set(state, props['aria-label'] || props['aria-labelledby'] ? calendarId : null);
51
52
 
53
+ // If the next or previous buttons become disabled while they are focused, move focus to the calendar body.
54
+ let nextFocused = useRef(false);
55
+ let nextDisabled = props.isDisabled || state.isNextVisibleRangeInvalid();
56
+ if (nextDisabled && nextFocused.current) {
57
+ nextFocused.current = false;
58
+ state.setFocused(true);
59
+ }
60
+
61
+ let previousFocused = useRef(false);
62
+ let previousDisabled = props.isDisabled || state.isPreviousVisibleRangeInvalid();
63
+ if (previousDisabled && previousFocused.current) {
64
+ previousFocused.current = false;
65
+ state.setFocused(true);
66
+ }
67
+
52
68
  return {
53
69
  calendarProps: mergeProps(descriptionProps, {
54
70
  role: 'group',
@@ -59,12 +75,16 @@ export function useCalendarBase(props: CalendarPropsBase & DOMProps, state: Cale
59
75
  nextButtonProps: {
60
76
  onPress: () => state.focusNextPage(),
61
77
  'aria-label': formatMessage('next'),
62
- isDisabled: props.isDisabled || state.isNextVisibleRangeInvalid()
78
+ isDisabled: nextDisabled,
79
+ onFocus: () => nextFocused.current = true,
80
+ onBlur: () => nextFocused.current = false
63
81
  },
64
82
  prevButtonProps: {
65
83
  onPress: () => state.focusPreviousPage(),
66
84
  'aria-label': formatMessage('previous'),
67
- isDisabled: props.isDisabled || state.isPreviousVisibleRangeInvalid()
85
+ isDisabled: previousDisabled,
86
+ onFocus: () => previousFocused.current = true,
87
+ onBlur: () => previousFocused.current = false
68
88
  },
69
89
  title: visibleRangeDescription
70
90
  };
@@ -10,7 +10,7 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import {CalendarDate, isEqualDay, isSameDay, isSameMonth, isToday} from '@internationalized/date';
13
+ import {CalendarDate, isEqualDay, isSameDay, isToday} from '@internationalized/date';
14
14
  import {CalendarState, RangeCalendarState} from '@react-stately/calendar';
15
15
  import {focusWithoutScrolling} from '@react-aria/utils';
16
16
  import {HTMLAttributes, RefObject, useEffect, useMemo, useRef} from 'react';
@@ -41,8 +41,21 @@ interface CalendarCellAria {
41
41
  isSelected: boolean,
42
42
  /** Whether the cell is focused. */
43
43
  isFocused: boolean,
44
- /** Whether the cell is disabled. */
44
+ /**
45
+ * Whether the cell is disabled, according to the calendar's `minValue`, `maxValue`, and `isDisabled` props.
46
+ * Disabled dates are not focusable, and cannot be selected by the user. They are typically
47
+ * displayed with a dimmed appearance.
48
+ */
45
49
  isDisabled: boolean,
50
+ /**
51
+ * Whether the cell is unavailable, according to the calendar's `isDateUnavailable` prop. Unavailable dates remain
52
+ * focusable, but cannot be selected by the user. They should be displayed with a visual affordance to indicate they
53
+ * are unavailable, such as a different color or a strikethrough.
54
+ *
55
+ * Note that because they are focusable, unavailable dates must meet a 4.5:1 color contrast ratio,
56
+ * [as defined by WCAG](https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html).
57
+ */
58
+ isUnavailable: boolean,
46
59
  /**
47
60
  * Whether the cell is outside the visible range of the calendar.
48
61
  * For example, dates before the first day of a month in the same week.
@@ -70,6 +83,8 @@ export function useCalendarCell(props: AriaCalendarCellProps, state: CalendarSta
70
83
  let isSelected = state.isSelected(date);
71
84
  let isFocused = state.isCellFocused(date);
72
85
  isDisabled = isDisabled || state.isCellDisabled(date);
86
+ let isUnavailable = state.isCellUnavailable(date);
87
+ let isSelectable = !isDisabled && !isUnavailable;
73
88
 
74
89
  // For performance, reuse the same date object as before if the new date prop is the same.
75
90
  // This allows subsequent useMemo results to be reused.
@@ -102,7 +117,7 @@ export function useCalendarCell(props: AriaCalendarCellProps, state: CalendarSta
102
117
 
103
118
  // When a cell is focused and this is a range calendar, add a prompt to help
104
119
  // screenreader users know that they are in a range selection mode.
105
- if ('anchorDate' in state && isFocused && !state.isReadOnly) {
120
+ if ('anchorDate' in state && isFocused && !state.isReadOnly && isSelectable) {
106
121
  let rangeSelectionPrompt = '';
107
122
 
108
123
  // If selection has started add "click to finish selecting range"
@@ -127,7 +142,7 @@ export function useCalendarCell(props: AriaCalendarCellProps, state: CalendarSta
127
142
  // again to trigger onPressStart. Cancel presses immediately when the pointer exits.
128
143
  shouldCancelOnPointerExit: 'anchorDate' in state && !!state.anchorDate,
129
144
  preventFocusOnPress: true,
130
- isDisabled,
145
+ isDisabled: !isSelectable,
131
146
  onPressStart(e) {
132
147
  if (state.isReadOnly) {
133
148
  state.setFocusedDate(date);
@@ -214,7 +229,10 @@ export function useCalendarCell(props: AriaCalendarCellProps, state: CalendarSta
214
229
  // there will be an announcement to "click to finish selecting range" (above).
215
230
  state.selectDate(date);
216
231
  let nextDay = date.add({days: 1});
217
- if (isSameMonth(date, nextDay)) {
232
+ if (state.isInvalid(nextDay)) {
233
+ nextDay = date.subtract({days: 1});
234
+ }
235
+ if (!state.isInvalid(nextDay)) {
218
236
  state.setFocusedDate(nextDay);
219
237
  }
220
238
  } else if (e.pointerType === 'virtual') {
@@ -249,8 +267,8 @@ export function useCalendarCell(props: AriaCalendarCellProps, state: CalendarSta
249
267
  return {
250
268
  cellProps: {
251
269
  role: 'gridcell',
252
- 'aria-disabled': isDisabled || null,
253
- 'aria-selected': isSelected
270
+ 'aria-disabled': !isSelectable || null,
271
+ 'aria-selected': isSelectable ? isSelected : null
254
272
  },
255
273
  buttonProps: mergeProps(pressProps, {
256
274
  onFocus() {
@@ -260,11 +278,11 @@ export function useCalendarCell(props: AriaCalendarCellProps, state: CalendarSta
260
278
  },
261
279
  tabIndex,
262
280
  role: 'button',
263
- 'aria-disabled': isDisabled || null,
281
+ 'aria-disabled': !isSelectable || null,
264
282
  'aria-label': label,
265
283
  onPointerEnter(e) {
266
284
  // Highlight the date on hover or drag over a date when selecting a range.
267
- if ('highlightDate' in state && (e.pointerType !== 'touch' || state.isDragging)) {
285
+ if ('highlightDate' in state && (e.pointerType !== 'touch' || state.isDragging) && isSelectable) {
268
286
  state.highlightDate(date);
269
287
  }
270
288
  },
@@ -285,6 +303,7 @@ export function useCalendarCell(props: AriaCalendarCellProps, state: CalendarSta
285
303
  isFocused,
286
304
  isSelected,
287
305
  isDisabled,
306
+ isUnavailable,
288
307
  isOutsideVisibleRange: date.compare(state.visibleRange.start) < 0 || date.compare(state.visibleRange.end) > 0,
289
308
  formattedDate
290
309
  };
@@ -120,8 +120,8 @@ export function useCalendarGrid(props: CalendarGridProps, state: CalendarState |
120
120
  'aria-labelledby': calendarIds.get(state)
121
121
  });
122
122
 
123
- let dayFormatter = useDateFormatter({weekday: 'narrow'});
124
- let dayFormatterLong = useDateFormatter({weekday: 'long'});
123
+ let dayFormatter = useDateFormatter({weekday: 'narrow', timeZone: state.timeZone});
124
+ let dayFormatterLong = useDateFormatter({weekday: 'long', timeZone: state.timeZone});
125
125
  let {locale} = useLocale();
126
126
  let weekStart = startOfWeek(state.visibleRange.start, locale);
127
127
  let weekDays = [...new Array(7).keys()].map((index) => {
@@ -53,7 +53,7 @@ export function useRangeCalendar<T extends DateValue>(props: RangeCalendarProps<
53
53
  let target = e.target as HTMLElement;
54
54
  let body = document.getElementById(res.calendarProps.id);
55
55
  if (
56
- (!body.contains(target) || target.getAttribute('role') !== 'button') &&
56
+ (!body.contains(target) || !target.closest('[role="button"]')) &&
57
57
  !document.getElementById(res.nextButtonProps.id)?.contains(target) &&
58
58
  !document.getElementById(res.prevButtonProps.id)?.contains(target)
59
59
  ) {
package/src/utils.ts CHANGED
@@ -50,12 +50,14 @@ export function useVisibleRangeDescription(startDate: CalendarDate, endDate: Cal
50
50
  month: 'long',
51
51
  year: 'numeric',
52
52
  era: startDate.calendar.identifier !== 'gregory' ? 'long' : undefined,
53
- calendar: startDate.calendar.identifier
53
+ calendar: startDate.calendar.identifier,
54
+ timeZone
54
55
  });
55
56
 
56
57
  let dateFormatter = useDateFormatter({
57
58
  dateStyle: 'long',
58
- calendar: startDate.calendar.identifier
59
+ calendar: startDate.calendar.identifier,
60
+ timeZone
59
61
  });
60
62
 
61
63
  return useMemo(() => {