@torq-ui/react-date-picker 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1478 @@
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_RANGE": {
342
+ const start = action.start ? startOfDay(action.start) : null;
343
+ const end = action.end ? startOfDay(action.end) : null;
344
+ const anchor = end ?? start;
345
+ return {
346
+ ...state,
347
+ rangeStart: start,
348
+ rangeEnd: end,
349
+ hoverDate: null,
350
+ ...anchor ? {
351
+ focusedDate: anchor,
352
+ focusedMonth: anchor.getMonth(),
353
+ focusedYear: anchor.getFullYear(),
354
+ yearPageStart: yearPageStart(anchor.getFullYear())
355
+ } : {}
356
+ };
357
+ }
358
+ case "SET_INPUT":
359
+ return { ...state, inputValue: action.value };
360
+ case "COMMIT_INPUT": {
361
+ const parsed = parseDate(state.inputValue, config.locale);
362
+ if (!parsed || isDateDisabled(parsed, config)) return { ...state, inputValue: "" };
363
+ const d = startOfDay(parsed);
364
+ return {
365
+ ...state,
366
+ selectedDate: d,
367
+ focusedDate: d,
368
+ focusedMonth: d.getMonth(),
369
+ focusedYear: d.getFullYear(),
370
+ open: false
371
+ };
372
+ }
373
+ case "SELECT_MONTH":
374
+ return { ...state, focusedMonth: action.month, view: "day" };
375
+ case "SELECT_YEAR":
376
+ return {
377
+ ...state,
378
+ focusedYear: action.year,
379
+ yearPageStart: yearPageStart(action.year),
380
+ view: "month"
381
+ };
382
+ case "YEAR_PAGE_PREV":
383
+ return { ...state, yearPageStart: state.yearPageStart - 12 };
384
+ case "YEAR_PAGE_NEXT":
385
+ return { ...state, yearPageStart: state.yearPageStart + 12 };
386
+ default:
387
+ return state;
388
+ }
389
+ }
390
+ function createInitialState(config) {
391
+ const ref = config.selectedDate ?? config.rangeStart ?? /* @__PURE__ */ new Date();
392
+ return {
393
+ open: config.open ?? false,
394
+ openSource: null,
395
+ view: "day",
396
+ focusedMonth: ref.getMonth(),
397
+ focusedYear: ref.getFullYear(),
398
+ focusedDate: config.open ? startOfDay(ref) : null,
399
+ selectedDate: config.selectedDate ?? null,
400
+ rangeStart: config.rangeStart ?? null,
401
+ rangeEnd: config.rangeEnd ?? null,
402
+ hoverDate: null,
403
+ selectedDates: config.selectedDates ?? [],
404
+ inputValue: config.selectedDate ? formatDate(config.selectedDate, config.locale) : "",
405
+ yearPageStart: yearPageStart(ref.getFullYear())
406
+ };
407
+ }
408
+
409
+ // src/date-picker/use-date-picker.ts
410
+ function resolveConfig(props) {
411
+ const base = {
412
+ mode: props.mode ?? "single",
413
+ locale: props.locale ?? (typeof navigator !== "undefined" ? navigator.language : "en-US"),
414
+ readOnly: props.readOnly ?? false,
415
+ closeOnSelect: props.closeOnSelect ?? (props.mode !== "range" && props.mode !== "multiple"),
416
+ ...props.weekStartsOn !== void 0 && { weekStartsOn: props.weekStartsOn },
417
+ ...props.minDate !== void 0 && { minDate: props.minDate },
418
+ ...props.maxDate !== void 0 && { maxDate: props.maxDate },
419
+ ...props.disabled !== void 0 && { disabled: props.disabled }
420
+ };
421
+ return base;
422
+ }
423
+ function resolveInitialValue(props) {
424
+ if (!props.mode || props.mode === "single") {
425
+ const v = "value" in props && props.value !== void 0 ? props.value : "defaultValue" in props ? props.defaultValue : void 0;
426
+ return { selectedDate: v ?? null };
427
+ }
428
+ if (props.mode === "range") {
429
+ const v = "value" in props && props.value !== void 0 ? props.value : "defaultValue" in props ? props.defaultValue : void 0;
430
+ return { rangeStart: v?.start ?? null, rangeEnd: v?.end ?? null };
431
+ }
432
+ if (props.mode === "multiple") {
433
+ const v = "defaultValue" in props ? props.defaultValue : void 0;
434
+ return { selectedDates: v ?? [] };
435
+ }
436
+ return {};
437
+ }
438
+ function useDatePicker(props) {
439
+ const uid = useId();
440
+ const { mode, locale, weekStartsOn, minDate, maxDate, disabled, readOnly, closeOnSelect } = props;
441
+ const config = useMemo(
442
+ () => resolveConfig({
443
+ mode,
444
+ locale,
445
+ weekStartsOn,
446
+ minDate,
447
+ maxDate,
448
+ disabled,
449
+ readOnly,
450
+ closeOnSelect
451
+ }),
452
+ [mode, locale, weekStartsOn, minDate, maxDate, disabled, readOnly, closeOnSelect]
453
+ );
454
+ const initialValue = resolveInitialValue(props);
455
+ const [state, dispatch] = useReducer(
456
+ (s, a) => datePickerReducer(s, a, config),
457
+ createInitialState({
458
+ locale: config.locale,
459
+ ...props.defaultOpen !== void 0 && { open: props.defaultOpen },
460
+ ...initialValue
461
+ })
462
+ );
463
+ const onValueChangeRef = useRef(
464
+ !props.mode || props.mode === "single" ? props.onValueChange : void 0
465
+ );
466
+ useEffect(() => {
467
+ if (!props.mode || props.mode === "single") {
468
+ onValueChangeRef.current = props.onValueChange;
469
+ }
470
+ });
471
+ const prevSelectedRef = useRef(void 0);
472
+ useEffect(() => {
473
+ if (prevSelectedRef.current === void 0) {
474
+ prevSelectedRef.current = state.selectedDate;
475
+ return;
476
+ }
477
+ if (state.selectedDate !== prevSelectedRef.current) {
478
+ onValueChangeRef.current?.(state.selectedDate);
479
+ prevSelectedRef.current = state.selectedDate;
480
+ }
481
+ }, [state.selectedDate]);
482
+ const controlledRangeValue = props.mode === "range" && "value" in props && props.value !== void 0 ? props.value : void 0;
483
+ const lastSyncedPropRangeRef = useRef(
484
+ null
485
+ );
486
+ const propRangeStartT = controlledRangeValue?.start?.getTime() ?? null;
487
+ const propRangeEndT = controlledRangeValue?.end?.getTime() ?? null;
488
+ useEffect(() => {
489
+ if (controlledRangeValue === void 0) return;
490
+ const last = lastSyncedPropRangeRef.current;
491
+ if (last?.startT === propRangeStartT && last?.endT === propRangeEndT) return;
492
+ lastSyncedPropRangeRef.current = { startT: propRangeStartT, endT: propRangeEndT };
493
+ const stateStartT = state.rangeStart?.getTime() ?? null;
494
+ const stateEndT = state.rangeEnd?.getTime() ?? null;
495
+ if (propRangeStartT === stateStartT && propRangeEndT === stateEndT) return;
496
+ dispatch({
497
+ type: "SET_RANGE",
498
+ start: controlledRangeValue.start ?? null,
499
+ end: controlledRangeValue.end ?? null
500
+ });
501
+ }, [propRangeStartT, propRangeEndT, dispatch]);
502
+ const prevRangeRef = useRef(
503
+ void 0
504
+ );
505
+ useEffect(() => {
506
+ if (props.mode !== "range") return;
507
+ const p = props;
508
+ const startT = state.rangeStart?.getTime() ?? null;
509
+ const endT = state.rangeEnd?.getTime() ?? null;
510
+ if (prevRangeRef.current === void 0) {
511
+ prevRangeRef.current = { startT, endT };
512
+ return;
513
+ }
514
+ if (startT !== prevRangeRef.current.startT || endT !== prevRangeRef.current.endT) {
515
+ p.onValueChange?.({ start: state.rangeStart, end: state.rangeEnd });
516
+ prevRangeRef.current = { startT, endT };
517
+ }
518
+ }, [state.rangeStart, state.rangeEnd]);
519
+ const prevMultipleRef = useRef(void 0);
520
+ useEffect(() => {
521
+ if (props.mode !== "multiple") return;
522
+ const p = props;
523
+ if (prevMultipleRef.current === void 0) {
524
+ prevMultipleRef.current = state.selectedDates;
525
+ return;
526
+ }
527
+ if (state.selectedDates !== prevMultipleRef.current) {
528
+ p.onValueChange?.(state.selectedDates);
529
+ prevMultipleRef.current = state.selectedDates;
530
+ }
531
+ }, [state.selectedDates]);
532
+ const isControlledOpen = props.open !== void 0;
533
+ const effectiveOpen = isControlledOpen ? props.open ?? false : state.open;
534
+ const prevOpenRef = useRef(effectiveOpen);
535
+ useEffect(() => {
536
+ if (prevOpenRef.current !== effectiveOpen) {
537
+ props.onOpenChange?.(effectiveOpen);
538
+ }
539
+ prevOpenRef.current = effectiveOpen;
540
+ }, [effectiveOpen]);
541
+ const weekStartDay = useMemo(
542
+ () => getWeekStartDay(config.locale, config.weekStartsOn),
543
+ [config.locale, config.weekStartsOn]
544
+ );
545
+ const weeks = useMemo(
546
+ () => buildCalendarGrid(state.focusedYear, state.focusedMonth, weekStartDay),
547
+ [state.focusedYear, state.focusedMonth, weekStartDay]
548
+ );
549
+ const weekDays = useMemo(
550
+ () => buildWeekDays(config.locale, weekStartDay),
551
+ [config.locale, weekStartDay]
552
+ );
553
+ const monthItems = useMemo(
554
+ () => buildMonthItems(
555
+ state.focusedYear,
556
+ config.locale,
557
+ state.focusedMonth,
558
+ config.minDate,
559
+ config.maxDate
560
+ ),
561
+ [state.focusedYear, config.locale, state.focusedMonth, config.minDate, config.maxDate]
562
+ );
563
+ const yearItems = useMemo(
564
+ () => buildYearItems(state.yearPageStart, 12, state.focusedYear, config.minDate, config.maxDate),
565
+ [state.yearPageStart, state.focusedYear, config.minDate, config.maxDate]
566
+ );
567
+ const ids = useMemo(
568
+ () => ({
569
+ root: `dp-${uid}`,
570
+ label: `dp-label-${uid}`,
571
+ input: `dp-input-${uid}`,
572
+ trigger: `dp-trigger-${uid}`,
573
+ content: `dp-content-${uid}`
574
+ }),
575
+ [uid]
576
+ );
577
+ return {
578
+ state: { ...state, open: effectiveOpen },
579
+ dispatch,
580
+ config,
581
+ ids,
582
+ derived: { weeks, weekDays, monthItems, yearItems, weekStartDay }
583
+ };
584
+ }
585
+ function Root(props) {
586
+ const { children, ...pickerProps } = props;
587
+ const picker = useDatePicker(pickerProps);
588
+ return /* @__PURE__ */ jsx(
589
+ DatePickerContext.Provider,
590
+ {
591
+ value: {
592
+ state: picker.state,
593
+ dispatch: picker.dispatch,
594
+ config: picker.config,
595
+ ids: picker.ids
596
+ },
597
+ children
598
+ }
599
+ );
600
+ }
601
+ function Label({ children, ...props }) {
602
+ const { ids } = useDatePickerContext();
603
+ return /* @__PURE__ */ jsx("label", { id: ids.label, htmlFor: ids.input, ...props, children });
604
+ }
605
+ function wrapRef(ref) {
606
+ return (el) => {
607
+ ref(el);
608
+ };
609
+ }
610
+ var DEFAULT_LABELS = { month: "Month", day: "Day", year: "Year" };
611
+ var FIELD_MAP = {
612
+ year: "years",
613
+ month: "months",
614
+ day: "days"
615
+ };
616
+ function Segments({
617
+ sourceDate,
618
+ segmentOrder,
619
+ separator,
620
+ labels,
621
+ onDateChange,
622
+ groupId,
623
+ className,
624
+ style
625
+ }) {
626
+ const { state, dispatch, ids, config } = useDatePickerContext();
627
+ const onDateChangeRef = useRef(onDateChange);
628
+ onDateChangeRef.current = onDateChange;
629
+ const opts = {
630
+ wrapAround: true,
631
+ onChangeDate: (d) => onDateChangeRef.current(d)
632
+ };
633
+ if (config.minDate) opts.minDate = config.minDate;
634
+ if (config.maxDate) opts.maxDate = config.maxDate;
635
+ if (sourceDate) opts.date = sourceDate;
636
+ const { getRootProps, getInputProps } = useTimescape(opts);
637
+ const [focused, setFocused] = useState(null);
638
+ function handleSegmentFocus(field) {
639
+ setFocused(field);
640
+ if (!state.open && !config.readOnly) dispatch({ type: "OPEN", source: "input" });
641
+ }
642
+ function handleSegmentBlur(field, e) {
643
+ const relatedTarget = e.relatedTarget;
644
+ const isMovingWithin = e.currentTarget.parentElement?.contains(relatedTarget);
645
+ if (!isMovingWithin) setFocused(null);
646
+ }
647
+ function handleKeyDown(e) {
648
+ if (config.readOnly) {
649
+ e.preventDefault();
650
+ return;
651
+ }
652
+ if (e.key === "Escape") {
653
+ e.preventDefault();
654
+ if (state.open) dispatch({ type: "CLOSE" });
655
+ }
656
+ }
657
+ const { ref: rootRef, ...rootProps } = getRootProps();
658
+ return /* @__PURE__ */ jsx(
659
+ "div",
660
+ {
661
+ role: "group",
662
+ id: groupId ?? ids.input,
663
+ "aria-labelledby": ids.label,
664
+ tabIndex: -1,
665
+ className,
666
+ style,
667
+ ...rootProps,
668
+ ref: wrapRef(rootRef),
669
+ ...config.readOnly ? { "data-readonly": "true" } : {},
670
+ children: segmentOrder.map((field, i) => {
671
+ const type = FIELD_MAP[field];
672
+ const isFocused = focused === field;
673
+ const isTabStop = isFocused || focused === null && i === 0;
674
+ const { ref: inputRef, ...inputProps } = getInputProps(type);
675
+ const wrappedInputRef = wrapRef(inputRef);
676
+ return /* @__PURE__ */ jsxs(React.Fragment, { children: [
677
+ i > 0 && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", "data-separator": "true", children: separator }),
678
+ /* @__PURE__ */ jsx(
679
+ "input",
680
+ {
681
+ ...inputProps,
682
+ ref: wrappedInputRef,
683
+ "aria-label": labels[field],
684
+ "data-segment": field,
685
+ tabIndex: config.readOnly ? -1 : isTabStop ? 0 : -1,
686
+ disabled: config.readOnly,
687
+ onFocus: (e) => {
688
+ inputRef(e.currentTarget);
689
+ handleSegmentFocus(field);
690
+ },
691
+ onBlur: (e) => handleSegmentBlur(field, e),
692
+ onKeyDown: handleKeyDown,
693
+ className: "timescape-input",
694
+ style: {
695
+ width: field === "year" ? "4ch" : "2ch",
696
+ border: "none",
697
+ background: "transparent",
698
+ padding: 0,
699
+ margin: 0,
700
+ textAlign: "center",
701
+ font: "inherit",
702
+ outline: "none"
703
+ }
704
+ }
705
+ )
706
+ ] }, field);
707
+ })
708
+ }
709
+ );
710
+ }
711
+ function Input({ index, segmentLabels, className, style }) {
712
+ const { state, dispatch, config, ids } = useDatePickerContext();
713
+ const { order: segmentOrder, separator } = useMemo(
714
+ () => getSegmentInfo(config.locale),
715
+ [config.locale]
716
+ );
717
+ const labels = {
718
+ month: segmentLabels?.month ?? DEFAULT_LABELS.month,
719
+ day: segmentLabels?.day ?? DEFAULT_LABELS.day,
720
+ year: segmentLabels?.year ?? DEFAULT_LABELS.year
721
+ };
722
+ const sourceDate = index === 1 ? state.rangeEnd : index === 0 ? state.rangeStart : state.selectedDate;
723
+ const isInternalChangeRef = useRef(false);
724
+ const prevSourceDateRef = useRef(sourceDate);
725
+ const [timescapeKey, setTimescapeKey] = useState(0);
726
+ useEffect(() => {
727
+ const prev = prevSourceDateRef.current;
728
+ prevSourceDateRef.current = sourceDate;
729
+ if (isInternalChangeRef.current) {
730
+ isInternalChangeRef.current = false;
731
+ return;
732
+ }
733
+ const prevTime = prev?.getTime();
734
+ const currTime = sourceDate?.getTime();
735
+ if (prevTime !== currTime) {
736
+ setTimescapeKey((k) => k + 1);
737
+ }
738
+ }, [sourceDate]);
739
+ const onDateChange = useCallback(
740
+ (nextDate) => {
741
+ if (!nextDate) return;
742
+ if (isDateDisabled(nextDate, config)) return;
743
+ const prevDate = prevSourceDateRef.current;
744
+ if (prevDate && prevDate.getTime() === nextDate.getTime()) return;
745
+ if (config.mode === "multiple") {
746
+ dispatch({ type: "FOCUS_DATE", date: nextDate });
747
+ dispatch({ type: "TOGGLE_DATE", date: nextDate });
748
+ setTimescapeKey((k) => k + 1);
749
+ return;
750
+ }
751
+ isInternalChangeRef.current = true;
752
+ dispatch({ type: "FOCUS_DATE", date: nextDate });
753
+ if (config.mode === "single") {
754
+ dispatch({ type: "SELECT_DATE", date: nextDate });
755
+ } else if (config.mode === "range" && index === 0) {
756
+ dispatch({ type: "ANCHOR_DATE", date: nextDate });
757
+ } else if (config.mode === "range" && index === 1) {
758
+ dispatch({ type: "EXTEND_RANGE", date: nextDate });
759
+ }
760
+ },
761
+ [config, dispatch, index]
762
+ );
763
+ const groupId = index !== void 0 ? `${ids.input}-${index}` : ids.input;
764
+ return /* @__PURE__ */ jsx(
765
+ Segments,
766
+ {
767
+ sourceDate,
768
+ segmentOrder,
769
+ separator,
770
+ labels,
771
+ onDateChange,
772
+ groupId,
773
+ className,
774
+ style
775
+ },
776
+ timescapeKey
777
+ );
778
+ }
779
+ function Trigger({ children, onClick, disabled, ...props }) {
780
+ const { state, dispatch, ids, config } = useDatePickerContext();
781
+ const isDisabled = disabled ?? config.disabled === true;
782
+ const isReadOnly = config.readOnly;
783
+ return /* @__PURE__ */ jsx(
784
+ "button",
785
+ {
786
+ type: "button",
787
+ id: ids.trigger,
788
+ "aria-haspopup": "dialog",
789
+ "aria-expanded": state.open,
790
+ "aria-controls": state.open ? ids.content : void 0,
791
+ "aria-label": children ? void 0 : "Open date picker",
792
+ disabled: isDisabled || isReadOnly,
793
+ "data-disabled": isDisabled || isReadOnly ? "true" : void 0,
794
+ onClick: (e) => {
795
+ if (isDisabled || isReadOnly) return;
796
+ dispatch({ type: "TOGGLE", source: "trigger" });
797
+ onClick?.(e);
798
+ },
799
+ ...props,
800
+ children
801
+ }
802
+ );
803
+ }
804
+ function useClickOutside(refs, handler, enabled = true) {
805
+ const refsRef = useRef(refs);
806
+ refsRef.current = refs;
807
+ useEffect(() => {
808
+ if (!enabled) return;
809
+ function onPointerDown(e) {
810
+ const target = e.target;
811
+ if (refsRef.current.every((r) => !r.current?.contains(target))) {
812
+ handler();
813
+ }
814
+ }
815
+ document.addEventListener("pointerdown", onPointerDown, true);
816
+ return () => document.removeEventListener("pointerdown", onPointerDown, true);
817
+ }, [enabled, handler]);
818
+ }
819
+
820
+ // src/utils/aria.ts
821
+ function getFocusableElements(container) {
822
+ const selector = [
823
+ "a[href]",
824
+ "button:not([disabled])",
825
+ "input:not([disabled])",
826
+ "select:not([disabled])",
827
+ "textarea:not([disabled])",
828
+ '[tabindex]:not([tabindex="-1"])'
829
+ ].join(", ");
830
+ return Array.from(container.querySelectorAll(selector)).filter(
831
+ (el) => !el.closest("[hidden]") && el.offsetParent !== null
832
+ );
833
+ }
834
+
835
+ // src/date-picker/use-focus-trap.ts
836
+ function useFocusTrap(containerRef, enabled = true) {
837
+ useEffect(() => {
838
+ if (!enabled || !containerRef.current) return;
839
+ const container = containerRef.current;
840
+ function onKeyDown(e) {
841
+ if (e.key !== "Tab") return;
842
+ const focusable = getFocusableElements(container);
843
+ if (!focusable.length) return;
844
+ const first = focusable[0];
845
+ const last = focusable[focusable.length - 1];
846
+ if (e.shiftKey) {
847
+ if (document.activeElement === first) {
848
+ e.preventDefault();
849
+ last.focus();
850
+ }
851
+ } else {
852
+ if (document.activeElement === last) {
853
+ e.preventDefault();
854
+ first.focus();
855
+ }
856
+ }
857
+ }
858
+ const observer = new MutationObserver(() => {
859
+ const active = document.activeElement;
860
+ if (active?.getAttribute("role") === "spinbutton") return;
861
+ if (!container.contains(active)) {
862
+ const firstFocusable = getFocusableElements(container)[0];
863
+ firstFocusable?.focus();
864
+ }
865
+ });
866
+ observer.observe(container, { childList: true, subtree: true });
867
+ container.addEventListener("keydown", onKeyDown);
868
+ return () => {
869
+ container.removeEventListener("keydown", onKeyDown);
870
+ observer.disconnect();
871
+ };
872
+ }, [enabled, containerRef]);
873
+ }
874
+ function toPlacement(side, align) {
875
+ return align === "center" ? side : `${side}-${align}`;
876
+ }
877
+ function useDatePickerFloating({
878
+ referenceElement,
879
+ open,
880
+ side = "bottom",
881
+ align = "start",
882
+ sideOffset = 4,
883
+ alignOffset = 0,
884
+ avoidCollisions = true,
885
+ collisionPadding = 8
886
+ }) {
887
+ const { refs, floatingStyles, isPositioned } = useFloating({
888
+ placement: toPlacement(side, align),
889
+ strategy: "fixed",
890
+ open,
891
+ whileElementsMounted: autoUpdate,
892
+ middleware: [
893
+ offset({ mainAxis: sideOffset, crossAxis: alignOffset }),
894
+ ...avoidCollisions ? [flip(), shift({ padding: collisionPadding })] : []
895
+ ],
896
+ ...referenceElement !== void 0 ? { elements: { reference: referenceElement } } : {}
897
+ });
898
+ return {
899
+ setReference: refs.setReference,
900
+ setFloating: refs.setFloating,
901
+ floatingStyles,
902
+ isPositioned
903
+ };
904
+ }
905
+ function Content({
906
+ children,
907
+ forceMount,
908
+ side,
909
+ align,
910
+ sideOffset,
911
+ alignOffset,
912
+ avoidCollisions,
913
+ collisionPadding,
914
+ portal = false,
915
+ style,
916
+ onKeyDown,
917
+ ...props
918
+ }) {
919
+ const { state, dispatch, ids, config } = useDatePickerContext();
920
+ const contentRef = useRef(null);
921
+ const isOpen = state.open;
922
+ const shouldRender = forceMount || isOpen;
923
+ const triggerRef = useRef(null);
924
+ const inputRef = useRef(null);
925
+ const input0Ref = useRef(null);
926
+ const input1Ref = useRef(null);
927
+ useEffect(() => {
928
+ triggerRef.current = document.getElementById(ids.trigger);
929
+ inputRef.current = document.getElementById(ids.input);
930
+ input0Ref.current = document.getElementById(`${ids.input}-0`);
931
+ input1Ref.current = document.getElementById(`${ids.input}-1`);
932
+ });
933
+ const { setReference, setFloating, floatingStyles, isPositioned } = useDatePickerFloating({
934
+ open: isOpen,
935
+ ...side !== void 0 && { side },
936
+ ...align !== void 0 && { align },
937
+ ...sideOffset !== void 0 && { sideOffset },
938
+ ...alignOffset !== void 0 && { alignOffset },
939
+ ...avoidCollisions !== void 0 && { avoidCollisions },
940
+ ...collisionPadding !== void 0 && { collisionPadding }
941
+ });
942
+ useLayoutEffect(() => {
943
+ if (!isOpen) return;
944
+ const resolveAnchor = () => {
945
+ const input = document.getElementById(ids.input) ?? document.getElementById(`${ids.input}-0`);
946
+ const trigger = document.getElementById(ids.trigger);
947
+ if (input && trigger) {
948
+ const row = input.parentElement;
949
+ if (row && row === trigger.parentElement) return row;
950
+ }
951
+ return input ?? document.getElementById(`${ids.input}-1`) ?? trigger;
952
+ };
953
+ let current = resolveAnchor();
954
+ setReference(current);
955
+ const observer = new MutationObserver(() => {
956
+ const next = resolveAnchor();
957
+ if (next && next !== current) {
958
+ current = next;
959
+ setReference(next);
960
+ }
961
+ });
962
+ observer.observe(document.body, { childList: true, subtree: true });
963
+ return () => observer.disconnect();
964
+ }, [isOpen, ids.input, ids.trigger, setReference]);
965
+ const mergedRef = useCallback(
966
+ (node) => {
967
+ contentRef.current = node;
968
+ setFloating(node);
969
+ },
970
+ [setFloating]
971
+ );
972
+ useClickOutside(
973
+ [contentRef, triggerRef, inputRef, input0Ref, input1Ref],
974
+ () => dispatch({ type: "CLOSE" }),
975
+ isOpen
976
+ );
977
+ useFocusTrap(contentRef, isOpen);
978
+ const [transitionsReady, setTransitionsReady] = useState(false);
979
+ useEffect(() => {
980
+ if (!isOpen || !isPositioned) {
981
+ setTransitionsReady(false);
982
+ return;
983
+ }
984
+ const raf = requestAnimationFrame(() => setTransitionsReady(true));
985
+ return () => cancelAnimationFrame(raf);
986
+ }, [isOpen, isPositioned]);
987
+ useEffect(() => {
988
+ if (!isOpen || !contentRef.current) return;
989
+ if (state.openSource === "input") return;
990
+ const active = document.activeElement;
991
+ const activeRole = active?.getAttribute("role");
992
+ if (activeRole === "combobox" || activeRole === "spinbutton") return;
993
+ const firstFocusable = contentRef.current.querySelector(
994
+ 'button:not([disabled]), [tabindex="0"]'
995
+ );
996
+ firstFocusable?.focus();
997
+ }, [isOpen, state.openSource]);
998
+ 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}`;
999
+ if (!shouldRender) return null;
1000
+ const content = /* @__PURE__ */ jsxs(
1001
+ "div",
1002
+ {
1003
+ ref: mergedRef,
1004
+ id: ids.content,
1005
+ role: "dialog",
1006
+ "aria-modal": "true",
1007
+ "aria-labelledby": ids.label,
1008
+ "data-state": isOpen ? "open" : "closed",
1009
+ style: {
1010
+ ...floatingStyles,
1011
+ ...!isOpen ? { display: "none" } : void 0,
1012
+ // Hide via opacity (not `visibility`/`display`) while Floating UI
1013
+ // resolves the first position: this keeps the popover at (0,0)
1014
+ // invisible to sighted users for one frame without removing it from
1015
+ // the accessibility tree, so assistive tech still sees the dialog.
1016
+ ...isOpen && !isPositioned ? { opacity: 0, pointerEvents: "none" } : void 0,
1017
+ ...isOpen && !transitionsReady ? { transition: "none" } : void 0,
1018
+ ...style
1019
+ },
1020
+ onKeyDown: (e) => {
1021
+ if (e.key === "Escape") {
1022
+ e.preventDefault();
1023
+ dispatch({ type: "CLOSE" });
1024
+ document.getElementById(ids.trigger)?.focus();
1025
+ }
1026
+ onKeyDown?.(e);
1027
+ },
1028
+ ...props,
1029
+ children: [
1030
+ /* @__PURE__ */ jsx(
1031
+ "span",
1032
+ {
1033
+ "aria-live": "polite",
1034
+ "aria-atomic": "true",
1035
+ style: {
1036
+ position: "absolute",
1037
+ width: 1,
1038
+ height: 1,
1039
+ overflow: "hidden",
1040
+ clip: "rect(0,0,0,0)",
1041
+ whiteSpace: "nowrap"
1042
+ },
1043
+ children: isOpen ? announcement : ""
1044
+ }
1045
+ ),
1046
+ children
1047
+ ]
1048
+ }
1049
+ );
1050
+ return portal ? createPortal(content, document.body) : content;
1051
+ }
1052
+ function ViewControl({ children, ...props }) {
1053
+ return /* @__PURE__ */ jsx("div", { role: "group", ...props, children });
1054
+ }
1055
+ function PrevTrigger({ children, onClick, ...props }) {
1056
+ const { state, dispatch } = useDatePickerContext();
1057
+ const labels = {
1058
+ day: "Go to previous month",
1059
+ month: "Go to previous year",
1060
+ year: "Go to previous years"
1061
+ };
1062
+ return /* @__PURE__ */ jsx(
1063
+ "button",
1064
+ {
1065
+ type: "button",
1066
+ "aria-label": labels[state.view],
1067
+ onClick: (e) => {
1068
+ dispatch({ type: "NAV_PREV" });
1069
+ onClick?.(e);
1070
+ },
1071
+ ...props,
1072
+ children: children ?? "\u2039"
1073
+ }
1074
+ );
1075
+ }
1076
+ function NextTrigger({ children, onClick, ...props }) {
1077
+ const { state, dispatch } = useDatePickerContext();
1078
+ const labels = {
1079
+ day: "Go to next month",
1080
+ month: "Go to next year",
1081
+ year: "Go to next years"
1082
+ };
1083
+ return /* @__PURE__ */ jsx(
1084
+ "button",
1085
+ {
1086
+ type: "button",
1087
+ "aria-label": labels[state.view],
1088
+ onClick: (e) => {
1089
+ dispatch({ type: "NAV_NEXT" });
1090
+ onClick?.(e);
1091
+ },
1092
+ ...props,
1093
+ children: children ?? "\u203A"
1094
+ }
1095
+ );
1096
+ }
1097
+ var nextView = {
1098
+ day: "month",
1099
+ month: "year",
1100
+ year: "day"
1101
+ };
1102
+ var ariaLabel = {
1103
+ day: "Switch to month view",
1104
+ month: "Switch to year view",
1105
+ year: "Switch to day view"
1106
+ };
1107
+ function ViewTrigger({ children, onClick, ...props }) {
1108
+ const { state, dispatch, config } = useDatePickerContext();
1109
+ const label = formatMonthYear(new Date(state.focusedYear, state.focusedMonth, 1), config.locale);
1110
+ const yearRange = `${state.yearPageStart}\u2013${state.yearPageStart + 11}`;
1111
+ const displayLabel = state.view === "year" ? yearRange : state.view === "month" ? String(state.focusedYear) : label;
1112
+ return /* @__PURE__ */ jsx(
1113
+ "button",
1114
+ {
1115
+ type: "button",
1116
+ "aria-label": ariaLabel[state.view],
1117
+ "aria-live": "polite",
1118
+ onClick: (e) => {
1119
+ dispatch({ type: "SET_VIEW", view: nextView[state.view] });
1120
+ onClick?.(e);
1121
+ },
1122
+ ...props,
1123
+ children: children ?? displayLabel
1124
+ }
1125
+ );
1126
+ }
1127
+ function View({ view, children }) {
1128
+ const { state } = useDatePickerContext();
1129
+ if (state.view !== view) return null;
1130
+ return /* @__PURE__ */ jsx(DatePickerViewContext.Provider, { value: { view }, children });
1131
+ }
1132
+ function WeekDays({ format = "short", className }) {
1133
+ const { config } = useDatePickerContext();
1134
+ const weekStartDay = getWeekStartDay(config.locale, config.weekStartsOn);
1135
+ const days = buildWeekDays(config.locale, weekStartDay, format);
1136
+ return /* @__PURE__ */ jsx("tr", { className, children: days.map((day) => /* @__PURE__ */ jsx("th", { scope: "col", abbr: day.ariaLabel, children: day.label }, day.ariaLabel)) });
1137
+ }
1138
+ function Day({ date, children, className, style }) {
1139
+ const { state, dispatch, config } = useDatePickerContext();
1140
+ const today = startOfDay(/* @__PURE__ */ new Date());
1141
+ const meta = {
1142
+ date,
1143
+ isCurrentMonth: date.getMonth() === state.focusedMonth,
1144
+ isToday: isSameDay(date, today),
1145
+ 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),
1146
+ isDisabled: isDateDisabled(date, config),
1147
+ isRangeStart: isSameDay(date, state.rangeStart),
1148
+ isRangeEnd: isSameDay(date, state.rangeEnd ?? state.hoverDate),
1149
+ isInRange: isInRange(date, state.rangeStart, state.rangeEnd ?? state.hoverDate),
1150
+ isHovered: isSameDay(date, state.hoverDate)
1151
+ };
1152
+ const isFocused = isSameDay(date, state.focusedDate);
1153
+ return /* @__PURE__ */ jsx(
1154
+ "td",
1155
+ {
1156
+ role: "gridcell",
1157
+ "aria-selected": meta.isSelected,
1158
+ "aria-disabled": meta.isDisabled,
1159
+ "aria-current": meta.isToday ? "date" : void 0,
1160
+ tabIndex: isFocused ? 0 : -1,
1161
+ className,
1162
+ style,
1163
+ "data-selected": meta.isSelected || void 0,
1164
+ "data-today": meta.isToday || void 0,
1165
+ "data-disabled": meta.isDisabled || void 0,
1166
+ "data-outside-month": !meta.isCurrentMonth || void 0,
1167
+ "data-range-start": meta.isRangeStart || void 0,
1168
+ "data-range-end": meta.isRangeEnd || void 0,
1169
+ "data-in-range": meta.isInRange || void 0,
1170
+ onClick: () => {
1171
+ if (!meta.isDisabled && !config.readOnly) dispatch({ type: "SELECT_DATE", date });
1172
+ },
1173
+ onMouseEnter: () => {
1174
+ if (config.mode === "range") dispatch({ type: "HOVER_DATE", date });
1175
+ },
1176
+ onMouseLeave: () => {
1177
+ if (config.mode === "range") dispatch({ type: "HOVER_DATE", date: null });
1178
+ },
1179
+ onKeyDown: (e) => {
1180
+ if (e.key === "Enter" || e.key === " ") {
1181
+ e.preventDefault();
1182
+ if (!meta.isDisabled && !config.readOnly) dispatch({ type: "SELECT_DATE", date });
1183
+ }
1184
+ },
1185
+ children: children ? children(meta) : date.getDate()
1186
+ }
1187
+ );
1188
+ }
1189
+ function defaultRender(ws) {
1190
+ return ws.map((week, wi) => /* @__PURE__ */ jsx("tr", { children: week.map((day, di) => /* @__PURE__ */ jsx(Day, { date: day }, di)) }, wi));
1191
+ }
1192
+ function Grid({ children, header, className }) {
1193
+ const { state, dispatch, config } = useDatePickerContext();
1194
+ const gridRef = useRef(null);
1195
+ const weekStartDay = getWeekStartDay(config.locale, config.weekStartsOn);
1196
+ const weeks = buildCalendarGrid(state.focusedYear, state.focusedMonth, weekStartDay);
1197
+ const label = formatMonthYear(new Date(state.focusedYear, state.focusedMonth, 1), config.locale);
1198
+ const focusedDateStr = state.focusedDate?.toDateString();
1199
+ useEffect(() => {
1200
+ if (!gridRef.current || !focusedDateStr) return;
1201
+ if (document.activeElement?.getAttribute("role") === "spinbutton") return;
1202
+ if (state.openSource === "input" && !gridRef.current.contains(document.activeElement)) {
1203
+ return;
1204
+ }
1205
+ const focused = gridRef.current.querySelector('[tabindex="0"]');
1206
+ if (focused && document.activeElement !== focused) focused.focus();
1207
+ }, [focusedDateStr, state.openSource]);
1208
+ function handleKeyDown(e) {
1209
+ if (!state.focusedDate) return;
1210
+ let next = null;
1211
+ switch (e.key) {
1212
+ case "ArrowRight":
1213
+ next = addDays(state.focusedDate, 1);
1214
+ break;
1215
+ case "ArrowLeft":
1216
+ next = addDays(state.focusedDate, -1);
1217
+ break;
1218
+ case "ArrowDown":
1219
+ next = addDays(state.focusedDate, 7);
1220
+ break;
1221
+ case "ArrowUp":
1222
+ next = addDays(state.focusedDate, -7);
1223
+ break;
1224
+ case "PageDown":
1225
+ next = e.ctrlKey ? new Date(state.focusedYear + 1, state.focusedMonth, state.focusedDate.getDate()) : addMonths(state.focusedDate, 1);
1226
+ break;
1227
+ case "PageUp":
1228
+ next = e.ctrlKey ? new Date(state.focusedYear - 1, state.focusedMonth, state.focusedDate.getDate()) : addMonths(state.focusedDate, -1);
1229
+ break;
1230
+ case "Home": {
1231
+ const d = new Date(state.focusedDate);
1232
+ const diff = (d.getDay() - weekStartDay + 7) % 7;
1233
+ next = addDays(d, -diff);
1234
+ break;
1235
+ }
1236
+ case "End": {
1237
+ const d = new Date(state.focusedDate);
1238
+ const diff = (weekStartDay + 6 - d.getDay() + 7) % 7;
1239
+ next = addDays(d, diff);
1240
+ break;
1241
+ }
1242
+ default:
1243
+ return;
1244
+ }
1245
+ e.preventDefault();
1246
+ dispatch({ type: "FOCUS_DATE", date: next });
1247
+ }
1248
+ return /* @__PURE__ */ jsxs(
1249
+ "table",
1250
+ {
1251
+ ref: gridRef,
1252
+ role: "grid",
1253
+ "aria-label": label,
1254
+ className,
1255
+ onKeyDown: handleKeyDown,
1256
+ children: [
1257
+ header && /* @__PURE__ */ jsx("thead", { children: header }),
1258
+ /* @__PURE__ */ jsx("tbody", { children: children ? children({ weeks }) : defaultRender(weeks) })
1259
+ ]
1260
+ }
1261
+ );
1262
+ }
1263
+ function MonthGrid({ children, className }) {
1264
+ const { state, config, dispatch } = useDatePickerContext();
1265
+ const months = buildMonthItems(
1266
+ state.focusedYear,
1267
+ config.locale,
1268
+ state.focusedMonth,
1269
+ config.minDate,
1270
+ config.maxDate
1271
+ );
1272
+ const [focusedIndex, setFocusedIndex] = useState(state.focusedMonth);
1273
+ const gridRef = useRef(null);
1274
+ const focusedMonthRef = useRef(state.focusedMonth);
1275
+ focusedMonthRef.current = state.focusedMonth;
1276
+ useEffect(() => {
1277
+ setFocusedIndex(focusedMonthRef.current);
1278
+ }, [state.focusedYear]);
1279
+ useEffect(() => {
1280
+ const focused = gridRef.current?.querySelector('[tabindex="0"]');
1281
+ if (focused && gridRef.current?.contains(document.activeElement)) {
1282
+ focused.focus();
1283
+ }
1284
+ }, [focusedIndex, state.focusedYear]);
1285
+ function handleKeyDown(e) {
1286
+ let next = focusedIndex;
1287
+ switch (e.key) {
1288
+ case "ArrowRight":
1289
+ next = Math.min(focusedIndex + 1, 11);
1290
+ break;
1291
+ case "ArrowLeft":
1292
+ next = Math.max(focusedIndex - 1, 0);
1293
+ break;
1294
+ case "ArrowDown":
1295
+ next = Math.min(focusedIndex + 3, 11);
1296
+ break;
1297
+ case "ArrowUp":
1298
+ next = Math.max(focusedIndex - 3, 0);
1299
+ break;
1300
+ case "Home":
1301
+ next = 0;
1302
+ break;
1303
+ case "End":
1304
+ next = 11;
1305
+ break;
1306
+ case "PageDown":
1307
+ dispatch({ type: "NAV_NEXT" });
1308
+ e.preventDefault();
1309
+ return;
1310
+ case "PageUp":
1311
+ dispatch({ type: "NAV_PREV" });
1312
+ e.preventDefault();
1313
+ return;
1314
+ case "Escape":
1315
+ dispatch({ type: "SET_VIEW", view: "day" });
1316
+ e.preventDefault();
1317
+ return;
1318
+ case "Enter":
1319
+ case " ":
1320
+ dispatch({ type: "SELECT_MONTH", month: focusedIndex });
1321
+ e.preventDefault();
1322
+ return;
1323
+ default:
1324
+ return;
1325
+ }
1326
+ e.preventDefault();
1327
+ setFocusedIndex(next);
1328
+ }
1329
+ return /* @__PURE__ */ jsx(MonthGridFocusContext.Provider, { value: focusedIndex, children: /* @__PURE__ */ jsx(
1330
+ "div",
1331
+ {
1332
+ ref: gridRef,
1333
+ role: "grid",
1334
+ "aria-label": String(state.focusedYear),
1335
+ className,
1336
+ onKeyDown: handleKeyDown,
1337
+ children: children({ months })
1338
+ }
1339
+ ) });
1340
+ }
1341
+ function MonthCell({ value, children, onClick, ...props }) {
1342
+ const { dispatch } = useDatePickerContext();
1343
+ const focusedIndex = useContext(MonthGridFocusContext);
1344
+ const isFocused = focusedIndex === value;
1345
+ return /* @__PURE__ */ jsx(
1346
+ "button",
1347
+ {
1348
+ type: "button",
1349
+ role: "gridcell",
1350
+ "aria-selected": isFocused,
1351
+ tabIndex: isFocused ? 0 : -1,
1352
+ "data-selected": isFocused || void 0,
1353
+ onClick: (e) => {
1354
+ dispatch({ type: "SELECT_MONTH", month: value });
1355
+ onClick?.(e);
1356
+ },
1357
+ ...props,
1358
+ children
1359
+ }
1360
+ );
1361
+ }
1362
+ function YearGrid({ children, className }) {
1363
+ const { state, config, dispatch } = useDatePickerContext();
1364
+ const years = buildYearItems(
1365
+ state.yearPageStart,
1366
+ 12,
1367
+ state.focusedYear,
1368
+ config.minDate,
1369
+ config.maxDate
1370
+ );
1371
+ const [focusedYear, setFocusedYear] = useState(state.focusedYear);
1372
+ const gridRef = useRef(null);
1373
+ useEffect(() => {
1374
+ const inPage = focusedYear >= state.yearPageStart && focusedYear < state.yearPageStart + 12;
1375
+ if (!inPage) setFocusedYear(state.yearPageStart);
1376
+ }, [state.yearPageStart]);
1377
+ useEffect(() => {
1378
+ const focused = gridRef.current?.querySelector('[tabindex="0"]');
1379
+ if (focused && gridRef.current?.contains(document.activeElement)) {
1380
+ focused.focus();
1381
+ }
1382
+ }, [focusedYear, state.yearPageStart]);
1383
+ function handleKeyDown(e) {
1384
+ let next = focusedYear;
1385
+ const pageStart = state.yearPageStart;
1386
+ const pageEnd = pageStart + 11;
1387
+ switch (e.key) {
1388
+ case "ArrowRight":
1389
+ next = Math.min(focusedYear + 1, pageEnd);
1390
+ break;
1391
+ case "ArrowLeft":
1392
+ next = Math.max(focusedYear - 1, pageStart);
1393
+ break;
1394
+ // 4-column grid: ↓/↑ = ±4 years (next/prev row)
1395
+ case "ArrowDown":
1396
+ next = Math.min(focusedYear + 4, pageEnd);
1397
+ break;
1398
+ case "ArrowUp":
1399
+ next = Math.max(focusedYear - 4, pageStart);
1400
+ break;
1401
+ case "Home":
1402
+ next = pageStart;
1403
+ break;
1404
+ case "End":
1405
+ next = pageEnd;
1406
+ break;
1407
+ case "PageDown":
1408
+ dispatch({ type: "YEAR_PAGE_NEXT" });
1409
+ e.preventDefault();
1410
+ return;
1411
+ case "PageUp":
1412
+ dispatch({ type: "YEAR_PAGE_PREV" });
1413
+ e.preventDefault();
1414
+ return;
1415
+ case "Escape":
1416
+ dispatch({ type: "SET_VIEW", view: "month" });
1417
+ e.preventDefault();
1418
+ return;
1419
+ case "Enter":
1420
+ case " ":
1421
+ dispatch({ type: "SELECT_YEAR", year: focusedYear });
1422
+ e.preventDefault();
1423
+ return;
1424
+ default:
1425
+ return;
1426
+ }
1427
+ e.preventDefault();
1428
+ setFocusedYear(next);
1429
+ }
1430
+ return /* @__PURE__ */ jsx(YearGridFocusContext.Provider, { value: focusedYear, children: /* @__PURE__ */ jsx(
1431
+ "div",
1432
+ {
1433
+ ref: gridRef,
1434
+ role: "grid",
1435
+ "aria-label": `${state.yearPageStart}\u2013${state.yearPageStart + 11}`,
1436
+ className,
1437
+ onKeyDown: handleKeyDown,
1438
+ children: children({ years })
1439
+ }
1440
+ ) });
1441
+ }
1442
+ function YearCell({ value, children, onClick, ...props }) {
1443
+ const { dispatch } = useDatePickerContext();
1444
+ const focusedYear = useContext(YearGridFocusContext);
1445
+ const isFocused = focusedYear === value;
1446
+ return /* @__PURE__ */ jsx(
1447
+ "button",
1448
+ {
1449
+ type: "button",
1450
+ role: "gridcell",
1451
+ "aria-selected": isFocused,
1452
+ tabIndex: isFocused ? 0 : -1,
1453
+ "data-selected": isFocused || void 0,
1454
+ onClick: (e) => {
1455
+ dispatch({ type: "SELECT_YEAR", year: value });
1456
+ onClick?.(e);
1457
+ },
1458
+ ...props,
1459
+ children
1460
+ }
1461
+ );
1462
+ }
1463
+ function Calendar({ className }) {
1464
+ return /* @__PURE__ */ jsxs("div", { className, children: [
1465
+ /* @__PURE__ */ jsxs(ViewControl, { children: [
1466
+ /* @__PURE__ */ jsx(PrevTrigger, {}),
1467
+ /* @__PURE__ */ jsx(ViewTrigger, {}),
1468
+ /* @__PURE__ */ jsx(NextTrigger, {})
1469
+ ] }),
1470
+ /* @__PURE__ */ jsx(View, { view: "day", children: /* @__PURE__ */ jsx(Grid, { header: /* @__PURE__ */ jsx(WeekDays, {}) }) }),
1471
+ /* @__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)) }) }) }),
1472
+ /* @__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)) }) }) })
1473
+ ] });
1474
+ }
1475
+
1476
+ export { date_picker_exports as DatePicker, useDatePickerContext };
1477
+ //# sourceMappingURL=index.js.map
1478
+ //# sourceMappingURL=index.js.map