@kalyx/react 1.0.0-rc.0 → 1.0.0-rc.10

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 CHANGED
@@ -1,8 +1,9 @@
1
- import { createContext, forwardRef, useState, useCallback, useContext, useId, useRef, useMemo, useEffect } from 'react';
2
- import { parseInputValue, formatTimeString, parseTimeString, getTimeInTimezone, getTime, DateFnsAdapter, DEFAULT_DATEPICKER_LABELS, civilMidnightFromUtcDay, getMonthName, getWeekdayNames, getCalendarDays, formatMonthYear, isDateDisabled, DEFAULT_RANGEPICKER_LABELS, DEFAULT_TIMEPICKER_LABELS, setTimeInTimezone, setTime, to12Hour, to24Hour, generateMinutes, generateHours, formatFullDate } from '@kalyx/core';
3
- export { DateFnsAdapter } from '@kalyx/core';
4
- import { jsx, jsxs } from 'react/jsx-runtime';
5
- import { useFloating, autoUpdate, offset, flip, shift } from '@floating-ui/react';
1
+ "use client";
2
+ import { createContext, forwardRef, useState, useRef, useCallback, useContext, useId, useMemo, useEffect } from 'react';
3
+ import { parseInputValue, formatTimeString, parseTimeString, getTimeInTimezone, getTime, DateFnsAdapter, DEFAULT_DATEPICKER_LABELS, civilMidnightFromUtcDay, getMonthName, getWeekdayNames, getCalendarDays, formatMonthYear, isDateDisabled, getISOWeekNumber, DEFAULT_RANGEPICKER_LABELS, DEFAULT_TIMEPICKER_LABELS, setTimeInTimezone, setTime, to12Hour, to24Hour, generateMinutes, generateHours, formatFullDate } from '@kalyx/core';
4
+ export { DEFAULT_DATEPICKER_LABELS, DEFAULT_DATETIMEPICKER_LABELS, DEFAULT_RANGEPICKER_LABELS, DEFAULT_TIMEPICKER_LABELS, DateFnsAdapter } from '@kalyx/core';
5
+ import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
6
+ import { offset, flip, shift, useFloating, autoUpdate } from '@floating-ui/react';
6
7
 
7
8
  // src/components/DatePicker/Root.tsx
8
9
  var DatePickerContext = createContext(null);
