@proyecto-viviana/solid-stately 0.2.7 → 0.4.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 (254) hide show
  1. package/README.md +30 -283
  2. package/dist/autocomplete/createAutocompleteState.d.ts +2 -2
  3. package/dist/autocomplete/index.d.ts +1 -1
  4. package/dist/calendar/IncompleteDate.d.ts +61 -0
  5. package/dist/calendar/createCalendarState.d.ts +41 -16
  6. package/dist/calendar/createDateFieldState.d.ts +39 -9
  7. package/dist/calendar/createDatePickerState.d.ts +60 -0
  8. package/dist/calendar/createRangeCalendarState.d.ts +37 -10
  9. package/dist/calendar/createTimeFieldState.d.ts +34 -7
  10. package/dist/calendar/index.d.ts +7 -6
  11. package/dist/checkbox/createCheckboxGroupState.d.ts +6 -6
  12. package/dist/checkbox/index.d.ts +1 -1
  13. package/dist/collections/ListCollection.d.ts +1 -1
  14. package/dist/collections/createListState.d.ts +11 -8
  15. package/dist/collections/createMenuState.d.ts +7 -7
  16. package/dist/collections/createSelectionState.d.ts +22 -10
  17. package/dist/collections/getChildNodes.d.ts +11 -0
  18. package/dist/collections/index.d.ts +6 -5
  19. package/dist/collections/types.d.ts +12 -12
  20. package/dist/color/Color.d.ts +1 -1
  21. package/dist/color/createColorAreaState.d.ts +4 -2
  22. package/dist/color/createColorFieldState.d.ts +38 -12
  23. package/dist/color/createColorSliderState.d.ts +9 -2
  24. package/dist/color/createColorWheelState.d.ts +13 -2
  25. package/dist/color/getColorChannels.d.ts +1 -1
  26. package/dist/color/index.d.ts +6 -6
  27. package/dist/color/types.d.ts +4 -4
  28. package/dist/combobox/createComboBoxState.d.ts +34 -13
  29. package/dist/combobox/index.d.ts +1 -1
  30. package/dist/data/createAsyncList.d.ts +4 -4
  31. package/dist/data/createListData.d.ts +2 -2
  32. package/dist/data/createTreeData.d.ts +6 -2
  33. package/dist/data/index.d.ts +3 -3
  34. package/dist/datepicker/index.d.ts +2 -4
  35. package/dist/disclosure/createDisclosureState.d.ts +2 -2
  36. package/dist/disclosure/index.d.ts +1 -1
  37. package/dist/dnd/createDragState.d.ts +2 -2
  38. package/dist/dnd/createDraggableCollectionState.d.ts +2 -2
  39. package/dist/dnd/createDropState.d.ts +2 -2
  40. package/dist/dnd/createDroppableCollectionState.d.ts +3 -3
  41. package/dist/dnd/index.d.ts +5 -5
  42. package/dist/dnd/types.d.ts +19 -17
  43. package/dist/form/createFormValidationState.d.ts +3 -3
  44. package/dist/form/index.d.ts +1 -1
  45. package/dist/grid/createGridState.d.ts +2 -2
  46. package/dist/grid/index.d.ts +2 -2
  47. package/dist/grid/types.d.ts +27 -11
  48. package/dist/index.d.ts +31 -30
  49. package/dist/index.js +9077 -7246
  50. package/dist/index.js.map +1 -1
  51. package/dist/menu/index.d.ts +2 -2
  52. package/dist/numberfield/createNumberFieldState.d.ts +2 -2
  53. package/dist/numberfield/index.d.ts +1 -1
  54. package/dist/overlays/createOverlayTriggerState.d.ts +2 -2
  55. package/dist/overlays/index.d.ts +1 -1
  56. package/dist/radio/createRadioGroupState.d.ts +7 -7
  57. package/dist/radio/index.d.ts +1 -1
  58. package/dist/searchfield/createSearchFieldState.d.ts +2 -2
  59. package/dist/searchfield/index.d.ts +2 -2
  60. package/dist/select/createSelectState.d.ts +14 -8
  61. package/dist/select/index.d.ts +1 -1
  62. package/dist/selection/Selection.d.ts +16 -0
  63. package/dist/selection/SelectionManager.d.ts +110 -0
  64. package/dist/selection/createMultipleSelectionState.d.ts +59 -0
  65. package/dist/selection/index.d.ts +6 -5
  66. package/dist/slider/createSliderState.d.ts +3 -3
  67. package/dist/slider/index.d.ts +2 -2
  68. package/dist/steplist/createStepListState.d.ts +48 -0
  69. package/dist/steplist/index.d.ts +1 -0
  70. package/dist/table/TableCollection.d.ts +33 -3
  71. package/dist/table/createTableColumnResizeState.d.ts +54 -0
  72. package/dist/table/createTableState.d.ts +2 -2
  73. package/dist/table/createTreeGridState.d.ts +15 -0
  74. package/dist/table/index.d.ts +6 -3
  75. package/dist/table/types.d.ts +68 -10
  76. package/dist/tabs/createTabListState.d.ts +5 -5
  77. package/dist/tabs/index.d.ts +1 -1
  78. package/dist/textfield/createTextFieldState.d.ts +2 -2
  79. package/dist/textfield/index.d.ts +1 -1
  80. package/dist/toast/createToastState.d.ts +24 -2
  81. package/dist/toast/index.d.ts +1 -1
  82. package/dist/toggle/createToggleGroupState.d.ts +4 -4
  83. package/dist/toggle/createToggleState.d.ts +2 -2
  84. package/dist/toggle/index.d.ts +2 -2
  85. package/dist/tooltip/createTooltipTriggerState.d.ts +2 -2
  86. package/dist/tooltip/index.d.ts +1 -1
  87. package/dist/tree/TreeCollection.d.ts +2 -2
  88. package/dist/tree/createTreeState.d.ts +2 -2
  89. package/dist/tree/index.d.ts +3 -3
  90. package/dist/tree/types.d.ts +27 -13
  91. package/dist/utils/index.d.ts +1 -1
  92. package/dist/utils/reactivity.d.ts +1 -1
  93. package/package.json +29 -30
  94. package/src/autocomplete/createAutocompleteState.d.ts +19 -17
  95. package/src/autocomplete/createAutocompleteState.ts +24 -30
  96. package/src/autocomplete/index.d.ts +6 -2
  97. package/src/autocomplete/index.ts +1 -1
  98. package/src/calendar/IncompleteDate.ts +430 -0
  99. package/src/calendar/createCalendarState.d.ts +139 -117
  100. package/src/calendar/createCalendarState.ts +308 -94
  101. package/src/calendar/createDateFieldState.d.ts +130 -95
  102. package/src/calendar/createDateFieldState.ts +447 -334
  103. package/src/calendar/createDatePickerState.ts +347 -0
  104. package/src/calendar/createRangeCalendarState.d.ts +154 -129
  105. package/src/calendar/createRangeCalendarState.ts +411 -85
  106. package/src/calendar/createTimeFieldState.d.ts +81 -79
  107. package/src/calendar/createTimeFieldState.ts +187 -108
  108. package/src/calendar/index.d.ts +80 -7
  109. package/src/calendar/index.ts +23 -12
  110. package/src/checkbox/createCheckboxGroupState.d.ts +57 -55
  111. package/src/checkbox/createCheckboxGroupState.ts +20 -29
  112. package/src/checkbox/index.d.ts +6 -2
  113. package/src/checkbox/index.ts +1 -1
  114. package/src/collections/ListCollection.d.ts +25 -22
  115. package/src/collections/ListCollection.ts +6 -6
  116. package/src/collections/createListState.d.ts +73 -58
  117. package/src/collections/createListState.ts +67 -35
  118. package/src/collections/createMenuState.d.ts +36 -31
  119. package/src/collections/createMenuState.ts +21 -12
  120. package/src/collections/createSelectionState.d.ts +78 -65
  121. package/src/collections/createSelectionState.ts +66 -52
  122. package/src/collections/getChildNodes.ts +98 -0
  123. package/src/collections/index.d.ts +38 -6
  124. package/src/collections/index.ts +14 -10
  125. package/src/collections/types.d.ts +101 -101
  126. package/src/collections/types.ts +12 -12
  127. package/src/color/Color.d.ts +20 -5
  128. package/src/color/Color.ts +357 -254
  129. package/src/color/createColorAreaState.d.ts +67 -65
  130. package/src/color/createColorAreaState.ts +21 -13
  131. package/src/color/createColorFieldState.d.ts +46 -44
  132. package/src/color/createColorFieldState.ts +285 -149
  133. package/src/color/createColorSliderState.d.ts +58 -56
  134. package/src/color/createColorSliderState.ts +45 -14
  135. package/src/color/createColorWheelState.d.ts +42 -40
  136. package/src/color/createColorWheelState.ts +101 -64
  137. package/src/color/getColorChannels.ts +7 -5
  138. package/src/color/index.d.ts +36 -7
  139. package/src/color/index.ts +6 -9
  140. package/src/color/types.d.ts +68 -63
  141. package/src/color/types.ts +11 -19
  142. package/src/combobox/createComboBoxState.d.ts +107 -105
  143. package/src/combobox/createComboBoxState.ts +184 -89
  144. package/src/combobox/index.d.ts +10 -2
  145. package/src/combobox/index.ts +1 -1
  146. package/src/data/createAsyncList.ts +89 -63
  147. package/src/data/createListData.ts +67 -67
  148. package/src/data/createTreeData.ts +304 -91
  149. package/src/data/index.ts +3 -3
  150. package/src/datepicker/index.ts +6 -10
  151. package/src/disclosure/createDisclosureState.d.ts +47 -43
  152. package/src/disclosure/createDisclosureState.ts +7 -21
  153. package/src/disclosure/index.d.ts +10 -2
  154. package/src/disclosure/index.ts +1 -1
  155. package/src/dnd/createDragState.d.ts +51 -42
  156. package/src/dnd/createDragState.ts +12 -14
  157. package/src/dnd/createDraggableCollectionState.d.ts +52 -41
  158. package/src/dnd/createDraggableCollectionState.ts +14 -21
  159. package/src/dnd/createDropState.d.ts +64 -45
  160. package/src/dnd/createDropState.ts +22 -32
  161. package/src/dnd/createDroppableCollectionState.d.ts +88 -59
  162. package/src/dnd/createDroppableCollectionState.ts +40 -43
  163. package/src/dnd/index.d.ts +55 -6
  164. package/src/dnd/index.ts +6 -18
  165. package/src/dnd/types.d.ts +170 -157
  166. package/src/dnd/types.ts +24 -22
  167. package/src/form/createFormValidationState.d.ts +47 -45
  168. package/src/form/createFormValidationState.ts +30 -63
  169. package/src/form/index.d.ts +16 -2
  170. package/src/form/index.ts +1 -1
  171. package/src/grid/createGridState.d.ts +7 -4
  172. package/src/grid/createGridState.ts +78 -51
  173. package/src/grid/index.d.ts +3 -3
  174. package/src/grid/index.ts +2 -8
  175. package/src/grid/types.d.ts +136 -129
  176. package/src/grid/types.ts +34 -18
  177. package/src/index.d.ts +311 -26
  178. package/src/index.ts +59 -89
  179. package/src/menu/index.ts +2 -4
  180. package/src/numberfield/createNumberFieldState.d.ts +56 -54
  181. package/src/numberfield/createNumberFieldState.ts +26 -31
  182. package/src/numberfield/index.d.ts +6 -2
  183. package/src/numberfield/index.ts +1 -1
  184. package/src/overlays/createOverlayTriggerState.d.ts +22 -20
  185. package/src/overlays/createOverlayTriggerState.ts +3 -4
  186. package/src/overlays/index.d.ts +6 -2
  187. package/src/overlays/index.ts +1 -1
  188. package/src/radio/createRadioGroupState.d.ts +54 -52
  189. package/src/radio/createRadioGroupState.ts +27 -29
  190. package/src/radio/index.d.ts +7 -2
  191. package/src/radio/index.ts +1 -1
  192. package/src/searchfield/createSearchFieldState.d.ts +16 -14
  193. package/src/searchfield/createSearchFieldState.ts +5 -11
  194. package/src/searchfield/index.d.ts +3 -3
  195. package/src/searchfield/index.ts +2 -5
  196. package/src/select/createSelectState.d.ts +63 -61
  197. package/src/select/createSelectState.ts +42 -29
  198. package/src/select/index.d.ts +2 -2
  199. package/src/select/index.ts +1 -5
  200. package/src/selection/Selection.ts +28 -0
  201. package/src/selection/SelectionManager.ts +526 -0
  202. package/src/selection/createMultipleSelectionState.ts +247 -0
  203. package/src/selection/index.ts +12 -7
  204. package/src/slider/createSliderState.d.ts +60 -60
  205. package/src/slider/createSliderState.ts +55 -33
  206. package/src/slider/index.d.ts +3 -3
  207. package/src/slider/index.ts +2 -6
  208. package/src/ssr/index.d.ts +1 -1
  209. package/src/ssr/index.ts +2 -2
  210. package/src/steplist/createStepListState.ts +191 -0
  211. package/src/steplist/index.ts +5 -0
  212. package/src/table/TableCollection.d.ts +42 -40
  213. package/src/table/TableCollection.ts +266 -99
  214. package/src/table/createTableColumnResizeState.ts +242 -0
  215. package/src/table/createTableState.d.ts +7 -4
  216. package/src/table/createTableState.ts +30 -7
  217. package/src/table/createTreeGridState.ts +146 -0
  218. package/src/table/index.d.ts +14 -4
  219. package/src/table/index.ts +13 -3
  220. package/src/table/types.d.ts +98 -92
  221. package/src/table/types.ts +83 -13
  222. package/src/tabs/createTabListState.d.ts +59 -57
  223. package/src/tabs/createTabListState.ts +34 -39
  224. package/src/tabs/index.d.ts +8 -2
  225. package/src/tabs/index.ts +1 -1
  226. package/src/textfield/createTextFieldState.d.ts +16 -14
  227. package/src/textfield/createTextFieldState.ts +7 -18
  228. package/src/textfield/index.d.ts +6 -2
  229. package/src/textfield/index.ts +1 -1
  230. package/src/toast/createToastState.d.ts +92 -86
  231. package/src/toast/createToastState.ts +90 -52
  232. package/src/toast/index.d.ts +12 -2
  233. package/src/toast/index.ts +2 -1
  234. package/src/toggle/createToggleGroupState.ts +11 -21
  235. package/src/toggle/createToggleState.d.ts +19 -19
  236. package/src/toggle/createToggleState.ts +2 -16
  237. package/src/toggle/index.d.ts +2 -2
  238. package/src/toggle/index.ts +2 -6
  239. package/src/tooltip/createTooltipTriggerState.d.ts +17 -17
  240. package/src/tooltip/createTooltipTriggerState.ts +12 -11
  241. package/src/tooltip/index.d.ts +7 -2
  242. package/src/tooltip/index.ts +1 -1
  243. package/src/tree/TreeCollection.d.ts +26 -23
  244. package/src/tree/TreeCollection.ts +29 -22
  245. package/src/tree/createTreeState.d.ts +7 -4
  246. package/src/tree/createTreeState.ts +70 -41
  247. package/src/tree/index.d.ts +10 -4
  248. package/src/tree/index.ts +3 -3
  249. package/src/tree/types.d.ts +131 -126
  250. package/src/tree/types.ts +30 -13
  251. package/src/utils/index.d.ts +2 -2
  252. package/src/utils/index.ts +1 -1
  253. package/src/utils/reactivity.d.ts +2 -2
  254. package/src/utils/reactivity.ts +3 -3
