@kalyx/react 1.0.0-rc.4 → 1.0.0-rc.6

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.d.cts CHANGED
@@ -692,9 +692,18 @@ interface MonthPickerGridProps extends Omit<HTMLAttributes<HTMLDivElement>, 'rol
692
692
  * Unlike `DatePicker.MonthGrid` (drilldown), this component commits the month selection
693
693
  * via `ctx.selectDate`, emitting the month-start ISO string.
694
694
  *
695
+ * Disabled state: a month is marked unselectable when every day in it is
696
+ * excluded by a `before`/`after` rule on the `disabled` prop. The cell is
697
+ * rendered with `disabled` + `aria-disabled` + the `monthDisabled` className,
698
+ * and keyboard navigation skips it.
699
+ *
695
700
  * @example
696
701
  * ```tsx
697
- * <MonthPicker value={month} onChange={setMonth} displayFormat="yyyy-MM">
702
+ * <MonthPicker
703
+ * value={month}
704
+ * onChange={setMonth}
705
+ * disabled={[{ before: '2026-04-01T00:00:00.000Z' }]}
706
+ * >
698
707
  * <MonthPicker.Input />
699
708
  * <MonthPicker.Popover>
700
709
  * <MonthPicker.Grid />
@@ -756,9 +765,18 @@ interface YearPickerGridProps extends Omit<HTMLAttributes<HTMLDivElement>, 'role
756
765
  * Unlike `DatePicker.YearGrid` (drilldown), this component commits the year selection via
757
766
  * `ctx.selectDate`, emitting the year-start ISO string (Jan 1 at UTC midnight).
758
767
  *
768
+ * Disabled state: a year is marked unselectable when every day in it is
769
+ * excluded by a `before`/`after` rule on the `disabled` prop. The cell is
770
+ * rendered with `disabled` + `aria-disabled` + the `yearDisabled` className,
771
+ * and keyboard navigation skips it.
772
+ *
759
773
  * @example
760
774
  * ```tsx
761
- * <YearPicker value={year} onChange={setYear}>
775
+ * <YearPicker
776
+ * value={year}
777
+ * onChange={setYear}
778
+ * disabled={[{ before: '2024-01-01T00:00:00.000Z' }]}
779
+ * >
762
780
  * <YearPicker.Input />
763
781
  * <YearPicker.Popover>
764
782
  * <YearPicker.Grid />
package/dist/index.d.ts CHANGED
@@ -692,9 +692,18 @@ interface MonthPickerGridProps extends Omit<HTMLAttributes<HTMLDivElement>, 'rol
692
692
  * Unlike `DatePicker.MonthGrid` (drilldown), this component commits the month selection
693
693
  * via `ctx.selectDate`, emitting the month-start ISO string.
694
694
  *
695
+ * Disabled state: a month is marked unselectable when every day in it is
696
+ * excluded by a `before`/`after` rule on the `disabled` prop. The cell is
697
+ * rendered with `disabled` + `aria-disabled` + the `monthDisabled` className,
698
+ * and keyboard navigation skips it.
699
+ *
695
700
  * @example
696
701
  * ```tsx
697
- * <MonthPicker value={month} onChange={setMonth} displayFormat="yyyy-MM">
702
+ * <MonthPicker
703
+ * value={month}
704
+ * onChange={setMonth}
705
+ * disabled={[{ before: '2026-04-01T00:00:00.000Z' }]}
706
+ * >
698
707
  * <MonthPicker.Input />
699
708
  * <MonthPicker.Popover>
700
709
  * <MonthPicker.Grid />
@@ -756,9 +765,18 @@ interface YearPickerGridProps extends Omit<HTMLAttributes<HTMLDivElement>, 'role
756
765
  * Unlike `DatePicker.YearGrid` (drilldown), this component commits the year selection via
757
766
  * `ctx.selectDate`, emitting the year-start ISO string (Jan 1 at UTC midnight).
758
767
  *
768
+ * Disabled state: a year is marked unselectable when every day in it is
769
+ * excluded by a `before`/`after` rule on the `disabled` prop. The cell is
770
+ * rendered with `disabled` + `aria-disabled` + the `yearDisabled` className,
771
+ * and keyboard navigation skips it.
772
+ *
759
773
  * @example
760
774
  * ```tsx
