@at5/kairo 0.2.0 → 0.2.2

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