@@ -5,28 +5,35 @@
5
5
  * Based on @react-stately/calendar useRangeCalendarState
6
6
  */
7
7
 
8
- import { createSignal, createMemo, type Accessor } from 'solid-js';
8
+ import { createSignal, createMemo, createEffect, type Accessor } from "solid-js";
9
9
  import {
10
+ type Calendar as InternationalizedCalendar,
10
11
  type CalendarDate,
12
+ type CalendarIdentifier,
11
13
  type DateValue,
14
+ GregorianCalendar,
12
15
  today,
13
16
  getLocalTimeZone,
17
+ isEqualCalendar,
14
18
  isSameDay,
15
19
  isSameMonth,
16
20
  startOfMonth,
17
21
  endOfMonth,
18
22
  startOfWeek,
19
23
  getWeeksInMonth,
20
- getDayOfWeek,
24
+ maxDate,
25
+ minDate,
21
26
  DateFormatter,
27
+ createCalendar as intlCreateCalendar,
28
+ toCalendar as intlToCalendar,
22
29
  toCalendarDate as intlToCalendarDate,
23
- } from '@internationalized/date';
24
- import { access, type MaybeAccessor } from '../utils';
25
- import type { ValidationState } from './createCalendarState';
30
+ } from "@internationalized/date";
31
+ import { access, type MaybeAccessor } from "../utils";
32
+ import type { CalendarDayOfWeek, ValidationState } from "./createCalendarState";
26
33
 
