@carbon/utilities 0.23.0 → 0.24.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.
@@ -0,0 +1,2015 @@
1
+ //#region src/date-picker/primitives/states.ts
2
+ /**
3
+ * Copyright IBM Corp. 2026
4
+ *
5
+ * This source code is licensed under the Apache-2.0 license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ /**
9
+ * Date picker state machine states
10
+ */
11
+ let DatePickerState = /* @__PURE__ */ function(DatePickerState) {
12
+ /**
13
+ * Initial state - calendar closed, no focus
14
+ */
15
+ DatePickerState["IDLE"] = "idle";
16
+ /**
17
+ * Input has focus, calendar closed
18
+ */
19
+ DatePickerState["FOCUSED"] = "focused";
20
+ /**
21
+ * Calendar dropdown is open
22
+ */
23
+ DatePickerState["CALENDAR_OPEN"] = "calendar_open";
24
+ /**
25
+ * User is selecting the start date (range mode)
26
+ */
27
+ DatePickerState["SELECTING_START"] = "selecting_start";
28
+ /**
29
+ * User is selecting the end date (range mode)
30
+ */
31
+ DatePickerState["SELECTING_END"] = "selecting_end";
32
+ /**
33
+ * Date(s) have been selected
34
+ */
35
+ DatePickerState["DATE_SELECTED"] = "date_selected";
36
+ /**
37
+ * Component is disabled
38
+ */
39
+ DatePickerState["DISABLED"] = "disabled";
40
+ /**
41
+ * Component is read-only
42
+ */
43
+ DatePickerState["READONLY"] = "readonly";
44
+ /**
45
+ * Component is in an error state
46
+ */
47
+ DatePickerState["ERROR"] = "error";
48
+ return DatePickerState;
49
+ }({});
50
+ /**
51
+ * Date picker events
52
+ */
53
+ let DatePickerEvent = /* @__PURE__ */ function(DatePickerEvent) {
54
+ DatePickerEvent["INPUT_FOCUS"] = "INPUT_FOCUS";
55
+ DatePickerEvent["INPUT_BLUR"] = "INPUT_BLUR";
56
+ DatePickerEvent["INPUT_CHANGE"] = "INPUT_CHANGE";
57
+ DatePickerEvent["CALENDAR_ICON_CLICK"] = "CALENDAR_ICON_CLICK";
58
+ DatePickerEvent["CALENDAR_OPEN"] = "CALENDAR_OPEN";
59
+ DatePickerEvent["CALENDAR_CLOSE"] = "CALENDAR_CLOSE";
60
+ DatePickerEvent["PREV_MONTH"] = "PREV_MONTH";
61
+ DatePickerEvent["NEXT_MONTH"] = "NEXT_MONTH";
62
+ DatePickerEvent["PREV_YEAR"] = "PREV_YEAR";
63
+ DatePickerEvent["NEXT_YEAR"] = "NEXT_YEAR";
64
+ DatePickerEvent["GO_TO_TODAY"] = "GO_TO_TODAY";
65
+ DatePickerEvent["DATE_SELECT"] = "DATE_SELECT";
66
+ DatePickerEvent["RANGE_START_SELECT"] = "RANGE_START_SELECT";
67
+ DatePickerEvent["RANGE_END_SELECT"] = "RANGE_END_SELECT";
68
+ DatePickerEvent["OUTSIDE_CLICK"] = "OUTSIDE_CLICK";
69
+ DatePickerEvent["ESCAPE_KEY"] = "ESCAPE_KEY";
70
+ DatePickerEvent["TAB_KEY"] = "TAB_KEY";
71
+ DatePickerEvent["SHIFT_TAB_KEY"] = "SHIFT_TAB_KEY";
72
+ DatePickerEvent["ENTER_KEY"] = "ENTER_KEY";
73
+ DatePickerEvent["ARROW_UP"] = "ARROW_UP";
74
+ DatePickerEvent["ARROW_DOWN"] = "ARROW_DOWN";
75
+ DatePickerEvent["ARROW_LEFT"] = "ARROW_LEFT";
76
+ DatePickerEvent["ARROW_RIGHT"] = "ARROW_RIGHT";
77
+ DatePickerEvent["PAGE_UP"] = "PAGE_UP";
78
+ DatePickerEvent["PAGE_DOWN"] = "PAGE_DOWN";
79
+ DatePickerEvent["HOME_KEY"] = "HOME_KEY";
80
+ DatePickerEvent["END_KEY"] = "END_KEY";
81
+ DatePickerEvent["DISABLE"] = "DISABLE";
82
+ DatePickerEvent["ENABLE"] = "ENABLE";
83
+ DatePickerEvent["SET_READONLY"] = "SET_READONLY";
84
+ DatePickerEvent["UNSET_READONLY"] = "UNSET_READONLY";
85
+ DatePickerEvent["VALUE_CHANGE"] = "VALUE_CHANGE";
86
+ DatePickerEvent["VALIDATION_ERROR"] = "VALIDATION_ERROR";
87
+ DatePickerEvent["CLEAR_ERROR"] = "CLEAR_ERROR";
88
+ DatePickerEvent["SET_MIN_DATE"] = "SET_MIN_DATE";
89
+ DatePickerEvent["SET_MAX_DATE"] = "SET_MAX_DATE";
90
+ DatePickerEvent["SET_DATE_FORMAT"] = "SET_DATE_FORMAT";
91
+ return DatePickerEvent;
92
+ }({});
93
+ //#endregion
94
+ //#region src/date-picker/primitives/temporal-utils.ts
95
+ /**
96
+ * Copyright IBM Corp. 2026
97
+ *
98
+ * This source code is licensed under the Apache-2.0 license found in the
99
+ * LICENSE file in the root directory of this source tree.
100
+ */
101
+ /**
102
+ * Temporal API utilities for date picker
103
+ * Uses the modern Temporal API for robust date handling
104
+ */
105
+ /**
106
+ * Convert a Date object to Temporal.PlainDate
107
+ *
108
+ * @param {Date} date - JavaScript Date object
109
+ * @returns Temporal.PlainDate
110
+ */
111
+ function dateToPlainDate(date) {
112
+ return Temporal.PlainDate.from({
113
+ year: date.getFullYear(),
114
+ month: date.getMonth() + 1,
115
+ day: date.getDate()
116
+ });
117
+ }
118
+ /**
119
+ * Convert Temporal.PlainDate to Date object
120
+ *
121
+ * @param {Temporal.PlainDate} plainDate - Temporal.PlainDate
122
+ * @returns JavaScript Date object
123
+ */
124
+ function plainDateToDate(plainDate) {
125
+ return new Date(plainDate.year, plainDate.month - 1, plainDate.day);
126
+ }
127
+ /**
128
+ * Convert Temporal.PlainDate to ISO date string (YYYY-MM-DD)
129
+ *
130
+ * @param {Temporal.PlainDate} plainDate - Temporal.PlainDate
131
+ * @returns ISO date string
132
+ */
133
+ function plainDateToISOString(plainDate) {
134
+ return plainDate.toString();
135
+ }
136
+ /**
137
+ * Parse ISO date string to Temporal.PlainDate
138
+ *
139
+ * @param {string} isoString - ISO date string (YYYY-MM-DD)
140
+ * @returns Temporal.PlainDate or null if invalid
141
+ */
142
+ function parseISOToPlainDate(isoString) {
143
+ try {
144
+ return Temporal.PlainDate.from(isoString);
145
+ } catch {
146
+ return null;
147
+ }
148
+ }
149
+ /**
150
+ * Parse a date string in various formats to Temporal.PlainDate
151
+ * Supports: ISO (YYYY-MM-DD), US (MM/DD/YYYY), and JavaScript Date objects
152
+ *
153
+ * @param {string | Date} dateInput - Date string or Date object
154
+ * @returns Temporal.PlainDate or null if invalid
155
+ */
156
+ function parseDateToPlainDate(dateInput) {
157
+ if (!dateInput) return null;
158
+ if (dateInput instanceof Date) return dateToPlainDate(dateInput);
159
+ try {
160
+ return Temporal.PlainDate.from(dateInput);
161
+ } catch {
162
+ const parts = dateInput.split("/");
163
+ if (parts.length === 3) {
164
+ const month = parseInt(parts[0], 10);
165
+ const day = parseInt(parts[1], 10);
166
+ const year = parseInt(parts[2], 10);
167
+ if (!isNaN(month) && !isNaN(day) && !isNaN(year)) try {
168
+ return Temporal.PlainDate.from({
169
+ year,
170
+ month,
171
+ day
172
+ });
173
+ } catch {
174
+ return null;
175
+ }
176
+ }
177
+ return null;
178
+ }
179
+ }
180
+ /**
181
+ * Compare two Temporal.PlainDate objects
182
+ *
183
+ * @param {Temporal.PlainDate} date1 - First date
184
+ * @param {Temporal.PlainDate} date2 - Second date
185
+ * @returns -1 if date1 < date2, 0 if equal, 1 if date1 > date2
186
+ */
187
+ function comparePlainDates(date1, date2) {
188
+ return Temporal.PlainDate.compare(date1, date2);
189
+ }
190
+ /**
191
+ * Check if a date is within a range
192
+ *
193
+ * @param {Temporal.PlainDate} date - Date to check
194
+ * @param {Temporal.PlainDate | null} minDate - Minimum date (inclusive)
195
+ * @param {Temporal.PlainDate | null} maxDate - Maximum date (inclusive)
196
+ * @returns True if date is within range
197
+ */
198
+ function isDateInRange(date, minDate, maxDate) {
199
+ if (minDate && comparePlainDates(date, minDate) < 0) return false;
200
+ if (maxDate && comparePlainDates(date, maxDate) > 0) return false;
201
+ return true;
202
+ }
203
+ /**
204
+ * Format a Temporal.PlainDate according to a format string
205
+ * Supports Flatpickr-compatible format tokens:
206
+ * - Y: 4-digit year (e.g., 2026)
207
+ * - y: 2-digit year (e.g., 26)
208
+ * - m: 2-digit month with leading zero (01-12)
209
+ * - n: month without leading zero (1-12)
210
+ * - d: 2-digit day with leading zero (01-31)
211
+ * - j: day without leading zero (1-31)
212
+ *
213
+ * @param {Temporal.PlainDate} date - Date to format
214
+ * @param {string} format - Format string (e.g., 'd/m/Y', 'm/d/Y', 'Y-m-d')
215
+ * @returns Formatted date string
216
+ */
217
+ function formatPlainDate(date, format) {
218
+ const year4 = date.year.toString();
219
+ const year2 = year4.slice(-2);
220
+ const month2 = date.month.toString().padStart(2, "0");
221
+ const month1 = date.month.toString();
222
+ const day2 = date.day.toString().padStart(2, "0");
223
+ const day1 = date.day.toString();
224
+ return format.replace(/Y/g, year4).replace(/y/g, year2).replace(/m/g, month2).replace(/n/g, month1).replace(/d/g, day2).replace(/j/g, day1);
225
+ }
226
+ /**
227
+ * Get today's date as Temporal.PlainDate
228
+ *
229
+ * @returns Today's date
230
+ */
231
+ function getToday() {
232
+ return Temporal.Now.plainDateISO();
233
+ }
234
+ /**
235
+ * Add days to a date
236
+ *
237
+ * @param {Temporal.PlainDate} date - Starting date
238
+ * @param {number} days - Number of days to add (can be negative)
239
+ * @returns New date
240
+ */
241
+ function addDays(date, days) {
242
+ return date.add({ days });
243
+ }
244
+ /**
245
+ * Add months to a date
246
+ *
247
+ * @param {Temporal.PlainDate} date - Starting date
248
+ * @param {number} months - Number of months to add (can be negative)
249
+ * @returns New date
250
+ */
251
+ function addMonths(date, months) {
252
+ return date.add({ months });
253
+ }
254
+ /**
255
+ * Get the number of days between two dates
256
+ *
257
+ * @param {Temporal.PlainDate} date1 - First date
258
+ * @param {Temporal.PlainDate} date2 - Second date
259
+ * @returns Number of days (positive if date2 is after date1)
260
+ */
261
+ function daysBetween(date1, date2) {
262
+ return date1.until(date2).days;
263
+ }
264
+ /**
265
+ * Check if two dates are equal
266
+ *
267
+ * @param {Temporal.PlainDate} date1 - First date
268
+ * @param {Temporal.PlainDate} date2 - Second date
269
+ * @returns True if dates are equal
270
+ */
271
+ function areDatesEqual(date1, date2) {
272
+ if (date1 === null && date2 === null) return true;
273
+ if (date1 === null || date2 === null) return false;
274
+ return comparePlainDates(date1, date2) === 0;
275
+ }
276
+ /**
277
+ * Get the start of the month for a given date
278
+ *
279
+ * @param {Temporal.PlainDate} date - Input date
280
+ * @returns First day of the month
281
+ */
282
+ function getMonthStart(date) {
283
+ return date.with({ day: 1 });
284
+ }
285
+ /**
286
+ * Get the end of the month for a given date
287
+ *
288
+ * @param {Temporal.PlainDate} date - Input date
289
+ * @returns Last day of the month
290
+ */
291
+ function getMonthEnd(date) {
292
+ return date.with({ day: date.daysInMonth });
293
+ }
294
+ /**
295
+ * Check if a date is today
296
+ *
297
+ * @param {Temporal.PlainDate} date - Date to check
298
+ * @returns True if date is today
299
+ */
300
+ function isToday(date) {
301
+ return areDatesEqual(date, getToday());
302
+ }
303
+ /**
304
+ * Check if a date is in the past
305
+ *
306
+ * @param {Temporal.PlainDate} date - Date to check
307
+ * @returns True if date is before today
308
+ */
309
+ function isPast(date) {
310
+ return comparePlainDates(date, getToday()) < 0;
311
+ }
312
+ /**
313
+ * Check if a date is in the future
314
+ *
315
+ * @param {Temporal.PlainDate} date - Date to check
316
+ * @returns True if date is after today
317
+ */
318
+ function isFuture(date) {
319
+ return comparePlainDates(date, getToday()) > 0;
320
+ }
321
+ /**
322
+ * Parse a date string with a specific format
323
+ * Supports common format tokens: Y, m, d
324
+ *
325
+ * @param {string} dateString - Date string to parse
326
+ * @param {string} format - Format string (e.g., 'm/d/Y', 'Y-m-d')
327
+ * @returns Temporal.PlainDate or null if invalid
328
+ */
329
+ function parseDateString(dateString, format) {
330
+ try {
331
+ const formatParts = format.split(/[^YmdHis]/);
332
+ const dateParts = dateString.split(/[^0-9]/);
333
+ if (formatParts.length !== dateParts.length) return null;
334
+ let year = 0;
335
+ let month = 0;
336
+ let day = 0;
337
+ formatParts.forEach((part, index) => {
338
+ const value = parseInt(dateParts[index], 10);
339
+ if (part === "Y") year = value;
340
+ else if (part === "m") month = value;
341
+ else if (part === "d") day = value;
342
+ });
343
+ return Temporal.PlainDate.from({
344
+ year,
345
+ month,
346
+ day
347
+ });
348
+ } catch {
349
+ return null;
350
+ }
351
+ }
352
+ /**
353
+ * Polyfill check for Temporal API
354
+ *
355
+ * @returns True if Temporal API is available
356
+ */
357
+ function isTemporalAvailable() {
358
+ return typeof Temporal !== "undefined" && typeof Temporal.PlainDate !== "undefined";
359
+ }
360
+ /**
361
+ * Get a fallback date handler if Temporal is not available
362
+ * This provides a migration path for browsers without Temporal support
363
+ */
364
+ function getDateHandler() {
365
+ if (isTemporalAvailable()) return {
366
+ type: "temporal",
367
+ toISOString: plainDateToISOString,
368
+ fromISOString: parseISOToPlainDate,
369
+ compare: comparePlainDates,
370
+ format: formatPlainDate,
371
+ isInRange: isDateInRange
372
+ };
373
+ return {
374
+ type: "date",
375
+ /**
376
+ *
377
+ * @param {Temporal.PlainDate} date - The date to convert
378
+ */
379
+ toISOString: (date) => date.toISOString().split("T")[0],
380
+ /**
381
+ *
382
+ * @param {string} str - The ISO string to parse
383
+ */
384
+ fromISOString: (str) => {
385
+ const date = new Date(str);
386
+ return isNaN(date.getTime()) ? null : date;
387
+ },
388
+ /**
389
+ *
390
+ * @param {Temporal.PlainDate} d1 - First date
391
+ * @param {Temporal.PlainDate} d2 - Second date
392
+ */
393
+ compare: (d1, d2) => d1.getTime() - d2.getTime(),
394
+ /**
395
+ *
396
+ * @param {Temporal.PlainDate} date - The date to format
397
+ * @param {string} format - The format string
398
+ */
399
+ format: (date, format) => {
400
+ const year = date.getFullYear().toString();
401
+ const month = (date.getMonth() + 1).toString().padStart(2, "0");
402
+ const day = date.getDate().toString().padStart(2, "0");
403
+ return format.replace("Y", year).replace("m", month).replace("d", day);
404
+ },
405
+ /**
406
+ *
407
+ * @param {Temporal.PlainDate} date - The date to check
408
+ * @param {Temporal.PlainDate | null} min - Minimum date
409
+ * @param {Temporal.PlainDate | null} max - Maximum date
410
+ */
411
+ isInRange: (date, min, max) => {
412
+ if (min && date < min) return false;
413
+ if (max && date > max) return false;
414
+ return true;
415
+ }
416
+ };
417
+ }
418
+ //#endregion
419
+ //#region src/date-picker/primitives/guards.ts
420
+ /**
421
+ * Check if the component is interactive (not disabled or readonly)
422
+ *
423
+ * @param {DatePickerContext} context - The current context
424
+ * @returns {boolean} True if interactive
425
+ */
426
+ const isInteractive = (context) => {
427
+ return !context.isDisabled && !context.isReadonly;
428
+ };
429
+ /**
430
+ * Check if component is in range mode
431
+ *
432
+ * @param {DatePickerContext} context - The current context
433
+ * @returns {boolean} True if in range mode
434
+ */
435
+ const isRangeMode = (context) => {
436
+ return context.mode === "range";
437
+ };
438
+ /**
439
+ * Check if component is in single mode
440
+ * @param {DatePickerContext} context - Current context
441
+ * @returns {boolean} True if in single mode
442
+ */
443
+ const isSingleMode = (context) => {
444
+ return context.mode === "single";
445
+ };
446
+ /**
447
+ * Guards for state transitions
448
+ */
449
+ const guards = {
450
+ ["idle"]: {},
451
+ ["focused"]: { INPUT_FOCUS: isInteractive },
452
+ ["calendar_open"]: {
453
+ CALENDAR_OPEN: isInteractive,
454
+ CALENDAR_ICON_CLICK: isInteractive,
455
+ TAB_KEY: isInteractive
456
+ },
457
+ ["selecting_start"]: {
458
+ /**
459
+ * Guard for calendar open in range mode
460
+ *
461
+ * @param {DatePickerContext} context - The current context
462
+ * @returns {boolean} True if allowed
463
+ */
464
+ CALENDAR_OPEN: (context) => isInteractive(context) && isRangeMode(context) },
465
+ ["selecting_end"]: {
466
+ /**
467
+ * Guard for range start selection
468
+ *
469
+ * @param {DatePickerContext} context - The current context
470
+ * @param {DatePickerEvent} event - The event
471
+ * @returns {boolean} True if allowed
472
+ */
473
+ RANGE_START_SELECT: (context, event) => {
474
+ if (!isInteractive(context) || !isRangeMode(context)) return false;
475
+ const payload = event.payload;
476
+ if (!payload?.date) return false;
477
+ return isDateInRange(payload.date, context.minDate, context.maxDate);
478
+ } },
479
+ ["date_selected"]: {
480
+ /**
481
+ * Guard for date selection
482
+ *
483
+ * @param {DatePickerContext} context - The current context
484
+ * @param {DatePickerEvent} event - The event
485
+ * @returns {boolean} True if allowed
486
+ */
487
+ DATE_SELECT: (context, event) => {
488
+ if (!isInteractive(context)) return false;
489
+ const payload = event.payload;
490
+ if (!payload?.date) return false;
491
+ return isDateInRange(payload.date, context.minDate, context.maxDate);
492
+ },
493
+ /**
494
+ * Guard for range end selection
495
+ *
496
+ * @param {DatePickerContext} context - The current context
497
+ * @param {DatePickerEvent} event - The event
498
+ * @returns {boolean} True if allowed
499
+ */
500
+ RANGE_END_SELECT: (context, event) => {
501
+ if (!isInteractive(context) || !isRangeMode(context)) return false;
502
+ const payload = event.payload;
503
+ if (!payload?.date) return false;
504
+ if (!context.startDate) return false;
505
+ return isDateInRange(payload.date, context.minDate, context.maxDate);
506
+ }
507
+ },
508
+ ["disabled"]: {},
509
+ ["readonly"]: {},
510
+ ["error"]: {}
511
+ };
512
+ /**
513
+ * Get guard for a specific state and event
514
+ *
515
+ * @param {DatePickerState} state - The current state
516
+ * @param {string} eventType - The event type
517
+ * @returns {StateGuard | undefined} The guard function or undefined
518
+ */
519
+ function getGuard(state, eventType) {
520
+ return guards[state]?.[eventType];
521
+ }
522
+ /**
523
+ * Check if a transition is guarded and allowed
524
+ *
525
+ * @param {DatePickerState} state - The current state
526
+ * @param {string} eventType - The event type
527
+ * @param {DatePickerContext} context - The current context
528
+ * @param {DatePickerEvent} event - The event
529
+ * @returns {boolean} True if guard passes
530
+ */
531
+ function checkGuard(state, eventType, context, event) {
532
+ const guard = getGuard(state, eventType);
533
+ if (!guard) return true;
534
+ return guard(context, event);
535
+ }
536
+ //#endregion
537
+ //#region src/date-picker/primitives/actions.ts
538
+ /**
539
+ * Actions for state transitions
540
+ */
541
+ const actions = {
542
+ ["idle"]: {
543
+ /**
544
+ * Action for CALENDAR_ICON_CLICK event
545
+ *
546
+ * @param {DatePickerContext} context - Current context
547
+ * @returns {Partial<DatePickerContext>} Updated context
548
+ */
549
+ CALENDAR_ICON_CLICK: (context) => {
550
+ return {
551
+ isOpen: true,
552
+ viewDate: context.viewDate || context.startDate || Temporal.Now.plainDateISO(),
553
+ focusedDate: context.focusedDate || context.startDate || null
554
+ };
555
+ },
556
+ /**
557
+ * Action for INPUT_FOCUS event
558
+ *
559
+ * @param {DatePickerContext} _context - The current context (unused)
560
+ * @param {DatePickerEvent} event - The event
561
+ * @returns {Partial<DatePickerContext>} Updated context
562
+ */
563
+ INPUT_FOCUS: (_context, event) => {
564
+ return {
565
+ isFocused: true,
566
+ lastFocusedInput: event.payload?.inputType || "from"
567
+ };
568
+ },
569
+ /** Action for INPUT_BLUR event */
570
+ INPUT_BLUR: () => ({ isFocused: false }),
571
+ /** Action for OUTSIDE_CLICK event */
572
+ OUTSIDE_CLICK: () => ({
573
+ isOpen: false,
574
+ isFocused: false,
575
+ restoreFocusTo: null,
576
+ shouldRestoreFocus: false
577
+ }),
578
+ /**
579
+ * Action for CALENDAR_CLOSE event
580
+ *
581
+ * @param {DatePickerContext} context - Current context
582
+ * @returns {Partial<DatePickerContext>} Updated context
583
+ */
584
+ CALENDAR_CLOSE: (context) => ({
585
+ isOpen: false,
586
+ restoreFocusTo: context.shouldRestoreFocus ? context.restoreFocusTo : null,
587
+ shouldRestoreFocus: context.shouldRestoreFocus
588
+ })
589
+ },
590
+ ["focused"]: {
591
+ /**
592
+ * Action for INPUT_FOCUS event
593
+ *
594
+ * @param {DatePickerContext} _context - The current context (unused)
595
+ * @param {DatePickerEvent} event - The event
596
+ * @returns {Partial<DatePickerContext>} Updated context
597
+ */
598
+ INPUT_FOCUS: (_context, event) => {
599
+ return {
600
+ isFocused: true,
601
+ lastFocusedInput: event.payload?.inputType || "from"
602
+ };
603
+ },
604
+ /**
605
+ * Action for CALENDAR_OPEN event from FOCUSED state
606
+ *
607
+ * @param {DatePickerContext} context - Current context
608
+ * @returns {Partial<DatePickerContext>} Updated context
609
+ */
610
+ CALENDAR_OPEN: (context) => {
611
+ const focusedDate = context.startDate || Temporal.Now.plainDateISO();
612
+ return {
613
+ isOpen: true,
614
+ viewDate: focusedDate,
615
+ focusedDate
616
+ };
617
+ },
618
+ /**
619
+ * Action for CALENDAR_ICON_CLICK event from FOCUSED state
620
+ * Behaves identically to CALENDAR_OPEN — opens the calendar preserving
621
+ * any existing viewDate/focusedDate context.
622
+ *
623
+ * @param {DatePickerContext} context - Current context
624
+ * @returns {Partial<DatePickerContext>} Updated context
625
+ */
626
+ CALENDAR_ICON_CLICK: (context) => {
627
+ return {
628
+ isOpen: true,
629
+ viewDate: context.viewDate || context.startDate || Temporal.Now.plainDateISO(),
630
+ focusedDate: context.focusedDate || context.startDate || null
631
+ };
632
+ }
633
+ },
634
+ ["calendar_open"]: {
635
+ /**
636
+ * Action for CALENDAR_OPEN event
637
+ *
638
+ * @param {DatePickerContext} context - Current context
639
+ * @returns {Partial<DatePickerContext>} Updated context
640
+ */
641
+ CALENDAR_OPEN: (context) => {
642
+ const viewDate = context.viewDate || context.startDate || Temporal.Now.plainDateISO();
643
+ return {
644
+ isOpen: true,
645
+ viewDate,
646
+ focusedDate: context.startDate || viewDate
647
+ };
648
+ },
649
+ /**
650
+ * Action for OUTSIDE_CLICK event
651
+ * Close the calendar when clicking outside
652
+ *
653
+ * @returns {Partial<DatePickerContext>} Updated context
654
+ */
655
+ OUTSIDE_CLICK: () => ({
656
+ isOpen: false,
657
+ isFocused: false,
658
+ restoreFocusTo: null,
659
+ shouldRestoreFocus: false
660
+ }),
661
+ /**
662
+ * Action for CALENDAR_ICON_CLICK event
663
+ *
664
+ * @param {DatePickerContext} context - Current context
665
+ * @returns {Partial<DatePickerContext>} Updated context
666
+ */
667
+ CALENDAR_ICON_CLICK: (context) => {
668
+ const viewDate = context.viewDate || context.startDate || Temporal.Now.plainDateISO();
669
+ return {
670
+ isOpen: true,
671
+ viewDate,
672
+ focusedDate: context.startDate || viewDate
673
+ };
674
+ },
675
+ /**
676
+ * Action for RANGE_START_SELECT event
677
+ *
678
+ * @param {DatePickerContext} _context - Current context
679
+ * @param {DatePickerEvent} event - The event
680
+ * @returns {Partial<DatePickerContext>} Updated context
681
+ */
682
+ RANGE_START_SELECT: (_context, event) => {
683
+ const startDate = event.payload?.date;
684
+ if (!startDate) return {};
685
+ return {
686
+ startDate,
687
+ endDate: null,
688
+ value: plainDateToISOString(startDate),
689
+ viewDate: startDate,
690
+ focusedDate: startDate,
691
+ isOpen: true,
692
+ restoreFocusTo: "from",
693
+ shouldRestoreFocus: false
694
+ };
695
+ },
696
+ /**
697
+ * Action for DATE_SELECT event (single mode)
698
+ *
699
+ * @param {DatePickerContext} _context - Current context
700
+ * @param {DatePickerEvent} event - The event
701
+ * @returns {Partial<DatePickerContext>} Updated context
702
+ */
703
+ DATE_SELECT: (_context, event) => {
704
+ const startDate = event.payload?.date;
705
+ if (!startDate) return {};
706
+ return {
707
+ startDate,
708
+ value: plainDateToISOString(startDate),
709
+ isOpen: _context.closeOnSelect ? false : _context.isOpen,
710
+ restoreFocusTo: _context.lastFocusedInput || "from",
711
+ shouldRestoreFocus: _context.closeOnSelect
712
+ };
713
+ },
714
+ /**
715
+ * Action for PREV_MONTH event
716
+ *
717
+ * @param {DatePickerContext} context - Current context
718
+ * @returns {Partial<DatePickerContext>} Updated context
719
+ */
720
+ PREV_MONTH: (context) => {
721
+ if (!context.viewDate) return {};
722
+ return {
723
+ viewDate: context.viewDate.add({ months: -1 }),
724
+ focusedDate: context.focusedDate ? context.focusedDate.add({ months: -1 }) : null
725
+ };
726
+ },
727
+ /**
728
+ * Action for NEXT_MONTH event
729
+ *
730
+ * @param {DatePickerContext} context - Current context
731
+ * @returns {Partial<DatePickerContext>} Updated context
732
+ */
733
+ NEXT_MONTH: (context) => {
734
+ if (!context.viewDate) return {};
735
+ return {
736
+ viewDate: context.viewDate.add({ months: 1 }),
737
+ focusedDate: context.focusedDate ? context.focusedDate.add({ months: 1 }) : null
738
+ };
739
+ },
740
+ /**
741
+ * Action for PREV_YEAR event
742
+ *
743
+ * @param {DatePickerContext} context - Current context
744
+ * @returns {Partial<DatePickerContext>} Updated context
745
+ */
746
+ PREV_YEAR: (context) => {
747
+ if (!context.viewDate) return {};
748
+ return { viewDate: context.viewDate.add({ years: -1 }) };
749
+ },
750
+ /**
751
+ * Action for NEXT_YEAR event
752
+ *
753
+ * @param {DatePickerContext} context - Current context
754
+ * @returns {Partial<DatePickerContext>} Updated context
755
+ */
756
+ NEXT_YEAR: (context) => {
757
+ if (!context.viewDate) return {};
758
+ return { viewDate: context.viewDate.add({ years: 1 }) };
759
+ },
760
+ /** Action for GO_TO_TODAY event */
761
+ GO_TO_TODAY: () => ({ viewDate: Temporal.Now.plainDateISO() }),
762
+ /**
763
+ * Action for ESCAPE_KEY event - close calendar
764
+ *
765
+ * @returns {Partial<DatePickerContext>} Updated context
766
+ */
767
+ ESCAPE_KEY: () => ({ isOpen: false }),
768
+ /**
769
+ * Action for TAB_KEY event - close calendar
770
+ *
771
+ * @returns {Partial<DatePickerContext>} Updated context
772
+ */
773
+ TAB_KEY: () => ({ isOpen: false }),
774
+ /**
775
+ * Action for ENTER_KEY event - select focused date
776
+ *
777
+ * @param {DatePickerContext} context - Current context
778
+ * @returns {Partial<DatePickerContext>} Updated context
779
+ */
780
+ ENTER_KEY: (context) => {
781
+ if (!context.focusedDate) return {};
782
+ return {
783
+ startDate: context.focusedDate,
784
+ value: plainDateToISOString(context.focusedDate),
785
+ isOpen: context.closeOnSelect ? false : context.isOpen,
786
+ restoreFocusTo: context.lastFocusedInput || "from",
787
+ shouldRestoreFocus: context.closeOnSelect
788
+ };
789
+ },
790
+ /**
791
+ * Action for ARROW_UP event - move focus up one week
792
+ *
793
+ * @param {DatePickerContext} context - Current context
794
+ * @returns {Partial<DatePickerContext>} Updated context
795
+ */
796
+ ARROW_UP: (context) => {
797
+ const focusedDate = context.focusedDate || context.startDate || context.viewDate || Temporal.Now.plainDateISO();
798
+ let newFocusedDate = focusedDate.add({ days: -7 });
799
+ if (context.minDate && Temporal.PlainDate.compare(newFocusedDate, context.minDate) < 0) newFocusedDate = context.minDate;
800
+ const viewDate = newFocusedDate.month !== focusedDate.month ? newFocusedDate : context.viewDate;
801
+ return {
802
+ focusedDate: newFocusedDate,
803
+ viewDate
804
+ };
805
+ },
806
+ /**
807
+ * Action for ARROW_DOWN event - move focus down one week
808
+ *
809
+ * @param {DatePickerContext} context - Current context
810
+ * @returns {Partial<DatePickerContext>} Updated context
811
+ */
812
+ ARROW_DOWN: (context) => {
813
+ const focusedDate = context.focusedDate || context.startDate || context.viewDate || Temporal.Now.plainDateISO();
814
+ let newFocusedDate = focusedDate.add({ days: 7 });
815
+ if (context.maxDate && Temporal.PlainDate.compare(newFocusedDate, context.maxDate) > 0) newFocusedDate = context.maxDate;
816
+ const viewDate = newFocusedDate.month !== focusedDate.month ? newFocusedDate : context.viewDate;
817
+ return {
818
+ focusedDate: newFocusedDate,
819
+ viewDate
820
+ };
821
+ },
822
+ /**
823
+ * Action for ARROW_LEFT event - move focus left one day
824
+ *
825
+ * @param {DatePickerContext} context - Current context
826
+ * @returns {Partial<DatePickerContext>} Updated context
827
+ */
828
+ ARROW_LEFT: (context) => {
829
+ const focusedDate = context.focusedDate || context.startDate || context.viewDate || Temporal.Now.plainDateISO();
830
+ let newFocusedDate = focusedDate.add({ days: -1 });
831
+ if (context.minDate && Temporal.PlainDate.compare(newFocusedDate, context.minDate) < 0) newFocusedDate = context.minDate;
832
+ const viewDate = newFocusedDate.month !== focusedDate.month ? newFocusedDate : context.viewDate;
833
+ return {
834
+ focusedDate: newFocusedDate,
835
+ viewDate
836
+ };
837
+ },
838
+ /**
839
+ * Action for ARROW_RIGHT event - move focus right one day
840
+ *
841
+ * @param {DatePickerContext} context - Current context
842
+ * @returns {Partial<DatePickerContext>} Updated context
843
+ */
844
+ ARROW_RIGHT: (context) => {
845
+ const focusedDate = context.focusedDate || context.startDate || context.viewDate || Temporal.Now.plainDateISO();
846
+ let newFocusedDate = focusedDate.add({ days: 1 });
847
+ if (context.maxDate && Temporal.PlainDate.compare(newFocusedDate, context.maxDate) > 0) newFocusedDate = context.maxDate;
848
+ const viewDate = newFocusedDate.month !== focusedDate.month ? newFocusedDate : context.viewDate;
849
+ return {
850
+ focusedDate: newFocusedDate,
851
+ viewDate
852
+ };
853
+ },
854
+ /**
855
+ * Action for PAGE_UP event - move to previous month
856
+ *
857
+ * @param {DatePickerContext} context - Current context
858
+ * @returns {Partial<DatePickerContext>} Updated context
859
+ */
860
+ PAGE_UP: (context) => {
861
+ if (!context.viewDate) return {};
862
+ const newViewDate = context.viewDate.add({ months: -1 });
863
+ let focusedDate = context.focusedDate ? context.focusedDate.add({ months: -1 }) : null;
864
+ if (focusedDate && context.minDate && Temporal.PlainDate.compare(focusedDate, context.minDate) < 0) focusedDate = context.minDate;
865
+ return {
866
+ viewDate: newViewDate,
867
+ focusedDate
868
+ };
869
+ },
870
+ /**
871
+ * Action for PAGE_DOWN event - move to next month
872
+ *
873
+ * @param {DatePickerContext} context - Current context
874
+ * @returns {Partial<DatePickerContext>} Updated context
875
+ */
876
+ PAGE_DOWN: (context) => {
877
+ if (!context.viewDate) return {};
878
+ const newViewDate = context.viewDate.add({ months: 1 });
879
+ let focusedDate = context.focusedDate ? context.focusedDate.add({ months: 1 }) : null;
880
+ if (focusedDate && context.maxDate && Temporal.PlainDate.compare(focusedDate, context.maxDate) > 0) focusedDate = context.maxDate;
881
+ return {
882
+ viewDate: newViewDate,
883
+ focusedDate
884
+ };
885
+ },
886
+ /**
887
+ * Action for HOME_KEY event - move to start of week
888
+ *
889
+ * @param {DatePickerContext} context - Current context
890
+ * @returns {Partial<DatePickerContext>} Updated context
891
+ */
892
+ HOME_KEY: (context) => {
893
+ const focusedDate = context.focusedDate || context.startDate || context.viewDate || Temporal.Now.plainDateISO();
894
+ const daysToSubtract = new Date(focusedDate.year, focusedDate.month - 1, focusedDate.day).getDay();
895
+ let newFocusedDate = focusedDate.add({ days: -daysToSubtract });
896
+ if (context.minDate && Temporal.PlainDate.compare(newFocusedDate, context.minDate) < 0) newFocusedDate = context.minDate;
897
+ const viewDate = newFocusedDate.month !== focusedDate.month ? newFocusedDate : context.viewDate;
898
+ return {
899
+ focusedDate: newFocusedDate,
900
+ viewDate
901
+ };
902
+ },
903
+ /**
904
+ * Action for END_KEY event - move to end of week
905
+ *
906
+ * @param {DatePickerContext} context - Current context
907
+ * @returns {Partial<DatePickerContext>} Updated context
908
+ */
909
+ END_KEY: (context) => {
910
+ const focusedDate = context.focusedDate || context.startDate || context.viewDate || Temporal.Now.plainDateISO();
911
+ const daysToAdd = 6 - new Date(focusedDate.year, focusedDate.month - 1, focusedDate.day).getDay();
912
+ let newFocusedDate = focusedDate.add({ days: daysToAdd });
913
+ if (context.maxDate && Temporal.PlainDate.compare(newFocusedDate, context.maxDate) > 0) newFocusedDate = context.maxDate;
914
+ const viewDate = newFocusedDate.month !== focusedDate.month ? newFocusedDate : context.viewDate;
915
+ return {
916
+ focusedDate: newFocusedDate,
917
+ viewDate
918
+ };
919
+ }
920
+ },
921
+ ["selecting_start"]: {
922
+ /**
923
+ * Action for CALENDAR_OPEN event
924
+ *
925
+ * @param {DatePickerContext} context - Current context
926
+ * @returns {Partial<DatePickerContext>} Updated context
927
+ */
928
+ CALENDAR_OPEN: (context) => {
929
+ if (context.mode === "range") return {
930
+ isOpen: true,
931
+ startDate: null,
932
+ endDate: null
933
+ };
934
+ return { isOpen: true };
935
+ } },
936
+ ["selecting_end"]: {
937
+ /**
938
+ * Action for RANGE_START_SELECT event
939
+ *
940
+ * @param {DatePickerContext} _context - Current context
941
+ * @param {DatePickerEvent} event - The event
942
+ * @returns {Partial<DatePickerContext>} Updated context
943
+ */
944
+ RANGE_START_SELECT: (_context, event) => {
945
+ const startDate = event.payload?.date;
946
+ if (!startDate) return {};
947
+ return {
948
+ startDate,
949
+ endDate: null,
950
+ value: plainDateToISOString(startDate),
951
+ restoreFocusTo: "from",
952
+ shouldRestoreFocus: false
953
+ };
954
+ },
955
+ /**
956
+ * Action for RANGE_END_SELECT event
957
+ *
958
+ * @param {DatePickerContext} context - Current context
959
+ * @param {DatePickerEvent} event - The event
960
+ * @returns {Partial<DatePickerContext>} Updated context
961
+ */
962
+ RANGE_END_SELECT: (context, event) => {
963
+ const endDate = event.payload?.date;
964
+ const { startDate } = context;
965
+ if (!endDate || !startDate) return {};
966
+ let finalStartDate = startDate;
967
+ let finalEndDate = endDate;
968
+ if (comparePlainDates(endDate, startDate) < 0) {
969
+ finalStartDate = endDate;
970
+ finalEndDate = startDate;
971
+ }
972
+ return {
973
+ startDate: finalStartDate,
974
+ endDate: finalEndDate,
975
+ value: `${plainDateToISOString(finalStartDate)}/${plainDateToISOString(finalEndDate)}`,
976
+ isOpen: false,
977
+ lastFocusedInput: "to",
978
+ restoreFocusTo: "to",
979
+ shouldRestoreFocus: true
980
+ };
981
+ },
982
+ /**
983
+ * Action for ESCAPE_KEY event - close calendar
984
+ *
985
+ * @returns {Partial<DatePickerContext>} Updated context
986
+ */
987
+ ESCAPE_KEY: () => ({
988
+ isOpen: false,
989
+ restoreFocusTo: null,
990
+ shouldRestoreFocus: false
991
+ }),
992
+ /**
993
+ * Action for TAB_KEY event - close calendar
994
+ *
995
+ * @returns {Partial<DatePickerContext>} Updated context
996
+ */
997
+ TAB_KEY: () => ({
998
+ isOpen: false,
999
+ restoreFocusTo: null,
1000
+ shouldRestoreFocus: false
1001
+ }),
1002
+ /**
1003
+ * Action for ARROW_UP event - move focus up one week
1004
+ *
1005
+ * @param {DatePickerContext} context - Current context
1006
+ * @returns {Partial<DatePickerContext>} Updated context
1007
+ */
1008
+ ARROW_UP: (context) => {
1009
+ const focusedDate = context.focusedDate || context.startDate || context.viewDate || Temporal.Now.plainDateISO();
1010
+ let newFocusedDate = focusedDate.add({ days: -7 });
1011
+ if (context.minDate && Temporal.PlainDate.compare(newFocusedDate, context.minDate) < 0) newFocusedDate = context.minDate;
1012
+ const viewDate = newFocusedDate.month !== focusedDate.month ? newFocusedDate : context.viewDate;
1013
+ return {
1014
+ focusedDate: newFocusedDate,
1015
+ viewDate
1016
+ };
1017
+ },
1018
+ /**
1019
+ * Action for ARROW_DOWN event - move focus down one week
1020
+ *
1021
+ * @param {DatePickerContext} context - Current context
1022
+ * @returns {Partial<DatePickerContext>} Updated context
1023
+ */
1024
+ ARROW_DOWN: (context) => {
1025
+ const focusedDate = context.focusedDate || context.startDate || context.viewDate || Temporal.Now.plainDateISO();
1026
+ let newFocusedDate = focusedDate.add({ days: 7 });
1027
+ if (context.maxDate && Temporal.PlainDate.compare(newFocusedDate, context.maxDate) > 0) newFocusedDate = context.maxDate;
1028
+ const viewDate = newFocusedDate.month !== focusedDate.month ? newFocusedDate : context.viewDate;
1029
+ return {
1030
+ focusedDate: newFocusedDate,
1031
+ viewDate
1032
+ };
1033
+ },
1034
+ /**
1035
+ * Action for ARROW_LEFT event - move focus left one day
1036
+ *
1037
+ * @param {DatePickerContext} context - Current context
1038
+ * @returns {Partial<DatePickerContext>} Updated context
1039
+ */
1040
+ ARROW_LEFT: (context) => {
1041
+ const focusedDate = context.focusedDate || context.startDate || context.viewDate || Temporal.Now.plainDateISO();
1042
+ let newFocusedDate = focusedDate.add({ days: -1 });
1043
+ if (context.minDate && Temporal.PlainDate.compare(newFocusedDate, context.minDate) < 0) newFocusedDate = context.minDate;
1044
+ const viewDate = newFocusedDate.month !== focusedDate.month ? newFocusedDate : context.viewDate;
1045
+ return {
1046
+ focusedDate: newFocusedDate,
1047
+ viewDate
1048
+ };
1049
+ },
1050
+ /**
1051
+ * Action for ARROW_RIGHT event - move focus right one day
1052
+ *
1053
+ * @param {DatePickerContext} context - Current context
1054
+ * @returns {Partial<DatePickerContext>} Updated context
1055
+ */
1056
+ ARROW_RIGHT: (context) => {
1057
+ const focusedDate = context.focusedDate || context.startDate || context.viewDate || Temporal.Now.plainDateISO();
1058
+ let newFocusedDate = focusedDate.add({ days: 1 });
1059
+ if (context.maxDate && Temporal.PlainDate.compare(newFocusedDate, context.maxDate) > 0) newFocusedDate = context.maxDate;
1060
+ const viewDate = newFocusedDate.month !== focusedDate.month ? newFocusedDate : context.viewDate;
1061
+ return {
1062
+ focusedDate: newFocusedDate,
1063
+ viewDate
1064
+ };
1065
+ },
1066
+ /**
1067
+ * Action for PAGE_UP event - move to previous month
1068
+ *
1069
+ * @param {DatePickerContext} context - Current context
1070
+ * @returns {Partial<DatePickerContext>} Updated context
1071
+ */
1072
+ PAGE_UP: (context) => {
1073
+ let newFocusedDate = (context.focusedDate || context.startDate || context.viewDate || Temporal.Now.plainDateISO()).add({ months: -1 });
1074
+ if (context.minDate && Temporal.PlainDate.compare(newFocusedDate, context.minDate) < 0) newFocusedDate = context.minDate;
1075
+ return {
1076
+ focusedDate: newFocusedDate,
1077
+ viewDate: newFocusedDate
1078
+ };
1079
+ },
1080
+ /**
1081
+ * Action for PAGE_DOWN event - move to next month
1082
+ *
1083
+ * @param {DatePickerContext} context - Current context
1084
+ * @returns {Partial<DatePickerContext>} Updated context
1085
+ */
1086
+ PAGE_DOWN: (context) => {
1087
+ let newFocusedDate = (context.focusedDate || context.startDate || context.viewDate || Temporal.Now.plainDateISO()).add({ months: 1 });
1088
+ if (context.maxDate && Temporal.PlainDate.compare(newFocusedDate, context.maxDate) > 0) newFocusedDate = context.maxDate;
1089
+ return {
1090
+ focusedDate: newFocusedDate,
1091
+ viewDate: newFocusedDate
1092
+ };
1093
+ },
1094
+ /**
1095
+ * Action for HOME_KEY event - move to start of week
1096
+ *
1097
+ * @param {DatePickerContext} context - Current context
1098
+ * @returns {Partial<DatePickerContext>} Updated context
1099
+ */
1100
+ HOME_KEY: (context) => {
1101
+ const focusedDate = context.focusedDate || context.startDate || context.viewDate || Temporal.Now.plainDateISO();
1102
+ const daysToSubtract = new Date(focusedDate.year, focusedDate.month - 1, focusedDate.day).getDay();
1103
+ let newFocusedDate = focusedDate.add({ days: -daysToSubtract });
1104
+ if (context.minDate && Temporal.PlainDate.compare(newFocusedDate, context.minDate) < 0) newFocusedDate = context.minDate;
1105
+ const viewDate = newFocusedDate.month !== focusedDate.month ? newFocusedDate : context.viewDate;
1106
+ return {
1107
+ focusedDate: newFocusedDate,
1108
+ viewDate
1109
+ };
1110
+ },
1111
+ /**
1112
+ * Action for END_KEY event - move to end of week
1113
+ *
1114
+ * @param {DatePickerContext} context - Current context
1115
+ * @returns {Partial<DatePickerContext>} Updated context
1116
+ */
1117
+ END_KEY: (context) => {
1118
+ const focusedDate = context.focusedDate || context.startDate || context.viewDate || Temporal.Now.plainDateISO();
1119
+ const dayOfWeek = new Date(focusedDate.year, focusedDate.month - 1, focusedDate.day).getDay();
1120
+ const daysToAdd = dayOfWeek === 6 ? 0 : 6 - dayOfWeek;
1121
+ let newFocusedDate = focusedDate.add({ days: daysToAdd });
1122
+ if (context.maxDate && Temporal.PlainDate.compare(newFocusedDate, context.maxDate) > 0) newFocusedDate = context.maxDate;
1123
+ const viewDate = newFocusedDate.month !== focusedDate.month ? newFocusedDate : context.viewDate;
1124
+ return {
1125
+ focusedDate: newFocusedDate,
1126
+ viewDate
1127
+ };
1128
+ },
1129
+ /**
1130
+ * Action for ENTER_KEY event - select focused date as end date
1131
+ *
1132
+ * @param {DatePickerContext} context - Current context
1133
+ * @returns {Partial<DatePickerContext>} Updated context
1134
+ */
1135
+ ENTER_KEY: (context) => {
1136
+ if (!context.focusedDate || !context.startDate) return {};
1137
+ const endDate = context.focusedDate;
1138
+ const { startDate } = context;
1139
+ let finalStartDate = startDate;
1140
+ let finalEndDate = endDate;
1141
+ if (comparePlainDates(endDate, startDate) < 0) {
1142
+ finalStartDate = endDate;
1143
+ finalEndDate = startDate;
1144
+ }
1145
+ return {
1146
+ startDate: finalStartDate,
1147
+ endDate: finalEndDate,
1148
+ value: `${plainDateToISOString(finalStartDate)}/${plainDateToISOString(finalEndDate)}`,
1149
+ isOpen: false,
1150
+ restoreFocusTo: "to",
1151
+ shouldRestoreFocus: true
1152
+ };
1153
+ },
1154
+ /**
1155
+ * Action for PREV_MONTH event
1156
+ *
1157
+ * @param {DatePickerContext} context - Current context
1158
+ * @returns {Partial<DatePickerContext>} Updated context
1159
+ */
1160
+ PREV_MONTH: (context) => {
1161
+ if (!context.viewDate) return {};
1162
+ return {
1163
+ viewDate: context.viewDate.add({ months: -1 }),
1164
+ focusedDate: context.focusedDate ? context.focusedDate.add({ months: -1 }) : null
1165
+ };
1166
+ },
1167
+ /**
1168
+ * Action for NEXT_MONTH event
1169
+ *
1170
+ * @param {DatePickerContext} context - Current context
1171
+ * @returns {Partial<DatePickerContext>} Updated context
1172
+ */
1173
+ NEXT_MONTH: (context) => {
1174
+ if (!context.viewDate) return {};
1175
+ return {
1176
+ viewDate: context.viewDate.add({ months: 1 }),
1177
+ focusedDate: context.focusedDate ? context.focusedDate.add({ months: 1 }) : null
1178
+ };
1179
+ }
1180
+ },
1181
+ ["date_selected"]: {
1182
+ /**
1183
+ * Action for DATE_SELECT event
1184
+ *
1185
+ * @param {DatePickerContext} context - Current context
1186
+ * @param {DatePickerEvent} event - The event
1187
+ * @returns {Partial<DatePickerContext>} Updated context
1188
+ */
1189
+ DATE_SELECT: (context, event) => {
1190
+ const date = event.payload?.date;
1191
+ if (!date) return {};
1192
+ if (context.mode === "single") return {
1193
+ startDate: date,
1194
+ value: plainDateToISOString(date),
1195
+ isOpen: context.closeOnSelect ? false : context.isOpen,
1196
+ restoreFocusTo: context.lastFocusedInput || "from",
1197
+ shouldRestoreFocus: context.closeOnSelect
1198
+ };
1199
+ return {};
1200
+ },
1201
+ /**
1202
+ * Action for RANGE_END_SELECT event
1203
+ *
1204
+ * @param {DatePickerContext} context - Current context
1205
+ * @param {DatePickerEvent} event - The event
1206
+ * @returns {Partial<DatePickerContext>} Updated context
1207
+ */
1208
+ RANGE_END_SELECT: (context, event) => {
1209
+ const endDate = event.payload?.date;
1210
+ const { startDate } = context;
1211
+ if (!endDate || !startDate) return {};
1212
+ let finalStartDate = startDate;
1213
+ let finalEndDate = endDate;
1214
+ if (comparePlainDates(endDate, startDate) < 0) {
1215
+ finalStartDate = endDate;
1216
+ finalEndDate = startDate;
1217
+ }
1218
+ return {
1219
+ startDate: finalStartDate,
1220
+ endDate: finalEndDate,
1221
+ value: `${plainDateToISOString(finalStartDate)}/${plainDateToISOString(finalEndDate)}`,
1222
+ isOpen: context.closeOnSelect ? false : context.isOpen,
1223
+ restoreFocusTo: "to",
1224
+ shouldRestoreFocus: context.closeOnSelect
1225
+ };
1226
+ },
1227
+ /**
1228
+ * Action for CALENDAR_CLOSE event
1229
+ *
1230
+ * @param {DatePickerContext} context - Current context
1231
+ * @returns {Partial<DatePickerContext>} Updated context
1232
+ */
1233
+ CALENDAR_CLOSE: (context) => ({
1234
+ isOpen: false,
1235
+ restoreFocusTo: context.shouldRestoreFocus ? context.restoreFocusTo : null,
1236
+ shouldRestoreFocus: context.shouldRestoreFocus
1237
+ })
1238
+ },
1239
+ ["disabled"]: {
1240
+ /** Action for a DISABLE event */
1241
+ DISABLE: () => ({
1242
+ isDisabled: true,
1243
+ isOpen: false
1244
+ }),
1245
+ /** Action for ENABLE event */
1246
+ ENABLE: () => ({ isDisabled: false })
1247
+ },
1248
+ ["readonly"]: {
1249
+ /** Action for SET_READONLY event */
1250
+ SET_READONLY: () => ({
1251
+ isReadonly: true,
1252
+ isOpen: false
1253
+ }),
1254
+ /** Action for UNSET_READONLY event */
1255
+ UNSET_READONLY: () => ({ isReadonly: false })
1256
+ },
1257
+ ["error"]: {
1258
+ /**
1259
+ * Action for VALIDATION_ERROR event
1260
+ *
1261
+ * @param {DatePickerContext} _context - Current context (unused)
1262
+ * @param {DatePickerEvent} event - The event
1263
+ * @returns {Partial<DatePickerContext>} Updated context
1264
+ */
1265
+ VALIDATION_ERROR: (_context, event) => {
1266
+ return {
1267
+ isInvalid: true,
1268
+ errorMessage: event.payload?.message || "Invalid date"
1269
+ };
1270
+ },
1271
+ /** Action for CLEAR_ERROR event */
1272
+ CLEAR_ERROR: () => ({
1273
+ isInvalid: false,
1274
+ errorMessage: void 0
1275
+ }),
1276
+ /** Action for VALUE_CHANGE event */
1277
+ VALUE_CHANGE: () => ({
1278
+ isInvalid: false,
1279
+ errorMessage: void 0
1280
+ })
1281
+ }
1282
+ };
1283
+ /**
1284
+ * Get action for a specific state and event
1285
+ *
1286
+ * @param {DatePickerState} state - The current state
1287
+ * @param {string} eventType - The event type
1288
+ * @returns {StateAction | undefined} The action function or undefined
1289
+ */
1290
+ function getAction(state, eventType) {
1291
+ return actions[state]?.[eventType];
1292
+ }
1293
+ /**
1294
+ * Execute an action and return context updates
1295
+ *
1296
+ * @param {DatePickerState} state - The current state
1297
+ * @param {string} eventType - The event type
1298
+ * @param {DatePickerContext} context - The current context
1299
+ * @param {DatePickerEvent} event - The event
1300
+ * @returns {Partial<DatePickerContext>} Partial context updates
1301
+ */
1302
+ function executeAction(state, eventType, context, event) {
1303
+ const action = getAction(state, eventType);
1304
+ if (!action) return {};
1305
+ return action(context, event);
1306
+ }
1307
+ //#endregion
1308
+ //#region src/date-picker/primitives/effects.ts
1309
+ /**
1310
+ * Side effects for state transitions
1311
+ * These are executed after the state transition is complete
1312
+ */
1313
+ const effects = {
1314
+ ["idle"]: {},
1315
+ ["focused"]: {},
1316
+ ["calendar_open"]: {
1317
+ /**
1318
+ * Focus on the calendar when it opens
1319
+ */
1320
+ CALENDAR_OPEN: () => {},
1321
+ /**
1322
+ * Focus the calendar when opened via a Tab key
1323
+ */
1324
+ TAB_KEY: () => {}
1325
+ },
1326
+ ["selecting_start"]: {},
1327
+ ["selecting_end"]: {},
1328
+ ["date_selected"]: {
1329
+ /**
1330
+ * Dispatch custom event when a date is selected
1331
+ */
1332
+ DATE_SELECT: () => {},
1333
+ /**
1334
+ * Dispatch custom event when the range end is selected
1335
+ */
1336
+ RANGE_END_SELECT: () => {}
1337
+ },
1338
+ ["disabled"]: {},
1339
+ ["readonly"]: {},
1340
+ ["error"]: {
1341
+ /**
1342
+ * Log validation errors
1343
+ * @param {DatePickerContext} _context - Current context
1344
+ * @param {DatePickerEvent} event - The event
1345
+ */
1346
+ VALIDATION_ERROR: (_context, event) => {
1347
+ if (process.env.NODE_ENV !== "production") console.warn("DatePicker validation error:", event.payload);
1348
+ } }
1349
+ };
1350
+ /**
1351
+ * Get effect for a specific state and event
1352
+ *
1353
+ * @param {DatePickerState} state - The current state
1354
+ * @param {string} eventType - The event type
1355
+ * @returns The effect function or undefined
1356
+ */
1357
+ function getEffect(state, eventType) {
1358
+ return effects[state]?.[eventType];
1359
+ }
1360
+ /**
1361
+ * Execute an effect
1362
+ *
1363
+ * @param {DatePickerState} state - The current state
1364
+ * @param {string} eventType - The event type
1365
+ * @param {DatePickerContext} context - The current context
1366
+ * @param {DatePickerEvent} event - The event
1367
+ */
1368
+ function executeEffect(state, eventType, context, event) {
1369
+ const effect = getEffect(state, eventType);
1370
+ if (effect) effect(context, event);
1371
+ }
1372
+ //#endregion
1373
+ //#region src/date-picker/primitives/machine.ts
1374
+ /**
1375
+ * State transition configuration
1376
+ * Maps the current state + event type to the next state
1377
+ */
1378
+ const stateTransitions = {
1379
+ ["idle"]: {
1380
+ ["INPUT_FOCUS"]: "focused",
1381
+ ["CALENDAR_ICON_CLICK"]: "calendar_open",
1382
+ ["DISABLE"]: "disabled",
1383
+ ["SET_READONLY"]: "readonly"
1384
+ },
1385
+ ["focused"]: {
1386
+ ["INPUT_BLUR"]: "idle",
1387
+ ["CALENDAR_OPEN"]: "calendar_open",
1388
+ ["CALENDAR_ICON_CLICK"]: "calendar_open",
1389
+ ["DISABLE"]: "disabled"
1390
+ },
1391
+ ["calendar_open"]: {
1392
+ ["DATE_SELECT"]: "date_selected",
1393
+ ["RANGE_START_SELECT"]: "selecting_end",
1394
+ ["OUTSIDE_CLICK"]: "idle",
1395
+ ["ESCAPE_KEY"]: "focused",
1396
+ ["TAB_KEY"]: "idle",
1397
+ ["CALENDAR_CLOSE"]: "focused",
1398
+ ["PREV_MONTH"]: "calendar_open",
1399
+ ["NEXT_MONTH"]: "calendar_open",
1400
+ ["PREV_YEAR"]: "calendar_open",
1401
+ ["NEXT_YEAR"]: "calendar_open",
1402
+ ["GO_TO_TODAY"]: "calendar_open",
1403
+ ["ARROW_UP"]: "calendar_open",
1404
+ ["ARROW_DOWN"]: "calendar_open",
1405
+ ["ARROW_LEFT"]: "calendar_open",
1406
+ ["ARROW_RIGHT"]: "calendar_open",
1407
+ ["PAGE_UP"]: "calendar_open",
1408
+ ["PAGE_DOWN"]: "calendar_open",
1409
+ ["HOME_KEY"]: "calendar_open",
1410
+ ["END_KEY"]: "calendar_open"
1411
+ },
1412
+ ["selecting_start"]: {
1413
+ ["RANGE_START_SELECT"]: "selecting_end",
1414
+ ["OUTSIDE_CLICK"]: "idle",
1415
+ ["ESCAPE_KEY"]: "focused"
1416
+ },
1417
+ ["selecting_end"]: {
1418
+ ["RANGE_END_SELECT"]: "date_selected",
1419
+ ["RANGE_START_SELECT"]: "selecting_end",
1420
+ ["OUTSIDE_CLICK"]: "idle",
1421
+ ["ESCAPE_KEY"]: "focused",
1422
+ ["TAB_KEY"]: "idle",
1423
+ ["ARROW_UP"]: "selecting_end",
1424
+ ["ARROW_DOWN"]: "selecting_end",
1425
+ ["ARROW_LEFT"]: "selecting_end",
1426
+ ["ARROW_RIGHT"]: "selecting_end",
1427
+ ["PAGE_UP"]: "selecting_end",
1428
+ ["PAGE_DOWN"]: "selecting_end",
1429
+ ["HOME_KEY"]: "selecting_end",
1430
+ ["END_KEY"]: "selecting_end",
1431
+ ["ENTER_KEY"]: "date_selected",
1432
+ ["PREV_MONTH"]: "selecting_end",
1433
+ ["NEXT_MONTH"]: "selecting_end"
1434
+ },
1435
+ ["date_selected"]: {
1436
+ ["CALENDAR_CLOSE"]: "idle",
1437
+ ["INPUT_FOCUS"]: "focused",
1438
+ ["CALENDAR_ICON_CLICK"]: "calendar_open"
1439
+ },
1440
+ ["disabled"]: { ["ENABLE"]: "idle" },
1441
+ ["readonly"]: { ["UNSET_READONLY"]: "idle" },
1442
+ ["error"]: {
1443
+ ["VALUE_CHANGE"]: "idle",
1444
+ ["CLEAR_ERROR"]: "idle"
1445
+ }
1446
+ };
1447
+ /**
1448
+ * Date picker state machine
1449
+ * Manages state transitions and context updates for the date picker
1450
+ */
1451
+ var DatePickerStateMachine = class {
1452
+ /**
1453
+ * Create a new date picker state machine
1454
+ *
1455
+ * @param {Partial<DatePickerContext>} initialContext - Initial context values
1456
+ */
1457
+ constructor(initialContext = {}) {
1458
+ this.listeners = /* @__PURE__ */ new Set();
1459
+ this.currentState = "idle";
1460
+ this.context = this.createInitialContext(initialContext);
1461
+ }
1462
+ /**
1463
+ * Create initial context with defaults
1464
+ *
1465
+ * @param {Partial<DatePickerContext>} partial - Partial context to merge with defaults
1466
+ * @returns Complete context object
1467
+ */
1468
+ createInitialContext(partial) {
1469
+ return {
1470
+ mode: "single",
1471
+ value: "",
1472
+ startDate: null,
1473
+ endDate: null,
1474
+ isOpen: false,
1475
+ isFocused: false,
1476
+ isDisabled: false,
1477
+ isReadonly: false,
1478
+ isInvalid: false,
1479
+ lastFocusedInput: null,
1480
+ restoreFocusTo: null,
1481
+ shouldRestoreFocus: false,
1482
+ minDate: null,
1483
+ maxDate: null,
1484
+ dateFormat: "m/d/Y",
1485
+ allowInput: true,
1486
+ closeOnSelect: true,
1487
+ viewDate: null,
1488
+ focusedDate: null,
1489
+ ...partial
1490
+ };
1491
+ }
1492
+ /**
1493
+ * Send an event to the state machine
1494
+ * This is the primary method for triggering state transitions
1495
+ *
1496
+ * @param {string} eventType - The type of event to send
1497
+ * @param {unknown} payload - Optional event payload
1498
+ * @returns The updated context
1499
+ */
1500
+ send(eventType, payload) {
1501
+ const event = {
1502
+ type: eventType,
1503
+ payload,
1504
+ timestamp: Date.now()
1505
+ };
1506
+ const nextState = this.getNextState(this.currentState, event);
1507
+ if (!nextState) return this.context;
1508
+ if (!checkGuard(nextState, event.type, this.context, event)) return this.context;
1509
+ const contextUpdates = executeAction(this.currentState, event.type, this.context, event);
1510
+ const newContext = {
1511
+ ...this.context,
1512
+ ...contextUpdates
1513
+ };
1514
+ const stateTransition = {
1515
+ from: this.currentState,
1516
+ to: nextState,
1517
+ event,
1518
+ context: newContext
1519
+ };
1520
+ this.currentState = nextState;
1521
+ this.context = newContext;
1522
+ executeEffect(nextState, event.type, this.context, event);
1523
+ this.notifyListeners(stateTransition);
1524
+ return this.context;
1525
+ }
1526
+ /**
1527
+ * Get the current state
1528
+ *
1529
+ * @returns The current state
1530
+ */
1531
+ getState() {
1532
+ return this.currentState;
1533
+ }
1534
+ /**
1535
+ * Get the current context
1536
+ *
1537
+ * @returns A copy of the current context
1538
+ */
1539
+ getContext() {
1540
+ return { ...this.context };
1541
+ }
1542
+ /**
1543
+ * Update context directly (use sparingly)
1544
+ *
1545
+ * @param {Partial<DatePickerContext>} updates - Partial context updates
1546
+ * @returns The updated context
1547
+ */
1548
+ updateContext(updates) {
1549
+ this.context = {
1550
+ ...this.context,
1551
+ ...updates
1552
+ };
1553
+ return this.context;
1554
+ }
1555
+ /**
1556
+ * Subscribe to state transitions
1557
+ *
1558
+ * @param {TransitionListener} listener - Function to call on each transition
1559
+ * @returns Unsubscribe function
1560
+ */
1561
+ subscribe(listener) {
1562
+ this.listeners.add(listener);
1563
+ return () => this.listeners.delete(listener);
1564
+ }
1565
+ /**
1566
+ * Check if a transition is valid from the current state
1567
+ *
1568
+ * @param {string} eventType - The event type to check
1569
+ * @returns True if the transition is valid
1570
+ */
1571
+ canTransition(eventType) {
1572
+ const event = {
1573
+ type: eventType,
1574
+ timestamp: Date.now()
1575
+ };
1576
+ const nextState = this.getNextState(this.currentState, event);
1577
+ if (!nextState) return false;
1578
+ return checkGuard(nextState, eventType, this.context, event);
1579
+ }
1580
+ /**
1581
+ * Get the next state for a given event
1582
+ *
1583
+ * @param {DatePickerState} from - Current state
1584
+ * @param {DatePickerEvent} event - The event
1585
+ * @returns Next state or null if no transition exists
1586
+ */
1587
+ getNextState(from, event) {
1588
+ const transitions = stateTransitions[from];
1589
+ if (!transitions) return null;
1590
+ const nextState = transitions[event.type];
1591
+ if (!nextState) return null;
1592
+ return nextState;
1593
+ }
1594
+ /**
1595
+ * Notify all listeners of a state transition
1596
+ *
1597
+ * @param {StateTransition} transition - The state transition
1598
+ */
1599
+ notifyListeners(transition) {
1600
+ this.listeners.forEach((listener) => listener(transition));
1601
+ }
1602
+ /**
1603
+ * Reset the state machine to initial state
1604
+ *
1605
+ * @param {Partial<DatePickerContext>} initialContext - Optional new initial context
1606
+ */
1607
+ reset(initialContext) {
1608
+ this.currentState = "idle";
1609
+ this.context = this.createInitialContext(initialContext || {});
1610
+ }
1611
+ };
1612
+ //#endregion
1613
+ //#region src/date-picker/primitives/click-outside-handler.ts
1614
+ /**
1615
+ * Creates a click outside handler with proper lifecycle management
1616
+ *
1617
+ * This utility provides a framework-agnostic way to detect clicks outside
1618
+ * a component and trigger a callback. It combines best practices from both
1619
+ * React and Web Components implementations:
1620
+ *
1621
+ * - Uses defensive timing (from React) to prevent the opening click from closing
1622
+ * - Supports flexible element detection (from Web Components)
1623
+ * - Provides clean lifecycle management
1624
+ * - Fully testable and reusable
1625
+ *
1626
+ * @example React usage
1627
+ * ```typescript
1628
+ * useEffect(() => {
1629
+ * if (!context.isOpen) {
1630
+ * return;
1631
+ * }
1632
+ *
1633
+ * const handler = new ClickOutsideHandler({
1634
+ * isOpen: context.isOpen,
1635
+ * containsNode: (node: Node) => {
1636
+ * const calendarEl = calendarRef.current;
1637
+ * const startInputEl = startInputRef.current;
1638
+ * const endInputEl = endInputRef.current;
1639
+ *
1640
+ * return (
1641
+ * (calendarEl?.contains(node) ?? false) ||
1642
+ * (startInputEl?.contains(node) ?? false) ||
1643
+ * (endInputEl?.contains(node) ?? false)
1644
+ * );
1645
+ * },
1646
+ * onOutsideClick: () => send(DatePickerEvent.OUTSIDE_CLICK),
1647
+ * useCapture: true,
1648
+ * attachDelay: 0,
1649
+ * });
1650
+ *
1651
+ * handler.attach();
1652
+ *
1653
+ * return () => {
1654
+ * handler.detach();
1655
+ * };
1656
+ * }, [context.isOpen, send]);
1657
+ * ```
1658
+ *
1659
+ * @example Web Components usage
1660
+ * ```typescript
1661
+ * connectedCallback() {
1662
+ * super.connectedCallback();
1663
+ *
1664
+ * this._clickOutsideHandler = new ClickOutsideHandler({
1665
+ * isOpen: this.open,
1666
+ * containsNode: (node: Node) => {
1667
+ * return this.contains(node) || (this.shadowRoot?.contains(node) ?? false);
1668
+ * },
1669
+ * onOutsideClick: () => {
1670
+ * if (this._adapter) {
1671
+ * this._adapter.send(DatePickerEvent.OUTSIDE_CLICK);
1672
+ * }
1673
+ * },
1674
+ * useCapture: true,
1675
+ * attachDelay: 0,
1676
+ * });
1677
+ *
1678
+ * this._clickOutsideHandler.attach();
1679
+ * }
1680
+ *
1681
+ * disconnectedCallback() {
1682
+ * super.disconnectedCallback();
1683
+ * this._clickOutsideHandler?.detach();
1684
+ * this._clickOutsideHandler = null;
1685
+ * }
1686
+ *
1687
+ * updated(changedProperties: PropertyValues) {
1688
+ * super.updated(changedProperties);
1689
+ *
1690
+ * if (changedProperties.has('open')) {
1691
+ * this._clickOutsideHandler?.updateConfig({ isOpen: this.open });
1692
+ * }
1693
+ * }
1694
+ * ```
1695
+ */
1696
+ var ClickOutsideHandler = class {
1697
+ /**
1698
+ * Creates a new ClickOutsideHandler instance
1699
+ *
1700
+ * @param {ClickOutsideConfig} config - Configuration for the click outside handler
1701
+ */
1702
+ constructor(config) {
1703
+ this.handler = null;
1704
+ this.timeoutId = null;
1705
+ this.handleClick = (event) => {
1706
+ if (!this.config.isOpen) return;
1707
+ const target = event.target;
1708
+ if (!this.config.containsNode(target)) this.config.onOutsideClick();
1709
+ };
1710
+ this.config = config;
1711
+ }
1712
+ /**
1713
+ * Attach the click outside listener
1714
+ *
1715
+ * This method adds the click event listener to the document.
1716
+ * If attachDelay is configured, it will delay the attachment to prevent
1717
+ * the opening click from immediately triggering a close.
1718
+ *
1719
+ * Calling attach() multiple times is safe - it will only attach once.
1720
+ */
1721
+ attach() {
1722
+ if (this.handler) return;
1723
+ const useCapture = this.config.useCapture ?? true;
1724
+ const delay = this.config.attachDelay ?? 0;
1725
+ this.timeoutId = window.setTimeout(() => {
1726
+ document.addEventListener("click", this.handleClick, useCapture);
1727
+ this.handler = this.handleClick;
1728
+ this.timeoutId = null;
1729
+ }, delay);
1730
+ }
1731
+ /**
1732
+ * Detach the click outside listener
1733
+ *
1734
+ * This method removes the click event listener and cleans up any pending timeouts.
1735
+ * It's safe to call detach() multiple times or even if attach() was never called.
1736
+ *
1737
+ * Always call detach() when the component unmounts or when you no longer need
1738
+ * click outside detection to prevent memory leaks.
1739
+ */
1740
+ detach() {
1741
+ if (this.timeoutId !== null) {
1742
+ clearTimeout(this.timeoutId);
1743
+ this.timeoutId = null;
1744
+ }
1745
+ if (this.handler) {
1746
+ const useCapture = this.config.useCapture ?? true;
1747
+ document.removeEventListener("click", this.handler, useCapture);
1748
+ this.handler = null;
1749
+ }
1750
+ }
1751
+ /**
1752
+ * Update configuration
1753
+ *
1754
+ * This is useful in React when dependencies change, or in Web Components
1755
+ * when properties update. You can update any part of the configuration
1756
+ * without needing to detach and reattach the handler.
1757
+ *
1758
+ * @param {Partial<ClickOutsideConfig>} config - Partial configuration to update
1759
+ *
1760
+ * @example
1761
+ * ```typescript
1762
+ * // Update just the isOpen state
1763
+ * handler.updateConfig({ isOpen: true });
1764
+ *
1765
+ * // Update multiple properties
1766
+ * handler.updateConfig({
1767
+ * isOpen: true,
1768
+ * onOutsideClick: newCallback
1769
+ * });
1770
+ * ```
1771
+ */
1772
+ updateConfig(config) {
1773
+ this.config = {
1774
+ ...this.config,
1775
+ ...config
1776
+ };
1777
+ }
1778
+ /**
1779
+ * Check if the handler is currently attached
1780
+ *
1781
+ * @returns true if the handler is attached, false otherwise
1782
+ */
1783
+ isAttached() {
1784
+ return this.handler !== null;
1785
+ }
1786
+ /**
1787
+ * Check if there's a pending attachment
1788
+ *
1789
+ * @returns true if attachment is pending (waiting for delay), false otherwise
1790
+ */
1791
+ isPending() {
1792
+ return this.timeoutId !== null;
1793
+ }
1794
+ };
1795
+ //#endregion
1796
+ //#region src/date-picker/primitives/keyboard-utils.ts
1797
+ /**
1798
+ * Map a keyboard event to a state machine event
1799
+ * This provides a single source of truth for keyboard shortcuts across React and Web Components
1800
+ *
1801
+ * @param {KeyboardEventInfo} info - Information about the keyboard event and current state
1802
+ * @returns {KeyboardEventResult | null} The state machine event to dispatch, or null if key not handled
1803
+ */
1804
+ function mapKeyboardToStateMachineEvent(info) {
1805
+ const { key, mode, state, focusedDate } = info;
1806
+ if (key === "Escape") return {
1807
+ eventType: "ESCAPE_KEY",
1808
+ preventDefault: true
1809
+ };
1810
+ if (key === "Enter") {
1811
+ if (!focusedDate) return null;
1812
+ if (mode === "range") if (state === "selecting_end") return {
1813
+ eventType: "RANGE_END_SELECT",
1814
+ payload: { date: focusedDate },
1815
+ preventDefault: true
1816
+ };
1817
+ else return {
1818
+ eventType: "RANGE_START_SELECT",
1819
+ payload: { date: focusedDate },
1820
+ preventDefault: true
1821
+ };
1822
+ else return {
1823
+ eventType: "DATE_SELECT",
1824
+ payload: { date: focusedDate },
1825
+ preventDefault: true
1826
+ };
1827
+ }
1828
+ if (key === "ArrowUp") return {
1829
+ eventType: "ARROW_UP",
1830
+ preventDefault: true
1831
+ };
1832
+ if (key === "ArrowDown") return {
1833
+ eventType: "ARROW_DOWN",
1834
+ preventDefault: true
1835
+ };
1836
+ if (key === "ArrowLeft") return {
1837
+ eventType: "ARROW_LEFT",
1838
+ preventDefault: true
1839
+ };
1840
+ if (key === "ArrowRight") return {
1841
+ eventType: "ARROW_RIGHT",
1842
+ preventDefault: true
1843
+ };
1844
+ if (key === "PageUp") return {
1845
+ eventType: "PAGE_UP",
1846
+ preventDefault: true
1847
+ };
1848
+ if (key === "PageDown") return {
1849
+ eventType: "PAGE_DOWN",
1850
+ preventDefault: true
1851
+ };
1852
+ if (key === "Home") return {
1853
+ eventType: "HOME_KEY",
1854
+ preventDefault: true
1855
+ };
1856
+ if (key === "End") return {
1857
+ eventType: "END_KEY",
1858
+ preventDefault: true
1859
+ };
1860
+ return null;
1861
+ }
1862
+ //#endregion
1863
+ //#region src/date-picker/primitives/calendar-grid.ts
1864
+ /**
1865
+ * Copyright IBM Corp. 2026
1866
+ *
1867
+ * This source code is licensed under the Apache-2.0 license found in the
1868
+ * LICENSE file in the root directory of this source tree.
1869
+ */
1870
+ /**
1871
+ * Shared calendar-grid generation for date picker renderers.
1872
+ */
1873
+ /**
1874
+ * Number of weeks rendered per month. Fixed at 6 so the grid height stays
1875
+ * constant regardless of how the month falls across weeks.
1876
+ */
1877
+ const WEEKS_IN_GRID = 6;
1878
+ /**
1879
+ * Number of days in a week.
1880
+ */
1881
+ const DAYS_IN_WEEK$1 = 7;
1882
+ /**
1883
+ * Generate the calendar grid for the month containing `viewDate`.
1884
+ *
1885
+ *
1886
+ * @param {Temporal.PlainDate} viewDate - The date to generate calendar for
1887
+ * @param {Temporal.PlainDate | null} minDate - Minimum selectable date (inclusive), or `null` for no lower bound.
1888
+ * @param {Temporal.PlainDate | null} maxDate - Maximum selectable date (inclusive), or `null` for no upper bound.
1889
+ * @param {number} weekStartsOn - First day of the week: 0 = Sunday (default) through 6 = Saturday.
1890
+ * @returns {CalendarDay[][]} 6×7 grid of day cells (weeks × days).
1891
+ */
1892
+ function generateCalendarGrid(viewDate, minDate = null, maxDate = null, weekStartsOn = 0) {
1893
+ const firstDayOfMonth = getMonthStart(viewDate);
1894
+ const startDate = addDays(firstDayOfMonth, -((plainDateToDate(firstDayOfMonth).getDay() - weekStartsOn + 7) % 7));
1895
+ const weeks = [];
1896
+ let currentDate = startDate;
1897
+ for (let week = 0; week < WEEKS_IN_GRID; week++) {
1898
+ const days = [];
1899
+ for (let day = 0; day < DAYS_IN_WEEK$1; day++) {
1900
+ days.push({
1901
+ date: currentDate,
1902
+ isCurrentMonth: currentDate.month === viewDate.month,
1903
+ isToday: isToday(currentDate),
1904
+ isDisabled: !isDateInRange(currentDate, minDate, maxDate)
1905
+ });
1906
+ currentDate = addDays(currentDate, 1);
1907
+ }
1908
+ weeks.push(days);
1909
+ }
1910
+ return weeks;
1911
+ }
1912
+ //#endregion
1913
+ //#region src/date-picker/primitives/calendar-labels.ts
1914
+ /**
1915
+ * Copyright IBM Corp. 2026
1916
+ *
1917
+ * This source code is licensed under the Apache-2.0 license found in the
1918
+ * LICENSE file in the root directory of this source tree.
1919
+ */
1920
+ /**
1921
+ * Localized label helpers for date picker calendar renderers.
1922
+ */
1923
+ /**
1924
+ * Number of days in a week.
1925
+ */
1926
+ const DAYS_IN_WEEK = 7;
1927
+ /**
1928
+ * A date known to be a Sunday, used as the anchor for generating weekday names
1929
+ * in order. Can be any Sunday.
1930
+ */
1931
+ const SUNDAY_ANCHOR = new Date(2024, 0, 7);
1932
+ /**
1933
+ * Locale used when the caller supplies a structurally invalid BCP 47 tag.
1934
+ */
1935
+ const FALLBACK_LOCALE = "en";
1936
+ /**
1937
+ * Resolve a caller-supplied locale to one that is safe to hand to `Intl`.
1938
+ *
1939
+ * Gracefully fallback to FALLBACK_LOCALE when an invalid BCP 47 locale is provided
1940
+ *
1941
+ * @param {string} locale - Candidate BCP 47 locale tag.
1942
+ * @returns {string} The original locale if structurally valid, else `'en'`.
1943
+ */
1944
+ function resolveLocale(locale) {
1945
+ try {
1946
+ Intl.getCanonicalLocales(locale);
1947
+ return locale;
1948
+ } catch {
1949
+ return FALLBACK_LOCALE;
1950
+ }
1951
+ }
1952
+ /**
1953
+ * Whether a locale is English. Carbon renders single-letter weekday headers
1954
+ * for English only; other locales keep their localized short names.
1955
+ *
1956
+ * @param {string} locale - BCP 47 locale tag.
1957
+ * @returns {boolean} True if the locale is a form of English.
1958
+ */
1959
+ function isEnglishLocale(locale) {
1960
+ return locale.toLowerCase().startsWith("en");
1961
+ }
1962
+ /**
1963
+ * Get the month-and-year heading for the calendar (e.g. "January 2026").
1964
+ *
1965
+ * @param {Temporal.PlainDate} viewDate - Any date within the displayed month.
1966
+ * @param {string} locale - BCP 47 locale tag (default 'en').
1967
+ * @returns {string} Localized "month year" label.
1968
+ */
1969
+ function getMonthYearLabel(viewDate, locale = "en") {
1970
+ return new Intl.DateTimeFormat(resolveLocale(locale), {
1971
+ month: "long",
1972
+ year: "numeric"
1973
+ }).format(plainDateToDate(viewDate));
1974
+ }
1975
+ /**
1976
+ * Get a full, localized label for a single date (e.g. "January 1, 2026"),
1977
+ * suitable for a day cell's `aria-label`.
1978
+ *
1979
+ * @param {Temporal.PlainDate} date - The date to label.
1980
+ * @param {string} locale - BCP 47 locale tag (default 'en').
1981
+ * @returns {string} Localized full-date label.
1982
+ */
1983
+ function getFullDateLabel(date, locale = "en") {
1984
+ return new Intl.DateTimeFormat(resolveLocale(locale), {
1985
+ year: "numeric",
1986
+ month: "long",
1987
+ day: "numeric"
1988
+ }).format(plainDateToDate(date));
1989
+ }
1990
+ /**
1991
+ * Get the ordered weekday header labels.
1992
+ *
1993
+ * For English locales this returns `S M T W Th F S`,
1994
+ * with `Th` disambiguating Thursday from Tuesday). Other
1995
+ * locales get their `Intl` short weekday names unchanged.
1996
+ *
1997
+ * @param {string} locale - BCP 47 locale tag (default 'en').
1998
+ * @param {number} weekStartsOn - First day of the week: 0 = Sunday (default) through 6 = Saturday.
1999
+ * @returns {string[]} Seven weekday labels, ordered from `weekStartsOn`.
2000
+ */
2001
+ function getWeekdayLabels(locale = "en", weekStartsOn = 0) {
2002
+ const safeLocale = resolveLocale(locale);
2003
+ const formatter = new Intl.DateTimeFormat(safeLocale, { weekday: "short" });
2004
+ const english = isEnglishLocale(safeLocale);
2005
+ const labels = [];
2006
+ for (let i = 0; i < DAYS_IN_WEEK; i++) {
2007
+ const offset = (weekStartsOn + i) % DAYS_IN_WEEK;
2008
+ const date = new Date(SUNDAY_ANCHOR.getFullYear(), SUNDAY_ANCHOR.getMonth(), SUNDAY_ANCHOR.getDate() + offset);
2009
+ const short = formatter.format(date);
2010
+ labels.push(english ? short === "Thu" ? "Th" : short.charAt(0) : short);
2011
+ }
2012
+ return labels;
2013
+ }
2014
+ //#endregion
2015
+ export { ClickOutsideHandler, DatePickerEvent, DatePickerState, DatePickerStateMachine, actions, addDays, addMonths, areDatesEqual, checkGuard, comparePlainDates, dateToPlainDate, daysBetween, effects, executeAction, executeEffect, formatPlainDate, generateCalendarGrid, getAction, getDateHandler, getEffect, getFullDateLabel, getGuard, getMonthEnd, getMonthStart, getMonthYearLabel, getToday, getWeekdayLabels, guards, isDateInRange, isFuture, isPast, isSingleMode, isTemporalAvailable, isToday, mapKeyboardToStateMachineEvent, parseDateString, parseDateToPlainDate, parseISOToPlainDate, plainDateToDate, plainDateToISOString };