@proyecto-viviana/solid-stately 0.3.0 → 0.4.1

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 (77) hide show
  1. package/dist/calendar/IncompleteDate.d.ts +61 -0
  2. package/dist/calendar/createCalendarState.d.ts +20 -10
  3. package/dist/calendar/createDateFieldState.d.ts +65 -20
  4. package/dist/calendar/createRangeCalendarState.d.ts +20 -2
  5. package/dist/calendar/createTimeFieldState.d.ts +38 -67
  6. package/dist/calendar/index.d.ts +1 -1
  7. package/dist/calendar/intl/index.d.ts +10 -0
  8. package/dist/collections/ListCollection.d.ts +8 -0
  9. package/dist/collections/createListState.d.ts +16 -1
  10. package/dist/collections/createSelectionState.d.ts +17 -5
  11. package/dist/collections/getChildNodes.d.ts +19 -0
  12. package/dist/collections/index.d.ts +3 -2
  13. package/dist/collections/types.d.ts +9 -0
  14. package/dist/color/intl/index.d.ts +14 -0
  15. package/dist/combobox/createComboBoxState.d.ts +7 -0
  16. package/dist/dnd/createDroppableCollectionState.d.ts +17 -2
  17. package/dist/dnd/index.d.ts +1 -1
  18. package/dist/dnd/types.d.ts +3 -1
  19. package/dist/grid/types.d.ts +17 -3
  20. package/dist/index.d.ts +6 -6
  21. package/dist/index.js +4372 -1087
  22. package/dist/index.js.map +1 -1
  23. package/dist/select/createSelectState.d.ts +6 -0
  24. package/dist/selection/Selection.d.ts +16 -0
  25. package/dist/selection/SelectionManager.d.ts +119 -0
  26. package/dist/selection/createMultipleSelectionState.d.ts +59 -0
  27. package/dist/selection/index.d.ts +1 -0
  28. package/dist/table/TableCollection.d.ts +30 -0
  29. package/dist/table/createTreeGridState.d.ts +15 -0
  30. package/dist/table/index.d.ts +2 -1
  31. package/dist/table/types.d.ts +58 -0
  32. package/dist/toast/createToastState.d.ts +16 -0
  33. package/dist/toast/index.d.ts +1 -1
  34. package/dist/tree/types.d.ts +14 -6
  35. package/package.json +1 -1
  36. package/src/calendar/IncompleteDate.ts +430 -0
  37. package/src/calendar/createCalendarState.ts +129 -40
  38. package/src/calendar/createDateFieldState.ts +633 -477
  39. package/src/calendar/createRangeCalendarState.ts +343 -30
  40. package/src/calendar/createTimeFieldState.ts +179 -467
  41. package/src/calendar/index.ts +2 -0
  42. package/src/calendar/intl/index.ts +364 -0
  43. package/src/collections/ListCollection.ts +31 -0
  44. package/src/collections/createListState.ts +139 -17
  45. package/src/collections/createMenuState.ts +3 -0
  46. package/src/collections/createSelectionState.ts +38 -28
  47. package/src/collections/getChildNodes.ts +129 -0
  48. package/src/collections/index.ts +12 -0
  49. package/src/collections/types.ts +10 -0
  50. package/src/color/Color.ts +104 -133
  51. package/src/color/intl/index.ts +1409 -0
  52. package/src/combobox/createComboBoxState.ts +38 -13
  53. package/src/dnd/createDroppableCollectionState.ts +125 -6
  54. package/src/dnd/index.d.ts +1 -0
  55. package/src/dnd/index.ts +1 -0
  56. package/src/dnd/types.d.ts +3 -1
  57. package/src/dnd/types.ts +4 -1
  58. package/src/grid/createGridState.ts +36 -2
  59. package/src/grid/types.ts +17 -3
  60. package/src/index.d.ts +1 -0
  61. package/src/index.ts +14 -0
  62. package/src/select/createSelectState.ts +15 -2
  63. package/src/selection/Selection.ts +28 -0
  64. package/src/selection/SelectionManager.ts +541 -0
  65. package/src/selection/createMultipleSelectionState.ts +247 -0
  66. package/src/selection/index.ts +6 -0
  67. package/src/steplist/createStepListState.ts +27 -4
  68. package/src/table/TableCollection.ts +213 -43
  69. package/src/table/createTableState.ts +20 -0
  70. package/src/table/createTreeGridState.ts +146 -0
  71. package/src/table/index.ts +3 -0
  72. package/src/table/types.ts +66 -0
  73. package/src/tabs/createTabListState.ts +11 -9
  74. package/src/toast/createToastState.ts +43 -10
  75. package/src/toast/index.ts +1 -0
  76. package/src/tree/createTreeState.ts +37 -6
  77. package/src/tree/types.ts +14 -6
