@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,37 +5,53 @@
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
- export interface CalendarStateProps<T extends DateValue = DateValue> {
38
+ export type CalendarSelectionMode = "single" | "multiple";
39
+ export type CalendarValueType<T, M extends CalendarSelectionMode> = M extends "single"
40
+ ? T
41
+ : readonly T[];
42
+
43
+ export interface CalendarStateProps<
44
+ T extends DateValue = DateValue,
45
+ M extends CalendarSelectionMode = "single",
46
+ > {
33
47
  /** The current value (controlled). */
34
- value?: MaybeAccessor<T | null>;
48
+ value?: MaybeAccessor<CalendarValueType<T | null, M> | undefined>;
35
49
  /** The default value (uncontrolled). */
36
- defaultValue?: T | null;
50
+ defaultValue?: CalendarValueType<T | null, M> | null;
37
51
  /** Handler called when the value changes. */
38
- onChange?: (value: T) => void;
52
+ onChange?: (value: CalendarValueType<T | null, M>) => void;
53
+ /** Whether single or multiple date selection is enabled. */
54
+ selectionMode?: MaybeAccessor<M>;
39
55
  /** The minimum allowed date. */
40
56
  minValue?: MaybeAccessor<DateValue | undefined>;
41
57
  /** The maximum allowed date. */
@@ -47,17 +63,23 @@ export interface CalendarStateProps<T extends DateValue = DateValue> {
47
63
  /** Whether dates outside the visible range are automatically focused. */
48
64
  autoFocus?: boolean;
49
65
  /** The date that is focused when the calendar first mounts. */
50
- focusedValue?: MaybeAccessor<DateValue | undefined>;
66
+ focusedValue?: MaybeAccessor<DateValue | null | undefined>;
51
67
  /** The default focused date (uncontrolled). */
52
68
  defaultFocusedValue?: DateValue;
53
69
  /** Handler called when the focused date changes. */
54
70
  onFocusChange?: (date: CalendarDate) => void;
55
71
  /** The locale to use for formatting. */
56
- locale?: string;
72
+ locale?: MaybeAccessor<string | undefined>;
73
+ /** Creates a calendar object for a locale calendar identifier. */
74
+ createCalendar?: (identifier: CalendarIdentifier) => InternationalizedCalendar;
57
75
  /** Callback that is called for each date in the calendar to determine if it is unavailable. */
58
76
  isDateUnavailable?: (date: DateValue) => boolean;
59
77
  /** The number of months to display at once. */
60
78
  visibleMonths?: number;
79
+ /** Controls whether paging advances by one month or by the visible month range. */
80
+ pageBehavior?: CalendarPageBehavior;
81
+ /** Determines how the visible months align around the initial focused date. */
82
+ selectionAlignment?: CalendarSelectionAlignment;
61
83
  /** Whether to automatically focus the calendar when it is mounted. */
62
84
  autoFocusOnMount?: boolean;
63
85
  /** Controls which days are disabled. */
@@ -70,15 +92,24 @@ export interface CalendarStateProps<T extends DateValue = DateValue> {
70
92
  firstDayOfWeek?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
71
93
  }
72
94
 
73
- export interface CalendarState<T extends DateValue = DateValue> {
74
- /** The currently selected date. */
75
- value: Accessor<T | null>;
76
- /** Sets the selected date. */
77
- setValue: (value: T | null) => void;
95
+ export interface CalendarState<
96
+ T extends DateValue = DateValue,
97
+ M extends CalendarSelectionMode = "single",
98
+ > {
99
+ /** Whether single or multiple selection is enabled. */
100
+ selectionMode: Accessor<CalendarSelectionMode>;
101
+ /** The currently selected date(s). */
102
+ value: Accessor<CalendarValueType<T | null, M>>;
103
+ /** Sets the selected date(s). */
104
+ setValue: (value: CalendarValueType<T | null, M>) => void;
78
105
  /** The currently focused date. */
79
106
  focusedDate: Accessor<CalendarDate>;
80
107
  /** Sets the focused date. */
81
108
  setFocusedDate: (date: CalendarDate) => void;
109
+ /** The minimum allowed date that a user may select. */
110
+ minValue: Accessor<DateValue | null>;
111
+ /** The maximum allowed date that a user may select. */
112
+ maxValue: Accessor<DateValue | null>;
82
113
  /** Whether the calendar is disabled. */
83
114
  isDisabled: Accessor<boolean>;
84
115
  /** Whether the calendar is read-only. */
@@ -87,8 +118,14 @@ export interface CalendarState<T extends DateValue = DateValue> {
87
118
  visibleRange: Accessor<{ start: CalendarDate; end: CalendarDate }>;
88
119
  /** The timezone used for date calculations. */
89
120
  timeZone: string;
121
+ /** The locale used for formatting and locale-specific week starts. */
122
+ locale: Accessor<string>;
123
+ /** The explicitly requested first weekday, or undefined for the locale default. */
124
+ firstDayOfWeek: Accessor<CalendarDayOfWeek | undefined>;
90
125
  /** The validation state. */
91
126
  validationState: Accessor<ValidationState | undefined>;
127
+ /** Whether the current value is invalid according to validation state. */
128
+ isValueInvalid: Accessor<boolean>;
92
129
  /** Whether a date is selected. */
93
130
  isSelected: (date: DateValue) => boolean;
94
131
  /** Whether a date is focused. */
@@ -143,84 +180,210 @@ export interface CalendarState<T extends DateValue = DateValue> {
143
180
  isPaginating: Accessor<boolean>;
144
181
  }
145
182
 
146
- // ============================================
147
- // IMPLEMENTATION
148
- // ============================================
149
-
150
183
  /**
151
184
  * Provides state management for a calendar component.
152
185
  */
153
- export function createCalendarState<T extends DateValue = CalendarDate>(
154
- props: CalendarStateProps<T> = {}
155
- ): CalendarState<T> {
186
+ export function createCalendarState<
187
+ T extends DateValue = CalendarDate,
188
+ M extends CalendarSelectionMode = "single",
189
+ >(props: CalendarStateProps<T, M> = {}): CalendarState<T, M> {
156
190
  const timeZone = getLocalTimeZone();
157
- const locale = props.locale ?? 'en-US';
191
+ const locale = createMemo(() => access(props.locale) ?? "en-US");
192
+ const minValueState = createMemo(() => access(props.minValue) ?? null);
193
+ const maxValueState = createMemo(() => access(props.maxValue) ?? null);
194
+ const selectionMode = createMemo<CalendarSelectionMode>(
195
+ () => access(props.selectionMode) ?? "single",
196
+ );
197
+ const resolvedOptions = createMemo(() => new DateFormatter(locale()).resolvedOptions());
198
+ const calendar = createMemo(() =>
199
+ (props.createCalendar ?? intlCreateCalendar)(resolvedOptions().calendar as CalendarIdentifier),
200
+ );
158
201
  const visibleMonths = props.visibleMonths ?? 1;
202
+ const firstDayOfWeekName = (): CalendarDayOfWeek | undefined =>
203
+ props.firstDayOfWeek == null ? undefined : dayOfWeekNames[props.firstDayOfWeek];
204
+
205
+ const toDisplayCalendarDate = (date: DateValue): CalendarDate =>
206
+ intlToCalendar(toCalendarDate(date), calendar());
207
+
208
+ const selectionAlignmentOffset = (): number => {
209
+ // Mirrors @react-stately/calendar useCalendarState, where selectionAlignment
210
+ // defaults to "center" (alignCenter) when not provided.
211
+ switch (props.selectionAlignment) {
212
+ case "start":
213
+ return 0;
214
+ case "end":
215
+ return Math.max(visibleMonths - 1, 0);
216
+ case "center":
217
+ default: {
218
+ let halfDuration = Math.floor(visibleMonths / 2);
219
+ if (halfDuration > 0 && visibleMonths % 2 === 0) {
220
+ halfDuration--;
221
+ }
222
+ return halfDuration;
223
+ }
224
+ }
225
+ };
226
+
227
+ const alignVisibleRangeStart = (date: CalendarDate): CalendarDate => {
228
+ const offset = selectionAlignmentOffset();
229
+ return startOfMonth(offset > 0 ? date.subtract({ months: offset }) : date);
230
+ };
231
+
232
+ const visibleRangeEnd = (start: CalendarDate): CalendarDate => {
233
+ let end = endOfMonth(start);
234
+
235
+ for (let i = 1; i < visibleMonths; i++) {
236
+ end = endOfMonth(end.add({ months: 1 }));
237
+ }
238
+
239
+ return end;
240
+ };
241
+
242
+ const constrainDate = (date: CalendarDate): CalendarDate => {
243
+ const minValue = access(props.minValue);
244
+ const maxValue = access(props.maxValue);
245
+
246
+ let constrained = date;
247
+
248
+ if (minValue && constrained.compare(toDisplayCalendarDate(minValue)) < 0) {
249
+ constrained = toDisplayCalendarDate(minValue);
250
+ }
251
+ if (maxValue && constrained.compare(toDisplayCalendarDate(maxValue)) > 0) {
252
+ constrained = toDisplayCalendarDate(maxValue);
253
+ }
254
+
255
+ return constrained;
256
+ };
159
257
 
160
258
  // Determine the initially focused date
161
259
  const getInitialFocusedDate = (): CalendarDate => {
162
260
  const controlledFocused = access(props.focusedValue);
163
261
  if (controlledFocused) {
164
- return toCalendarDate(controlledFocused);
262
+ return toDisplayCalendarDate(controlledFocused);
165
263
  }
166
264
  if (props.defaultFocusedValue) {
167
- return toCalendarDate(props.defaultFocusedValue);
265
+ return toDisplayCalendarDate(props.defaultFocusedValue);
168
266
  }
169
267
  const controlledValue = access(props.value);
170
- if (controlledValue) {
171
- return toCalendarDate(controlledValue);
268
+ const controlledFirst = Array.isArray(controlledValue) ? controlledValue[0] : controlledValue;
269
+ if (controlledFirst) {
270
+ return toDisplayCalendarDate(controlledFirst);
172
271
  }
173
- if (props.defaultValue) {
174
- return toCalendarDate(props.defaultValue);
272
+ const defaultFirst = Array.isArray(props.defaultValue)
273
+ ? props.defaultValue[0]
274
+ : props.defaultValue;
275
+ if (defaultFirst) {
276
+ return toDisplayCalendarDate(defaultFirst);
175
277
  }
176
- return today(timeZone);
278
+ return intlToCalendar(today(timeZone), calendar());
177
279
  };
178
280
 
179
281
  // State signals
180
- const [internalValue, setInternalValue] = createSignal<T | null>(
181
- props.defaultValue ?? null
282
+ const initialFocusedDate = constrainDate(getInitialFocusedDate());
283
+ const [internalValue, setInternalValue] = createSignal<T | T[] | null>(
284
+ (props.defaultValue as T | T[] | null) ?? null,
182
285
  );
183
- const [focusedDate, setFocusedDateInternal] = createSignal<CalendarDate>(
184
- getInitialFocusedDate()
286
+ const [focusedDate, setFocusedDateInternal] = createSignal<CalendarDate>(initialFocusedDate);
287
+ const [visibleRangeStart, setVisibleRangeStart] = createSignal<CalendarDate>(
288
+ alignVisibleRangeStart(initialFocusedDate),
185
289
  );
186
290
  const [isFocused, setFocused] = createSignal(false);
187
291
  const [isPaginating, setIsPaginating] = createSignal(false);
188
292
 
189
293
  // Controlled vs uncontrolled value
190
- const value = createMemo<T | null>(() => {
294
+ const sourceValue = createMemo<T | T[] | null>(() => {
191
295
  const controlled = access(props.value);
192
- return controlled !== undefined ? controlled : internalValue();
296
+ return controlled !== undefined ? (controlled as T | T[] | null) : internalValue();
297
+ });
298
+ const value = createMemo<T | T[] | null>(() => {
299
+ const currentValue = sourceValue();
300
+ if (Array.isArray(currentValue)) {
301
+ return currentValue.map((v) => toDisplayCalendarDate(v) as unknown as T);
302
+ }
303
+ return currentValue ? (toDisplayCalendarDate(currentValue) as unknown as T) : null;
193
304
  });
194
305
 
195
306
  // Derived states
196
307
  const isDisabled = createMemo(() => access(props.isDisabled) ?? false);
197
308
  const isReadOnly = createMemo(() => access(props.isReadOnly) ?? false);
198
309
  const validationState = createMemo(() => access(props.validationState));
310
+ const isValueInvalid = createMemo(() => validationState() === "invalid");
199
311
 
200
- // Visible range based on focused date
312
+ // Visible range based on the paged range start.
201
313
  const visibleRange = createMemo(() => {
202
- const focused = focusedDate();
203
- const start = startOfMonth(focused);
204
- let end = endOfMonth(focused);
314
+ const start = visibleRangeStart();
315
+ const end = visibleRangeEnd(start);
205
316
 
206
- // Extend for multiple visible months
207
- for (let i = 1; i < visibleMonths; i++) {
208
- end = endOfMonth(end.add({ months: 1 }));
317
+ return { start, end };
318
+ });
319
+
320
+ const syncVisibleRangeForFocusedDate = (nextFocusedDate: CalendarDate) => {
321
+ const range = visibleRange();
322
+
323
+ if (nextFocusedDate.compare(range.start) < 0) {
324
+ setVisibleRangeStart(startOfMonth(nextFocusedDate.subtract({ months: visibleMonths - 1 })));
325
+ } else if (nextFocusedDate.compare(range.end) > 0) {
326
+ setVisibleRangeStart(startOfMonth(nextFocusedDate));
209
327
  }
328
+ };
210
329
 
211
- return { start, end };
330
+ createEffect(() => {
331
+ const controlledFocused = access(props.focusedValue);
332
+ if (!controlledFocused) {
333
+ return;
334
+ }
335
+
336
+ const nextFocusedDate = constrainDate(toDisplayCalendarDate(controlledFocused));
337
+
338
+ const currentFocusedDate = focusedDate();
339
+ if (
340
+ nextFocusedDate.compare(currentFocusedDate) !== 0 ||
341
+ !isEqualCalendar(nextFocusedDate.calendar, currentFocusedDate.calendar)
342
+ ) {
343
+ setFocusedDateInternal(nextFocusedDate);
344
+ }
345
+
346
+ syncVisibleRangeForFocusedDate(nextFocusedDate);
347
+ });
348
+
349
+ createEffect(() => {
350
+ const controlledFocused = access(props.focusedValue);
351
+ if (controlledFocused) {
352
+ return;
353
+ }
354
+
355
+ const currentFocusedDate = focusedDate();
356
+ const nextFocusedDate = constrainDate(currentFocusedDate);
357
+
358
+ if (
359
+ nextFocusedDate.compare(currentFocusedDate) === 0 &&
360
+ isEqualCalendar(nextFocusedDate.calendar, currentFocusedDate.calendar)
361
+ ) {
362
+ return;
363
+ }
364
+
365
+ syncVisibleRangeForFocusedDate(nextFocusedDate);
366
+ setFocusedDateInternal(nextFocusedDate);
367
+ props.onFocusChange?.(nextFocusedDate);
368
+ });
369
+
370
+ createEffect(() => {
371
+ const currentFocusedDate = focusedDate();
372
+ const nextFocusedDate = toDisplayCalendarDate(currentFocusedDate);
373
+
374
+ if (isEqualCalendar(currentFocusedDate.calendar, nextFocusedDate.calendar)) {
375
+ return;
376
+ }
377
+
378
+ setFocusedDateInternal(nextFocusedDate);
379
+ setVisibleRangeStart(alignVisibleRangeStart(nextFocusedDate));
212
380
  });
213
381
 
214
382
  // Format week days for headers
215
383
  const weekDays = createMemo(() => {
216
- const formatter = new DateFormatter(locale, { weekday: 'short' });
217
- const startDay = props.firstDayOfWeek ?? 0;
384
+ const formatter = new DateFormatter(locale(), { weekday: "narrow" });
218
385
  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 });
386
+ const weekStart = startOfWeek(today(timeZone), locale(), firstDayOfWeekName());
224
387
 
225
388
  for (let i = 0; i < 7; i++) {
226
389
  const day = weekStart.add({ days: i });
@@ -232,41 +395,53 @@ export function createCalendarState<T extends DateValue = CalendarDate>(
232
395
 
233
396
  // Title (formatted month/year)
234
397
  const title = createMemo(() => {
235
- const formatter = new DateFormatter(locale, {
236
- month: 'long',
237
- year: 'numeric',
398
+ const formatter = new DateFormatter(locale(), {
399
+ month: "long",
400
+ year: "numeric",
401
+ calendar: focusedDate().calendar.identifier,
238
402
  });
239
- return formatter.format(focusedDate().toDate(timeZone));
403
+ const date = focusedDate();
404
+ const formattableMonth = date.calendar.getFormattableMonth?.(date) ?? date;
405
+ return formatter.format(formattableMonth.toDate(timeZone));
240
406
  });
241
407
 
242
408
  // Set value with onChange callback
243
- const setValue = (newValue: T | null) => {
409
+ const setValue = (newValue: T | T[] | null) => {
244
410
  if (isDisabled() || isReadOnly()) return;
245
411
 
412
+ const oldValue = sourceValue();
413
+ const referenceValue = Array.isArray(oldValue) ? (oldValue[0] ?? null) : oldValue;
414
+
415
+ let nextValue: T | T[] | null;
416
+ if (newValue == null) {
417
+ nextValue = selectionMode() === "multiple" ? ([] as T[]) : null;
418
+ } else if (Array.isArray(newValue)) {
419
+ nextValue = newValue.map(
420
+ (v) => convertValue(constrainDate(toDisplayCalendarDate(v)), referenceValue) as T,
421
+ );
422
+ } else {
423
+ nextValue = convertValue(constrainDate(toDisplayCalendarDate(newValue)), referenceValue) as T;
424
+ }
425
+
246
426
  const controlled = access(props.value);
247
427
  if (controlled === undefined) {
248
- setInternalValue(() => newValue);
428
+ setInternalValue(() => nextValue);
249
429
  }
250
430
 
251
- if (newValue && props.onChange) {
252
- props.onChange(newValue);
431
+ if (!Object.is(oldValue, nextValue) && props.onChange) {
432
+ props.onChange(nextValue as CalendarValueType<T | null, M>);
253
433
  }
254
434
  };
255
435
 
256
436
  // Set focused date with constraints
257
437
  const setFocusedDate = (date: CalendarDate) => {
258
- const minValue = access(props.minValue);
259
- const maxValue = access(props.maxValue);
260
-
261
- let constrained = date;
438
+ const constrained = constrainDate(date);
262
439
 
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);
440
+ if (Object.is(constrained, focusedDate())) {
441
+ return;
268
442
  }
269
443
 
444
+ syncVisibleRangeForFocusedDate(constrained);
270
445
  setFocusedDateInternal(constrained);
271
446
  props.onFocusChange?.(constrained);
272
447
  };
@@ -274,13 +449,17 @@ export function createCalendarState<T extends DateValue = CalendarDate>(
274
449
  // Check if a date is selected
275
450
  const isSelected = (date: DateValue): boolean => {
276
451
  const v = value();
277
- if (!v) return false;
278
- return isSameDay(toCalendarDate(date), toCalendarDate(v));
452
+ if (v == null) return false;
453
+ const target = toDisplayCalendarDate(date);
454
+ if (Array.isArray(v)) {
455
+ return v.some((d) => isSameDay(target, toDisplayCalendarDate(d)));
456
+ }
457
+ return isSameDay(target, toDisplayCalendarDate(v));
279
458
  };
280
459
 
281
460
  // Check if a date is focused
282
461
  const isCellFocused = (date: DateValue): boolean => {
283
- return isSameDay(toCalendarDate(date), focusedDate());
462
+ return isSameDay(toDisplayCalendarDate(date), focusedDate());
284
463
  };
285
464
 
286
465
  // Check if a date is unavailable
@@ -295,10 +474,10 @@ export function createCalendarState<T extends DateValue = CalendarDate>(
295
474
 
296
475
  const minValue = access(props.minValue);
297
476
  const maxValue = access(props.maxValue);
298
- const calDate = toCalendarDate(date);
477
+ const calDate = toDisplayCalendarDate(date);
299
478
 
300
- if (minValue && calDate.compare(toCalendarDate(minValue)) < 0) return true;
301
- if (maxValue && calDate.compare(toCalendarDate(maxValue)) > 0) return true;
479
+ if (minValue && calDate.compare(toDisplayCalendarDate(minValue)) < 0) return true;
480
+ if (maxValue && calDate.compare(toDisplayCalendarDate(maxValue)) > 0) return true;
302
481
 
303
482
  return false;
304
483
  };
@@ -306,7 +485,7 @@ export function createCalendarState<T extends DateValue = CalendarDate>(
306
485
  // Check if a date is outside the visible range
307
486
  const isOutsideVisibleRange = (date: DateValue): boolean => {
308
487
  const range = visibleRange();
309
- const calDate = toCalendarDate(date);
488
+ const calDate = toDisplayCalendarDate(date);
310
489
  return !isSameMonth(calDate, range.start) && !isSameMonth(calDate, range.end);
311
490
  };
312
491
 
@@ -318,13 +497,21 @@ export function createCalendarState<T extends DateValue = CalendarDate>(
318
497
  // Navigation methods
319
498
  const focusPreviousPage = () => {
320
499
  setIsPaginating(true);
321
- setFocusedDate(focusedDate().subtract({ months: 1 }));
500
+ const pageMonths = props.pageBehavior === "single" ? 1 : visibleMonths;
501
+ const nextFocusedDate = constrainDate(focusedDate().subtract({ months: pageMonths }));
502
+ setFocusedDateInternal(nextFocusedDate);
503
+ setVisibleRangeStart(startOfMonth(visibleRangeStart().subtract({ months: pageMonths })));
504
+ props.onFocusChange?.(nextFocusedDate);
322
505
  setIsPaginating(false);
323
506
  };
324
507
 
325
508
  const focusNextPage = () => {
326
509
  setIsPaginating(true);
327
- setFocusedDate(focusedDate().add({ months: 1 }));
510
+ const pageMonths = props.pageBehavior === "single" ? 1 : visibleMonths;
511
+ const nextFocusedDate = constrainDate(focusedDate().add({ months: pageMonths }));
512
+ setFocusedDateInternal(nextFocusedDate);
513
+ setVisibleRangeStart(startOfMonth(visibleRangeStart().add({ months: pageMonths })));
514
+ props.onFocusChange?.(nextFocusedDate);
328
515
  setIsPaginating(false);
329
516
  };
330
517
 
@@ -364,28 +551,43 @@ export function createCalendarState<T extends DateValue = CalendarDate>(
364
551
  const selectFocusedDate = () => {
365
552
  if (isReadOnly() || isDisabled()) return;
366
553
  const date = focusedDate();
367
- if (!isCellDisabled(date) && !isCellUnavailable(date)) {
368
- setValue(date as unknown as T);
554
+ if (!isCellUnavailable(date)) {
555
+ selectDate(date);
369
556
  }
370
557
  };
371
558
 
372
559
  const selectDate = (date: CalendarDate) => {
373
560
  if (isReadOnly() || isDisabled()) return;
374
- if (!isCellDisabled(date) && !isCellUnavailable(date)) {
561
+ if (isCellDisabled(date) || isCellUnavailable(date)) return;
562
+
563
+ if (selectionMode() === "multiple") {
564
+ const current = value();
565
+ const base: CalendarDate[] = Array.isArray(current)
566
+ ? current.map((v) => toDisplayCalendarDate(v))
567
+ : current != null
568
+ ? [toDisplayCalendarDate(current)]
569
+ : [];
570
+ const target = toDisplayCalendarDate(date);
571
+ const index = base.findIndex((d) => isSameDay(d, target));
572
+ const nextValue =
573
+ index >= 0 ? [...base.slice(0, index), ...base.slice(index + 1)] : [...base, target];
574
+ setValue(nextValue as unknown as T[]);
575
+ } else {
375
576
  setValue(date as unknown as T);
376
- setFocusedDate(date);
377
577
  }
578
+
579
+ setFocusedDate(date);
378
580
  };
379
581
 
380
582
  // Get dates in a specific week
381
583
  const getDatesInWeek = (
382
584
  weekIndex: number,
383
- monthStartDate?: CalendarDate
585
+ monthStartDate?: CalendarDate,
384
586
  ): (CalendarDate | null)[] => {
385
587
  const startDate = monthStartDate ?? visibleRange().start;
386
588
 
387
589
  const monthStart = startOfMonth(startDate);
388
- const weekStart = startOfWeek(monthStart, locale);
590
+ const weekStart = startOfWeek(monthStart, locale(), firstDayOfWeekName());
389
591
 
390
592
  const week: (CalendarDate | null)[] = [];
391
593
  const firstDayOfWeek = weekStart.add({ weeks: weekIndex });
@@ -406,19 +608,25 @@ export function createCalendarState<T extends DateValue = CalendarDate>(
406
608
  // Get number of weeks in a month
407
609
  const getWeeksInMonthFn = (date?: CalendarDate): number => {
408
610
  const monthDate = date ?? focusedDate();
409
- return getWeeksInMonth(monthDate, locale);
611
+ return getWeeksInMonth(monthDate, locale(), firstDayOfWeekName());
410
612
  };
411
613
 
412
614
  return {
413
- value,
414
- setValue,
615
+ selectionMode,
616
+ value: value as unknown as Accessor<CalendarValueType<T | null, M>>,
617
+ setValue: setValue as unknown as (value: CalendarValueType<T | null, M>) => void,
415
618
  focusedDate,
416
619
  setFocusedDate,
417
620
  isDisabled,
418
621
  isReadOnly,
419
622
  visibleRange,
420
623
  timeZone,
624
+ locale,
625
+ minValue: minValueState,
626
+ maxValue: maxValueState,
627
+ firstDayOfWeek: firstDayOfWeekName,
421
628
  validationState,
629
+ isValueInvalid,
422
630
  isSelected,
423
631
  isCellFocused,
424
632
  isCellUnavailable,
@@ -448,10 +656,6 @@ export function createCalendarState<T extends DateValue = CalendarDate>(
448
656
  };
449
657
  }
450
658
 
451
- // ============================================
452
- // HELPER FUNCTIONS
453
- // ============================================
454
-
455
659
  /**
456
660
  * Converts a DateValue to a CalendarDate.
457
661
  */
@@ -459,3 +663,13 @@ function toCalendarDate(date: DateValue): CalendarDate {
459
663
  // Use the library function to convert
460
664
  return intlToCalendarDate(date);
461
665
  }
666
+
667
+ function convertValue(newValue: CalendarDate, oldValue?: DateValue | null): DateValue {
668
+ const localValue = intlToCalendar(newValue, oldValue?.calendar ?? new GregorianCalendar());
669
+
670
+ if (oldValue && "hour" in oldValue) {
671
+ return oldValue.set(localValue);
672
+ }
673
+
674
+ return localValue;
675
+ }