@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,36 +5,41 @@
5
5
  * Based on @react-stately/datepicker useDateFieldState
6
6
  */
7
7
 
8
- import { createSignal, createMemo, type Accessor } from 'solid-js';
8
+ import { createSignal, createMemo, type Accessor } from "solid-js";
9
9
  import {
10
- type CalendarDate,
10
+ CalendarDate,
11
11
  type CalendarDateTime,
12
- type ZonedDateTime,
13
12
  type DateValue,
14
13
  today,
15
14
  getLocalTimeZone,
16
15
  DateFormatter,
17
16
  toCalendarDate as intlToCalendarDate,
18
17
  toCalendarDateTime,
19
- } from '@internationalized/date';
20
- import { access, type MaybeAccessor } from '../utils';
21
- import type { ValidationState } from './createCalendarState';
22
-
23
- // ============================================
24
- // TYPES
25
- // ============================================
18
+ endOfMonth,
19
+ } from "@internationalized/date";
20
+ import { access, type MaybeAccessor } from "../utils";
21
+ import type { ValidationState } from "./createCalendarState";
22
+ import {
23
+ createFormValidationState,
24
+ DEFAULT_VALIDATION_RESULT,
25
+ VALID_VALIDITY_STATE,
26
+ type ValidationBehavior,
27
+ type ValidationFunction,
28
+ type ValidationResult,
29
+ type ValidityState,
30
+ } from "../form";
26
31
 
27
32
  export type DateSegmentType =
28
- | 'year'
29
- | 'month'
30
- | 'day'
31
- | 'hour'
32
- | 'minute'
33
- | 'second'
34
- | 'dayPeriod'
35
- | 'era'
36
- | 'timeZoneName'
37
- | 'literal';
33
+ | "year"
34
+ | "month"
35
+ | "day"
36
+ | "hour"
37
+ | "minute"
38
+ | "second"
39
+ | "dayPeriod"
40
+ | "era"
41
+ | "timeZoneName"
42
+ | "literal";
38
43
 