@@ -0,0 +1,430 @@
1
+ /**
2
+ * IncompleteDate for Solid-Stately
3
+ *
4
+ * Represents a date that is incomplete or otherwise invalid as a result of user
5
+ * editing (for example February 31st, if the day is edited before the month).
6
+ * This lets a date field hold an invalid intermediate value while typing and
7
+ * only constrain it on blur, matching upstream behavior.
8
+ *
9
+ * Based on @react-stately/datepicker IncompleteDate
10
+ */
11
+
12
+ import {
13
+ type AnyDateTime,
14
+ type Calendar,
15
+ CalendarDate,
16
+ type DateValue,
17
+ ZonedDateTime,
18
+ } from "@internationalized/date";
19
+ import type { DateSegmentType } from "./createDateFieldState";
20
+
21
+ type HourCycle = "h12" | "h11" | "h23" | "h24";
22
+
23
+ /**
24
+ * This class represents a date that is incomplete or otherwise invalid as a result of user editing.
25
+ * For example, it can represent temporary dates such as February 31st if the user edits the day
26
+ * before the month. Times are represented according to an hour cycle rather than always in 24 hour
27
+ * time. This enables the user to adjust the day period (e.g. am/pm) independently from the hour.
28
+ */
29
+ export class IncompleteDate {
30
+ calendar: Calendar;
31
+ era: string | null;
32
+ year: number | null;
33
+ month: number | null;
34
+ day: number | null;
35
+ hour: number | null;
36
+ hourCycle: HourCycle;
37
+ dayPeriod: number | null;
38
+ minute: number | null;
39
+ second: number | null;
40
+ millisecond: number | null;
41
+ offset: number | null;
42
+
43
+ constructor(
44
+ calendar: Calendar,
45
+ hourCycle: HourCycle,
46
+ dateValue?: Partial<Omit<AnyDateTime, "copy">> | null,
47
+ ) {
48
+ this.era = dateValue?.era ?? null;
49
+ this.calendar = calendar;
50
+ this.year = dateValue?.year ?? null;
51
+ this.month = dateValue?.month ?? null;
52
+ this.day = dateValue?.day ?? null;
53
+ this.hour = dateValue?.hour ?? null;
54
+ this.hourCycle = hourCycle;
55
+ this.dayPeriod = null;
56
+ this.minute = dateValue?.minute ?? null;
57
+ this.second = dateValue?.second ?? null;
58
+ this.millisecond = dateValue?.millisecond ?? null;
59
+ this.offset = "offset" in (dateValue ?? {}) ? (dateValue as { offset: number }).offset : null;
60
+
61
+ // Convert the hour from 24 hour time to the given hour cycle.
62
+ if (this.hour != null) {
63
+ const [dayPeriod, hour] = toHourCycle(this.hour, hourCycle);
64
+ this.dayPeriod = dayPeriod;
65
+ this.hour = hour;
66
+ }
67
+ }
68
+
69
+ copy(): IncompleteDate {
70
+ const res = new IncompleteDate(this.calendar, this.hourCycle);
71
+ res.era = this.era;
72
+ res.year = this.year;
73
+ res.month = this.month;
74
+ res.day = this.day;
75
+ res.hour = this.hour;
76
+ res.dayPeriod = this.dayPeriod;
77
+ res.minute = this.minute;
78
+ res.second = this.second;
79
+ res.millisecond = this.millisecond;
80
+ res.offset = this.offset;
81
+ return res;
82
+ }
83
+
84
+ /** Checks whether all the specified segments have a value. */
85
+ isComplete(segments: DateSegmentType[]): boolean {
86
+ return segments.every((segment) => this[segment as keyof IncompleteDate] != null);
87
+ }
88
+
89
+ /** Checks whether the given date value matches this value for the specified segments. */
90
+ validate(dt: DateValue, segments: DateSegmentType[]): boolean {
91
+ return segments.every((segment) => {
92
+ if ((segment === "hour" || segment === "dayPeriod") && "hour" in dt) {
93
+ const [dayPeriod, hour] = toHourCycle(dt.hour, this.hourCycle);
94
+ return this.dayPeriod === dayPeriod && this.hour === hour;
95
+ }
96
+ return this[segment as keyof IncompleteDate] === dt[segment as keyof DateValue];
97
+ });
98
+ }
99
+
100
+ /** Checks if the date is empty (i.e. all specified segments are null). */
101
+ isCleared(segments: DateSegmentType[]): boolean {
102
+ return segments.every((segment) => this[segment as keyof IncompleteDate] === null);
103
+ }
104
+
105
+ /** Sets the given field. */
106
+ set(field: DateSegmentType, value: number | string, placeholder: DateValue): IncompleteDate {
107
+ const result = this.copy();
108
+ (result as unknown as Record<string, unknown>)[field] = value;
109
+ if (field === "hour" && result.dayPeriod == null && "hour" in placeholder) {
110
+ result.dayPeriod = toHourCycle(placeholder.hour, this.hourCycle)[0];
111
+ }
112
+ if (field === "year" && result.era == null) {
113
+ result.era = placeholder.era;
114
+ }
115
+
116
+ // clear offset when a date/time field changes since it may no longer be valid
117
+ if (field !== "second" && field !== "literal" && field !== "timeZoneName") {
118
+ result.offset = null;
119
+ }
120
+ return result;
121
+ }
122
+
123
+ /** Sets the given field to null. */
124
+ clear(field: DateSegmentType): IncompleteDate {
125
+ const result = this.copy();
126
+ (result as unknown as Record<string, unknown>)[field] = null;
127
+ if (field === "year") {
128
+ result.era = null;
129
+ }
130
+
131
+ // clear offset when a field is cleared since it may no longer be valid
132
+ result.offset = null;
133
+ return result;
134
+ }
135
+
136
+ /**
137
+ * Increments or decrements the given field. If it is null, then it is set to the placeholder
138
+ * value.
139
+ */
140
+ cycle(
141
+ field: DateSegmentType,
142
+ amount: number,
143
+ placeholder: DateValue,
144
+ displaySegments: DateSegmentType[],
145
+ ): IncompleteDate {
146
+ const res = this.copy();
147
+
148
+ // If field is null, default to placeholder.
149
+ if (res[field as keyof IncompleteDate] == null && field !== "dayPeriod" && field !== "era") {
150
+ if (field === "hour" && "hour" in placeholder) {
151
+ const [dayPeriod, hour] = toHourCycle(placeholder.hour, this.hourCycle);
152
+ res.dayPeriod = dayPeriod;
153
+ res.hour = hour;
154
+ } else {
155
+ (res as unknown as Record<string, unknown>)[field] = placeholder[field as keyof DateValue];
156
+ }
157
+ if (field === "year" && res.era == null) {
158
+ res.era = placeholder.era;
159
+ }
160
+
161
+ return res;
162
+ }
163
+
164
+ switch (field) {
165
+ case "era": {
166
+ const eras = this.calendar.getEras();
167
+ let index = eras.indexOf(res.era!);
168
+ index = cycleValue(index, amount, 0, eras.length - 1);
169
+ res.era = eras[index];
170
+ break;
171
+ }
172
+ case "year": {
173
+ // Use CalendarDate to cycle so that we update the era when going between 1 AD and 1 BC.
174
+ let date = new CalendarDate(
175
+ this.calendar,
176
+ this.era ?? placeholder.era,
177
+ this.year ?? placeholder.year,
178
+ this.month ?? 1,
179
+ this.day ?? 1,
180
+ );
181
+ date = date.cycle(field, amount, { round: field === "year" });
182
+ res.era = date.era;
183
+ res.year = date.year;
184
+ break;
185
+ }
186
+ case "month":
187
+ res.month = cycleValue(res.month ?? 1, amount, 1, this.calendar.getMaximumMonthsInYear());
188
+ break;
189
+ case "day":
190
+ // Allow incrementing up to the maximum number of days in any month.
191
+ res.day = cycleValue(res.day ?? 1, amount, 1, this.calendar.getMaximumDaysInMonth());
192
+ break;
193
+ case "hour": {
194
+ // if date is fully defined or it is just a time field, and we have a time zone, use toValue to get a ZonedDateTime to cycle
195
+ // so DST fallback is properly handled
196
+ const hasDateSegements = displaySegments.some((s) => ["year", "month", "day"].includes(s));
197
+ if (
198
+ "timeZone" in placeholder &&
199
+ (!hasDateSegements || (res.year != null && res.month != null && res.day != null))
200
+ ) {
201
+ let date = this.toValue(placeholder) as ZonedDateTime;
202
+ date = date.cycle("hour", amount, {
203
+ hourCycle: this.hourCycle === "h12" ? 12 : 24,
204
+ round: false,
205
+ });
206
+ const [dayPeriod, adjustedHour] = toHourCycle(date.hour, this.hourCycle);
207
+ res.hour = adjustedHour;
208
+ res.dayPeriod = dayPeriod;
209
+ res.offset = date.offset;
210
+ } else {
211
+ const hours = res.hour ?? 0;
212
+ const limits = this.getSegmentLimits("hour")!;
213
+ res.hour = cycleValue(hours, amount, limits.minValue, limits.maxValue);
214
+ if (res.dayPeriod == null && "hour" in placeholder) {
215
+ res.dayPeriod = toHourCycle(placeholder.hour, this.hourCycle)[0];
216
+ }
217
+ }
218
+ break;
219
+ }
220
+ case "dayPeriod":
221
+ res.dayPeriod = cycleValue(res.dayPeriod ?? 0, amount, 0, 1);
222
+ break;
223
+ case "minute":
224
+ res.minute = cycleValue(res.minute ?? 0, amount, 0, 59, true);
225
+ break;
226
+ case "second":
227
+ res.second = cycleValue(res.second ?? 0, amount, 0, 59, true);
228
+ break;
229
+ }
230
+
231
+ return res;
232
+ }
233
+
234
+ /**
235
+ * Converts the incomplete date to a full date value, using the provided value for any unset
236
+ * fields.
237
+ */
238
+ toValue(value: DateValue): DateValue {
239
+ if ("hour" in value) {
240
+ let hour = this.hour;
241
+ if (hour != null) {
242
+ hour = fromHourCycle(hour, this.dayPeriod ?? 0, this.hourCycle);
243
+ } else if (this.hourCycle === "h12" || this.hourCycle === "h11") {
244
+ hour = this.dayPeriod === 1 ? 12 : 0;
245
+ }
246
+
247
+ let res = value.set({
248
+ era: this.era ?? value.era,
249
+ year: this.year ?? value.year,
250
+ month: this.month ?? value.month,
251
+ day: this.day ?? value.day,
252
+ hour: hour ?? value.hour,
253
+ minute: this.minute ?? value.minute,
254
+ second: this.second ?? value.second,
255
+ millisecond: this.millisecond ?? value.millisecond,
256
+ });
257
+
258
+ if ("offset" in res && this.offset != null && res.offset !== this.offset) {
259
+ res = res.add({ milliseconds: res.offset - this.offset });
260
+ }
261
+
262
+ return res;
263
+ } else {
264
+ return value.set({
265
+ era: this.era ?? value.era,
266
+ year: this.year ?? value.year,
267
+ month: this.month ?? value.month,
268
+ day: this.day ?? value.day,
269
+ });
270
+ }
271
+ }
272
+
273
+ getSegmentLimits(
274
+ type: string,
275
+ ): { value: number | null; minValue: number; maxValue: number } | undefined {
276
+ switch (type) {
277
+ case "era": {
278
+ const eras = this.calendar.getEras();
279
+ return {
280
+ value: this.era != null ? eras.indexOf(this.era) : eras.length - 1,
281
+ minValue: 0,
282
+ maxValue: eras.length - 1,
283
+ };
284
+ }
285
+ case "year":
286
+ return {
287
+ value: this.year,
288
+ minValue: 1,
289
+ maxValue: 9999,
290
+ };
291
+ case "month":
292
+ return {
293
+ value: this.month,
294
+ minValue: 1,
295
+ maxValue: this.calendar.getMaximumMonthsInYear(),
296
+ };
297
+ case "day":
298
+ return {
299
+ value: this.day,
300
+ minValue: 1,
301
+ maxValue: this.calendar.getMaximumDaysInMonth(),
302
+ };
303
+ case "dayPeriod": {
304
+ return {
305
+ value: this.dayPeriod,
306
+ minValue: 0,
307
+ maxValue: 1,
308
+ };
309
+ }
310
+ case "hour": {
311
+ let minValue = 0;
312
+ let maxValue = 23;
313
+ if (this.hourCycle === "h12") {
314
+ minValue = 1;
315
+ maxValue = 12;
316
+ } else if (this.hourCycle === "h11") {
317
+ minValue = 0;
318
+ maxValue = 11;
319
+ }
320
+
321
+ return {
322
+ value: this.hour,
323
+ minValue,
324
+ maxValue,
325
+ };
326
+ }
327
+ case "minute":
328
+ return {
329
+ value: this.minute,
330
+ minValue: 0,
331
+ maxValue: 59,
332
+ };
333
+ case "second":
334
+ return {
335
+ value: this.second,
336
+ minValue: 0,
337
+ maxValue: 59,
338
+ };
339
+ }
340
+ }
341
+ }
342
+
343
+ function cycleValue(
344
+ value: number,
345
+ amount: number,
346
+ min: number,
347
+ max: number,
348
+ round = false,
349
+ ): number {
350
+ if (round) {
351
+ value += Math.sign(amount);
352
+
353
+ if (value < min) {
354
+ value = max;
355
+ }
356
+
357
+ const div = Math.abs(amount);
358
+ if (amount > 0) {
359
+ value = Math.ceil(value / div) * div;
360
+ } else {
361
+ value = Math.floor(value / div) * div;
362
+ }
363
+
364
+ if (value > max) {
365
+ value = min;
366
+ }
367
+ } else {
368
+ value += amount;
369
+ if (value < min) {
370
+ value = max - (min - value - 1);
371
+ } else if (value > max) {
372
+ value = min + (value - max - 1);
373
+ }
374
+ }
375
+
376
+ return value;
377
+ }
378
+
379
+ function toHourCycle(hour: number, hourCycle: HourCycle): [number | null, number] {
380
+ let dayPeriod: number | null = hour >= 12 ? 1 : 0;
381
+ switch (hourCycle) {
382
+ case "h11":
383
+ // Hours are numbered from 0 to 11. Used in Japan.
384
+ if (hour >= 12) {
385
+ hour -= 12;
386
+ }
387
+ break;
388
+ case "h12":
389
+ // Hours are numbered from 12 (representing 0) to 11.
390
+ if (hour === 0) {
391
+ hour = 12;
392
+ } else if (hour > 12) {
393
+ hour -= 12;
394
+ }
395
+ break;
396
+ case "h23":
397
+ // 24 hour time, numbered 0 to 23.
398
+ dayPeriod = null;
399
+ break;
400
+ case "h24":
401
+ // 24 hour time numbered 24 to 23. Unused but supported by Intl.DateTimeFormat.
402
+ hour += 1;
403
+ dayPeriod = null;
404
+ }
405
+
406
+ return [dayPeriod, hour];
407
+ }
408
+
409
+ function fromHourCycle(hour: number, dayPeriod: number, hourCycle: HourCycle): number {
410
+ switch (hourCycle) {
411
+ case "h11":
412
+ if (dayPeriod === 1) {
413
+ hour += 12;
414
+ }
415
+ break;
416
+ case "h12":
417
+ if (hour === 12) {
418
+ hour = 0;
419
+ }
420
+ if (dayPeriod === 1) {
421
+ hour += 12;
422
+ }
423
+ break;
424
+ case "h24":
425
+ hour -= 1;
426
+ break;
427
+ }
428
+
429
+ return hour;
430
+ }