@kalyx/react 0.4.0 → 1.0.0-rc.0
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 +128 -0
- package/dist/index.cjs +395 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +257 -5
- package/dist/index.d.ts +257 -5
- package/dist/index.js +393 -11
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,133 @@
|
|
|
1
1
|
# @kalyx/react
|
|
2
2
|
|
|
3
|
+
## 1.0.0-rc.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- ca7180e: chore: v1.0 milestone — API freeze.
|
|
8
|
+
|
|
9
|
+
Kalyx v1.0 declares the public API stable. This is a milestone release bundling the v0.5 surface additions (MonthPicker, YearPicker, WeekPicker, DatePicker.Presets, `onOpenChange`/`onCalendarNavigate` event callbacks) with an explicit commitment to semantic versioning going forward.
|
|
10
|
+
|
|
11
|
+
### What v1.0 commits to
|
|
12
|
+
|
|
13
|
+
- **Public API surface** — exports from `@kalyx/react` and `@kalyx/core` listed in their `index.ts` files. Any breaking change requires a major bump.
|
|
14
|
+
- **Compositional structure** — Root + subcomponent names (`DatePicker.Input`, `DatePicker.Calendar`, …) are stable. Removal or renaming requires a major bump.
|
|
15
|
+
- **Value semantics** — ISO 8601 UTC strings for single dates, `DateRange` `{start, end}` for ranges. `displayTimezone` behavior (civil-midnight-in-tz for date selection) is stable.
|
|
16
|
+
- **Accessibility contracts** — role/aria-\* attributes emitted by each component are stable.
|
|
17
|
+
|
|
18
|
+
### What v1.0 does NOT freeze
|
|
19
|
+
|
|
20
|
+
- Internal implementation details (non-exported functions, component file layout).
|
|
21
|
+
- CSS class name strings on elements — no classes are applied by default; only when a consumer passes them via `classNames` props.
|
|
22
|
+
- Error message text.
|
|
23
|
+
- Peer dependency version ranges (may expand to cover new React majors).
|
|
24
|
+
|
|
25
|
+
### Breaking changes vs 0.4.x
|
|
26
|
+
|
|
27
|
+
None. v1.0 is API-compatible with 0.4.x — existing code continues to work. The major bump communicates stability commitment, not breakage.
|
|
28
|
+
|
|
29
|
+
### Minor Changes
|
|
30
|
+
|
|
31
|
+
- 3db8444: feat: add `DatePicker.Presets` and `DatePicker.Preset` for single-date quick selection.
|
|
32
|
+
|
|
33
|
+
Mirrors the existing `RangePicker.Presets` API. Pass a predefined `value` key (`today`, `tomorrow`, `yesterday`, `startOfMonth`, `endOfMonth`, `startOfYear`) or a direct ISO via `date`.
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
<DatePicker value={date} onChange={setDate}>
|
|
37
|
+
<DatePicker.Input />
|
|
38
|
+
<DatePicker.Popover>
|
|
39
|
+
<DatePicker.Presets>
|
|
40
|
+
<DatePicker.Preset value="today">Today</DatePicker.Preset>
|
|
41
|
+
<DatePicker.Preset value="tomorrow">Tomorrow</DatePicker.Preset>
|
|
42
|
+
<DatePicker.Preset date="2026-12-25T00:00:00.000Z">
|
|
43
|
+
Christmas
|
|
44
|
+
</DatePicker.Preset>
|
|
45
|
+
</DatePicker.Presets>
|
|
46
|
+
<DatePicker.Calendar />
|
|
47
|
+
</DatePicker.Popover>
|
|
48
|
+
</DatePicker>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
- Active preset is marked `aria-selected="true"` when its resolved date matches the current value (timezone-aware).
|
|
52
|
+
- Clicking a preset commits and closes the popover.
|
|
53
|
+
- `displayTimezone` is honored when resolving "today"-relative presets.
|
|
54
|
+
|
|
55
|
+
- 56e1ce9: feat: add `onOpenChange` and `onCalendarNavigate` callbacks on `DatePicker`, `RangePicker`, and `DateTimePicker` Root components.
|
|
56
|
+
|
|
57
|
+
- `onOpenChange(isOpen: boolean)` fires whenever the popover opens or closes (regardless of trigger — click, keyboard, outside click, selection).
|
|
58
|
+
- `onCalendarNavigate(viewMonth: ISODateString)` fires when the calendar view moves to a different month. The emitted value is the first day of the newly-visible month in UTC.
|
|
59
|
+
|
|
60
|
+
Neither callback fires on initial mount. `TimePicker` does not expose these callbacks since it has no popover or calendar.
|
|
61
|
+
|
|
62
|
+
- 6fc7c59: feat: add `MonthPicker` — a headless month selector.
|
|
63
|
+
|
|
64
|
+
`MonthPicker` stores the selected month as the first day of that month in UTC-ISO form (e.g., `"2026-04-01T00:00:00.000Z"`). It reuses `DatePicker` infrastructure (Input, Trigger, Popover), so the only new primitive is `MonthPicker.Grid`, a 12-month commit grid with year navigation.
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
<MonthPicker value={month} onChange={setMonth}>
|
|
68
|
+
<MonthPicker.Input placeholder="Pick a month" />
|
|
69
|
+
<MonthPicker.Popover>
|
|
70
|
+
<MonthPicker.Grid />
|
|
71
|
+
</MonthPicker.Popover>
|
|
72
|
+
</MonthPicker>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
- Default `displayFormat` is `"yyyy-MM"`.
|
|
76
|
+
- `displayTimezone` is supported (commits map to civil midnight of month-start in the target zone).
|
|
77
|
+
- Month selection highlighting is timezone-aware — the grid reflects the month of the current value even when stored in zone-adjusted UTC form.
|
|
78
|
+
- Primary UX is click-to-select; full `yyyy-MM-dd` typed input still works via the inherited Input behavior.
|
|
79
|
+
|
|
80
|
+
- 6fdf8fe: feat: add `WeekPicker` — a headless week selector.
|
|
81
|
+
|
|
82
|
+
`WeekPicker` stores the selected week as a `DateRange` covering all seven days (based on `weekStartsOn`). Unlike `RangePicker`, a single click on any day selects the entire week containing that day.
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
<WeekPicker value={week} onChange={setWeek} weekStartsOn={1}>
|
|
86
|
+
<WeekPicker.Input part="start" />
|
|
87
|
+
<WeekPicker.Input part="end" />
|
|
88
|
+
<WeekPicker.Popover>
|
|
89
|
+
<WeekPicker.Calendar />
|
|
90
|
+
</WeekPicker.Popover>
|
|
91
|
+
</WeekPicker>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
- Reuses `RangePicker` Root / Input / Popover; only `WeekPicker.Calendar` is new.
|
|
95
|
+
- `weekStartsOn` (0=Sunday, 1=Monday) controls which seven days constitute a week.
|
|
96
|
+
- Enter / Space on the focused day commits the full week containing it.
|
|
97
|
+
- `displayTimezone`, `disabled` rules, and all other RangePicker props are supported.
|
|
98
|
+
|
|
99
|
+
- 6fc7c59: feat: add `YearPicker` — a headless year selector.
|
|
100
|
+
|
|
101
|
+
`YearPicker` stores the selected year as Jan 1 of that year in UTC-ISO form (e.g., `"2026-01-01T00:00:00.000Z"`). It reuses `DatePicker` infrastructure (Input, Trigger, Popover) and exposes `YearPicker.Grid`, a 12-year decade commit grid with decade navigation.
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
<YearPicker value={year} onChange={setYear}>
|
|
105
|
+
<YearPicker.Input placeholder="Pick a year" />
|
|
106
|
+
<YearPicker.Popover>
|
|
107
|
+
<YearPicker.Grid />
|
|
108
|
+
</YearPicker.Popover>
|
|
109
|
+
</YearPicker>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
- Default `displayFormat` is `"yyyy"`.
|
|
113
|
+
- `displayTimezone` is supported with timezone-aware year highlighting.
|
|
114
|
+
- Primary UX is click-to-select; full `yyyy-MM-dd` typed input still works via the inherited Input behavior.
|
|
115
|
+
|
|
116
|
+
### Patch Changes
|
|
117
|
+
|
|
118
|
+
- 1ca818c: fix(react): prevent WeekPicker from mutating RangePicker.Calendar
|
|
119
|
+
|
|
120
|
+
`WeekPicker` previously called `Object.assign(RangePickerRoot, { ..., Calendar: WeekPickerCalendar })`, which mutates the shared `RangePickerRoot` function object. Because `RangePicker.Calendar` is attached to the same object (via the earlier `Object.assign` in `RangePicker/index.ts`), importing `WeekPicker` would overwrite `RangePicker.Calendar` with `WeekPickerCalendar`.
|
|
121
|
+
|
|
122
|
+
Users of `RangePicker` would then see week-selection behavior (single click commits a full week and closes the popover) instead of the documented two-click range flow — even without importing `WeekPicker` directly, because both pickers share the module graph.
|
|
123
|
+
|
|
124
|
+
Added an internal `WeekPickerRoot` wrapper that the `Object.assign` target now uses, preserving `RangePickerRoot.Calendar` intact.
|
|
125
|
+
|
|
126
|
+
Caught by the `RangePicker › select range in start-date -> end-date order` Playwright test; all existing behavior is restored.
|
|
127
|
+
|
|
128
|
+
- Updated dependencies [ca7180e]
|
|
129
|
+
- @kalyx/core@1.0.0-rc.0
|
|
130
|
+
|
|
3
131
|
## 0.4.0
|
|
4
132
|
|
|
5
133
|
### Minor Changes
|
package/dist/index.cjs
CHANGED
|
@@ -21,10 +21,23 @@ function useDatePickerContext(componentName) {
|
|
|
21
21
|
}
|
|
22
22
|
return context;
|
|
23
23
|
}
|
|
24
|
+
function useChangeEffect(value, callback) {
|
|
25
|
+
const callbackRef = react.useRef(callback);
|
|
26
|
+
callbackRef.current = callback;
|
|
27
|
+
const prevRef = react.useRef(value);
|
|
28
|
+
react.useEffect(() => {
|
|
29
|
+
if (prevRef.current !== value) {
|
|
30
|
+
prevRef.current = value;
|
|
31
|
+
callbackRef.current?.(value);
|
|
32
|
+
}
|
|
33
|
+
}, [value]);
|
|
34
|
+
}
|
|
24
35
|
function DatePickerRoot({
|
|
25
36
|
value: controlledValue,
|
|
26
37
|
defaultValue,
|
|
27
38
|
onChange,
|
|
39
|
+
onOpenChange,
|
|
40
|
+
onCalendarNavigate,
|
|
28
41
|
disabled = false,
|
|
29
42
|
readOnly = false,
|
|
30
43
|
weekStartsOn = 0,
|
|
@@ -49,6 +62,9 @@ function DatePickerRoot({
|
|
|
49
62
|
const [focusedDate, setFocusedDate] = react.useState(
|
|
50
63
|
currentValue ?? adapter.today(displayTimezone)
|
|
51
64
|
);
|
|
65
|
+
useChangeEffect(isOpen, onOpenChange);
|
|
66
|
+
const viewMonthStart = react.useMemo(() => adapter.startOfMonth(viewMonth), [viewMonth, adapter]);
|
|
67
|
+
useChangeEffect(viewMonthStart, onCalendarNavigate);
|
|
52
68
|
const mergedLabels = react.useMemo(
|
|
53
69
|
() => ({ ...core.DEFAULT_DATEPICKER_LABELS, ...labelsProp }),
|
|
54
70
|
[labelsProp]
|
|
@@ -778,6 +794,95 @@ function DatePickerYearGrid({
|
|
|
778
794
|
)
|
|
779
795
|
] });
|
|
780
796
|
}
|
|
797
|
+
function DatePickerPresets({ classNames, children, ...props }) {
|
|
798
|
+
const ctx = useDatePickerContext("DatePicker.Presets");
|
|
799
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
800
|
+
"div",
|
|
801
|
+
{
|
|
802
|
+
role: "group",
|
|
803
|
+
"aria-label": ctx.labels.popoverLabel,
|
|
804
|
+
className: classNames?.root,
|
|
805
|
+
...props,
|
|
806
|
+
children
|
|
807
|
+
}
|
|
808
|
+
);
|
|
809
|
+
}
|
|
810
|
+
function resolveDatePreset(key, today, adapter) {
|
|
811
|
+
switch (key) {
|
|
812
|
+
case "today":
|
|
813
|
+
return today;
|
|
814
|
+
case "tomorrow":
|
|
815
|
+
return adapter.addDays(today, 1);
|
|
816
|
+
case "yesterday":
|
|
817
|
+
return adapter.addDays(today, -1);
|
|
818
|
+
case "startOfMonth":
|
|
819
|
+
return adapter.startOfMonth(today);
|
|
820
|
+
case "endOfMonth":
|
|
821
|
+
return adapter.startOfDay(adapter.endOfMonth(today));
|
|
822
|
+
case "startOfYear": {
|
|
823
|
+
const currentMonth = adapter.getMonth(today);
|
|
824
|
+
return adapter.startOfMonth(adapter.addMonths(today, -currentMonth));
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
function DatePickerPreset({
|
|
829
|
+
value: presetKey,
|
|
830
|
+
date: directDate,
|
|
831
|
+
children,
|
|
832
|
+
onClick,
|
|
833
|
+
...props
|
|
834
|
+
}) {
|
|
835
|
+
const ctx = useDatePickerContext("DatePicker.Preset");
|
|
836
|
+
const handleClick = react.useCallback(
|
|
837
|
+
(e) => {
|
|
838
|
+
if (ctx.isDisabled || ctx.isReadOnly) return;
|
|
839
|
+
let resolved;
|
|
840
|
+
if (directDate) {
|
|
841
|
+
resolved = directDate;
|
|
842
|
+
} else if (presetKey) {
|
|
843
|
+
resolved = resolveDatePreset(
|
|
844
|
+
presetKey,
|
|
845
|
+
ctx.adapter.today(ctx.displayTimezone),
|
|
846
|
+
ctx.adapter
|
|
847
|
+
);
|
|
848
|
+
} else {
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
ctx.selectDate(resolved);
|
|
852
|
+
onClick?.(e);
|
|
853
|
+
},
|
|
854
|
+
[ctx, presetKey, directDate, onClick]
|
|
855
|
+
);
|
|
856
|
+
const isActive = (() => {
|
|
857
|
+
if (!ctx.value) return false;
|
|
858
|
+
let target;
|
|
859
|
+
if (directDate) {
|
|
860
|
+
target = directDate;
|
|
861
|
+
} else if (presetKey) {
|
|
862
|
+
target = resolveDatePreset(
|
|
863
|
+
presetKey,
|
|
864
|
+
ctx.adapter.today(ctx.displayTimezone),
|
|
865
|
+
ctx.adapter
|
|
866
|
+
);
|
|
867
|
+
} else {
|
|
868
|
+
return false;
|
|
869
|
+
}
|
|
870
|
+
return ctx.adapter.isSameDay(ctx.value, target, ctx.displayTimezone);
|
|
871
|
+
})();
|
|
872
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
873
|
+
"button",
|
|
874
|
+
{
|
|
875
|
+
type: "button",
|
|
876
|
+
role: "option",
|
|
877
|
+
"aria-selected": isActive,
|
|
878
|
+
"data-active": isActive || void 0,
|
|
879
|
+
disabled: ctx.isDisabled,
|
|
880
|
+
onClick: handleClick,
|
|
881
|
+
...props,
|
|
882
|
+
children
|
|
883
|
+
}
|
|
884
|
+
);
|
|
885
|
+
}
|
|
781
886
|
|
|
782
887
|
// src/components/DatePicker/index.ts
|
|
783
888
|
var DatePicker = Object.assign(DatePickerRoot, {
|
|
@@ -786,7 +891,9 @@ var DatePicker = Object.assign(DatePickerRoot, {
|
|
|
786
891
|
Popover: DatePickerPopover,
|
|
787
892
|
Calendar: DatePickerCalendar,
|
|
788
893
|
MonthGrid: DatePickerMonthGrid,
|
|
789
|
-
YearGrid: DatePickerYearGrid
|
|
894
|
+
YearGrid: DatePickerYearGrid,
|
|
895
|
+
Presets: DatePickerPresets,
|
|
896
|
+
Preset: DatePickerPreset
|
|
790
897
|
});
|
|
791
898
|
var RangePickerContext = react.createContext(null);
|
|
792
899
|
function useRangePickerContext(componentName) {
|
|
@@ -808,6 +915,8 @@ function RangePickerRoot({
|
|
|
808
915
|
value: controlledValue,
|
|
809
916
|
defaultValue,
|
|
810
917
|
onChange,
|
|
918
|
+
onOpenChange,
|
|
919
|
+
onCalendarNavigate,
|
|
811
920
|
disabled = false,
|
|
812
921
|
readOnly = false,
|
|
813
922
|
weekStartsOn = 0,
|
|
@@ -834,6 +943,9 @@ function RangePickerRoot({
|
|
|
834
943
|
const [focusedDate, setFocusedDate] = react.useState(
|
|
835
944
|
currentValue.start ?? adapter.today(displayTimezone)
|
|
836
945
|
);
|
|
946
|
+
useChangeEffect(isOpen, onOpenChange);
|
|
947
|
+
const viewMonthStart = react.useMemo(() => adapter.startOfMonth(viewMonth), [viewMonth, adapter]);
|
|
948
|
+
useChangeEffect(viewMonthStart, onCalendarNavigate);
|
|
837
949
|
const mergedLabels = react.useMemo(
|
|
838
950
|
() => ({ ...core.DEFAULT_RANGEPICKER_LABELS, ...labelsProp }),
|
|
839
951
|
[labelsProp]
|
|
@@ -1056,7 +1168,11 @@ var srOnly2 = {
|
|
|
1056
1168
|
whiteSpace: "nowrap",
|
|
1057
1169
|
border: 0
|
|
1058
1170
|
};
|
|
1059
|
-
function RangePickerCalendar({
|
|
1171
|
+
function RangePickerCalendar({
|
|
1172
|
+
classNames,
|
|
1173
|
+
selectionMode = "range",
|
|
1174
|
+
...props
|
|
1175
|
+
}) {
|
|
1060
1176
|
const ctx = useRangePickerContext("RangePicker.Calendar");
|
|
1061
1177
|
const gridRef = react.useRef(null);
|
|
1062
1178
|
const [announcement, setAnnouncement] = react.useState("");
|
|
@@ -1102,21 +1218,39 @@ function RangePickerCalendar({ classNames, ...props }) {
|
|
|
1102
1218
|
},
|
|
1103
1219
|
[adapter, viewMonth, ctx, locale]
|
|
1104
1220
|
);
|
|
1221
|
+
const commitDay = react.useCallback(
|
|
1222
|
+
(iso) => {
|
|
1223
|
+
if (selectionMode === "week") {
|
|
1224
|
+
const weekStart = adapter.startOfWeek(iso, weekStartsOn);
|
|
1225
|
+
const weekEnd = adapter.startOfDay(adapter.endOfWeek(iso, weekStartsOn));
|
|
1226
|
+
const range = { start: weekStart, end: weekEnd };
|
|
1227
|
+
ctx.setRange(range);
|
|
1228
|
+
ctx.close();
|
|
1229
|
+
setAnnouncement(
|
|
1230
|
+
`${safeFormatFullDate2(weekStart, locale)} \u2013 ${safeFormatFullDate2(weekEnd, locale)}`
|
|
1231
|
+
);
|
|
1232
|
+
} else {
|
|
1233
|
+
ctx.selectDate(iso);
|
|
1234
|
+
setAnnouncement(safeFormatFullDate2(iso, locale));
|
|
1235
|
+
}
|
|
1236
|
+
},
|
|
1237
|
+
[selectionMode, adapter, weekStartsOn, ctx, locale]
|
|
1238
|
+
);
|
|
1105
1239
|
const handleDayClick = react.useCallback(
|
|
1106
1240
|
(day) => {
|
|
1107
1241
|
if (day.isDisabled) return;
|
|
1108
|
-
|
|
1109
|
-
setAnnouncement(safeFormatFullDate2(day.isoString, locale));
|
|
1242
|
+
commitDay(day.isoString);
|
|
1110
1243
|
},
|
|
1111
|
-
[
|
|
1244
|
+
[commitDay]
|
|
1112
1245
|
);
|
|
1113
1246
|
const handleDayMouseEnter = react.useCallback(
|
|
1114
1247
|
(day) => {
|
|
1248
|
+
if (selectionMode === "week") return;
|
|
1115
1249
|
if (selectingTarget === "end" && value.start && !day.isDisabled) {
|
|
1116
1250
|
ctx.setHoverDate(day.isoString);
|
|
1117
1251
|
}
|
|
1118
1252
|
},
|
|
1119
|
-
[selectingTarget, value.start, ctx]
|
|
1253
|
+
[selectionMode, selectingTarget, value.start, ctx]
|
|
1120
1254
|
);
|
|
1121
1255
|
const handleMouseLeave = react.useCallback(() => {
|
|
1122
1256
|
ctx.setHoverDate(null);
|
|
@@ -1153,7 +1287,7 @@ function RangePickerCalendar({ classNames, ...props }) {
|
|
|
1153
1287
|
case " ":
|
|
1154
1288
|
e.preventDefault();
|
|
1155
1289
|
if (!core.isDateDisabled(focusedDate, disabled, adapter)) {
|
|
1156
|
-
|
|
1290
|
+
commitDay(focusedDate);
|
|
1157
1291
|
}
|
|
1158
1292
|
return;
|
|
1159
1293
|
case "Escape":
|
|
@@ -1168,12 +1302,12 @@ function RangePickerCalendar({ classNames, ...props }) {
|
|
|
1168
1302
|
if (!adapter.isSameMonth(newFocused, viewMonth)) {
|
|
1169
1303
|
ctx.setViewMonth(newFocused);
|
|
1170
1304
|
}
|
|
1171
|
-
if (selectingTarget === "end" && value.start) {
|
|
1305
|
+
if (selectionMode === "range" && selectingTarget === "end" && value.start) {
|
|
1172
1306
|
ctx.setHoverDate(newFocused);
|
|
1173
1307
|
}
|
|
1174
1308
|
}
|
|
1175
1309
|
},
|
|
1176
|
-
[adapter, focusedDate, viewMonth, weekStartsOn, disabled, ctx, selectingTarget, value.start]
|
|
1310
|
+
[adapter, focusedDate, viewMonth, weekStartsOn, disabled, ctx, selectionMode, selectingTarget, value.start, commitDay]
|
|
1177
1311
|
);
|
|
1178
1312
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.root, ...props, onMouseLeave: handleMouseLeave, children: [
|
|
1179
1313
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.header, children: [
|
|
@@ -1230,7 +1364,7 @@ function RangePickerCalendar({ classNames, ...props }) {
|
|
|
1230
1364
|
day.isDisabled && classNames?.dayDisabled,
|
|
1231
1365
|
!day.isCurrentMonth && classNames?.dayOutsideMonth
|
|
1232
1366
|
].filter(Boolean).join(" ") || void 0;
|
|
1233
|
-
const isSelected = day.isRangeStart || day.isRangeEnd;
|
|
1367
|
+
const isSelected = selectionMode === "week" ? day.isRangeStart || day.isRangeEnd || day.isInRange : day.isRangeStart || day.isRangeEnd;
|
|
1234
1368
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1235
1369
|
"td",
|
|
1236
1370
|
{
|
|
@@ -1702,6 +1836,8 @@ function DateTimePickerRoot({
|
|
|
1702
1836
|
value: controlledValue,
|
|
1703
1837
|
defaultValue,
|
|
1704
1838
|
onChange,
|
|
1839
|
+
onOpenChange,
|
|
1840
|
+
onCalendarNavigate,
|
|
1705
1841
|
format = "24h",
|
|
1706
1842
|
step = 1,
|
|
1707
1843
|
disabled = false,
|
|
@@ -1736,6 +1872,9 @@ function DateTimePickerRoot({
|
|
|
1736
1872
|
const [focusedDate, setFocusedDate] = react.useState(
|
|
1737
1873
|
currentValue ?? adapter.today(displayTimezone)
|
|
1738
1874
|
);
|
|
1875
|
+
useChangeEffect(isOpen, onOpenChange);
|
|
1876
|
+
const viewMonthStart = react.useMemo(() => adapter.startOfMonth(viewMonth), [viewMonth, adapter]);
|
|
1877
|
+
useChangeEffect(viewMonthStart, onCalendarNavigate);
|
|
1739
1878
|
const isDisabled = typeof disabled === "boolean" ? disabled : false;
|
|
1740
1879
|
const disabledRules = react.useMemo(
|
|
1741
1880
|
() => Array.isArray(disabled) ? disabled : [],
|
|
@@ -1925,6 +2064,249 @@ var DateTimePicker = Object.assign(DateTimePickerRoot, {
|
|
|
1925
2064
|
MinuteList: TimePickerMinuteList,
|
|
1926
2065
|
AmPmToggle: TimePickerAmPmToggle
|
|
1927
2066
|
});
|
|
2067
|
+
function MonthPickerRoot(props) {
|
|
2068
|
+
const displayFormat = props.displayFormat ?? "yyyy-MM";
|
|
2069
|
+
return /* @__PURE__ */ jsxRuntime.jsx(DatePickerRoot, { ...props, displayFormat });
|
|
2070
|
+
}
|
|
2071
|
+
function MonthPickerGrid({
|
|
2072
|
+
classNames,
|
|
2073
|
+
...props
|
|
2074
|
+
}) {
|
|
2075
|
+
const ctx = useDatePickerContext("MonthPicker.Grid");
|
|
2076
|
+
const { adapter, viewMonth, locale, value, displayTimezone, labels } = ctx;
|
|
2077
|
+
const currentYear = adapter.getYear(viewMonth);
|
|
2078
|
+
const [valueYear, valueMonthZeroBased] = react.useMemo(() => {
|
|
2079
|
+
if (!value) return [null, null];
|
|
2080
|
+
try {
|
|
2081
|
+
const [y, m] = adapter.format(value, "yyyy-MM", displayTimezone).split("-").map(Number);
|
|
2082
|
+
return [y, m - 1];
|
|
2083
|
+
} catch {
|
|
2084
|
+
return [null, null];
|
|
2085
|
+
}
|
|
2086
|
+
}, [value, adapter, displayTimezone]);
|
|
2087
|
+
const today = adapter.today(displayTimezone);
|
|
2088
|
+
const todayYear = adapter.getYear(today);
|
|
2089
|
+
const todayMonth = adapter.getMonth(today);
|
|
2090
|
+
const navigateYear = react.useCallback(
|
|
2091
|
+
(direction) => {
|
|
2092
|
+
ctx.setViewMonth(adapter.addYears(viewMonth, direction));
|
|
2093
|
+
},
|
|
2094
|
+
[adapter, viewMonth, ctx]
|
|
2095
|
+
);
|
|
2096
|
+
const handleMonthSelect = react.useCallback(
|
|
2097
|
+
(monthIndex) => {
|
|
2098
|
+
const target = new Date(
|
|
2099
|
+
Date.UTC(currentYear, monthIndex, 1)
|
|
2100
|
+
).toISOString();
|
|
2101
|
+
ctx.selectDate(target);
|
|
2102
|
+
},
|
|
2103
|
+
[currentYear, ctx]
|
|
2104
|
+
);
|
|
2105
|
+
const months = Array.from({ length: 12 }, (_, i) => ({
|
|
2106
|
+
index: i,
|
|
2107
|
+
name: core.getMonthName(i, locale),
|
|
2108
|
+
isSelected: valueYear === currentYear && valueMonthZeroBased === i,
|
|
2109
|
+
isCurrent: todayYear === currentYear && todayMonth === i
|
|
2110
|
+
}));
|
|
2111
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.root, ...props, children: [
|
|
2112
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.header, children: [
|
|
2113
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2114
|
+
"button",
|
|
2115
|
+
{
|
|
2116
|
+
type: "button",
|
|
2117
|
+
className: classNames?.navButton,
|
|
2118
|
+
onClick: () => navigateYear(-1),
|
|
2119
|
+
"aria-label": labels.prevYear,
|
|
2120
|
+
children: "<"
|
|
2121
|
+
}
|
|
2122
|
+
),
|
|
2123
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: classNames?.title, children: currentYear }),
|
|
2124
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2125
|
+
"button",
|
|
2126
|
+
{
|
|
2127
|
+
type: "button",
|
|
2128
|
+
className: classNames?.navButton,
|
|
2129
|
+
onClick: () => navigateYear(1),
|
|
2130
|
+
"aria-label": labels.nextYear,
|
|
2131
|
+
children: ">"
|
|
2132
|
+
}
|
|
2133
|
+
)
|
|
2134
|
+
] }),
|
|
2135
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2136
|
+
"div",
|
|
2137
|
+
{
|
|
2138
|
+
role: "grid",
|
|
2139
|
+
"aria-label": `${currentYear} months`,
|
|
2140
|
+
className: classNames?.grid,
|
|
2141
|
+
children: Array.from({ length: 4 }, (_, rowIndex) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2142
|
+
"div",
|
|
2143
|
+
{
|
|
2144
|
+
role: "row",
|
|
2145
|
+
className: classNames?.gridRow,
|
|
2146
|
+
style: { display: "grid", gridTemplateColumns: "repeat(3, 1fr)" },
|
|
2147
|
+
children: months.slice(rowIndex * 3, rowIndex * 3 + 3).map((m) => {
|
|
2148
|
+
const monthClass = [
|
|
2149
|
+
classNames?.month,
|
|
2150
|
+
m.isSelected && classNames?.monthSelected,
|
|
2151
|
+
m.isCurrent && classNames?.monthCurrent
|
|
2152
|
+
].filter(Boolean).join(" ") || void 0;
|
|
2153
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2154
|
+
"button",
|
|
2155
|
+
{
|
|
2156
|
+
type: "button",
|
|
2157
|
+
role: "gridcell",
|
|
2158
|
+
"aria-selected": m.isSelected || void 0,
|
|
2159
|
+
"aria-current": m.isCurrent ? "date" : void 0,
|
|
2160
|
+
"data-selected": m.isSelected || void 0,
|
|
2161
|
+
"data-current": m.isCurrent || void 0,
|
|
2162
|
+
className: monthClass,
|
|
2163
|
+
onClick: () => handleMonthSelect(m.index),
|
|
2164
|
+
children: m.name
|
|
2165
|
+
},
|
|
2166
|
+
m.index
|
|
2167
|
+
);
|
|
2168
|
+
})
|
|
2169
|
+
},
|
|
2170
|
+
rowIndex
|
|
2171
|
+
))
|
|
2172
|
+
}
|
|
2173
|
+
)
|
|
2174
|
+
] });
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
// src/components/MonthPicker/index.ts
|
|
2178
|
+
var MonthPicker = Object.assign(MonthPickerRoot, {
|
|
2179
|
+
Input: DatePickerInput,
|
|
2180
|
+
Trigger: DatePickerTrigger,
|
|
2181
|
+
Popover: DatePickerPopover,
|
|
2182
|
+
Grid: MonthPickerGrid
|
|
2183
|
+
});
|
|
2184
|
+
function YearPickerRoot(props) {
|
|
2185
|
+
const displayFormat = props.displayFormat ?? "yyyy";
|
|
2186
|
+
return /* @__PURE__ */ jsxRuntime.jsx(DatePickerRoot, { ...props, displayFormat });
|
|
2187
|
+
}
|
|
2188
|
+
function YearPickerGrid({ classNames, ...props }) {
|
|
2189
|
+
const ctx = useDatePickerContext("YearPicker.Grid");
|
|
2190
|
+
const { adapter, viewMonth, value, displayTimezone, labels } = ctx;
|
|
2191
|
+
const currentYear = adapter.getYear(viewMonth);
|
|
2192
|
+
const decadeStart = currentYear - currentYear % 12;
|
|
2193
|
+
const valueYear = react.useMemo(() => {
|
|
2194
|
+
if (!value) return null;
|
|
2195
|
+
try {
|
|
2196
|
+
return Number(adapter.format(value, "yyyy", displayTimezone));
|
|
2197
|
+
} catch {
|
|
2198
|
+
return null;
|
|
2199
|
+
}
|
|
2200
|
+
}, [value, adapter, displayTimezone]);
|
|
2201
|
+
const todayYear = adapter.getYear(adapter.today(displayTimezone));
|
|
2202
|
+
const navigateDecade = react.useCallback(
|
|
2203
|
+
(direction) => {
|
|
2204
|
+
ctx.setViewMonth(adapter.addYears(viewMonth, direction * 12));
|
|
2205
|
+
},
|
|
2206
|
+
[adapter, viewMonth, ctx]
|
|
2207
|
+
);
|
|
2208
|
+
const handleYearSelect = react.useCallback(
|
|
2209
|
+
(year) => {
|
|
2210
|
+
const target = new Date(Date.UTC(year, 0, 1)).toISOString();
|
|
2211
|
+
ctx.selectDate(target);
|
|
2212
|
+
},
|
|
2213
|
+
[ctx]
|
|
2214
|
+
);
|
|
2215
|
+
const years = Array.from({ length: 12 }, (_, i) => {
|
|
2216
|
+
const year = decadeStart + i;
|
|
2217
|
+
return {
|
|
2218
|
+
value: year,
|
|
2219
|
+
isSelected: year === valueYear,
|
|
2220
|
+
isCurrent: year === todayYear
|
|
2221
|
+
};
|
|
2222
|
+
});
|
|
2223
|
+
const rangeLabel = `${decadeStart}\u2013${decadeStart + 11}`;
|
|
2224
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.root, ...props, children: [
|
|
2225
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.header, children: [
|
|
2226
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2227
|
+
"button",
|
|
2228
|
+
{
|
|
2229
|
+
type: "button",
|
|
2230
|
+
className: classNames?.navButton,
|
|
2231
|
+
onClick: () => navigateDecade(-1),
|
|
2232
|
+
"aria-label": labels.prevDecade,
|
|
2233
|
+
children: "<"
|
|
2234
|
+
}
|
|
2235
|
+
),
|
|
2236
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: classNames?.title, children: rangeLabel }),
|
|
2237
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2238
|
+
"button",
|
|
2239
|
+
{
|
|
2240
|
+
type: "button",
|
|
2241
|
+
className: classNames?.navButton,
|
|
2242
|
+
onClick: () => navigateDecade(1),
|
|
2243
|
+
"aria-label": labels.nextDecade,
|
|
2244
|
+
children: ">"
|
|
2245
|
+
}
|
|
2246
|
+
)
|
|
2247
|
+
] }),
|
|
2248
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2249
|
+
"div",
|
|
2250
|
+
{
|
|
2251
|
+
role: "grid",
|
|
2252
|
+
"aria-label": rangeLabel,
|
|
2253
|
+
className: classNames?.grid,
|
|
2254
|
+
children: Array.from({ length: 4 }, (_, rowIndex) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2255
|
+
"div",
|
|
2256
|
+
{
|
|
2257
|
+
role: "row",
|
|
2258
|
+
className: classNames?.gridRow,
|
|
2259
|
+
style: { display: "grid", gridTemplateColumns: "repeat(3, 1fr)" },
|
|
2260
|
+
children: years.slice(rowIndex * 3, rowIndex * 3 + 3).map((y) => {
|
|
2261
|
+
const yearClass = [
|
|
2262
|
+
classNames?.year,
|
|
2263
|
+
y.isSelected && classNames?.yearSelected,
|
|
2264
|
+
y.isCurrent && classNames?.yearCurrent
|
|
2265
|
+
].filter(Boolean).join(" ") || void 0;
|
|
2266
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2267
|
+
"button",
|
|
2268
|
+
{
|
|
2269
|
+
type: "button",
|
|
2270
|
+
role: "gridcell",
|
|
2271
|
+
"aria-selected": y.isSelected || void 0,
|
|
2272
|
+
"aria-current": y.isCurrent ? "date" : void 0,
|
|
2273
|
+
"data-selected": y.isSelected || void 0,
|
|
2274
|
+
"data-current": y.isCurrent || void 0,
|
|
2275
|
+
className: yearClass,
|
|
2276
|
+
onClick: () => handleYearSelect(y.value),
|
|
2277
|
+
children: y.value
|
|
2278
|
+
},
|
|
2279
|
+
y.value
|
|
2280
|
+
);
|
|
2281
|
+
})
|
|
2282
|
+
},
|
|
2283
|
+
rowIndex
|
|
2284
|
+
))
|
|
2285
|
+
}
|
|
2286
|
+
)
|
|
2287
|
+
] });
|
|
2288
|
+
}
|
|
2289
|
+
|
|
2290
|
+
// src/components/YearPicker/index.ts
|
|
2291
|
+
var YearPicker = Object.assign(YearPickerRoot, {
|
|
2292
|
+
Input: DatePickerInput,
|
|
2293
|
+
Trigger: DatePickerTrigger,
|
|
2294
|
+
Popover: DatePickerPopover,
|
|
2295
|
+
Grid: YearPickerGrid
|
|
2296
|
+
});
|
|
2297
|
+
function WeekPickerRoot(props) {
|
|
2298
|
+
return /* @__PURE__ */ jsxRuntime.jsx(RangePickerRoot, { ...props });
|
|
2299
|
+
}
|
|
2300
|
+
function WeekPickerCalendar(props) {
|
|
2301
|
+
return /* @__PURE__ */ jsxRuntime.jsx(RangePickerCalendar, { ...props, selectionMode: "week" });
|
|
2302
|
+
}
|
|
2303
|
+
|
|
2304
|
+
// src/components/WeekPicker/index.ts
|
|
2305
|
+
var WeekPicker = Object.assign(WeekPickerRoot, {
|
|
2306
|
+
Input: RangePickerInput,
|
|
2307
|
+
Popover: RangePickerPopover,
|
|
2308
|
+
Calendar: WeekPickerCalendar
|
|
2309
|
+
});
|
|
1928
2310
|
function useDatePicker(options = {}) {
|
|
1929
2311
|
const {
|
|
1930
2312
|
value: controlledValue,
|
|
@@ -2203,8 +2585,11 @@ Object.defineProperty(exports, "DateFnsAdapter", {
|
|
|
2203
2585
|
});
|
|
2204
2586
|
exports.DatePicker = DatePicker;
|
|
2205
2587
|
exports.DateTimePicker = DateTimePicker;
|
|
2588
|
+
exports.MonthPicker = MonthPicker;
|
|
2206
2589
|
exports.RangePicker = RangePicker;
|
|
2207
2590
|
exports.TimePicker = TimePicker;
|
|
2591
|
+
exports.WeekPicker = WeekPicker;
|
|
2592
|
+
exports.YearPicker = YearPicker;
|
|
2208
2593
|
exports.useDatePicker = useDatePicker;
|
|
2209
2594
|
exports.useRangePicker = useRangePicker;
|
|
2210
2595
|
exports.useTimePicker = useTimePicker;
|