@react-aria/calendar 3.9.5 → 3.10.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.
Files changed (221) hide show
  1. package/dist/import.mjs +3 -7
  2. package/dist/main.js +6 -10
  3. package/dist/main.js.map +1 -1
  4. package/dist/module.js +3 -7
  5. package/dist/module.js.map +1 -1
  6. package/dist/types/src/index.d.ts +6 -0
  7. package/package.json +16 -20
  8. package/src/index.ts +6 -8
  9. package/dist/ar-AE.main.js +0 -17
  10. package/dist/ar-AE.main.js.map +0 -1
  11. package/dist/ar-AE.mjs +0 -19
  12. package/dist/ar-AE.module.js +0 -19
  13. package/dist/ar-AE.module.js.map +0 -1
  14. package/dist/bg-BG.main.js +0 -17
  15. package/dist/bg-BG.main.js.map +0 -1
  16. package/dist/bg-BG.mjs +0 -19
  17. package/dist/bg-BG.module.js +0 -19
  18. package/dist/bg-BG.module.js.map +0 -1
  19. package/dist/cs-CZ.main.js +0 -17
  20. package/dist/cs-CZ.main.js.map +0 -1
  21. package/dist/cs-CZ.mjs +0 -19
  22. package/dist/cs-CZ.module.js +0 -19
  23. package/dist/cs-CZ.module.js.map +0 -1
  24. package/dist/da-DK.main.js +0 -17
  25. package/dist/da-DK.main.js.map +0 -1
  26. package/dist/da-DK.mjs +0 -19
  27. package/dist/da-DK.module.js +0 -19
  28. package/dist/da-DK.module.js.map +0 -1
  29. package/dist/de-DE.main.js +0 -17
  30. package/dist/de-DE.main.js.map +0 -1
  31. package/dist/de-DE.mjs +0 -19
  32. package/dist/de-DE.module.js +0 -19
  33. package/dist/de-DE.module.js.map +0 -1
  34. package/dist/el-GR.main.js +0 -17
  35. package/dist/el-GR.main.js.map +0 -1
  36. package/dist/el-GR.mjs +0 -19
  37. package/dist/el-GR.module.js +0 -19
  38. package/dist/el-GR.module.js.map +0 -1
  39. package/dist/en-US.main.js +0 -17
  40. package/dist/en-US.main.js.map +0 -1
  41. package/dist/en-US.mjs +0 -19
  42. package/dist/en-US.module.js +0 -19
  43. package/dist/en-US.module.js.map +0 -1
  44. package/dist/es-ES.main.js +0 -17
  45. package/dist/es-ES.main.js.map +0 -1
  46. package/dist/es-ES.mjs +0 -19
  47. package/dist/es-ES.module.js +0 -19
  48. package/dist/es-ES.module.js.map +0 -1
  49. package/dist/et-EE.main.js +0 -17
  50. package/dist/et-EE.main.js.map +0 -1
  51. package/dist/et-EE.mjs +0 -19
  52. package/dist/et-EE.module.js +0 -19
  53. package/dist/et-EE.module.js.map +0 -1
  54. package/dist/fi-FI.main.js +0 -17
  55. package/dist/fi-FI.main.js.map +0 -1
  56. package/dist/fi-FI.mjs +0 -19
  57. package/dist/fi-FI.module.js +0 -19
  58. package/dist/fi-FI.module.js.map +0 -1
  59. package/dist/fr-FR.main.js +0 -17
  60. package/dist/fr-FR.main.js.map +0 -1
  61. package/dist/fr-FR.mjs +0 -19
  62. package/dist/fr-FR.module.js +0 -19
  63. package/dist/fr-FR.module.js.map +0 -1
  64. package/dist/he-IL.main.js +0 -17
  65. package/dist/he-IL.main.js.map +0 -1
  66. package/dist/he-IL.mjs +0 -19
  67. package/dist/he-IL.module.js +0 -19
  68. package/dist/he-IL.module.js.map +0 -1
  69. package/dist/hr-HR.main.js +0 -17
  70. package/dist/hr-HR.main.js.map +0 -1
  71. package/dist/hr-HR.mjs +0 -19
  72. package/dist/hr-HR.module.js +0 -19
  73. package/dist/hr-HR.module.js.map +0 -1
  74. package/dist/hu-HU.main.js +0 -17
  75. package/dist/hu-HU.main.js.map +0 -1
  76. package/dist/hu-HU.mjs +0 -19
  77. package/dist/hu-HU.module.js +0 -19
  78. package/dist/hu-HU.module.js.map +0 -1
  79. package/dist/intlStrings.main.js +0 -108
  80. package/dist/intlStrings.main.js.map +0 -1
  81. package/dist/intlStrings.mjs +0 -110
  82. package/dist/intlStrings.module.js +0 -110
  83. package/dist/intlStrings.module.js.map +0 -1
  84. package/dist/it-IT.main.js +0 -17
  85. package/dist/it-IT.main.js.map +0 -1
  86. package/dist/it-IT.mjs +0 -19
  87. package/dist/it-IT.module.js +0 -19
  88. package/dist/it-IT.module.js.map +0 -1
  89. package/dist/ja-JP.main.js +0 -17
  90. package/dist/ja-JP.main.js.map +0 -1
  91. package/dist/ja-JP.mjs +0 -19
  92. package/dist/ja-JP.module.js +0 -19
  93. package/dist/ja-JP.module.js.map +0 -1
  94. package/dist/ko-KR.main.js +0 -17
  95. package/dist/ko-KR.main.js.map +0 -1
  96. package/dist/ko-KR.mjs +0 -19
  97. package/dist/ko-KR.module.js +0 -19
  98. package/dist/ko-KR.module.js.map +0 -1
  99. package/dist/lt-LT.main.js +0 -17
  100. package/dist/lt-LT.main.js.map +0 -1
  101. package/dist/lt-LT.mjs +0 -19
  102. package/dist/lt-LT.module.js +0 -19
  103. package/dist/lt-LT.module.js.map +0 -1
  104. package/dist/lv-LV.main.js +0 -17
  105. package/dist/lv-LV.main.js.map +0 -1
  106. package/dist/lv-LV.mjs +0 -19
  107. package/dist/lv-LV.module.js +0 -19
  108. package/dist/lv-LV.module.js.map +0 -1
  109. package/dist/nb-NO.main.js +0 -17
  110. package/dist/nb-NO.main.js.map +0 -1
  111. package/dist/nb-NO.mjs +0 -19
  112. package/dist/nb-NO.module.js +0 -19
  113. package/dist/nb-NO.module.js.map +0 -1
  114. package/dist/nl-NL.main.js +0 -17
  115. package/dist/nl-NL.main.js.map +0 -1
  116. package/dist/nl-NL.mjs +0 -19
  117. package/dist/nl-NL.module.js +0 -19
  118. package/dist/nl-NL.module.js.map +0 -1
  119. package/dist/pl-PL.main.js +0 -17
  120. package/dist/pl-PL.main.js.map +0 -1
  121. package/dist/pl-PL.mjs +0 -19
  122. package/dist/pl-PL.module.js +0 -19
  123. package/dist/pl-PL.module.js.map +0 -1
  124. package/dist/pt-BR.main.js +0 -17
  125. package/dist/pt-BR.main.js.map +0 -1
  126. package/dist/pt-BR.mjs +0 -19
  127. package/dist/pt-BR.module.js +0 -19
  128. package/dist/pt-BR.module.js.map +0 -1
  129. package/dist/pt-PT.main.js +0 -17
  130. package/dist/pt-PT.main.js.map +0 -1
  131. package/dist/pt-PT.mjs +0 -19
  132. package/dist/pt-PT.module.js +0 -19
  133. package/dist/pt-PT.module.js.map +0 -1
  134. package/dist/ro-RO.main.js +0 -17
  135. package/dist/ro-RO.main.js.map +0 -1
  136. package/dist/ro-RO.mjs +0 -19
  137. package/dist/ro-RO.module.js +0 -19
  138. package/dist/ro-RO.module.js.map +0 -1
  139. package/dist/ru-RU.main.js +0 -17
  140. package/dist/ru-RU.main.js.map +0 -1
  141. package/dist/ru-RU.mjs +0 -19
  142. package/dist/ru-RU.module.js +0 -19
  143. package/dist/ru-RU.module.js.map +0 -1
  144. package/dist/sk-SK.main.js +0 -17
  145. package/dist/sk-SK.main.js.map +0 -1
  146. package/dist/sk-SK.mjs +0 -19
  147. package/dist/sk-SK.module.js +0 -19
  148. package/dist/sk-SK.module.js.map +0 -1
  149. package/dist/sl-SI.main.js +0 -17
  150. package/dist/sl-SI.main.js.map +0 -1
  151. package/dist/sl-SI.mjs +0 -19
  152. package/dist/sl-SI.module.js +0 -19
  153. package/dist/sl-SI.module.js.map +0 -1
  154. package/dist/sr-SP.main.js +0 -17
  155. package/dist/sr-SP.main.js.map +0 -1
  156. package/dist/sr-SP.mjs +0 -19
  157. package/dist/sr-SP.module.js +0 -19
  158. package/dist/sr-SP.module.js.map +0 -1
  159. package/dist/sv-SE.main.js +0 -17
  160. package/dist/sv-SE.main.js.map +0 -1
  161. package/dist/sv-SE.mjs +0 -19
  162. package/dist/sv-SE.module.js +0 -19
  163. package/dist/sv-SE.module.js.map +0 -1
  164. package/dist/tr-TR.main.js +0 -17
  165. package/dist/tr-TR.main.js.map +0 -1
  166. package/dist/tr-TR.mjs +0 -19
  167. package/dist/tr-TR.module.js +0 -19
  168. package/dist/tr-TR.module.js.map +0 -1
  169. package/dist/types.d.ts +0 -124
  170. package/dist/types.d.ts.map +0 -1
  171. package/dist/uk-UA.main.js +0 -17
  172. package/dist/uk-UA.main.js.map +0 -1
  173. package/dist/uk-UA.mjs +0 -19
  174. package/dist/uk-UA.module.js +0 -19
  175. package/dist/uk-UA.module.js.map +0 -1
  176. package/dist/useCalendar.main.js +0 -25
  177. package/dist/useCalendar.main.js.map +0 -1
  178. package/dist/useCalendar.mjs +0 -20
  179. package/dist/useCalendar.module.js +0 -20
  180. package/dist/useCalendar.module.js.map +0 -1
  181. package/dist/useCalendarBase.main.js +0 -113
  182. package/dist/useCalendarBase.main.js.map +0 -1
  183. package/dist/useCalendarBase.mjs +0 -108
  184. package/dist/useCalendarBase.module.js +0 -108
  185. package/dist/useCalendarBase.module.js.map +0 -1
  186. package/dist/useCalendarCell.main.js +0 -295
  187. package/dist/useCalendarCell.main.js.map +0 -1
  188. package/dist/useCalendarCell.mjs +0 -290
  189. package/dist/useCalendarCell.module.js +0 -290
  190. package/dist/useCalendarCell.module.js.map +0 -1
  191. package/dist/useCalendarGrid.main.js +0 -142
  192. package/dist/useCalendarGrid.main.js.map +0 -1
  193. package/dist/useCalendarGrid.mjs +0 -137
  194. package/dist/useCalendarGrid.module.js +0 -137
  195. package/dist/useCalendarGrid.module.js.map +0 -1
  196. package/dist/useRangeCalendar.main.js +0 -66
  197. package/dist/useRangeCalendar.main.js.map +0 -1
  198. package/dist/useRangeCalendar.mjs +0 -61
  199. package/dist/useRangeCalendar.module.js +0 -61
  200. package/dist/useRangeCalendar.module.js.map +0 -1
  201. package/dist/utils.main.js +0 -143
  202. package/dist/utils.main.js.map +0 -1
  203. package/dist/utils.mjs +0 -135
  204. package/dist/utils.module.js +0 -135
  205. package/dist/utils.module.js.map +0 -1
  206. package/dist/zh-CN.main.js +0 -17
  207. package/dist/zh-CN.main.js.map +0 -1
  208. package/dist/zh-CN.mjs +0 -19
  209. package/dist/zh-CN.module.js +0 -19
  210. package/dist/zh-CN.module.js.map +0 -1
  211. package/dist/zh-TW.main.js +0 -17
  212. package/dist/zh-TW.main.js.map +0 -1
  213. package/dist/zh-TW.mjs +0 -19
  214. package/dist/zh-TW.module.js +0 -19
  215. package/dist/zh-TW.module.js.map +0 -1
  216. package/src/useCalendar.ts +0 -23
  217. package/src/useCalendarBase.ts +0 -116
  218. package/src/useCalendarCell.ts +0 -375
  219. package/src/useCalendarGrid.ts +0 -174
  220. package/src/useRangeCalendar.ts +0 -82
  221. package/src/utils.ts +0 -147
