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