@proyecto-viviana/solid-stately 0.2.4 → 0.3.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 (243) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +30 -283
  3. package/dist/autocomplete/createAutocompleteState.d.ts +3 -2
  4. package/dist/autocomplete/index.d.ts +1 -1
  5. package/dist/calendar/createCalendarState.d.ts +22 -7
  6. package/dist/calendar/createDateFieldState.d.ts +30 -7
  7. package/dist/calendar/createDatePickerState.d.ts +60 -0
  8. package/dist/calendar/createRangeCalendarState.d.ts +27 -8
  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 +12 -3
  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 +8 -8
  15. package/dist/collections/createMenuState.d.ts +7 -7
  16. package/dist/collections/createSelectionState.d.ts +5 -5
  17. package/dist/collections/index.d.ts +5 -5
  18. package/dist/collections/types.d.ts +23 -12
  19. package/dist/color/Color.d.ts +1 -1
  20. package/dist/color/createColorAreaState.d.ts +4 -2
  21. package/dist/color/createColorFieldState.d.ts +38 -12
  22. package/dist/color/createColorSliderState.d.ts +9 -2
  23. package/dist/color/createColorWheelState.d.ts +13 -2
  24. package/dist/color/getColorChannels.d.ts +20 -0
  25. package/dist/color/index.d.ts +6 -6
  26. package/dist/color/types.d.ts +4 -4
  27. package/dist/combobox/createComboBoxState.d.ts +27 -13
  28. package/dist/combobox/index.d.ts +1 -1
  29. package/dist/data/createAsyncList.d.ts +111 -0
  30. package/dist/data/createListData.d.ts +65 -0
  31. package/dist/data/createTreeData.d.ts +65 -0
  32. package/dist/data/index.d.ts +3 -0
  33. package/dist/datepicker/index.d.ts +8 -0
  34. package/dist/disclosure/createDisclosureState.d.ts +2 -2
  35. package/dist/disclosure/index.d.ts +1 -1
  36. package/dist/dnd/createDragState.d.ts +2 -2
  37. package/dist/dnd/createDraggableCollectionState.d.ts +2 -2
  38. package/dist/dnd/createDropState.d.ts +2 -2
  39. package/dist/dnd/createDroppableCollectionState.d.ts +3 -3
  40. package/dist/dnd/index.d.ts +5 -5
  41. package/dist/dnd/types.d.ts +16 -16
  42. package/dist/form/createFormValidationState.d.ts +3 -3
  43. package/dist/form/index.d.ts +1 -1
  44. package/dist/grid/createGridState.d.ts +2 -2
  45. package/dist/grid/index.d.ts +2 -2
  46. package/dist/grid/types.d.ts +17 -11
  47. package/dist/index.d.ts +31 -25
  48. package/dist/index.js +7990 -6264
  49. package/dist/index.js.map +1 -7
  50. package/dist/menu/index.d.ts +8 -0
  51. package/dist/numberfield/createNumberFieldState.d.ts +2 -2
  52. package/dist/numberfield/index.d.ts +1 -1
  53. package/dist/overlays/createOverlayTriggerState.d.ts +2 -2
  54. package/dist/overlays/index.d.ts +1 -1
  55. package/dist/radio/createRadioGroupState.d.ts +13 -4
  56. package/dist/radio/index.d.ts +1 -1
  57. package/dist/searchfield/createSearchFieldState.d.ts +2 -2
  58. package/dist/searchfield/index.d.ts +2 -2
  59. package/dist/select/createSelectState.d.ts +20 -3
  60. package/dist/select/index.d.ts +1 -1
  61. package/dist/selection/index.d.ts +11 -0
  62. package/dist/slider/createSliderState.d.ts +3 -3
  63. package/dist/slider/index.d.ts +2 -2
  64. package/dist/steplist/createStepListState.d.ts +48 -0
  65. package/dist/steplist/index.d.ts +1 -0
  66. package/dist/table/TableCollection.d.ts +3 -3
  67. package/dist/table/createTableColumnResizeState.d.ts +54 -0
  68. package/dist/table/createTableState.d.ts +2 -2
  69. package/dist/table/index.d.ts +5 -3
  70. package/dist/table/types.d.ts +10 -10
  71. package/dist/tabs/createTabListState.d.ts +5 -5
  72. package/dist/tabs/index.d.ts +1 -1
  73. package/dist/textfield/createTextFieldState.d.ts +2 -2
  74. package/dist/textfield/index.d.ts +1 -1
  75. package/dist/toast/createToastState.d.ts +15 -3
  76. package/dist/toast/index.d.ts +1 -1
  77. package/dist/toggle/createToggleGroupState.d.ts +45 -0
  78. package/dist/toggle/createToggleState.d.ts +2 -2
  79. package/dist/toggle/index.d.ts +2 -1
  80. package/dist/tooltip/createTooltipTriggerState.d.ts +2 -2
  81. package/dist/tooltip/index.d.ts +1 -1
  82. package/dist/tree/TreeCollection.d.ts +5 -4
  83. package/dist/tree/createTreeState.d.ts +2 -2
  84. package/dist/tree/index.d.ts +3 -3
  85. package/dist/tree/types.d.ts +19 -13
  86. package/dist/utils/index.d.ts +1 -1
  87. package/dist/utils/reactivity.d.ts +1 -1
  88. package/package.json +30 -30
  89. package/src/autocomplete/createAutocompleteState.d.ts +19 -17
  90. package/src/autocomplete/createAutocompleteState.ts +25 -32
  91. package/src/autocomplete/index.d.ts +6 -2
  92. package/src/autocomplete/index.ts +1 -1
  93. package/src/calendar/createCalendarState.d.ts +139 -117
  94. package/src/calendar/createCalendarState.ts +221 -74
  95. package/src/calendar/createDateFieldState.d.ts +130 -95
  96. package/src/calendar/createDateFieldState.ts +286 -151
  97. package/src/calendar/createDatePickerState.ts +347 -0
  98. package/src/calendar/createRangeCalendarState.d.ts +154 -129
  99. package/src/calendar/createRangeCalendarState.ts +293 -79
  100. package/src/calendar/createTimeFieldState.d.ts +81 -79
  101. package/src/calendar/createTimeFieldState.ts +187 -108
  102. package/src/calendar/index.d.ts +80 -7
  103. package/src/calendar/index.ts +21 -12
  104. package/src/checkbox/createCheckboxGroupState.d.ts +57 -55
  105. package/src/checkbox/createCheckboxGroupState.ts +57 -30
  106. package/src/checkbox/index.d.ts +6 -2
  107. package/src/checkbox/index.ts +1 -1
  108. package/src/collections/ListCollection.d.ts +25 -22
  109. package/src/collections/ListCollection.ts +152 -146
  110. package/src/collections/createListState.d.ts +73 -58
  111. package/src/collections/createListState.ts +267 -264
  112. package/src/collections/createMenuState.d.ts +36 -31
  113. package/src/collections/createMenuState.ts +112 -106
  114. package/src/collections/createSelectionState.d.ts +78 -65
  115. package/src/collections/createSelectionState.ts +340 -336
  116. package/src/collections/index.d.ts +38 -6
  117. package/src/collections/index.ts +41 -46
  118. package/src/collections/types.d.ts +101 -101
  119. package/src/collections/types.ts +181 -169
  120. package/src/color/Color.d.ts +20 -5
  121. package/src/color/Color.ts +1054 -951
  122. package/src/color/createColorAreaState.d.ts +67 -65
  123. package/src/color/createColorAreaState.ts +301 -293
  124. package/src/color/createColorFieldState.d.ts +46 -44
  125. package/src/color/createColorFieldState.ts +428 -292
  126. package/src/color/createColorSliderState.d.ts +58 -56
  127. package/src/color/createColorSliderState.ts +272 -241
  128. package/src/color/createColorWheelState.d.ts +42 -40
  129. package/src/color/createColorWheelState.ts +248 -211
  130. package/src/color/getColorChannels.ts +36 -0
  131. package/src/color/index.d.ts +36 -7
  132. package/src/color/index.ts +44 -47
  133. package/src/color/types.d.ts +68 -63
  134. package/src/color/types.ts +119 -127
  135. package/src/combobox/createComboBoxState.d.ts +107 -105
  136. package/src/combobox/createComboBoxState.ts +790 -703
  137. package/src/combobox/index.d.ts +10 -2
  138. package/src/combobox/index.ts +13 -13
  139. package/src/data/createAsyncList.ts +403 -0
  140. package/src/data/createListData.ts +298 -0
  141. package/src/data/createTreeData.ts +646 -0
  142. package/src/data/index.ts +25 -0
  143. package/src/datepicker/index.ts +32 -0
  144. package/src/disclosure/createDisclosureState.d.ts +47 -43
  145. package/src/disclosure/createDisclosureState.ts +9 -23
  146. package/src/disclosure/index.d.ts +10 -2
  147. package/src/disclosure/index.ts +1 -1
  148. package/src/dnd/createDragState.d.ts +51 -42
  149. package/src/dnd/createDragState.ts +151 -153
  150. package/src/dnd/createDraggableCollectionState.d.ts +52 -41
  151. package/src/dnd/createDraggableCollectionState.ts +158 -165
  152. package/src/dnd/createDropState.d.ts +64 -45
  153. package/src/dnd/createDropState.ts +202 -212
  154. package/src/dnd/createDroppableCollectionState.d.ts +88 -59
  155. package/src/dnd/createDroppableCollectionState.ts +354 -357
  156. package/src/dnd/index.d.ts +54 -6
  157. package/src/dnd/index.ts +63 -76
  158. package/src/dnd/types.d.ts +167 -156
  159. package/src/dnd/types.ts +316 -317
  160. package/src/form/createFormValidationState.d.ts +47 -45
  161. package/src/form/createFormValidationState.ts +356 -389
  162. package/src/form/index.d.ts +16 -2
  163. package/src/form/index.ts +15 -15
  164. package/src/grid/createGridState.d.ts +7 -4
  165. package/src/grid/createGridState.ts +44 -51
  166. package/src/grid/index.d.ts +3 -3
  167. package/src/grid/index.ts +2 -8
  168. package/src/grid/types.d.ts +136 -129
  169. package/src/grid/types.ts +24 -17
  170. package/src/index.d.ts +310 -26
  171. package/src/index.ts +87 -79
  172. package/src/menu/index.ts +17 -0
  173. package/src/numberfield/createNumberFieldState.d.ts +56 -54
  174. package/src/numberfield/createNumberFieldState.ts +422 -383
  175. package/src/numberfield/index.d.ts +6 -2
  176. package/src/numberfield/index.ts +5 -5
  177. package/src/overlays/createOverlayTriggerState.d.ts +22 -20
  178. package/src/overlays/createOverlayTriggerState.ts +66 -67
  179. package/src/overlays/index.d.ts +6 -2
  180. package/src/overlays/index.ts +5 -5
  181. package/src/radio/createRadioGroupState.d.ts +54 -52
  182. package/src/radio/createRadioGroupState.ts +66 -30
  183. package/src/radio/index.d.ts +7 -2
  184. package/src/radio/index.ts +1 -1
  185. package/src/searchfield/createSearchFieldState.d.ts +16 -14
  186. package/src/searchfield/createSearchFieldState.ts +56 -62
  187. package/src/searchfield/index.d.ts +3 -3
  188. package/src/searchfield/index.ts +2 -5
  189. package/src/select/createSelectState.d.ts +63 -61
  190. package/src/select/createSelectState.ts +290 -181
  191. package/src/select/index.d.ts +2 -2
  192. package/src/select/index.ts +1 -5
  193. package/src/selection/index.ts +27 -0
  194. package/src/slider/createSliderState.d.ts +60 -60
  195. package/src/slider/createSliderState.ts +233 -211
  196. package/src/slider/index.d.ts +3 -3
  197. package/src/slider/index.ts +2 -6
  198. package/src/ssr/index.d.ts +1 -1
  199. package/src/ssr/index.ts +2 -2
  200. package/src/steplist/createStepListState.ts +191 -0
  201. package/src/steplist/index.ts +5 -0
  202. package/src/table/TableCollection.d.ts +42 -40
  203. package/src/table/TableCollection.ts +61 -64
  204. package/src/table/createTableColumnResizeState.ts +242 -0
  205. package/src/table/createTableState.d.ts +7 -4
  206. package/src/table/createTableState.ts +10 -7
  207. package/src/table/index.d.ts +14 -4
  208. package/src/table/index.ts +10 -3
  209. package/src/table/types.d.ts +98 -92
  210. package/src/table/types.ts +17 -13
  211. package/src/tabs/createTabListState.d.ts +59 -57
  212. package/src/tabs/createTabListState.ts +58 -39
  213. package/src/tabs/index.d.ts +8 -2
  214. package/src/tabs/index.ts +1 -1
  215. package/src/textfield/createTextFieldState.d.ts +16 -14
  216. package/src/textfield/createTextFieldState.ts +7 -18
  217. package/src/textfield/index.d.ts +6 -2
  218. package/src/textfield/index.ts +1 -1
  219. package/src/toast/createToastState.d.ts +92 -81
  220. package/src/toast/createToastState.ts +56 -44
  221. package/src/toast/index.d.ts +12 -2
  222. package/src/toast/index.ts +1 -1
  223. package/src/toggle/createToggleGroupState.ts +117 -0
  224. package/src/toggle/createToggleState.d.ts +19 -19
  225. package/src/toggle/createToggleState.ts +2 -16
  226. package/src/toggle/index.d.ts +2 -2
  227. package/src/toggle/index.ts +6 -4
  228. package/src/tooltip/createTooltipTriggerState.d.ts +17 -17
  229. package/src/tooltip/createTooltipTriggerState.ts +184 -183
  230. package/src/tooltip/index.d.ts +7 -2
  231. package/src/tooltip/index.ts +6 -6
  232. package/src/tree/TreeCollection.d.ts +26 -23
  233. package/src/tree/TreeCollection.ts +215 -175
  234. package/src/tree/createTreeState.d.ts +7 -4
  235. package/src/tree/createTreeState.ts +390 -392
  236. package/src/tree/index.d.ts +10 -4
  237. package/src/tree/index.ts +13 -13
  238. package/src/tree/types.d.ts +131 -126
  239. package/src/tree/types.ts +183 -174
  240. package/src/utils/index.d.ts +2 -2
  241. package/src/utils/index.ts +1 -1
  242. package/src/utils/reactivity.d.ts +2 -2
  243. package/src/utils/reactivity.ts +3 -3