27
- // ============================================
28
- // TYPES
29
- // ============================================
34
+ const dayOfWeekNames: CalendarDayOfWeek[] = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"];
35
+ type RangeCalendarSelectionAlignment = "start" | "center" | "end";
36
+ type RangeCalendarPageBehavior = "single" | "visible";
30
37
 
31
38
  export interface DateRange {
32
39
  start: CalendarDate;
@@ -44,7 +51,7 @@ export interface RangeCalendarStateProps<T extends DateValue = DateValue> {
44
51
  /** The default value (uncontrolled). */
45
52
  defaultValue?: RangeValue<T> | null;
46
53
  /** Handler called when the value changes. */
47
- onChange?: (value: RangeValue<T>) => void;
54
+ onChange?: (value: RangeValue<T> | null) => void;
48
55
  /** The minimum allowed date. */
49
56
  minValue?: MaybeAccessor<DateValue | undefined>;
50
57
  /** The maximum allowed date. */
@@ -54,17 +61,27 @@ export interface RangeCalendarStateProps<T extends DateValue = DateValue> {
54
61
  /** Whether the calendar is read-only. */
55
62
  isReadOnly?: MaybeAccessor<boolean>;
56
63
  /** The date that is focused when the calendar first mounts. */
57
- focusedValue?: MaybeAccessor<DateValue | undefined>;
64
+ focusedValue?: MaybeAccessor<DateValue | null | undefined>;
58
65
  /** The default focused date (uncontrolled). */
59
66
  defaultFocusedValue?: DateValue;
60
67
  /** Handler called when the focused date changes. */
61
68
  onFocusChange?: (date: CalendarDate) => void;
62
69
  /** The locale to use for formatting. */
63
- locale?: string;
64
- /** Callback to determine if a date is unavailable. */
65
- isDateUnavailable?: (date: DateValue) => boolean;
70
+ locale?: MaybeAccessor<string | undefined>;
71
+ /** Creates a calendar object for a locale calendar identifier. */
72
+ createCalendar?: (identifier: CalendarIdentifier) => InternationalizedCalendar;
73
+ /**
74
+ * Callback that determines if a date is unavailable. The second argument is the
75
+ * current selection anchor (the first selected date), so availability can be
76
+ * derived from it. Mirrors @react-stately/calendar 1.18.
77
+ */
78
+ isDateUnavailable?: (date: DateValue, anchorDate: CalendarDate | null) => boolean;
66
79
  /** The number of months to display at once. */
67
- visibleMonths?: number;
80
+ visibleMonths?: MaybeAccessor<number | undefined>;
81
+ /** Controls how the initial visible months are aligned around the current value. */
82
+ selectionAlignment?: MaybeAccessor<RangeCalendarSelectionAlignment | undefined>;
83
+ /** Controls whether pagination moves by one month or the visible month count. */
84
+ pageBehavior?: MaybeAccessor<RangeCalendarPageBehavior | undefined>;
68
85
  /** Controls which days are disabled. */
69
86
  isDateDisabled?: (date: DateValue) => boolean;
70
87
  /** Validation state. */
@@ -84,6 +101,10 @@ export interface RangeCalendarState<T extends DateValue = DateValue> {
84
101
  focusedDate: Accessor<CalendarDate>;
85
102
  /** Sets the focused date. */
86
103
  setFocusedDate: (date: CalendarDate) => void;
104
+ /** The minimum allowed date that a user may select. */
105
+ minValue: Accessor<DateValue | null>;
106
+ /** The maximum allowed date that a user may select. */
107
+ maxValue: Accessor<DateValue | null>;
87
108
  /** The anchor date when selecting a range (first click). */
88
109
  anchorDate: Accessor<CalendarDate | null>;
89
110
  /** Sets the anchor date. */
@@ -98,8 +119,14 @@ export interface RangeCalendarState<T extends DateValue = DateValue> {
98
119
  visibleRange: Accessor<{ start: CalendarDate; end: CalendarDate }>;
99
120
  /** The timezone used for date calculations. */
100
121
  timeZone: string;
122
+ /** The locale used for formatting and locale-specific week starts. */
123
+ locale: Accessor<string>;
124
+ /** The explicitly requested first weekday, or undefined for the locale default. */
125
+ firstDayOfWeek: Accessor<CalendarDayOfWeek | undefined>;
101
126
  /** The validation state. */
102
127
  validationState: Accessor<ValidationState | undefined>;
128
+ /** Whether the current value is invalid according to validation state. */
129
+ isValueInvalid: Accessor<boolean>;
103
130
  /** Whether a date is within the selected range. */
104
131
  isSelected: (date: DateValue) => boolean;
105
132
  /** Whether a date is the start of the selection. */
@@ -124,6 +151,10 @@ export interface RangeCalendarState<T extends DateValue = DateValue> {
124
151
  focusPreviousSection: () => void;
125
152
  /** Moves focus to the next section (year). */
126
153
  focusNextSection: () => void;
154
+ /** Whether the previous visible range would be outside the allowed date range. */
155
+ isPreviousVisibleRangeInvalid: () => boolean;
156
+ /** Whether the next visible range would be outside the allowed date range. */
157
+ isNextVisibleRangeInvalid: () => boolean;
127
158
  /** Moves focus to the previous day. */
128
159
  focusPreviousDay: () => void;
129
160
  /** Moves focus to the next day. */
@@ -160,60 +191,230 @@ export interface RangeCalendarState<T extends DateValue = DateValue> {
160
191
  setDragging: (dragging: boolean) => void;
161
192
  }
162
193
 
163
- // ============================================
164
- // IMPLEMENTATION
165
- // ============================================
166
-
167
194
  /**
168
195
  * Provides state management for a range calendar component.
169
196
  */
170
197
  export function createRangeCalendarState<T extends DateValue = CalendarDate>(
171
- props: RangeCalendarStateProps<T> = {}
198
+ props: RangeCalendarStateProps<T> = {},
172
199
  ): RangeCalendarState<T> {
173
200
  const timeZone = getLocalTimeZone();
174
- const locale = props.locale ?? 'en-US';
175
- const visibleMonths = props.visibleMonths ?? 1;
201
+ const locale = createMemo(() => access(props.locale) ?? "en-US");
202
+ const minValueState = createMemo(() => access(props.minValue) ?? null);
203
+ const maxValueState = createMemo(() => access(props.maxValue) ?? null);
204
+ const resolvedOptions = createMemo(() => new DateFormatter(locale()).resolvedOptions());
205
+ const calendar = createMemo(() =>
206
+ (props.createCalendar ?? intlCreateCalendar)(resolvedOptions().calendar as CalendarIdentifier),
207
+ );
208
+ const visibleMonths = Math.max(1, Number(access(props.visibleMonths) ?? 1));
209
+ const firstDayOfWeekName = (): CalendarDayOfWeek | undefined =>
210
+ props.firstDayOfWeek == null ? undefined : dayOfWeekNames[props.firstDayOfWeek];
211
+
212
+ const toDisplayCalendarDate = (date: DateValue): CalendarDate =>
213
+ intlToCalendar(toCalendarDate(date), calendar());
214
+
215
+ // The anchor date is the first selected date during a range drag. Declared up
216
+ // here (ahead of constrainDate) because the available-range derivation below
217
+ // depends on it, and constrainDate consults that range.
218
+ const [anchorDate, setAnchorDate] = createSignal<CalendarDate | null>(null);
219
+
220
+ // Mirrors @react-stately/calendar useRangeCalendarState getAvailableRange: once
221
+ // a range selection is anchored and unavailable dates exist, contiguous ranges
222
+ // are required (unless allowsNonContiguousRanges), so the selectable span is
223
+ // bounded by the nearest unavailable date on each side of the anchor.
224
+ const getAvailableRange = (
225
+ anchor: CalendarDate | null,
226
+ ): { start?: CalendarDate; end?: CalendarDate } | null => {
227
+ if (anchor && props.isDateUnavailable && !props.allowsNonContiguousRanges) {
228
+ const predicate = (date: CalendarDate): boolean => props.isDateUnavailable!(date, anchor);
229
+ return {
230
+ start: nextUnavailableDate(anchor, predicate, visibleMonths, -1),
231
+ end: nextUnavailableDate(anchor, predicate, visibleMonths, 1),
232
+ };
233
+ }
234
+ return null;
235
+ };
236
+
237
+ const availableRange = createMemo(() => getAvailableRange(anchorDate()));
238
+
239
+ // minValue/maxValue narrowed by the available range, in display-calendar space
240
+ // (what every cell/navigation bound below compares against). Mirrors upstream's
241
+ // maxDate(minValue, availableRange?.start) / minDate(maxValue, availableRange?.end);
242
+ // maxDate/minDate ignore undefined operands, so an absent bound passes through.
243
+ const effectiveMinValue = createMemo<CalendarDate | null>(() => {
244
+ const min = access(props.minValue);
245
+ const minCal = min != null ? toDisplayCalendarDate(min) : undefined;
246
+ return maxDate(minCal, availableRange()?.start) ?? null;
247
+ });
248
+ const effectiveMaxValue = createMemo<CalendarDate | null>(() => {
249
+ const max = access(props.maxValue);
250
+ const maxCal = max != null ? toDisplayCalendarDate(max) : undefined;
251
+ return minDate(maxCal, availableRange()?.end) ?? null;
252
+ });
253
+
254
+ const constrainDate = (date: CalendarDate): CalendarDate => {
255
+ const min = effectiveMinValue();
256
+ const max = effectiveMaxValue();
257
+
258
+ let constrained = date;
259
+
260
+ if (min && constrained.compare(min) < 0) {
261
+ constrained = min;
262
+ }
263
+ if (max && constrained.compare(max) > 0) {
264
+ constrained = max;
265
+ }
266
+
267
+ return constrained;
268
+ };
269
+
270
+ const visibleRangeEndFromStart = (start: CalendarDate): CalendarDate => {
271
+ let end = endOfMonth(start);
272
+
273
+ for (let i = 1; i < visibleMonths; i++) {
274
+ end = endOfMonth(end.add({ months: 1 }));
275
+ }
276
+
277
+ return end;
278
+ };
279
+
280
+ const rawAlignStart = (date: CalendarDate): CalendarDate => startOfMonth(date);
281
+
282
+ const rawAlignEnd = (date: CalendarDate): CalendarDate =>
283
+ startOfMonth(date).subtract({ months: Math.max(visibleMonths - 1, 0) });
284
+
285
+ const constrainVisibleRangeStart = (date: CalendarDate, aligned: CalendarDate): CalendarDate => {
286
+ const minValue = access(props.minValue);
287
+ const maxValue = access(props.maxValue);
288
+ let constrained = aligned;
289
+
290
+ if (minValue && date.compare(toDisplayCalendarDate(minValue)) >= 0) {
291
+ const minStart = rawAlignStart(toDisplayCalendarDate(minValue));
292
+ if (minStart.compare(constrained) > 0) {
293
+ constrained = minStart;
294
+ }
295
+ }
296
+
297
+ if (maxValue && date.compare(toDisplayCalendarDate(maxValue)) <= 0) {
298
+ const maxStart = rawAlignEnd(toDisplayCalendarDate(maxValue));
299
+ if (maxStart.compare(constrained) < 0) {
300
+ constrained = maxStart;
301
+ }
302
+ }
303
+
304
+ return constrained;
305
+ };
306
+
307
+ const alignStart = (date: CalendarDate): CalendarDate =>
308
+ constrainVisibleRangeStart(date, rawAlignStart(date));
309
+
310
+ const alignEnd = (date: CalendarDate): CalendarDate =>
311
+ constrainVisibleRangeStart(date, rawAlignEnd(date));
312
+
313
+ const alignCenter = (date: CalendarDate): CalendarDate => {
314
+ let offset = Math.floor(visibleMonths / 2);
315
+ if (offset > 0 && visibleMonths % 2 === 0) {
316
+ offset--;
317
+ }
318
+
319
+ return constrainVisibleRangeStart(date, rawAlignStart(date).subtract({ months: offset }));
320
+ };
321
+
322
+ const defaultSelectionAlignment = (): RangeCalendarSelectionAlignment => {
323
+ const currentValue = access(props.value) ?? props.defaultValue;
324
+ if (!currentValue?.start || !currentValue?.end) {
325
+ return "center";
326
+ }
327
+
328
+ const start = alignCenter(toDisplayCalendarDate(currentValue.start));
329
+ const end = visibleRangeEndFromStart(start);
330
+ return toDisplayCalendarDate(currentValue.end).compare(end) > 0 ? "start" : "center";
331
+ };
332
+
333
+ const alignVisibleRangeStart = (date: CalendarDate): CalendarDate => {
334
+ switch (access(props.selectionAlignment) ?? defaultSelectionAlignment()) {
335
+ case "start":
336
+ return alignStart(date);
337
+ case "end":
338
+ return alignEnd(date);
339
+ case "center":
340
+ default:
341
+ return alignCenter(date);
342
+ }
343
+ };
176
344
 
177
345
  // Determine the initially focused date
178
346
  const getInitialFocusedDate = (): CalendarDate => {
179
347
  const controlledFocused = access(props.focusedValue);
180
348
  if (controlledFocused) {
181
- return toCalendarDate(controlledFocused);
349
+ return toDisplayCalendarDate(controlledFocused);
182
350
  }
183
351
  if (props.defaultFocusedValue) {
184
- return toCalendarDate(props.defaultFocusedValue);
352
+ return toDisplayCalendarDate(props.defaultFocusedValue);
185
353
  }
186
354
  const controlledValue = access(props.value);
187
355
  if (controlledValue?.start) {
188
- return toCalendarDate(controlledValue.start);
356
+ return toDisplayCalendarDate(controlledValue.start);
189
357
  }
190
358
  if (props.defaultValue?.start) {
191
- return toCalendarDate(props.defaultValue.start);
359
+ return toDisplayCalendarDate(props.defaultValue.start);
192
360
  }
193
- return today(timeZone);
361
+ return intlToCalendar(today(timeZone), calendar());
194
362
  };
195
363
 
196
364
  // State signals
197
365
  const [internalValue, setInternalValue] = createSignal<RangeValue<T> | null>(
198
- props.defaultValue ?? null
366
+ props.defaultValue ?? null,
199
367
  );
200
- const [focusedDate, setFocusedDateInternal] = createSignal<CalendarDate>(
201
- getInitialFocusedDate()
368
+ const initialFocusedDate = constrainDate(getInitialFocusedDate());
369
+ const [focusedDate, setFocusedDateInternal] = createSignal<CalendarDate>(initialFocusedDate);
370
+ const [visibleRangeStart, setVisibleRangeStart] = createSignal<CalendarDate>(
371
+ alignVisibleRangeStart(initialFocusedDate),
202
372
  );
203
- const [anchorDate, setAnchorDate] = createSignal<CalendarDate | null>(null);
204
373
  const [isFocused, setFocused] = createSignal(false);
205
374
  const [isDragging, setDragging] = createSignal(false);
206
375
 
207
376
  // Controlled vs uncontrolled value
208
- const value = createMemo<RangeValue<T> | null>(() => {
377
+ const sourceValue = createMemo<RangeValue<T> | null>(() => {
209
378
  const controlled = access(props.value);
210
379
  return controlled !== undefined ? controlled : internalValue();
211
380
  });
381
+ const value = createMemo<RangeValue<T> | null>(() => {
382
+ const currentValue = sourceValue();
383
+ if (!currentValue) {
384
+ return null;
385
+ }
386
+
387
+ return {
388
+ start: toDisplayCalendarDate(currentValue.start) as unknown as T,
389
+ end: toDisplayCalendarDate(currentValue.end) as unknown as T,
390
+ };
391
+ });
212
392
 
213
393
  // Derived states
214
394
  const isDisabled = createMemo(() => access(props.isDisabled) ?? false);
215
395
  const isReadOnly = createMemo(() => access(props.isReadOnly) ?? false);
216
396
  const validationState = createMemo(() => access(props.validationState));
397
+ // A committed range is invalid when (not mid-drag) either endpoint is
398
+ // unavailable or falls outside [minValue, maxValue]. Mirrors upstream
399
+ // useRangeCalendarState's isInvalid derivation. Calls props.isDateUnavailable
400
+ // directly (with a null anchor) rather than the closures defined further down,
401
+ // since this memo runs eagerly during init.
402
+ const isInvalidSelection = createMemo(() => {
403
+ const v = sourceValue();
404
+ if (!v || anchorDate()) return false;
405
+ if (props.isDateUnavailable?.(v.start, null) || props.isDateUnavailable?.(v.end, null)) {
406
+ return true;
407
+ }
408
+ const min = access(props.minValue);
409
+ const max = access(props.maxValue);
410
+ const minCal = min != null ? toDisplayCalendarDate(min) : null;
411
+ const maxCal = max != null ? toDisplayCalendarDate(max) : null;
412
+ return (
413
+ isDateOutsideRange(toDisplayCalendarDate(v.start), minCal, maxCal) ||
414
+ isDateOutsideRange(toDisplayCalendarDate(v.end), minCal, maxCal)
415
+ );
416
+ });
417
+ const isValueInvalid = createMemo(() => validationState() === "invalid" || isInvalidSelection());
217
418
 
218
419
  // Highlighted range during selection
219
420
  const highlightedRange = createMemo<RangeValue<CalendarDate> | null>(() => {
@@ -222,8 +423,8 @@ export function createRangeCalendarState<T extends DateValue = CalendarDate>(
222
423
  const v = value();
223
424
  if (v) {
224
425
  return {
225
- start: toCalendarDate(v.start),
226
- end: toCalendarDate(v.end),
426
+ start: toDisplayCalendarDate(v.start),
427
+ end: toDisplayCalendarDate(v.end),
227
428
  };
228
429
  }
229
430
  return null;
@@ -236,28 +437,68 @@ export function createRangeCalendarState<T extends DateValue = CalendarDate>(
236
437
  return { start: focused, end: anchor };
237
438
  });
238
439
 
239
- // Visible range based on focused date
440
+ // Visible range based on React Stately's selection alignment rules.
240
441
  const visibleRange = createMemo(() => {
241
- const focused = focusedDate();
242
- const start = startOfMonth(focused);
243
- let end = endOfMonth(focused);
442
+ const start = visibleRangeStart();
443
+ return { start, end: visibleRangeEndFromStart(start) };
444
+ });
244
445
 
245
- for (let i = 1; i < visibleMonths; i++) {
246
- end = endOfMonth(end.add({ months: 1 }));
446
+ createEffect(() => {
447
+ const controlledFocused = access(props.focusedValue);
448
+ if (!controlledFocused) {
449
+ return;
247
450
  }
248
451
 
249
- return { start, end };
452
+ const nextFocusedDate = constrainDate(toDisplayCalendarDate(controlledFocused));
453
+ const currentFocusedDate = focusedDate();
454
+
455
+ if (
456
+ nextFocusedDate.compare(currentFocusedDate) !== 0 ||
457
+ !isEqualCalendar(nextFocusedDate.calendar, currentFocusedDate.calendar)
458
+ ) {
459
+ setFocusedDateInternal(nextFocusedDate);
460
+ setVisibleRangeStart(alignVisibleRangeStart(nextFocusedDate));
461
+ }
462
+ });
463
+
464
+ createEffect(() => {
465
+ const controlledFocused = access(props.focusedValue);
466
+ if (controlledFocused) {
467
+ return;
468
+ }
469
+
470
+ const currentFocusedDate = focusedDate();
471
+ const nextFocusedDate = constrainDate(currentFocusedDate);
472
+
473
+ if (
474
+ nextFocusedDate.compare(currentFocusedDate) === 0 &&
475
+ isEqualCalendar(nextFocusedDate.calendar, currentFocusedDate.calendar)
476
+ ) {
477
+ return;
478
+ }
479
+
480
+ setFocusedDateInternal(nextFocusedDate);
481
+ setVisibleRangeStart(alignVisibleRangeStart(nextFocusedDate));
482
+ props.onFocusChange?.(nextFocusedDate);
483
+ });
484
+
485
+ createEffect(() => {
486
+ const currentFocusedDate = focusedDate();
487
+ const nextFocusedDate = toDisplayCalendarDate(currentFocusedDate);
488
+
489
+ if (isEqualCalendar(currentFocusedDate.calendar, nextFocusedDate.calendar)) {
490
+ return;
491
+ }
492
+
493
+ setFocusedDateInternal(nextFocusedDate);
494
+ setVisibleRangeStart(alignVisibleRangeStart(nextFocusedDate));
250
495
  });
251
496
 
252
497
  // Format week days for headers
253
498
  const weekDays = createMemo(() => {
254
- const formatter = new DateFormatter(locale, { weekday: 'short' });
255
- const startDay = props.firstDayOfWeek ?? 0;
499
+ const formatter = new DateFormatter(locale(), { weekday: "narrow" });
256
500
  const days: string[] = [];
257
- const base = today(timeZone);
258
-
259
- const dayOfWeek = getDayOfWeek(base, locale);
260
- const weekStart = base.subtract({ days: (dayOfWeek - startDay + 7) % 7 });
501
+ const weekStart = startOfWeek(today(timeZone), locale(), firstDayOfWeekName());
261
502
 
262
503
  for (let i = 0; i < 7; i++) {
263
504
  const day = weekStart.add({ days: i });
@@ -269,42 +510,51 @@ export function createRangeCalendarState<T extends DateValue = CalendarDate>(
269
510
 
270
511
  // Title (formatted month/year)
271
512
  const title = createMemo(() => {
272
- const formatter = new DateFormatter(locale, {
273
- month: 'long',
274
- year: 'numeric',
513
+ const formatter = new DateFormatter(locale(), {
514
+ month: "long",
515
+ year: "numeric",
516
+ calendar: focusedDate().calendar.identifier,
275
517
  });
276
- return formatter.format(focusedDate().toDate(timeZone));
518
+ const date = focusedDate();
519
+ const formattableMonth = date.calendar.getFormattableMonth?.(date) ?? date;
520
+ return formatter.format(formattableMonth.toDate(timeZone));
277
521
  });
278
522
 
279
523
  // Set value with onChange callback
280
524
  const setValue = (newValue: RangeValue<T> | null) => {
281
525
  if (isDisabled() || isReadOnly()) return;
282
526
 
527
+ const oldValue = sourceValue();
528
+ const nextValue = newValue
529
+ ? {
530
+ start: convertValue(
531
+ constrainDate(toDisplayCalendarDate(newValue.start)),
532
+ oldValue?.start,
533
+ ) as T,
534
+ end: convertValue(constrainDate(toDisplayCalendarDate(newValue.end)), oldValue?.end) as T,
535
+ }
536
+ : null;
537
+
283
538
  const controlled = access(props.value);
284
539
  if (controlled === undefined) {
285
- setInternalValue(() => newValue);
540
+ setInternalValue(() => nextValue);
286
541
  }
287
542
 
288
- if (newValue && props.onChange) {
289
- props.onChange(newValue);
543
+ if (!Object.is(oldValue, nextValue) && props.onChange) {
544
+ props.onChange(nextValue);
290
545
  }
291
546
  };
292
547
 
293
548
  // Set focused date with constraints
294
549
  const setFocusedDate = (date: CalendarDate) => {
295
- const minValue = access(props.minValue);
296
- const maxValue = access(props.maxValue);
297
-
298
- let constrained = date;
550
+ const constrained = constrainDate(date);
299
551
 
300
- if (minValue && date.compare(toCalendarDate(minValue)) < 0) {
301
- constrained = toCalendarDate(minValue);
302
- }
303
- if (maxValue && date.compare(toCalendarDate(maxValue)) > 0) {
304
- constrained = toCalendarDate(maxValue);
552
+ if (Object.is(constrained, focusedDate())) {
553
+ return;
305
554
  }
306
555
 
307
556
  setFocusedDateInternal(constrained);
557
+ setVisibleRangeStart(alignVisibleRangeStart(constrained));
308
558
  props.onFocusChange?.(constrained);
309
559
  };
310
560
 
@@ -313,34 +563,32 @@ export function createRangeCalendarState<T extends DateValue = CalendarDate>(
313
563
  const range = highlightedRange();
314
564
  if (!range) return false;
315
565
 
316
- const calDate = toCalendarDate(date);
317
- return (
318
- calDate.compare(range.start) >= 0 && calDate.compare(range.end) <= 0
319
- );
566
+ const calDate = toDisplayCalendarDate(date);
567
+ return calDate.compare(range.start) >= 0 && calDate.compare(range.end) <= 0;
320
568
  };
321
569
 
322
570
  // Check if a date is the start of the selection
323
571
  const isSelectionStart = (date: DateValue): boolean => {
324
572
  const range = highlightedRange();
325
573
  if (!range) return false;
326
- return isSameDay(toCalendarDate(date), range.start);
574
+ return isSameDay(toDisplayCalendarDate(date), range.start);
327
575
  };
328
576
 
329
577
  // Check if a date is the end of the selection
330
578
  const isSelectionEnd = (date: DateValue): boolean => {
331
579
  const range = highlightedRange();
332
580
  if (!range) return false;
333
- return isSameDay(toCalendarDate(date), range.end);
581
+ return isSameDay(toDisplayCalendarDate(date), range.end);
334
582
  };
335
583
 
336
584
  // Check if a date is focused
337
585
  const isCellFocused = (date: DateValue): boolean => {
338
- return isSameDay(toCalendarDate(date), focusedDate());
586
+ return isSameDay(toDisplayCalendarDate(date), focusedDate());
339
587
  };
340
588
 
341
589
  // Check if a date is unavailable
342
590
  const isCellUnavailable = (date: DateValue): boolean => {
343
- return props.isDateUnavailable?.(date) ?? false;
591
+ return props.isDateUnavailable?.(date, anchorDate()) ?? false;
344
592
  };
345
593
 
346
594
  // Check if a date is disabled
@@ -348,12 +596,23 @@ export function createRangeCalendarState<T extends DateValue = CalendarDate>(
348
596
  if (isDisabled()) return true;
349
597
  if (props.isDateDisabled?.(date)) return true;
350
598
 
351
- const minValue = access(props.minValue);
352
- const maxValue = access(props.maxValue);
353
- const calDate = toCalendarDate(date);
599
+ const min = effectiveMinValue();
600
+ const max = effectiveMaxValue();
601
+ const calDate = toDisplayCalendarDate(date);
354
602
 
355
- if (minValue && calDate.compare(toCalendarDate(minValue)) < 0) return true;
356
- if (maxValue && calDate.compare(toCalendarDate(maxValue)) > 0) return true;
603
+ if (min && calDate.compare(min) < 0) return true;
604
+ if (max && calDate.compare(max) > 0) return true;
605
+
606
+ return false;
607
+ };
608
+
609
+ const isDateOutsideAllowedRange = (date: DateValue): boolean => {
610
+ const min = effectiveMinValue();
611
+ const max = effectiveMaxValue();
612
+ const calDate = toDisplayCalendarDate(date);
613
+
614
+ if (min && calDate.compare(min) < 0) return true;
615
+ if (max && calDate.compare(max) > 0) return true;
357
616
 
358
617
  return false;
359
618
  };
@@ -361,7 +620,7 @@ export function createRangeCalendarState<T extends DateValue = CalendarDate>(
361
620
  // Check if a date is outside the visible range
362
621
  const isOutsideVisibleRange = (date: DateValue): boolean => {
363
622
  const range = visibleRange();
364
- const calDate = toCalendarDate(date);
623
+ const calDate = toDisplayCalendarDate(date);
365
624
  return !isSameMonth(calDate, range.start) && !isSameMonth(calDate, range.end);
366
625
  };
367
626
 
@@ -372,11 +631,13 @@ export function createRangeCalendarState<T extends DateValue = CalendarDate>(
372
631
 
373
632
  // Navigation methods
374
633
  const focusPreviousPage = () => {
375
- setFocusedDate(focusedDate().subtract({ months: 1 }));
634
+ const pageMonths = access(props.pageBehavior) === "single" ? 1 : visibleMonths;
635
+ setFocusedDate(focusedDate().subtract({ months: pageMonths }));
376
636
  };
377
637
 
378
638
  const focusNextPage = () => {
379
- setFocusedDate(focusedDate().add({ months: 1 }));
639
+ const pageMonths = access(props.pageBehavior) === "single" ? 1 : visibleMonths;
640
+ setFocusedDate(focusedDate().add({ months: pageMonths }));
380
641
  };
381
642
 
382
643
  const focusPreviousSection = () => {
@@ -387,6 +648,18 @@ export function createRangeCalendarState<T extends DateValue = CalendarDate>(
387
648
  setFocusedDate(focusedDate().add({ years: 1 }));
388
649
  };
389
650
 
651
+ const isPreviousVisibleRangeInvalid = () => {
652
+ const start = visibleRange().start;
653
+ const previous = start.subtract({ days: 1 });
654
+ return isSameDay(previous, start) || isDateOutsideAllowedRange(previous);
655
+ };
656
+
657
+ const isNextVisibleRangeInvalid = () => {
658
+ const end = visibleRange().end;
659
+ const next = end.add({ days: 1 });
660
+ return isSameDay(next, end) || isDateOutsideAllowedRange(next);
661
+ };
662
+
390
663
  const focusPreviousDay = () => {
391
664
  setFocusedDate(focusedDate().subtract({ days: 1 }));
392
665
  };
@@ -454,12 +727,12 @@ export function createRangeCalendarState<T extends DateValue = CalendarDate>(
454
727
  // Get dates in a specific week
455
728
  const getDatesInWeek = (
456
729
  weekIndex: number,
457
- monthStartDate?: CalendarDate
730
+ monthStartDate?: CalendarDate,
458
731
  ): (CalendarDate | null)[] => {
459
732
  const startDate = monthStartDate ?? visibleRange().start;
460
733
 
461
734
  const monthStart = startOfMonth(startDate);
462
- const weekStart = startOfWeek(monthStart, locale);
735
+ const weekStart = startOfWeek(monthStart, locale(), firstDayOfWeekName());
463
736
 
464
737
  const week: (CalendarDate | null)[] = [];
465
738
  const firstDayOfWeek = weekStart.add({ weeks: weekIndex });
@@ -475,7 +748,7 @@ export function createRangeCalendarState<T extends DateValue = CalendarDate>(
475
748
  // Get number of weeks in a month
476
749
  const getWeeksInMonthFn = (date?: CalendarDate): number => {
477
750
  const monthDate = date ?? focusedDate();
478
- return getWeeksInMonth(monthDate, locale);
751
+ return getWeeksInMonth(monthDate, locale(), firstDayOfWeekName());
479
752
  };
480
753
 
481
754
  return {
@@ -490,7 +763,12 @@ export function createRangeCalendarState<T extends DateValue = CalendarDate>(
490
763
  isReadOnly,
491
764
  visibleRange,
492
765
  timeZone,
766
+ locale,
767
+ minValue: minValueState,
768
+ maxValue: maxValueState,
769
+ firstDayOfWeek: firstDayOfWeekName,
493
770
  validationState,
771
+ isValueInvalid,
494
772
  isSelected,
495
773
  isSelectionStart,
496
774
  isSelectionEnd,
@@ -503,6 +781,8 @@ export function createRangeCalendarState<T extends DateValue = CalendarDate>(
503
781
  focusNextPage,
504
782
  focusPreviousSection,
505
783
  focusNextSection,
784
+ isPreviousVisibleRangeInvalid,
785
+ isNextVisibleRangeInvalid,
506
786
  focusPreviousDay,
507
787
  focusNextDay,
508
788
  focusPreviousWeek,
@@ -523,13 +803,59 @@ export function createRangeCalendarState<T extends DateValue = CalendarDate>(
523
803
  };
524
804
  }
525
805
 
526
- // ============================================
527
- // HELPER FUNCTIONS
528
- // ============================================
529
-
530
806
  /**
531
807
  * Converts a DateValue to a CalendarDate.
532
808
  */
533
809
  function toCalendarDate(date: DateValue): CalendarDate {
534
810
  return intlToCalendarDate(date);
535
811
  }
812
+
813
+ function convertValue(newValue: CalendarDate, oldValue?: DateValue | null): DateValue {
814
+ const localValue = intlToCalendar(newValue, oldValue?.calendar ?? new GregorianCalendar());
815
+
816
+ if (oldValue && "hour" in oldValue) {
817
+ return oldValue.set(localValue);
818
+ }
819
+
820
+ return localValue;
821
+ }
822
+
823
+ /** Whether a date falls outside the closed [minValue, maxValue] interval. */
824
+ function isDateOutsideRange(
825
+ date: CalendarDate,
826
+ minValue?: CalendarDate | null,
827
+ maxValue?: CalendarDate | null,
828
+ ): boolean {
829
+ return (
830
+ (minValue != null && date.compare(minValue) < 0) ||
831
+ (maxValue != null && date.compare(maxValue) > 0)
832
+ );
833
+ }
834
+
835
+ /**
836
+ * Walks outward from the anchor one day at a time in `dir` until it reaches an
837
+ * unavailable date, then returns the last available date before it — or
838
+ * undefined when no unavailable date is found within `visibleMonths` of the
839
+ * anchor (no narrowing needed on that side). Mirrors @react-stately/calendar
840
+ * nextUnavailableDate.
841
+ */
842
+ function nextUnavailableDate(
843
+ anchorDate: CalendarDate,
844
+ isDateUnavailable: (date: CalendarDate) => boolean,
845
+ visibleMonths: number,
846
+ dir: number,
847
+ ): CalendarDate | undefined {
848
+ let nextDate = anchorDate.add({ days: dir });
849
+ const minBound = anchorDate.subtract({ months: visibleMonths });
850
+ const maxBound = anchorDate.add({ months: visibleMonths });
851
+ while (
852
+ (dir < 0 ? nextDate.compare(minBound) >= 0 : nextDate.compare(maxBound) <= 0) &&
853
+ !isDateUnavailable(nextDate)
854
+ ) {
855
+ nextDate = nextDate.add({ days: dir });
856
+ }
857
+ if (isDateUnavailable(nextDate)) {
858
+ return nextDate.add({ days: -dir });
859
+ }
860
+ return undefined;
861
+ }