@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/CHANGELOG.md +83 -0
- package/README.md +2 -2
- package/dist/index.cjs +285 -126
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -2
- package/dist/index.d.ts +20 -2
- package/dist/index.js +285 -126
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,88 @@
|
|
|
1
1
|
# @kalyx/react
|
|
2
2
|
|
|
3
|
+
## 1.0.0-rc.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- abc56ac: Security: pin transitive `fast-uri` to `>=3.1.2` and `@babel/plugin-transform-modules-systemjs` to `>=7.29.4` via `pnpm.overrides`.
|
|
8
|
+
|
|
9
|
+
Resolves three Code Scanning alerts on `pnpm-lock.yaml`:
|
|
10
|
+
- `fast-uri@3.1.0` — [GHSA-v39h-62p7-jpjc](https://osv.dev/GHSA-v39h-62p7-jpjc) (CVE-2026-6322), first patched in `3.1.2`.
|
|
11
|
+
- `fast-uri@3.1.0` — [GHSA-q3j6-qgpj-74h6](https://osv.dev/GHSA-q3j6-qgpj-74h6) (CVE-2026-6321), first patched in `3.1.1`.
|
|
12
|
+
- `@babel/plugin-transform-modules-systemjs@7.29.0` — [GHSA-fv7c-fp4j-7gwp](https://osv.dev/GHSA-fv7c-fp4j-7gwp) (CVE-2026-44728), first patched in `7.29.4` on the 7.x line.
|
|
13
|
+
|
|
14
|
+
All three packages are transitive build-time dependencies (ajv → fast-uri, Babel preset-env → systemjs plugin); no public API impact.
|
|
15
|
+
|
|
16
|
+
- Updated dependencies [abc56ac]
|
|
17
|
+
- @kalyx/core@1.0.0-rc.6
|
|
18
|
+
|
|
19
|
+
## 1.0.0-rc.5
|
|
20
|
+
|
|
21
|
+
### Patch Changes
|
|
22
|
+
|
|
23
|
+
- 9f3cf9b: WAI-ARIA grid keyboard navigation for the four 3×4 picker grids
|
|
24
|
+
(`DatePicker.MonthGrid`, `DatePicker.YearGrid`, `MonthPicker.Grid`,
|
|
25
|
+
`YearPicker.Grid`).
|
|
26
|
+
|
|
27
|
+
Before, these grids declared `role="grid"` but had no key handler — keyboard
|
|
28
|
+
users could not select a month or year, in violation of CLAUDE.md §7.
|
|
29
|
+
|
|
30
|
+
Now each grid implements:
|
|
31
|
+
- **Arrow keys** — ±1 column / ±3 rows, clamped to grid bounds.
|
|
32
|
+
- **Home / End** — first / last cell of the current row.
|
|
33
|
+
- **PageUp / PageDown** — previous / next year (or decade for year grids).
|
|
34
|
+
- **Enter / Space** — commit the focused cell (drilldown grids switch view via
|
|
35
|
+
`onSelect`; commit grids close the popover via `ctx.selectDate`).
|
|
36
|
+
- **Roving tabIndex** — only the focused cell has `tabIndex=0`; the
|
|
37
|
+
`data-focused` attribute follows.
|
|
38
|
+
- **Auto-refocus** — DOM focus moves with `focusedIndex` so PageUp/Down lands
|
|
39
|
+
the user back on the same column position. Cells use stable index keys so
|
|
40
|
+
the buttons persist across page nav.
|
|
41
|
+
|
|
42
|
+
Component-level integration tests added per CLAUDE.md §7 across `DatePicker`,
|
|
43
|
+
`RangePicker`, `DateTimePicker`, and `WeekPicker`: leap-year (Feb 29 2024)
|
|
44
|
+
click commit, `before`/`after` rule click block, `dayOfWeek` rule click block
|
|
45
|
+
plus visual `aria-disabled`, and keyboard ArrowLeft skip-disabled.
|
|
46
|
+
|
|
47
|
+
**Bundle target raised to 14 KB** — full grid keyboard nav (state + handlers
|
|
48
|
+
- auto-refocus) added ~1.4 KB gzip across the four grids. Measured 12.85 KB
|
|
49
|
+
ESM / 13.64 KB CJS at this point. README, docs, `scripts/check-bundle-size.js`,
|
|
50
|
+
PR template, and CI gate updated to ≤14 KB.
|
|
51
|
+
|
|
52
|
+
**Internal:** new shared `useGridState` hook in
|
|
53
|
+
`packages/react/src/components/_shared/grid-keyboard.ts` (not exported from
|
|
54
|
+
the package public API) consolidates keyboard handling and roving-focus
|
|
55
|
+
state across all four grids.
|
|
56
|
+
|
|
57
|
+
- 9b19df4: `MonthPicker.Grid` and `YearPicker.Grid` now respect `before` / `after`
|
|
58
|
+
disabled rules — months/years that fall entirely outside the allowed range
|
|
59
|
+
are rendered with the `disabled` HTML attribute, `aria-disabled="true"`, the
|
|
60
|
+
new `monthDisabled` / `yearDisabled` className slots, and are skipped during
|
|
61
|
+
keyboard navigation.
|
|
62
|
+
|
|
63
|
+
This was deliberately deferred from PR #46 to keep that bundle under 14 KB;
|
|
64
|
+
it lands now with a 14 → 15 KB ceiling bump.
|
|
65
|
+
|
|
66
|
+
Behavioral details:
|
|
67
|
+
- A month is "fully disabled" only when every day in it is excluded by a
|
|
68
|
+
`before` or `after` rule. `date` and `dayOfWeek` rules can never disable a
|
|
69
|
+
whole month, so they remain a per-day concern.
|
|
70
|
+
- A year follows the same rule against `[Jan 1 00:00:00, Dec 31 23:59:59.999]`.
|
|
71
|
+
- Click and keyboard `Enter` / `Space` on a disabled cell are no-ops.
|
|
72
|
+
- Initial focus and post-PageUp/PageDown focus both re-anchor to the first
|
|
73
|
+
enabled cell when the natural target is itself disabled. (A `disabled`
|
|
74
|
+
HTML button can't receive DOM focus, so without the re-anchor the user
|
|
75
|
+
would silently lose keyboard navigation.)
|
|
76
|
+
|
|
77
|
+
**Internal:** `useGridState` regains its optional `disabledFlags` parameter
|
|
78
|
+
plus a focus re-anchor effect; `isRangeFullyDisabled` is reintroduced as an
|
|
79
|
+
internal helper. Neither is exposed in the package public API.
|
|
80
|
+
|
|
81
|
+
**Bundle target:** raised 14 → 15 KB (measured 13.96 KB ESM / 14.21 KB CJS).
|
|
82
|
+
Same precedent as the 12 → 13 KB and 13 → 14 KB bumps when prior feature
|
|
83
|
+
work landed. Updated `scripts/check-bundle-size.js`, `pr-check.yml`, READMEs,
|
|
84
|
+
CLAUDE.md, PR template, and `check-bundle.md`.
|
|
85
|
+
|
|
3
86
|
## 1.0.0-rc.4
|
|
4
87
|
|
|
5
88
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# @kalyx/react
|
|
2
2
|
|
|
3
|
-
> The headless React DatePicker, finally complete. Zero CSS · SSR-safe · ~
|
|
3
|
+
> The headless React DatePicker, finally complete. Zero CSS · SSR-safe · ~14 KB gzip (≤ 15 KB ceiling).
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@kalyx/react)
|
|
6
|
-
[](https://kalyx-docs.vercel.app/docs/api/react#bundle-size)
|
|
7
7
|
[](https://www.typescriptlang.org/)
|
|
8
8
|
[](https://github.com/jiji-hoon96/kalyx/blob/main/LICENSE)
|
|
9
9
|
|
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,
|
|
@@ -704,8 +784,7 @@ function DatePickerMonthGrid({
|
|
|
704
784
|
const todayYear = today !== null ? adapter.getYear(today) : -1;
|
|
705
785
|
const navigateYear = react.useCallback(
|
|
706
786
|
(direction) => {
|
|
707
|
-
|
|
708
|
-
ctx.setViewMonth(newDate);
|
|
787
|
+
ctx.setViewMonth(adapter.addYears(viewMonth, direction));
|
|
709
788
|
},
|
|
710
789
|
[adapter, viewMonth, ctx]
|
|
711
790
|
);
|
|
@@ -718,12 +797,13 @@ function DatePickerMonthGrid({
|
|
|
718
797
|
},
|
|
719
798
|
[currentYear, ctx, onSelect]
|
|
720
799
|
);
|
|
721
|
-
const
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
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
|
+
});
|
|
727
807
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.root, ...props, children: [
|
|
728
808
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.header, children: [
|
|
729
809
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -751,30 +831,37 @@ function DatePickerMonthGrid({
|
|
|
751
831
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
752
832
|
"div",
|
|
753
833
|
{
|
|
834
|
+
ref: gridRef,
|
|
754
835
|
role: "grid",
|
|
755
836
|
"aria-label": `${currentYear} months`,
|
|
756
837
|
className: classNames?.grid,
|
|
757
838
|
style: { display: "grid", gridTemplateColumns: "repeat(3, 1fr)" },
|
|
758
|
-
|
|
759
|
-
|
|
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 = [
|
|
760
845
|
classNames?.month,
|
|
761
|
-
|
|
762
|
-
|
|
846
|
+
isSelected && classNames?.monthSelected,
|
|
847
|
+
isCurrent && classNames?.monthCurrent
|
|
763
848
|
].filter(Boolean).join(" ") || void 0;
|
|
764
849
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
765
850
|
"button",
|
|
766
851
|
{
|
|
767
852
|
type: "button",
|
|
768
853
|
role: "gridcell",
|
|
769
|
-
|
|
770
|
-
"aria-
|
|
771
|
-
"
|
|
772
|
-
"data-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
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)
|
|
776
863
|
},
|
|
777
|
-
|
|
864
|
+
i
|
|
778
865
|
);
|
|
779
866
|
})
|
|
780
867
|
}
|
|
@@ -793,32 +880,28 @@ function DatePickerYearGrid({ classNames, onSelect, ...props }) {
|
|
|
793
880
|
const decadeStart = currentYear - currentYear % 12;
|
|
794
881
|
const navigateDecade = react.useCallback(
|
|
795
882
|
(direction) => {
|
|
796
|
-
|
|
797
|
-
ctx.setViewMonth(newDate);
|
|
883
|
+
ctx.setViewMonth(adapter.addYears(viewMonth, direction * 12));
|
|
798
884
|
},
|
|
799
885
|
[adapter, viewMonth, ctx]
|
|
800
886
|
);
|
|
801
887
|
const handleYearSelect = react.useCallback(
|
|
802
|
-
(
|
|
888
|
+
(indexInDecade) => {
|
|
889
|
+
const year = decadeStart + indexInDecade;
|
|
803
890
|
const currentMonth = adapter.getMonth(viewMonth);
|
|
804
891
|
const target = new Date(Date.UTC(year, currentMonth, 1)).toISOString();
|
|
805
892
|
ctx.setViewMonth(target);
|
|
806
893
|
ctx.setFocusedDate(target);
|
|
807
894
|
onSelect?.();
|
|
808
895
|
},
|
|
809
|
-
[adapter, viewMonth, ctx, onSelect]
|
|
810
|
-
);
|
|
811
|
-
const
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
};
|
|
819
|
-
}),
|
|
820
|
-
[decadeStart, currentYear, todayYear]
|
|
821
|
-
);
|
|
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
|
+
});
|
|
822
905
|
const rangeLabel = `${decadeStart}\u2013${decadeStart + 11}`;
|
|
823
906
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.root, ...props, children: [
|
|
824
907
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.header, children: [
|
|
@@ -847,30 +930,38 @@ function DatePickerYearGrid({ classNames, onSelect, ...props }) {
|
|
|
847
930
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
848
931
|
"div",
|
|
849
932
|
{
|
|
933
|
+
ref: gridRef,
|
|
850
934
|
role: "grid",
|
|
851
935
|
"aria-label": rangeLabel,
|
|
852
936
|
className: classNames?.grid,
|
|
853
937
|
style: { display: "grid", gridTemplateColumns: "repeat(3, 1fr)" },
|
|
854
|
-
|
|
855
|
-
|
|
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 = [
|
|
856
945
|
classNames?.year,
|
|
857
|
-
|
|
858
|
-
|
|
946
|
+
isSelected && classNames?.yearSelected,
|
|
947
|
+
isCurrent && classNames?.yearCurrent
|
|
859
948
|
].filter(Boolean).join(" ") || void 0;
|
|
860
949
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
861
950
|
"button",
|
|
862
951
|
{
|
|
863
952
|
type: "button",
|
|
864
953
|
role: "gridcell",
|
|
865
|
-
|
|
866
|
-
"aria-
|
|
867
|
-
"
|
|
868
|
-
"data-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
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
|
|
872
963
|
},
|
|
873
|
-
|
|
964
|
+
i
|
|
874
965
|
);
|
|
875
966
|
})
|
|
876
967
|
}
|
|
@@ -2247,7 +2338,7 @@ function MonthPickerRoot(props) {
|
|
|
2247
2338
|
}
|
|
2248
2339
|
function MonthPickerGrid({ classNames, ...props }) {
|
|
2249
2340
|
const ctx = useDatePickerContext("MonthPicker.Grid");
|
|
2250
|
-
const { adapter, viewMonth, locale, value, displayTimezone, labels } = ctx;
|
|
2341
|
+
const { adapter, viewMonth, locale, value, displayTimezone, labels, disabled } = ctx;
|
|
2251
2342
|
const currentYear = adapter.getYear(viewMonth);
|
|
2252
2343
|
const [valueYear, valueMonthZeroBased] = react.useMemo(() => {
|
|
2253
2344
|
if (!value) return [null, null];
|
|
@@ -2264,6 +2355,13 @@ function MonthPickerGrid({ classNames, ...props }) {
|
|
|
2264
2355
|
}, [adapter, displayTimezone]);
|
|
2265
2356
|
const todayYear = today !== null ? adapter.getYear(today) : -1;
|
|
2266
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
|
+
);
|
|
2267
2365
|
const navigateYear = react.useCallback(
|
|
2268
2366
|
(direction) => {
|
|
2269
2367
|
ctx.setViewMonth(adapter.addYears(viewMonth, direction));
|
|
@@ -2272,17 +2370,23 @@ function MonthPickerGrid({ classNames, ...props }) {
|
|
|
2272
2370
|
);
|
|
2273
2371
|
const handleMonthSelect = react.useCallback(
|
|
2274
2372
|
(monthIndex) => {
|
|
2373
|
+
if (monthDisabledFlags[monthIndex]) return;
|
|
2275
2374
|
const target = new Date(Date.UTC(currentYear, monthIndex, 1)).toISOString();
|
|
2276
2375
|
ctx.selectDate(target);
|
|
2277
2376
|
},
|
|
2278
|
-
[currentYear, ctx]
|
|
2279
|
-
);
|
|
2280
|
-
const
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
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
|
+
});
|
|
2286
2390
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.root, ...props, children: [
|
|
2287
2391
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.header, children: [
|
|
2288
2392
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -2307,37 +2411,57 @@ function MonthPickerGrid({ classNames, ...props }) {
|
|
|
2307
2411
|
}
|
|
2308
2412
|
)
|
|
2309
2413
|
] }),
|
|
2310
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2414
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2311
2415
|
"div",
|
|
2312
2416
|
{
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
"
|
|
2324
|
-
{
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
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
|
+
)
|
|
2341
2465
|
] });
|
|
2342
2466
|
}
|
|
2343
2467
|
|
|
@@ -2354,7 +2478,7 @@ function YearPickerRoot(props) {
|
|
|
2354
2478
|
}
|
|
2355
2479
|
function YearPickerGrid({ classNames, ...props }) {
|
|
2356
2480
|
const ctx = useDatePickerContext("YearPicker.Grid");
|
|
2357
|
-
const { adapter, viewMonth, value, displayTimezone, labels } = ctx;
|
|
2481
|
+
const { adapter, viewMonth, value, displayTimezone, labels, disabled } = ctx;
|
|
2358
2482
|
const currentYear = adapter.getYear(viewMonth);
|
|
2359
2483
|
const decadeStart = currentYear - currentYear % 12;
|
|
2360
2484
|
const valueYear = react.useMemo(() => {
|
|
@@ -2370,6 +2494,15 @@ function YearPickerGrid({ classNames, ...props }) {
|
|
|
2370
2494
|
setToday(adapter.today(displayTimezone));
|
|
2371
2495
|
}, [adapter, displayTimezone]);
|
|
2372
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
|
+
);
|
|
2373
2506
|
const navigateDecade = react.useCallback(
|
|
2374
2507
|
(direction) => {
|
|
2375
2508
|
ctx.setViewMonth(adapter.addYears(viewMonth, direction * 12));
|
|
@@ -2377,19 +2510,24 @@ function YearPickerGrid({ classNames, ...props }) {
|
|
|
2377
2510
|
[adapter, viewMonth, ctx]
|
|
2378
2511
|
);
|
|
2379
2512
|
const handleYearSelect = react.useCallback(
|
|
2380
|
-
(
|
|
2513
|
+
(indexInDecade) => {
|
|
2514
|
+
if (yearDisabledFlags[indexInDecade]) return;
|
|
2515
|
+
const year = decadeStart + indexInDecade;
|
|
2381
2516
|
const target = new Date(Date.UTC(year, 0, 1)).toISOString();
|
|
2382
2517
|
ctx.selectDate(target);
|
|
2383
2518
|
},
|
|
2384
|
-
[ctx]
|
|
2385
|
-
);
|
|
2386
|
-
const
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
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
|
|
2393
2531
|
});
|
|
2394
2532
|
const rangeLabel = `${decadeStart}\u2013${decadeStart + 11}`;
|
|
2395
2533
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.root, ...props, children: [
|
|
@@ -2416,37 +2554,58 @@ function YearPickerGrid({ classNames, ...props }) {
|
|
|
2416
2554
|
}
|
|
2417
2555
|
)
|
|
2418
2556
|
] }),
|
|
2419
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2557
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2420
2558
|
"div",
|
|
2421
2559
|
{
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
"
|
|
2433
|
-
{
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
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
|
+
)
|
|
2450
2609
|
] });
|
|
2451
2610
|
}
|
|
2452
2611
|
|