@@ -56,10 +57,10 @@ function DatePickerRoot({
56
57
  const currentValue = isControlled ? controlledValue ?? null : uncontrolledValue;
57
58
  const [isOpen, setIsOpen] = useState(false);
58
59
  const [viewMonth, setViewMonth] = useState(
59
- currentValue ?? adapter.today(displayTimezone)
60
+ () => currentValue ?? adapter.today(displayTimezone)
60
61
  );
61
62
  const [focusedDate, setFocusedDate] = useState(
62
- currentValue ?? adapter.today(displayTimezone)
63
+ () => currentValue ?? adapter.today(displayTimezone)
63
64
  );
64
65
  useChangeEffect(isOpen, onOpenChange);
65
66
  const viewMonthStart = useMemo(() => adapter.startOfMonth(viewMonth), [viewMonth, adapter]);
@@ -150,10 +151,11 @@ function DatePickerRoot({
150
151
  return /* @__PURE__ */ jsx(DatePickerContext.Provider, { value: contextValue, children });
151
152
  }
152
153
  var DatePickerInput = forwardRef(
153
- function DatePickerInput2({ format: formatProp, onClick, onBlur, onKeyDown, ...props }, ref) {
154
+ function DatePickerInput2({ format: formatProp, name, onClick, onBlur, onKeyDown, ...props }, ref) {
154
155
  const ctx = useDatePickerContext("DatePicker.Input");
155
156
  const displayFormat = formatProp ?? ctx.displayFormat;
156
157
  const [inputText, setInputText] = useState(null);
158
+ const isComposingRef = useRef(false);
157
159
  let formattedValue = "";
158
160
  if (ctx.value) {
159
161
  try {
@@ -170,47 +172,62 @@ var DatePickerInput = forwardRef(
170
172
  },
171
173
  [ctx, onClick]
172
174
  );
175
+ const commitText = useCallback(
176
+ (text) => {
177
+ if (!text) {
178
+ ctx.selectDate(null);
179
+ setInputText(null);
180
+ return true;
181
+ }
182
+ const parsed = parseInputValue(text, ctx.adapter);
183
+ if (parsed) {
184
+ ctx.selectDate(parsed);
185
+ setInputText(null);
186
+ return true;
187
+ }
188
+ return false;
189
+ },
190
+ [ctx]
191
+ );
173
192
  const handleBlur = useCallback(
174
193
  (e) => {
175
194
  if (inputText !== null) {
176
- const parsed = parseInputValue(inputText, ctx.adapter);
177
- if (parsed) {
178
- ctx.selectDate(parsed);
179
- }
195
+ commitText(inputText);
180
196
  setInputText(null);
181
197
  }
182
198
  onBlur?.(e);
183
199
  },
184
- [inputText, displayFormat, ctx, onBlur]
200
+ [inputText, commitText, onBlur]
185
201
  );
186
202
  const handleChange = useCallback(
187
203
  (e) => {
188
204
  const text = e.target.value;
189
205
  setInputText(text);
190
- if (!text) {
191
- ctx.selectDate(null);
192
- setInputText(null);
193
- return;
194
- }
195
- const parsed = parseInputValue(text, ctx.adapter);
196
- if (parsed) {
197
- ctx.selectDate(parsed);
198
- setInputText(null);
199
- }
206
+ if (isComposingRef.current) return;
207
+ commitText(text);
208
+ },
209
+ [commitText]
210
+ );
211
+ const handleCompositionStart = useCallback(() => {
212
+ isComposingRef.current = true;
213
+ }, []);
214
+ const handleCompositionEnd = useCallback(
215
+ (e) => {
216
+ isComposingRef.current = false;
217
+ commitText(e.target.value);
200
218
  },
201
- [displayFormat, ctx]
219
+ [commitText]
202
220
  );
203
221
  const handleKeyDown = useCallback(
204
222
  (e) => {
205
223
  if (e.key === "Escape") {
206
224
  ctx.close();
207
225
  } else if (e.key === "Enter") {
226
+ if (ctx.isOpen) e.preventDefault();
208
227
  if (inputText !== null) {
209
- const parsed = parseInputValue(inputText, ctx.adapter);
210
- if (parsed) {
211
- ctx.selectDate(parsed);
212
- setInputText(null);
213
- }
228
+ commitText(inputText);
229
+ } else if (ctx.isOpen) {
230
+ ctx.selectDate(ctx.focusedDate);
214
231
  }
215
232
  } else if (e.key === "ArrowDown" && !ctx.isOpen) {
216
233
  e.preventDefault();
@@ -218,36 +235,42 @@ var DatePickerInput = forwardRef(
218
235
  }
219
236
  onKeyDown?.(e);
220
237
  },
221
- [ctx, inputText, displayFormat, onKeyDown]
238
+ [ctx, inputText, commitText, onKeyDown]
222
239
  );
223
240
  const calendarId = `${ctx.pickerId}-calendar`;
224
- return /* @__PURE__ */ jsx(
225
- "input",
226
- {
227
- ref: (node) => {
228
- ctx.referenceRef.current = node;
229
- if (typeof ref === "function") ref(node);
230
- else if (ref) ref.current = node;
231
- },
232
- type: "text",
233
- role: "combobox",
234
- "aria-expanded": ctx.isOpen,
235
- "aria-haspopup": "dialog",
236
- "aria-controls": ctx.isOpen ? calendarId : void 0,
237
- "aria-autocomplete": "none",
238
- autoComplete: "off",
239
- value: displayValue,
240
- disabled: ctx.isDisabled || props.disabled,
241
- readOnly: ctx.isReadOnly,
242
- onChange: handleChange,
243
- onClick: handleClick,
244
- onBlur: handleBlur,
245
- onKeyDown: handleKeyDown,
246
- ...props
247
- }
248
- );
241
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
242
+ /* @__PURE__ */ jsx(
243
+ "input",
244
+ {
245
+ ref: (node) => {
246
+ ctx.referenceRef.current = node;
247
+ if (typeof ref === "function") ref(node);
248
+ else if (ref) ref.current = node;
249
+ },
250
+ type: "text",
251
+ role: "combobox",
252
+ "aria-expanded": ctx.isOpen,
253
+ "aria-haspopup": "dialog",
254
+ "aria-controls": ctx.isOpen ? calendarId : void 0,
255
+ "aria-autocomplete": "none",
256
+ autoComplete: "off",
257
+ value: displayValue,
258
+ disabled: ctx.isDisabled || props.disabled,
259
+ readOnly: ctx.isReadOnly,
260
+ onChange: handleChange,
261
+ onClick: handleClick,
262
+ onBlur: handleBlur,
263
+ onKeyDown: handleKeyDown,
264
+ onCompositionStart: handleCompositionStart,
265
+ onCompositionEnd: handleCompositionEnd,
266
+ ...props
267
+ }
268
+ ),
269
+ name ? /* @__PURE__ */ jsx("input", { type: "hidden", name, value: ctx.value ?? "" }) : null
270
+ ] });
249
271
  }
250
272
  );
273
+ DatePickerInput.displayName = "DatePicker.Input";
251
274
  var DatePickerTrigger = forwardRef(
252
275
  function DatePickerTrigger2({ onClick, children, ...props }, ref) {
253
276
  const ctx = useDatePickerContext("DatePicker.Trigger");
@@ -271,6 +294,7 @@ var DatePickerTrigger = forwardRef(
271
294
  tabIndex: 0,
272
295
  "aria-label": ctx.isOpen ? ctx.labels.triggerClose : ctx.labels.triggerOpen,
273
296
  "aria-expanded": ctx.isOpen,
297
+ "aria-haspopup": "dialog",
274
298
  "aria-controls": ctx.isOpen ? calendarId : void 0,
275
299
  disabled: ctx.isDisabled || props.disabled,
276
300
  onClick: handleClick,
@@ -301,13 +325,20 @@ var DatePickerTrigger = forwardRef(
301
325
  );
302
326
  }
303
327
  );
304
- function usePopover({ isOpen, close, referenceRef, placement = "bottom-start" }) {
328
+ DatePickerTrigger.displayName = "DatePicker.Trigger";
329
+ var POPOVER_MIDDLEWARE = [offset(4), flip(), shift({ padding: 8 })];
330
+ function usePopover({
331
+ isOpen,
332
+ close,
333
+ referenceRef,
334
+ placement = "bottom-start"
335
+ }) {
305
336
  const floatingRef = useRef(null);
306
337
  const previousFocusRef = useRef(null);
307
- const { refs, floatingStyles } = useFloating({
338
+ const { refs, floatingStyles, isPositioned } = useFloating({
308
339
  open: isOpen,
309
340
  placement,
310
- middleware: [offset(4), flip(), shift({ padding: 8 })],
341
+ middleware: POPOVER_MIDDLEWARE,
311
342
  whileElementsMounted: autoUpdate
312
343
  });
313
344
  useEffect(() => {
@@ -354,21 +385,46 @@ function usePopover({ isOpen, close, referenceRef, placement = "bottom-start" })
354
385
  document.addEventListener("keydown", handleKeyDown);
355
386
  return () => document.removeEventListener("keydown", handleKeyDown);
356
387
  }, [isOpen, close]);
357
- const setFloatingRef = (node) => {
358
- floatingRef.current = node;
359
- refs.setFloating(node);
360
- };
361
- return { floatingStyles, setFloatingRef };
388
+ useEffect(() => {
389
+ if (!isOpen) return;
390
+ function handleFocusOut(e) {
391
+ const next = e.relatedTarget;
392
+ const floating = floatingRef.current;
393
+ const reference = referenceRef.current;
394
+ if (!next) return;
395
+ const insideFloating = floating?.contains(next) ?? false;
396
+ const insideReference = reference?.contains(next) ?? false;
397
+ if (!insideFloating && !insideReference) {
398
+ close();
399
+ }
400
+ }
401
+ const node = floatingRef.current;
402
+ if (!node) return;
403
+ node.addEventListener("focusout", handleFocusOut);
404
+ return () => node.removeEventListener("focusout", handleFocusOut);
405
+ }, [isOpen, close, referenceRef]);
406
+ const setFloatingRef = useCallback(
407
+ (node) => {
408
+ floatingRef.current = node;
409
+ refs.setFloating(node);
410
+ if (node && referenceRef.current) {
411
+ refs.setReference(referenceRef.current);
412
+ }
413
+ },
414
+ [refs, referenceRef]
415
+ );
416
+ return { floatingStyles, setFloatingRef, isPositioned };
362
417
  }
363
418
  function DatePickerPopover({ children, ...props }) {
364
419
  const ctx = useDatePickerContext("DatePicker.Popover");
365
420
  const calendarId = `${ctx.pickerId}-calendar`;
366
- const { floatingStyles, setFloatingRef } = usePopover({
421
+ const { floatingStyles, setFloatingRef, isPositioned } = usePopover({
367
422
  isOpen: ctx.isOpen,
368
423
  close: ctx.close,
369
424
  referenceRef: ctx.referenceRef
370
425
  });
371
426
  if (!ctx.isOpen) return null;
427
+ const { style: userStyle, ...rest } = props;
372
428
  return /* @__PURE__ */ jsx(
373
429
  "div",
374
430
  {
@@ -377,8 +433,12 @@ function DatePickerPopover({ children, ...props }) {
377
433
  role: "dialog",
378
434
  "aria-label": ctx.labels.popoverLabel,
379
435
  "aria-modal": "false",
380
- style: floatingStyles,
381
- ...props,
436
+ ...rest,
437
+ style: {
438
+ ...userStyle,
439
+ ...floatingStyles,
440
+ visibility: isPositioned ? void 0 : "hidden"
441
+ },
382
442
  children
383
443
  }
384
444
  );
@@ -401,27 +461,45 @@ var srOnly = {
401
461
  whiteSpace: "nowrap",
402
462
  border: 0
403
463
  };
404
- function DatePickerCalendar({ classNames, onTitleClick, ...props }) {
464
+ function DatePickerCalendar({
465
+ classNames,
466
+ onTitleClick,
467
+ showWeekNumber = false,
468
+ fixedWeeks = false,
469
+ ...props
470
+ }) {
405
471
  const ctx = useDatePickerContext("DatePicker.Calendar");
406
472
  const gridRef = useRef(null);
407
473
  const [announcement, setAnnouncement] = useState("");
408
474
  const { adapter, viewMonth, focusedDate, weekStartsOn, disabled, locale, displayTimezone } = ctx;
409
- const weekdays = getWeekdayNames(locale, weekStartsOn);
410
- const weeks = getCalendarDays(viewMonth, adapter, {
411
- weekStartsOn,
412
- selected: ctx.value,
413
- focusedDate,
414
- disabled,
415
- timezone: displayTimezone
416
- });
475
+ const weekdays = useMemo(() => getWeekdayNames(locale, weekStartsOn), [locale, weekStartsOn]);
476
+ const weeks = useMemo(
477
+ () => getCalendarDays(viewMonth, adapter, {
478
+ weekStartsOn,
479
+ selected: ctx.value,
480
+ focusedDate,
481
+ disabled,
482
+ timezone: displayTimezone,
483
+ fixedWeeks
484
+ }),
485
+ [
486
+ viewMonth,
487
+ adapter,
488
+ weekStartsOn,
489
+ ctx.value,
490
+ focusedDate,
491
+ disabled,
492
+ displayTimezone,
493
+ fixedWeeks
494
+ ]
495
+ );
496
+ const thursdayIndex = weekStartsOn === 0 ? 4 : 3;
417
497
  const year = adapter.getYear(viewMonth);
418
498
  const month = adapter.getMonth(viewMonth);
419
499
  const title = formatMonthYear(year, month, locale);
420
500
  useEffect(() => {
421
501
  if (!ctx.isOpen || !gridRef.current) return;
422
- const focusedButton = gridRef.current.querySelector(
423
- '[data-focused="true"]'
424
- );
502
+ const focusedButton = gridRef.current.querySelector('[data-focused="true"]');
425
503
  focusedButton?.focus({ preventScroll: true });
426
504
  }, [focusedDate, ctx.isOpen]);
427
505
  const navigateMonth = useCallback(
@@ -495,6 +573,15 @@ function DatePickerCalendar({ classNames, onTitleClick, ...props }) {
495
573
  }
496
574
  if (newFocused) {
497
575
  e.preventDefault();
576
+ const skipStep = e.key === "ArrowLeft" || e.key === "ArrowUp" || e.key === "PageUp" || e.key === "Home" ? -1 : 1;
577
+ let attempts = 0;
578
+ while (isDateDisabled(newFocused, disabled, adapter) && attempts < 42) {
579
+ newFocused = adapter.addDays(newFocused, skipStep);
580
+ attempts++;
581
+ }
582
+ if (attempts >= 42) {
583
+ return;
584
+ }
498
585
  ctx.setFocusedDate(newFocused);
499
586
  if (!adapter.isSameMonth(newFocused, viewMonth)) {
500
587
  ctx.setViewMonth(newFocused);
@@ -542,62 +629,170 @@ function DatePickerCalendar({ classNames, onTitleClick, ...props }) {
542
629
  ref: gridRef,
543
630
  role: "grid",
544
631
  "aria-label": title,
632
+ "aria-rowcount": weeks.length + 1,
633
+ "aria-colcount": 7,
545
634
  className: classNames?.grid,
546
635
  onKeyDown: handleKeyDown,
547
636
  children: [
548
- /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { role: "row", children: weekdays.map((day) => /* @__PURE__ */ jsx(
549
- "th",
550
- {
551
- role: "columnheader",
552
- abbr: day.full,
553
- scope: "col",
554
- className: classNames?.weekdayHeader,
555
- children: day.short
556
- },
557
- day.short
558
- )) }) }),
559
- /* @__PURE__ */ jsx("tbody", { children: weeks.map((week, weekIndex) => /* @__PURE__ */ jsx("tr", { role: "row", className: classNames?.gridRow, children: week.map((day) => {
560
- const dayClasses = [
561
- classNames?.day,
562
- day.isSelected && classNames?.daySelected,
563
- day.isToday && classNames?.dayToday,
564
- day.isDisabled && classNames?.dayDisabled,
565
- !day.isCurrentMonth && classNames?.dayOutsideMonth
566
- ].filter(Boolean).join(" ") || void 0;
567
- return /* @__PURE__ */ jsx(
568
- "td",
637
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { role: "row", "aria-rowindex": 1, children: [
638
+ showWeekNumber ? /* @__PURE__ */ jsx("th", { scope: "col", "aria-hidden": "true", className: classNames?.weekNumberHeader, children: "#" }) : null,
639
+ weekdays.map((day, colIndex) => /* @__PURE__ */ jsx(
640
+ "th",
569
641
  {
570
- role: "gridcell",
571
- "aria-selected": day.isSelected || void 0,
572
- "aria-disabled": day.isDisabled || void 0,
573
- "aria-current": day.isToday ? "date" : void 0,
574
- className: classNames?.gridCell,
575
- children: /* @__PURE__ */ jsx(
576
- "button",
642
+ role: "columnheader",
643
+ abbr: day.full,
644
+ scope: "col",
645
+ "aria-colindex": colIndex + 1,
646
+ className: classNames?.weekdayHeader,
647
+ children: day.short
648
+ },
649
+ day.short
650
+ ))
651
+ ] }) }),
652
+ /* @__PURE__ */ jsx("tbody", { children: weeks.map((week, weekIndex) => /* @__PURE__ */ jsxs(
653
+ "tr",
654
+ {
655
+ role: "row",
656
+ "aria-rowindex": weekIndex + 2,
657
+ className: classNames?.gridRow,
658
+ children: [
659
+ showWeekNumber ? /* @__PURE__ */ jsx(
660
+ "th",
577
661
  {
578
- type: "button",
579
- tabIndex: day.isFocused ? 0 : -1,
580
- disabled: day.isDisabled,
581
- "data-focused": day.isFocused || void 0,
582
- "data-selected": day.isSelected || void 0,
583
- "data-today": day.isToday || void 0,
584
- "data-outside-month": !day.isCurrentMonth || void 0,
585
- className: dayClasses,
586
- onClick: () => handleDayClick(day),
587
- "aria-label": safeFormatFullDate(day.isoString, locale),
588
- children: day.dayNumber
662
+ scope: "row",
663
+ "aria-hidden": "true",
664
+ className: classNames?.weekNumber,
665
+ "data-week-number": true,
666
+ children: getISOWeekNumber(week[thursdayIndex].isoString)
589
667
  }
590
- )
591
- },
592
- day.isoString
593
- );
594
- }) }, weekIndex)) })
668
+ ) : null,
669
+ week.map((day, colIndex) => {
670
+ const dayClasses = [
671
+ classNames?.day,
672
+ day.isSelected && classNames?.daySelected,
673
+ day.isToday && classNames?.dayToday,
674
+ day.isDisabled && classNames?.dayDisabled,
675
+ !day.isCurrentMonth && classNames?.dayOutsideMonth
676
+ ].filter(Boolean).join(" ") || void 0;
677
+ return /* @__PURE__ */ jsx(
678
+ "td",
679
+ {
680
+ role: "gridcell",
681
+ "aria-colindex": colIndex + 1,
682
+ "aria-selected": day.isSelected || void 0,
683
+ "aria-disabled": day.isDisabled || void 0,
684
+ "aria-current": day.isToday ? "date" : void 0,
685
+ className: classNames?.gridCell,
686
+ children: /* @__PURE__ */ jsx(
687
+ "button",
688
+ {
689
+ type: "button",
690
+ tabIndex: day.isFocused ? 0 : -1,
691
+ disabled: day.isDisabled,
692
+ "data-focused": day.isFocused || void 0,
693
+ "data-selected": day.isSelected || void 0,
694
+ "data-today": day.isToday || void 0,
695
+ "data-outside-month": !day.isCurrentMonth || void 0,
696
+ className: dayClasses,
697
+ onClick: () => handleDayClick(day),
698
+ "aria-label": safeFormatFullDate(day.isoString, locale),
699
+ children: day.dayNumber
700
+ }
701
+ )
702
+ },
703
+ day.isoString
704
+ );
705
+ })
706
+ ]
707
+ },
708
+ weekIndex
709
+ )) })
595
710
  ]
596
711
  }
597
712
  ),
598
713
  /* @__PURE__ */ jsx("div", { role: "status", "aria-live": "polite", "aria-atomic": "true", style: srOnly, children: announcement })
599
714
  ] });
600
715
  }
716
+ function isRangeFullyDisabled(start, end, rules, adapter) {
717
+ for (const rule of rules) {
718
+ if ("before" in rule && adapter.isBefore(end, rule.before)) return true;
719
+ if ("after" in rule && adapter.isAfter(start, rule.after)) return true;
720
+ }
721
+ return false;
722
+ }
723
+ function useGridState(opts) {
724
+ const { initialIndex, disabledFlags, onSelect, onPageUp, onPageDown, onEscape } = opts;
725
+ const gridRef = useRef(null);
726
+ const [focusedIndex, setFocusedIndex] = useState(initialIndex);
727
+ const handleKeyDown = (e) => {
728
+ let next = null;
729
+ let step = 1;
730
+ switch (e.key) {
731
+ case "ArrowLeft":
732
+ next = Math.max(0, focusedIndex - 1);
733
+ step = -1;
734
+ break;
735
+ case "ArrowRight":
736
+ next = Math.min(11, focusedIndex + 1);
737
+ break;
738
+ case "ArrowUp":
739
+ next = Math.max(0, focusedIndex - 3);
740
+ step = -1;
741
+ break;
742
+ case "ArrowDown":
743
+ next = Math.min(11, focusedIndex + 3);
744
+ break;
745
+ case "Home":
746
+ next = focusedIndex - focusedIndex % 3;
747
+ step = -1;
748
+ break;
749
+ case "End":
750
+ next = focusedIndex - focusedIndex % 3 + 2;
751
+ break;
752
+ case "PageUp":
753
+ e.preventDefault();
754
+ onPageUp();
755
+ return;
756
+ case "PageDown":
757
+ e.preventDefault();
758
+ onPageDown();
759
+ return;
760
+ case "Enter":
761
+ case " ":
762
+ e.preventDefault();
763
+ onSelect(focusedIndex);
764
+ return;
765
+ case "Escape":
766
+ onEscape();
767
+ return;
768
+ default:
769
+ return;
770
+ }
771
+ if (next === null) return;
772
+ e.preventDefault();
773
+ if (disabledFlags) {
774
+ let attempts = 0;
775
+ while (next >= 0 && next < 12 && disabledFlags[next] && attempts < 12) {
776
+ next += step;
777
+ attempts++;
778
+ }
779
+ if (next < 0 || next >= 12 || disabledFlags[next]) return;
780
+ }
781
+ if (next !== focusedIndex) setFocusedIndex(next);
782
+ };
783
+ useEffect(() => {
784
+ if (!disabledFlags || !disabledFlags[focusedIndex]) return;
785
+ const firstEnabled = disabledFlags.findIndex((d) => !d);
786
+ if (firstEnabled !== -1 && firstEnabled !== focusedIndex) {
787
+ setFocusedIndex(firstEnabled);
788
+ }
789
+ }, [disabledFlags, focusedIndex]);
790
+ useEffect(() => {
791
+ const btn = gridRef.current?.querySelector('[data-focused="true"]');
792
+ btn?.focus({ preventScroll: true });
793
+ }, [focusedIndex]);
794
+ return { gridRef, focusedIndex, handleKeyDown };
795
+ }
601
796
  function DatePickerMonthGrid({
602
797
  classNames,
603
798
  onSelect,
@@ -605,15 +800,18 @@ function DatePickerMonthGrid({
605
800
  ...props
606
801
  }) {
607
802
  const ctx = useDatePickerContext("DatePicker.MonthGrid");
608
- const { adapter, viewMonth, locale } = ctx;
803
+ const { adapter, viewMonth, locale, displayTimezone } = ctx;
609
804
  const currentYear = adapter.getYear(viewMonth);
610
805
  const currentMonth = adapter.getMonth(viewMonth);
611
- const todayMonth = adapter.getMonth(adapter.today());
612
- const todayYear = adapter.getYear(adapter.today());
806
+ const [today, setToday] = useState(null);
807
+ useEffect(() => {
808
+ setToday(adapter.today(displayTimezone));
809
+ }, [adapter, displayTimezone]);
810
+ const todayMonth = today !== null ? adapter.getMonth(today) : -1;
811
+ const todayYear = today !== null ? adapter.getYear(today) : -1;
613
812
  const navigateYear = useCallback(
614
813
  (direction) => {
615
- const newDate = adapter.addYears(viewMonth, direction);
616
- ctx.setViewMonth(newDate);
814
+ ctx.setViewMonth(adapter.addYears(viewMonth, direction));
617
815
  },
618
816
  [adapter, viewMonth, ctx]
619
817
  );
@@ -626,12 +824,13 @@ function DatePickerMonthGrid({
626
824
  },
627
825
  [currentYear, ctx, onSelect]
628
826
  );
629
- const months = Array.from({ length: 12 }, (_, i) => ({
630
- index: i,
631
- name: getMonthName(i, locale),
632
- isSelected: i === currentMonth,
633
- isCurrent: i === todayMonth && currentYear === todayYear
634
- }));
827
+ const { gridRef, focusedIndex, handleKeyDown } = useGridState({
828
+ initialIndex: currentMonth,
829
+ onSelect: handleMonthSelect,
830
+ onPageUp: () => navigateYear(-1),
831
+ onPageDown: () => navigateYear(1),
832
+ onEscape: ctx.close
833
+ });
635
834
  return /* @__PURE__ */ jsxs("div", { className: classNames?.root, ...props, children: [
636
835
  /* @__PURE__ */ jsxs("div", { className: classNames?.header, children: [
637
836
  /* @__PURE__ */ jsx(
@@ -644,15 +843,7 @@ function DatePickerMonthGrid({
644
843
  children: "<"
645
844
  }
646
845
  ),
647
- onTitleClick ? /* @__PURE__ */ jsx(
648
- "button",
649
- {
650
- type: "button",
651
- className: classNames?.title,
652
- onClick: onTitleClick,
653
- children: currentYear
654
- }
655
- ) : /* @__PURE__ */ jsx("span", { className: classNames?.title, children: currentYear }),
846
+ onTitleClick ? /* @__PURE__ */ jsx("button", { type: "button", className: classNames?.title, onClick: onTitleClick, children: currentYear }) : /* @__PURE__ */ jsx("span", { className: classNames?.title, children: currentYear }),
656
847
  /* @__PURE__ */ jsx(
657
848
  "button",
658
849
  {
@@ -667,74 +858,77 @@ function DatePickerMonthGrid({
667
858
  /* @__PURE__ */ jsx(
668
859
  "div",
669
860
  {
861
+ ref: gridRef,
670
862
  role: "grid",
671
863
  "aria-label": `${currentYear} months`,
672
864
  className: classNames?.grid,
673
865
  style: { display: "grid", gridTemplateColumns: "repeat(3, 1fr)" },
674
- children: months.map((m) => {
675
- const monthClass = [
866
+ onKeyDown: handleKeyDown,
867
+ children: Array.from({ length: 12 }, (_, i) => {
868
+ const isSelected = i === currentMonth;
869
+ const isCurrent = i === todayMonth && currentYear === todayYear;
870
+ const isFocused = i === focusedIndex;
871
+ const cls = [
676
872
  classNames?.month,
677
- m.isSelected && classNames?.monthSelected,
678
- m.isCurrent && classNames?.monthCurrent
873
+ isSelected && classNames?.monthSelected,
874
+ isCurrent && classNames?.monthCurrent
679
875
  ].filter(Boolean).join(" ") || void 0;
680
876
  return /* @__PURE__ */ jsx(
681
877
  "button",
682
878
  {
683
879
  type: "button",
684
880
  role: "gridcell",
685
- "aria-selected": m.isSelected || void 0,
686
- "aria-current": m.isCurrent ? "date" : void 0,
687
- "data-selected": m.isSelected || void 0,
688
- "data-current": m.isCurrent || void 0,
689
- className: monthClass,
690
- onClick: () => handleMonthSelect(m.index),
691
- children: m.name
881
+ tabIndex: isFocused ? 0 : -1,
882
+ "aria-selected": isSelected || void 0,
883
+ "aria-current": isCurrent ? "date" : void 0,
884
+ "data-selected": isSelected || void 0,
885
+ "data-current": isCurrent || void 0,
886
+ "data-focused": isFocused || void 0,
887
+ className: cls,
888
+ onClick: () => handleMonthSelect(i),
889
+ children: getMonthName(i, locale)
692
890
  },
693
- m.index
891
+ i
694
892
  );
695
893
  })
696
894
  }
697
895
  )
698
896
  ] });
699
897
  }
700
- function DatePickerYearGrid({
701
- classNames,
702
- onSelect,
703
- ...props
704
- }) {
898
+ function DatePickerYearGrid({ classNames, onSelect, ...props }) {
705
899
  const ctx = useDatePickerContext("DatePicker.YearGrid");
706
- const { adapter, viewMonth } = ctx;
900
+ const { adapter, viewMonth, displayTimezone } = ctx;
707
901
  const currentYear = adapter.getYear(viewMonth);
708
- const todayYear = adapter.getYear(adapter.today());
902
+ const [today, setToday] = useState(null);
903
+ useEffect(() => {
904
+ setToday(adapter.today(displayTimezone));
905
+ }, [adapter, displayTimezone]);
906
+ const todayYear = today !== null ? adapter.getYear(today) : -1;
709
907
  const decadeStart = currentYear - currentYear % 12;
710
908
  const navigateDecade = useCallback(
711
909
  (direction) => {
712
- const newDate = adapter.addYears(viewMonth, direction * 12);
713
- ctx.setViewMonth(newDate);
910
+ ctx.setViewMonth(adapter.addYears(viewMonth, direction * 12));
714
911
  },
715
912
  [adapter, viewMonth, ctx]
716
913
  );
717
914
  const handleYearSelect = useCallback(
718
- (year) => {
915
+ (indexInDecade) => {
916
+ const year = decadeStart + indexInDecade;
719
917
  const currentMonth = adapter.getMonth(viewMonth);
720
918
  const target = new Date(Date.UTC(year, currentMonth, 1)).toISOString();
721
919
  ctx.setViewMonth(target);
722
920
  ctx.setFocusedDate(target);
723
921
  onSelect?.();
724
922
  },
725
- [adapter, viewMonth, ctx, onSelect]
726
- );
727
- const years = useMemo(
728
- () => Array.from({ length: 12 }, (_, i) => {
729
- const year = decadeStart + i;
730
- return {
731
- value: year,
732
- isSelected: year === currentYear,
733
- isCurrent: year === todayYear
734
- };
735
- }),
736
- [decadeStart, currentYear, todayYear]
737
- );
923
+ [adapter, viewMonth, ctx, onSelect, decadeStart]
924
+ );
925
+ const { gridRef, focusedIndex, handleKeyDown } = useGridState({
926
+ initialIndex: currentYear - decadeStart,
927
+ onSelect: handleYearSelect,
928
+ onPageUp: () => navigateDecade(-1),
929
+ onPageDown: () => navigateDecade(1),
930
+ onEscape: ctx.close
931
+ });
738
932
  const rangeLabel = `${decadeStart}\u2013${decadeStart + 11}`;
739
933
  return /* @__PURE__ */ jsxs("div", { className: classNames?.root, ...props, children: [
740
934
  /* @__PURE__ */ jsxs("div", { className: classNames?.header, children: [
@@ -763,30 +957,38 @@ function DatePickerYearGrid({
763
957
  /* @__PURE__ */ jsx(
764
958
  "div",
765
959
  {
960
+ ref: gridRef,
766
961
  role: "grid",
767
962
  "aria-label": rangeLabel,
768
963
  className: classNames?.grid,
769
964
  style: { display: "grid", gridTemplateColumns: "repeat(3, 1fr)" },
770
- children: years.map((y) => {
771
- const yearClass = [
965
+ onKeyDown: handleKeyDown,
966
+ children: Array.from({ length: 12 }, (_, i) => {
967
+ const year = decadeStart + i;
968
+ const isSelected = year === currentYear;
969
+ const isCurrent = year === todayYear;
970
+ const isFocused = i === focusedIndex;
971
+ const cls = [
772
972
  classNames?.year,
773
- y.isSelected && classNames?.yearSelected,
774
- y.isCurrent && classNames?.yearCurrent
973
+ isSelected && classNames?.yearSelected,
974
+ isCurrent && classNames?.yearCurrent
775
975
  ].filter(Boolean).join(" ") || void 0;
776
976
  return /* @__PURE__ */ jsx(
777
977
  "button",
778
978
  {
779
979
  type: "button",
780
980
  role: "gridcell",
781
- "aria-selected": y.isSelected || void 0,
782
- "aria-current": y.isCurrent ? "date" : void 0,
783
- "data-selected": y.isSelected || void 0,
784
- "data-current": y.isCurrent || void 0,
785
- className: yearClass,
786
- onClick: () => handleYearSelect(y.value),
787
- children: y.value
981
+ tabIndex: isFocused ? 0 : -1,
982
+ "aria-selected": isSelected || void 0,
983
+ "aria-current": isCurrent ? "date" : void 0,
984
+ "data-selected": isSelected || void 0,
985
+ "data-current": isCurrent || void 0,
986
+ "data-focused": isFocused || void 0,
987
+ className: cls,
988
+ onClick: () => handleYearSelect(i),
989
+ children: year
788
990
  },
789
- y.value
991
+ i
790
992
  );
791
993
  })
792
994
  }
@@ -795,16 +997,7 @@ function DatePickerYearGrid({
795
997
  }
796
998
  function DatePickerPresets({ classNames, children, ...props }) {
797
999
  const ctx = useDatePickerContext("DatePicker.Presets");
798
- return /* @__PURE__ */ jsx(
799
- "div",
800
- {
801
- role: "group",
802
- "aria-label": ctx.labels.popoverLabel,
803
- className: classNames?.root,
804
- ...props,
805
- children
806
- }
807
- );
1000
+ return /* @__PURE__ */ jsx("div", { role: "group", "aria-label": ctx.labels.popoverLabel, className: classNames?.root, ...props, children });
808
1001
  }
809
1002
  function resolveDatePreset(key, today, adapter) {
810
1003
  switch (key) {
@@ -858,11 +1051,7 @@ function DatePickerPreset({
858
1051
  if (directDate) {
859
1052
  target = directDate;
860
1053
  } else if (presetKey) {
861
- target = resolveDatePreset(
862
- presetKey,
863
- ctx.adapter.today(ctx.displayTimezone),
864
- ctx.adapter
865
- );
1054
+ target = resolveDatePreset(presetKey, ctx.adapter.today(ctx.displayTimezone), ctx.adapter);
866
1055
  } else {
867
1056
  return false;
868
1057
  }
@@ -872,8 +1061,7 @@ function DatePickerPreset({
872
1061
  "button",
873
1062
  {
874
1063
  type: "button",
875
- role: "option",
876
- "aria-selected": isActive,
1064
+ "aria-pressed": isActive,
877
1065
  "data-active": isActive || void 0,
878
1066
  disabled: ctx.isDisabled,
879
1067
  onClick: handleClick,
@@ -937,10 +1125,10 @@ function RangePickerRoot({
937
1125
  const [selectingTarget, setSelectingTarget] = useState("start");
938
1126
  const [hoverDate, setHoverDate] = useState(null);
939
1127
  const [viewMonth, setViewMonth] = useState(
940
- currentValue.start ?? adapter.today(displayTimezone)
1128
+ () => currentValue.start ?? adapter.today(displayTimezone)
941
1129
  );
942
1130
  const [focusedDate, setFocusedDate] = useState(
943
- currentValue.start ?? adapter.today(displayTimezone)
1131
+ () => currentValue.start ?? adapter.today(displayTimezone)
944
1132
  );
945
1133
  useChangeEffect(isOpen, onOpenChange);
946
1134
  const viewMonthStart = useMemo(() => adapter.startOfMonth(viewMonth), [viewMonth, adapter]);
@@ -1090,6 +1278,8 @@ var RangePickerInput = forwardRef(
1090
1278
  (e) => {
1091
1279
  if (e.key === "Escape") {
1092
1280
  ctx.close();
1281
+ } else if (e.key === "Enter" && ctx.isOpen) {
1282
+ e.preventDefault();
1093
1283
  } else if (e.key === "ArrowDown" && !ctx.isOpen) {
1094
1284
  e.preventDefault();
1095
1285
  ctx.open();
@@ -1126,15 +1316,17 @@ var RangePickerInput = forwardRef(
1126
1316
  );
1127
1317
  }
1128
1318
  );
1319
+ RangePickerInput.displayName = "RangePicker.Input";
1129
1320
  function RangePickerPopover({ children, ...props }) {
1130
1321
  const ctx = useRangePickerContext("RangePicker.Popover");
1131
1322
  const calendarId = `${ctx.pickerId}-calendar`;
1132
- const { floatingStyles, setFloatingRef } = usePopover({
1323
+ const { floatingStyles, setFloatingRef, isPositioned } = usePopover({
1133
1324
  isOpen: ctx.isOpen,
1134
1325
  close: ctx.close,
1135
1326
  referenceRef: ctx.referenceRef
1136
1327
  });
1137
1328
  if (!ctx.isOpen) return null;
1329
+ const { style: userStyle, ...rest } = props;
1138
1330
  return /* @__PURE__ */ jsx(
1139
1331
  "div",
1140
1332
  {
@@ -1143,8 +1335,12 @@ function RangePickerPopover({ children, ...props }) {
1143
1335
  role: "dialog",
1144
1336
  "aria-label": ctx.labels.popoverLabel,
1145
1337
  "aria-modal": "false",
1146
- style: floatingStyles,
1147
- ...props,
1338
+ ...rest,
1339
+ style: {
1340
+ ...userStyle,
1341
+ ...floatingStyles,
1342
+ visibility: isPositioned ? void 0 : "hidden"
1343
+ },
1148
1344
  children
1149
1345
  }
1150
1346
  );
@@ -1170,6 +1366,8 @@ var srOnly2 = {
1170
1366
  function RangePickerCalendar({
1171
1367
  classNames,
1172
1368
  selectionMode = "range",
1369
+ showWeekNumber = false,
1370
+ fixedWeeks = false,
1173
1371
  ...props
1174
1372
  }) {
1175
1373
  const ctx = useRangePickerContext("RangePicker.Calendar");
@@ -1187,23 +1385,36 @@ function RangePickerCalendar({
1187
1385
  displayTimezone
1188
1386
  } = ctx;
1189
1387
  const { locale } = ctx;
1190
- const weekdays = getWeekdayNames(locale, weekStartsOn);
1191
- const weeks = getCalendarDays(viewMonth, adapter, {
1192
- weekStartsOn,
1193
- focusedDate,
1194
- disabled,
1195
- range: value,
1196
- rangeHover: hoverDate,
1197
- timezone: displayTimezone
1198
- });
1388
+ const weekdays = useMemo(() => getWeekdayNames(locale, weekStartsOn), [locale, weekStartsOn]);
1389
+ const weeks = useMemo(
1390
+ () => getCalendarDays(viewMonth, adapter, {
1391
+ weekStartsOn,
1392
+ focusedDate,
1393
+ disabled,
1394
+ range: value,
1395
+ rangeHover: hoverDate,
1396
+ timezone: displayTimezone,
1397
+ fixedWeeks
1398
+ }),
1399
+ [
1400
+ viewMonth,
1401
+ adapter,
1402
+ weekStartsOn,
1403
+ focusedDate,
1404
+ disabled,
1405
+ value,
1406
+ hoverDate,
1407
+ displayTimezone,
1408
+ fixedWeeks
1409
+ ]
1410
+ );
1411
+ const thursdayIndex = weekStartsOn === 0 ? 4 : 3;
1199
1412
  const year = adapter.getYear(viewMonth);
1200
1413
  const month = adapter.getMonth(viewMonth);
1201
1414
  const title = formatMonthYear(year, month, locale);
1202
1415
  useEffect(() => {
1203
1416
  if (!ctx.isOpen || !gridRef.current) return;
1204
- const focusedButton = gridRef.current.querySelector(
1205
- '[data-focused="true"]'
1206
- );
1417
+ const focusedButton = gridRef.current.querySelector('[data-focused="true"]');
1207
1418
  focusedButton?.focus({ preventScroll: true });
1208
1419
  }, [focusedDate, ctx.isOpen]);
1209
1420
  const navigateMonth = useCallback(
@@ -1297,6 +1508,13 @@ function RangePickerCalendar({
1297
1508
  }
1298
1509
  if (newFocused) {
1299
1510
  e.preventDefault();
1511
+ const skipStep = e.key === "ArrowLeft" || e.key === "ArrowUp" || e.key === "PageUp" || e.key === "Home" ? -1 : 1;
1512
+ let attempts = 0;
1513
+ while (isDateDisabled(newFocused, disabled, adapter) && attempts < 42) {
1514
+ newFocused = adapter.addDays(newFocused, skipStep);
1515
+ attempts++;
1516
+ }
1517
+ if (attempts >= 42) return;
1300
1518
  ctx.setFocusedDate(newFocused);
1301
1519
  if (!adapter.isSameMonth(newFocused, viewMonth)) {
1302
1520
  ctx.setViewMonth(newFocused);
@@ -1306,7 +1524,18 @@ function RangePickerCalendar({
1306
1524
  }
1307
1525
  }
1308
1526
  },
1309
- [adapter, focusedDate, viewMonth, weekStartsOn, disabled, ctx, selectionMode, selectingTarget, value.start, commitDay]
1527
+ [
1528
+ adapter,
1529
+ focusedDate,
1530
+ viewMonth,
1531
+ weekStartsOn,
1532
+ disabled,
1533
+ ctx,
1534
+ selectionMode,
1535
+ selectingTarget,
1536
+ value.start,
1537
+ commitDay
1538
+ ]
1310
1539
  );
1311
1540
  return /* @__PURE__ */ jsxs("div", { className: classNames?.root, ...props, onMouseLeave: handleMouseLeave, children: [
1312
1541
  /* @__PURE__ */ jsxs("div", { className: classNames?.header, children: [
@@ -1338,63 +1567,90 @@ function RangePickerCalendar({
1338
1567
  ref: gridRef,
1339
1568
  role: "grid",
1340
1569
  "aria-label": title,
1341
- "aria-multiselectable": "true",
1570
+ "aria-rowcount": weeks.length + 1,
1571
+ "aria-colcount": 7,
1342
1572
  className: classNames?.grid,
1343
1573
  onKeyDown: handleKeyDown,
1344
1574
  children: [
1345
- /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { role: "row", children: weekdays.map((day) => /* @__PURE__ */ jsx(
1346
- "th",
1347
- {
1348
- role: "columnheader",
1349
- abbr: day.full,
1350
- scope: "col",
1351
- className: classNames?.weekdayHeader,
1352
- children: day.short
1353
- },
1354
- day.short
1355
- )) }) }),
1356
- /* @__PURE__ */ jsx("tbody", { children: weeks.map((week, weekIndex) => /* @__PURE__ */ jsx("tr", { role: "row", className: classNames?.gridRow, children: week.map((day) => {
1357
- const dayClasses = [
1358
- classNames?.day,
1359
- day.isRangeStart && classNames?.dayRangeStart,
1360
- day.isRangeEnd && classNames?.dayRangeEnd,
1361
- day.isInRange && classNames?.dayInRange,
1362
- day.isToday && classNames?.dayToday,
1363
- day.isDisabled && classNames?.dayDisabled,
1364
- !day.isCurrentMonth && classNames?.dayOutsideMonth
1365
- ].filter(Boolean).join(" ") || void 0;
1366
- const isSelected = selectionMode === "week" ? day.isRangeStart || day.isRangeEnd || day.isInRange : day.isRangeStart || day.isRangeEnd;
1367
- return /* @__PURE__ */ jsx(
1368
- "td",
1575
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { role: "row", "aria-rowindex": 1, children: [
1576
+ showWeekNumber ? /* @__PURE__ */ jsx("th", { scope: "col", "aria-hidden": "true", className: classNames?.weekNumberHeader, children: "#" }) : null,
1577
+ weekdays.map((day, colIndex) => /* @__PURE__ */ jsx(
1578
+ "th",
1369
1579
  {
1370
- role: "gridcell",
1371
- "aria-selected": isSelected || void 0,
1372
- "aria-disabled": day.isDisabled || void 0,
1373
- "aria-current": day.isToday ? "date" : void 0,
1374
- className: classNames?.gridCell,
1375
- children: /* @__PURE__ */ jsx(
1376
- "button",
1580
+ role: "columnheader",
1581
+ abbr: day.full,
1582
+ scope: "col",
1583
+ "aria-colindex": colIndex + 1,
1584
+ className: classNames?.weekdayHeader,
1585
+ children: day.short
1586
+ },
1587
+ day.short
1588
+ ))
1589
+ ] }) }),
1590
+ /* @__PURE__ */ jsx("tbody", { children: weeks.map((week, weekIndex) => /* @__PURE__ */ jsxs(
1591
+ "tr",
1592
+ {
1593
+ role: "row",
1594
+ "aria-rowindex": weekIndex + 2,
1595
+ className: classNames?.gridRow,
1596
+ children: [
1597
+ showWeekNumber ? /* @__PURE__ */ jsx(
1598
+ "th",
1377
1599
  {
1378
- type: "button",
1379
- tabIndex: day.isFocused ? 0 : -1,
1380
- disabled: day.isDisabled,
1381
- "data-focused": day.isFocused || void 0,
1382
- "data-range-start": day.isRangeStart || void 0,
1383
- "data-range-end": day.isRangeEnd || void 0,
1384
- "data-in-range": day.isInRange || void 0,
1385
- "data-today": day.isToday || void 0,
1386
- "data-outside-month": !day.isCurrentMonth || void 0,
1387
- className: dayClasses,
1388
- onClick: () => handleDayClick(day),
1389
- onMouseEnter: () => handleDayMouseEnter(day),
1390
- "aria-label": safeFormatFullDate2(day.isoString, locale),
1391
- children: day.dayNumber
1600
+ scope: "row",
1601
+ "aria-hidden": "true",
1602
+ className: classNames?.weekNumber,
1603
+ "data-week-number": true,
1604
+ children: getISOWeekNumber(week[thursdayIndex].isoString)
1392
1605
  }
1393
- )
1394
- },
1395
- day.isoString
1396
- );
1397
- }) }, weekIndex)) })
1606
+ ) : null,
1607
+ week.map((day, colIndex) => {
1608
+ const dayClasses = [
1609
+ classNames?.day,
1610
+ day.isRangeStart && classNames?.dayRangeStart,
1611
+ day.isRangeEnd && classNames?.dayRangeEnd,
1612
+ day.isInRange && classNames?.dayInRange,
1613
+ day.isToday && classNames?.dayToday,
1614
+ day.isDisabled && classNames?.dayDisabled,
1615
+ !day.isCurrentMonth && classNames?.dayOutsideMonth
1616
+ ].filter(Boolean).join(" ") || void 0;
1617
+ const isSelected = selectionMode === "week" ? day.isRangeStart || day.isRangeEnd || day.isInRange : day.isRangeStart || day.isRangeEnd;
1618
+ return /* @__PURE__ */ jsx(
1619
+ "td",
1620
+ {
1621
+ role: "gridcell",
1622
+ "aria-colindex": colIndex + 1,
1623
+ "aria-selected": isSelected || void 0,
1624
+ "aria-disabled": day.isDisabled || void 0,
1625
+ "aria-current": day.isToday ? "date" : void 0,
1626
+ className: classNames?.gridCell,
1627
+ children: /* @__PURE__ */ jsx(
1628
+ "button",
1629
+ {
1630
+ type: "button",
1631
+ tabIndex: day.isFocused ? 0 : -1,
1632
+ disabled: day.isDisabled,
1633
+ "data-focused": day.isFocused || void 0,
1634
+ "data-range-start": day.isRangeStart || void 0,
1635
+ "data-range-end": day.isRangeEnd || void 0,
1636
+ "data-in-range": day.isInRange || void 0,
1637
+ "data-today": day.isToday || void 0,
1638
+ "data-outside-month": !day.isCurrentMonth || void 0,
1639
+ className: dayClasses,
1640
+ onClick: () => handleDayClick(day),
1641
+ onMouseEnter: () => handleDayMouseEnter(day),
1642
+ "aria-label": safeFormatFullDate2(day.isoString, locale),
1643
+ children: day.dayNumber
1644
+ }
1645
+ )
1646
+ },
1647
+ day.isoString
1648
+ );
1649
+ })
1650
+ ]
1651
+ },
1652
+ weekIndex
1653
+ )) })
1398
1654
  ]
1399
1655
  }
1400
1656
  ),
@@ -1443,9 +1699,7 @@ function resolvePreset(key, today, adapter) {
1443
1699
  }
1444
1700
  case "thisYear": {
1445
1701
  const currentMonth = new Date(today).getUTCMonth();
1446
- const yearStart = adapter.startOfMonth(
1447
- adapter.addMonths(today, -currentMonth)
1448
- );
1702
+ const yearStart = adapter.startOfMonth(adapter.addMonths(today, -currentMonth));
1449
1703
  return { start: yearStart, end: today };
1450
1704
  }
1451
1705
  }
@@ -1458,41 +1712,33 @@ function RangePickerPreset({
1458
1712
  ...props
1459
1713
  }) {
1460
1714
  const ctx = useRangePickerContext("RangePicker.Preset");
1715
+ const resolved = useMemo(() => {
1716
+ if (directRange) return directRange;
1717
+ if (presetKey)
1718
+ return resolvePreset(presetKey, ctx.adapter.today(ctx.displayTimezone), ctx.adapter);
1719
+ return null;
1720
+ }, [directRange, presetKey, ctx.adapter, ctx.displayTimezone]);
1461
1721
  const handleClick = useCallback(
1462
1722
  (e) => {
1463
1723
  if (ctx.isDisabled || ctx.isReadOnly) return;
1464
- let resolved;
1465
- if (directRange) {
1466
- resolved = directRange;
1467
- } else if (presetKey) {
1468
- resolved = resolvePreset(presetKey, ctx.adapter.today(), ctx.adapter);
1469
- } else {
1470
- return;
1471
- }
1724
+ if (!resolved) return;
1472
1725
  ctx.setRange(resolved);
1473
1726
  ctx.close();
1474
1727
  onClick?.(e);
1475
1728
  },
1476
- [ctx, presetKey, directRange, onClick]
1729
+ [ctx, resolved, onClick]
1477
1730
  );
1478
- const isActive = (() => {
1479
- if (!ctx.value.start || !ctx.value.end) return false;
1480
- let target;
1481
- if (directRange) {
1482
- target = directRange;
1483
- } else if (presetKey) {
1484
- target = resolvePreset(presetKey, ctx.adapter.today(), ctx.adapter);
1485
- } else {
1731
+ const isActive = useMemo(() => {
1732
+ if (!ctx.value.start || !ctx.value.end || !resolved || !resolved.start || !resolved.end) {
1486
1733
  return false;
1487
1734
  }
1488
- return target.start !== null && target.end !== null && ctx.adapter.isSameDay(ctx.value.start, target.start) && ctx.adapter.isSameDay(ctx.value.end, target.end);
1489
- })();
1735
+ return ctx.adapter.isSameDay(ctx.value.start, resolved.start) && ctx.adapter.isSameDay(ctx.value.end, resolved.end);
1736
+ }, [ctx.value.start, ctx.value.end, ctx.adapter, resolved]);
1490
1737
  return /* @__PURE__ */ jsx(
1491
1738
  "button",
1492
1739
  {
1493
1740
  type: "button",
1494
- role: "option",
1495
- "aria-selected": isActive,
1741
+ "aria-pressed": isActive,
1496
1742
  "data-active": isActive || void 0,
1497
1743
  disabled: ctx.isDisabled,
1498
1744
  onClick: handleClick,
@@ -1525,9 +1771,6 @@ function useTimePickerContext(componentName) {
1525
1771
  }
1526
1772
  return context;
1527
1773
  }
1528
- function getDefaultIso() {
1529
- return DateFnsAdapter.today();
1530
- }
1531
1774
  function TimePickerRoot({
1532
1775
  value: controlledValue,
1533
1776
  defaultValue,
@@ -1538,6 +1781,7 @@ function TimePickerRoot({
1538
1781
  displayTimezone,
1539
1782
  disabled = false,
1540
1783
  readOnly = false,
1784
+ filterTime,
1541
1785
  labels: labelsProp,
1542
1786
  children
1543
1787
  }) {
@@ -1551,21 +1795,21 @@ function TimePickerRoot({
1551
1795
  defaultValue ?? null
1552
1796
  );
1553
1797
  const currentValue = isControlled ? controlledValue ?? null : uncontrolledValue;
1554
- const baseIso = currentValue ?? getDefaultIso();
1555
- const currentTime = useMemo(
1556
- () => displayTimezone ? getTimeInTimezone(baseIso, displayTimezone) : getTime(baseIso),
1557
- [baseIso, displayTimezone]
1558
- );
1798
+ const currentTime = useMemo(() => {
1799
+ if (!currentValue) return { hours: 0, minutes: 0, seconds: 0 };
1800
+ return displayTimezone ? getTimeInTimezone(currentValue, displayTimezone) : getTime(currentValue);
1801
+ }, [currentValue, displayTimezone]);
1559
1802
  const setTime$1 = useCallback(
1560
1803
  (partial) => {
1561
1804
  if (disabled || readOnly) return;
1562
- const newIso = displayTimezone ? setTimeInTimezone(baseIso, partial, displayTimezone) : setTime(baseIso, partial);
1805
+ const base = currentValue ?? DateFnsAdapter.today(displayTimezone);
1806
+ const newIso = displayTimezone ? setTimeInTimezone(base, partial, displayTimezone) : setTime(base, partial);
1563
1807
  if (!isControlled) {
1564
1808
  setUncontrolledValue(newIso);
1565
1809
  }
1566
1810
  onChange?.(newIso);
1567
1811
  },
1568
- [disabled, readOnly, baseIso, isControlled, onChange, displayTimezone]
1812
+ [disabled, readOnly, currentValue, displayTimezone, isControlled, onChange]
1569
1813
  );
1570
1814
  const contextValue = useMemo(
1571
1815
  () => ({
@@ -1579,9 +1823,23 @@ function TimePickerRoot({
1579
1823
  isReadOnly: readOnly,
1580
1824
  currentTime,
1581
1825
  pickerId,
1582
- labels: mergedLabels
1826
+ labels: mergedLabels,
1827
+ filterTime
1583
1828
  }),
1584
- [currentValue, setTime$1, format, step, withSeconds, displayTimezone, disabled, readOnly, currentTime, pickerId, mergedLabels]
1829
+ [
1830
+ currentValue,
1831
+ setTime$1,
1832
+ format,
1833
+ step,
1834
+ withSeconds,
1835
+ displayTimezone,
1836
+ disabled,
1837
+ readOnly,
1838
+ currentTime,
1839
+ pickerId,
1840
+ mergedLabels,
1841
+ filterTime
1842
+ ]
1585
1843
  );
1586
1844
  return /* @__PURE__ */ jsx(TimePickerContext.Provider, { value: contextValue, children });
1587
1845
  }
@@ -1598,12 +1856,9 @@ var TimePickerInput = forwardRef(
1598
1856
  }
1599
1857
  setInputText(null);
1600
1858
  }, [inputText, ctx]);
1601
- const handleChange = useCallback(
1602
- (e) => {
1603
- setInputText(e.target.value);
1604
- },
1605
- []
1606
- );
1859
+ const handleChange = useCallback((e) => {
1860
+ setInputText(e.target.value);
1861
+ }, []);
1607
1862
  const handleBlur = useCallback(
1608
1863
  (e) => {
1609
1864
  commitInput();
@@ -1640,6 +1895,7 @@ var TimePickerInput = forwardRef(
1640
1895
  );
1641
1896
  }
1642
1897
  );
1898
+ TimePickerInput.displayName = "TimePicker.Input";
1643
1899
  function useListboxNavigation({
1644
1900
  items,
1645
1901
  onSelect,
@@ -1676,9 +1932,7 @@ function useListboxNavigation({
1676
1932
  onSelect(target);
1677
1933
  cancelAnimationFrame(rafIdRef.current);
1678
1934
  rafIdRef.current = requestAnimationFrame(() => {
1679
- const next = listRef.current?.querySelector(
1680
- '[data-selected="true"]'
1681
- );
1935
+ const next = listRef.current?.querySelector('[data-selected="true"]');
1682
1936
  next?.focus();
1683
1937
  });
1684
1938
  }
@@ -1689,17 +1943,41 @@ function useListboxNavigation({
1689
1943
  }
1690
1944
  function TimePickerHourList({ classNames, ...props }) {
1691
1945
  const ctx = useTimePickerContext("TimePicker.HourList");
1692
- const { format, currentTime, isDisabled, isReadOnly } = ctx;
1693
- const hours = generateHours(format);
1946
+ const { format, step, currentTime, isDisabled, isReadOnly, filterTime } = ctx;
1947
+ const hours = useMemo(() => generateHours(format), [format]);
1694
1948
  const selectedHourDisplay = format === "12h" ? to12Hour(currentTime.hours).hours12 : currentTime.hours;
1695
1949
  const currentPeriod = format === "12h" ? to12Hour(currentTime.hours).period : null;
1950
+ const fullyDisabledHours24 = useMemo(() => {
1951
+ if (!filterTime) return null;
1952
+ const disabled = /* @__PURE__ */ new Set();
1953
+ for (let h = 0; h < 24; h++) {
1954
+ let allRejected = true;
1955
+ for (let m = 0; m < 60; m += step) {
1956
+ if (!filterTime(h, m)) {
1957
+ allRejected = false;
1958
+ break;
1959
+ }
1960
+ }
1961
+ if (allRejected) disabled.add(h);
1962
+ }
1963
+ return disabled;
1964
+ }, [filterTime, step]);
1965
+ const isHourDisabled = useCallback(
1966
+ (hourDisplay) => {
1967
+ if (!fullyDisabledHours24) return false;
1968
+ const hours24 = format === "12h" && currentPeriod ? to24Hour(hourDisplay, currentPeriod) : hourDisplay;
1969
+ return fullyDisabledHours24.has(hours24);
1970
+ },
1971
+ [fullyDisabledHours24, format, currentPeriod]
1972
+ );
1696
1973
  const handleSelect = useCallback(
1697
1974
  (hourDisplay) => {
1698
1975
  if (isDisabled || isReadOnly) return;
1976
+ if (isHourDisabled(hourDisplay)) return;
1699
1977
  const hours24 = format === "12h" && currentPeriod ? to24Hour(hourDisplay, currentPeriod) : hourDisplay;
1700
1978
  ctx.setTime({ hours: hours24 });
1701
1979
  },
1702
- [format, currentPeriod, ctx, isDisabled, isReadOnly]
1980
+ [format, currentPeriod, ctx, isDisabled, isReadOnly, isHourDisabled]
1703
1981
  );
1704
1982
  const { listRef, handleKeyDown } = useListboxNavigation({
1705
1983
  items: hours,
@@ -1717,13 +1995,14 @@ function TimePickerHourList({ classNames, ...props }) {
1717
1995
  ...props,
1718
1996
  children: hours.map((hour) => {
1719
1997
  const isSelected = hour === selectedHourDisplay;
1998
+ const isHourFullyDisabled = isHourDisabled(hour);
1720
1999
  const optionClass = [classNames?.option, isSelected && classNames?.optionSelected].filter(Boolean).join(" ") || void 0;
1721
2000
  return /* @__PURE__ */ jsx(
1722
2001
  "li",
1723
2002
  {
1724
2003
  role: "option",
1725
2004
  "aria-selected": isSelected,
1726
- "aria-disabled": isDisabled || void 0,
2005
+ "aria-disabled": isDisabled || isHourFullyDisabled || void 0,
1727
2006
  "aria-label": ctx.labels.hourOption(hour),
1728
2007
  "data-selected": isSelected || void 0,
1729
2008
  tabIndex: isSelected ? 0 : -1,
@@ -1740,14 +2019,22 @@ function TimePickerHourList({ classNames, ...props }) {
1740
2019
  }
1741
2020
  function TimePickerMinuteList({ classNames, ...props }) {
1742
2021
  const ctx = useTimePickerContext("TimePicker.MinuteList");
1743
- const { step, currentTime, isDisabled, isReadOnly } = ctx;
1744
- const minutes = generateMinutes(step);
2022
+ const { step, currentTime, isDisabled, isReadOnly, filterTime } = ctx;
2023
+ const minutes = useMemo(() => generateMinutes(step), [step]);
2024
+ const isMinuteDisabled = useCallback(
2025
+ (minute) => {
2026
+ if (!filterTime) return false;
2027
+ return filterTime(currentTime.hours, minute);
2028
+ },
2029
+ [filterTime, currentTime.hours]
2030
+ );
1745
2031
  const handleSelect = useCallback(
1746
2032
  (minute) => {
1747
2033
  if (isDisabled || isReadOnly) return;
2034
+ if (isMinuteDisabled(minute)) return;
1748
2035
  ctx.setTime({ minutes: minute });
1749
2036
  },
1750
- [ctx, isDisabled, isReadOnly]
2037
+ [ctx, isDisabled, isReadOnly, isMinuteDisabled]
1751
2038
  );
1752
2039
  const { listRef, handleKeyDown } = useListboxNavigation({
1753
2040
  items: minutes,
@@ -1765,13 +2052,14 @@ function TimePickerMinuteList({ classNames, ...props }) {
1765
2052
  ...props,
1766
2053
  children: minutes.map((minute) => {
1767
2054
  const isSelected = minute === currentTime.minutes;
2055
+ const isMinuteFullyDisabled = isMinuteDisabled(minute);
1768
2056
  const optionClass = [classNames?.option, isSelected && classNames?.optionSelected].filter(Boolean).join(" ") || void 0;
1769
2057
  return /* @__PURE__ */ jsx(
1770
2058
  "li",
1771
2059
  {
1772
2060
  role: "option",
1773
2061
  "aria-selected": isSelected,
1774
- "aria-disabled": isDisabled || void 0,
2062
+ "aria-disabled": isDisabled || isMinuteFullyDisabled || void 0,
1775
2063
  "aria-label": ctx.labels.minuteOption(minute),
1776
2064
  "data-selected": isSelected || void 0,
1777
2065
  tabIndex: isSelected ? 0 : -1,
@@ -1788,37 +2076,87 @@ function TimePickerMinuteList({ classNames, ...props }) {
1788
2076
  }
1789
2077
  function TimePickerAmPmToggle({ classNames, ...props }) {
1790
2078
  const ctx = useTimePickerContext("TimePicker.AmPmToggle");
1791
- if (ctx.format !== "12h") return null;
1792
- const { period, hours12 } = to12Hour(ctx.currentTime.hours);
2079
+ const amRef = useRef(null);
2080
+ const pmRef = useRef(null);
1793
2081
  const setPeriod = useCallback(
1794
2082
  (newPeriod) => {
1795
2083
  if (ctx.isDisabled || ctx.isReadOnly) return;
2084
+ const { hours12 } = to12Hour(ctx.currentTime.hours);
1796
2085
  const newHours24 = to24Hour(hours12, newPeriod);
1797
2086
  ctx.setTime({ hours: newHours24 });
1798
2087
  },
1799
- [hours12, ctx]
2088
+ [ctx]
1800
2089
  );
2090
+ if (ctx.format !== "12h") return null;
2091
+ const { period } = to12Hour(ctx.currentTime.hours);
2092
+ const focusOther = (target) => {
2093
+ (target === "AM" ? amRef : pmRef).current?.focus();
2094
+ };
2095
+ const handleKeyDown = (e, target) => {
2096
+ switch (e.key) {
2097
+ case "ArrowRight":
2098
+ case "ArrowDown":
2099
+ case "ArrowLeft":
2100
+ case "ArrowUp": {
2101
+ e.preventDefault();
2102
+ const next = target === "AM" ? "PM" : "AM";
2103
+ setPeriod(next);
2104
+ focusOther(next);
2105
+ break;
2106
+ }
2107
+ case "Home": {
2108
+ e.preventDefault();
2109
+ setPeriod("AM");
2110
+ focusOther("AM");
2111
+ break;
2112
+ }
2113
+ case "End": {
2114
+ e.preventDefault();
2115
+ setPeriod("PM");
2116
+ focusOther("PM");
2117
+ break;
2118
+ }
2119
+ case " ":
2120
+ case "Enter": {
2121
+ e.preventDefault();
2122
+ setPeriod(target);
2123
+ break;
2124
+ }
2125
+ }
2126
+ };
1801
2127
  const renderButton = (target) => {
1802
2128
  const isSelected = period === target;
1803
2129
  const optionClass = [classNames?.option, isSelected && classNames?.optionSelected].filter(Boolean).join(" ") || void 0;
1804
2130
  return /* @__PURE__ */ jsx(
1805
2131
  "button",
1806
2132
  {
2133
+ ref: target === "AM" ? amRef : pmRef,
1807
2134
  type: "button",
1808
2135
  role: "radio",
1809
2136
  "aria-checked": isSelected,
2137
+ tabIndex: isSelected ? 0 : -1,
1810
2138
  "data-selected": isSelected || void 0,
1811
2139
  disabled: ctx.isDisabled,
1812
2140
  className: optionClass,
1813
2141
  onClick: () => setPeriod(target),
2142
+ onKeyDown: (e) => handleKeyDown(e, target),
1814
2143
  children: target
1815
2144
  }
1816
2145
  );
1817
2146
  };
1818
- return /* @__PURE__ */ jsxs("div", { role: "radiogroup", "aria-label": ctx.labels.amPmToggle, className: classNames?.root, ...props, children: [
1819
- renderButton("AM"),
1820
- renderButton("PM")
1821
- ] });
2147
+ return /* @__PURE__ */ jsxs(
2148
+ "div",
2149
+ {
2150
+ role: "radiogroup",
2151
+ "aria-label": ctx.labels.amPmToggle,
2152
+ className: classNames?.root,
2153
+ ...props,
2154
+ children: [
2155
+ renderButton("AM"),
2156
+ renderButton("PM")
2157
+ ]
2158
+ }
2159
+ );
1822
2160
  }
1823
2161
 
1824
2162
  // src/components/TimePicker/index.ts
@@ -1828,9 +2166,6 @@ var TimePicker = Object.assign(TimePickerRoot, {
1828
2166
  MinuteList: TimePickerMinuteList,
1829
2167
  AmPmToggle: TimePickerAmPmToggle
1830
2168
  });
1831
- function getDefaultIso2() {
1832
- return DateFnsAdapter.today();
1833
- }
1834
2169
  function DateTimePickerRoot({
1835
2170
  value: controlledValue,
1836
2171
  defaultValue,
@@ -1839,6 +2174,8 @@ function DateTimePickerRoot({
1839
2174
  onCalendarNavigate,
1840
2175
  format = "24h",
1841
2176
  step = 1,
2177
+ withSeconds = false,
2178
+ filterTime,
1842
2179
  disabled = false,
1843
2180
  readOnly = false,
1844
2181
  weekStartsOn = 0,
@@ -1866,10 +2203,10 @@ function DateTimePickerRoot({
1866
2203
  const currentValue = isControlled ? controlledValue ?? null : uncontrolledValue;
1867
2204
  const [isOpen, setIsOpen] = useState(false);
1868
2205
  const [viewMonth, setViewMonth] = useState(
1869
- currentValue ?? adapter.today(displayTimezone)
2206
+ () => currentValue ?? adapter.today(displayTimezone)
1870
2207
  );
1871
2208
  const [focusedDate, setFocusedDate] = useState(
1872
- currentValue ?? adapter.today(displayTimezone)
2209
+ () => currentValue ?? adapter.today(displayTimezone)
1873
2210
  );
1874
2211
  useChangeEffect(isOpen, onOpenChange);
1875
2212
  const viewMonthStart = useMemo(() => adapter.startOfMonth(viewMonth), [viewMonth, adapter]);
@@ -1879,11 +2216,10 @@ function DateTimePickerRoot({
1879
2216
  () => Array.isArray(disabled) ? disabled : [],
1880
2217
  [disabled]
1881
2218
  );
1882
- const baseIso = currentValue ?? getDefaultIso2();
1883
- const currentTime = useMemo(
1884
- () => displayTimezone ? getTimeInTimezone(baseIso, displayTimezone) : getTime(baseIso),
1885
- [baseIso, displayTimezone]
1886
- );
2219
+ const currentTime = useMemo(() => {
2220
+ if (!currentValue) return { hours: 0, minutes: 0, seconds: 0 };
2221
+ return displayTimezone ? getTimeInTimezone(currentValue, displayTimezone) : getTime(currentValue);
2222
+ }, [currentValue, displayTimezone]);
1887
2223
  const updateValue = useCallback(
1888
2224
  (next) => {
1889
2225
  if (isDisabled || readOnly) return;
@@ -1909,11 +2245,11 @@ function DateTimePickerRoot({
1909
2245
  );
1910
2246
  const setTime$1 = useCallback(
1911
2247
  (partial) => {
1912
- const base = currentValue ?? getDefaultIso2();
2248
+ const base = currentValue ?? adapter.today(displayTimezone);
1913
2249
  const merged = displayTimezone ? setTimeInTimezone(base, partial, displayTimezone) : setTime(base, partial);
1914
2250
  updateValue(merged);
1915
2251
  },
1916
- [currentValue, updateValue, displayTimezone]
2252
+ [currentValue, updateValue, displayTimezone, adapter]
1917
2253
  );
1918
2254
  const open = useCallback(() => {
1919
2255
  if (isDisabled || readOnly) return;
@@ -1980,15 +2316,29 @@ function DateTimePickerRoot({
1980
2316
  setTime: setTime$1,
1981
2317
  format,
1982
2318
  step,
1983
- withSeconds: false,
2319
+ withSeconds,
1984
2320
  displayTimezone,
1985
2321
  isDisabled,
1986
2322
  isReadOnly: readOnly,
1987
2323
  currentTime,
1988
2324
  pickerId,
1989
- labels: mergedTimeLabels
2325
+ labels: mergedTimeLabels,
2326
+ filterTime
1990
2327
  }),
1991
- [currentValue, setTime$1, format, step, displayTimezone, isDisabled, readOnly, currentTime, pickerId, mergedTimeLabels]
2328
+ [
2329
+ currentValue,
2330
+ setTime$1,
2331
+ format,
2332
+ step,
2333
+ withSeconds,
2334
+ displayTimezone,
2335
+ isDisabled,
2336
+ readOnly,
2337
+ currentTime,
2338
+ pickerId,
2339
+ mergedTimeLabels,
2340
+ filterTime
2341
+ ]
1992
2342
  );
1993
2343
  return /* @__PURE__ */ jsx(DatePickerContext.Provider, { value: dateContext, children: /* @__PURE__ */ jsx(TimePickerContext.Provider, { value: timeContext, children }) });
1994
2344
  }
@@ -2016,6 +2366,8 @@ var DateTimePickerInput = forwardRef(
2016
2366
  (e) => {
2017
2367
  if (e.key === "Escape") {
2018
2368
  ctx.close();
2369
+ } else if (e.key === "Enter" && ctx.isOpen) {
2370
+ e.preventDefault();
2019
2371
  } else if (e.key === "ArrowDown" && !ctx.isOpen) {
2020
2372
  e.preventDefault();
2021
2373
  ctx.open();
@@ -2051,6 +2403,7 @@ var DateTimePickerInput = forwardRef(
2051
2403
  );
2052
2404
  }
2053
2405
  );
2406
+ DateTimePickerInput.displayName = "DateTimePicker.Input";
2054
2407
 
2055
2408
  // src/components/DateTimePicker/index.ts
2056
2409
  var DateTimePicker = Object.assign(DateTimePickerRoot, {
@@ -2067,12 +2420,9 @@ function MonthPickerRoot(props) {
2067
2420
  const displayFormat = props.displayFormat ?? "yyyy-MM";
2068
2421
  return /* @__PURE__ */ jsx(DatePickerRoot, { ...props, displayFormat });
2069
2422
  }
2070
- function MonthPickerGrid({
2071
- classNames,
2072
- ...props
2073
- }) {
2423
+ function MonthPickerGrid({ classNames, ...props }) {
2074
2424
  const ctx = useDatePickerContext("MonthPicker.Grid");
2075
- const { adapter, viewMonth, locale, value, displayTimezone, labels } = ctx;
2425
+ const { adapter, viewMonth, locale, value, displayTimezone, labels, disabled } = ctx;
2076
2426
  const currentYear = adapter.getYear(viewMonth);
2077
2427
  const [valueYear, valueMonthZeroBased] = useMemo(() => {
2078
2428
  if (!value) return [null, null];
@@ -2083,9 +2433,19 @@ function MonthPickerGrid({
2083
2433
  return [null, null];
2084
2434
  }
2085
2435
  }, [value, adapter, displayTimezone]);
2086
- const today = adapter.today(displayTimezone);
2087
- const todayYear = adapter.getYear(today);
2088
- const todayMonth = adapter.getMonth(today);
2436
+ const [today, setToday] = useState(null);
2437
+ useEffect(() => {
2438
+ setToday(adapter.today(displayTimezone));
2439
+ }, [adapter, displayTimezone]);
2440
+ const todayYear = today !== null ? adapter.getYear(today) : -1;
2441
+ const todayMonth = today !== null ? adapter.getMonth(today) : -1;
2442
+ const monthDisabledFlags = useMemo(
2443
+ () => Array.from({ length: 12 }, (_, i) => {
2444
+ const monthStart = new Date(Date.UTC(currentYear, i, 1)).toISOString();
2445
+ return isRangeFullyDisabled(monthStart, adapter.endOfMonth(monthStart), disabled, adapter);
2446
+ }),
2447
+ [currentYear, disabled, adapter]
2448
+ );
2089
2449
  const navigateYear = useCallback(
2090
2450
  (direction) => {
2091
2451
  ctx.setViewMonth(adapter.addYears(viewMonth, direction));
@@ -2094,19 +2454,23 @@ function MonthPickerGrid({
2094
2454
  );
2095
2455
  const handleMonthSelect = useCallback(
2096
2456
  (monthIndex) => {
2097
- const target = new Date(
2098
- Date.UTC(currentYear, monthIndex, 1)
2099
- ).toISOString();
2457
+ if (monthDisabledFlags[monthIndex]) return;
2458
+ const target = new Date(Date.UTC(currentYear, monthIndex, 1)).toISOString();
2100
2459
  ctx.selectDate(target);
2101
2460
  },
2102
- [currentYear, ctx]
2103
- );
2104
- const months = Array.from({ length: 12 }, (_, i) => ({
2105
- index: i,
2106
- name: getMonthName(i, locale),
2107
- isSelected: valueYear === currentYear && valueMonthZeroBased === i,
2108
- isCurrent: todayYear === currentYear && todayMonth === i
2109
- }));
2461
+ [currentYear, ctx, monthDisabledFlags]
2462
+ );
2463
+ const naturalIndex = valueYear === currentYear && valueMonthZeroBased !== null ? valueMonthZeroBased : adapter.getMonth(viewMonth);
2464
+ const firstEnabled = monthDisabledFlags.findIndex((d) => !d);
2465
+ const initialIndex = monthDisabledFlags[naturalIndex] ? firstEnabled === -1 ? naturalIndex : firstEnabled : naturalIndex;
2466
+ const { gridRef, focusedIndex, handleKeyDown } = useGridState({
2467
+ initialIndex,
2468
+ disabledFlags: monthDisabledFlags,
2469
+ onSelect: handleMonthSelect,
2470
+ onPageUp: () => navigateYear(-1),
2471
+ onPageDown: () => navigateYear(1),
2472
+ onEscape: ctx.close
2473
+ });
2110
2474
  return /* @__PURE__ */ jsxs("div", { className: classNames?.root, ...props, children: [
2111
2475
  /* @__PURE__ */ jsxs("div", { className: classNames?.header, children: [
2112
2476
  /* @__PURE__ */ jsx(
@@ -2134,35 +2498,47 @@ function MonthPickerGrid({
2134
2498
  /* @__PURE__ */ jsx(
2135
2499
  "div",
2136
2500
  {
2501
+ ref: gridRef,
2137
2502
  role: "grid",
2138
2503
  "aria-label": `${currentYear} months`,
2139
2504
  className: classNames?.grid,
2505
+ onKeyDown: handleKeyDown,
2140
2506
  children: Array.from({ length: 4 }, (_, rowIndex) => /* @__PURE__ */ jsx(
2141
2507
  "div",
2142
2508
  {
2143
2509
  role: "row",
2144
2510
  className: classNames?.gridRow,
2145
2511
  style: { display: "grid", gridTemplateColumns: "repeat(3, 1fr)" },
2146
- children: months.slice(rowIndex * 3, rowIndex * 3 + 3).map((m) => {
2147
- const monthClass = [
2512
+ children: Array.from({ length: 3 }, (_2, col) => {
2513
+ const i = rowIndex * 3 + col;
2514
+ const isSelected = valueYear === currentYear && valueMonthZeroBased === i;
2515
+ const isCurrent = todayYear === currentYear && todayMonth === i;
2516
+ const isFocused = i === focusedIndex;
2517
+ const isDisabled = monthDisabledFlags[i] ?? false;
2518
+ const cls = [
2148
2519
  classNames?.month,
2149
- m.isSelected && classNames?.monthSelected,
2150
- m.isCurrent && classNames?.monthCurrent
2520
+ isSelected && classNames?.monthSelected,
2521
+ isCurrent && classNames?.monthCurrent,
2522
+ isDisabled && classNames?.monthDisabled
2151
2523
  ].filter(Boolean).join(" ") || void 0;
2152
2524
  return /* @__PURE__ */ jsx(
2153
2525
  "button",
2154
2526
  {
2155
2527
  type: "button",
2156
2528
  role: "gridcell",
2157
- "aria-selected": m.isSelected || void 0,
2158
- "aria-current": m.isCurrent ? "date" : void 0,
2159
- "data-selected": m.isSelected || void 0,
2160
- "data-current": m.isCurrent || void 0,
2161
- className: monthClass,
2162
- onClick: () => handleMonthSelect(m.index),
2163
- children: m.name
2529
+ tabIndex: isFocused ? 0 : -1,
2530
+ disabled: isDisabled,
2531
+ "aria-selected": isSelected || void 0,
2532
+ "aria-disabled": isDisabled || void 0,
2533
+ "aria-current": isCurrent ? "date" : void 0,
2534
+ "data-selected": isSelected || void 0,
2535
+ "data-current": isCurrent || void 0,
2536
+ "data-focused": isFocused || void 0,
2537
+ className: cls,
2538
+ onClick: () => handleMonthSelect(i),
2539
+ children: getMonthName(i, locale)
2164
2540
  },
2165
- m.index
2541
+ i
2166
2542
  );
2167
2543
  })
2168
2544
  },
@@ -2186,7 +2562,7 @@ function YearPickerRoot(props) {
2186
2562
  }
2187
2563
  function YearPickerGrid({ classNames, ...props }) {
2188
2564
  const ctx = useDatePickerContext("YearPicker.Grid");
2189
- const { adapter, viewMonth, value, displayTimezone, labels } = ctx;
2565
+ const { adapter, viewMonth, value, displayTimezone, labels, disabled } = ctx;
2190
2566
  const currentYear = adapter.getYear(viewMonth);
2191
2567
  const decadeStart = currentYear - currentYear % 12;
2192
2568
  const valueYear = useMemo(() => {
@@ -2197,7 +2573,20 @@ function YearPickerGrid({ classNames, ...props }) {
2197
2573
  return null;
2198
2574
  }
2199
2575
  }, [value, adapter, displayTimezone]);
2200
- const todayYear = adapter.getYear(adapter.today(displayTimezone));
2576
+ const [today, setToday] = useState(null);
2577
+ useEffect(() => {
2578
+ setToday(adapter.today(displayTimezone));
2579
+ }, [adapter, displayTimezone]);
2580
+ const todayYear = today !== null ? adapter.getYear(today) : -1;
2581
+ const yearDisabledFlags = useMemo(
2582
+ () => Array.from({ length: 12 }, (_, i) => {
2583
+ const year = decadeStart + i;
2584
+ const yearStart = new Date(Date.UTC(year, 0, 1)).toISOString();
2585
+ const yearEnd = new Date(Date.UTC(year, 11, 31, 23, 59, 59, 999)).toISOString();
2586
+ return isRangeFullyDisabled(yearStart, yearEnd, disabled, adapter);
2587
+ }),
2588
+ [decadeStart, disabled, adapter]
2589
+ );
2201
2590
  const navigateDecade = useCallback(
2202
2591
  (direction) => {
2203
2592
  ctx.setViewMonth(adapter.addYears(viewMonth, direction * 12));
@@ -2205,19 +2594,24 @@ function YearPickerGrid({ classNames, ...props }) {
2205
2594
  [adapter, viewMonth, ctx]
2206
2595
  );
2207
2596
  const handleYearSelect = useCallback(
2208
- (year) => {
2597
+ (indexInDecade) => {
2598
+ if (yearDisabledFlags[indexInDecade]) return;
2599
+ const year = decadeStart + indexInDecade;
2209
2600
  const target = new Date(Date.UTC(year, 0, 1)).toISOString();
2210
2601
  ctx.selectDate(target);
2211
2602
  },
2212
- [ctx]
2213
- );
2214
- const years = Array.from({ length: 12 }, (_, i) => {
2215
- const year = decadeStart + i;
2216
- return {
2217
- value: year,
2218
- isSelected: year === valueYear,
2219
- isCurrent: year === todayYear
2220
- };
2603
+ [ctx, decadeStart, yearDisabledFlags]
2604
+ );
2605
+ const naturalIndex = valueYear !== null && valueYear >= decadeStart && valueYear <= decadeStart + 11 ? valueYear - decadeStart : currentYear - decadeStart;
2606
+ const firstEnabled = yearDisabledFlags.findIndex((d) => !d);
2607
+ const initialIndex = yearDisabledFlags[naturalIndex] ? firstEnabled === -1 ? naturalIndex : firstEnabled : naturalIndex;
2608
+ const { gridRef, focusedIndex, handleKeyDown } = useGridState({
2609
+ initialIndex,
2610
+ disabledFlags: yearDisabledFlags,
2611
+ onSelect: handleYearSelect,
2612
+ onPageUp: () => navigateDecade(-1),
2613
+ onPageDown: () => navigateDecade(1),
2614
+ onEscape: ctx.close
2221
2615
  });
2222
2616
  const rangeLabel = `${decadeStart}\u2013${decadeStart + 11}`;
2223
2617
  return /* @__PURE__ */ jsxs("div", { className: classNames?.root, ...props, children: [
@@ -2247,35 +2641,48 @@ function YearPickerGrid({ classNames, ...props }) {
2247
2641
  /* @__PURE__ */ jsx(
2248
2642
  "div",
2249
2643
  {
2644
+ ref: gridRef,
2250
2645
  role: "grid",
2251
2646
  "aria-label": rangeLabel,
2252
2647
  className: classNames?.grid,
2648
+ onKeyDown: handleKeyDown,
2253
2649
  children: Array.from({ length: 4 }, (_, rowIndex) => /* @__PURE__ */ jsx(
2254
2650
  "div",
2255
2651
  {
2256
2652
  role: "row",
2257
2653
  className: classNames?.gridRow,
2258
2654
  style: { display: "grid", gridTemplateColumns: "repeat(3, 1fr)" },
2259
- children: years.slice(rowIndex * 3, rowIndex * 3 + 3).map((y) => {
2260
- const yearClass = [
2655
+ children: Array.from({ length: 3 }, (_2, col) => {
2656
+ const i = rowIndex * 3 + col;
2657
+ const year = decadeStart + i;
2658
+ const isSelected = year === valueYear;
2659
+ const isCurrent = year === todayYear;
2660
+ const isFocused = i === focusedIndex;
2661
+ const isDisabled = yearDisabledFlags[i] ?? false;
2662
+ const cls = [
2261
2663
  classNames?.year,
2262
- y.isSelected && classNames?.yearSelected,
2263
- y.isCurrent && classNames?.yearCurrent
2664
+ isSelected && classNames?.yearSelected,
2665
+ isCurrent && classNames?.yearCurrent,
2666
+ isDisabled && classNames?.yearDisabled
2264
2667
  ].filter(Boolean).join(" ") || void 0;
2265
2668
  return /* @__PURE__ */ jsx(
2266
2669
  "button",
2267
2670
  {
2268
2671
  type: "button",
2269
2672
  role: "gridcell",
2270
- "aria-selected": y.isSelected || void 0,
2271
- "aria-current": y.isCurrent ? "date" : void 0,
2272
- "data-selected": y.isSelected || void 0,
2273
- "data-current": y.isCurrent || void 0,
2274
- className: yearClass,
2275
- onClick: () => handleYearSelect(y.value),
2276
- children: y.value
2673
+ tabIndex: isFocused ? 0 : -1,
2674
+ disabled: isDisabled,
2675
+ "aria-selected": isSelected || void 0,
2676
+ "aria-disabled": isDisabled || void 0,
2677
+ "aria-current": isCurrent ? "date" : void 0,
2678
+ "data-selected": isSelected || void 0,
2679
+ "data-current": isCurrent || void 0,
2680
+ "data-focused": isFocused || void 0,
2681
+ className: cls,
2682
+ onClick: () => handleYearSelect(i),
2683
+ children: year
2277
2684
  },
2278
- y.value
2685
+ i
2279
2686
  );
2280
2687
  })
2281
2688
  },
@@ -2503,7 +2910,7 @@ function useRangePicker(options = {}) {
2503
2910
  adapter
2504
2911
  };
2505
2912
  }
2506
- function getDefaultIso3() {
2913
+ function getDefaultIso() {
2507
2914
  return DateFnsAdapter.today();
2508
2915
  }
2509
2916
  function useTimePicker(options = {}) {
@@ -2521,7 +2928,7 @@ function useTimePicker(options = {}) {
2521
2928
  defaultValue ?? null
2522
2929
  );
2523
2930
  const currentValue = isControlled ? controlledValue ?? null : uncontrolledValue;
2524
- const baseIso = currentValue ?? getDefaultIso3();
2931
+ const baseIso = currentValue ?? getDefaultIso();
2525
2932
  const currentTime = useMemo(
2526
2933
  () => displayTimezone ? getTimeInTimezone(baseIso, displayTimezone) : getTime(baseIso),
2527
2934
  [baseIso, displayTimezone]
@@ -2545,14 +2952,8 @@ function useTimePicker(options = {}) {
2545
2952
  },
2546
2953
  [format, period, setTime$1]
2547
2954
  );
2548
- const setMinute = useCallback(
2549
- (minute) => setTime$1({ minutes: minute }),
2550
- [setTime$1]
2551
- );
2552
- const setSecond = useCallback(
2553
- (second) => setTime$1({ seconds: second }),
2554
- [setTime$1]
2555
- );
2955
+ const setMinute = useCallback((minute) => setTime$1({ minutes: minute }), [setTime$1]);
2956
+ const setSecond = useCallback((second) => setTime$1({ seconds: second }), [setTime$1]);
2556
2957
  const setPeriod = useCallback(
2557
2958
  (newPeriod) => {
2558
2959
  if (format !== "12h") return;