@kalyx/react 1.0.0-rc.1 → 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.cjs CHANGED
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  'use strict';
2
3
 
3
4
  var react = require('react');
@@ -57,10 +58,10 @@ function DatePickerRoot({
57
58
  const currentValue = isControlled ? controlledValue ?? null : uncontrolledValue;
58
59
  const [isOpen, setIsOpen] = react.useState(false);
59
60
  const [viewMonth, setViewMonth] = react.useState(
60
- currentValue ?? adapter.today(displayTimezone)
61
+ () => currentValue ?? adapter.today(displayTimezone)
61
62
  );
62
63
  const [focusedDate, setFocusedDate] = react.useState(
63
- currentValue ?? adapter.today(displayTimezone)
64
+ () => currentValue ?? adapter.today(displayTimezone)
64
65
  );
65
66
  useChangeEffect(isOpen, onOpenChange);
66
67
  const viewMonthStart = react.useMemo(() => adapter.startOfMonth(viewMonth), [viewMonth, adapter]);
@@ -151,10 +152,11 @@ function DatePickerRoot({
151
152
  return /* @__PURE__ */ jsxRuntime.jsx(DatePickerContext.Provider, { value: contextValue, children });
152
153
  }
153
154
  var DatePickerInput = react.forwardRef(
154
- function DatePickerInput2({ format: formatProp, onClick, onBlur, onKeyDown, ...props }, ref) {
155
+ function DatePickerInput2({ format: formatProp, name, onClick, onBlur, onKeyDown, ...props }, ref) {
155
156
  const ctx = useDatePickerContext("DatePicker.Input");
156
157
  const displayFormat = formatProp ?? ctx.displayFormat;
157
158
  const [inputText, setInputText] = react.useState(null);
159
+ const isComposingRef = react.useRef(false);
158
160
  let formattedValue = "";
159
161
  if (ctx.value) {
160
162
  try {
@@ -171,47 +173,62 @@ var DatePickerInput = react.forwardRef(
171
173
  },
172
174
  [ctx, onClick]
173
175
  );
176
+ const commitText = react.useCallback(
177
+ (text) => {
178
+ if (!text) {
179
+ ctx.selectDate(null);
180
+ setInputText(null);
181
+ return true;
182
+ }
183
+ const parsed = core.parseInputValue(text, ctx.adapter);
184
+ if (parsed) {
185
+ ctx.selectDate(parsed);
186
+ setInputText(null);
187
+ return true;
188
+ }
189
+ return false;
190
+ },
191
+ [ctx]
192
+ );
174
193
  const handleBlur = react.useCallback(
175
194
  (e) => {
176
195
  if (inputText !== null) {
177
- const parsed = core.parseInputValue(inputText, ctx.adapter);
178
- if (parsed) {
179
- ctx.selectDate(parsed);
180
- }
196
+ commitText(inputText);
181
197
  setInputText(null);
182
198
  }
183
199
  onBlur?.(e);
184
200
  },
185
- [inputText, displayFormat, ctx, onBlur]
201
+ [inputText, commitText, onBlur]
186
202
  );
187
203
  const handleChange = react.useCallback(
188
204
  (e) => {
189
205
  const text = e.target.value;
190
206
  setInputText(text);
191
- if (!text) {
192
- ctx.selectDate(null);
193
- setInputText(null);
194
- return;
195
- }
196
- const parsed = core.parseInputValue(text, ctx.adapter);
197
- if (parsed) {
198
- ctx.selectDate(parsed);
199
- setInputText(null);
200
- }
207
+ if (isComposingRef.current) return;
208
+ commitText(text);
209
+ },
210
+ [commitText]
211
+ );
212
+ const handleCompositionStart = react.useCallback(() => {
213
+ isComposingRef.current = true;
214
+ }, []);
215
+ const handleCompositionEnd = react.useCallback(
216
+ (e) => {
217
+ isComposingRef.current = false;
218
+ commitText(e.target.value);
201
219
  },
202
- [displayFormat, ctx]
220
+ [commitText]
203
221
  );
204
222
  const handleKeyDown = react.useCallback(
205
223
  (e) => {
206
224
  if (e.key === "Escape") {
207
225
  ctx.close();
208
226
  } else if (e.key === "Enter") {
227
+ if (ctx.isOpen) e.preventDefault();
209
228
  if (inputText !== null) {
210
- const parsed = core.parseInputValue(inputText, ctx.adapter);
211
- if (parsed) {
212
- ctx.selectDate(parsed);
213
- setInputText(null);
214
- }
229
+ commitText(inputText);
230
+ } else if (ctx.isOpen) {
231
+ ctx.selectDate(ctx.focusedDate);
215
232
  }
216
233
  } else if (e.key === "ArrowDown" && !ctx.isOpen) {
217
234
  e.preventDefault();
@@ -219,36 +236,42 @@ var DatePickerInput = react.forwardRef(
219
236
  }
220
237
  onKeyDown?.(e);
221
238
  },
222
- [ctx, inputText, displayFormat, onKeyDown]
239
+ [ctx, inputText, commitText, onKeyDown]
223
240
  );
224
241
  const calendarId = `${ctx.pickerId}-calendar`;
225
- return /* @__PURE__ */ jsxRuntime.jsx(
226
- "input",
227
- {
228
- ref: (node) => {
229
- ctx.referenceRef.current = node;
230
- if (typeof ref === "function") ref(node);
231
- else if (ref) ref.current = node;
232
- },
233
- type: "text",
234
- role: "combobox",
235
- "aria-expanded": ctx.isOpen,
236
- "aria-haspopup": "dialog",
237
- "aria-controls": ctx.isOpen ? calendarId : void 0,
238
- "aria-autocomplete": "none",
239
- autoComplete: "off",
240
- value: displayValue,
241
- disabled: ctx.isDisabled || props.disabled,
242
- readOnly: ctx.isReadOnly,
243
- onChange: handleChange,
244
- onClick: handleClick,
245
- onBlur: handleBlur,
246
- onKeyDown: handleKeyDown,
247
- ...props
248
- }
249
- );
242
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
243
+ /* @__PURE__ */ jsxRuntime.jsx(
244
+ "input",
245
+ {
246
+ ref: (node) => {
247
+ ctx.referenceRef.current = node;
248
+ if (typeof ref === "function") ref(node);
249
+ else if (ref) ref.current = node;
250
+ },
251
+ type: "text",
252
+ role: "combobox",
253
+ "aria-expanded": ctx.isOpen,
254
+ "aria-haspopup": "dialog",
255
+ "aria-controls": ctx.isOpen ? calendarId : void 0,
256
+ "aria-autocomplete": "none",
257
+ autoComplete: "off",
258
+ value: displayValue,
259
+ disabled: ctx.isDisabled || props.disabled,
260
+ readOnly: ctx.isReadOnly,
261
+ onChange: handleChange,
262
+ onClick: handleClick,
263
+ onBlur: handleBlur,
264
+ onKeyDown: handleKeyDown,
265
+ onCompositionStart: handleCompositionStart,
266
+ onCompositionEnd: handleCompositionEnd,
267
+ ...props
268
+ }
269
+ ),
270
+ name ? /* @__PURE__ */ jsxRuntime.jsx("input", { type: "hidden", name, value: ctx.value ?? "" }) : null
271
+ ] });
250
272
  }
251
273
  );
274
+ DatePickerInput.displayName = "DatePicker.Input";
252
275
  var DatePickerTrigger = react.forwardRef(
253
276
  function DatePickerTrigger2({ onClick, children, ...props }, ref) {
254
277
  const ctx = useDatePickerContext("DatePicker.Trigger");
@@ -272,6 +295,7 @@ var DatePickerTrigger = react.forwardRef(
272
295
  tabIndex: 0,
273
296
  "aria-label": ctx.isOpen ? ctx.labels.triggerClose : ctx.labels.triggerOpen,
274
297
  "aria-expanded": ctx.isOpen,
298
+ "aria-haspopup": "dialog",
275
299
  "aria-controls": ctx.isOpen ? calendarId : void 0,
276
300
  disabled: ctx.isDisabled || props.disabled,
277
301
  onClick: handleClick,
@@ -302,6 +326,8 @@ var DatePickerTrigger = react.forwardRef(
302
326
  );
303
327
  }
304
328
  );
329
+ DatePickerTrigger.displayName = "DatePicker.Trigger";
330
+ var POPOVER_MIDDLEWARE = [react$1.offset(4), react$1.flip(), react$1.shift({ padding: 8 })];
305
331
  function usePopover({
306
332
  isOpen,
307
333
  close,
@@ -313,7 +339,7 @@ function usePopover({
313
339
  const { refs, floatingStyles, isPositioned } = react$1.useFloating({
314
340
  open: isOpen,
315
341
  placement,
316
- middleware: [react$1.offset(4), react$1.flip(), react$1.shift({ padding: 8 })],
342
+ middleware: POPOVER_MIDDLEWARE,
317
343
  whileElementsMounted: react$1.autoUpdate
318
344
  });
319
345
  react.useEffect(() => {
@@ -360,6 +386,24 @@ function usePopover({
360
386
  document.addEventListener("keydown", handleKeyDown);
361
387
  return () => document.removeEventListener("keydown", handleKeyDown);
362
388
  }, [isOpen, close]);
389
+ react.useEffect(() => {
390
+ if (!isOpen) return;
391
+ function handleFocusOut(e) {
392
+ const next = e.relatedTarget;
393
+ const floating = floatingRef.current;
394
+ const reference = referenceRef.current;
395
+ if (!next) return;
396
+ const insideFloating = floating?.contains(next) ?? false;
397
+ const insideReference = reference?.contains(next) ?? false;
398
+ if (!insideFloating && !insideReference) {
399
+ close();
400
+ }
401
+ }
402
+ const node = floatingRef.current;
403
+ if (!node) return;
404
+ node.addEventListener("focusout", handleFocusOut);
405
+ return () => node.removeEventListener("focusout", handleFocusOut);
406
+ }, [isOpen, close, referenceRef]);
363
407
  const setFloatingRef = react.useCallback(
364
408
  (node) => {
365
409
  floatingRef.current = node;
@@ -421,20 +465,36 @@ var srOnly = {
421
465
  function DatePickerCalendar({
422
466
  classNames,
423
467
  onTitleClick,
468
+ showWeekNumber = false,
469
+ fixedWeeks = false,
424
470
  ...props
425
471
  }) {
426
472
  const ctx = useDatePickerContext("DatePicker.Calendar");
427
473
  const gridRef = react.useRef(null);
428
474
  const [announcement, setAnnouncement] = react.useState("");
429
475
  const { adapter, viewMonth, focusedDate, weekStartsOn, disabled, locale, displayTimezone } = ctx;
430
- const weekdays = core.getWeekdayNames(locale, weekStartsOn);
431
- const weeks = core.getCalendarDays(viewMonth, adapter, {
432
- weekStartsOn,
433
- selected: ctx.value,
434
- focusedDate,
435
- disabled,
436
- timezone: displayTimezone
437
- });
476
+ const weekdays = react.useMemo(() => core.getWeekdayNames(locale, weekStartsOn), [locale, weekStartsOn]);
477
+ const weeks = react.useMemo(
478
+ () => core.getCalendarDays(viewMonth, adapter, {
479
+ weekStartsOn,
480
+ selected: ctx.value,
481
+ focusedDate,
482
+ disabled,
483
+ timezone: displayTimezone,
484
+ fixedWeeks
485
+ }),
486
+ [
487
+ viewMonth,
488
+ adapter,
489
+ weekStartsOn,
490
+ ctx.value,
491
+ focusedDate,
492
+ disabled,
493
+ displayTimezone,
494
+ fixedWeeks
495
+ ]
496
+ );
497
+ const thursdayIndex = weekStartsOn === 0 ? 4 : 3;
438
498
  const year = adapter.getYear(viewMonth);
439
499
  const month = adapter.getMonth(viewMonth);
440
500
  const title = core.formatMonthYear(year, month, locale);
@@ -514,6 +574,15 @@ function DatePickerCalendar({
514
574
  }
515
575
  if (newFocused) {
516
576
  e.preventDefault();
577
+ const skipStep = e.key === "ArrowLeft" || e.key === "ArrowUp" || e.key === "PageUp" || e.key === "Home" ? -1 : 1;
578
+ let attempts = 0;
579
+ while (core.isDateDisabled(newFocused, disabled, adapter) && attempts < 42) {
580
+ newFocused = adapter.addDays(newFocused, skipStep);
581
+ attempts++;
582
+ }
583
+ if (attempts >= 42) {
584
+ return;
585
+ }
517
586
  ctx.setFocusedDate(newFocused);
518
587
  if (!adapter.isSameMonth(newFocused, viewMonth)) {
519
588
  ctx.setViewMonth(newFocused);
@@ -561,62 +630,170 @@ function DatePickerCalendar({
561
630
  ref: gridRef,
562
631
  role: "grid",
563
632
  "aria-label": title,
633
+ "aria-rowcount": weeks.length + 1,
634
+ "aria-colcount": 7,
564
635
  className: classNames?.grid,
565
636
  onKeyDown: handleKeyDown,
566
637
  children: [
567
- /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsx("tr", { role: "row", children: weekdays.map((day) => /* @__PURE__ */ jsxRuntime.jsx(
568
- "th",
569
- {
570
- role: "columnheader",
571
- abbr: day.full,
572
- scope: "col",
573
- className: classNames?.weekdayHeader,
574
- children: day.short
575
- },
576
- day.short
577
- )) }) }),
578
- /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: weeks.map((week, weekIndex) => /* @__PURE__ */ jsxRuntime.jsx("tr", { role: "row", className: classNames?.gridRow, children: week.map((day) => {
579
- const dayClasses = [
580
- classNames?.day,
581
- day.isSelected && classNames?.daySelected,
582
- day.isToday && classNames?.dayToday,
583
- day.isDisabled && classNames?.dayDisabled,
584
- !day.isCurrentMonth && classNames?.dayOutsideMonth
585
- ].filter(Boolean).join(" ") || void 0;
586
- return /* @__PURE__ */ jsxRuntime.jsx(
587
- "td",
638
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { role: "row", "aria-rowindex": 1, children: [
639
+ showWeekNumber ? /* @__PURE__ */ jsxRuntime.jsx("th", { scope: "col", "aria-hidden": "true", className: classNames?.weekNumberHeader, children: "#" }) : null,
640
+ weekdays.map((day, colIndex) => /* @__PURE__ */ jsxRuntime.jsx(
641
+ "th",
588
642
  {
589
- role: "gridcell",
590
- "aria-selected": day.isSelected || void 0,
591
- "aria-disabled": day.isDisabled || void 0,
592
- "aria-current": day.isToday ? "date" : void 0,
593
- className: classNames?.gridCell,
594
- children: /* @__PURE__ */ jsxRuntime.jsx(
595
- "button",
643
+ role: "columnheader",
644
+ abbr: day.full,
645
+ scope: "col",
646
+ "aria-colindex": colIndex + 1,
647
+ className: classNames?.weekdayHeader,
648
+ children: day.short
649
+ },
650
+ day.short
651
+ ))
652
+ ] }) }),
653
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: weeks.map((week, weekIndex) => /* @__PURE__ */ jsxRuntime.jsxs(
654
+ "tr",
655
+ {
656
+ role: "row",
657
+ "aria-rowindex": weekIndex + 2,
658
+ className: classNames?.gridRow,
659
+ children: [
660
+ showWeekNumber ? /* @__PURE__ */ jsxRuntime.jsx(
661
+ "th",
596
662
  {
597
- type: "button",
598
- tabIndex: day.isFocused ? 0 : -1,
599
- disabled: day.isDisabled,
600
- "data-focused": day.isFocused || void 0,
601
- "data-selected": day.isSelected || void 0,
602
- "data-today": day.isToday || void 0,
603
- "data-outside-month": !day.isCurrentMonth || void 0,
604
- className: dayClasses,
605
- onClick: () => handleDayClick(day),
606
- "aria-label": safeFormatFullDate(day.isoString, locale),
607
- children: day.dayNumber
663
+ scope: "row",
664
+ "aria-hidden": "true",
665
+ className: classNames?.weekNumber,
666
+ "data-week-number": true,
667
+ children: core.getISOWeekNumber(week[thursdayIndex].isoString)
608
668
  }
609
- )
610
- },
611
- day.isoString
612
- );
613
- }) }, weekIndex)) })
669
+ ) : null,
670
+ week.map((day, colIndex) => {
671
+ const dayClasses = [
672
+ classNames?.day,
673
+ day.isSelected && classNames?.daySelected,
674
+ day.isToday && classNames?.dayToday,
675
+ day.isDisabled && classNames?.dayDisabled,
676
+ !day.isCurrentMonth && classNames?.dayOutsideMonth
677
+ ].filter(Boolean).join(" ") || void 0;
678
+ return /* @__PURE__ */ jsxRuntime.jsx(
679
+ "td",
680
+ {
681
+ role: "gridcell",
682
+ "aria-colindex": colIndex + 1,
683
+ "aria-selected": day.isSelected || void 0,
684
+ "aria-disabled": day.isDisabled || void 0,
685
+ "aria-current": day.isToday ? "date" : void 0,
686
+ className: classNames?.gridCell,
687
+ children: /* @__PURE__ */ jsxRuntime.jsx(
688
+ "button",
689
+ {
690
+ type: "button",
691
+ tabIndex: day.isFocused ? 0 : -1,
692
+ disabled: day.isDisabled,
693
+ "data-focused": day.isFocused || void 0,
694
+ "data-selected": day.isSelected || void 0,
695
+ "data-today": day.isToday || void 0,
696
+ "data-outside-month": !day.isCurrentMonth || void 0,
697
+ className: dayClasses,
698
+ onClick: () => handleDayClick(day),
699
+ "aria-label": safeFormatFullDate(day.isoString, locale),
700
+ children: day.dayNumber
701
+ }
702
+ )
703
+ },
704
+ day.isoString
705
+ );
706
+ })
707
+ ]
708
+ },
709
+ weekIndex
710
+ )) })
614
711
  ]
615
712
  }
616
713
  ),
617
714
  /* @__PURE__ */ jsxRuntime.jsx("div", { role: "status", "aria-live": "polite", "aria-atomic": "true", style: srOnly, children: announcement })
618
715
  ] });
619
716
  }
717
+ function isRangeFullyDisabled(start, end, rules, adapter) {
718
+ for (const rule of rules) {
719
+ if ("before" in rule && adapter.isBefore(end, rule.before)) return true;
720
+ if ("after" in rule && adapter.isAfter(start, rule.after)) return true;
721
+ }
722
+ return false;
723
+ }
724
+ function useGridState(opts) {
725
+ const { initialIndex, disabledFlags, onSelect, onPageUp, onPageDown, onEscape } = opts;
726
+ const gridRef = react.useRef(null);
727
+ const [focusedIndex, setFocusedIndex] = react.useState(initialIndex);
728
+ const handleKeyDown = (e) => {
729
+ let next = null;
730
+ let step = 1;
731
+ switch (e.key) {
732
+ case "ArrowLeft":
733
+ next = Math.max(0, focusedIndex - 1);
734
+ step = -1;
735
+ break;
736
+ case "ArrowRight":
737
+ next = Math.min(11, focusedIndex + 1);
738
+ break;
739
+ case "ArrowUp":
740
+ next = Math.max(0, focusedIndex - 3);
741
+ step = -1;
742
+ break;
743
+ case "ArrowDown":
744
+ next = Math.min(11, focusedIndex + 3);
745
+ break;
746
+ case "Home":
747
+ next = focusedIndex - focusedIndex % 3;
748
+ step = -1;
749
+ break;
750
+ case "End":
751
+ next = focusedIndex - focusedIndex % 3 + 2;
752
+ break;
753
+ case "PageUp":
754
+ e.preventDefault();
755
+ onPageUp();
756
+ return;
757
+ case "PageDown":
758
+ e.preventDefault();
759
+ onPageDown();
760
+ return;
761
+ case "Enter":
762
+ case " ":
763
+ e.preventDefault();
764
+ onSelect(focusedIndex);
765
+ return;
766
+ case "Escape":
767
+ onEscape();
768
+ return;
769
+ default:
770
+ return;
771
+ }
772
+ if (next === null) return;
773
+ e.preventDefault();
774
+ if (disabledFlags) {
775
+ let attempts = 0;
776
+ while (next >= 0 && next < 12 && disabledFlags[next] && attempts < 12) {
777
+ next += step;
778
+ attempts++;
779
+ }
780
+ if (next < 0 || next >= 12 || disabledFlags[next]) return;
781
+ }
782
+ if (next !== focusedIndex) setFocusedIndex(next);
783
+ };
784
+ react.useEffect(() => {
785
+ if (!disabledFlags || !disabledFlags[focusedIndex]) return;
786
+ const firstEnabled = disabledFlags.findIndex((d) => !d);
787
+ if (firstEnabled !== -1 && firstEnabled !== focusedIndex) {
788
+ setFocusedIndex(firstEnabled);
789
+ }
790
+ }, [disabledFlags, focusedIndex]);
791
+ react.useEffect(() => {
792
+ const btn = gridRef.current?.querySelector('[data-focused="true"]');
793
+ btn?.focus({ preventScroll: true });
794
+ }, [focusedIndex]);
795
+ return { gridRef, focusedIndex, handleKeyDown };
796
+ }
620
797
  function DatePickerMonthGrid({
621
798
  classNames,
622
799
  onSelect,
@@ -624,15 +801,18 @@ function DatePickerMonthGrid({
624
801
  ...props
625
802
  }) {
626
803
  const ctx = useDatePickerContext("DatePicker.MonthGrid");
627
- const { adapter, viewMonth, locale } = ctx;
804
+ const { adapter, viewMonth, locale, displayTimezone } = ctx;
628
805
  const currentYear = adapter.getYear(viewMonth);
629
806
  const currentMonth = adapter.getMonth(viewMonth);
630
- const todayMonth = adapter.getMonth(adapter.today());
631
- const todayYear = adapter.getYear(adapter.today());
807
+ const [today, setToday] = react.useState(null);
808
+ react.useEffect(() => {
809
+ setToday(adapter.today(displayTimezone));
810
+ }, [adapter, displayTimezone]);
811
+ const todayMonth = today !== null ? adapter.getMonth(today) : -1;
812
+ const todayYear = today !== null ? adapter.getYear(today) : -1;
632
813
  const navigateYear = react.useCallback(
633
814
  (direction) => {
634
- const newDate = adapter.addYears(viewMonth, direction);
635
- ctx.setViewMonth(newDate);
815
+ ctx.setViewMonth(adapter.addYears(viewMonth, direction));
636
816
  },
637
817
  [adapter, viewMonth, ctx]
638
818
  );
@@ -645,12 +825,13 @@ function DatePickerMonthGrid({
645
825
  },
646
826
  [currentYear, ctx, onSelect]
647
827
  );
648
- const months = Array.from({ length: 12 }, (_, i) => ({
649
- index: i,
650
- name: core.getMonthName(i, locale),
651
- isSelected: i === currentMonth,
652
- isCurrent: i === todayMonth && currentYear === todayYear
653
- }));
828
+ const { gridRef, focusedIndex, handleKeyDown } = useGridState({
829
+ initialIndex: currentMonth,
830
+ onSelect: handleMonthSelect,
831
+ onPageUp: () => navigateYear(-1),
832
+ onPageDown: () => navigateYear(1),
833
+ onEscape: ctx.close
834
+ });
654
835
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.root, ...props, children: [
655
836
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.header, children: [
656
837
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -678,30 +859,37 @@ function DatePickerMonthGrid({
678
859
  /* @__PURE__ */ jsxRuntime.jsx(
679
860
  "div",
680
861
  {
862
+ ref: gridRef,
681
863
  role: "grid",
682
864
  "aria-label": `${currentYear} months`,
683
865
  className: classNames?.grid,
684
866
  style: { display: "grid", gridTemplateColumns: "repeat(3, 1fr)" },
685
- children: months.map((m) => {
686
- const monthClass = [
867
+ onKeyDown: handleKeyDown,
868
+ children: Array.from({ length: 12 }, (_, i) => {
869
+ const isSelected = i === currentMonth;
870
+ const isCurrent = i === todayMonth && currentYear === todayYear;
871
+ const isFocused = i === focusedIndex;
872
+ const cls = [
687
873
  classNames?.month,
688
- m.isSelected && classNames?.monthSelected,
689
- m.isCurrent && classNames?.monthCurrent
874
+ isSelected && classNames?.monthSelected,
875
+ isCurrent && classNames?.monthCurrent
690
876
  ].filter(Boolean).join(" ") || void 0;
691
877
  return /* @__PURE__ */ jsxRuntime.jsx(
692
878
  "button",
693
879
  {
694
880
  type: "button",
695
881
  role: "gridcell",
696
- "aria-selected": m.isSelected || void 0,
697
- "aria-current": m.isCurrent ? "date" : void 0,
698
- "data-selected": m.isSelected || void 0,
699
- "data-current": m.isCurrent || void 0,
700
- className: monthClass,
701
- onClick: () => handleMonthSelect(m.index),
702
- children: m.name
882
+ tabIndex: isFocused ? 0 : -1,
883
+ "aria-selected": isSelected || void 0,
884
+ "aria-current": isCurrent ? "date" : void 0,
885
+ "data-selected": isSelected || void 0,
886
+ "data-current": isCurrent || void 0,
887
+ "data-focused": isFocused || void 0,
888
+ className: cls,
889
+ onClick: () => handleMonthSelect(i),
890
+ children: core.getMonthName(i, locale)
703
891
  },
704
- m.index
892
+ i
705
893
  );
706
894
  })
707
895
  }
@@ -710,38 +898,38 @@ function DatePickerMonthGrid({
710
898
  }
711
899
  function DatePickerYearGrid({ classNames, onSelect, ...props }) {
712
900
  const ctx = useDatePickerContext("DatePicker.YearGrid");
713
- const { adapter, viewMonth } = ctx;
901
+ const { adapter, viewMonth, displayTimezone } = ctx;
714
902
  const currentYear = adapter.getYear(viewMonth);
715
- const todayYear = adapter.getYear(adapter.today());
903
+ const [today, setToday] = react.useState(null);
904
+ react.useEffect(() => {
905
+ setToday(adapter.today(displayTimezone));
906
+ }, [adapter, displayTimezone]);
907
+ const todayYear = today !== null ? adapter.getYear(today) : -1;
716
908
  const decadeStart = currentYear - currentYear % 12;
717
909
  const navigateDecade = react.useCallback(
718
910
  (direction) => {
719
- const newDate = adapter.addYears(viewMonth, direction * 12);
720
- ctx.setViewMonth(newDate);
911
+ ctx.setViewMonth(adapter.addYears(viewMonth, direction * 12));
721
912
  },
722
913
  [adapter, viewMonth, ctx]
723
914
  );
724
915
  const handleYearSelect = react.useCallback(
725
- (year) => {
916
+ (indexInDecade) => {
917
+ const year = decadeStart + indexInDecade;
726
918
  const currentMonth = adapter.getMonth(viewMonth);
727
919
  const target = new Date(Date.UTC(year, currentMonth, 1)).toISOString();
728
920
  ctx.setViewMonth(target);
729
921
  ctx.setFocusedDate(target);
730
922
  onSelect?.();
731
923
  },
732
- [adapter, viewMonth, ctx, onSelect]
733
- );
734
- const years = react.useMemo(
735
- () => Array.from({ length: 12 }, (_, i) => {
736
- const year = decadeStart + i;
737
- return {
738
- value: year,
739
- isSelected: year === currentYear,
740
- isCurrent: year === todayYear
741
- };
742
- }),
743
- [decadeStart, currentYear, todayYear]
744
- );
924
+ [adapter, viewMonth, ctx, onSelect, decadeStart]
925
+ );
926
+ const { gridRef, focusedIndex, handleKeyDown } = useGridState({
927
+ initialIndex: currentYear - decadeStart,
928
+ onSelect: handleYearSelect,
929
+ onPageUp: () => navigateDecade(-1),
930
+ onPageDown: () => navigateDecade(1),
931
+ onEscape: ctx.close
932
+ });
745
933
  const rangeLabel = `${decadeStart}\u2013${decadeStart + 11}`;
746
934
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.root, ...props, children: [
747
935
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.header, children: [
@@ -770,30 +958,38 @@ function DatePickerYearGrid({ classNames, onSelect, ...props }) {
770
958
  /* @__PURE__ */ jsxRuntime.jsx(
771
959
  "div",
772
960
  {
961
+ ref: gridRef,
773
962
  role: "grid",
774
963
  "aria-label": rangeLabel,
775
964
  className: classNames?.grid,
776
965
  style: { display: "grid", gridTemplateColumns: "repeat(3, 1fr)" },
777
- children: years.map((y) => {
778
- const yearClass = [
966
+ onKeyDown: handleKeyDown,
967
+ children: Array.from({ length: 12 }, (_, i) => {
968
+ const year = decadeStart + i;
969
+ const isSelected = year === currentYear;
970
+ const isCurrent = year === todayYear;
971
+ const isFocused = i === focusedIndex;
972
+ const cls = [
779
973
  classNames?.year,
780
- y.isSelected && classNames?.yearSelected,
781
- y.isCurrent && classNames?.yearCurrent
974
+ isSelected && classNames?.yearSelected,
975
+ isCurrent && classNames?.yearCurrent
782
976
  ].filter(Boolean).join(" ") || void 0;
783
977
  return /* @__PURE__ */ jsxRuntime.jsx(
784
978
  "button",
785
979
  {
786
980
  type: "button",
787
981
  role: "gridcell",
788
- "aria-selected": y.isSelected || void 0,
789
- "aria-current": y.isCurrent ? "date" : void 0,
790
- "data-selected": y.isSelected || void 0,
791
- "data-current": y.isCurrent || void 0,
792
- className: yearClass,
793
- onClick: () => handleYearSelect(y.value),
794
- children: y.value
982
+ tabIndex: isFocused ? 0 : -1,
983
+ "aria-selected": isSelected || void 0,
984
+ "aria-current": isCurrent ? "date" : void 0,
985
+ "data-selected": isSelected || void 0,
986
+ "data-current": isCurrent || void 0,
987
+ "data-focused": isFocused || void 0,
988
+ className: cls,
989
+ onClick: () => handleYearSelect(i),
990
+ children: year
795
991
  },
796
- y.value
992
+ i
797
993
  );
798
994
  })
799
995
  }
@@ -866,8 +1062,7 @@ function DatePickerPreset({
866
1062
  "button",
867
1063
  {
868
1064
  type: "button",
869
- role: "option",
870
- "aria-selected": isActive,
1065
+ "aria-pressed": isActive,
871
1066
  "data-active": isActive || void 0,
872
1067
  disabled: ctx.isDisabled,
873
1068
  onClick: handleClick,
@@ -931,10 +1126,10 @@ function RangePickerRoot({
931
1126
  const [selectingTarget, setSelectingTarget] = react.useState("start");
932
1127
  const [hoverDate, setHoverDate] = react.useState(null);
933
1128
  const [viewMonth, setViewMonth] = react.useState(
934
- currentValue.start ?? adapter.today(displayTimezone)
1129
+ () => currentValue.start ?? adapter.today(displayTimezone)
935
1130
  );
936
1131
  const [focusedDate, setFocusedDate] = react.useState(
937
- currentValue.start ?? adapter.today(displayTimezone)
1132
+ () => currentValue.start ?? adapter.today(displayTimezone)
938
1133
  );
939
1134
  useChangeEffect(isOpen, onOpenChange);
940
1135
  const viewMonthStart = react.useMemo(() => adapter.startOfMonth(viewMonth), [viewMonth, adapter]);
@@ -1084,6 +1279,8 @@ var RangePickerInput = react.forwardRef(
1084
1279
  (e) => {
1085
1280
  if (e.key === "Escape") {
1086
1281
  ctx.close();
1282
+ } else if (e.key === "Enter" && ctx.isOpen) {
1283
+ e.preventDefault();
1087
1284
  } else if (e.key === "ArrowDown" && !ctx.isOpen) {
1088
1285
  e.preventDefault();
1089
1286
  ctx.open();
@@ -1120,6 +1317,7 @@ var RangePickerInput = react.forwardRef(
1120
1317
  );
1121
1318
  }
1122
1319
  );
1320
+ RangePickerInput.displayName = "RangePicker.Input";
1123
1321
  function RangePickerPopover({ children, ...props }) {
1124
1322
  const ctx = useRangePickerContext("RangePicker.Popover");
1125
1323
  const calendarId = `${ctx.pickerId}-calendar`;
@@ -1169,6 +1367,8 @@ var srOnly2 = {
1169
1367
  function RangePickerCalendar({
1170
1368
  classNames,
1171
1369
  selectionMode = "range",
1370
+ showWeekNumber = false,
1371
+ fixedWeeks = false,
1172
1372
  ...props
1173
1373
  }) {
1174
1374
  const ctx = useRangePickerContext("RangePicker.Calendar");
@@ -1186,15 +1386,30 @@ function RangePickerCalendar({
1186
1386
  displayTimezone
1187
1387
  } = ctx;
1188
1388
  const { locale } = ctx;
1189
- const weekdays = core.getWeekdayNames(locale, weekStartsOn);
1190
- const weeks = core.getCalendarDays(viewMonth, adapter, {
1191
- weekStartsOn,
1192
- focusedDate,
1193
- disabled,
1194
- range: value,
1195
- rangeHover: hoverDate,
1196
- timezone: displayTimezone
1197
- });
1389
+ const weekdays = react.useMemo(() => core.getWeekdayNames(locale, weekStartsOn), [locale, weekStartsOn]);
1390
+ const weeks = react.useMemo(
1391
+ () => core.getCalendarDays(viewMonth, adapter, {
1392
+ weekStartsOn,
1393
+ focusedDate,
1394
+ disabled,
1395
+ range: value,
1396
+ rangeHover: hoverDate,
1397
+ timezone: displayTimezone,
1398
+ fixedWeeks
1399
+ }),
1400
+ [
1401
+ viewMonth,
1402
+ adapter,
1403
+ weekStartsOn,
1404
+ focusedDate,
1405
+ disabled,
1406
+ value,
1407
+ hoverDate,
1408
+ displayTimezone,
1409
+ fixedWeeks
1410
+ ]
1411
+ );
1412
+ const thursdayIndex = weekStartsOn === 0 ? 4 : 3;
1198
1413
  const year = adapter.getYear(viewMonth);
1199
1414
  const month = adapter.getMonth(viewMonth);
1200
1415
  const title = core.formatMonthYear(year, month, locale);
@@ -1294,6 +1509,13 @@ function RangePickerCalendar({
1294
1509
  }
1295
1510
  if (newFocused) {
1296
1511
  e.preventDefault();
1512
+ const skipStep = e.key === "ArrowLeft" || e.key === "ArrowUp" || e.key === "PageUp" || e.key === "Home" ? -1 : 1;
1513
+ let attempts = 0;
1514
+ while (core.isDateDisabled(newFocused, disabled, adapter) && attempts < 42) {
1515
+ newFocused = adapter.addDays(newFocused, skipStep);
1516
+ attempts++;
1517
+ }
1518
+ if (attempts >= 42) return;
1297
1519
  ctx.setFocusedDate(newFocused);
1298
1520
  if (!adapter.isSameMonth(newFocused, viewMonth)) {
1299
1521
  ctx.setViewMonth(newFocused);
@@ -1346,63 +1568,90 @@ function RangePickerCalendar({
1346
1568
  ref: gridRef,
1347
1569
  role: "grid",
1348
1570
  "aria-label": title,
1349
- "aria-multiselectable": "true",
1571
+ "aria-rowcount": weeks.length + 1,
1572
+ "aria-colcount": 7,
1350
1573
  className: classNames?.grid,
1351
1574
  onKeyDown: handleKeyDown,
1352
1575
  children: [
1353
- /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsx("tr", { role: "row", children: weekdays.map((day) => /* @__PURE__ */ jsxRuntime.jsx(
1354
- "th",
1355
- {
1356
- role: "columnheader",
1357
- abbr: day.full,
1358
- scope: "col",
1359
- className: classNames?.weekdayHeader,
1360
- children: day.short
1361
- },
1362
- day.short
1363
- )) }) }),
1364
- /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: weeks.map((week, weekIndex) => /* @__PURE__ */ jsxRuntime.jsx("tr", { role: "row", className: classNames?.gridRow, children: week.map((day) => {
1365
- const dayClasses = [
1366
- classNames?.day,
1367
- day.isRangeStart && classNames?.dayRangeStart,
1368
- day.isRangeEnd && classNames?.dayRangeEnd,
1369
- day.isInRange && classNames?.dayInRange,
1370
- day.isToday && classNames?.dayToday,
1371
- day.isDisabled && classNames?.dayDisabled,
1372
- !day.isCurrentMonth && classNames?.dayOutsideMonth
1373
- ].filter(Boolean).join(" ") || void 0;
1374
- const isSelected = selectionMode === "week" ? day.isRangeStart || day.isRangeEnd || day.isInRange : day.isRangeStart || day.isRangeEnd;
1375
- return /* @__PURE__ */ jsxRuntime.jsx(
1376
- "td",
1576
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { role: "row", "aria-rowindex": 1, children: [
1577
+ showWeekNumber ? /* @__PURE__ */ jsxRuntime.jsx("th", { scope: "col", "aria-hidden": "true", className: classNames?.weekNumberHeader, children: "#" }) : null,
1578
+ weekdays.map((day, colIndex) => /* @__PURE__ */ jsxRuntime.jsx(
1579
+ "th",
1377
1580
  {
1378
- role: "gridcell",
1379
- "aria-selected": isSelected || void 0,
1380
- "aria-disabled": day.isDisabled || void 0,
1381
- "aria-current": day.isToday ? "date" : void 0,
1382
- className: classNames?.gridCell,
1383
- children: /* @__PURE__ */ jsxRuntime.jsx(
1384
- "button",
1581
+ role: "columnheader",
1582
+ abbr: day.full,
1583
+ scope: "col",
1584
+ "aria-colindex": colIndex + 1,
1585
+ className: classNames?.weekdayHeader,
1586
+ children: day.short
1587
+ },
1588
+ day.short
1589
+ ))
1590
+ ] }) }),
1591
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: weeks.map((week, weekIndex) => /* @__PURE__ */ jsxRuntime.jsxs(
1592
+ "tr",
1593
+ {
1594
+ role: "row",
1595
+ "aria-rowindex": weekIndex + 2,
1596
+ className: classNames?.gridRow,
1597
+ children: [
1598
+ showWeekNumber ? /* @__PURE__ */ jsxRuntime.jsx(
1599
+ "th",
1385
1600
  {
1386
- type: "button",
1387
- tabIndex: day.isFocused ? 0 : -1,
1388
- disabled: day.isDisabled,
1389
- "data-focused": day.isFocused || void 0,
1390
- "data-range-start": day.isRangeStart || void 0,
1391
- "data-range-end": day.isRangeEnd || void 0,
1392
- "data-in-range": day.isInRange || void 0,
1393
- "data-today": day.isToday || void 0,
1394
- "data-outside-month": !day.isCurrentMonth || void 0,
1395
- className: dayClasses,
1396
- onClick: () => handleDayClick(day),
1397
- onMouseEnter: () => handleDayMouseEnter(day),
1398
- "aria-label": safeFormatFullDate2(day.isoString, locale),
1399
- children: day.dayNumber
1601
+ scope: "row",
1602
+ "aria-hidden": "true",
1603
+ className: classNames?.weekNumber,
1604
+ "data-week-number": true,
1605
+ children: core.getISOWeekNumber(week[thursdayIndex].isoString)
1400
1606
  }
1401
- )
1402
- },
1403
- day.isoString
1404
- );
1405
- }) }, weekIndex)) })
1607
+ ) : null,
1608
+ week.map((day, colIndex) => {
1609
+ const dayClasses = [
1610
+ classNames?.day,
1611
+ day.isRangeStart && classNames?.dayRangeStart,
1612
+ day.isRangeEnd && classNames?.dayRangeEnd,
1613
+ day.isInRange && classNames?.dayInRange,
1614
+ day.isToday && classNames?.dayToday,
1615
+ day.isDisabled && classNames?.dayDisabled,
1616
+ !day.isCurrentMonth && classNames?.dayOutsideMonth
1617
+ ].filter(Boolean).join(" ") || void 0;
1618
+ const isSelected = selectionMode === "week" ? day.isRangeStart || day.isRangeEnd || day.isInRange : day.isRangeStart || day.isRangeEnd;
1619
+ return /* @__PURE__ */ jsxRuntime.jsx(
1620
+ "td",
1621
+ {
1622
+ role: "gridcell",
1623
+ "aria-colindex": colIndex + 1,
1624
+ "aria-selected": isSelected || void 0,
1625
+ "aria-disabled": day.isDisabled || void 0,
1626
+ "aria-current": day.isToday ? "date" : void 0,
1627
+ className: classNames?.gridCell,
1628
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1629
+ "button",
1630
+ {
1631
+ type: "button",
1632
+ tabIndex: day.isFocused ? 0 : -1,
1633
+ disabled: day.isDisabled,
1634
+ "data-focused": day.isFocused || void 0,
1635
+ "data-range-start": day.isRangeStart || void 0,
1636
+ "data-range-end": day.isRangeEnd || void 0,
1637
+ "data-in-range": day.isInRange || void 0,
1638
+ "data-today": day.isToday || void 0,
1639
+ "data-outside-month": !day.isCurrentMonth || void 0,
1640
+ className: dayClasses,
1641
+ onClick: () => handleDayClick(day),
1642
+ onMouseEnter: () => handleDayMouseEnter(day),
1643
+ "aria-label": safeFormatFullDate2(day.isoString, locale),
1644
+ children: day.dayNumber
1645
+ }
1646
+ )
1647
+ },
1648
+ day.isoString
1649
+ );
1650
+ })
1651
+ ]
1652
+ },
1653
+ weekIndex
1654
+ )) })
1406
1655
  ]
1407
1656
  }
1408
1657
  ),
@@ -1464,41 +1713,33 @@ function RangePickerPreset({
1464
1713
  ...props
1465
1714
  }) {
1466
1715
  const ctx = useRangePickerContext("RangePicker.Preset");
1716
+ const resolved = react.useMemo(() => {
1717
+ if (directRange) return directRange;
1718
+ if (presetKey)
1719
+ return resolvePreset(presetKey, ctx.adapter.today(ctx.displayTimezone), ctx.adapter);
1720
+ return null;
1721
+ }, [directRange, presetKey, ctx.adapter, ctx.displayTimezone]);
1467
1722
  const handleClick = react.useCallback(
1468
1723
  (e) => {
1469
1724
  if (ctx.isDisabled || ctx.isReadOnly) return;
1470
- let resolved;
1471
- if (directRange) {
1472
- resolved = directRange;
1473
- } else if (presetKey) {
1474
- resolved = resolvePreset(presetKey, ctx.adapter.today(), ctx.adapter);
1475
- } else {
1476
- return;
1477
- }
1725
+ if (!resolved) return;
1478
1726
  ctx.setRange(resolved);
1479
1727
  ctx.close();
1480
1728
  onClick?.(e);
1481
1729
  },
1482
- [ctx, presetKey, directRange, onClick]
1730
+ [ctx, resolved, onClick]
1483
1731
  );
1484
- const isActive = (() => {
1485
- if (!ctx.value.start || !ctx.value.end) return false;
1486
- let target;
1487
- if (directRange) {
1488
- target = directRange;
1489
- } else if (presetKey) {
1490
- target = resolvePreset(presetKey, ctx.adapter.today(), ctx.adapter);
1491
- } else {
1732
+ const isActive = react.useMemo(() => {
1733
+ if (!ctx.value.start || !ctx.value.end || !resolved || !resolved.start || !resolved.end) {
1492
1734
  return false;
1493
1735
  }
1494
- return target.start !== null && target.end !== null && ctx.adapter.isSameDay(ctx.value.start, target.start) && ctx.adapter.isSameDay(ctx.value.end, target.end);
1495
- })();
1736
+ return ctx.adapter.isSameDay(ctx.value.start, resolved.start) && ctx.adapter.isSameDay(ctx.value.end, resolved.end);
1737
+ }, [ctx.value.start, ctx.value.end, ctx.adapter, resolved]);
1496
1738
  return /* @__PURE__ */ jsxRuntime.jsx(
1497
1739
  "button",
1498
1740
  {
1499
1741
  type: "button",
1500
- role: "option",
1501
- "aria-selected": isActive,
1742
+ "aria-pressed": isActive,
1502
1743
  "data-active": isActive || void 0,
1503
1744
  disabled: ctx.isDisabled,
1504
1745
  onClick: handleClick,
@@ -1531,9 +1772,6 @@ function useTimePickerContext(componentName) {
1531
1772
  }
1532
1773
  return context;
1533
1774
  }
1534
- function getDefaultIso() {
1535
- return core.DateFnsAdapter.today();
1536
- }
1537
1775
  function TimePickerRoot({
1538
1776
  value: controlledValue,
1539
1777
  defaultValue,
@@ -1544,6 +1782,7 @@ function TimePickerRoot({
1544
1782
  displayTimezone,
1545
1783
  disabled = false,
1546
1784
  readOnly = false,
1785
+ filterTime,
1547
1786
  labels: labelsProp,
1548
1787
  children
1549
1788
  }) {
@@ -1557,21 +1796,21 @@ function TimePickerRoot({
1557
1796
  defaultValue ?? null
1558
1797
  );
1559
1798
  const currentValue = isControlled ? controlledValue ?? null : uncontrolledValue;
1560
- const baseIso = currentValue ?? getDefaultIso();
1561
- const currentTime = react.useMemo(
1562
- () => displayTimezone ? core.getTimeInTimezone(baseIso, displayTimezone) : core.getTime(baseIso),
1563
- [baseIso, displayTimezone]
1564
- );
1799
+ const currentTime = react.useMemo(() => {
1800
+ if (!currentValue) return { hours: 0, minutes: 0, seconds: 0 };
1801
+ return displayTimezone ? core.getTimeInTimezone(currentValue, displayTimezone) : core.getTime(currentValue);
1802
+ }, [currentValue, displayTimezone]);
1565
1803
  const setTime = react.useCallback(
1566
1804
  (partial) => {
1567
1805
  if (disabled || readOnly) return;
1568
- const newIso = displayTimezone ? core.setTimeInTimezone(baseIso, partial, displayTimezone) : core.setTime(baseIso, partial);
1806
+ const base = currentValue ?? core.DateFnsAdapter.today(displayTimezone);
1807
+ const newIso = displayTimezone ? core.setTimeInTimezone(base, partial, displayTimezone) : core.setTime(base, partial);
1569
1808
  if (!isControlled) {
1570
1809
  setUncontrolledValue(newIso);
1571
1810
  }
1572
1811
  onChange?.(newIso);
1573
1812
  },
1574
- [disabled, readOnly, baseIso, isControlled, onChange, displayTimezone]
1813
+ [disabled, readOnly, currentValue, displayTimezone, isControlled, onChange]
1575
1814
  );
1576
1815
  const contextValue = react.useMemo(
1577
1816
  () => ({
@@ -1585,7 +1824,8 @@ function TimePickerRoot({
1585
1824
  isReadOnly: readOnly,
1586
1825
  currentTime,
1587
1826
  pickerId,
1588
- labels: mergedLabels
1827
+ labels: mergedLabels,
1828
+ filterTime
1589
1829
  }),
1590
1830
  [
1591
1831
  currentValue,
@@ -1598,7 +1838,8 @@ function TimePickerRoot({
1598
1838
  readOnly,
1599
1839
  currentTime,
1600
1840
  pickerId,
1601
- mergedLabels
1841
+ mergedLabels,
1842
+ filterTime
1602
1843
  ]
1603
1844
  );
1604
1845
  return /* @__PURE__ */ jsxRuntime.jsx(TimePickerContext.Provider, { value: contextValue, children });
@@ -1655,6 +1896,7 @@ var TimePickerInput = react.forwardRef(
1655
1896
  );
1656
1897
  }
1657
1898
  );
1899
+ TimePickerInput.displayName = "TimePicker.Input";
1658
1900
  function useListboxNavigation({
1659
1901
  items,
1660
1902
  onSelect,
@@ -1702,17 +1944,41 @@ function useListboxNavigation({
1702
1944
  }
1703
1945
  function TimePickerHourList({ classNames, ...props }) {
1704
1946
  const ctx = useTimePickerContext("TimePicker.HourList");
1705
- const { format, currentTime, isDisabled, isReadOnly } = ctx;
1706
- const hours = core.generateHours(format);
1947
+ const { format, step, currentTime, isDisabled, isReadOnly, filterTime } = ctx;
1948
+ const hours = react.useMemo(() => core.generateHours(format), [format]);
1707
1949
  const selectedHourDisplay = format === "12h" ? core.to12Hour(currentTime.hours).hours12 : currentTime.hours;
1708
1950
  const currentPeriod = format === "12h" ? core.to12Hour(currentTime.hours).period : null;
1951
+ const fullyDisabledHours24 = react.useMemo(() => {
1952
+ if (!filterTime) return null;
1953
+ const disabled = /* @__PURE__ */ new Set();
1954
+ for (let h = 0; h < 24; h++) {
1955
+ let allRejected = true;
1956
+ for (let m = 0; m < 60; m += step) {
1957
+ if (!filterTime(h, m)) {
1958
+ allRejected = false;
1959
+ break;
1960
+ }
1961
+ }
1962
+ if (allRejected) disabled.add(h);
1963
+ }
1964
+ return disabled;
1965
+ }, [filterTime, step]);
1966
+ const isHourDisabled = react.useCallback(
1967
+ (hourDisplay) => {
1968
+ if (!fullyDisabledHours24) return false;
1969
+ const hours24 = format === "12h" && currentPeriod ? core.to24Hour(hourDisplay, currentPeriod) : hourDisplay;
1970
+ return fullyDisabledHours24.has(hours24);
1971
+ },
1972
+ [fullyDisabledHours24, format, currentPeriod]
1973
+ );
1709
1974
  const handleSelect = react.useCallback(
1710
1975
  (hourDisplay) => {
1711
1976
  if (isDisabled || isReadOnly) return;
1977
+ if (isHourDisabled(hourDisplay)) return;
1712
1978
  const hours24 = format === "12h" && currentPeriod ? core.to24Hour(hourDisplay, currentPeriod) : hourDisplay;
1713
1979
  ctx.setTime({ hours: hours24 });
1714
1980
  },
1715
- [format, currentPeriod, ctx, isDisabled, isReadOnly]
1981
+ [format, currentPeriod, ctx, isDisabled, isReadOnly, isHourDisabled]
1716
1982
  );
1717
1983
  const { listRef, handleKeyDown } = useListboxNavigation({
1718
1984
  items: hours,
@@ -1730,13 +1996,14 @@ function TimePickerHourList({ classNames, ...props }) {
1730
1996
  ...props,
1731
1997
  children: hours.map((hour) => {
1732
1998
  const isSelected = hour === selectedHourDisplay;
1999
+ const isHourFullyDisabled = isHourDisabled(hour);
1733
2000
  const optionClass = [classNames?.option, isSelected && classNames?.optionSelected].filter(Boolean).join(" ") || void 0;
1734
2001
  return /* @__PURE__ */ jsxRuntime.jsx(
1735
2002
  "li",
1736
2003
  {
1737
2004
  role: "option",
1738
2005
  "aria-selected": isSelected,
1739
- "aria-disabled": isDisabled || void 0,
2006
+ "aria-disabled": isDisabled || isHourFullyDisabled || void 0,
1740
2007
  "aria-label": ctx.labels.hourOption(hour),
1741
2008
  "data-selected": isSelected || void 0,
1742
2009
  tabIndex: isSelected ? 0 : -1,
@@ -1753,14 +2020,22 @@ function TimePickerHourList({ classNames, ...props }) {
1753
2020
  }
1754
2021
  function TimePickerMinuteList({ classNames, ...props }) {
1755
2022
  const ctx = useTimePickerContext("TimePicker.MinuteList");
1756
- const { step, currentTime, isDisabled, isReadOnly } = ctx;
1757
- const minutes = core.generateMinutes(step);
2023
+ const { step, currentTime, isDisabled, isReadOnly, filterTime } = ctx;
2024
+ const minutes = react.useMemo(() => core.generateMinutes(step), [step]);
2025
+ const isMinuteDisabled = react.useCallback(
2026
+ (minute) => {
2027
+ if (!filterTime) return false;
2028
+ return filterTime(currentTime.hours, minute);
2029
+ },
2030
+ [filterTime, currentTime.hours]
2031
+ );
1758
2032
  const handleSelect = react.useCallback(
1759
2033
  (minute) => {
1760
2034
  if (isDisabled || isReadOnly) return;
2035
+ if (isMinuteDisabled(minute)) return;
1761
2036
  ctx.setTime({ minutes: minute });
1762
2037
  },
1763
- [ctx, isDisabled, isReadOnly]
2038
+ [ctx, isDisabled, isReadOnly, isMinuteDisabled]
1764
2039
  );
1765
2040
  const { listRef, handleKeyDown } = useListboxNavigation({
1766
2041
  items: minutes,
@@ -1778,13 +2053,14 @@ function TimePickerMinuteList({ classNames, ...props }) {
1778
2053
  ...props,
1779
2054
  children: minutes.map((minute) => {
1780
2055
  const isSelected = minute === currentTime.minutes;
2056
+ const isMinuteFullyDisabled = isMinuteDisabled(minute);
1781
2057
  const optionClass = [classNames?.option, isSelected && classNames?.optionSelected].filter(Boolean).join(" ") || void 0;
1782
2058
  return /* @__PURE__ */ jsxRuntime.jsx(
1783
2059
  "li",
1784
2060
  {
1785
2061
  role: "option",
1786
2062
  "aria-selected": isSelected,
1787
- "aria-disabled": isDisabled || void 0,
2063
+ "aria-disabled": isDisabled || isMinuteFullyDisabled || void 0,
1788
2064
  "aria-label": ctx.labels.minuteOption(minute),
1789
2065
  "data-selected": isSelected || void 0,
1790
2066
  tabIndex: isSelected ? 0 : -1,
@@ -1801,29 +2077,70 @@ function TimePickerMinuteList({ classNames, ...props }) {
1801
2077
  }
1802
2078
  function TimePickerAmPmToggle({ classNames, ...props }) {
1803
2079
  const ctx = useTimePickerContext("TimePicker.AmPmToggle");
1804
- if (ctx.format !== "12h") return null;
1805
- const { period, hours12 } = core.to12Hour(ctx.currentTime.hours);
2080
+ const amRef = react.useRef(null);
2081
+ const pmRef = react.useRef(null);
1806
2082
  const setPeriod = react.useCallback(
1807
2083
  (newPeriod) => {
1808
2084
  if (ctx.isDisabled || ctx.isReadOnly) return;
2085
+ const { hours12 } = core.to12Hour(ctx.currentTime.hours);
1809
2086
  const newHours24 = core.to24Hour(hours12, newPeriod);
1810
2087
  ctx.setTime({ hours: newHours24 });
1811
2088
  },
1812
- [hours12, ctx]
2089
+ [ctx]
1813
2090
  );
2091
+ if (ctx.format !== "12h") return null;
2092
+ const { period } = core.to12Hour(ctx.currentTime.hours);
2093
+ const focusOther = (target) => {
2094
+ (target === "AM" ? amRef : pmRef).current?.focus();
2095
+ };
2096
+ const handleKeyDown = (e, target) => {
2097
+ switch (e.key) {
2098
+ case "ArrowRight":
2099
+ case "ArrowDown":
2100
+ case "ArrowLeft":
2101
+ case "ArrowUp": {
2102
+ e.preventDefault();
2103
+ const next = target === "AM" ? "PM" : "AM";
2104
+ setPeriod(next);
2105
+ focusOther(next);
2106
+ break;
2107
+ }
2108
+ case "Home": {
2109
+ e.preventDefault();
2110
+ setPeriod("AM");
2111
+ focusOther("AM");
2112
+ break;
2113
+ }
2114
+ case "End": {
2115
+ e.preventDefault();
2116
+ setPeriod("PM");
2117
+ focusOther("PM");
2118
+ break;
2119
+ }
2120
+ case " ":
2121
+ case "Enter": {
2122
+ e.preventDefault();
2123
+ setPeriod(target);
2124
+ break;
2125
+ }
2126
+ }
2127
+ };
1814
2128
  const renderButton = (target) => {
1815
2129
  const isSelected = period === target;
1816
2130
  const optionClass = [classNames?.option, isSelected && classNames?.optionSelected].filter(Boolean).join(" ") || void 0;
1817
2131
  return /* @__PURE__ */ jsxRuntime.jsx(
1818
2132
  "button",
1819
2133
  {
2134
+ ref: target === "AM" ? amRef : pmRef,
1820
2135
  type: "button",
1821
2136
  role: "radio",
1822
2137
  "aria-checked": isSelected,
2138
+ tabIndex: isSelected ? 0 : -1,
1823
2139
  "data-selected": isSelected || void 0,
1824
2140
  disabled: ctx.isDisabled,
1825
2141
  className: optionClass,
1826
2142
  onClick: () => setPeriod(target),
2143
+ onKeyDown: (e) => handleKeyDown(e, target),
1827
2144
  children: target
1828
2145
  }
1829
2146
  );
@@ -1850,9 +2167,6 @@ var TimePicker = Object.assign(TimePickerRoot, {
1850
2167
  MinuteList: TimePickerMinuteList,
1851
2168
  AmPmToggle: TimePickerAmPmToggle
1852
2169
  });
1853
- function getDefaultIso2() {
1854
- return core.DateFnsAdapter.today();
1855
- }
1856
2170
  function DateTimePickerRoot({
1857
2171
  value: controlledValue,
1858
2172
  defaultValue,
@@ -1861,6 +2175,8 @@ function DateTimePickerRoot({
1861
2175
  onCalendarNavigate,
1862
2176
  format = "24h",
1863
2177
  step = 1,
2178
+ withSeconds = false,
2179
+ filterTime,
1864
2180
  disabled = false,
1865
2181
  readOnly = false,
1866
2182
  weekStartsOn = 0,
@@ -1888,10 +2204,10 @@ function DateTimePickerRoot({
1888
2204
  const currentValue = isControlled ? controlledValue ?? null : uncontrolledValue;
1889
2205
  const [isOpen, setIsOpen] = react.useState(false);
1890
2206
  const [viewMonth, setViewMonth] = react.useState(
1891
- currentValue ?? adapter.today(displayTimezone)
2207
+ () => currentValue ?? adapter.today(displayTimezone)
1892
2208
  );
1893
2209
  const [focusedDate, setFocusedDate] = react.useState(
1894
- currentValue ?? adapter.today(displayTimezone)
2210
+ () => currentValue ?? adapter.today(displayTimezone)
1895
2211
  );
1896
2212
  useChangeEffect(isOpen, onOpenChange);
1897
2213
  const viewMonthStart = react.useMemo(() => adapter.startOfMonth(viewMonth), [viewMonth, adapter]);
@@ -1901,11 +2217,10 @@ function DateTimePickerRoot({
1901
2217
  () => Array.isArray(disabled) ? disabled : [],
1902
2218
  [disabled]
1903
2219
  );
1904
- const baseIso = currentValue ?? getDefaultIso2();
1905
- const currentTime = react.useMemo(
1906
- () => displayTimezone ? core.getTimeInTimezone(baseIso, displayTimezone) : core.getTime(baseIso),
1907
- [baseIso, displayTimezone]
1908
- );
2220
+ const currentTime = react.useMemo(() => {
2221
+ if (!currentValue) return { hours: 0, minutes: 0, seconds: 0 };
2222
+ return displayTimezone ? core.getTimeInTimezone(currentValue, displayTimezone) : core.getTime(currentValue);
2223
+ }, [currentValue, displayTimezone]);
1909
2224
  const updateValue = react.useCallback(
1910
2225
  (next) => {
1911
2226
  if (isDisabled || readOnly) return;
@@ -1931,11 +2246,11 @@ function DateTimePickerRoot({
1931
2246
  );
1932
2247
  const setTime = react.useCallback(
1933
2248
  (partial) => {
1934
- const base = currentValue ?? getDefaultIso2();
2249
+ const base = currentValue ?? adapter.today(displayTimezone);
1935
2250
  const merged = displayTimezone ? core.setTimeInTimezone(base, partial, displayTimezone) : core.setTime(base, partial);
1936
2251
  updateValue(merged);
1937
2252
  },
1938
- [currentValue, updateValue, displayTimezone]
2253
+ [currentValue, updateValue, displayTimezone, adapter]
1939
2254
  );
1940
2255
  const open = react.useCallback(() => {
1941
2256
  if (isDisabled || readOnly) return;
@@ -2002,25 +2317,28 @@ function DateTimePickerRoot({
2002
2317
  setTime,
2003
2318
  format,
2004
2319
  step,
2005
- withSeconds: false,
2320
+ withSeconds,
2006
2321
  displayTimezone,
2007
2322
  isDisabled,
2008
2323
  isReadOnly: readOnly,
2009
2324
  currentTime,
2010
2325
  pickerId,
2011
- labels: mergedTimeLabels
2326
+ labels: mergedTimeLabels,
2327
+ filterTime
2012
2328
  }),
2013
2329
  [
2014
2330
  currentValue,
2015
2331
  setTime,
2016
2332
  format,
2017
2333
  step,
2334
+ withSeconds,
2018
2335
  displayTimezone,
2019
2336
  isDisabled,
2020
2337
  readOnly,
2021
2338
  currentTime,
2022
2339
  pickerId,
2023
- mergedTimeLabels
2340
+ mergedTimeLabels,
2341
+ filterTime
2024
2342
  ]
2025
2343
  );
2026
2344
  return /* @__PURE__ */ jsxRuntime.jsx(DatePickerContext.Provider, { value: dateContext, children: /* @__PURE__ */ jsxRuntime.jsx(TimePickerContext.Provider, { value: timeContext, children }) });
@@ -2049,6 +2367,8 @@ var DateTimePickerInput = react.forwardRef(
2049
2367
  (e) => {
2050
2368
  if (e.key === "Escape") {
2051
2369
  ctx.close();
2370
+ } else if (e.key === "Enter" && ctx.isOpen) {
2371
+ e.preventDefault();
2052
2372
  } else if (e.key === "ArrowDown" && !ctx.isOpen) {
2053
2373
  e.preventDefault();
2054
2374
  ctx.open();
@@ -2084,6 +2404,7 @@ var DateTimePickerInput = react.forwardRef(
2084
2404
  );
2085
2405
  }
2086
2406
  );
2407
+ DateTimePickerInput.displayName = "DateTimePicker.Input";
2087
2408
 
2088
2409
  // src/components/DateTimePicker/index.ts
2089
2410
  var DateTimePicker = Object.assign(DateTimePickerRoot, {
@@ -2102,7 +2423,7 @@ function MonthPickerRoot(props) {
2102
2423
  }
2103
2424
  function MonthPickerGrid({ classNames, ...props }) {
2104
2425
  const ctx = useDatePickerContext("MonthPicker.Grid");
2105
- const { adapter, viewMonth, locale, value, displayTimezone, labels } = ctx;
2426
+ const { adapter, viewMonth, locale, value, displayTimezone, labels, disabled } = ctx;
2106
2427
  const currentYear = adapter.getYear(viewMonth);
2107
2428
  const [valueYear, valueMonthZeroBased] = react.useMemo(() => {
2108
2429
  if (!value) return [null, null];
@@ -2113,9 +2434,19 @@ function MonthPickerGrid({ classNames, ...props }) {
2113
2434
  return [null, null];
2114
2435
  }
2115
2436
  }, [value, adapter, displayTimezone]);
2116
- const today = adapter.today(displayTimezone);
2117
- const todayYear = adapter.getYear(today);
2118
- const todayMonth = adapter.getMonth(today);
2437
+ const [today, setToday] = react.useState(null);
2438
+ react.useEffect(() => {
2439
+ setToday(adapter.today(displayTimezone));
2440
+ }, [adapter, displayTimezone]);
2441
+ const todayYear = today !== null ? adapter.getYear(today) : -1;
2442
+ const todayMonth = today !== null ? adapter.getMonth(today) : -1;
2443
+ const monthDisabledFlags = react.useMemo(
2444
+ () => Array.from({ length: 12 }, (_, i) => {
2445
+ const monthStart = new Date(Date.UTC(currentYear, i, 1)).toISOString();
2446
+ return isRangeFullyDisabled(monthStart, adapter.endOfMonth(monthStart), disabled, adapter);
2447
+ }),
2448
+ [currentYear, disabled, adapter]
2449
+ );
2119
2450
  const navigateYear = react.useCallback(
2120
2451
  (direction) => {
2121
2452
  ctx.setViewMonth(adapter.addYears(viewMonth, direction));
@@ -2124,17 +2455,23 @@ function MonthPickerGrid({ classNames, ...props }) {
2124
2455
  );
2125
2456
  const handleMonthSelect = react.useCallback(
2126
2457
  (monthIndex) => {
2458
+ if (monthDisabledFlags[monthIndex]) return;
2127
2459
  const target = new Date(Date.UTC(currentYear, monthIndex, 1)).toISOString();
2128
2460
  ctx.selectDate(target);
2129
2461
  },
2130
- [currentYear, ctx]
2131
- );
2132
- const months = Array.from({ length: 12 }, (_, i) => ({
2133
- index: i,
2134
- name: core.getMonthName(i, locale),
2135
- isSelected: valueYear === currentYear && valueMonthZeroBased === i,
2136
- isCurrent: todayYear === currentYear && todayMonth === i
2137
- }));
2462
+ [currentYear, ctx, monthDisabledFlags]
2463
+ );
2464
+ const naturalIndex = valueYear === currentYear && valueMonthZeroBased !== null ? valueMonthZeroBased : adapter.getMonth(viewMonth);
2465
+ const firstEnabled = monthDisabledFlags.findIndex((d) => !d);
2466
+ const initialIndex = monthDisabledFlags[naturalIndex] ? firstEnabled === -1 ? naturalIndex : firstEnabled : naturalIndex;
2467
+ const { gridRef, focusedIndex, handleKeyDown } = useGridState({
2468
+ initialIndex,
2469
+ disabledFlags: monthDisabledFlags,
2470
+ onSelect: handleMonthSelect,
2471
+ onPageUp: () => navigateYear(-1),
2472
+ onPageDown: () => navigateYear(1),
2473
+ onEscape: ctx.close
2474
+ });
2138
2475
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.root, ...props, children: [
2139
2476
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.header, children: [
2140
2477
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -2159,37 +2496,57 @@ function MonthPickerGrid({ classNames, ...props }) {
2159
2496
  }
2160
2497
  )
2161
2498
  ] }),
2162
- /* @__PURE__ */ jsxRuntime.jsx("div", { role: "grid", "aria-label": `${currentYear} months`, className: classNames?.grid, children: Array.from({ length: 4 }, (_, rowIndex) => /* @__PURE__ */ jsxRuntime.jsx(
2499
+ /* @__PURE__ */ jsxRuntime.jsx(
2163
2500
  "div",
2164
2501
  {
2165
- role: "row",
2166
- className: classNames?.gridRow,
2167
- style: { display: "grid", gridTemplateColumns: "repeat(3, 1fr)" },
2168
- children: months.slice(rowIndex * 3, rowIndex * 3 + 3).map((m) => {
2169
- const monthClass = [
2170
- classNames?.month,
2171
- m.isSelected && classNames?.monthSelected,
2172
- m.isCurrent && classNames?.monthCurrent
2173
- ].filter(Boolean).join(" ") || void 0;
2174
- return /* @__PURE__ */ jsxRuntime.jsx(
2175
- "button",
2176
- {
2177
- type: "button",
2178
- role: "gridcell",
2179
- "aria-selected": m.isSelected || void 0,
2180
- "aria-current": m.isCurrent ? "date" : void 0,
2181
- "data-selected": m.isSelected || void 0,
2182
- "data-current": m.isCurrent || void 0,
2183
- className: monthClass,
2184
- onClick: () => handleMonthSelect(m.index),
2185
- children: m.name
2186
- },
2187
- m.index
2188
- );
2189
- })
2190
- },
2191
- rowIndex
2192
- )) })
2502
+ ref: gridRef,
2503
+ role: "grid",
2504
+ "aria-label": `${currentYear} months`,
2505
+ className: classNames?.grid,
2506
+ onKeyDown: handleKeyDown,
2507
+ children: Array.from({ length: 4 }, (_, rowIndex) => /* @__PURE__ */ jsxRuntime.jsx(
2508
+ "div",
2509
+ {
2510
+ role: "row",
2511
+ className: classNames?.gridRow,
2512
+ style: { display: "grid", gridTemplateColumns: "repeat(3, 1fr)" },
2513
+ children: Array.from({ length: 3 }, (_2, col) => {
2514
+ const i = rowIndex * 3 + col;
2515
+ const isSelected = valueYear === currentYear && valueMonthZeroBased === i;
2516
+ const isCurrent = todayYear === currentYear && todayMonth === i;
2517
+ const isFocused = i === focusedIndex;
2518
+ const isDisabled = monthDisabledFlags[i] ?? false;
2519
+ const cls = [
2520
+ classNames?.month,
2521
+ isSelected && classNames?.monthSelected,
2522
+ isCurrent && classNames?.monthCurrent,
2523
+ isDisabled && classNames?.monthDisabled
2524
+ ].filter(Boolean).join(" ") || void 0;
2525
+ return /* @__PURE__ */ jsxRuntime.jsx(
2526
+ "button",
2527
+ {
2528
+ type: "button",
2529
+ role: "gridcell",
2530
+ tabIndex: isFocused ? 0 : -1,
2531
+ disabled: isDisabled,
2532
+ "aria-selected": isSelected || void 0,
2533
+ "aria-disabled": isDisabled || void 0,
2534
+ "aria-current": isCurrent ? "date" : void 0,
2535
+ "data-selected": isSelected || void 0,
2536
+ "data-current": isCurrent || void 0,
2537
+ "data-focused": isFocused || void 0,
2538
+ className: cls,
2539
+ onClick: () => handleMonthSelect(i),
2540
+ children: core.getMonthName(i, locale)
2541
+ },
2542
+ i
2543
+ );
2544
+ })
2545
+ },
2546
+ rowIndex
2547
+ ))
2548
+ }
2549
+ )
2193
2550
  ] });
2194
2551
  }
2195
2552
 
@@ -2206,7 +2563,7 @@ function YearPickerRoot(props) {
2206
2563
  }
2207
2564
  function YearPickerGrid({ classNames, ...props }) {
2208
2565
  const ctx = useDatePickerContext("YearPicker.Grid");
2209
- const { adapter, viewMonth, value, displayTimezone, labels } = ctx;
2566
+ const { adapter, viewMonth, value, displayTimezone, labels, disabled } = ctx;
2210
2567
  const currentYear = adapter.getYear(viewMonth);
2211
2568
  const decadeStart = currentYear - currentYear % 12;
2212
2569
  const valueYear = react.useMemo(() => {
@@ -2217,7 +2574,20 @@ function YearPickerGrid({ classNames, ...props }) {
2217
2574
  return null;
2218
2575
  }
2219
2576
  }, [value, adapter, displayTimezone]);
2220
- const todayYear = adapter.getYear(adapter.today(displayTimezone));
2577
+ const [today, setToday] = react.useState(null);
2578
+ react.useEffect(() => {
2579
+ setToday(adapter.today(displayTimezone));
2580
+ }, [adapter, displayTimezone]);
2581
+ const todayYear = today !== null ? adapter.getYear(today) : -1;
2582
+ const yearDisabledFlags = react.useMemo(
2583
+ () => Array.from({ length: 12 }, (_, i) => {
2584
+ const year = decadeStart + i;
2585
+ const yearStart = new Date(Date.UTC(year, 0, 1)).toISOString();
2586
+ const yearEnd = new Date(Date.UTC(year, 11, 31, 23, 59, 59, 999)).toISOString();
2587
+ return isRangeFullyDisabled(yearStart, yearEnd, disabled, adapter);
2588
+ }),
2589
+ [decadeStart, disabled, adapter]
2590
+ );
2221
2591
  const navigateDecade = react.useCallback(
2222
2592
  (direction) => {
2223
2593
  ctx.setViewMonth(adapter.addYears(viewMonth, direction * 12));
@@ -2225,19 +2595,24 @@ function YearPickerGrid({ classNames, ...props }) {
2225
2595
  [adapter, viewMonth, ctx]
2226
2596
  );
2227
2597
  const handleYearSelect = react.useCallback(
2228
- (year) => {
2598
+ (indexInDecade) => {
2599
+ if (yearDisabledFlags[indexInDecade]) return;
2600
+ const year = decadeStart + indexInDecade;
2229
2601
  const target = new Date(Date.UTC(year, 0, 1)).toISOString();
2230
2602
  ctx.selectDate(target);
2231
2603
  },
2232
- [ctx]
2233
- );
2234
- const years = Array.from({ length: 12 }, (_, i) => {
2235
- const year = decadeStart + i;
2236
- return {
2237
- value: year,
2238
- isSelected: year === valueYear,
2239
- isCurrent: year === todayYear
2240
- };
2604
+ [ctx, decadeStart, yearDisabledFlags]
2605
+ );
2606
+ const naturalIndex = valueYear !== null && valueYear >= decadeStart && valueYear <= decadeStart + 11 ? valueYear - decadeStart : currentYear - decadeStart;
2607
+ const firstEnabled = yearDisabledFlags.findIndex((d) => !d);
2608
+ const initialIndex = yearDisabledFlags[naturalIndex] ? firstEnabled === -1 ? naturalIndex : firstEnabled : naturalIndex;
2609
+ const { gridRef, focusedIndex, handleKeyDown } = useGridState({
2610
+ initialIndex,
2611
+ disabledFlags: yearDisabledFlags,
2612
+ onSelect: handleYearSelect,
2613
+ onPageUp: () => navigateDecade(-1),
2614
+ onPageDown: () => navigateDecade(1),
2615
+ onEscape: ctx.close
2241
2616
  });
2242
2617
  const rangeLabel = `${decadeStart}\u2013${decadeStart + 11}`;
2243
2618
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.root, ...props, children: [
@@ -2264,37 +2639,58 @@ function YearPickerGrid({ classNames, ...props }) {
2264
2639
  }
2265
2640
  )
2266
2641
  ] }),
2267
- /* @__PURE__ */ jsxRuntime.jsx("div", { role: "grid", "aria-label": rangeLabel, className: classNames?.grid, children: Array.from({ length: 4 }, (_, rowIndex) => /* @__PURE__ */ jsxRuntime.jsx(
2642
+ /* @__PURE__ */ jsxRuntime.jsx(
2268
2643
  "div",
2269
2644
  {
2270
- role: "row",
2271
- className: classNames?.gridRow,
2272
- style: { display: "grid", gridTemplateColumns: "repeat(3, 1fr)" },
2273
- children: years.slice(rowIndex * 3, rowIndex * 3 + 3).map((y) => {
2274
- const yearClass = [
2275
- classNames?.year,
2276
- y.isSelected && classNames?.yearSelected,
2277
- y.isCurrent && classNames?.yearCurrent
2278
- ].filter(Boolean).join(" ") || void 0;
2279
- return /* @__PURE__ */ jsxRuntime.jsx(
2280
- "button",
2281
- {
2282
- type: "button",
2283
- role: "gridcell",
2284
- "aria-selected": y.isSelected || void 0,
2285
- "aria-current": y.isCurrent ? "date" : void 0,
2286
- "data-selected": y.isSelected || void 0,
2287
- "data-current": y.isCurrent || void 0,
2288
- className: yearClass,
2289
- onClick: () => handleYearSelect(y.value),
2290
- children: y.value
2291
- },
2292
- y.value
2293
- );
2294
- })
2295
- },
2296
- rowIndex
2297
- )) })
2645
+ ref: gridRef,
2646
+ role: "grid",
2647
+ "aria-label": rangeLabel,
2648
+ className: classNames?.grid,
2649
+ onKeyDown: handleKeyDown,
2650
+ children: Array.from({ length: 4 }, (_, rowIndex) => /* @__PURE__ */ jsxRuntime.jsx(
2651
+ "div",
2652
+ {
2653
+ role: "row",
2654
+ className: classNames?.gridRow,
2655
+ style: { display: "grid", gridTemplateColumns: "repeat(3, 1fr)" },
2656
+ children: Array.from({ length: 3 }, (_2, col) => {
2657
+ const i = rowIndex * 3 + col;
2658
+ const year = decadeStart + i;
2659
+ const isSelected = year === valueYear;
2660
+ const isCurrent = year === todayYear;
2661
+ const isFocused = i === focusedIndex;
2662
+ const isDisabled = yearDisabledFlags[i] ?? false;
2663
+ const cls = [
2664
+ classNames?.year,
2665
+ isSelected && classNames?.yearSelected,
2666
+ isCurrent && classNames?.yearCurrent,
2667
+ isDisabled && classNames?.yearDisabled
2668
+ ].filter(Boolean).join(" ") || void 0;
2669
+ return /* @__PURE__ */ jsxRuntime.jsx(
2670
+ "button",
2671
+ {
2672
+ type: "button",
2673
+ role: "gridcell",
2674
+ tabIndex: isFocused ? 0 : -1,
2675
+ disabled: isDisabled,
2676
+ "aria-selected": isSelected || void 0,
2677
+ "aria-disabled": isDisabled || void 0,
2678
+ "aria-current": isCurrent ? "date" : void 0,
2679
+ "data-selected": isSelected || void 0,
2680
+ "data-current": isCurrent || void 0,
2681
+ "data-focused": isFocused || void 0,
2682
+ className: cls,
2683
+ onClick: () => handleYearSelect(i),
2684
+ children: year
2685
+ },
2686
+ i
2687
+ );
2688
+ })
2689
+ },
2690
+ rowIndex
2691
+ ))
2692
+ }
2693
+ )
2298
2694
  ] });
2299
2695
  }
2300
2696
 
@@ -2515,7 +2911,7 @@ function useRangePicker(options = {}) {
2515
2911
  adapter
2516
2912
  };
2517
2913
  }
2518
- function getDefaultIso3() {
2914
+ function getDefaultIso() {
2519
2915
  return core.DateFnsAdapter.today();
2520
2916
  }
2521
2917
  function useTimePicker(options = {}) {
@@ -2533,7 +2929,7 @@ function useTimePicker(options = {}) {
2533
2929
  defaultValue ?? null
2534
2930
  );
2535
2931
  const currentValue = isControlled ? controlledValue ?? null : uncontrolledValue;
2536
- const baseIso = currentValue ?? getDefaultIso3();
2932
+ const baseIso = currentValue ?? getDefaultIso();
2537
2933
  const currentTime = react.useMemo(
2538
2934
  () => displayTimezone ? core.getTimeInTimezone(baseIso, displayTimezone) : core.getTime(baseIso),
2539
2935
  [baseIso, displayTimezone]
@@ -2584,6 +2980,22 @@ function useTimePicker(options = {}) {
2584
2980
  };
2585
2981
  }
2586
2982
 
2983
+ Object.defineProperty(exports, "DEFAULT_DATEPICKER_LABELS", {
2984
+ enumerable: true,
2985
+ get: function () { return core.DEFAULT_DATEPICKER_LABELS; }
2986
+ });
2987
+ Object.defineProperty(exports, "DEFAULT_DATETIMEPICKER_LABELS", {
2988
+ enumerable: true,
2989
+ get: function () { return core.DEFAULT_DATETIMEPICKER_LABELS; }
2990
+ });
2991
+ Object.defineProperty(exports, "DEFAULT_RANGEPICKER_LABELS", {
2992
+ enumerable: true,
2993
+ get: function () { return core.DEFAULT_RANGEPICKER_LABELS; }
2994
+ });
2995
+ Object.defineProperty(exports, "DEFAULT_TIMEPICKER_LABELS", {
2996
+ enumerable: true,
2997
+ get: function () { return core.DEFAULT_TIMEPICKER_LABELS; }
2998
+ });
2587
2999
  Object.defineProperty(exports, "DateFnsAdapter", {
2588
3000
  enumerable: true,
2589
3001
  get: function () { return core.DateFnsAdapter; }