@kalyx/react 0.4.0 → 1.0.0-rc.1

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
@@ -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]
@@ -286,10 +302,15 @@ var DatePickerTrigger = react.forwardRef(
286
302
  );
287
303
  }
288
304
  );
289
- function usePopover({ isOpen, close, referenceRef, placement = "bottom-start" }) {
305
+ function usePopover({
306
+ isOpen,
307
+ close,
308
+ referenceRef,
309
+ placement = "bottom-start"
310
+ }) {
290
311
  const floatingRef = react.useRef(null);
291
312
  const previousFocusRef = react.useRef(null);
292
- const { refs, floatingStyles } = react$1.useFloating({
313
+ const { refs, floatingStyles, isPositioned } = react$1.useFloating({
293
314
  open: isOpen,
294
315
  placement,
295
316
  middleware: [react$1.offset(4), react$1.flip(), react$1.shift({ padding: 8 })],
@@ -339,21 +360,28 @@ function usePopover({ isOpen, close, referenceRef, placement = "bottom-start" })
339
360
  document.addEventListener("keydown", handleKeyDown);
340
361
  return () => document.removeEventListener("keydown", handleKeyDown);
341
362
  }, [isOpen, close]);
342
- const setFloatingRef = (node) => {
343
- floatingRef.current = node;
344
- refs.setFloating(node);
345
- };
346
- return { floatingStyles, setFloatingRef };
363
+ const setFloatingRef = react.useCallback(
364
+ (node) => {
365
+ floatingRef.current = node;
366
+ refs.setFloating(node);
367
+ if (node && referenceRef.current) {
368
+ refs.setReference(referenceRef.current);
369
+ }
370
+ },
371
+ [refs, referenceRef]
372
+ );
373
+ return { floatingStyles, setFloatingRef, isPositioned };
347
374
  }
348
375
  function DatePickerPopover({ children, ...props }) {
349
376
  const ctx = useDatePickerContext("DatePicker.Popover");
350
377
  const calendarId = `${ctx.pickerId}-calendar`;
351
- const { floatingStyles, setFloatingRef } = usePopover({
378
+ const { floatingStyles, setFloatingRef, isPositioned } = usePopover({
352
379
  isOpen: ctx.isOpen,
353
380
  close: ctx.close,
354
381
  referenceRef: ctx.referenceRef
355
382
  });
356
383
  if (!ctx.isOpen) return null;
384
+ const { style: userStyle, ...rest } = props;
357
385
  return /* @__PURE__ */ jsxRuntime.jsx(
358
386
  "div",
359
387
  {
@@ -362,8 +390,12 @@ function DatePickerPopover({ children, ...props }) {
362
390
  role: "dialog",
363
391
  "aria-label": ctx.labels.popoverLabel,
364
392
  "aria-modal": "false",
365
- style: floatingStyles,
366
- ...props,
393
+ ...rest,
394
+ style: {
395
+ ...userStyle,
396
+ ...floatingStyles,
397
+ visibility: isPositioned ? void 0 : "hidden"
398
+ },
367
399
  children
368
400
  }
369
401
  );
@@ -386,7 +418,11 @@ var srOnly = {
386
418
  whiteSpace: "nowrap",
387
419
  border: 0
388
420
  };
389
- function DatePickerCalendar({ classNames, onTitleClick, ...props }) {
421
+ function DatePickerCalendar({
422
+ classNames,
423
+ onTitleClick,
424
+ ...props
425
+ }) {
390
426
  const ctx = useDatePickerContext("DatePicker.Calendar");
391
427
  const gridRef = react.useRef(null);
392
428
  const [announcement, setAnnouncement] = react.useState("");
@@ -404,9 +440,7 @@ function DatePickerCalendar({ classNames, onTitleClick, ...props }) {
404
440
  const title = core.formatMonthYear(year, month, locale);
405
441
  react.useEffect(() => {
406
442
  if (!ctx.isOpen || !gridRef.current) return;
407
- const focusedButton = gridRef.current.querySelector(
408
- '[data-focused="true"]'
409
- );
443
+ const focusedButton = gridRef.current.querySelector('[data-focused="true"]');
410
444
  focusedButton?.focus({ preventScroll: true });
411
445
  }, [focusedDate, ctx.isOpen]);
412
446
  const navigateMonth = react.useCallback(
@@ -629,15 +663,7 @@ function DatePickerMonthGrid({
629
663
  children: "<"
630
664
  }
631
665
  ),
632
- onTitleClick ? /* @__PURE__ */ jsxRuntime.jsx(
633
- "button",
634
- {
635
- type: "button",
636
- className: classNames?.title,
637
- onClick: onTitleClick,
638
- children: currentYear
639
- }
640
- ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: classNames?.title, children: currentYear }),
666
+ onTitleClick ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: classNames?.title, onClick: onTitleClick, children: currentYear }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: classNames?.title, children: currentYear }),
641
667
  /* @__PURE__ */ jsxRuntime.jsx(
642
668
  "button",
643
669
  {
@@ -682,11 +708,7 @@ function DatePickerMonthGrid({
682
708
  )
683
709
  ] });
684
710
  }
685
- function DatePickerYearGrid({
686
- classNames,
687
- onSelect,
688
- ...props
689
- }) {
711
+ function DatePickerYearGrid({ classNames, onSelect, ...props }) {
690
712
  const ctx = useDatePickerContext("DatePicker.YearGrid");
691
713
  const { adapter, viewMonth } = ctx;
692
714
  const currentYear = adapter.getYear(viewMonth);
@@ -778,6 +800,82 @@ function DatePickerYearGrid({
778
800
  )
779
801
  ] });
780
802
  }
803
+ function DatePickerPresets({ classNames, children, ...props }) {
804
+ const ctx = useDatePickerContext("DatePicker.Presets");
805
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "group", "aria-label": ctx.labels.popoverLabel, className: classNames?.root, ...props, children });
806
+ }
807
+ function resolveDatePreset(key, today, adapter) {
808
+ switch (key) {
809
+ case "today":
810
+ return today;
811
+ case "tomorrow":
812
+ return adapter.addDays(today, 1);
813
+ case "yesterday":
814
+ return adapter.addDays(today, -1);
815
+ case "startOfMonth":
816
+ return adapter.startOfMonth(today);
817
+ case "endOfMonth":
818
+ return adapter.startOfDay(adapter.endOfMonth(today));
819
+ case "startOfYear": {
820
+ const currentMonth = adapter.getMonth(today);
821
+ return adapter.startOfMonth(adapter.addMonths(today, -currentMonth));
822
+ }
823
+ }
824
+ }
825
+ function DatePickerPreset({
826
+ value: presetKey,
827
+ date: directDate,
828
+ children,
829
+ onClick,
830
+ ...props
831
+ }) {
832
+ const ctx = useDatePickerContext("DatePicker.Preset");
833
+ const handleClick = react.useCallback(
834
+ (e) => {
835
+ if (ctx.isDisabled || ctx.isReadOnly) return;
836
+ let resolved;
837
+ if (directDate) {
838
+ resolved = directDate;
839
+ } else if (presetKey) {
840
+ resolved = resolveDatePreset(
841
+ presetKey,
842
+ ctx.adapter.today(ctx.displayTimezone),
843
+ ctx.adapter
844
+ );
845
+ } else {
846
+ return;
847
+ }
848
+ ctx.selectDate(resolved);
849
+ onClick?.(e);
850
+ },
851
+ [ctx, presetKey, directDate, onClick]
852
+ );
853
+ const isActive = (() => {
854
+ if (!ctx.value) return false;
855
+ let target;
856
+ if (directDate) {
857
+ target = directDate;
858
+ } else if (presetKey) {
859
+ target = resolveDatePreset(presetKey, ctx.adapter.today(ctx.displayTimezone), ctx.adapter);
860
+ } else {
861
+ return false;
862
+ }
863
+ return ctx.adapter.isSameDay(ctx.value, target, ctx.displayTimezone);
864
+ })();
865
+ return /* @__PURE__ */ jsxRuntime.jsx(
866
+ "button",
867
+ {
868
+ type: "button",
869
+ role: "option",
870
+ "aria-selected": isActive,
871
+ "data-active": isActive || void 0,
872
+ disabled: ctx.isDisabled,
873
+ onClick: handleClick,
874
+ ...props,
875
+ children
876
+ }
877
+ );
878
+ }
781
879
 
782
880
  // src/components/DatePicker/index.ts
783
881
  var DatePicker = Object.assign(DatePickerRoot, {
@@ -786,7 +884,9 @@ var DatePicker = Object.assign(DatePickerRoot, {
786
884
  Popover: DatePickerPopover,
787
885
  Calendar: DatePickerCalendar,
788
886
  MonthGrid: DatePickerMonthGrid,
789
- YearGrid: DatePickerYearGrid
887
+ YearGrid: DatePickerYearGrid,
888
+ Presets: DatePickerPresets,
889
+ Preset: DatePickerPreset
790
890
  });
791
891
  var RangePickerContext = react.createContext(null);
792
892
  function useRangePickerContext(componentName) {
@@ -808,6 +908,8 @@ function RangePickerRoot({
808
908
  value: controlledValue,
809
909
  defaultValue,
810
910
  onChange,
911
+ onOpenChange,
912
+ onCalendarNavigate,
811
913
  disabled = false,
812
914
  readOnly = false,
813
915
  weekStartsOn = 0,
@@ -834,6 +936,9 @@ function RangePickerRoot({
834
936
  const [focusedDate, setFocusedDate] = react.useState(
835
937
  currentValue.start ?? adapter.today(displayTimezone)
836
938
  );
939
+ useChangeEffect(isOpen, onOpenChange);
940
+ const viewMonthStart = react.useMemo(() => adapter.startOfMonth(viewMonth), [viewMonth, adapter]);
941
+ useChangeEffect(viewMonthStart, onCalendarNavigate);
837
942
  const mergedLabels = react.useMemo(
838
943
  () => ({ ...core.DEFAULT_RANGEPICKER_LABELS, ...labelsProp }),
839
944
  [labelsProp]
@@ -1018,12 +1123,13 @@ var RangePickerInput = react.forwardRef(
1018
1123
  function RangePickerPopover({ children, ...props }) {
1019
1124
  const ctx = useRangePickerContext("RangePicker.Popover");
1020
1125
  const calendarId = `${ctx.pickerId}-calendar`;
1021
- const { floatingStyles, setFloatingRef } = usePopover({
1126
+ const { floatingStyles, setFloatingRef, isPositioned } = usePopover({
1022
1127
  isOpen: ctx.isOpen,
1023
1128
  close: ctx.close,
1024
1129
  referenceRef: ctx.referenceRef
1025
1130
  });
1026
1131
  if (!ctx.isOpen) return null;
1132
+ const { style: userStyle, ...rest } = props;
1027
1133
  return /* @__PURE__ */ jsxRuntime.jsx(
1028
1134
  "div",
1029
1135
  {
@@ -1032,8 +1138,12 @@ function RangePickerPopover({ children, ...props }) {
1032
1138
  role: "dialog",
1033
1139
  "aria-label": ctx.labels.popoverLabel,
1034
1140
  "aria-modal": "false",
1035
- style: floatingStyles,
1036
- ...props,
1141
+ ...rest,
1142
+ style: {
1143
+ ...userStyle,
1144
+ ...floatingStyles,
1145
+ visibility: isPositioned ? void 0 : "hidden"
1146
+ },
1037
1147
  children
1038
1148
  }
1039
1149
  );
@@ -1056,7 +1166,11 @@ var srOnly2 = {
1056
1166
  whiteSpace: "nowrap",
1057
1167
  border: 0
1058
1168
  };
1059
- function RangePickerCalendar({ classNames, ...props }) {
1169
+ function RangePickerCalendar({
1170
+ classNames,
1171
+ selectionMode = "range",
1172
+ ...props
1173
+ }) {
1060
1174
  const ctx = useRangePickerContext("RangePicker.Calendar");
1061
1175
  const gridRef = react.useRef(null);
1062
1176
  const [announcement, setAnnouncement] = react.useState("");
@@ -1086,9 +1200,7 @@ function RangePickerCalendar({ classNames, ...props }) {
1086
1200
  const title = core.formatMonthYear(year, month, locale);
1087
1201
  react.useEffect(() => {
1088
1202
  if (!ctx.isOpen || !gridRef.current) return;
1089
- const focusedButton = gridRef.current.querySelector(
1090
- '[data-focused="true"]'
1091
- );
1203
+ const focusedButton = gridRef.current.querySelector('[data-focused="true"]');
1092
1204
  focusedButton?.focus({ preventScroll: true });
1093
1205
  }, [focusedDate, ctx.isOpen]);
1094
1206
  const navigateMonth = react.useCallback(
@@ -1102,21 +1214,39 @@ function RangePickerCalendar({ classNames, ...props }) {
1102
1214
  },
1103
1215
  [adapter, viewMonth, ctx, locale]
1104
1216
  );
1217
+ const commitDay = react.useCallback(
1218
+ (iso) => {
1219
+ if (selectionMode === "week") {
1220
+ const weekStart = adapter.startOfWeek(iso, weekStartsOn);
1221
+ const weekEnd = adapter.startOfDay(adapter.endOfWeek(iso, weekStartsOn));
1222
+ const range = { start: weekStart, end: weekEnd };
1223
+ ctx.setRange(range);
1224
+ ctx.close();
1225
+ setAnnouncement(
1226
+ `${safeFormatFullDate2(weekStart, locale)} \u2013 ${safeFormatFullDate2(weekEnd, locale)}`
1227
+ );
1228
+ } else {
1229
+ ctx.selectDate(iso);
1230
+ setAnnouncement(safeFormatFullDate2(iso, locale));
1231
+ }
1232
+ },
1233
+ [selectionMode, adapter, weekStartsOn, ctx, locale]
1234
+ );
1105
1235
  const handleDayClick = react.useCallback(
1106
1236
  (day) => {
1107
1237
  if (day.isDisabled) return;
1108
- ctx.selectDate(day.isoString);
1109
- setAnnouncement(safeFormatFullDate2(day.isoString, locale));
1238
+ commitDay(day.isoString);
1110
1239
  },
1111
- [ctx, locale]
1240
+ [commitDay]
1112
1241
  );
1113
1242
  const handleDayMouseEnter = react.useCallback(
1114
1243
  (day) => {
1244
+ if (selectionMode === "week") return;
1115
1245
  if (selectingTarget === "end" && value.start && !day.isDisabled) {
1116
1246
  ctx.setHoverDate(day.isoString);
1117
1247
  }
1118
1248
  },
1119
- [selectingTarget, value.start, ctx]
1249
+ [selectionMode, selectingTarget, value.start, ctx]
1120
1250
  );
1121
1251
  const handleMouseLeave = react.useCallback(() => {
1122
1252
  ctx.setHoverDate(null);
@@ -1153,7 +1283,7 @@ function RangePickerCalendar({ classNames, ...props }) {
1153
1283
  case " ":
1154
1284
  e.preventDefault();
1155
1285
  if (!core.isDateDisabled(focusedDate, disabled, adapter)) {
1156
- ctx.selectDate(focusedDate);
1286
+ commitDay(focusedDate);
1157
1287
  }
1158
1288
  return;
1159
1289
  case "Escape":
@@ -1168,12 +1298,23 @@ function RangePickerCalendar({ classNames, ...props }) {
1168
1298
  if (!adapter.isSameMonth(newFocused, viewMonth)) {
1169
1299
  ctx.setViewMonth(newFocused);
1170
1300
  }
1171
- if (selectingTarget === "end" && value.start) {
1301
+ if (selectionMode === "range" && selectingTarget === "end" && value.start) {
1172
1302
  ctx.setHoverDate(newFocused);
1173
1303
  }
1174
1304
  }
1175
1305
  },
1176
- [adapter, focusedDate, viewMonth, weekStartsOn, disabled, ctx, selectingTarget, value.start]
1306
+ [
1307
+ adapter,
1308
+ focusedDate,
1309
+ viewMonth,
1310
+ weekStartsOn,
1311
+ disabled,
1312
+ ctx,
1313
+ selectionMode,
1314
+ selectingTarget,
1315
+ value.start,
1316
+ commitDay
1317
+ ]
1177
1318
  );
1178
1319
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.root, ...props, onMouseLeave: handleMouseLeave, children: [
1179
1320
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.header, children: [
@@ -1230,7 +1371,7 @@ function RangePickerCalendar({ classNames, ...props }) {
1230
1371
  day.isDisabled && classNames?.dayDisabled,
1231
1372
  !day.isCurrentMonth && classNames?.dayOutsideMonth
1232
1373
  ].filter(Boolean).join(" ") || void 0;
1233
- const isSelected = day.isRangeStart || day.isRangeEnd;
1374
+ const isSelected = selectionMode === "week" ? day.isRangeStart || day.isRangeEnd || day.isInRange : day.isRangeStart || day.isRangeEnd;
1234
1375
  return /* @__PURE__ */ jsxRuntime.jsx(
1235
1376
  "td",
1236
1377
  {
@@ -1310,9 +1451,7 @@ function resolvePreset(key, today, adapter) {
1310
1451
  }
1311
1452
  case "thisYear": {
1312
1453
  const currentMonth = new Date(today).getUTCMonth();
1313
- const yearStart = adapter.startOfMonth(
1314
- adapter.addMonths(today, -currentMonth)
1315
- );
1454
+ const yearStart = adapter.startOfMonth(adapter.addMonths(today, -currentMonth));
1316
1455
  return { start: yearStart, end: today };
1317
1456
  }
1318
1457
  }
@@ -1448,7 +1587,19 @@ function TimePickerRoot({
1448
1587
  pickerId,
1449
1588
  labels: mergedLabels
1450
1589
  }),
1451
- [currentValue, setTime, format, step, withSeconds, displayTimezone, disabled, readOnly, currentTime, pickerId, mergedLabels]
1590
+ [
1591
+ currentValue,
1592
+ setTime,
1593
+ format,
1594
+ step,
1595
+ withSeconds,
1596
+ displayTimezone,
1597
+ disabled,
1598
+ readOnly,
1599
+ currentTime,
1600
+ pickerId,
1601
+ mergedLabels
1602
+ ]
1452
1603
  );
1453
1604
  return /* @__PURE__ */ jsxRuntime.jsx(TimePickerContext.Provider, { value: contextValue, children });
1454
1605
  }
@@ -1465,12 +1616,9 @@ var TimePickerInput = react.forwardRef(
1465
1616
  }
1466
1617
  setInputText(null);
1467
1618
  }, [inputText, ctx]);
1468
- const handleChange = react.useCallback(
1469
- (e) => {
1470
- setInputText(e.target.value);
1471
- },
1472
- []
1473
- );
1619
+ const handleChange = react.useCallback((e) => {
1620
+ setInputText(e.target.value);
1621
+ }, []);
1474
1622
  const handleBlur = react.useCallback(
1475
1623
  (e) => {
1476
1624
  commitInput();
@@ -1543,9 +1691,7 @@ function useListboxNavigation({
1543
1691
  onSelect(target);
1544
1692
  cancelAnimationFrame(rafIdRef.current);
1545
1693
  rafIdRef.current = requestAnimationFrame(() => {
1546
- const next = listRef.current?.querySelector(
1547
- '[data-selected="true"]'
1548
- );
1694
+ const next = listRef.current?.querySelector('[data-selected="true"]');
1549
1695
  next?.focus();
1550
1696
  });
1551
1697
  }
@@ -1682,10 +1828,19 @@ function TimePickerAmPmToggle({ classNames, ...props }) {
1682
1828
  }
1683
1829
  );
1684
1830
  };
1685
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "radiogroup", "aria-label": ctx.labels.amPmToggle, className: classNames?.root, ...props, children: [
1686
- renderButton("AM"),
1687
- renderButton("PM")
1688
- ] });
1831
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1832
+ "div",
1833
+ {
1834
+ role: "radiogroup",
1835
+ "aria-label": ctx.labels.amPmToggle,
1836
+ className: classNames?.root,
1837
+ ...props,
1838
+ children: [
1839
+ renderButton("AM"),
1840
+ renderButton("PM")
1841
+ ]
1842
+ }
1843
+ );
1689
1844
  }
1690
1845
 
1691
1846
  // src/components/TimePicker/index.ts
@@ -1702,6 +1857,8 @@ function DateTimePickerRoot({
1702
1857
  value: controlledValue,
1703
1858
  defaultValue,
1704
1859
  onChange,
1860
+ onOpenChange,
1861
+ onCalendarNavigate,
1705
1862
  format = "24h",
1706
1863
  step = 1,
1707
1864
  disabled = false,
@@ -1736,6 +1893,9 @@ function DateTimePickerRoot({
1736
1893
  const [focusedDate, setFocusedDate] = react.useState(
1737
1894
  currentValue ?? adapter.today(displayTimezone)
1738
1895
  );
1896
+ useChangeEffect(isOpen, onOpenChange);
1897
+ const viewMonthStart = react.useMemo(() => adapter.startOfMonth(viewMonth), [viewMonth, adapter]);
1898
+ useChangeEffect(viewMonthStart, onCalendarNavigate);
1739
1899
  const isDisabled = typeof disabled === "boolean" ? disabled : false;
1740
1900
  const disabledRules = react.useMemo(
1741
1901
  () => Array.isArray(disabled) ? disabled : [],
@@ -1850,7 +2010,18 @@ function DateTimePickerRoot({
1850
2010
  pickerId,
1851
2011
  labels: mergedTimeLabels
1852
2012
  }),
1853
- [currentValue, setTime, format, step, displayTimezone, isDisabled, readOnly, currentTime, pickerId, mergedTimeLabels]
2013
+ [
2014
+ currentValue,
2015
+ setTime,
2016
+ format,
2017
+ step,
2018
+ displayTimezone,
2019
+ isDisabled,
2020
+ readOnly,
2021
+ currentTime,
2022
+ pickerId,
2023
+ mergedTimeLabels
2024
+ ]
1854
2025
  );
1855
2026
  return /* @__PURE__ */ jsxRuntime.jsx(DatePickerContext.Provider, { value: dateContext, children: /* @__PURE__ */ jsxRuntime.jsx(TimePickerContext.Provider, { value: timeContext, children }) });
1856
2027
  }
@@ -1925,6 +2096,228 @@ var DateTimePicker = Object.assign(DateTimePickerRoot, {
1925
2096
  MinuteList: TimePickerMinuteList,
1926
2097
  AmPmToggle: TimePickerAmPmToggle
1927
2098
  });
2099
+ function MonthPickerRoot(props) {
2100
+ const displayFormat = props.displayFormat ?? "yyyy-MM";
2101
+ return /* @__PURE__ */ jsxRuntime.jsx(DatePickerRoot, { ...props, displayFormat });
2102
+ }
2103
+ function MonthPickerGrid({ classNames, ...props }) {
2104
+ const ctx = useDatePickerContext("MonthPicker.Grid");
2105
+ const { adapter, viewMonth, locale, value, displayTimezone, labels } = ctx;
2106
+ const currentYear = adapter.getYear(viewMonth);
2107
+ const [valueYear, valueMonthZeroBased] = react.useMemo(() => {
2108
+ if (!value) return [null, null];
2109
+ try {
2110
+ const [y, m] = adapter.format(value, "yyyy-MM", displayTimezone).split("-").map(Number);
2111
+ return [y, m - 1];
2112
+ } catch {
2113
+ return [null, null];
2114
+ }
2115
+ }, [value, adapter, displayTimezone]);
2116
+ const today = adapter.today(displayTimezone);
2117
+ const todayYear = adapter.getYear(today);
2118
+ const todayMonth = adapter.getMonth(today);
2119
+ const navigateYear = react.useCallback(
2120
+ (direction) => {
2121
+ ctx.setViewMonth(adapter.addYears(viewMonth, direction));
2122
+ },
2123
+ [adapter, viewMonth, ctx]
2124
+ );
2125
+ const handleMonthSelect = react.useCallback(
2126
+ (monthIndex) => {
2127
+ const target = new Date(Date.UTC(currentYear, monthIndex, 1)).toISOString();
2128
+ ctx.selectDate(target);
2129
+ },
2130
+ [currentYear, ctx]
2131
+ );
2132
+ const months = Array.from({ length: 12 }, (_, i) => ({
2133
+ index: i,
2134
+ name: core.getMonthName(i, locale),
2135
+ isSelected: valueYear === currentYear && valueMonthZeroBased === i,
2136
+ isCurrent: todayYear === currentYear && todayMonth === i
2137
+ }));
2138
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.root, ...props, children: [
2139
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.header, children: [
2140
+ /* @__PURE__ */ jsxRuntime.jsx(
2141
+ "button",
2142
+ {
2143
+ type: "button",
2144
+ className: classNames?.navButton,
2145
+ onClick: () => navigateYear(-1),
2146
+ "aria-label": labels.prevYear,
2147
+ children: "<"
2148
+ }
2149
+ ),
2150
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: classNames?.title, children: currentYear }),
2151
+ /* @__PURE__ */ jsxRuntime.jsx(
2152
+ "button",
2153
+ {
2154
+ type: "button",
2155
+ className: classNames?.navButton,
2156
+ onClick: () => navigateYear(1),
2157
+ "aria-label": labels.nextYear,
2158
+ children: ">"
2159
+ }
2160
+ )
2161
+ ] }),
2162
+ /* @__PURE__ */ jsxRuntime.jsx("div", { role: "grid", "aria-label": `${currentYear} months`, className: classNames?.grid, children: Array.from({ length: 4 }, (_, rowIndex) => /* @__PURE__ */ jsxRuntime.jsx(
2163
+ "div",
2164
+ {
2165
+ role: "row",
2166
+ className: classNames?.gridRow,
2167
+ style: { display: "grid", gridTemplateColumns: "repeat(3, 1fr)" },
2168
+ children: months.slice(rowIndex * 3, rowIndex * 3 + 3).map((m) => {
2169
+ const monthClass = [
2170
+ classNames?.month,
2171
+ m.isSelected && classNames?.monthSelected,
2172
+ m.isCurrent && classNames?.monthCurrent
2173
+ ].filter(Boolean).join(" ") || void 0;
2174
+ return /* @__PURE__ */ jsxRuntime.jsx(
2175
+ "button",
2176
+ {
2177
+ type: "button",
2178
+ role: "gridcell",
2179
+ "aria-selected": m.isSelected || void 0,
2180
+ "aria-current": m.isCurrent ? "date" : void 0,
2181
+ "data-selected": m.isSelected || void 0,
2182
+ "data-current": m.isCurrent || void 0,
2183
+ className: monthClass,
2184
+ onClick: () => handleMonthSelect(m.index),
2185
+ children: m.name
2186
+ },
2187
+ m.index
2188
+ );
2189
+ })
2190
+ },
2191
+ rowIndex
2192
+ )) })
2193
+ ] });
2194
+ }
2195
+
2196
+ // src/components/MonthPicker/index.ts
2197
+ var MonthPicker = Object.assign(MonthPickerRoot, {
2198
+ Input: DatePickerInput,
2199
+ Trigger: DatePickerTrigger,
2200
+ Popover: DatePickerPopover,
2201
+ Grid: MonthPickerGrid
2202
+ });
2203
+ function YearPickerRoot(props) {
2204
+ const displayFormat = props.displayFormat ?? "yyyy";
2205
+ return /* @__PURE__ */ jsxRuntime.jsx(DatePickerRoot, { ...props, displayFormat });
2206
+ }
2207
+ function YearPickerGrid({ classNames, ...props }) {
2208
+ const ctx = useDatePickerContext("YearPicker.Grid");
2209
+ const { adapter, viewMonth, value, displayTimezone, labels } = ctx;
2210
+ const currentYear = adapter.getYear(viewMonth);
2211
+ const decadeStart = currentYear - currentYear % 12;
2212
+ const valueYear = react.useMemo(() => {
2213
+ if (!value) return null;
2214
+ try {
2215
+ return Number(adapter.format(value, "yyyy", displayTimezone));
2216
+ } catch {
2217
+ return null;
2218
+ }
2219
+ }, [value, adapter, displayTimezone]);
2220
+ const todayYear = adapter.getYear(adapter.today(displayTimezone));
2221
+ const navigateDecade = react.useCallback(
2222
+ (direction) => {
2223
+ ctx.setViewMonth(adapter.addYears(viewMonth, direction * 12));
2224
+ },
2225
+ [adapter, viewMonth, ctx]
2226
+ );
2227
+ const handleYearSelect = react.useCallback(
2228
+ (year) => {
2229
+ const target = new Date(Date.UTC(year, 0, 1)).toISOString();
2230
+ ctx.selectDate(target);
2231
+ },
2232
+ [ctx]
2233
+ );
2234
+ const years = Array.from({ length: 12 }, (_, i) => {
2235
+ const year = decadeStart + i;
2236
+ return {
2237
+ value: year,
2238
+ isSelected: year === valueYear,
2239
+ isCurrent: year === todayYear
2240
+ };
2241
+ });
2242
+ const rangeLabel = `${decadeStart}\u2013${decadeStart + 11}`;
2243
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.root, ...props, children: [
2244
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.header, children: [
2245
+ /* @__PURE__ */ jsxRuntime.jsx(
2246
+ "button",
2247
+ {
2248
+ type: "button",
2249
+ className: classNames?.navButton,
2250
+ onClick: () => navigateDecade(-1),
2251
+ "aria-label": labels.prevDecade,
2252
+ children: "<"
2253
+ }
2254
+ ),
2255
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: classNames?.title, children: rangeLabel }),
2256
+ /* @__PURE__ */ jsxRuntime.jsx(
2257
+ "button",
2258
+ {
2259
+ type: "button",
2260
+ className: classNames?.navButton,
2261
+ onClick: () => navigateDecade(1),
2262
+ "aria-label": labels.nextDecade,
2263
+ children: ">"
2264
+ }
2265
+ )
2266
+ ] }),
2267
+ /* @__PURE__ */ jsxRuntime.jsx("div", { role: "grid", "aria-label": rangeLabel, className: classNames?.grid, children: Array.from({ length: 4 }, (_, rowIndex) => /* @__PURE__ */ jsxRuntime.jsx(
2268
+ "div",
2269
+ {
2270
+ role: "row",
2271
+ className: classNames?.gridRow,
2272
+ style: { display: "grid", gridTemplateColumns: "repeat(3, 1fr)" },
2273
+ children: years.slice(rowIndex * 3, rowIndex * 3 + 3).map((y) => {
2274
+ const yearClass = [
2275
+ classNames?.year,
2276
+ y.isSelected && classNames?.yearSelected,
2277
+ y.isCurrent && classNames?.yearCurrent
2278
+ ].filter(Boolean).join(" ") || void 0;
2279
+ return /* @__PURE__ */ jsxRuntime.jsx(
2280
+ "button",
2281
+ {
2282
+ type: "button",
2283
+ role: "gridcell",
2284
+ "aria-selected": y.isSelected || void 0,
2285
+ "aria-current": y.isCurrent ? "date" : void 0,
2286
+ "data-selected": y.isSelected || void 0,
2287
+ "data-current": y.isCurrent || void 0,
2288
+ className: yearClass,
2289
+ onClick: () => handleYearSelect(y.value),
2290
+ children: y.value
2291
+ },
2292
+ y.value
2293
+ );
2294
+ })
2295
+ },
2296
+ rowIndex
2297
+ )) })
2298
+ ] });
2299
+ }
2300
+
2301
+ // src/components/YearPicker/index.ts
2302
+ var YearPicker = Object.assign(YearPickerRoot, {
2303
+ Input: DatePickerInput,
2304
+ Trigger: DatePickerTrigger,
2305
+ Popover: DatePickerPopover,
2306
+ Grid: YearPickerGrid
2307
+ });
2308
+ function WeekPickerRoot(props) {
2309
+ return /* @__PURE__ */ jsxRuntime.jsx(RangePickerRoot, { ...props });
2310
+ }
2311
+ function WeekPickerCalendar(props) {
2312
+ return /* @__PURE__ */ jsxRuntime.jsx(RangePickerCalendar, { ...props, selectionMode: "week" });
2313
+ }
2314
+
2315
+ // src/components/WeekPicker/index.ts
2316
+ var WeekPicker = Object.assign(WeekPickerRoot, {
2317
+ Input: RangePickerInput,
2318
+ Popover: RangePickerPopover,
2319
+ Calendar: WeekPickerCalendar
2320
+ });
1928
2321
  function useDatePicker(options = {}) {
1929
2322
  const {
1930
2323
  value: controlledValue,
@@ -2164,14 +2557,8 @@ function useTimePicker(options = {}) {
2164
2557
  },
2165
2558
  [format, period, setTime]
2166
2559
  );
2167
- const setMinute = react.useCallback(
2168
- (minute) => setTime({ minutes: minute }),
2169
- [setTime]
2170
- );
2171
- const setSecond = react.useCallback(
2172
- (second) => setTime({ seconds: second }),
2173
- [setTime]
2174
- );
2560
+ const setMinute = react.useCallback((minute) => setTime({ minutes: minute }), [setTime]);
2561
+ const setSecond = react.useCallback((second) => setTime({ seconds: second }), [setTime]);
2175
2562
  const setPeriod = react.useCallback(
2176
2563
  (newPeriod) => {
2177
2564
  if (format !== "12h") return;
@@ -2203,8 +2590,11 @@ Object.defineProperty(exports, "DateFnsAdapter", {
2203
2590
  });
2204
2591
  exports.DatePicker = DatePicker;
2205
2592
  exports.DateTimePicker = DateTimePicker;
2593
+ exports.MonthPicker = MonthPicker;
2206
2594
  exports.RangePicker = RangePicker;
2207
2595
  exports.TimePicker = TimePicker;
2596
+ exports.WeekPicker = WeekPicker;
2597
+ exports.YearPicker = YearPicker;
2208
2598
  exports.useDatePicker = useDatePicker;
2209
2599
  exports.useRangePicker = useRangePicker;
2210
2600
  exports.useTimePicker = useTimePicker;