@kalyx/react 1.0.0-rc.3 → 1.0.0-rc.5

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
@@ -686,6 +686,86 @@ function DatePickerCalendar({
686
686
  /* @__PURE__ */ jsxRuntime.jsx("div", { role: "status", "aria-live": "polite", "aria-atomic": "true", style: srOnly, children: announcement })
687
687
  ] });
688
688
  }
689
+ function isRangeFullyDisabled(start, end, rules, adapter) {
690
+ for (const rule of rules) {
691
+ if ("before" in rule && adapter.isBefore(end, rule.before)) return true;
692
+ if ("after" in rule && adapter.isAfter(start, rule.after)) return true;
693
+ }
694
+ return false;
695
+ }
696
+ function useGridState(opts) {
697
+ const { initialIndex, disabledFlags, onSelect, onPageUp, onPageDown, onEscape } = opts;
698
+ const gridRef = react.useRef(null);
699
+ const [focusedIndex, setFocusedIndex] = react.useState(initialIndex);
700
+ const handleKeyDown = (e) => {
701
+ let next = null;
702
+ let step = 1;
703
+ switch (e.key) {
704
+ case "ArrowLeft":
705
+ next = Math.max(0, focusedIndex - 1);
706
+ step = -1;
707
+ break;
708
+ case "ArrowRight":
709
+ next = Math.min(11, focusedIndex + 1);
710
+ break;
711
+ case "ArrowUp":
712
+ next = Math.max(0, focusedIndex - 3);
713
+ step = -1;
714
+ break;
715
+ case "ArrowDown":
716
+ next = Math.min(11, focusedIndex + 3);
717
+ break;
718
+ case "Home":
719
+ next = focusedIndex - focusedIndex % 3;
720
+ step = -1;
721
+ break;
722
+ case "End":
723
+ next = focusedIndex - focusedIndex % 3 + 2;
724
+ break;
725
+ case "PageUp":
726
+ e.preventDefault();
727
+ onPageUp();
728
+ return;
729
+ case "PageDown":
730
+ e.preventDefault();
731
+ onPageDown();
732
+ return;
733
+ case "Enter":
734
+ case " ":
735
+ e.preventDefault();
736
+ onSelect(focusedIndex);
737
+ return;
738
+ case "Escape":
739
+ onEscape();
740
+ return;
741
+ default:
742
+ return;
743
+ }
744
+ if (next === null) return;
745
+ e.preventDefault();
746
+ if (disabledFlags) {
747
+ let attempts = 0;
748
+ while (next >= 0 && next < 12 && disabledFlags[next] && attempts < 12) {
749
+ next += step;
750
+ attempts++;
751
+ }
752
+ if (next < 0 || next >= 12 || disabledFlags[next]) return;
753
+ }
754
+ if (next !== focusedIndex) setFocusedIndex(next);
755
+ };
756
+ react.useEffect(() => {
757
+ if (!disabledFlags || !disabledFlags[focusedIndex]) return;
758
+ const firstEnabled = disabledFlags.findIndex((d) => !d);
759
+ if (firstEnabled !== -1 && firstEnabled !== focusedIndex) {
760
+ setFocusedIndex(firstEnabled);
761
+ }
762
+ }, [disabledFlags, focusedIndex]);
763
+ react.useEffect(() => {
764
+ const btn = gridRef.current?.querySelector('[data-focused="true"]');
765
+ btn?.focus({ preventScroll: true });
766
+ }, [focusedIndex]);
767
+ return { gridRef, focusedIndex, handleKeyDown };
768
+ }
689
769
  function DatePickerMonthGrid({
690
770
  classNames,
691
771
  onSelect,
@@ -693,15 +773,18 @@ function DatePickerMonthGrid({
693
773
  ...props
694
774
  }) {
695
775
  const ctx = useDatePickerContext("DatePicker.MonthGrid");
696
- const { adapter, viewMonth, locale } = ctx;
776
+ const { adapter, viewMonth, locale, displayTimezone } = ctx;
697
777
  const currentYear = adapter.getYear(viewMonth);
698
778
  const currentMonth = adapter.getMonth(viewMonth);
699
- const todayMonth = adapter.getMonth(adapter.today());
700
- const todayYear = adapter.getYear(adapter.today());
779
+ const [today, setToday] = react.useState(null);
780
+ react.useEffect(() => {
781
+ setToday(adapter.today(displayTimezone));
782
+ }, [adapter, displayTimezone]);
783
+ const todayMonth = today !== null ? adapter.getMonth(today) : -1;
784
+ const todayYear = today !== null ? adapter.getYear(today) : -1;
701
785
  const navigateYear = react.useCallback(
702
786
  (direction) => {
703
- const newDate = adapter.addYears(viewMonth, direction);
704
- ctx.setViewMonth(newDate);
787
+ ctx.setViewMonth(adapter.addYears(viewMonth, direction));
705
788
  },
706
789
  [adapter, viewMonth, ctx]
707
790
  );
@@ -714,12 +797,13 @@ function DatePickerMonthGrid({
714
797
  },
715
798
  [currentYear, ctx, onSelect]
716
799
  );
717
- const months = Array.from({ length: 12 }, (_, i) => ({
718
- index: i,
719
- name: core.getMonthName(i, locale),
720
- isSelected: i === currentMonth,
721
- isCurrent: i === todayMonth && currentYear === todayYear
722
- }));
800
+ const { gridRef, focusedIndex, handleKeyDown } = useGridState({
801
+ initialIndex: currentMonth,
802
+ onSelect: handleMonthSelect,
803
+ onPageUp: () => navigateYear(-1),
804
+ onPageDown: () => navigateYear(1),
805
+ onEscape: ctx.close
806
+ });
723
807
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.root, ...props, children: [
724
808
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.header, children: [
725
809
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -747,30 +831,37 @@ function DatePickerMonthGrid({
747
831
  /* @__PURE__ */ jsxRuntime.jsx(
748
832
  "div",
749
833
  {
834
+ ref: gridRef,
750
835
  role: "grid",
751
836
  "aria-label": `${currentYear} months`,
752
837
  className: classNames?.grid,
753
838
  style: { display: "grid", gridTemplateColumns: "repeat(3, 1fr)" },
754
- children: months.map((m) => {
755
- const monthClass = [
839
+ onKeyDown: handleKeyDown,
840
+ children: Array.from({ length: 12 }, (_, i) => {
841
+ const isSelected = i === currentMonth;
842
+ const isCurrent = i === todayMonth && currentYear === todayYear;
843
+ const isFocused = i === focusedIndex;
844
+ const cls = [
756
845
  classNames?.month,
757
- m.isSelected && classNames?.monthSelected,
758
- m.isCurrent && classNames?.monthCurrent
846
+ isSelected && classNames?.monthSelected,
847
+ isCurrent && classNames?.monthCurrent
759
848
  ].filter(Boolean).join(" ") || void 0;
760
849
  return /* @__PURE__ */ jsxRuntime.jsx(
761
850
  "button",
762
851
  {
763
852
  type: "button",
764
853
  role: "gridcell",
765
- "aria-selected": m.isSelected || void 0,
766
- "aria-current": m.isCurrent ? "date" : void 0,
767
- "data-selected": m.isSelected || void 0,
768
- "data-current": m.isCurrent || void 0,
769
- className: monthClass,
770
- onClick: () => handleMonthSelect(m.index),
771
- children: m.name
854
+ tabIndex: isFocused ? 0 : -1,
855
+ "aria-selected": isSelected || void 0,
856
+ "aria-current": isCurrent ? "date" : void 0,
857
+ "data-selected": isSelected || void 0,
858
+ "data-current": isCurrent || void 0,
859
+ "data-focused": isFocused || void 0,
860
+ className: cls,
861
+ onClick: () => handleMonthSelect(i),
862
+ children: core.getMonthName(i, locale)
772
863
  },
773
- m.index
864
+ i
774
865
  );
775
866
  })
776
867
  }
@@ -779,38 +870,38 @@ function DatePickerMonthGrid({
779
870
  }
780
871
  function DatePickerYearGrid({ classNames, onSelect, ...props }) {
781
872
  const ctx = useDatePickerContext("DatePicker.YearGrid");
782
- const { adapter, viewMonth } = ctx;
873
+ const { adapter, viewMonth, displayTimezone } = ctx;
783
874
  const currentYear = adapter.getYear(viewMonth);
784
- const todayYear = adapter.getYear(adapter.today());
875
+ const [today, setToday] = react.useState(null);
876
+ react.useEffect(() => {
877
+ setToday(adapter.today(displayTimezone));
878
+ }, [adapter, displayTimezone]);
879
+ const todayYear = today !== null ? adapter.getYear(today) : -1;
785
880
  const decadeStart = currentYear - currentYear % 12;
786
881
  const navigateDecade = react.useCallback(
787
882
  (direction) => {
788
- const newDate = adapter.addYears(viewMonth, direction * 12);
789
- ctx.setViewMonth(newDate);
883
+ ctx.setViewMonth(adapter.addYears(viewMonth, direction * 12));
790
884
  },
791
885
  [adapter, viewMonth, ctx]
792
886
  );
793
887
  const handleYearSelect = react.useCallback(
794
- (year) => {
888
+ (indexInDecade) => {
889
+ const year = decadeStart + indexInDecade;
795
890
  const currentMonth = adapter.getMonth(viewMonth);
796
891
  const target = new Date(Date.UTC(year, currentMonth, 1)).toISOString();
797
892
  ctx.setViewMonth(target);
798
893
  ctx.setFocusedDate(target);
799
894
  onSelect?.();
800
895
  },
801
- [adapter, viewMonth, ctx, onSelect]
802
- );
803
- const years = react.useMemo(
804
- () => Array.from({ length: 12 }, (_, i) => {
805
- const year = decadeStart + i;
806
- return {
807
- value: year,
808
- isSelected: year === currentYear,
809
- isCurrent: year === todayYear
810
- };
811
- }),
812
- [decadeStart, currentYear, todayYear]
813
- );
896
+ [adapter, viewMonth, ctx, onSelect, decadeStart]
897
+ );
898
+ const { gridRef, focusedIndex, handleKeyDown } = useGridState({
899
+ initialIndex: currentYear - decadeStart,
900
+ onSelect: handleYearSelect,
901
+ onPageUp: () => navigateDecade(-1),
902
+ onPageDown: () => navigateDecade(1),
903
+ onEscape: ctx.close
904
+ });
814
905
  const rangeLabel = `${decadeStart}\u2013${decadeStart + 11}`;
815
906
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.root, ...props, children: [
816
907
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.header, children: [
@@ -839,30 +930,38 @@ function DatePickerYearGrid({ classNames, onSelect, ...props }) {
839
930
  /* @__PURE__ */ jsxRuntime.jsx(
840
931
  "div",
841
932
  {
933
+ ref: gridRef,
842
934
  role: "grid",
843
935
  "aria-label": rangeLabel,
844
936
  className: classNames?.grid,
845
937
  style: { display: "grid", gridTemplateColumns: "repeat(3, 1fr)" },
846
- children: years.map((y) => {
847
- const yearClass = [
938
+ onKeyDown: handleKeyDown,
939
+ children: Array.from({ length: 12 }, (_, i) => {
940
+ const year = decadeStart + i;
941
+ const isSelected = year === currentYear;
942
+ const isCurrent = year === todayYear;
943
+ const isFocused = i === focusedIndex;
944
+ const cls = [
848
945
  classNames?.year,
849
- y.isSelected && classNames?.yearSelected,
850
- y.isCurrent && classNames?.yearCurrent
946
+ isSelected && classNames?.yearSelected,
947
+ isCurrent && classNames?.yearCurrent
851
948
  ].filter(Boolean).join(" ") || void 0;
852
949
  return /* @__PURE__ */ jsxRuntime.jsx(
853
950
  "button",
854
951
  {
855
952
  type: "button",
856
953
  role: "gridcell",
857
- "aria-selected": y.isSelected || void 0,
858
- "aria-current": y.isCurrent ? "date" : void 0,
859
- "data-selected": y.isSelected || void 0,
860
- "data-current": y.isCurrent || void 0,
861
- className: yearClass,
862
- onClick: () => handleYearSelect(y.value),
863
- children: y.value
954
+ tabIndex: isFocused ? 0 : -1,
955
+ "aria-selected": isSelected || void 0,
956
+ "aria-current": isCurrent ? "date" : void 0,
957
+ "data-selected": isSelected || void 0,
958
+ "data-current": isCurrent || void 0,
959
+ "data-focused": isFocused || void 0,
960
+ className: cls,
961
+ onClick: () => handleYearSelect(i),
962
+ children: year
864
963
  },
865
- y.value
964
+ i
866
965
  );
867
966
  })
868
967
  }
@@ -935,8 +1034,7 @@ function DatePickerPreset({
935
1034
  "button",
936
1035
  {
937
1036
  type: "button",
938
- role: "option",
939
- "aria-selected": isActive,
1037
+ "aria-pressed": isActive,
940
1038
  "data-active": isActive || void 0,
941
1039
  disabled: ctx.isDisabled,
942
1040
  onClick: handleClick,
@@ -1428,7 +1526,6 @@ function RangePickerCalendar({
1428
1526
  ref: gridRef,
1429
1527
  role: "grid",
1430
1528
  "aria-label": title,
1431
- "aria-multiselectable": "true",
1432
1529
  "aria-rowcount": weeks.length + 1,
1433
1530
  "aria-colcount": 7,
1434
1531
  className: classNames?.grid,
@@ -1592,8 +1689,7 @@ function RangePickerPreset({
1592
1689
  "button",
1593
1690
  {
1594
1691
  type: "button",
1595
- role: "option",
1596
- "aria-selected": isActive,
1692
+ "aria-pressed": isActive,
1597
1693
  "data-active": isActive || void 0,
1598
1694
  disabled: ctx.isDisabled,
1599
1695
  onClick: handleClick,
@@ -1897,29 +1993,70 @@ function TimePickerMinuteList({ classNames, ...props }) {
1897
1993
  }
1898
1994
  function TimePickerAmPmToggle({ classNames, ...props }) {
1899
1995
  const ctx = useTimePickerContext("TimePicker.AmPmToggle");
1900
- if (ctx.format !== "12h") return null;
1901
- const { period, hours12 } = core.to12Hour(ctx.currentTime.hours);
1996
+ const amRef = react.useRef(null);
1997
+ const pmRef = react.useRef(null);
1902
1998
  const setPeriod = react.useCallback(
1903
1999
  (newPeriod) => {
1904
2000
  if (ctx.isDisabled || ctx.isReadOnly) return;
2001
+ const { hours12 } = core.to12Hour(ctx.currentTime.hours);
1905
2002
  const newHours24 = core.to24Hour(hours12, newPeriod);
1906
2003
  ctx.setTime({ hours: newHours24 });
1907
2004
  },
1908
- [hours12, ctx]
2005
+ [ctx]
1909
2006
  );
2007
+ if (ctx.format !== "12h") return null;
2008
+ const { period } = core.to12Hour(ctx.currentTime.hours);
2009
+ const focusOther = (target) => {
2010
+ (target === "AM" ? amRef : pmRef).current?.focus();
2011
+ };
2012
+ const handleKeyDown = (e, target) => {
2013
+ switch (e.key) {
2014
+ case "ArrowRight":
2015
+ case "ArrowDown":
2016
+ case "ArrowLeft":
2017
+ case "ArrowUp": {
2018
+ e.preventDefault();
2019
+ const next = target === "AM" ? "PM" : "AM";
2020
+ setPeriod(next);
2021
+ focusOther(next);
2022
+ break;
2023
+ }
2024
+ case "Home": {
2025
+ e.preventDefault();
2026
+ setPeriod("AM");
2027
+ focusOther("AM");
2028
+ break;
2029
+ }
2030
+ case "End": {
2031
+ e.preventDefault();
2032
+ setPeriod("PM");
2033
+ focusOther("PM");
2034
+ break;
2035
+ }
2036
+ case " ":
2037
+ case "Enter": {
2038
+ e.preventDefault();
2039
+ setPeriod(target);
2040
+ break;
2041
+ }
2042
+ }
2043
+ };
1910
2044
  const renderButton = (target) => {
1911
2045
  const isSelected = period === target;
1912
2046
  const optionClass = [classNames?.option, isSelected && classNames?.optionSelected].filter(Boolean).join(" ") || void 0;
1913
2047
  return /* @__PURE__ */ jsxRuntime.jsx(
1914
2048
  "button",
1915
2049
  {
2050
+ ref: target === "AM" ? amRef : pmRef,
1916
2051
  type: "button",
1917
2052
  role: "radio",
1918
2053
  "aria-checked": isSelected,
2054
+ tabIndex: isSelected ? 0 : -1,
1919
2055
  "data-selected": isSelected || void 0,
1920
2056
  disabled: ctx.isDisabled,
1921
2057
  className: optionClass,
1922
2058
  onClick: () => setPeriod(target),
2059
+ onKeyDown: (e) => handleKeyDown(e, target),
1923
2060
  children: target
1924
2061
  }
1925
2062
  );
@@ -2201,7 +2338,7 @@ function MonthPickerRoot(props) {
2201
2338
  }
2202
2339
  function MonthPickerGrid({ classNames, ...props }) {
2203
2340
  const ctx = useDatePickerContext("MonthPicker.Grid");
2204
- const { adapter, viewMonth, locale, value, displayTimezone, labels } = ctx;
2341
+ const { adapter, viewMonth, locale, value, displayTimezone, labels, disabled } = ctx;
2205
2342
  const currentYear = adapter.getYear(viewMonth);
2206
2343
  const [valueYear, valueMonthZeroBased] = react.useMemo(() => {
2207
2344
  if (!value) return [null, null];
@@ -2212,9 +2349,19 @@ function MonthPickerGrid({ classNames, ...props }) {
2212
2349
  return [null, null];
2213
2350
  }
2214
2351
  }, [value, adapter, displayTimezone]);
2215
- const today = adapter.today(displayTimezone);
2216
- const todayYear = adapter.getYear(today);
2217
- const todayMonth = adapter.getMonth(today);
2352
+ const [today, setToday] = react.useState(null);
2353
+ react.useEffect(() => {
2354
+ setToday(adapter.today(displayTimezone));
2355
+ }, [adapter, displayTimezone]);
2356
+ const todayYear = today !== null ? adapter.getYear(today) : -1;
2357
+ const todayMonth = today !== null ? adapter.getMonth(today) : -1;
2358
+ const monthDisabledFlags = react.useMemo(
2359
+ () => Array.from({ length: 12 }, (_, i) => {
2360
+ const monthStart = new Date(Date.UTC(currentYear, i, 1)).toISOString();
2361
+ return isRangeFullyDisabled(monthStart, adapter.endOfMonth(monthStart), disabled, adapter);
2362
+ }),
2363
+ [currentYear, disabled, adapter]
2364
+ );
2218
2365
  const navigateYear = react.useCallback(
2219
2366
  (direction) => {
2220
2367
  ctx.setViewMonth(adapter.addYears(viewMonth, direction));
@@ -2223,17 +2370,23 @@ function MonthPickerGrid({ classNames, ...props }) {
2223
2370
  );
2224
2371
  const handleMonthSelect = react.useCallback(
2225
2372
  (monthIndex) => {
2373
+ if (monthDisabledFlags[monthIndex]) return;
2226
2374
  const target = new Date(Date.UTC(currentYear, monthIndex, 1)).toISOString();
2227
2375
  ctx.selectDate(target);
2228
2376
  },
2229
- [currentYear, ctx]
2230
- );
2231
- const months = Array.from({ length: 12 }, (_, i) => ({
2232
- index: i,
2233
- name: core.getMonthName(i, locale),
2234
- isSelected: valueYear === currentYear && valueMonthZeroBased === i,
2235
- isCurrent: todayYear === currentYear && todayMonth === i
2236
- }));
2377
+ [currentYear, ctx, monthDisabledFlags]
2378
+ );
2379
+ const naturalIndex = valueYear === currentYear && valueMonthZeroBased !== null ? valueMonthZeroBased : adapter.getMonth(viewMonth);
2380
+ const firstEnabled = monthDisabledFlags.findIndex((d) => !d);
2381
+ const initialIndex = monthDisabledFlags[naturalIndex] ? firstEnabled === -1 ? naturalIndex : firstEnabled : naturalIndex;
2382
+ const { gridRef, focusedIndex, handleKeyDown } = useGridState({
2383
+ initialIndex,
2384
+ disabledFlags: monthDisabledFlags,
2385
+ onSelect: handleMonthSelect,
2386
+ onPageUp: () => navigateYear(-1),
2387
+ onPageDown: () => navigateYear(1),
2388
+ onEscape: ctx.close
2389
+ });
2237
2390
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.root, ...props, children: [
2238
2391
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.header, children: [
2239
2392
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -2258,37 +2411,57 @@ function MonthPickerGrid({ classNames, ...props }) {
2258
2411
  }
2259
2412
  )
2260
2413
  ] }),
2261
- /* @__PURE__ */ jsxRuntime.jsx("div", { role: "grid", "aria-label": `${currentYear} months`, className: classNames?.grid, children: Array.from({ length: 4 }, (_, rowIndex) => /* @__PURE__ */ jsxRuntime.jsx(
2414
+ /* @__PURE__ */ jsxRuntime.jsx(
2262
2415
  "div",
2263
2416
  {
2264
- role: "row",
2265
- className: classNames?.gridRow,
2266
- style: { display: "grid", gridTemplateColumns: "repeat(3, 1fr)" },
2267
- children: months.slice(rowIndex * 3, rowIndex * 3 + 3).map((m) => {
2268
- const monthClass = [
2269
- classNames?.month,
2270
- m.isSelected && classNames?.monthSelected,
2271
- m.isCurrent && classNames?.monthCurrent
2272
- ].filter(Boolean).join(" ") || void 0;
2273
- return /* @__PURE__ */ jsxRuntime.jsx(
2274
- "button",
2275
- {
2276
- type: "button",
2277
- role: "gridcell",
2278
- "aria-selected": m.isSelected || void 0,
2279
- "aria-current": m.isCurrent ? "date" : void 0,
2280
- "data-selected": m.isSelected || void 0,
2281
- "data-current": m.isCurrent || void 0,
2282
- className: monthClass,
2283
- onClick: () => handleMonthSelect(m.index),
2284
- children: m.name
2285
- },
2286
- m.index
2287
- );
2288
- })
2289
- },
2290
- rowIndex
2291
- )) })
2417
+ ref: gridRef,
2418
+ role: "grid",
2419
+ "aria-label": `${currentYear} months`,
2420
+ className: classNames?.grid,
2421
+ onKeyDown: handleKeyDown,
2422
+ children: Array.from({ length: 4 }, (_, rowIndex) => /* @__PURE__ */ jsxRuntime.jsx(
2423
+ "div",
2424
+ {
2425
+ role: "row",
2426
+ className: classNames?.gridRow,
2427
+ style: { display: "grid", gridTemplateColumns: "repeat(3, 1fr)" },
2428
+ children: Array.from({ length: 3 }, (_2, col) => {
2429
+ const i = rowIndex * 3 + col;
2430
+ const isSelected = valueYear === currentYear && valueMonthZeroBased === i;
2431
+ const isCurrent = todayYear === currentYear && todayMonth === i;
2432
+ const isFocused = i === focusedIndex;
2433
+ const isDisabled = monthDisabledFlags[i] ?? false;
2434
+ const cls = [
2435
+ classNames?.month,
2436
+ isSelected && classNames?.monthSelected,
2437
+ isCurrent && classNames?.monthCurrent,
2438
+ isDisabled && classNames?.monthDisabled
2439
+ ].filter(Boolean).join(" ") || void 0;
2440
+ return /* @__PURE__ */ jsxRuntime.jsx(
2441
+ "button",
2442
+ {
2443
+ type: "button",
2444
+ role: "gridcell",
2445
+ tabIndex: isFocused ? 0 : -1,
2446
+ disabled: isDisabled,
2447
+ "aria-selected": isSelected || void 0,
2448
+ "aria-disabled": isDisabled || void 0,
2449
+ "aria-current": isCurrent ? "date" : void 0,
2450
+ "data-selected": isSelected || void 0,
2451
+ "data-current": isCurrent || void 0,
2452
+ "data-focused": isFocused || void 0,
2453
+ className: cls,
2454
+ onClick: () => handleMonthSelect(i),
2455
+ children: core.getMonthName(i, locale)
2456
+ },
2457
+ i
2458
+ );
2459
+ })
2460
+ },
2461
+ rowIndex
2462
+ ))
2463
+ }
2464
+ )
2292
2465
  ] });
2293
2466
  }
2294
2467
 
@@ -2305,7 +2478,7 @@ function YearPickerRoot(props) {
2305
2478
  }
2306
2479
  function YearPickerGrid({ classNames, ...props }) {
2307
2480
  const ctx = useDatePickerContext("YearPicker.Grid");
2308
- const { adapter, viewMonth, value, displayTimezone, labels } = ctx;
2481
+ const { adapter, viewMonth, value, displayTimezone, labels, disabled } = ctx;
2309
2482
  const currentYear = adapter.getYear(viewMonth);
2310
2483
  const decadeStart = currentYear - currentYear % 12;
2311
2484
  const valueYear = react.useMemo(() => {
@@ -2316,7 +2489,20 @@ function YearPickerGrid({ classNames, ...props }) {
2316
2489
  return null;
2317
2490
  }
2318
2491
  }, [value, adapter, displayTimezone]);
2319
- const todayYear = adapter.getYear(adapter.today(displayTimezone));
2492
+ const [today, setToday] = react.useState(null);
2493
+ react.useEffect(() => {
2494
+ setToday(adapter.today(displayTimezone));
2495
+ }, [adapter, displayTimezone]);
2496
+ const todayYear = today !== null ? adapter.getYear(today) : -1;
2497
+ const yearDisabledFlags = react.useMemo(
2498
+ () => Array.from({ length: 12 }, (_, i) => {
2499
+ const year = decadeStart + i;
2500
+ const yearStart = new Date(Date.UTC(year, 0, 1)).toISOString();
2501
+ const yearEnd = new Date(Date.UTC(year, 11, 31, 23, 59, 59, 999)).toISOString();
2502
+ return isRangeFullyDisabled(yearStart, yearEnd, disabled, adapter);
2503
+ }),
2504
+ [decadeStart, disabled, adapter]
2505
+ );
2320
2506
  const navigateDecade = react.useCallback(
2321
2507
  (direction) => {
2322
2508
  ctx.setViewMonth(adapter.addYears(viewMonth, direction * 12));
@@ -2324,19 +2510,24 @@ function YearPickerGrid({ classNames, ...props }) {
2324
2510
  [adapter, viewMonth, ctx]
2325
2511
  );
2326
2512
  const handleYearSelect = react.useCallback(
2327
- (year) => {
2513
+ (indexInDecade) => {
2514
+ if (yearDisabledFlags[indexInDecade]) return;
2515
+ const year = decadeStart + indexInDecade;
2328
2516
  const target = new Date(Date.UTC(year, 0, 1)).toISOString();
2329
2517
  ctx.selectDate(target);
2330
2518
  },
2331
- [ctx]
2332
- );
2333
- const years = Array.from({ length: 12 }, (_, i) => {
2334
- const year = decadeStart + i;
2335
- return {
2336
- value: year,
2337
- isSelected: year === valueYear,
2338
- isCurrent: year === todayYear
2339
- };
2519
+ [ctx, decadeStart, yearDisabledFlags]
2520
+ );
2521
+ const naturalIndex = valueYear !== null && valueYear >= decadeStart && valueYear <= decadeStart + 11 ? valueYear - decadeStart : currentYear - decadeStart;
2522
+ const firstEnabled = yearDisabledFlags.findIndex((d) => !d);
2523
+ const initialIndex = yearDisabledFlags[naturalIndex] ? firstEnabled === -1 ? naturalIndex : firstEnabled : naturalIndex;
2524
+ const { gridRef, focusedIndex, handleKeyDown } = useGridState({
2525
+ initialIndex,
2526
+ disabledFlags: yearDisabledFlags,
2527
+ onSelect: handleYearSelect,
2528
+ onPageUp: () => navigateDecade(-1),
2529
+ onPageDown: () => navigateDecade(1),
2530
+ onEscape: ctx.close
2340
2531
  });
2341
2532
  const rangeLabel = `${decadeStart}\u2013${decadeStart + 11}`;
2342
2533
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.root, ...props, children: [
@@ -2363,37 +2554,58 @@ function YearPickerGrid({ classNames, ...props }) {
2363
2554
  }
2364
2555
  )
2365
2556
  ] }),
2366
- /* @__PURE__ */ jsxRuntime.jsx("div", { role: "grid", "aria-label": rangeLabel, className: classNames?.grid, children: Array.from({ length: 4 }, (_, rowIndex) => /* @__PURE__ */ jsxRuntime.jsx(
2557
+ /* @__PURE__ */ jsxRuntime.jsx(
2367
2558
  "div",
2368
2559
  {
2369
- role: "row",
2370
- className: classNames?.gridRow,
2371
- style: { display: "grid", gridTemplateColumns: "repeat(3, 1fr)" },
2372
- children: years.slice(rowIndex * 3, rowIndex * 3 + 3).map((y) => {
2373
- const yearClass = [
2374
- classNames?.year,
2375
- y.isSelected && classNames?.yearSelected,
2376
- y.isCurrent && classNames?.yearCurrent
2377
- ].filter(Boolean).join(" ") || void 0;
2378
- return /* @__PURE__ */ jsxRuntime.jsx(
2379
- "button",
2380
- {
2381
- type: "button",
2382
- role: "gridcell",
2383
- "aria-selected": y.isSelected || void 0,
2384
- "aria-current": y.isCurrent ? "date" : void 0,
2385
- "data-selected": y.isSelected || void 0,
2386
- "data-current": y.isCurrent || void 0,
2387
- className: yearClass,
2388
- onClick: () => handleYearSelect(y.value),
2389
- children: y.value
2390
- },
2391
- y.value
2392
- );
2393
- })
2394
- },
2395
- rowIndex
2396
- )) })
2560
+ ref: gridRef,
2561
+ role: "grid",
2562
+ "aria-label": rangeLabel,
2563
+ className: classNames?.grid,
2564
+ onKeyDown: handleKeyDown,
2565
+ children: Array.from({ length: 4 }, (_, rowIndex) => /* @__PURE__ */ jsxRuntime.jsx(
2566
+ "div",
2567
+ {
2568
+ role: "row",
2569
+ className: classNames?.gridRow,
2570
+ style: { display: "grid", gridTemplateColumns: "repeat(3, 1fr)" },
2571
+ children: Array.from({ length: 3 }, (_2, col) => {
2572
+ const i = rowIndex * 3 + col;
2573
+ const year = decadeStart + i;
2574
+ const isSelected = year === valueYear;
2575
+ const isCurrent = year === todayYear;
2576
+ const isFocused = i === focusedIndex;
2577
+ const isDisabled = yearDisabledFlags[i] ?? false;
2578
+ const cls = [
2579
+ classNames?.year,
2580
+ isSelected && classNames?.yearSelected,
2581
+ isCurrent && classNames?.yearCurrent,
2582
+ isDisabled && classNames?.yearDisabled
2583
+ ].filter(Boolean).join(" ") || void 0;
2584
+ return /* @__PURE__ */ jsxRuntime.jsx(
2585
+ "button",
2586
+ {
2587
+ type: "button",
2588
+ role: "gridcell",
2589
+ tabIndex: isFocused ? 0 : -1,
2590
+ disabled: isDisabled,
2591
+ "aria-selected": isSelected || void 0,
2592
+ "aria-disabled": isDisabled || void 0,
2593
+ "aria-current": isCurrent ? "date" : void 0,
2594
+ "data-selected": isSelected || void 0,
2595
+ "data-current": isCurrent || void 0,
2596
+ "data-focused": isFocused || void 0,
2597
+ className: cls,
2598
+ onClick: () => handleYearSelect(i),
2599
+ children: year
2600
+ },
2601
+ i
2602
+ );
2603
+ })
2604
+ },
2605
+ rowIndex
2606
+ ))
2607
+ }
2608
+ )
2397
2609
  ] });
2398
2610
  }
2399
2611