761
- * <YearPicker value={year} onChange={setYear}>
775
+ * <YearPicker
776
+ * value={year}
777
+ * onChange={setYear}
778
+ * disabled={[{ before: '2024-01-01T00:00:00.000Z' }]}
779
+ * >
762
780
  * <YearPicker.Input />
763
781
  * <YearPicker.Popover>
764
782
  * <YearPicker.Grid />
package/dist/index.js CHANGED
@@ -685,6 +685,86 @@ function DatePickerCalendar({
685
685
  /* @__PURE__ */ jsx("div", { role: "status", "aria-live": "polite", "aria-atomic": "true", style: srOnly, children: announcement })
686
686
  ] });
687
687
  }
688
+ function isRangeFullyDisabled(start, end, rules, adapter) {
689
+ for (const rule of rules) {
690
+ if ("before" in rule && adapter.isBefore(end, rule.before)) return true;
691
+ if ("after" in rule && adapter.isAfter(start, rule.after)) return true;
692
+ }
693
+ return false;
694
+ }
695
+ function useGridState(opts) {
696
+ const { initialIndex, disabledFlags, onSelect, onPageUp, onPageDown, onEscape } = opts;
697
+ const gridRef = useRef(null);
698
+ const [focusedIndex, setFocusedIndex] = useState(initialIndex);
699
+ const handleKeyDown = (e) => {
700
+ let next = null;
701
+ let step = 1;
702
+ switch (e.key) {
703
+ case "ArrowLeft":
704
+ next = Math.max(0, focusedIndex - 1);
705
+ step = -1;
706
+ break;
707
+ case "ArrowRight":
708
+ next = Math.min(11, focusedIndex + 1);
709
+ break;
710
+ case "ArrowUp":
711
+ next = Math.max(0, focusedIndex - 3);
712
+ step = -1;
713
+ break;
714
+ case "ArrowDown":
715
+ next = Math.min(11, focusedIndex + 3);
716
+ break;
717
+ case "Home":
718
+ next = focusedIndex - focusedIndex % 3;
719
+ step = -1;
720
+ break;
721
+ case "End":
722
+ next = focusedIndex - focusedIndex % 3 + 2;
723
+ break;
724
+ case "PageUp":
725
+ e.preventDefault();
726
+ onPageUp();
727
+ return;
728
+ case "PageDown":
729
+ e.preventDefault();
730
+ onPageDown();
731
+ return;
732
+ case "Enter":
733
+ case " ":
734
+ e.preventDefault();
735
+ onSelect(focusedIndex);
736
+ return;
737
+ case "Escape":
738
+ onEscape();
739
+ return;
740
+ default:
741
+ return;
742
+ }
743
+ if (next === null) return;
744
+ e.preventDefault();
745
+ if (disabledFlags) {
746
+ let attempts = 0;
747
+ while (next >= 0 && next < 12 && disabledFlags[next] && attempts < 12) {
748
+ next += step;
749
+ attempts++;
750
+ }
751
+ if (next < 0 || next >= 12 || disabledFlags[next]) return;
752
+ }
753
+ if (next !== focusedIndex) setFocusedIndex(next);
754
+ };
755
+ useEffect(() => {
756
+ if (!disabledFlags || !disabledFlags[focusedIndex]) return;
757
+ const firstEnabled = disabledFlags.findIndex((d) => !d);
758
+ if (firstEnabled !== -1 && firstEnabled !== focusedIndex) {
759
+ setFocusedIndex(firstEnabled);
760
+ }
761
+ }, [disabledFlags, focusedIndex]);
762
+ useEffect(() => {
763
+ const btn = gridRef.current?.querySelector('[data-focused="true"]');
764
+ btn?.focus({ preventScroll: true });
765
+ }, [focusedIndex]);
766
+ return { gridRef, focusedIndex, handleKeyDown };
767
+ }
688
768
  function DatePickerMonthGrid({
689
769
  classNames,
690
770
  onSelect,
@@ -703,8 +783,7 @@ function DatePickerMonthGrid({
703
783
  const todayYear = today !== null ? adapter.getYear(today) : -1;
704
784
  const navigateYear = useCallback(
705
785
  (direction) => {
706
- const newDate = adapter.addYears(viewMonth, direction);
707
- ctx.setViewMonth(newDate);
786
+ ctx.setViewMonth(adapter.addYears(viewMonth, direction));
708
787
  },
709
788
  [adapter, viewMonth, ctx]
710
789
  );
@@ -717,12 +796,13 @@ function DatePickerMonthGrid({
717
796
  },
718
797
  [currentYear, ctx, onSelect]
719
798
  );
720
- const months = Array.from({ length: 12 }, (_, i) => ({
721
- index: i,
722
- name: getMonthName(i, locale),
723
- isSelected: i === currentMonth,
724
- isCurrent: i === todayMonth && currentYear === todayYear
725
- }));
799
+ const { gridRef, focusedIndex, handleKeyDown } = useGridState({
800
+ initialIndex: currentMonth,
801
+ onSelect: handleMonthSelect,
802
+ onPageUp: () => navigateYear(-1),
803
+ onPageDown: () => navigateYear(1),
804
+ onEscape: ctx.close
805
+ });
726
806
  return /* @__PURE__ */ jsxs("div", { className: classNames?.root, ...props, children: [
727
807
  /* @__PURE__ */ jsxs("div", { className: classNames?.header, children: [
728
808
  /* @__PURE__ */ jsx(
@@ -750,30 +830,37 @@ function DatePickerMonthGrid({
750
830
  /* @__PURE__ */ jsx(
751
831
  "div",
752
832
  {
833
+ ref: gridRef,
753
834
  role: "grid",
754
835
  "aria-label": `${currentYear} months`,
755
836
  className: classNames?.grid,
756
837
  style: { display: "grid", gridTemplateColumns: "repeat(3, 1fr)" },
757
- children: months.map((m) => {
758
- const monthClass = [
838
+ onKeyDown: handleKeyDown,
839
+ children: Array.from({ length: 12 }, (_, i) => {
840
+ const isSelected = i === currentMonth;
841
+ const isCurrent = i === todayMonth && currentYear === todayYear;
842
+ const isFocused = i === focusedIndex;
843
+ const cls = [
759
844
  classNames?.month,
760
- m.isSelected && classNames?.monthSelected,
761
- m.isCurrent && classNames?.monthCurrent
845
+ isSelected && classNames?.monthSelected,
846
+ isCurrent && classNames?.monthCurrent
762
847
  ].filter(Boolean).join(" ") || void 0;
763
848
  return /* @__PURE__ */ jsx(
764
849
  "button",
765
850
  {
766
851
  type: "button",
767
852
  role: "gridcell",
768
- "aria-selected": m.isSelected || void 0,
769
- "aria-current": m.isCurrent ? "date" : void 0,
770
- "data-selected": m.isSelected || void 0,
771
- "data-current": m.isCurrent || void 0,
772
- className: monthClass,
773
- onClick: () => handleMonthSelect(m.index),
774
- children: m.name
853
+ tabIndex: isFocused ? 0 : -1,
854
+ "aria-selected": isSelected || void 0,
855
+ "aria-current": isCurrent ? "date" : void 0,
856
+ "data-selected": isSelected || void 0,
857
+ "data-current": isCurrent || void 0,
858
+ "data-focused": isFocused || void 0,
859
+ className: cls,
860
+ onClick: () => handleMonthSelect(i),
861
+ children: getMonthName(i, locale)
775
862
  },
776
- m.index
863
+ i
777
864
  );
778
865
  })
779
866
  }
@@ -792,32 +879,28 @@ function DatePickerYearGrid({ classNames, onSelect, ...props }) {
792
879
  const decadeStart = currentYear - currentYear % 12;
793
880
  const navigateDecade = useCallback(
794
881
  (direction) => {
795
- const newDate = adapter.addYears(viewMonth, direction * 12);
796
- ctx.setViewMonth(newDate);
882
+ ctx.setViewMonth(adapter.addYears(viewMonth, direction * 12));
797
883
  },
798
884
  [adapter, viewMonth, ctx]
799
885
  );
800
886
  const handleYearSelect = useCallback(
801
- (year) => {
887
+ (indexInDecade) => {
888
+ const year = decadeStart + indexInDecade;
802
889
  const currentMonth = adapter.getMonth(viewMonth);
803
890
  const target = new Date(Date.UTC(year, currentMonth, 1)).toISOString();
804
891
  ctx.setViewMonth(target);
805
892
  ctx.setFocusedDate(target);
806
893
  onSelect?.();
807
894
  },
808
- [adapter, viewMonth, ctx, onSelect]
809
- );
810
- const years = useMemo(
811
- () => Array.from({ length: 12 }, (_, i) => {
812
- const year = decadeStart + i;
813
- return {
814
- value: year,
815
- isSelected: year === currentYear,
816
- isCurrent: year === todayYear
817
- };
818
- }),
819
- [decadeStart, currentYear, todayYear]
820
- );
895
+ [adapter, viewMonth, ctx, onSelect, decadeStart]
896
+ );
897
+ const { gridRef, focusedIndex, handleKeyDown } = useGridState({
898
+ initialIndex: currentYear - decadeStart,
899
+ onSelect: handleYearSelect,
900
+ onPageUp: () => navigateDecade(-1),
901
+ onPageDown: () => navigateDecade(1),
902
+ onEscape: ctx.close
903
+ });
821
904
  const rangeLabel = `${decadeStart}\u2013${decadeStart + 11}`;
822
905
  return /* @__PURE__ */ jsxs("div", { className: classNames?.root, ...props, children: [
823
906
  /* @__PURE__ */ jsxs("div", { className: classNames?.header, children: [
@@ -846,30 +929,38 @@ function DatePickerYearGrid({ classNames, onSelect, ...props }) {
846
929
  /* @__PURE__ */ jsx(
847
930
  "div",
848
931
  {
932
+ ref: gridRef,
849
933
  role: "grid",
850
934
  "aria-label": rangeLabel,
851
935
  className: classNames?.grid,
852
936
  style: { display: "grid", gridTemplateColumns: "repeat(3, 1fr)" },
853
- children: years.map((y) => {
854
- const yearClass = [
937
+ onKeyDown: handleKeyDown,
938
+ children: Array.from({ length: 12 }, (_, i) => {
939
+ const year = decadeStart + i;
940
+ const isSelected = year === currentYear;
941
+ const isCurrent = year === todayYear;
942
+ const isFocused = i === focusedIndex;
943
+ const cls = [
855
944
  classNames?.year,
856
- y.isSelected && classNames?.yearSelected,
857
- y.isCurrent && classNames?.yearCurrent
945
+ isSelected && classNames?.yearSelected,
946
+ isCurrent && classNames?.yearCurrent
858
947
  ].filter(Boolean).join(" ") || void 0;
859
948
  return /* @__PURE__ */ jsx(
860
949
  "button",
861
950
  {
862
951
  type: "button",
863
952
  role: "gridcell",
864
- "aria-selected": y.isSelected || void 0,
865
- "aria-current": y.isCurrent ? "date" : void 0,
866
- "data-selected": y.isSelected || void 0,
867
- "data-current": y.isCurrent || void 0,
868
- className: yearClass,
869
- onClick: () => handleYearSelect(y.value),
870
- children: y.value
953
+ tabIndex: isFocused ? 0 : -1,
954
+ "aria-selected": isSelected || void 0,
955
+ "aria-current": isCurrent ? "date" : void 0,
956
+ "data-selected": isSelected || void 0,
957
+ "data-current": isCurrent || void 0,
958
+ "data-focused": isFocused || void 0,
959
+ className: cls,
960
+ onClick: () => handleYearSelect(i),
961
+ children: year
871
962
  },
872
- y.value
963
+ i
873
964
  );
874
965
  })
875
966
  }
@@ -2246,7 +2337,7 @@ function MonthPickerRoot(props) {
2246
2337
  }
2247
2338
  function MonthPickerGrid({ classNames, ...props }) {
2248
2339
  const ctx = useDatePickerContext("MonthPicker.Grid");
2249
- const { adapter, viewMonth, locale, value, displayTimezone, labels } = ctx;
2340
+ const { adapter, viewMonth, locale, value, displayTimezone, labels, disabled } = ctx;
2250
2341
  const currentYear = adapter.getYear(viewMonth);
2251
2342
  const [valueYear, valueMonthZeroBased] = useMemo(() => {
2252
2343
  if (!value) return [null, null];
@@ -2263,6 +2354,13 @@ function MonthPickerGrid({ classNames, ...props }) {
2263
2354
  }, [adapter, displayTimezone]);
2264
2355
  const todayYear = today !== null ? adapter.getYear(today) : -1;
2265
2356
  const todayMonth = today !== null ? adapter.getMonth(today) : -1;
2357
+ const monthDisabledFlags = useMemo(
2358
+ () => Array.from({ length: 12 }, (_, i) => {
2359
+ const monthStart = new Date(Date.UTC(currentYear, i, 1)).toISOString();
2360
+ return isRangeFullyDisabled(monthStart, adapter.endOfMonth(monthStart), disabled, adapter);
2361
+ }),
2362
+ [currentYear, disabled, adapter]
2363
+ );
2266
2364
  const navigateYear = useCallback(
2267
2365
  (direction) => {
2268
2366
  ctx.setViewMonth(adapter.addYears(viewMonth, direction));
@@ -2271,17 +2369,23 @@ function MonthPickerGrid({ classNames, ...props }) {
2271
2369
  );
2272
2370
  const handleMonthSelect = useCallback(
2273
2371
  (monthIndex) => {
2372
+ if (monthDisabledFlags[monthIndex]) return;
2274
2373
  const target = new Date(Date.UTC(currentYear, monthIndex, 1)).toISOString();
2275
2374
  ctx.selectDate(target);
2276
2375
  },
2277
- [currentYear, ctx]
2278
- );
2279
- const months = Array.from({ length: 12 }, (_, i) => ({
2280
- index: i,
2281
- name: getMonthName(i, locale),
2282
- isSelected: valueYear === currentYear && valueMonthZeroBased === i,
2283
- isCurrent: todayYear === currentYear && todayMonth === i
2284
- }));
2376
+ [currentYear, ctx, monthDisabledFlags]
2377
+ );
2378
+ const naturalIndex = valueYear === currentYear && valueMonthZeroBased !== null ? valueMonthZeroBased : adapter.getMonth(viewMonth);
2379
+ const firstEnabled = monthDisabledFlags.findIndex((d) => !d);
2380
+ const initialIndex = monthDisabledFlags[naturalIndex] ? firstEnabled === -1 ? naturalIndex : firstEnabled : naturalIndex;
2381
+ const { gridRef, focusedIndex, handleKeyDown } = useGridState({
2382
+ initialIndex,
2383
+ disabledFlags: monthDisabledFlags,
2384
+ onSelect: handleMonthSelect,
2385
+ onPageUp: () => navigateYear(-1),
2386
+ onPageDown: () => navigateYear(1),
2387
+ onEscape: ctx.close
2388
+ });
2285
2389
  return /* @__PURE__ */ jsxs("div", { className: classNames?.root, ...props, children: [
2286
2390
  /* @__PURE__ */ jsxs("div", { className: classNames?.header, children: [
2287
2391
  /* @__PURE__ */ jsx(
@@ -2306,37 +2410,57 @@ function MonthPickerGrid({ classNames, ...props }) {
2306
2410
  }
2307
2411
  )
2308
2412
  ] }),
2309
- /* @__PURE__ */ jsx("div", { role: "grid", "aria-label": `${currentYear} months`, className: classNames?.grid, children: Array.from({ length: 4 }, (_, rowIndex) => /* @__PURE__ */ jsx(
2413
+ /* @__PURE__ */ jsx(
2310
2414
  "div",
2311
2415
  {
2312
- role: "row",
2313
- className: classNames?.gridRow,
2314
- style: { display: "grid", gridTemplateColumns: "repeat(3, 1fr)" },
2315
- children: months.slice(rowIndex * 3, rowIndex * 3 + 3).map((m) => {
2316
- const monthClass = [
2317
- classNames?.month,
2318
- m.isSelected && classNames?.monthSelected,
2319
- m.isCurrent && classNames?.monthCurrent
2320
- ].filter(Boolean).join(" ") || void 0;
2321
- return /* @__PURE__ */ jsx(
2322
- "button",
2323
- {
2324
- type: "button",
2325
- role: "gridcell",
2326
- "aria-selected": m.isSelected || void 0,
2327
- "aria-current": m.isCurrent ? "date" : void 0,
2328
- "data-selected": m.isSelected || void 0,
2329
- "data-current": m.isCurrent || void 0,
2330
- className: monthClass,
2331
- onClick: () => handleMonthSelect(m.index),
2332
- children: m.name
2333
- },
2334
- m.index
2335
- );
2336
- })
2337
- },
2338
- rowIndex
2339
- )) })
2416
+ ref: gridRef,
2417
+ role: "grid",
2418
+ "aria-label": `${currentYear} months`,
2419
+ className: classNames?.grid,
2420
+ onKeyDown: handleKeyDown,
2421
+ children: Array.from({ length: 4 }, (_, rowIndex) => /* @__PURE__ */ jsx(
2422
+ "div",
2423
+ {
2424
+ role: "row",
2425
+ className: classNames?.gridRow,
2426
+ style: { display: "grid", gridTemplateColumns: "repeat(3, 1fr)" },
2427
+ children: Array.from({ length: 3 }, (_2, col) => {
2428
+ const i = rowIndex * 3 + col;
2429
+ const isSelected = valueYear === currentYear && valueMonthZeroBased === i;
2430
+ const isCurrent = todayYear === currentYear && todayMonth === i;
2431
+ const isFocused = i === focusedIndex;
2432
+ const isDisabled = monthDisabledFlags[i] ?? false;
2433
+ const cls = [
2434
+ classNames?.month,
2435
+ isSelected && classNames?.monthSelected,
2436
+ isCurrent && classNames?.monthCurrent,
2437
+ isDisabled && classNames?.monthDisabled
2438
+ ].filter(Boolean).join(" ") || void 0;
2439
+ return /* @__PURE__ */ jsx(
2440
+ "button",
2441
+ {
2442
+ type: "button",
2443
+ role: "gridcell",
2444
+ tabIndex: isFocused ? 0 : -1,
2445
+ disabled: isDisabled,
2446
+ "aria-selected": isSelected || void 0,
2447
+ "aria-disabled": isDisabled || void 0,
2448
+ "aria-current": isCurrent ? "date" : void 0,
2449
+ "data-selected": isSelected || void 0,
2450
+ "data-current": isCurrent || void 0,
2451
+ "data-focused": isFocused || void 0,
2452
+ className: cls,
2453
+ onClick: () => handleMonthSelect(i),
2454
+ children: getMonthName(i, locale)
2455
+ },
2456
+ i
2457
+ );
2458
+ })
2459
+ },
2460
+ rowIndex
2461
+ ))
2462
+ }
2463
+ )
2340
2464
  ] });
2341
2465
  }
2342
2466
 
@@ -2353,7 +2477,7 @@ function YearPickerRoot(props) {
2353
2477
  }
2354
2478
  function YearPickerGrid({ classNames, ...props }) {
2355
2479
  const ctx = useDatePickerContext("YearPicker.Grid");
2356
- const { adapter, viewMonth, value, displayTimezone, labels } = ctx;
2480
+ const { adapter, viewMonth, value, displayTimezone, labels, disabled } = ctx;
2357
2481
  const currentYear = adapter.getYear(viewMonth);
2358
2482
  const decadeStart = currentYear - currentYear % 12;
2359
2483
  const valueYear = useMemo(() => {
@@ -2369,6 +2493,15 @@ function YearPickerGrid({ classNames, ...props }) {
2369
2493
  setToday(adapter.today(displayTimezone));
2370
2494
  }, [adapter, displayTimezone]);
2371
2495
  const todayYear = today !== null ? adapter.getYear(today) : -1;
2496
+ const yearDisabledFlags = useMemo(
2497
+ () => Array.from({ length: 12 }, (_, i) => {
2498
+ const year = decadeStart + i;
2499
+ const yearStart = new Date(Date.UTC(year, 0, 1)).toISOString();
2500
+ const yearEnd = new Date(Date.UTC(year, 11, 31, 23, 59, 59, 999)).toISOString();
2501
+ return isRangeFullyDisabled(yearStart, yearEnd, disabled, adapter);
2502
+ }),
2503
+ [decadeStart, disabled, adapter]
2504
+ );
2372
2505
  const navigateDecade = useCallback(
2373
2506
  (direction) => {
2374
2507
  ctx.setViewMonth(adapter.addYears(viewMonth, direction * 12));
@@ -2376,19 +2509,24 @@ function YearPickerGrid({ classNames, ...props }) {
2376
2509
  [adapter, viewMonth, ctx]
2377
2510
  );
2378
2511
  const handleYearSelect = useCallback(
2379
- (year) => {
2512
+ (indexInDecade) => {
2513
+ if (yearDisabledFlags[indexInDecade]) return;
2514
+ const year = decadeStart + indexInDecade;
2380
2515
  const target = new Date(Date.UTC(year, 0, 1)).toISOString();
2381
2516
  ctx.selectDate(target);
2382
2517
  },
2383
- [ctx]
2384
- );
2385
- const years = Array.from({ length: 12 }, (_, i) => {
2386
- const year = decadeStart + i;
2387
- return {
2388
- value: year,
2389
- isSelected: year === valueYear,
2390
- isCurrent: year === todayYear
2391
- };
2518
+ [ctx, decadeStart, yearDisabledFlags]
2519
+ );
2520
+ const naturalIndex = valueYear !== null && valueYear >= decadeStart && valueYear <= decadeStart + 11 ? valueYear - decadeStart : currentYear - decadeStart;
2521
+ const firstEnabled = yearDisabledFlags.findIndex((d) => !d);
2522
+ const initialIndex = yearDisabledFlags[naturalIndex] ? firstEnabled === -1 ? naturalIndex : firstEnabled : naturalIndex;
2523
+ const { gridRef, focusedIndex, handleKeyDown } = useGridState({
2524
+ initialIndex,
2525
+ disabledFlags: yearDisabledFlags,
2526
+ onSelect: handleYearSelect,
2527
+ onPageUp: () => navigateDecade(-1),
2528
+ onPageDown: () => navigateDecade(1),
2529
+ onEscape: ctx.close
2392
2530
  });
2393
2531
  const rangeLabel = `${decadeStart}\u2013${decadeStart + 11}`;
2394
2532
  return /* @__PURE__ */ jsxs("div", { className: classNames?.root, ...props, children: [
@@ -2415,37 +2553,58 @@ function YearPickerGrid({ classNames, ...props }) {
2415
2553
  }
2416
2554
  )
2417
2555
  ] }),
2418
- /* @__PURE__ */ jsx("div", { role: "grid", "aria-label": rangeLabel, className: classNames?.grid, children: Array.from({ length: 4 }, (_, rowIndex) => /* @__PURE__ */ jsx(
2556
+ /* @__PURE__ */ jsx(
2419
2557
  "div",
2420
2558
  {
2421
- role: "row",
2422
- className: classNames?.gridRow,
2423
- style: { display: "grid", gridTemplateColumns: "repeat(3, 1fr)" },
2424
- children: years.slice(rowIndex * 3, rowIndex * 3 + 3).map((y) => {
2425
- const yearClass = [
2426
- classNames?.year,
2427
- y.isSelected && classNames?.yearSelected,
2428
- y.isCurrent && classNames?.yearCurrent
2429
- ].filter(Boolean).join(" ") || void 0;
2430
- return /* @__PURE__ */ jsx(
2431
- "button",
2432
- {
2433
- type: "button",
2434
- role: "gridcell",
2435
- "aria-selected": y.isSelected || void 0,
2436
- "aria-current": y.isCurrent ? "date" : void 0,
2437
- "data-selected": y.isSelected || void 0,
2438
- "data-current": y.isCurrent || void 0,
2439
- className: yearClass,
2440
- onClick: () => handleYearSelect(y.value),
2441
- children: y.value
2442
- },
2443
- y.value
2444
- );
2445
- })
2446
- },
2447
- rowIndex
2448
- )) })
2559
+ ref: gridRef,
2560
+ role: "grid",
2561
+ "aria-label": rangeLabel,
2562
+ className: classNames?.grid,
2563
+ onKeyDown: handleKeyDown,
2564
+ children: Array.from({ length: 4 }, (_, rowIndex) => /* @__PURE__ */ jsx(
2565
+ "div",
2566
+ {
2567
+ role: "row",
2568
+ className: classNames?.gridRow,
2569
+ style: { display: "grid", gridTemplateColumns: "repeat(3, 1fr)" },
2570
+ children: Array.from({ length: 3 }, (_2, col) => {
2571
+ const i = rowIndex * 3 + col;
2572
+ const year = decadeStart + i;
2573
+ const isSelected = year === valueYear;
2574
+ const isCurrent = year === todayYear;
2575
+ const isFocused = i === focusedIndex;
2576
+ const isDisabled = yearDisabledFlags[i] ?? false;
2577
+ const cls = [
2578
+ classNames?.year,
2579
+ isSelected && classNames?.yearSelected,
2580
+ isCurrent && classNames?.yearCurrent,
2581
+ isDisabled && classNames?.yearDisabled
2582
+ ].filter(Boolean).join(" ") || void 0;
2583
+ return /* @__PURE__ */ jsx(
2584
+ "button",
2585
+ {
2586
+ type: "button",
2587
+ role: "gridcell",
2588
+ tabIndex: isFocused ? 0 : -1,
2589
+ disabled: isDisabled,
2590
+ "aria-selected": isSelected || void 0,
2591
+ "aria-disabled": isDisabled || void 0,
2592
+ "aria-current": isCurrent ? "date" : void 0,
2593
+ "data-selected": isSelected || void 0,
2594
+ "data-current": isCurrent || void 0,
2595
+ "data-focused": isFocused || void 0,
2596
+ className: cls,
2597
+ onClick: () => handleYearSelect(i),
2598
+ children: year
2599
+ },
2600
+ i
2601
+ );
2602
+ })
2603
+ },
2604
+ rowIndex
2605
+ ))
2606
+ }
2607
+ )
2449
2608
  ] });
2450
2609
  }
2451
2610