39
44
  export interface DateSegment {
40
45
  /** The type of segment. */
@@ -72,18 +77,26 @@ export interface DateFieldStateProps<T extends DateValue = DateValue> {
72
77
  isReadOnly?: MaybeAccessor<boolean>;
73
78
  /** Whether the field is required. */
74
79
  isRequired?: MaybeAccessor<boolean>;
80
+ /** Whether the value is invalid (controlled). */
81
+ isInvalid?: boolean;
75
82
  /** The locale to use for formatting. */
76
83
  locale?: string;
77
84
  /** The granularity of the date/time (day, hour, minute, second). */
78
- granularity?: 'day' | 'hour' | 'minute' | 'second';
85
+ granularity?: "day" | "hour" | "minute" | "second";
79
86
  /** Whether to show the hour in 12 or 24 hour format. */
80
87
  hourCycle?: 12 | 24;
81
88
  /** Whether to hide the time zone. */
82
89
  hideTimeZone?: boolean;
83
90
  /** The placeholder date (determines segment structure). */
84
91
  placeholderValue?: DateValue;
92
+ /** Whether to force leading zeroes in month/day segments. */
93
+ shouldForceLeadingZeros?: boolean;
85
94
  /** Validation state. */
86
95
  validationState?: MaybeAccessor<ValidationState | undefined>;
96
+ /** Validation behavior mode. */
97
+ validationBehavior?: ValidationBehavior;
98
+ /** Custom validation function. */
99
+ validate?: ValidationFunction<T>;
87
100
  /** Description text. */
88
101
  description?: string;
89
102
  /** Error message. */
@@ -92,6 +105,8 @@ export interface DateFieldStateProps<T extends DateValue = DateValue> {
92
105
  allowsNonContiguousRanges?: boolean;
93
106
  /** Whether to create a date or datetime. */
94
107
  createCalendar?: (name: string) => unknown;
108
+ /** Whether a date is unavailable. */
109
+ isDateUnavailable?: (date: DateValue) => boolean;
95
110
  }
96
111
 
97
112
  export interface DateFieldState<T extends DateValue = DateValue> {
@@ -124,33 +139,40 @@ export interface DateFieldState<T extends DateValue = DateValue> {
124
139
  /** The validation state. */
125
140
  validationState: Accessor<ValidationState | undefined>;
126
141
  /** The granularity. */
127
- granularity: 'day' | 'hour' | 'minute' | 'second';
142
+ granularity: "day" | "hour" | "minute" | "second";
128
143
  /** Whether the value is invalid. */
129
144
  isInvalid: Accessor<boolean>;
145
+ /** Realtime validation results, including native and custom constraints. */
146
+ realtimeValidation: Accessor<ValidationResult>;
147
+ /** Currently displayed validation results. */
148
+ displayValidation: Accessor<ValidationResult>;
149
+ /** Updates the current validation result. */
150
+ updateValidation: (result: ValidationResult) => void;
151
+ /** Resets displayed validation to valid. */
152
+ resetValidation: () => void;
153
+ /** Commits realtime validation to displayed validation. */
154
+ commitValidation: () => void;
130
155
  /** The locale. */
131
156
  locale: string;
132
157
  /** The time zone. */
133
158
  timeZone: string;
159
+ /** The default value. */
160
+ defaultValue: T | null;
134
161
  }
135
162
 
136
- // ============================================
137
- // IMPLEMENTATION
138
- // ============================================
139
-
140
163
  /**
141
164
  * Provides state management for a date field component.
142
165
  */
143
166
  export function createDateFieldState<T extends DateValue = CalendarDate>(
144
- props: DateFieldStateProps<T> = {}
167
+ props: DateFieldStateProps<T> = {},
145
168
  ): DateFieldState<T> {
146
169
  const timeZone = getLocalTimeZone();
147
- const locale = props.locale ?? 'en-US';
148
- const granularity = props.granularity ?? 'day';
170
+ const locale = props.locale ?? "en-US";
171
+ const granularity = props.granularity ?? "day";
149
172
 
150
173
  // State signals
151
- const [internalValue, setInternalValue] = createSignal<T | null>(
152
- props.defaultValue ?? null
153
- );
174
+ const initialValue = props.defaultValue ?? null;
175
+ const [internalValue, setInternalValue] = createSignal<T | null>(initialValue);
154
176
 
155
177
  // Track partial values during editing
156
178
  const [placeholderDate, setPlaceholderDate] = createSignal<Partial<DateParts>>({});
@@ -177,7 +199,7 @@ export function createDateFieldState<T extends DateValue = CalendarDate>(
177
199
  const month = parts.month ?? placeholder.month;
178
200
  const day = parts.day ?? placeholder.day;
179
201
 
180
- if (granularity === 'day') {
202
+ if (granularity === "day") {
181
203
  return intlToCalendarDate(placeholder).set({ year, month, day });
182
204
  }
183
205
 
@@ -200,21 +222,67 @@ export function createDateFieldState<T extends DateValue = CalendarDate>(
200
222
  const isDisabled = createMemo(() => access(props.isDisabled) ?? false);
201
223
  const isReadOnly = createMemo(() => access(props.isReadOnly) ?? false);
202
224
  const isRequired = createMemo(() => access(props.isRequired) ?? false);
203
- const validationState = createMemo(() => access(props.validationState));
204
-
205
- // Check if value is invalid
206
- const isInvalid = createMemo(() => {
225
+ const explicitValidationState = createMemo(() => access(props.validationState));
226
+ const validationBehavior = () => props.validationBehavior ?? "native";
227
+ const builtinValidation = createMemo<ValidationResult>(() => {
207
228
  const v = value();
208
- if (!v) return false;
229
+ if (!v) return DEFAULT_VALIDATION_RESULT;
209
230
 
210
231
  const minValue = access(props.minValue);
211
232
  const maxValue = access(props.maxValue);
233
+ const validationDetails: ValidityState = { ...VALID_VALIDITY_STATE };
234
+ const validationErrors: string[] = [];
235
+
236
+ if (minValue && v.compare(minValue) < 0) {
237
+ validationDetails.rangeUnderflow = true;
238
+ validationErrors.push("Value is below the minimum date.");
239
+ }
240
+
241
+ if (maxValue && v.compare(maxValue) > 0) {
242
+ validationDetails.rangeOverflow = true;
243
+ validationErrors.push("Value is above the maximum date.");
244
+ }
245
+
246
+ if (props.isDateUnavailable?.(v)) {
247
+ validationDetails.customError = true;
248
+ validationErrors.push("Date is unavailable.");
249
+ }
212
250
 
213
- if (minValue && v.compare(minValue) < 0) return true;
214
- if (maxValue && v.compare(maxValue) > 0) return true;
251
+ if (validationErrors.length === 0) {
252
+ return DEFAULT_VALIDATION_RESULT;
253
+ }
215
254
 
216
- return validationState() === 'invalid';
255
+ validationDetails.valid = false;
256
+ return {
257
+ isInvalid: true,
258
+ validationDetails,
259
+ validationErrors,
260
+ };
217
261
  });
262
+ const validation = createFormValidationState<T>({
263
+ get value() {
264
+ return value();
265
+ },
266
+ get isInvalid() {
267
+ return props.isInvalid;
268
+ },
269
+ get validationState() {
270
+ return explicitValidationState();
271
+ },
272
+ get validationBehavior() {
273
+ return validationBehavior();
274
+ },
275
+ get validate() {
276
+ return props.validate;
277
+ },
278
+ get builtinValidation() {
279
+ return builtinValidation();
280
+ },
281
+ });
282
+ const isInvalid = createMemo(() => validation.displayValidation().isInvalid);
283
+ const validationState = createMemo<ValidationState | undefined>(
284
+ () => explicitValidationState() ?? (isInvalid() ? "invalid" : undefined),
285
+ );
218
286
 
219
287
  // Generate segments based on granularity and locale
220
288
  const segments = createMemo<DateSegment[]>(() => {
@@ -225,20 +293,21 @@ export function createDateFieldState<T extends DateValue = CalendarDate>(
225
293
  const segs: DateSegment[] = [];
226
294
 
227
295
  // Determine format options based on granularity
296
+ const shouldForceLeadingZeros = props.shouldForceLeadingZeros === true;
228
297
  const formatOptions: Intl.DateTimeFormatOptions = {
229
- year: 'numeric',
230
- month: '2-digit',
231
- day: '2-digit',
298
+ year: "numeric",
299
+ month: shouldForceLeadingZeros ? "2-digit" : "numeric",
300
+ day: shouldForceLeadingZeros ? "2-digit" : "numeric",
232
301
  };
233
302
 
234
- if (granularity !== 'day') {
235
- formatOptions.hour = '2-digit';
236
- formatOptions.minute = '2-digit';
237
- if (granularity === 'second') {
238
- formatOptions.second = '2-digit';
303
+ if (granularity !== "day") {
304
+ formatOptions.hour = "2-digit";
305
+ formatOptions.minute = "2-digit";
306
+ if (granularity === "second") {
307
+ formatOptions.second = "2-digit";
239
308
  }
240
309
  if (props.hourCycle) {
241
- formatOptions.hourCycle = props.hourCycle === 12 ? 'h12' : 'h23';
310
+ formatOptions.hourCycle = props.hourCycle === 12 ? "h12" : "h23";
242
311
  }
243
312
  }
244
313
 
@@ -249,9 +318,9 @@ export function createDateFieldState<T extends DateValue = CalendarDate>(
249
318
  for (const part of formattedParts) {
250
319
  const type = mapPartType(part.type);
251
320
 
252
- if (type === 'literal') {
321
+ if (type === "literal") {
253
322
  segs.push({
254
- type: 'literal',
323
+ type: "literal",
255
324
  text: part.value,
256
325
  isEditable: false,
257
326
  isPlaceholder: false,
@@ -259,19 +328,20 @@ export function createDateFieldState<T extends DateValue = CalendarDate>(
259
328
  });
260
329
  } else if (type) {
261
330
  const segValue = getSegmentValue(v, type);
262
- const placeholderValue = getSegmentValue(placeholder, type);
263
331
  const partValue = parts[type as keyof DateParts];
264
332
  const hasValue = v !== null || partValue !== undefined;
265
333
 
266
334
  segs.push({
267
335
  type,
268
- text: hasValue ? part.value : getPlaceholderText(type),
336
+ text: hasValue
337
+ ? part.value
338
+ : getPlaceholderText(type, locale, granularity, shouldForceLeadingZeros),
269
339
  value: segValue ?? partValue,
270
340
  minValue: getMinValue(type),
271
341
  maxValue: getMaxValue(type, v ?? placeholder),
272
342
  isEditable: !isDisabled() && !isReadOnly(),
273
343
  isPlaceholder: !hasValue,
274
- placeholder: getPlaceholderText(type),
344
+ placeholder: getPlaceholderText(type, locale, granularity, shouldForceLeadingZeros),
275
345
  });
276
346
  }
277
347
  }
@@ -320,9 +390,7 @@ export function createDateFieldState<T extends DateValue = CalendarDate>(
320
390
  if (isDisabled() || isReadOnly()) return;
321
391
 
322
392
  const v = value();
323
- const current = v
324
- ? getSegmentValue(v, type)
325
- : placeholderDate()[type as keyof DateParts];
393
+ const current = v ? getSegmentValue(v, type) : placeholderDate()[type as keyof DateParts];
326
394
  const max = getMaxValue(type, v ?? props.placeholderValue ?? today(timeZone));
327
395
  const min = getMinValue(type);
328
396
 
@@ -335,9 +403,7 @@ export function createDateFieldState<T extends DateValue = CalendarDate>(
335
403
  if (isDisabled() || isReadOnly()) return;
336
404
 
337
405
  const v = value();
338
- const current = v
339
- ? getSegmentValue(v, type)
340
- : placeholderDate()[type as keyof DateParts];
406
+ const current = v ? getSegmentValue(v, type) : placeholderDate()[type as keyof DateParts];
341
407
  const max = getMaxValue(type, v ?? props.placeholderValue ?? today(timeZone));
342
408
  const min = getMinValue(type);
343
409
 
@@ -348,11 +414,14 @@ export function createDateFieldState<T extends DateValue = CalendarDate>(
348
414
  // Clear a segment
349
415
  const clearSegment = (type: DateSegmentType) => {
350
416
  if (isDisabled() || isReadOnly()) return;
417
+ if (type === "literal") return;
351
418
 
352
419
  const v = value();
353
420
  if (v) {
354
- // Clear entire value if any segment is cleared
355
- setValue(null);
421
+ // Reset only the target segment to its minimum value instead of clearing the entire value
422
+ const min = getMinValue(type);
423
+ const updated = updateDatePart(v, type, min);
424
+ setValue(updated as T);
356
425
  } else {
357
426
  setPlaceholderDate((prev) => {
358
427
  const next = { ...prev };
@@ -364,11 +433,37 @@ export function createDateFieldState<T extends DateValue = CalendarDate>(
364
433
 
365
434
  // Confirm placeholder value
366
435
  const confirmPlaceholder = () => {
436
+ if (isDisabled() || isReadOnly()) return;
437
+
438
+ const minValue = access(props.minValue);
439
+ const maxValue = access(props.maxValue);
440
+ const constrain = (candidate: DateValue): DateValue => {
441
+ if (minValue && candidate.compare(minValue) < 0) {
442
+ return minValue;
443
+ }
444
+ if (maxValue && candidate.compare(maxValue) > 0) {
445
+ return maxValue;
446
+ }
447
+ return candidate;
448
+ };
449
+
367
450
  const parts = placeholderDate();
368
451
  if (Object.keys(parts).length > 0) {
369
452
  const dv = dateValue();
370
453
  if (dv) {
371
- setValue(dv as T);
454
+ const candidate = constrain(dv);
455
+ // Commit even if unavailable so isInvalid can flag it; don't leave field stuck
456
+ setValue(candidate as T);
457
+ }
458
+ return;
459
+ }
460
+
461
+ const current = value();
462
+ if (current) {
463
+ const constrained = constrain(current);
464
+ // Apply min/max constraints even if unavailable; isInvalid will flag unavailable dates
465
+ if (constrained.compare(current) !== 0) {
466
+ setValue(constrained as T);
372
467
  }
373
468
  }
374
469
  };
@@ -376,19 +471,19 @@ export function createDateFieldState<T extends DateValue = CalendarDate>(
376
471
  // Format the current value
377
472
  const formatValue = (fieldOptions?: Intl.DateTimeFormatOptions): string => {
378
473
  const v = value();
379
- if (!v) return '';
474
+ if (!v) return "";
380
475
 
381
476
  const options: Intl.DateTimeFormatOptions = fieldOptions ?? {
382
- year: 'numeric',
383
- month: '2-digit',
384
- day: '2-digit',
477
+ year: "numeric",
478
+ month: props.shouldForceLeadingZeros === true ? "2-digit" : "numeric",
479
+ day: props.shouldForceLeadingZeros === true ? "2-digit" : "numeric",
385
480
  };
386
481
 
387
- if (granularity !== 'day') {
388
- options.hour = '2-digit';
389
- options.minute = '2-digit';
390
- if (granularity === 'second') {
391
- options.second = '2-digit';
482
+ if (granularity !== "day") {
483
+ options.hour = "2-digit";
484
+ options.minute = "2-digit";
485
+ if (granularity === "second") {
486
+ options.second = "2-digit";
392
487
  }
393
488
  }
394
489
 
@@ -398,6 +493,7 @@ export function createDateFieldState<T extends DateValue = CalendarDate>(
398
493
 
399
494
  return {
400
495
  value,
496
+ defaultValue: initialValue,
401
497
  dateValue,
402
498
  setValue,
403
499
  segments,
@@ -413,15 +509,16 @@ export function createDateFieldState<T extends DateValue = CalendarDate>(
413
509
  validationState,
414
510
  granularity,
415
511
  isInvalid,
512
+ realtimeValidation: validation.realtimeValidation,
513
+ displayValidation: validation.displayValidation,
514
+ updateValidation: validation.updateValidation,
515
+ resetValidation: validation.resetValidation,
516
+ commitValidation: validation.commitValidation,
416
517
  locale,
417
518
  timeZone,
418
519
  };
419
520
  }
420
521
 
421
- // ============================================
422
- // HELPER TYPES & FUNCTIONS
423
- // ============================================
424
-
425
522
  interface DateParts {
426
523
  year?: number;
427
524
  month?: number;
@@ -433,50 +530,47 @@ interface DateParts {
433
530
 
434
531
  function mapPartType(type: Intl.DateTimeFormatPartTypes): DateSegmentType | null {
435
532
  switch (type) {
436
- case 'year':
437
- return 'year';
438
- case 'month':
439
- return 'month';
440
- case 'day':
441
- return 'day';
442
- case 'hour':
443
- return 'hour';
444
- case 'minute':
445
- return 'minute';
446
- case 'second':
447
- return 'second';
448
- case 'dayPeriod':
449
- return 'dayPeriod';
450
- case 'era':
451
- return 'era';
452
- case 'timeZoneName':
453
- return 'timeZoneName';
454
- case 'literal':
455
- return 'literal';
533
+ case "year":
534
+ return "year";
535
+ case "month":
536
+ return "month";
537
+ case "day":
538
+ return "day";
539
+ case "hour":
540
+ return "hour";
541
+ case "minute":
542
+ return "minute";
543
+ case "second":
544
+ return "second";
545
+ case "dayPeriod":
546
+ return "dayPeriod";
547
+ case "era":
548
+ return "era";
549
+ case "timeZoneName":
550
+ return "timeZoneName";
551
+ case "literal":
552
+ return "literal";
456
553
  default:
457
554
  return null;
458
555
  }
459
556
  }
460
557
 
461
- function getSegmentValue(
462
- date: DateValue | null,
463
- type: DateSegmentType
464
- ): number | undefined {
558
+ function getSegmentValue(date: DateValue | null, type: DateSegmentType): number | undefined {
465
559
  if (!date) return undefined;
466
560
 
467
561
  switch (type) {
468
- case 'year':
562
+ case "year":
469
563
  return date.year;
470
- case 'month':
564
+ case "month":
471
565
  return date.month;
472
- case 'day':
566
+ case "day":
473
567
  return date.day;
474
- case 'hour':
475
- return 'hour' in date ? (date as CalendarDateTime).hour : undefined;
476
- case 'minute':
477
- return 'minute' in date ? (date as CalendarDateTime).minute : undefined;
478
- case 'second':
479
- return 'second' in date ? (date as CalendarDateTime).second : undefined;
568
+ case "hour":
569
+ return "hour" in date ? (date as CalendarDateTime).hour : undefined;
570
+ case "minute":
571
+ return "minute" in date ? (date as CalendarDateTime).minute : undefined;
572
+ case "second":
573
+ return "second" in date ? (date as CalendarDateTime).second : undefined;
480
574
  default:
481
575
  return undefined;
482
576
  }
@@ -484,17 +578,17 @@ function getSegmentValue(
484
578
 
485
579
  function getMinValue(type: DateSegmentType): number {
486
580
  switch (type) {
487
- case 'year':
581
+ case "year":
488
582
  return 1;
489
- case 'month':
583
+ case "month":
490
584
  return 1;
491
- case 'day':
585
+ case "day":
492
586
  return 1;
493
- case 'hour':
587
+ case "hour":
494
588
  return 0;
495
- case 'minute':
589
+ case "minute":
496
590
  return 0;
497
- case 'second':
591
+ case "second":
498
592
  return 0;
499
593
  default:
500
594
  return 0;
@@ -503,59 +597,100 @@ function getMinValue(type: DateSegmentType): number {
503
597
 
504
598
  function getMaxValue(type: DateSegmentType, date: DateValue): number {
505
599
  switch (type) {
506
- case 'year':
600
+ case "year":
507
601
  return 9999;
508
- case 'month':
602
+ case "month":
509
603
  return 12;
510
- case 'day':
604
+ case "day":
511
605
  // Get days in month
512
- return date.calendar.getDaysInMonth?.(date) ?? 31;
513
- case 'hour':
606
+ return endOfMonth(date).day;
607
+ case "hour":
514
608
  return 23;
515
- case 'minute':
609
+ case "minute":
516
610
  return 59;
517
- case 'second':
611
+ case "second":
518
612
  return 59;
519
613
  default:
520
614
  return 0;
521
615
  }
522
616
  }
523
617
 
524
- function getPlaceholderText(type: DateSegmentType): string {
618
+ function getPlaceholderText(
619
+ type: DateSegmentType,
620
+ locale: string,
621
+ granularity: "day" | "hour" | "minute" | "second",
622
+ shouldForceLeadingZeros = false,
623
+ ): string {
624
+ if (type === "literal") return "";
625
+ if (type === "dayPeriod") return "AM";
626
+
627
+ const formatOptions: Intl.DateTimeFormatOptions = {
628
+ year: "numeric",
629
+ month: shouldForceLeadingZeros ? "2-digit" : "numeric",
630
+ day: shouldForceLeadingZeros ? "2-digit" : "numeric",
631
+ };
632
+
633
+ if (granularity !== "day") {
634
+ formatOptions.hour = "2-digit";
635
+ formatOptions.minute = "2-digit";
636
+ if (granularity === "second") {
637
+ formatOptions.second = "2-digit";
638
+ }
639
+ }
640
+
641
+ const formatter = new DateFormatter(locale, formatOptions);
642
+ // Use a reference date with recognizable digits
643
+ const refDate = new Date(2222, 10, 22, 22, 22, 22);
644
+ const parts = formatter.formatToParts(refDate);
645
+
646
+ for (const part of parts) {
647
+ const mappedType = mapPartType(part.type);
648
+ if (mappedType === type) {
649
+ return part.value.replace(/\d/g, (d) => {
650
+ if (type === "year") return "y";
651
+ if (type === "month") return "m";
652
+ if (type === "day") return "d";
653
+ return "–";
654
+ });
655
+ }
656
+ }
657
+
525
658
  switch (type) {
526
- case 'year':
527
- return 'yyyy';
528
- case 'month':
529
- return 'mm';
530
- case 'day':
531
- return 'dd';
532
- case 'hour':
533
- return '––';
534
- case 'minute':
535
- return '––';
536
- case 'second':
537
- return '––';
538
- case 'dayPeriod':
539
- return 'AM';
659
+ case "year":
660
+ return "yyyy";
661
+ case "month":
662
+ return "mm";
663
+ case "day":
664
+ return "dd";
665
+ case "hour":
666
+ return "––";
667
+ case "minute":
668
+ return "––";
669
+ case "second":
670
+ return "––";
540
671
  default:
541
- return '';
672
+ return "";
542
673
  }
543
674
  }
544
675
 
545
676
  function updateDatePart(date: DateValue, type: DateSegmentType, value: number): DateValue {
677
+ if (typeof (date as { set?: unknown }).set !== "function") {
678
+ return date;
679
+ }
680
+
546
681
  switch (type) {
547
- case 'year':
548
- return (date as CalendarDate).set({ year: value });
549
- case 'month':
550
- return (date as CalendarDate).set({ month: value });
551
- case 'day':
552
- return (date as CalendarDate).set({ day: value });
553
- case 'hour':
554
- return (date as CalendarDateTime).set({ hour: value });
555
- case 'minute':
556
- return (date as CalendarDateTime).set({ minute: value });
557
- case 'second':
558
- return (date as CalendarDateTime).set({ second: value });
682
+ case "year":
683
+ return date.set({ year: value });
684
+ case "month":
685
+ return date.set({ month: value });
686
+ case "day":
687
+ return date.set({ day: value });
688
+ case "hour":
689
+ return date.set({ hour: value });
690
+ case "minute":
691
+ return date.set({ minute: value });
692
+ case "second":
693
+ return date.set({ second: value });
559
694
  default:
560
695
  return date;
561
696
  }