@@ -1,375 +0,0 @@
1
- /*
2
- * Copyright 2020 Adobe. All rights reserved.
3
- * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
- * you may not use this file except in compliance with the License. You may obtain a copy
5
- * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
- *
7
- * Unless required by applicable law or agreed to in writing, software distributed under
8
- * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
- * OF ANY KIND, either express or implied. See the License for the specific language
10
- * governing permissions and limitations under the License.
11
- */
12
-
13
- import {CalendarDate, isEqualDay, isSameDay, isToday} from '@internationalized/date';
14
- import {CalendarState, RangeCalendarState} from '@react-stately/calendar';
15
- import {DOMAttributes, RefObject} from '@react-types/shared';
16
- import {focusWithoutScrolling, getActiveElement, getEventTarget, getScrollParent, mergeProps, scrollIntoViewport, useDeepMemo, useDescription} from '@react-aria/utils';
17
- import {getEraFormat, hookData} from './utils';
18
- import {getInteractionModality, usePress} from '@react-aria/interactions';
19
- // @ts-ignore
20
- import intlMessages from '../intl/*.json';
21
- import {useDateFormatter, useLocalizedStringFormatter} from '@react-aria/i18n';
22
- import {useEffect, useMemo, useRef} from 'react';
23
-
24
- export interface AriaCalendarCellProps {
25
- /** The date that this cell represents. */
26
- date: CalendarDate,
27
- /**
28
- * Whether the cell is disabled. By default, this is determined by the
29
- * Calendar's `minValue`, `maxValue`, and `isDisabled` props.
30
- */
31
- isDisabled?: boolean,
32
-
33
- /**
34
- * Whether the cell is outside of the current month.
35
- */
36
- isOutsideMonth?: boolean
37
- }
38
-
39
- export interface CalendarCellAria {
40
- /** Props for the grid cell element (e.g. `<td>`). */
41
- cellProps: DOMAttributes,
42
- /** Props for the button element within the cell. */
43
- buttonProps: DOMAttributes,
44
- /** Whether the cell is currently being pressed. */
45
- isPressed: boolean,
46
- /** Whether the cell is selected. */
47
- isSelected: boolean,
48
- /** Whether the cell is focused. */
49
- isFocused: boolean,
50
- /**
51
- * Whether the cell is disabled, according to the calendar's `minValue`, `maxValue`, and `isDisabled` props.
52
- * Disabled dates are not focusable, and cannot be selected by the user. They are typically
53
- * displayed with a dimmed appearance.
54
- */
55
- isDisabled: boolean,
56
- /**
57
- * Whether the cell is unavailable, according to the calendar's `isDateUnavailable` prop. Unavailable dates remain
58
- * focusable, but cannot be selected by the user. They should be displayed with a visual affordance to indicate they
59
- * are unavailable, such as a different color or a strikethrough.
60
- *
61
- * Note that because they are focusable, unavailable dates must meet a 4.5:1 color contrast ratio,
62
- * [as defined by WCAG](https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html).
63
- */
64
- isUnavailable: boolean,
65
- /**
66
- * Whether the cell is outside the visible range of the calendar.
67
- * For example, dates before the first day of a month in the same week.
68
- */
69
- isOutsideVisibleRange: boolean,
70
- /** Whether the cell is part of an invalid selection. */
71
- isInvalid: boolean,
72
- /** The day number formatted according to the current locale. */
73
- formattedDate: string
74
- }
75
-
76
- /**
77
- * Provides the behavior and accessibility implementation for a calendar cell component.
78
- * A calendar cell displays a date cell within a calendar grid which can be selected by the user.
79
- */
80
- export function useCalendarCell(props: AriaCalendarCellProps, state: CalendarState | RangeCalendarState, ref: RefObject<HTMLElement | null>): CalendarCellAria {
81
- let {date, isDisabled} = props;
82
- let {errorMessageId, selectedDateDescription} = hookData.get(state)!;
83
- let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-aria/calendar');
84
- let dateFormatter = useDateFormatter({
85
- weekday: 'long',
86
- day: 'numeric',
87
- month: 'long',
88
- year: 'numeric',
89
- era: getEraFormat(date),
90
- timeZone: state.timeZone
91
- });
92
- let isSelected = state.isSelected(date);
93
- let isFocused = state.isCellFocused(date) && !props.isOutsideMonth;
94
- isDisabled = isDisabled || state.isCellDisabled(date);
95
- let isUnavailable = state.isCellUnavailable(date);
96
- let isSelectable = !isDisabled && !isUnavailable;
97
- let isInvalid = state.isValueInvalid && Boolean(
98
- 'highlightedRange' in state
99
- ? !state.anchorDate && state.highlightedRange && date.compare(state.highlightedRange.start) >= 0 && date.compare(state.highlightedRange.end) <= 0
100
- : state.value && isSameDay(state.value, date)
101
- );
102
-
103
- if (isInvalid) {
104
- isSelected = true;
105
- }
106
-
107
- // For performance, reuse the same date object as before if the new date prop is the same.
108
- // This allows subsequent useMemo results to be reused.
109
- date = useDeepMemo<CalendarDate>(date, isEqualDay);
110
- let nativeDate = useMemo(() => date.toDate(state.timeZone), [date, state.timeZone]);
111
-
112
- // aria-label should be localize Day of week, Month, Day and Year without Time.
113
- let isDateToday = isToday(date, state.timeZone);
114
- let label = useMemo(() => {
115
- let label = '';
116
-
117
- // If this is a range calendar, add a description of the full selected range
118
- // to the first and last selected date.
119
- if (
120
- 'highlightedRange' in state &&
121
- state.value &&
122
- !state.anchorDate &&
123
- (isSameDay(date, state.value.start) || isSameDay(date, state.value.end))
124
- ) {
125
- label = selectedDateDescription + ', ';
126
- }
127
-
128
- label += dateFormatter.format(nativeDate);
129
- if (isDateToday) {
130
- // If date is today, set appropriate string depending on selected state:
131
- label = stringFormatter.format(isSelected ? 'todayDateSelected' : 'todayDate', {
132
- date: label
133
- });
134
- } else if (isSelected) {
135
- // If date is selected but not today:
136
- label = stringFormatter.format('dateSelected', {
137
- date: label
138
- });
139
- }
140
-
141
- if (state.minValue && isSameDay(date, state.minValue)) {
142
- label += ', ' + stringFormatter.format('minimumDate');
143
- } else if (state.maxValue && isSameDay(date, state.maxValue)) {
144
- label += ', ' + stringFormatter.format('maximumDate');
145
- }
146
-
147
- return label;
148
- }, [dateFormatter, nativeDate, stringFormatter, isSelected, isDateToday, date, state, selectedDateDescription]);
149
-
150
- // When a cell is focused and this is a range calendar, add a prompt to help
151
- // screenreader users know that they are in a range selection mode.
152
- let rangeSelectionPrompt = '';
153
- if ('anchorDate' in state && isFocused && !state.isReadOnly && isSelectable) {
154
- // If selection has started add "click to finish selecting range"
155
- if (state.anchorDate) {
156
- rangeSelectionPrompt = stringFormatter.format('finishRangeSelectionPrompt');
157
- // Otherwise, add "click to start selecting range" prompt
158
- } else {
159
- rangeSelectionPrompt = stringFormatter.format('startRangeSelectionPrompt');
160
- }
161
- }
162
-
163
- let descriptionProps = useDescription(rangeSelectionPrompt);
164
-
165
- let isAnchorPressed = useRef(false);
166
- let isRangeBoundaryPressed = useRef(false);
167
- let touchDragTimerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
168
- let {pressProps, isPressed} = usePress({
169
- // When dragging to select a range, we don't want dragging over the original anchor
170
- // again to trigger onPressStart. Cancel presses immediately when the pointer exits.
171
- shouldCancelOnPointerExit: 'anchorDate' in state && !!state.anchorDate,
172
- preventFocusOnPress: true,
173
- isDisabled: !isSelectable || state.isReadOnly,
174
- onPressStart(e) {
175
- if (state.isReadOnly) {
176
- state.setFocusedDate(date);
177
- state.setFocused(true);
178
- return;
179
- }
180
-
181
- if ('highlightedRange' in state && !state.anchorDate && (e.pointerType === 'mouse' || e.pointerType === 'touch')) {
182
- // Allow dragging the start or end date of a range to modify it
183
- // rather than starting a new selection.
184
- // Don't allow dragging when invalid, or weird jumping behavior may occur as date ranges
185
- // are constrained to available dates. The user will need to select a new range in this case.
186
- if (state.highlightedRange && !isInvalid) {
187
- if (isSameDay(date, state.highlightedRange.start)) {
188
- state.setAnchorDate(state.highlightedRange.end);
189
- state.setFocusedDate(date);
190
- state.setFocused(true);
191
- state.setDragging(true);
192
- isRangeBoundaryPressed.current = true;
193
- return;
194
- } else if (isSameDay(date, state.highlightedRange.end)) {
195
- state.setAnchorDate(state.highlightedRange.start);
196
- state.setFocusedDate(date);
197
- state.setFocused(true);
198
- state.setDragging(true);
199
- isRangeBoundaryPressed.current = true;
200
- return;
201
- }
202
- }
203
-
204
- let startDragging = () => {
205
- state.setDragging(true);
206
- touchDragTimerRef.current = undefined;
207
-
208
- state.selectDate(date);
209
- state.setFocusedDate(date);
210
- state.setFocused(true);
211
- isAnchorPressed.current = true;
212
- };
213
-
214
- // Start selection on mouse/touch down so users can drag to select a range.
215
- // On touch, delay dragging to determine if the user really meant to scroll.
216
- if (e.pointerType === 'touch') {
217
- touchDragTimerRef.current = setTimeout(startDragging, 200);
218
- } else {
219
- startDragging();
220
- }
221
- }
222
- },
223
- onPressEnd() {
224
- isRangeBoundaryPressed.current = false;
225
- isAnchorPressed.current = false;
226
- clearTimeout(touchDragTimerRef.current);
227
- touchDragTimerRef.current = undefined;
228
- },
229
- onPress() {
230
- // For non-range selection, always select on press up.
231
- if (!('anchorDate' in state) && !state.isReadOnly) {
232
- state.selectDate(date);
233
- state.setFocusedDate(date);
234
- state.setFocused(true);
235
- }
236
- },
237
- onPressUp(e) {
238
- if (state.isReadOnly) {
239
- return;
240
- }
241
-
242
- // If the user tapped quickly, the date won't be selected yet and the
243
- // timer will still be in progress. In this case, select the date on touch up.
244
- // Timer is cleared in onPressEnd.
245
- if ('anchorDate' in state && touchDragTimerRef.current) {
246
- state.selectDate(date);
247
- state.setFocusedDate(date);
248
- state.setFocused(true);
249
- }
250
-
251
- if ('anchorDate' in state) {
252
- if (isRangeBoundaryPressed.current) {
253
- // When clicking on the start or end date of an already selected range,
254
- // start a new selection on press up to also allow dragging the date to
255
- // change the existing range.
256
- state.setAnchorDate(date);
257
- } else if (state.anchorDate && !isAnchorPressed.current) {
258
- // When releasing a drag or pressing the end date of a range, select it.
259
- state.selectDate(date);
260
- state.setFocusedDate(date);
261
- state.setFocused(true);
262
- } else if (e.pointerType === 'keyboard' && !state.anchorDate) {
263
- // For range selection, auto-advance the focused date by one if using keyboard.
264
- // This gives an indication that you're selecting a range rather than a single date.
265
- // For mouse, this is unnecessary because users will see the indication on hover. For screen readers,
266
- // there will be an announcement to "click to finish selecting range" (above).
267
- state.selectDate(date);
268
- let nextDay = date.add({days: 1});
269
- if (state.isInvalid(nextDay)) {
270
- nextDay = date.subtract({days: 1});
271
- }
272
- if (!state.isInvalid(nextDay)) {
273
- state.setFocusedDate(nextDay);
274
- state.setFocused(true);
275
- }
276
- } else if (e.pointerType === 'virtual') {
277
- // For screen readers, just select the date on click.
278
- state.selectDate(date);
279
- state.setFocusedDate(date);
280
- state.setFocused(true);
281
- }
282
- }
283
- }
284
- });
285
-
286
- let tabIndex: number | undefined = undefined;
287
- if (!isDisabled) {
288
- tabIndex = isSameDay(date, state.focusedDate) ? 0 : -1;
289
- }
290
-
291
- // Focus the button in the DOM when the state updates.
292
- useEffect(() => {
293
- if (isFocused && ref.current) {
294
- focusWithoutScrolling(ref.current);
295
-
296
- // Scroll into view if navigating with a keyboard, otherwise
297
- // try not to shift the view under the user's mouse/finger.
298
- // If in a overlay, scrollIntoViewport will only cause scrolling
299
- // up to the overlay scroll body to prevent overlay shifting.
300
- // Also only scroll into view if the cell actually got focused.
301
- // There are some cases where the cell might be disabled or inside,
302
- // an inert container and we don't want to scroll then.
303
- if (getInteractionModality() !== 'pointer' && getActiveElement() === ref.current) {
304
- scrollIntoViewport(ref.current, {containingElement: getScrollParent(ref.current)});
305
- }
306
- }
307
- }, [isFocused, ref]);
308
-
309
- let cellDateFormatter = useDateFormatter({
310
- day: 'numeric',
311
- timeZone: state.timeZone,
312
- calendar: date.calendar.identifier
313
- });
314
-
315
- let formattedDate = useMemo(() => cellDateFormatter.formatToParts(nativeDate).find(part => part.type === 'day')!.value, [cellDateFormatter, nativeDate]);
316
-
317
- return {
318
- cellProps: {
319
- role: 'gridcell',
320
- 'aria-disabled': !isSelectable || undefined,
321
- 'aria-selected': isSelected || undefined,
322
- 'aria-invalid': isInvalid || undefined
323
- },
324
- buttonProps: mergeProps(pressProps, {
325
- onFocus() {
326
- if (!isDisabled) {
327
- state.setFocusedDate(date);
328
- state.setFocused(true);
329
- }
330
- },
331
- tabIndex,
332
- role: 'button',
333
- 'aria-disabled': !isSelectable || undefined,
334
- 'aria-label': label,
335
- 'aria-invalid': isInvalid || undefined,
336
- 'aria-describedby': [
337
- isInvalid ? errorMessageId : undefined,
338
- descriptionProps['aria-describedby']
339
- ].filter(Boolean).join(' ') || undefined,
340
- onPointerEnter(e) {
341
- // Highlight the date on hover or drag over a date when selecting a range.
342
- if ('highlightDate' in state && (e.pointerType !== 'touch' || state.isDragging) && isSelectable) {
343
- state.highlightDate(date);
344
- }
345
- },
346
- onPointerDown(e: PointerEvent) {
347
- // This is necessary on touch devices to allow dragging
348
- // outside the original pressed element.
349
- // (JSDOM does not support this)
350
- let target = getEventTarget(e);
351
- if (target instanceof HTMLElement && 'releasePointerCapture' in target) {
352
- if ('hasPointerCapture' in target) {
353
- if (target.hasPointerCapture(e.pointerId)) {
354
- target.releasePointerCapture(e.pointerId);
355
- }
356
- } else {
357
- (target as HTMLElement).releasePointerCapture(e.pointerId);
358
- }
359
- }
360
- },
361
- onContextMenu(e) {
362
- // Prevent context menu on long press.
363
- e.preventDefault();
364
- }
365
- }),
366
- isPressed,
367
- isFocused,
368
- isSelected,
369
- isDisabled,
370
- isUnavailable,
371
- isOutsideVisibleRange: date.compare(state.visibleRange.start) < 0 || date.compare(state.visibleRange.end) > 0,
372
- isInvalid,
373
- formattedDate
374
- };
375
- }
@@ -1,174 +0,0 @@
1
- /*
2
- * Copyright 2020 Adobe. All rights reserved.
3
- * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
- * you may not use this file except in compliance with the License. You may obtain a copy
5
- * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
- *
7
- * Unless required by applicable law or agreed to in writing, software distributed under
8
- * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
- * OF ANY KIND, either express or implied. See the License for the specific language
10
- * governing permissions and limitations under the License.
11
- */
12
-
13
- import {CalendarDate, getWeeksInMonth, startOfWeek, today} from '@internationalized/date';
14
- import {CalendarState, RangeCalendarState} from '@react-stately/calendar';
15
- import {DOMAttributes} from '@react-types/shared';
16
- import {hookData, useVisibleRangeDescription} from './utils';
17
- import {KeyboardEvent, useMemo} from 'react';
18
- import {mergeProps, useLabels} from '@react-aria/utils';
19
- import {useDateFormatter, useLocale} from '@react-aria/i18n';
20
-
21
- export interface AriaCalendarGridProps {
22
- /**
23
- * The first date displayed in the calendar grid.
24
- * Defaults to the first visible date in the calendar.
25
- * Override this to display multiple date grids in a calendar.
26
- */
27
- startDate?: CalendarDate,
28
- /**
29
- * The last date displayed in the calendar grid.
30
- * Defaults to the last visible date in the calendar.
31
- * Override this to display multiple date grids in a calendar.
32
- */
33
- endDate?: CalendarDate,
34
- /**
35
- * The style of weekday names to display in the calendar grid header,
36
- * e.g. single letter, abbreviation, or full day name.
37
- * @default "narrow"
38
- */
39
- weekdayStyle?: 'narrow' | 'short' | 'long',
40
- /**
41
- * The day that starts the week.
42
- */
43
- firstDayOfWeek?: 'sun' | 'mon' | 'tue' | 'wed' | 'thu' | 'fri' | 'sat'
44
- }
45
-
46
- export interface CalendarGridAria {
47
- /** Props for the date grid element (e.g. `<table>`). */
48
- gridProps: DOMAttributes,
49
- /** Props for the grid header element (e.g. `<thead>`). */
50
- headerProps: DOMAttributes,
51
- /** A list of week day abbreviations formatted for the current locale, typically used in column headers. */
52
- weekDays: string[],
53
- /** The number of weeks in the month. */
54
- weeksInMonth: number
55
- }
56
-
57
- /**
58
- * Provides the behavior and accessibility implementation for a calendar grid component.
59
- * A calendar grid displays a single grid of days within a calendar or range calendar which
60
- * can be keyboard navigated and selected by the user.
61
- */
62
- export function useCalendarGrid(props: AriaCalendarGridProps, state: CalendarState | RangeCalendarState): CalendarGridAria {
63
- let {
64
- startDate = state.visibleRange.start,
65
- endDate = state.visibleRange.end,
66
- firstDayOfWeek
67
- } = props;
68
-
69
- let {direction} = useLocale();
70
-
71
- let onKeyDown = (e: KeyboardEvent) => {
72
- switch (e.key) {
73
- case 'Enter':
74
- case ' ':
75
- e.preventDefault();
76
- state.selectFocusedDate();
77
- break;
78
- case 'PageUp':
79
- e.preventDefault();
80
- e.stopPropagation();
81
- state.focusPreviousSection(e.shiftKey);
82
- break;
83
- case 'PageDown':
84
- e.preventDefault();
85
- e.stopPropagation();
86
- state.focusNextSection(e.shiftKey);
87
- break;
88
- case 'End':
89
- e.preventDefault();
90
- e.stopPropagation();
91
- state.focusSectionEnd();
92
- break;
93
- case 'Home':
94
- e.preventDefault();
95
- e.stopPropagation();
96
- state.focusSectionStart();
97
- break;
98
- case 'ArrowLeft':
99
- e.preventDefault();
100
- e.stopPropagation();
101
- if (direction === 'rtl') {
102
- state.focusNextDay();
103
- } else {
104
- state.focusPreviousDay();
105
- }
106
- break;
107
- case 'ArrowUp':
108
- e.preventDefault();
109
- e.stopPropagation();
110
- state.focusPreviousRow();
111
- break;
112
- case 'ArrowRight':
113
- e.preventDefault();
114
- e.stopPropagation();
115
- if (direction === 'rtl') {
116
- state.focusPreviousDay();
117
- } else {
118
- state.focusNextDay();
119
- }
120
- break;
121
- case 'ArrowDown':
122
- e.preventDefault();
123
- e.stopPropagation();
124
- state.focusNextRow();
125
- break;
126
- case 'Escape':
127
- // Cancel the selection.
128
- if ('setAnchorDate' in state) {
129
- e.preventDefault();
130
- state.setAnchorDate(null);
131
- }
132
- break;
133
- }
134
- };
135
-
136
- let visibleRangeDescription = useVisibleRangeDescription(startDate, endDate, state.timeZone, true);
137
-
138
- let {ariaLabel, ariaLabelledBy} = hookData.get(state)!;
139
- let labelProps = useLabels({
140
- 'aria-label': [ariaLabel, visibleRangeDescription].filter(Boolean).join(', '),
141
- 'aria-labelledby': ariaLabelledBy
142
- });
143
-
144
- let dayFormatter = useDateFormatter({weekday: props.weekdayStyle || 'narrow', timeZone: state.timeZone});
145
- let {locale} = useLocale();
146
- let weekDays = useMemo(() => {
147
- let weekStart = startOfWeek(today(state.timeZone), locale, firstDayOfWeek);
148
- return [...new Array(7).keys()].map((index) => {
149
- let date = weekStart.add({days: index});
150
- let dateDay = date.toDate(state.timeZone);
151
- return dayFormatter.format(dateDay);
152
- });
153
- }, [locale, state.timeZone, dayFormatter, firstDayOfWeek]);
154
- let weeksInMonth = getWeeksInMonth(startDate, locale, firstDayOfWeek);
155
-
156
- return {
157
- gridProps: mergeProps(labelProps, {
158
- role: 'grid',
159
- 'aria-readonly': state.isReadOnly || undefined,
160
- 'aria-disabled': state.isDisabled || undefined,
161
- 'aria-multiselectable': ('highlightedRange' in state) || undefined,
162
- onKeyDown,
163
- onFocus: () => state.setFocused(true),
164
- onBlur: () => state.setFocused(false)
165
- }),
166
- headerProps: {
167
- // Column headers are hidden to screen readers to make navigating with a touch screen reader easier.
168
- // The day names are already included in the label of each cell, so there's no need to announce them twice.
169
- 'aria-hidden': true
170
- },
171
- weekDays,
172
- weeksInMonth
173
- };
174
- }
@@ -1,82 +0,0 @@
1
- /*
2
- * Copyright 2020 Adobe. All rights reserved.
3
- * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
- * you may not use this file except in compliance with the License. You may obtain a copy
5
- * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
- *
7
- * Unless required by applicable law or agreed to in writing, software distributed under
8
- * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
- * OF ANY KIND, either express or implied. See the License for the specific language
10
- * governing permissions and limitations under the License.
11
- */
12
-
13
- import {AriaRangeCalendarProps, DateValue} from '@react-types/calendar';
14
- import {CalendarAria, useCalendarBase} from './useCalendarBase';
15
- import {FocusableElement, RefObject} from '@react-types/shared';
16
- import {isFocusWithin, nodeContains, useEvent} from '@react-aria/utils';
17
- import {RangeCalendarState} from '@react-stately/calendar';
18
- import {useRef} from 'react';
19
-
20
- /**
21
- * Provides the behavior and accessibility implementation for a range calendar component.
22
- * A range calendar displays one or more date grids and allows users to select a contiguous range of dates.
23
- */
24
- export function useRangeCalendar<T extends DateValue>(props: AriaRangeCalendarProps<T>, state: RangeCalendarState, ref: RefObject<FocusableElement | null>): CalendarAria {
25
- let res = useCalendarBase(props, state);
26
-
27
- // We need to ignore virtual pointer events from VoiceOver due to these bugs.
28
- // https://bugs.webkit.org/show_bug.cgi?id=222627
29
- // https://bugs.webkit.org/show_bug.cgi?id=223202
30
- // usePress also does this and waits for the following click event before firing.
31
- // We need to match that here otherwise this will fire before the press event in
32
- // useCalendarCell, causing range selection to not work properly.
33
- let isVirtualClick = useRef(false);
34
- let windowRef = useRef(typeof window !== 'undefined' ? window : null);
35
- useEvent(windowRef, 'pointerdown', e => {
36
- isVirtualClick.current = e.width === 0 && e.height === 0;
37
- });
38
-
39
- // Stop range selection when pressing or releasing a pointer outside the calendar body,
40
- // except when pressing the next or previous buttons to switch months.
41
- let endDragging = (e: PointerEvent) => {
42
- if (isVirtualClick.current) {
43
- isVirtualClick.current = false;
44
- return;
45
- }
46
-
47
- state.setDragging(false);
48
- if (!state.anchorDate) {
49
- return;
50
- }
51
-
52
- let target = e.target as Element;
53
- if (
54
- ref.current &&
55
- isFocusWithin(ref.current) &&
56
- (!nodeContains(ref.current, target) || !target.closest('button, [role="button"]'))
57
- ) {
58
- state.selectFocusedDate();
59
- }
60
- };
61
-
62
- useEvent(windowRef, 'pointerup', endDragging);
63
-
64
- // Also stop range selection on blur, e.g. tabbing away from the calendar.
65
- res.calendarProps.onBlur = e => {
66
- if (!ref.current) {
67
- return;
68
- }
69
- if ((!e.relatedTarget || !nodeContains(ref.current, e.relatedTarget)) && state.anchorDate) {
70
- state.selectFocusedDate();
71
- }
72
- };
73
-
74
- // Prevent touch scrolling while dragging
75
- useEvent(ref, 'touchmove', e => {
76
- if (state.isDragging) {
77
- e.preventDefault();
78
- }
79
- }, {passive: false, capture: true});
80
-
81
- return res;
82
- }