@at5/kairo 0.2.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.
package/dist/index.js ADDED
@@ -0,0 +1,1429 @@
1
+ import React, { createContext, useContext, useMemo, useRef, useState, useEffect, useCallback, useLayoutEffect, useId, useReducer } from 'react';
2
+ import { jsx, jsxs } from 'react/jsx-runtime';
3
+ import { useTimescape } from 'timescape/react';
4
+ import { createPortal } from 'react-dom';
5
+ import { useFloating, autoUpdate, offset, flip, shift } from '@floating-ui/react-dom';
6
+
7
+ var __defProp = Object.defineProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+
13
+ // src/date-picker/index.ts
14
+ var date_picker_exports = {};
15
+ __export(date_picker_exports, {
16
+ Calendar: () => Calendar,
17
+ Content: () => Content,
18
+ Day: () => Day,
19
+ Grid: () => Grid,
20
+ Input: () => Input,
21
+ Label: () => Label,
22
+ MonthCell: () => MonthCell,
23
+ MonthGrid: () => MonthGrid,
24
+ NextTrigger: () => NextTrigger,
25
+ PrevTrigger: () => PrevTrigger,
26
+ Root: () => Root,
27
+ Trigger: () => Trigger,
28
+ View: () => View,
29
+ ViewControl: () => ViewControl,
30
+ ViewTrigger: () => ViewTrigger,
31
+ WeekDays: () => WeekDays,
32
+ YearCell: () => YearCell,
33
+ YearGrid: () => YearGrid
34
+ });
35
+ var DatePickerContext = createContext(null);
36
+ function useDatePickerContext() {
37
+ const ctx = useContext(DatePickerContext);
38
+ if (!ctx) {
39
+ throw new Error("DatePicker compound components must be rendered inside <DatePicker.Root>.");
40
+ }
41
+ return ctx;
42
+ }
43
+ var DatePickerViewContext = createContext(null);
44
+ var MonthGridFocusContext = createContext(-1);
45
+ var YearGridFocusContext = createContext(-1);
46
+
47
+ // src/utils/locale.ts
48
+ function getWeekStartDay(locale, override) {
49
+ if (override !== void 0) return override;
50
+ try {
51
+ const weekInfo = new Intl.Locale(locale).weekInfo;
52
+ return weekInfo.firstDay === 7 ? 0 : weekInfo.firstDay;
53
+ } catch {
54
+ return 0;
55
+ }
56
+ }
57
+ function formatDate(date, locale) {
58
+ return new Intl.DateTimeFormat(locale, {
59
+ year: "numeric",
60
+ month: "2-digit",
61
+ day: "2-digit"
62
+ }).format(date);
63
+ }
64
+ function formatMonthYear(date, locale) {
65
+ return new Intl.DateTimeFormat(locale, {
66
+ month: "long",
67
+ year: "numeric"
68
+ }).format(date);
69
+ }
70
+ function formatYear(year, locale) {
71
+ return new Intl.DateTimeFormat(locale, { year: "numeric" }).format(new Date(year, 0, 1));
72
+ }
73
+ function getMonthNames(locale, format = "long") {
74
+ const formatter = new Intl.DateTimeFormat(locale, { month: format });
75
+ return Array.from({ length: 12 }, (_, i) => formatter.format(new Date(2024, i, 1)));
76
+ }
77
+ function getWeekDayNames(locale, weekStartDay, format = "short") {
78
+ const formatter = new Intl.DateTimeFormat(locale, { weekday: format });
79
+ const longFormatter = new Intl.DateTimeFormat(locale, { weekday: "long" });
80
+ const anchor = new Date(2025, 0, 5);
81
+ return Array.from({ length: 7 }, (_, i) => {
82
+ const day = new Date(anchor);
83
+ day.setDate(anchor.getDate() + (weekStartDay + i) % 7);
84
+ return { label: formatter.format(day), ariaLabel: longFormatter.format(day) };
85
+ });
86
+ }
87
+ function getDateFieldOrder(locale) {
88
+ const parts = new Intl.DateTimeFormat(locale, {
89
+ year: "numeric",
90
+ month: "2-digit",
91
+ day: "2-digit"
92
+ }).formatToParts(new Date(2024, 0, 15));
93
+ const order = [];
94
+ for (const part of parts) {
95
+ if (part.type === "year" || part.type === "month" || part.type === "day") {
96
+ order.push(part.type);
97
+ }
98
+ }
99
+ return order;
100
+ }
101
+ function getSegmentInfo(locale) {
102
+ const parts = new Intl.DateTimeFormat(locale, {
103
+ year: "numeric",
104
+ month: "2-digit",
105
+ day: "2-digit"
106
+ }).formatToParts(new Date(2024, 0, 15));
107
+ const order = [];
108
+ let separator = "/";
109
+ for (const part of parts) {
110
+ if (part.type === "year" || part.type === "month" || part.type === "day") {
111
+ order.push(part.type);
112
+ } else if (part.type === "literal" && part.value.trim()) {
113
+ separator = part.value.trim();
114
+ }
115
+ }
116
+ return { order, separator };
117
+ }
118
+ function parseDate(value, locale) {
119
+ if (!value.trim()) return null;
120
+ const isoMatch = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
121
+ if (isoMatch) {
122
+ const d = new Date(Number(isoMatch[1]), Number(isoMatch[2]) - 1, Number(isoMatch[3]));
123
+ return isNaN(d.getTime()) ? null : d;
124
+ }
125
+ const order = getDateFieldOrder(locale);
126
+ const parts = value.split(/[-/.\s]+/).map(Number);
127
+ if (parts.length < 3 || parts.some(isNaN)) return null;
128
+ const fields = { year: 0, month: 0, day: 0 };
129
+ order.forEach((field, i) => {
130
+ fields[field] = parts[i] ?? 0;
131
+ });
132
+ const { year, month, day } = fields;
133
+ if (!year || !month || !day) return null;
134
+ const date = new Date(year, month - 1, day);
135
+ if (isNaN(date.getTime())) return null;
136
+ if (date.getFullYear() !== year || date.getMonth() !== month - 1 || date.getDate() !== day) {
137
+ return null;
138
+ }
139
+ return date;
140
+ }
141
+
142
+ // src/utils/calendar.ts
143
+ function buildCalendarGrid(year, month, weekStartDay) {
144
+ const firstOfMonth = new Date(year, month, 1);
145
+ const offset2 = (firstOfMonth.getDay() - weekStartDay + 7) % 7;
146
+ const totalDays = new Date(year, month + 1, 0).getDate();
147
+ const rows = offset2 + totalDays <= 35 ? 5 : 6;
148
+ return Array.from(
149
+ { length: rows },
150
+ (_, row) => Array.from({ length: 7 }, (_2, col) => new Date(year, month, 1 - offset2 + row * 7 + col))
151
+ );
152
+ }
153
+ function buildWeekDays(locale, weekStartDay, format = "short") {
154
+ return getWeekDayNames(locale, weekStartDay, format);
155
+ }
156
+ function buildMonthItems(year, locale, selectedMonth, minDate, maxDate) {
157
+ const names = getMonthNames(locale, "long");
158
+ return names.map((label, i) => {
159
+ const isDisabled = minDate !== void 0 && year < minDate.getFullYear() || minDate !== void 0 && year === minDate.getFullYear() && i < minDate.getMonth() || maxDate !== void 0 && year > maxDate.getFullYear() || maxDate !== void 0 && year === maxDate.getFullYear() && i > maxDate.getMonth();
160
+ return {
161
+ value: i,
162
+ label,
163
+ isSelected: selectedMonth === i,
164
+ isDisabled
165
+ };
166
+ });
167
+ }
168
+ function buildYearItems(pageStart, count = 12, selectedYear, minDate, maxDate) {
169
+ return Array.from({ length: count }, (_, i) => {
170
+ const year = pageStart + i;
171
+ const isDisabled = minDate !== void 0 && year < minDate.getFullYear() || maxDate !== void 0 && year > maxDate.getFullYear();
172
+ return { value: year, isSelected: selectedYear === year, isDisabled };
173
+ });
174
+ }
175
+ function yearPageStart(year) {
176
+ return Math.floor(year / 12) * 12;
177
+ }
178
+
179
+ // src/utils/date.ts
180
+ function isSameDay(a, b) {
181
+ if (!a || !b) return false;
182
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
183
+ }
184
+ function isInRange(date, start, end) {
185
+ if (!start || !end) return false;
186
+ const [from, to] = start.getTime() <= end.getTime() ? [start, end] : [end, start];
187
+ return date.getTime() > from.getTime() && date.getTime() < to.getTime();
188
+ }
189
+ function startOfDay(date) {
190
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate());
191
+ }
192
+ function addDays(date, days) {
193
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate() + days);
194
+ }
195
+ function addMonths(date, months) {
196
+ const d = new Date(date.getFullYear(), date.getMonth() + months, 1);
197
+ const maxDay = new Date(d.getFullYear(), d.getMonth() + 1, 0).getDate();
198
+ d.setDate(Math.min(date.getDate(), maxDay));
199
+ return d;
200
+ }
201
+ function isDateDisabled(date, config) {
202
+ if (config.disabled === true) return true;
203
+ if (typeof config.disabled === "function" && config.disabled(date)) return true;
204
+ if (config.minDate && date.getTime() < startOfDay(config.minDate).getTime()) return true;
205
+ if (config.maxDate && date.getTime() > startOfDay(config.maxDate).getTime()) return true;
206
+ return false;
207
+ }
208
+
209
+ // src/date-picker/reducer.ts
210
+ function openState(state, anchor, source = null) {
211
+ const ref = anchor ?? startOfDay(/* @__PURE__ */ new Date());
212
+ return {
213
+ ...state,
214
+ open: true,
215
+ openSource: source,
216
+ view: "day",
217
+ focusedDate: ref,
218
+ focusedMonth: ref.getMonth(),
219
+ focusedYear: ref.getFullYear(),
220
+ yearPageStart: yearPageStart(ref.getFullYear())
221
+ };
222
+ }
223
+ function navMonth(state, delta) {
224
+ const d = new Date(state.focusedYear, state.focusedMonth + delta, 1);
225
+ return { ...state, focusedMonth: d.getMonth(), focusedYear: d.getFullYear() };
226
+ }
227
+ function datePickerReducer(state, action, config) {
228
+ switch (action.type) {
229
+ case "OPEN":
230
+ if (state.open || config.readOnly) return state;
231
+ return openState(state, state.selectedDate ?? state.rangeStart, action.source ?? null);
232
+ case "CLOSE":
233
+ return {
234
+ ...state,
235
+ open: false,
236
+ openSource: null,
237
+ view: "day",
238
+ hoverDate: null
239
+ };
240
+ case "TOGGLE":
241
+ if (state.open)
242
+ return {
243
+ ...state,
244
+ open: false,
245
+ openSource: null,
246
+ view: "day",
247
+ hoverDate: null
248
+ };
249
+ if (config.readOnly) return state;
250
+ return openState(state, state.selectedDate ?? state.rangeStart, action.source ?? "trigger");
251
+ case "SET_VIEW":
252
+ return { ...state, view: action.view };
253
+ case "NAV_PREV": {
254
+ if (state.view === "day") return navMonth(state, -1);
255
+ if (state.view === "month") return { ...state, focusedYear: state.focusedYear - 1 };
256
+ return { ...state, yearPageStart: state.yearPageStart - 12 };
257
+ }
258
+ case "NAV_NEXT": {
259
+ if (state.view === "day") return navMonth(state, 1);
260
+ if (state.view === "month") return { ...state, focusedYear: state.focusedYear + 1 };
261
+ return { ...state, yearPageStart: state.yearPageStart + 12 };
262
+ }
263
+ case "NAV_TO_DATE": {
264
+ const d = action.date;
265
+ return {
266
+ ...state,
267
+ focusedMonth: d.getMonth(),
268
+ focusedYear: d.getFullYear()
269
+ };
270
+ }
271
+ case "FOCUS_DATE": {
272
+ const d = action.date;
273
+ return {
274
+ ...state,
275
+ focusedDate: d,
276
+ focusedMonth: d.getMonth(),
277
+ focusedYear: d.getFullYear()
278
+ };
279
+ }
280
+ case "SELECT_DATE": {
281
+ const d = startOfDay(action.date);
282
+ if (isDateDisabled(d, config)) return state;
283
+ if (config.mode === "single") {
284
+ return {
285
+ ...state,
286
+ selectedDate: d,
287
+ inputValue: formatDate(d, config.locale),
288
+ open: config.closeOnSelect ? false : state.open,
289
+ hoverDate: null
290
+ };
291
+ }
292
+ if (config.mode === "range") {
293
+ if (!state.rangeStart || state.rangeEnd) {
294
+ return { ...state, rangeStart: d, rangeEnd: null, hoverDate: null };
295
+ }
296
+ const [start, end] = d.getTime() >= state.rangeStart.getTime() ? [state.rangeStart, d] : [d, state.rangeStart];
297
+ return {
298
+ ...state,
299
+ rangeStart: start,
300
+ rangeEnd: end,
301
+ hoverDate: null,
302
+ open: config.closeOnSelect ? false : state.open
303
+ };
304
+ }
305
+ if (config.mode === "multiple") {
306
+ const already = state.selectedDates.findIndex((s) => isSameDay(s, d));
307
+ const selectedDates = already >= 0 ? state.selectedDates.filter((_, i) => i !== already) : [...state.selectedDates, d];
308
+ return { ...state, selectedDates };
309
+ }
310
+ return state;
311
+ }
312
+ case "ANCHOR_DATE": {
313
+ const d = startOfDay(action.date);
314
+ if (isDateDisabled(d, config)) return state;
315
+ return { ...state, rangeStart: d, rangeEnd: null, hoverDate: null };
316
+ }
317
+ case "EXTEND_RANGE": {
318
+ const d = startOfDay(action.date);
319
+ if (!state.rangeStart || isDateDisabled(d, config)) return state;
320
+ const [start, end] = d.getTime() >= state.rangeStart.getTime() ? [state.rangeStart, d] : [d, state.rangeStart];
321
+ return {
322
+ ...state,
323
+ rangeStart: start,
324
+ rangeEnd: end,
325
+ hoverDate: null,
326
+ open: config.closeOnSelect ? false : state.open
327
+ };
328
+ }
329
+ case "TOGGLE_DATE": {
330
+ const d = startOfDay(action.date);
331
+ if (isDateDisabled(d, config)) return state;
332
+ const already = state.selectedDates.findIndex((s) => isSameDay(s, d));
333
+ const selectedDates = already >= 0 ? state.selectedDates.filter((_, i) => i !== already) : [...state.selectedDates, d];
334
+ return { ...state, selectedDates };
335
+ }
336
+ case "HOVER_DATE":
337
+ return {
338
+ ...state,
339
+ hoverDate: action.date ? startOfDay(action.date) : null
340
+ };
341
+ case "SET_INPUT":
342
+ return { ...state, inputValue: action.value };
343
+ case "COMMIT_INPUT": {
344
+ const parsed = parseDate(state.inputValue, config.locale);
345
+ if (!parsed || isDateDisabled(parsed, config)) return { ...state, inputValue: "" };
346
+ const d = startOfDay(parsed);
347
+ return {
348
+ ...state,
349
+ selectedDate: d,
350
+ focusedDate: d,
351
+ focusedMonth: d.getMonth(),
352
+ focusedYear: d.getFullYear(),
353
+ open: false
354
+ };
355
+ }
356
+ case "SELECT_MONTH":
357
+ return { ...state, focusedMonth: action.month, view: "day" };
358
+ case "SELECT_YEAR":
359
+ return {
360
+ ...state,
361
+ focusedYear: action.year,
362
+ yearPageStart: yearPageStart(action.year),
363
+ view: "month"
364
+ };
365
+ case "YEAR_PAGE_PREV":
366
+ return { ...state, yearPageStart: state.yearPageStart - 12 };
367
+ case "YEAR_PAGE_NEXT":
368
+ return { ...state, yearPageStart: state.yearPageStart + 12 };
369
+ default:
370
+ return state;
371
+ }
372
+ }
373
+ function createInitialState(config) {
374
+ const ref = config.selectedDate ?? config.rangeStart ?? /* @__PURE__ */ new Date();
375
+ return {
376
+ open: config.open ?? false,
377
+ openSource: null,
378
+ view: "day",
379
+ focusedMonth: ref.getMonth(),
380
+ focusedYear: ref.getFullYear(),
381
+ focusedDate: config.open ? startOfDay(ref) : null,
382
+ selectedDate: config.selectedDate ?? null,
383
+ rangeStart: config.rangeStart ?? null,
384
+ rangeEnd: config.rangeEnd ?? null,
385
+ hoverDate: null,
386
+ selectedDates: config.selectedDates ?? [],
387
+ inputValue: config.selectedDate ? formatDate(config.selectedDate, config.locale) : "",
388
+ yearPageStart: yearPageStart(ref.getFullYear())
389
+ };
390
+ }
391
+
392
+ // src/date-picker/use-date-picker.ts
393
+ function resolveConfig(props) {
394
+ const base = {
395
+ mode: props.mode ?? "single",
396
+ locale: props.locale ?? (typeof navigator !== "undefined" ? navigator.language : "en-US"),
397
+ readOnly: props.readOnly ?? false,
398
+ closeOnSelect: props.closeOnSelect ?? (props.mode !== "range" && props.mode !== "multiple"),
399
+ ...props.weekStartsOn !== void 0 && { weekStartsOn: props.weekStartsOn },
400
+ ...props.minDate !== void 0 && { minDate: props.minDate },
401
+ ...props.maxDate !== void 0 && { maxDate: props.maxDate },
402
+ ...props.disabled !== void 0 && { disabled: props.disabled }
403
+ };
404
+ return base;
405
+ }
406
+ function resolveInitialValue(props) {
407
+ if (!props.mode || props.mode === "single") {
408
+ const v = "defaultValue" in props ? props.defaultValue : void 0;
409
+ return { selectedDate: v ?? null };
410
+ }
411
+ if (props.mode === "range") {
412
+ const v = "defaultValue" in props ? props.defaultValue : void 0;
413
+ return { rangeStart: v?.start ?? null, rangeEnd: v?.end ?? null };
414
+ }
415
+ if (props.mode === "multiple") {
416
+ const v = "defaultValue" in props ? props.defaultValue : void 0;
417
+ return { selectedDates: v ?? [] };
418
+ }
419
+ return {};
420
+ }
421
+ function useDatePicker(props) {
422
+ const uid = useId();
423
+ const { mode, locale, weekStartsOn, minDate, maxDate, disabled, readOnly, closeOnSelect } = props;
424
+ const config = useMemo(
425
+ () => resolveConfig({
426
+ mode,
427
+ locale,
428
+ weekStartsOn,
429
+ minDate,
430
+ maxDate,
431
+ disabled,
432
+ readOnly,
433
+ closeOnSelect
434
+ }),
435
+ [mode, locale, weekStartsOn, minDate, maxDate, disabled, readOnly, closeOnSelect]
436
+ );
437
+ const initialValue = resolveInitialValue(props);
438
+ const [state, dispatch] = useReducer(
439
+ (s, a) => datePickerReducer(s, a, config),
440
+ createInitialState({
441
+ locale: config.locale,
442
+ ...props.defaultOpen !== void 0 && { open: props.defaultOpen },
443
+ ...initialValue
444
+ })
445
+ );
446
+ const onValueChangeRef = useRef(
447
+ !props.mode || props.mode === "single" ? props.onValueChange : void 0
448
+ );
449
+ useEffect(() => {
450
+ if (!props.mode || props.mode === "single") {
451
+ onValueChangeRef.current = props.onValueChange;
452
+ }
453
+ });
454
+ const prevSelectedRef = useRef(void 0);
455
+ useEffect(() => {
456
+ if (prevSelectedRef.current === void 0) {
457
+ prevSelectedRef.current = state.selectedDate;
458
+ return;
459
+ }
460
+ if (state.selectedDate !== prevSelectedRef.current) {
461
+ onValueChangeRef.current?.(state.selectedDate);
462
+ prevSelectedRef.current = state.selectedDate;
463
+ }
464
+ }, [state.selectedDate]);
465
+ const prevRangeRef = useRef(void 0);
466
+ useEffect(() => {
467
+ if (props.mode !== "range") return;
468
+ const p = props;
469
+ if (prevRangeRef.current === void 0) {
470
+ prevRangeRef.current = { start: state.rangeStart, end: state.rangeEnd };
471
+ return;
472
+ }
473
+ if (state.rangeStart !== prevRangeRef.current.start || state.rangeEnd !== prevRangeRef.current.end) {
474
+ p.onValueChange?.({ start: state.rangeStart, end: state.rangeEnd });
475
+ prevRangeRef.current = { start: state.rangeStart, end: state.rangeEnd };
476
+ }
477
+ }, [state.rangeStart, state.rangeEnd]);
478
+ const prevMultipleRef = useRef(void 0);
479
+ useEffect(() => {
480
+ if (props.mode !== "multiple") return;
481
+ const p = props;
482
+ if (prevMultipleRef.current === void 0) {
483
+ prevMultipleRef.current = state.selectedDates;
484
+ return;
485
+ }
486
+ if (state.selectedDates !== prevMultipleRef.current) {
487
+ p.onValueChange?.(state.selectedDates);
488
+ prevMultipleRef.current = state.selectedDates;
489
+ }
490
+ }, [state.selectedDates]);
491
+ const isControlledOpen = props.open !== void 0;
492
+ const effectiveOpen = isControlledOpen ? props.open ?? false : state.open;
493
+ const prevOpenRef = useRef(effectiveOpen);
494
+ useEffect(() => {
495
+ if (prevOpenRef.current !== effectiveOpen) {
496
+ props.onOpenChange?.(effectiveOpen);
497
+ }
498
+ prevOpenRef.current = effectiveOpen;
499
+ }, [effectiveOpen]);
500
+ const weekStartDay = useMemo(
501
+ () => getWeekStartDay(config.locale, config.weekStartsOn),
502
+ [config.locale, config.weekStartsOn]
503
+ );
504
+ const weeks = useMemo(
505
+ () => buildCalendarGrid(state.focusedYear, state.focusedMonth, weekStartDay),
506
+ [state.focusedYear, state.focusedMonth, weekStartDay]
507
+ );
508
+ const weekDays = useMemo(
509
+ () => buildWeekDays(config.locale, weekStartDay),
510
+ [config.locale, weekStartDay]
511
+ );
512
+ const monthItems = useMemo(
513
+ () => buildMonthItems(
514
+ state.focusedYear,
515
+ config.locale,
516
+ state.focusedMonth,
517
+ config.minDate,
518
+ config.maxDate
519
+ ),
520
+ [state.focusedYear, config.locale, state.focusedMonth, config.minDate, config.maxDate]
521
+ );
522
+ const yearItems = useMemo(
523
+ () => buildYearItems(state.yearPageStart, 12, state.focusedYear, config.minDate, config.maxDate),
524
+ [state.yearPageStart, state.focusedYear, config.minDate, config.maxDate]
525
+ );
526
+ const ids = useMemo(
527
+ () => ({
528
+ root: `dp-${uid}`,
529
+ label: `dp-label-${uid}`,
530
+ input: `dp-input-${uid}`,
531
+ trigger: `dp-trigger-${uid}`,
532
+ content: `dp-content-${uid}`
533
+ }),
534
+ [uid]
535
+ );
536
+ return {
537
+ state: { ...state, open: effectiveOpen },
538
+ dispatch,
539
+ config,
540
+ ids,
541
+ derived: { weeks, weekDays, monthItems, yearItems, weekStartDay }
542
+ };
543
+ }
544
+ function Root(props) {
545
+ const { children, ...pickerProps } = props;
546
+ const picker = useDatePicker(pickerProps);
547
+ return /* @__PURE__ */ jsx(
548
+ DatePickerContext.Provider,
549
+ {
550
+ value: {
551
+ state: picker.state,
552
+ dispatch: picker.dispatch,
553
+ config: picker.config,
554
+ ids: picker.ids
555
+ },
556
+ children
557
+ }
558
+ );
559
+ }
560
+ function Label({ children, ...props }) {
561
+ const { ids } = useDatePickerContext();
562
+ return /* @__PURE__ */ jsx("label", { id: ids.label, htmlFor: ids.input, ...props, children });
563
+ }
564
+ function wrapRef(ref) {
565
+ return (el) => {
566
+ ref(el);
567
+ };
568
+ }
569
+ var DEFAULT_LABELS = { month: "Month", day: "Day", year: "Year" };
570
+ var FIELD_MAP = {
571
+ year: "years",
572
+ month: "months",
573
+ day: "days"
574
+ };
575
+ function Segments({
576
+ sourceDate,
577
+ segmentOrder,
578
+ separator,
579
+ labels,
580
+ onDateChange,
581
+ groupId,
582
+ className,
583
+ style
584
+ }) {
585
+ const { state, dispatch, ids, config } = useDatePickerContext();
586
+ const onDateChangeRef = useRef(onDateChange);
587
+ onDateChangeRef.current = onDateChange;
588
+ const opts = {
589
+ wrapAround: true,
590
+ onChangeDate: (d) => onDateChangeRef.current(d)
591
+ };
592
+ if (config.minDate) opts.minDate = config.minDate;
593
+ if (config.maxDate) opts.maxDate = config.maxDate;
594
+ if (sourceDate) opts.date = sourceDate;
595
+ const { getRootProps, getInputProps } = useTimescape(opts);
596
+ const [focused, setFocused] = useState(null);
597
+ function handleSegmentFocus(field) {
598
+ setFocused(field);
599
+ if (!state.open && !config.readOnly) dispatch({ type: "OPEN", source: "input" });
600
+ }
601
+ function handleSegmentBlur(field, e) {
602
+ const relatedTarget = e.relatedTarget;
603
+ const isMovingWithin = e.currentTarget.parentElement?.contains(relatedTarget);
604
+ if (!isMovingWithin) setFocused(null);
605
+ }
606
+ function handleKeyDown(e) {
607
+ if (config.readOnly) {
608
+ e.preventDefault();
609
+ return;
610
+ }
611
+ if (e.key === "Escape") {
612
+ e.preventDefault();
613
+ if (state.open) dispatch({ type: "CLOSE" });
614
+ }
615
+ }
616
+ const { ref: rootRef, ...rootProps } = getRootProps();
617
+ return /* @__PURE__ */ jsx(
618
+ "div",
619
+ {
620
+ role: "group",
621
+ id: groupId ?? ids.input,
622
+ "aria-labelledby": ids.label,
623
+ tabIndex: -1,
624
+ className,
625
+ style,
626
+ ...rootProps,
627
+ ref: wrapRef(rootRef),
628
+ ...config.readOnly ? { "data-readonly": "true" } : {},
629
+ children: segmentOrder.map((field, i) => {
630
+ const type = FIELD_MAP[field];
631
+ const isFocused = focused === field;
632
+ const isTabStop = isFocused || focused === null && i === 0;
633
+ const { ref: inputRef, ...inputProps } = getInputProps(type);
634
+ const wrappedInputRef = wrapRef(inputRef);
635
+ return /* @__PURE__ */ jsxs(React.Fragment, { children: [
636
+ i > 0 && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", "data-separator": "true", children: separator }),
637
+ /* @__PURE__ */ jsx(
638
+ "input",
639
+ {
640
+ ...inputProps,
641
+ ref: wrappedInputRef,
642
+ "aria-label": labels[field],
643
+ "data-segment": field,
644
+ tabIndex: config.readOnly ? -1 : isTabStop ? 0 : -1,
645
+ disabled: config.readOnly,
646
+ onFocus: (e) => {
647
+ inputRef(e.currentTarget);
648
+ handleSegmentFocus(field);
649
+ },
650
+ onBlur: (e) => handleSegmentBlur(field, e),
651
+ onKeyDown: handleKeyDown,
652
+ className: "timescape-input",
653
+ style: {
654
+ width: field === "year" ? "4ch" : "2ch",
655
+ border: "none",
656
+ background: "transparent",
657
+ padding: 0,
658
+ margin: 0,
659
+ textAlign: "center",
660
+ font: "inherit",
661
+ outline: "none"
662
+ }
663
+ }
664
+ )
665
+ ] }, field);
666
+ })
667
+ }
668
+ );
669
+ }
670
+ function Input({ index, segmentLabels, className, style }) {
671
+ const { state, dispatch, config, ids } = useDatePickerContext();
672
+ const { order: segmentOrder, separator } = useMemo(
673
+ () => getSegmentInfo(config.locale),
674
+ [config.locale]
675
+ );
676
+ const labels = {
677
+ month: segmentLabels?.month ?? DEFAULT_LABELS.month,
678
+ day: segmentLabels?.day ?? DEFAULT_LABELS.day,
679
+ year: segmentLabels?.year ?? DEFAULT_LABELS.year
680
+ };
681
+ const sourceDate = index === 1 ? state.rangeEnd : index === 0 ? state.rangeStart : state.selectedDate;
682
+ const isInternalChangeRef = useRef(false);
683
+ const prevSourceDateRef = useRef(sourceDate);
684
+ const [timescapeKey, setTimescapeKey] = useState(0);
685
+ useEffect(() => {
686
+ const prev = prevSourceDateRef.current;
687
+ prevSourceDateRef.current = sourceDate;
688
+ if (isInternalChangeRef.current) {
689
+ isInternalChangeRef.current = false;
690
+ return;
691
+ }
692
+ const prevTime = prev?.getTime();
693
+ const currTime = sourceDate?.getTime();
694
+ if (prevTime !== currTime) {
695
+ setTimescapeKey((k) => k + 1);
696
+ }
697
+ }, [sourceDate]);
698
+ const onDateChange = useCallback(
699
+ (nextDate) => {
700
+ if (!nextDate) return;
701
+ if (isDateDisabled(nextDate, config)) return;
702
+ const prevDate = prevSourceDateRef.current;
703
+ if (prevDate && prevDate.getTime() === nextDate.getTime()) return;
704
+ if (config.mode === "multiple") {
705
+ dispatch({ type: "FOCUS_DATE", date: nextDate });
706
+ dispatch({ type: "TOGGLE_DATE", date: nextDate });
707
+ setTimescapeKey((k) => k + 1);
708
+ return;
709
+ }
710
+ isInternalChangeRef.current = true;
711
+ dispatch({ type: "FOCUS_DATE", date: nextDate });
712
+ if (config.mode === "single") {
713
+ dispatch({ type: "SELECT_DATE", date: nextDate });
714
+ } else if (config.mode === "range" && index === 0) {
715
+ dispatch({ type: "ANCHOR_DATE", date: nextDate });
716
+ } else if (config.mode === "range" && index === 1) {
717
+ dispatch({ type: "EXTEND_RANGE", date: nextDate });
718
+ }
719
+ },
720
+ [config, dispatch, index]
721
+ );
722
+ const groupId = index !== void 0 ? `${ids.input}-${index}` : ids.input;
723
+ return /* @__PURE__ */ jsx(
724
+ Segments,
725
+ {
726
+ sourceDate,
727
+ segmentOrder,
728
+ separator,
729
+ labels,
730
+ onDateChange,
731
+ groupId,
732
+ className,
733
+ style
734
+ },
735
+ timescapeKey
736
+ );
737
+ }
738
+ function Trigger({ children, onClick, disabled, ...props }) {
739
+ const { state, dispatch, ids, config } = useDatePickerContext();
740
+ const isDisabled = disabled ?? config.disabled === true;
741
+ const isReadOnly = config.readOnly;
742
+ return /* @__PURE__ */ jsx(
743
+ "button",
744
+ {
745
+ type: "button",
746
+ id: ids.trigger,
747
+ "aria-haspopup": "dialog",
748
+ "aria-expanded": state.open,
749
+ "aria-controls": state.open ? ids.content : void 0,
750
+ "aria-label": children ? void 0 : "Open date picker",
751
+ disabled: isDisabled || isReadOnly,
752
+ "data-disabled": isDisabled || isReadOnly ? "true" : void 0,
753
+ onClick: (e) => {
754
+ if (isDisabled || isReadOnly) return;
755
+ dispatch({ type: "TOGGLE", source: "trigger" });
756
+ onClick?.(e);
757
+ },
758
+ ...props,
759
+ children
760
+ }
761
+ );
762
+ }
763
+ function useClickOutside(refs, handler, enabled = true) {
764
+ const refsRef = useRef(refs);
765
+ refsRef.current = refs;
766
+ useEffect(() => {
767
+ if (!enabled) return;
768
+ function onPointerDown(e) {
769
+ const target = e.target;
770
+ if (refsRef.current.every((r) => !r.current?.contains(target))) {
771
+ handler();
772
+ }
773
+ }
774
+ document.addEventListener("pointerdown", onPointerDown, true);
775
+ return () => document.removeEventListener("pointerdown", onPointerDown, true);
776
+ }, [enabled, handler]);
777
+ }
778
+
779
+ // src/utils/aria.ts
780
+ function getFocusableElements(container) {
781
+ const selector = [
782
+ "a[href]",
783
+ "button:not([disabled])",
784
+ "input:not([disabled])",
785
+ "select:not([disabled])",
786
+ "textarea:not([disabled])",
787
+ '[tabindex]:not([tabindex="-1"])'
788
+ ].join(", ");
789
+ return Array.from(container.querySelectorAll(selector)).filter(
790
+ (el) => !el.closest("[hidden]") && el.offsetParent !== null
791
+ );
792
+ }
793
+
794
+ // src/date-picker/use-focus-trap.ts
795
+ function useFocusTrap(containerRef, enabled = true) {
796
+ useEffect(() => {
797
+ if (!enabled || !containerRef.current) return;
798
+ const container = containerRef.current;
799
+ function onKeyDown(e) {
800
+ if (e.key !== "Tab") return;
801
+ const focusable = getFocusableElements(container);
802
+ if (!focusable.length) return;
803
+ const first = focusable[0];
804
+ const last = focusable[focusable.length - 1];
805
+ if (e.shiftKey) {
806
+ if (document.activeElement === first) {
807
+ e.preventDefault();
808
+ last.focus();
809
+ }
810
+ } else {
811
+ if (document.activeElement === last) {
812
+ e.preventDefault();
813
+ first.focus();
814
+ }
815
+ }
816
+ }
817
+ const observer = new MutationObserver(() => {
818
+ const active = document.activeElement;
819
+ if (active?.getAttribute("role") === "spinbutton") return;
820
+ if (!container.contains(active)) {
821
+ const firstFocusable = getFocusableElements(container)[0];
822
+ firstFocusable?.focus();
823
+ }
824
+ });
825
+ observer.observe(container, { childList: true, subtree: true });
826
+ container.addEventListener("keydown", onKeyDown);
827
+ return () => {
828
+ container.removeEventListener("keydown", onKeyDown);
829
+ observer.disconnect();
830
+ };
831
+ }, [enabled, containerRef]);
832
+ }
833
+ function toPlacement(side, align) {
834
+ return align === "center" ? side : `${side}-${align}`;
835
+ }
836
+ function useDatePickerFloating({
837
+ referenceElement,
838
+ open,
839
+ side = "bottom",
840
+ align = "start",
841
+ sideOffset = 4,
842
+ alignOffset = 0,
843
+ avoidCollisions = true,
844
+ collisionPadding = 8
845
+ }) {
846
+ const { refs, floatingStyles, isPositioned } = useFloating({
847
+ placement: toPlacement(side, align),
848
+ strategy: "fixed",
849
+ open,
850
+ whileElementsMounted: autoUpdate,
851
+ middleware: [
852
+ offset({ mainAxis: sideOffset, crossAxis: alignOffset }),
853
+ ...avoidCollisions ? [flip(), shift({ padding: collisionPadding })] : []
854
+ ],
855
+ ...referenceElement !== void 0 ? { elements: { reference: referenceElement } } : {}
856
+ });
857
+ return {
858
+ setReference: refs.setReference,
859
+ setFloating: refs.setFloating,
860
+ floatingStyles,
861
+ isPositioned
862
+ };
863
+ }
864
+ function Content({
865
+ children,
866
+ forceMount,
867
+ side,
868
+ align,
869
+ sideOffset,
870
+ alignOffset,
871
+ avoidCollisions,
872
+ collisionPadding,
873
+ portal = false,
874
+ style,
875
+ onKeyDown,
876
+ ...props
877
+ }) {
878
+ const { state, dispatch, ids, config } = useDatePickerContext();
879
+ const contentRef = useRef(null);
880
+ const isOpen = state.open;
881
+ const shouldRender = forceMount || isOpen;
882
+ const triggerRef = useRef(null);
883
+ const inputRef = useRef(null);
884
+ const input0Ref = useRef(null);
885
+ const input1Ref = useRef(null);
886
+ useEffect(() => {
887
+ triggerRef.current = document.getElementById(ids.trigger);
888
+ inputRef.current = document.getElementById(ids.input);
889
+ input0Ref.current = document.getElementById(`${ids.input}-0`);
890
+ input1Ref.current = document.getElementById(`${ids.input}-1`);
891
+ });
892
+ const { setReference, setFloating, floatingStyles, isPositioned } = useDatePickerFloating({
893
+ open: isOpen,
894
+ ...side !== void 0 && { side },
895
+ ...align !== void 0 && { align },
896
+ ...sideOffset !== void 0 && { sideOffset },
897
+ ...alignOffset !== void 0 && { alignOffset },
898
+ ...avoidCollisions !== void 0 && { avoidCollisions },
899
+ ...collisionPadding !== void 0 && { collisionPadding }
900
+ });
901
+ useLayoutEffect(() => {
902
+ if (!isOpen) return;
903
+ const resolveAnchor = () => document.getElementById(ids.input) || document.getElementById(`${ids.input}-0`) || document.getElementById(ids.trigger);
904
+ let current = resolveAnchor();
905
+ setReference(current);
906
+ const observer = new MutationObserver(() => {
907
+ const next = resolveAnchor();
908
+ if (next && next !== current) {
909
+ current = next;
910
+ setReference(next);
911
+ }
912
+ });
913
+ observer.observe(document.body, { childList: true, subtree: true });
914
+ return () => observer.disconnect();
915
+ }, [isOpen, ids.input, ids.trigger, setReference]);
916
+ const mergedRef = useCallback(
917
+ (node) => {
918
+ contentRef.current = node;
919
+ setFloating(node);
920
+ },
921
+ [setFloating]
922
+ );
923
+ useClickOutside(
924
+ [contentRef, triggerRef, inputRef, input0Ref, input1Ref],
925
+ () => dispatch({ type: "CLOSE" }),
926
+ isOpen
927
+ );
928
+ useFocusTrap(contentRef, isOpen);
929
+ const [transitionsReady, setTransitionsReady] = useState(false);
930
+ useEffect(() => {
931
+ if (!isOpen || !isPositioned) {
932
+ setTransitionsReady(false);
933
+ return;
934
+ }
935
+ const raf = requestAnimationFrame(() => setTransitionsReady(true));
936
+ return () => cancelAnimationFrame(raf);
937
+ }, [isOpen, isPositioned]);
938
+ useEffect(() => {
939
+ if (!isOpen || !contentRef.current) return;
940
+ if (state.openSource === "input") return;
941
+ const active = document.activeElement;
942
+ const activeRole = active?.getAttribute("role");
943
+ if (activeRole === "combobox" || activeRole === "spinbutton") return;
944
+ const firstFocusable = contentRef.current.querySelector(
945
+ 'button:not([disabled]), [tabindex="0"]'
946
+ );
947
+ firstFocusable?.focus();
948
+ }, [isOpen, state.openSource]);
949
+ const announcement = state.view === "day" ? formatMonthYear(new Date(state.focusedYear, state.focusedMonth, 1), config.locale) : state.view === "month" ? formatYear(state.focusedYear, config.locale) : `${state.yearPageStart}\u2013${state.yearPageStart + 11}`;
950
+ if (!shouldRender) return null;
951
+ const content = /* @__PURE__ */ jsxs(
952
+ "div",
953
+ {
954
+ ref: mergedRef,
955
+ id: ids.content,
956
+ role: "dialog",
957
+ "aria-modal": "true",
958
+ "aria-labelledby": ids.label,
959
+ "data-state": isOpen ? "open" : "closed",
960
+ style: {
961
+ ...floatingStyles,
962
+ ...!isOpen ? { display: "none" } : void 0,
963
+ // Hide via opacity (not `visibility`/`display`) while Floating UI
964
+ // resolves the first position: this keeps the popover at (0,0)
965
+ // invisible to sighted users for one frame without removing it from
966
+ // the accessibility tree, so assistive tech still sees the dialog.
967
+ ...isOpen && !isPositioned ? { opacity: 0, pointerEvents: "none" } : void 0,
968
+ ...isOpen && !transitionsReady ? { transition: "none" } : void 0,
969
+ ...style
970
+ },
971
+ onKeyDown: (e) => {
972
+ if (e.key === "Escape") {
973
+ e.preventDefault();
974
+ dispatch({ type: "CLOSE" });
975
+ document.getElementById(ids.trigger)?.focus();
976
+ }
977
+ onKeyDown?.(e);
978
+ },
979
+ ...props,
980
+ children: [
981
+ /* @__PURE__ */ jsx(
982
+ "span",
983
+ {
984
+ "aria-live": "polite",
985
+ "aria-atomic": "true",
986
+ style: {
987
+ position: "absolute",
988
+ width: 1,
989
+ height: 1,
990
+ overflow: "hidden",
991
+ clip: "rect(0,0,0,0)",
992
+ whiteSpace: "nowrap"
993
+ },
994
+ children: isOpen ? announcement : ""
995
+ }
996
+ ),
997
+ children
998
+ ]
999
+ }
1000
+ );
1001
+ return portal ? createPortal(content, document.body) : content;
1002
+ }
1003
+ function ViewControl({ children, ...props }) {
1004
+ return /* @__PURE__ */ jsx("div", { role: "group", ...props, children });
1005
+ }
1006
+ function PrevTrigger({ children, onClick, ...props }) {
1007
+ const { state, dispatch } = useDatePickerContext();
1008
+ const labels = {
1009
+ day: "Go to previous month",
1010
+ month: "Go to previous year",
1011
+ year: "Go to previous years"
1012
+ };
1013
+ return /* @__PURE__ */ jsx(
1014
+ "button",
1015
+ {
1016
+ type: "button",
1017
+ "aria-label": labels[state.view],
1018
+ onClick: (e) => {
1019
+ dispatch({ type: "NAV_PREV" });
1020
+ onClick?.(e);
1021
+ },
1022
+ ...props,
1023
+ children: children ?? "\u2039"
1024
+ }
1025
+ );
1026
+ }
1027
+ function NextTrigger({ children, onClick, ...props }) {
1028
+ const { state, dispatch } = useDatePickerContext();
1029
+ const labels = {
1030
+ day: "Go to next month",
1031
+ month: "Go to next year",
1032
+ year: "Go to next years"
1033
+ };
1034
+ return /* @__PURE__ */ jsx(
1035
+ "button",
1036
+ {
1037
+ type: "button",
1038
+ "aria-label": labels[state.view],
1039
+ onClick: (e) => {
1040
+ dispatch({ type: "NAV_NEXT" });
1041
+ onClick?.(e);
1042
+ },
1043
+ ...props,
1044
+ children: children ?? "\u203A"
1045
+ }
1046
+ );
1047
+ }
1048
+ var nextView = {
1049
+ day: "month",
1050
+ month: "year",
1051
+ year: "day"
1052
+ };
1053
+ var ariaLabel = {
1054
+ day: "Switch to month view",
1055
+ month: "Switch to year view",
1056
+ year: "Switch to day view"
1057
+ };
1058
+ function ViewTrigger({ children, onClick, ...props }) {
1059
+ const { state, dispatch, config } = useDatePickerContext();
1060
+ const label = formatMonthYear(new Date(state.focusedYear, state.focusedMonth, 1), config.locale);
1061
+ const yearRange = `${state.yearPageStart}\u2013${state.yearPageStart + 11}`;
1062
+ const displayLabel = state.view === "year" ? yearRange : state.view === "month" ? String(state.focusedYear) : label;
1063
+ return /* @__PURE__ */ jsx(
1064
+ "button",
1065
+ {
1066
+ type: "button",
1067
+ "aria-label": ariaLabel[state.view],
1068
+ "aria-live": "polite",
1069
+ onClick: (e) => {
1070
+ dispatch({ type: "SET_VIEW", view: nextView[state.view] });
1071
+ onClick?.(e);
1072
+ },
1073
+ ...props,
1074
+ children: children ?? displayLabel
1075
+ }
1076
+ );
1077
+ }
1078
+ function View({ view, children }) {
1079
+ const { state } = useDatePickerContext();
1080
+ if (state.view !== view) return null;
1081
+ return /* @__PURE__ */ jsx(DatePickerViewContext.Provider, { value: { view }, children });
1082
+ }
1083
+ function WeekDays({ format = "short", className }) {
1084
+ const { config } = useDatePickerContext();
1085
+ const weekStartDay = getWeekStartDay(config.locale, config.weekStartsOn);
1086
+ const days = buildWeekDays(config.locale, weekStartDay, format);
1087
+ return /* @__PURE__ */ jsx("tr", { className, children: days.map((day) => /* @__PURE__ */ jsx("th", { scope: "col", abbr: day.ariaLabel, children: day.label }, day.ariaLabel)) });
1088
+ }
1089
+ function Day({ date, children, className, style }) {
1090
+ const { state, dispatch, config } = useDatePickerContext();
1091
+ const today = startOfDay(/* @__PURE__ */ new Date());
1092
+ const meta = {
1093
+ date,
1094
+ isCurrentMonth: date.getMonth() === state.focusedMonth,
1095
+ isToday: isSameDay(date, today),
1096
+ isSelected: config.mode === "single" ? isSameDay(date, state.selectedDate) : config.mode === "multiple" ? state.selectedDates.some((d) => isSameDay(d, date)) : isSameDay(date, state.rangeStart) || isSameDay(date, state.rangeEnd),
1097
+ isDisabled: isDateDisabled(date, config),
1098
+ isRangeStart: isSameDay(date, state.rangeStart),
1099
+ isRangeEnd: isSameDay(date, state.rangeEnd ?? state.hoverDate),
1100
+ isInRange: isInRange(date, state.rangeStart, state.rangeEnd ?? state.hoverDate),
1101
+ isHovered: isSameDay(date, state.hoverDate)
1102
+ };
1103
+ const isFocused = isSameDay(date, state.focusedDate);
1104
+ return /* @__PURE__ */ jsx(
1105
+ "td",
1106
+ {
1107
+ role: "gridcell",
1108
+ "aria-selected": meta.isSelected,
1109
+ "aria-disabled": meta.isDisabled,
1110
+ "aria-current": meta.isToday ? "date" : void 0,
1111
+ tabIndex: isFocused ? 0 : -1,
1112
+ className,
1113
+ style,
1114
+ "data-selected": meta.isSelected || void 0,
1115
+ "data-today": meta.isToday || void 0,
1116
+ "data-disabled": meta.isDisabled || void 0,
1117
+ "data-outside-month": !meta.isCurrentMonth || void 0,
1118
+ "data-range-start": meta.isRangeStart || void 0,
1119
+ "data-range-end": meta.isRangeEnd || void 0,
1120
+ "data-in-range": meta.isInRange || void 0,
1121
+ onClick: () => {
1122
+ if (!meta.isDisabled && !config.readOnly) dispatch({ type: "SELECT_DATE", date });
1123
+ },
1124
+ onMouseEnter: () => {
1125
+ if (config.mode === "range") dispatch({ type: "HOVER_DATE", date });
1126
+ },
1127
+ onMouseLeave: () => {
1128
+ if (config.mode === "range") dispatch({ type: "HOVER_DATE", date: null });
1129
+ },
1130
+ onKeyDown: (e) => {
1131
+ if (e.key === "Enter" || e.key === " ") {
1132
+ e.preventDefault();
1133
+ if (!meta.isDisabled && !config.readOnly) dispatch({ type: "SELECT_DATE", date });
1134
+ }
1135
+ },
1136
+ children: children ? children(meta) : date.getDate()
1137
+ }
1138
+ );
1139
+ }
1140
+ function defaultRender(ws) {
1141
+ return ws.map((week, wi) => /* @__PURE__ */ jsx("tr", { children: week.map((day, di) => /* @__PURE__ */ jsx(Day, { date: day }, di)) }, wi));
1142
+ }
1143
+ function Grid({ children, header, className }) {
1144
+ const { state, dispatch, config } = useDatePickerContext();
1145
+ const gridRef = useRef(null);
1146
+ const weekStartDay = getWeekStartDay(config.locale, config.weekStartsOn);
1147
+ const weeks = buildCalendarGrid(state.focusedYear, state.focusedMonth, weekStartDay);
1148
+ const label = formatMonthYear(new Date(state.focusedYear, state.focusedMonth, 1), config.locale);
1149
+ const focusedDateStr = state.focusedDate?.toDateString();
1150
+ useEffect(() => {
1151
+ if (!gridRef.current || !focusedDateStr) return;
1152
+ if (document.activeElement?.getAttribute("role") === "spinbutton") return;
1153
+ if (state.openSource === "input" && !gridRef.current.contains(document.activeElement)) {
1154
+ return;
1155
+ }
1156
+ const focused = gridRef.current.querySelector('[tabindex="0"]');
1157
+ if (focused && document.activeElement !== focused) focused.focus();
1158
+ }, [focusedDateStr, state.openSource]);
1159
+ function handleKeyDown(e) {
1160
+ if (!state.focusedDate) return;
1161
+ let next = null;
1162
+ switch (e.key) {
1163
+ case "ArrowRight":
1164
+ next = addDays(state.focusedDate, 1);
1165
+ break;
1166
+ case "ArrowLeft":
1167
+ next = addDays(state.focusedDate, -1);
1168
+ break;
1169
+ case "ArrowDown":
1170
+ next = addDays(state.focusedDate, 7);
1171
+ break;
1172
+ case "ArrowUp":
1173
+ next = addDays(state.focusedDate, -7);
1174
+ break;
1175
+ case "PageDown":
1176
+ next = e.ctrlKey ? new Date(state.focusedYear + 1, state.focusedMonth, state.focusedDate.getDate()) : addMonths(state.focusedDate, 1);
1177
+ break;
1178
+ case "PageUp":
1179
+ next = e.ctrlKey ? new Date(state.focusedYear - 1, state.focusedMonth, state.focusedDate.getDate()) : addMonths(state.focusedDate, -1);
1180
+ break;
1181
+ case "Home": {
1182
+ const d = new Date(state.focusedDate);
1183
+ const diff = (d.getDay() - weekStartDay + 7) % 7;
1184
+ next = addDays(d, -diff);
1185
+ break;
1186
+ }
1187
+ case "End": {
1188
+ const d = new Date(state.focusedDate);
1189
+ const diff = (weekStartDay + 6 - d.getDay() + 7) % 7;
1190
+ next = addDays(d, diff);
1191
+ break;
1192
+ }
1193
+ default:
1194
+ return;
1195
+ }
1196
+ e.preventDefault();
1197
+ dispatch({ type: "FOCUS_DATE", date: next });
1198
+ }
1199
+ return /* @__PURE__ */ jsxs(
1200
+ "table",
1201
+ {
1202
+ ref: gridRef,
1203
+ role: "grid",
1204
+ "aria-label": label,
1205
+ className,
1206
+ onKeyDown: handleKeyDown,
1207
+ children: [
1208
+ header && /* @__PURE__ */ jsx("thead", { children: header }),
1209
+ /* @__PURE__ */ jsx("tbody", { children: children ? children({ weeks }) : defaultRender(weeks) })
1210
+ ]
1211
+ }
1212
+ );
1213
+ }
1214
+ function MonthGrid({ children, className }) {
1215
+ const { state, config, dispatch } = useDatePickerContext();
1216
+ const months = buildMonthItems(
1217
+ state.focusedYear,
1218
+ config.locale,
1219
+ state.focusedMonth,
1220
+ config.minDate,
1221
+ config.maxDate
1222
+ );
1223
+ const [focusedIndex, setFocusedIndex] = useState(state.focusedMonth);
1224
+ const gridRef = useRef(null);
1225
+ const focusedMonthRef = useRef(state.focusedMonth);
1226
+ focusedMonthRef.current = state.focusedMonth;
1227
+ useEffect(() => {
1228
+ setFocusedIndex(focusedMonthRef.current);
1229
+ }, [state.focusedYear]);
1230
+ useEffect(() => {
1231
+ const focused = gridRef.current?.querySelector('[tabindex="0"]');
1232
+ if (focused && gridRef.current?.contains(document.activeElement)) {
1233
+ focused.focus();
1234
+ }
1235
+ }, [focusedIndex, state.focusedYear]);
1236
+ function handleKeyDown(e) {
1237
+ let next = focusedIndex;
1238
+ switch (e.key) {
1239
+ case "ArrowRight":
1240
+ next = Math.min(focusedIndex + 1, 11);
1241
+ break;
1242
+ case "ArrowLeft":
1243
+ next = Math.max(focusedIndex - 1, 0);
1244
+ break;
1245
+ case "ArrowDown":
1246
+ next = Math.min(focusedIndex + 3, 11);
1247
+ break;
1248
+ case "ArrowUp":
1249
+ next = Math.max(focusedIndex - 3, 0);
1250
+ break;
1251
+ case "Home":
1252
+ next = 0;
1253
+ break;
1254
+ case "End":
1255
+ next = 11;
1256
+ break;
1257
+ case "PageDown":
1258
+ dispatch({ type: "NAV_NEXT" });
1259
+ e.preventDefault();
1260
+ return;
1261
+ case "PageUp":
1262
+ dispatch({ type: "NAV_PREV" });
1263
+ e.preventDefault();
1264
+ return;
1265
+ case "Escape":
1266
+ dispatch({ type: "SET_VIEW", view: "day" });
1267
+ e.preventDefault();
1268
+ return;
1269
+ case "Enter":
1270
+ case " ":
1271
+ dispatch({ type: "SELECT_MONTH", month: focusedIndex });
1272
+ e.preventDefault();
1273
+ return;
1274
+ default:
1275
+ return;
1276
+ }
1277
+ e.preventDefault();
1278
+ setFocusedIndex(next);
1279
+ }
1280
+ return /* @__PURE__ */ jsx(MonthGridFocusContext.Provider, { value: focusedIndex, children: /* @__PURE__ */ jsx(
1281
+ "div",
1282
+ {
1283
+ ref: gridRef,
1284
+ role: "grid",
1285
+ "aria-label": String(state.focusedYear),
1286
+ className,
1287
+ onKeyDown: handleKeyDown,
1288
+ children: children({ months })
1289
+ }
1290
+ ) });
1291
+ }
1292
+ function MonthCell({ value, children, onClick, ...props }) {
1293
+ const { dispatch } = useDatePickerContext();
1294
+ const focusedIndex = useContext(MonthGridFocusContext);
1295
+ const isFocused = focusedIndex === value;
1296
+ return /* @__PURE__ */ jsx(
1297
+ "button",
1298
+ {
1299
+ type: "button",
1300
+ role: "gridcell",
1301
+ "aria-selected": isFocused,
1302
+ tabIndex: isFocused ? 0 : -1,
1303
+ "data-selected": isFocused || void 0,
1304
+ onClick: (e) => {
1305
+ dispatch({ type: "SELECT_MONTH", month: value });
1306
+ onClick?.(e);
1307
+ },
1308
+ ...props,
1309
+ children
1310
+ }
1311
+ );
1312
+ }
1313
+ function YearGrid({ children, className }) {
1314
+ const { state, config, dispatch } = useDatePickerContext();
1315
+ const years = buildYearItems(
1316
+ state.yearPageStart,
1317
+ 12,
1318
+ state.focusedYear,
1319
+ config.minDate,
1320
+ config.maxDate
1321
+ );
1322
+ const [focusedYear, setFocusedYear] = useState(state.focusedYear);
1323
+ const gridRef = useRef(null);
1324
+ useEffect(() => {
1325
+ const inPage = focusedYear >= state.yearPageStart && focusedYear < state.yearPageStart + 12;
1326
+ if (!inPage) setFocusedYear(state.yearPageStart);
1327
+ }, [state.yearPageStart]);
1328
+ useEffect(() => {
1329
+ const focused = gridRef.current?.querySelector('[tabindex="0"]');
1330
+ if (focused && gridRef.current?.contains(document.activeElement)) {
1331
+ focused.focus();
1332
+ }
1333
+ }, [focusedYear, state.yearPageStart]);
1334
+ function handleKeyDown(e) {
1335
+ let next = focusedYear;
1336
+ const pageStart = state.yearPageStart;
1337
+ const pageEnd = pageStart + 11;
1338
+ switch (e.key) {
1339
+ case "ArrowRight":
1340
+ next = Math.min(focusedYear + 1, pageEnd);
1341
+ break;
1342
+ case "ArrowLeft":
1343
+ next = Math.max(focusedYear - 1, pageStart);
1344
+ break;
1345
+ // 4-column grid: ↓/↑ = ±4 years (next/prev row)
1346
+ case "ArrowDown":
1347
+ next = Math.min(focusedYear + 4, pageEnd);
1348
+ break;
1349
+ case "ArrowUp":
1350
+ next = Math.max(focusedYear - 4, pageStart);
1351
+ break;
1352
+ case "Home":
1353
+ next = pageStart;
1354
+ break;
1355
+ case "End":
1356
+ next = pageEnd;
1357
+ break;
1358
+ case "PageDown":
1359
+ dispatch({ type: "YEAR_PAGE_NEXT" });
1360
+ e.preventDefault();
1361
+ return;
1362
+ case "PageUp":
1363
+ dispatch({ type: "YEAR_PAGE_PREV" });
1364
+ e.preventDefault();
1365
+ return;
1366
+ case "Escape":
1367
+ dispatch({ type: "SET_VIEW", view: "month" });
1368
+ e.preventDefault();
1369
+ return;
1370
+ case "Enter":
1371
+ case " ":
1372
+ dispatch({ type: "SELECT_YEAR", year: focusedYear });
1373
+ e.preventDefault();
1374
+ return;
1375
+ default:
1376
+ return;
1377
+ }
1378
+ e.preventDefault();
1379
+ setFocusedYear(next);
1380
+ }
1381
+ return /* @__PURE__ */ jsx(YearGridFocusContext.Provider, { value: focusedYear, children: /* @__PURE__ */ jsx(
1382
+ "div",
1383
+ {
1384
+ ref: gridRef,
1385
+ role: "grid",
1386
+ "aria-label": `${state.yearPageStart}\u2013${state.yearPageStart + 11}`,
1387
+ className,
1388
+ onKeyDown: handleKeyDown,
1389
+ children: children({ years })
1390
+ }
1391
+ ) });
1392
+ }
1393
+ function YearCell({ value, children, onClick, ...props }) {
1394
+ const { dispatch } = useDatePickerContext();
1395
+ const focusedYear = useContext(YearGridFocusContext);
1396
+ const isFocused = focusedYear === value;
1397
+ return /* @__PURE__ */ jsx(
1398
+ "button",
1399
+ {
1400
+ type: "button",
1401
+ role: "gridcell",
1402
+ "aria-selected": isFocused,
1403
+ tabIndex: isFocused ? 0 : -1,
1404
+ "data-selected": isFocused || void 0,
1405
+ onClick: (e) => {
1406
+ dispatch({ type: "SELECT_YEAR", year: value });
1407
+ onClick?.(e);
1408
+ },
1409
+ ...props,
1410
+ children
1411
+ }
1412
+ );
1413
+ }
1414
+ function Calendar({ className }) {
1415
+ return /* @__PURE__ */ jsxs("div", { className, children: [
1416
+ /* @__PURE__ */ jsxs(ViewControl, { children: [
1417
+ /* @__PURE__ */ jsx(PrevTrigger, {}),
1418
+ /* @__PURE__ */ jsx(ViewTrigger, {}),
1419
+ /* @__PURE__ */ jsx(NextTrigger, {})
1420
+ ] }),
1421
+ /* @__PURE__ */ jsx(View, { view: "day", children: /* @__PURE__ */ jsx(Grid, { header: /* @__PURE__ */ jsx(WeekDays, {}) }) }),
1422
+ /* @__PURE__ */ jsx(View, { view: "month", children: /* @__PURE__ */ jsx(MonthGrid, { children: ({ months }) => /* @__PURE__ */ jsx("div", { style: { display: "grid", gridTemplateColumns: "repeat(3, 1fr)" }, children: months.map((m) => /* @__PURE__ */ jsx(MonthCell, { value: m.value, disabled: m.isDisabled, children: m.label }, m.value)) }) }) }),
1423
+ /* @__PURE__ */ jsx(View, { view: "year", children: /* @__PURE__ */ jsx(YearGrid, { children: ({ years }) => /* @__PURE__ */ jsx("div", { style: { display: "grid", gridTemplateColumns: "repeat(4, 1fr)" }, children: years.map((y) => /* @__PURE__ */ jsx(YearCell, { value: y.value, disabled: y.isDisabled, children: y.value }, y.value)) }) }) })
1424
+ ] });
1425
+ }
1426
+
1427
+ export { date_picker_exports as DatePicker, useDatePickerContext };
1428
+ //# sourceMappingURL=index.js.map
1429
+ //# sourceMappingURL=index.js.map