@@ -5,29 +5,35 @@
5
5
  * Based on @react-stately/calendar useCalendarState
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,
21
24
  DateFormatter,
25
+ createCalendar as intlCreateCalendar,
26
+ toCalendar as intlToCalendar,
22
27
  toCalendarDate as intlToCalendarDate,
23
- } from '@internationalized/date';
24
- import { access, type MaybeAccessor } from '../utils';
28
+ } from "@internationalized/date";
29
+ import { access, type MaybeAccessor } from "../utils";
25
30
 
26
- // ============================================
27
- // TYPES
28
- // ============================================
31
+ export type ValidationState = "valid" | "invalid";
32
+ export type CalendarPageBehavior = "single" | "visible";
33
+ export type CalendarSelectionAlignment = "start" | "center" | "end";
34
+ export type CalendarDayOfWeek = "sun" | "mon" | "tue" | "wed" | "thu" | "fri" | "sat";
29
35
 
30
- export type ValidationState = 'valid' | 'invalid';
36
+ const dayOfWeekNames: CalendarDayOfWeek[] = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"];
31
37
 
32
38
  export interface CalendarStateProps<T extends DateValue = DateValue> {
33
39
  /** The current value (controlled). */
@@ -35,7 +41,7 @@ export interface CalendarStateProps<T extends DateValue = DateValue> {
35
41
  /** The default value (uncontrolled). */
36
42
  defaultValue?: T | null;
37
43
  /** Handler called when the value changes. */
38
- onChange?: (value: T) => void;
44
+ onChange?: (value: T | null) => void;
39
45
  /** The minimum allowed date. */
40
46
  minValue?: MaybeAccessor<DateValue | undefined>;
41
47
  /** The maximum allowed date. */
@@ -47,17 +53,23 @@ export interface CalendarStateProps<T extends DateValue = DateValue> {
47
53
  /** Whether dates outside the visible range are automatically focused. */
48
54
  autoFocus?: boolean;
49
55
  /** The date that is focused when the calendar first mounts. */
50
- focusedValue?: MaybeAccessor<DateValue | undefined>;
56
+ focusedValue?: MaybeAccessor<DateValue | null | undefined>;
51
57
  /** The default focused date (uncontrolled). */
52
58
  defaultFocusedValue?: DateValue;
53
59
  /** Handler called when the focused date changes. */
54
60
  onFocusChange?: (date: CalendarDate) => void;
55
61
  /** The locale to use for formatting. */
56
- locale?: string;
62
+ locale?: MaybeAccessor<string | undefined>;
63
+ /** Creates a calendar object for a locale calendar identifier. */
64
+ createCalendar?: (identifier: CalendarIdentifier) => InternationalizedCalendar;
57
65
  /** Callback that is called for each date in the calendar to determine if it is unavailable. */
58
66
  isDateUnavailable?: (date: DateValue) => boolean;
59
67
  /** The number of months to display at once. */
60
68
  visibleMonths?: number;
69
+ /** Controls whether paging advances by one month or by the visible month range. */
70
+ pageBehavior?: CalendarPageBehavior;
71
+ /** Determines how the visible months align around the initial focused date. */
72
+ selectionAlignment?: CalendarSelectionAlignment;
61
73
  /** Whether to automatically focus the calendar when it is mounted. */
62
74
  autoFocusOnMount?: boolean;
63
75
  /** Controls which days are disabled. */
@@ -87,8 +99,14 @@ export interface CalendarState<T extends DateValue = DateValue> {
87
99
  visibleRange: Accessor<{ start: CalendarDate; end: CalendarDate }>;
88
100
  /** The timezone used for date calculations. */
89
101
  timeZone: string;
102
+ /** The locale used for formatting and locale-specific week starts. */
103
+ locale: Accessor<string>;
104
+ /** The explicitly requested first weekday, or undefined for the locale default. */
105
+ firstDayOfWeek: Accessor<CalendarDayOfWeek | undefined>;
90
106
  /** The validation state. */
91
107
  validationState: Accessor<ValidationState | undefined>;
108
+ /** Whether the current value is invalid according to validation state. */
109
+ isValueInvalid: Accessor<boolean>;
92
110
  /** Whether a date is selected. */
93
111
  isSelected: (date: DateValue) => boolean;
94
112
  /** Whether a date is focused. */
@@ -143,84 +161,193 @@ export interface CalendarState<T extends DateValue = DateValue> {
143
161
  isPaginating: Accessor<boolean>;
144
162
  }
145
163
 
146
- // ============================================
147
- // IMPLEMENTATION
148
- // ============================================
149
-
150
164
  /**
151
165
  * Provides state management for a calendar component.
152
166
  */
153
167
  export function createCalendarState<T extends DateValue = CalendarDate>(
154
- props: CalendarStateProps<T> = {}
168
+ props: CalendarStateProps<T> = {},
155
169
  ): CalendarState<T> {
156
170
  const timeZone = getLocalTimeZone();
157
- const locale = props.locale ?? 'en-US';
171
+ const locale = createMemo(() => access(props.locale) ?? "en-US");
172
+ const resolvedOptions = createMemo(() => new DateFormatter(locale()).resolvedOptions());
173
+ const calendar = createMemo(() =>
174
+ (props.createCalendar ?? intlCreateCalendar)(resolvedOptions().calendar as CalendarIdentifier),
175
+ );
158
176
  const visibleMonths = props.visibleMonths ?? 1;
177
+ const firstDayOfWeekName = (): CalendarDayOfWeek | undefined =>
178
+ props.firstDayOfWeek == null ? undefined : dayOfWeekNames[props.firstDayOfWeek];
179
+
180
+ const toDisplayCalendarDate = (date: DateValue): CalendarDate =>
181
+ intlToCalendar(toCalendarDate(date), calendar());
182
+
183
+ const selectionAlignmentOffset = (): number => {
184
+ switch (props.selectionAlignment) {
185
+ case "end":
186
+ return Math.max(visibleMonths - 1, 0);
187
+ case "center": {
188
+ let halfDuration = Math.floor(visibleMonths / 2);
189
+ if (halfDuration > 0 && visibleMonths % 2 === 0) {
190
+ halfDuration--;
191
+ }
192
+ return halfDuration;
193
+ }
194
+ case "start":
195
+ default:
196
+ return 0;
197
+ }
198
+ };
199
+
200
+ const alignVisibleRangeStart = (date: CalendarDate): CalendarDate => {
201
+ const offset = selectionAlignmentOffset();
202
+ return startOfMonth(offset > 0 ? date.subtract({ months: offset }) : date);
203
+ };
204
+
205
+ const visibleRangeEnd = (start: CalendarDate): CalendarDate => {
206
+ let end = endOfMonth(start);
207
+
208
+ for (let i = 1; i < visibleMonths; i++) {
209
+ end = endOfMonth(end.add({ months: 1 }));
210
+ }
211
+
212
+ return end;
213
+ };
214
+
215
+ const constrainDate = (date: CalendarDate): CalendarDate => {
216
+ const minValue = access(props.minValue);
217
+ const maxValue = access(props.maxValue);
218
+
219
+ let constrained = date;
220
+
221
+ if (minValue && constrained.compare(toDisplayCalendarDate(minValue)) < 0) {
222
+ constrained = toDisplayCalendarDate(minValue);
223
+ }
224
+ if (maxValue && constrained.compare(toDisplayCalendarDate(maxValue)) > 0) {
225
+ constrained = toDisplayCalendarDate(maxValue);
226
+ }
227
+
228
+ return constrained;
229
+ };
159
230
 
160
231
  // Determine the initially focused date
161
232
  const getInitialFocusedDate = (): CalendarDate => {
162
233
  const controlledFocused = access(props.focusedValue);
163
234
  if (controlledFocused) {
164
- return toCalendarDate(controlledFocused);
235
+ return toDisplayCalendarDate(controlledFocused);
165
236
  }
166
237
  if (props.defaultFocusedValue) {
167
- return toCalendarDate(props.defaultFocusedValue);
238
+ return toDisplayCalendarDate(props.defaultFocusedValue);
168
239
  }
169
240
  const controlledValue = access(props.value);
170
241
  if (controlledValue) {
171
- return toCalendarDate(controlledValue);
242
+ return toDisplayCalendarDate(controlledValue);
172
243
  }
173
244
  if (props.defaultValue) {
174
- return toCalendarDate(props.defaultValue);
245
+ return toDisplayCalendarDate(props.defaultValue);
175
246
  }
176
- return today(timeZone);
247
+ return intlToCalendar(today(timeZone), calendar());
177
248
  };
178
249
 
179
250
  // State signals
180
- const [internalValue, setInternalValue] = createSignal<T | null>(
181
- props.defaultValue ?? null
182
- );
183
- const [focusedDate, setFocusedDateInternal] = createSignal<CalendarDate>(
184
- getInitialFocusedDate()
251
+ const initialFocusedDate = constrainDate(getInitialFocusedDate());
252
+ const [internalValue, setInternalValue] = createSignal<T | null>(props.defaultValue ?? null);
253
+ const [focusedDate, setFocusedDateInternal] = createSignal<CalendarDate>(initialFocusedDate);
254
+ const [visibleRangeStart, setVisibleRangeStart] = createSignal<CalendarDate>(
255
+ alignVisibleRangeStart(initialFocusedDate),
185
256
  );
186
257
  const [isFocused, setFocused] = createSignal(false);
187
258
  const [isPaginating, setIsPaginating] = createSignal(false);
188
259
 
189
260
  // Controlled vs uncontrolled value
190
- const value = createMemo<T | null>(() => {
261
+ const sourceValue = createMemo<T | null>(() => {
191
262
  const controlled = access(props.value);
192
263
  return controlled !== undefined ? controlled : internalValue();
193
264
  });
265
+ const value = createMemo<T | null>(() => {
266
+ const currentValue = sourceValue();
267
+ return currentValue ? (toDisplayCalendarDate(currentValue) as unknown as T) : null;
268
+ });
194
269
 
195
270
  // Derived states
196
271
  const isDisabled = createMemo(() => access(props.isDisabled) ?? false);
197
272
  const isReadOnly = createMemo(() => access(props.isReadOnly) ?? false);
198
273
  const validationState = createMemo(() => access(props.validationState));
274
+ const isValueInvalid = createMemo(() => validationState() === "invalid");
199
275
 
200
- // Visible range based on focused date
276
+ // Visible range based on the paged range start.
201
277
  const visibleRange = createMemo(() => {
202
- const focused = focusedDate();
203
- const start = startOfMonth(focused);
204
- let end = endOfMonth(focused);
278
+ const start = visibleRangeStart();
279
+ const end = visibleRangeEnd(start);
205
280
 
206
- // Extend for multiple visible months
207
- for (let i = 1; i < visibleMonths; i++) {
208
- end = endOfMonth(end.add({ months: 1 }));
281
+ return { start, end };
282
+ });
283
+
284
+ const syncVisibleRangeForFocusedDate = (nextFocusedDate: CalendarDate) => {
285
+ const range = visibleRange();
286
+
287
+ if (nextFocusedDate.compare(range.start) < 0) {
288
+ setVisibleRangeStart(startOfMonth(nextFocusedDate.subtract({ months: visibleMonths - 1 })));
289
+ } else if (nextFocusedDate.compare(range.end) > 0) {
290
+ setVisibleRangeStart(startOfMonth(nextFocusedDate));
209
291
  }
292
+ };
210
293
 
211
- return { start, end };
294
+ createEffect(() => {
295
+ const controlledFocused = access(props.focusedValue);
296
+ if (!controlledFocused) {
297
+ return;
298
+ }
299
+
300
+ const nextFocusedDate = constrainDate(toDisplayCalendarDate(controlledFocused));
301
+
302
+ const currentFocusedDate = focusedDate();
303
+ if (
304
+ nextFocusedDate.compare(currentFocusedDate) !== 0 ||
305
+ !isEqualCalendar(nextFocusedDate.calendar, currentFocusedDate.calendar)
306
+ ) {
307
+ setFocusedDateInternal(nextFocusedDate);
308
+ }
309
+
310
+ syncVisibleRangeForFocusedDate(nextFocusedDate);
311
+ });
312
+
313
+ createEffect(() => {
314
+ const controlledFocused = access(props.focusedValue);
315
+ if (controlledFocused) {
316
+ return;
317
+ }
318
+
319
+ const currentFocusedDate = focusedDate();
320
+ const nextFocusedDate = constrainDate(currentFocusedDate);
321
+
322
+ if (
323
+ nextFocusedDate.compare(currentFocusedDate) === 0 &&
324
+ isEqualCalendar(nextFocusedDate.calendar, currentFocusedDate.calendar)
325
+ ) {
326
+ return;
327
+ }
328
+
329
+ syncVisibleRangeForFocusedDate(nextFocusedDate);
330
+ setFocusedDateInternal(nextFocusedDate);
331
+ props.onFocusChange?.(nextFocusedDate);
332
+ });
333
+
334
+ createEffect(() => {
335
+ const currentFocusedDate = focusedDate();
336
+ const nextFocusedDate = toDisplayCalendarDate(currentFocusedDate);
337
+
338
+ if (isEqualCalendar(currentFocusedDate.calendar, nextFocusedDate.calendar)) {
339
+ return;
340
+ }
341
+
342
+ setFocusedDateInternal(nextFocusedDate);
343
+ setVisibleRangeStart(alignVisibleRangeStart(nextFocusedDate));
212
344
  });
213
345
 
214
346
  // Format week days for headers
215
347
  const weekDays = createMemo(() => {
216
- const formatter = new DateFormatter(locale, { weekday: 'short' });
217
- const startDay = props.firstDayOfWeek ?? 0;
348
+ const formatter = new DateFormatter(locale(), { weekday: "narrow" });
218
349
  const days: string[] = [];
219
- const base = today(timeZone);
220
-
221
- // Find the start of the week
222
- const dayOfWeek = getDayOfWeek(base, locale);
223
- const weekStart = base.subtract({ days: (dayOfWeek - startDay + 7) % 7 });
350
+ const weekStart = startOfWeek(today(timeZone), locale(), firstDayOfWeekName());
224
351
 
225
352
  for (let i = 0; i < 7; i++) {
226
353
  const day = weekStart.add({ days: i });
@@ -232,41 +359,44 @@ export function createCalendarState<T extends DateValue = CalendarDate>(
232
359
 
233
360
  // Title (formatted month/year)
234
361
  const title = createMemo(() => {
235
- const formatter = new DateFormatter(locale, {
236
- month: 'long',
237
- year: 'numeric',
362
+ const formatter = new DateFormatter(locale(), {
363
+ month: "long",
364
+ year: "numeric",
365
+ calendar: focusedDate().calendar.identifier,
238
366
  });
239
- return formatter.format(focusedDate().toDate(timeZone));
367
+ const date = focusedDate();
368
+ const formattableMonth = date.calendar.getFormattableMonth?.(date) ?? date;
369
+ return formatter.format(formattableMonth.toDate(timeZone));
240
370
  });
241
371
 
242
372
  // Set value with onChange callback
243
373
  const setValue = (newValue: T | null) => {
244
374
  if (isDisabled() || isReadOnly()) return;
245
375
 
376
+ const oldValue = sourceValue();
377
+ const nextValue = newValue
378
+ ? (convertValue(constrainDate(toDisplayCalendarDate(newValue)), oldValue) as T)
379
+ : null;
380
+
246
381
  const controlled = access(props.value);
247
382
  if (controlled === undefined) {
248
- setInternalValue(() => newValue);
383
+ setInternalValue(() => nextValue);
249
384
  }
250
385
 
251
- if (newValue && props.onChange) {
252
- props.onChange(newValue);
386
+ if (!Object.is(oldValue, nextValue) && props.onChange) {
387
+ props.onChange(nextValue);
253
388
  }
254
389
  };
255
390
 
256
391
  // Set focused date with constraints
257
392
  const setFocusedDate = (date: CalendarDate) => {
258
- const minValue = access(props.minValue);
259
- const maxValue = access(props.maxValue);
393
+ const constrained = constrainDate(date);
260
394
 
261
- let constrained = date;
262
-
263
- if (minValue && date.compare(toCalendarDate(minValue)) < 0) {
264
- constrained = toCalendarDate(minValue);
265
- }
266
- if (maxValue && date.compare(toCalendarDate(maxValue)) > 0) {
267
- constrained = toCalendarDate(maxValue);
395
+ if (Object.is(constrained, focusedDate())) {
396
+ return;
268
397
  }
269
398
 
399
+ syncVisibleRangeForFocusedDate(constrained);
270
400
  setFocusedDateInternal(constrained);
271
401
  props.onFocusChange?.(constrained);
272
402
  };
@@ -275,12 +405,12 @@ export function createCalendarState<T extends DateValue = CalendarDate>(
275
405
  const isSelected = (date: DateValue): boolean => {
276
406
  const v = value();
277
407
  if (!v) return false;
278
- return isSameDay(toCalendarDate(date), toCalendarDate(v));
408
+ return isSameDay(toDisplayCalendarDate(date), toDisplayCalendarDate(v));
279
409
  };
280
410
 
281
411
  // Check if a date is focused
282
412
  const isCellFocused = (date: DateValue): boolean => {
283
- return isSameDay(toCalendarDate(date), focusedDate());
413
+ return isSameDay(toDisplayCalendarDate(date), focusedDate());
284
414
  };
285
415
 
286
416
  // Check if a date is unavailable
@@ -295,10 +425,10 @@ export function createCalendarState<T extends DateValue = CalendarDate>(
295
425
 
296
426
  const minValue = access(props.minValue);
297
427
  const maxValue = access(props.maxValue);
298
- const calDate = toCalendarDate(date);
428
+ const calDate = toDisplayCalendarDate(date);
299
429
 
300
- if (minValue && calDate.compare(toCalendarDate(minValue)) < 0) return true;
301
- if (maxValue && calDate.compare(toCalendarDate(maxValue)) > 0) return true;
430
+ if (minValue && calDate.compare(toDisplayCalendarDate(minValue)) < 0) return true;
431
+ if (maxValue && calDate.compare(toDisplayCalendarDate(maxValue)) > 0) return true;
302
432
 
303
433
  return false;
304
434
  };
@@ -306,7 +436,7 @@ export function createCalendarState<T extends DateValue = CalendarDate>(
306
436
  // Check if a date is outside the visible range
307
437
  const isOutsideVisibleRange = (date: DateValue): boolean => {
308
438
  const range = visibleRange();
309
- const calDate = toCalendarDate(date);
439
+ const calDate = toDisplayCalendarDate(date);
310
440
  return !isSameMonth(calDate, range.start) && !isSameMonth(calDate, range.end);
311
441
  };
312
442
 
@@ -318,13 +448,21 @@ export function createCalendarState<T extends DateValue = CalendarDate>(
318
448
  // Navigation methods
319
449
  const focusPreviousPage = () => {
320
450
  setIsPaginating(true);
321
- setFocusedDate(focusedDate().subtract({ months: 1 }));
451
+ const pageMonths = props.pageBehavior === "single" ? 1 : visibleMonths;
452
+ const nextFocusedDate = constrainDate(focusedDate().subtract({ months: pageMonths }));
453
+ setFocusedDateInternal(nextFocusedDate);
454
+ setVisibleRangeStart(startOfMonth(visibleRangeStart().subtract({ months: pageMonths })));
455
+ props.onFocusChange?.(nextFocusedDate);
322
456
  setIsPaginating(false);
323
457
  };
324
458
 
325
459
  const focusNextPage = () => {
326
460
  setIsPaginating(true);
327
- setFocusedDate(focusedDate().add({ months: 1 }));
461
+ const pageMonths = props.pageBehavior === "single" ? 1 : visibleMonths;
462
+ const nextFocusedDate = constrainDate(focusedDate().add({ months: pageMonths }));
463
+ setFocusedDateInternal(nextFocusedDate);
464
+ setVisibleRangeStart(startOfMonth(visibleRangeStart().add({ months: pageMonths })));
465
+ props.onFocusChange?.(nextFocusedDate);
328
466
  setIsPaginating(false);
329
467
  };
330
468
 
@@ -380,12 +518,12 @@ export function createCalendarState<T extends DateValue = CalendarDate>(
380
518
  // Get dates in a specific week
381
519
  const getDatesInWeek = (
382
520
  weekIndex: number,
383
- monthStartDate?: CalendarDate
521
+ monthStartDate?: CalendarDate,
384
522
  ): (CalendarDate | null)[] => {
385
523
  const startDate = monthStartDate ?? visibleRange().start;
386
524
 
387
525
  const monthStart = startOfMonth(startDate);
388
- const weekStart = startOfWeek(monthStart, locale);
526
+ const weekStart = startOfWeek(monthStart, locale(), firstDayOfWeekName());
389
527
 
390
528
  const week: (CalendarDate | null)[] = [];
391
529
  const firstDayOfWeek = weekStart.add({ weeks: weekIndex });
@@ -406,7 +544,7 @@ export function createCalendarState<T extends DateValue = CalendarDate>(
406
544
  // Get number of weeks in a month
407
545
  const getWeeksInMonthFn = (date?: CalendarDate): number => {
408
546
  const monthDate = date ?? focusedDate();
409
- return getWeeksInMonth(monthDate, locale);
547
+ return getWeeksInMonth(monthDate, locale(), firstDayOfWeekName());
410
548
  };
411
549
 
412
550
  return {
@@ -418,7 +556,10 @@ export function createCalendarState<T extends DateValue = CalendarDate>(
418
556
  isReadOnly,
419
557
  visibleRange,
420
558
  timeZone,
559
+ locale,
560
+ firstDayOfWeek: firstDayOfWeekName,
421
561
  validationState,
562
+ isValueInvalid,
422
563
  isSelected,
423
564
  isCellFocused,
424
565
  isCellUnavailable,
@@ -448,10 +589,6 @@ export function createCalendarState<T extends DateValue = CalendarDate>(
448
589
  };
449
590
  }
450
591
 
451
- // ============================================
452
- // HELPER FUNCTIONS
453
- // ============================================
454
-
455
592
  /**
456
593
  * Converts a DateValue to a CalendarDate.
457
594
  */
@@ -459,3 +596,13 @@ function toCalendarDate(date: DateValue): CalendarDate {
459
596
  // Use the library function to convert
460
597
  return intlToCalendarDate(date);
461
598
  }
599
+
600
+ function convertValue(newValue: CalendarDate, oldValue?: DateValue | null): DateValue {
601
+ const localValue = intlToCalendar(newValue, oldValue?.calendar ?? new GregorianCalendar());
602
+
603
+ if (oldValue && "hour" in oldValue) {
604
+ return oldValue.set(localValue);
605
+ }
606
+
607
+ return localValue;
608
+ }