@kalyx/react 1.0.0-rc.1 → 1.0.0-rc.11

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
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  'use strict';
2
3
 
3
4
  var react = require('react');
@@ -57,10 +58,10 @@ function DatePickerRoot({
57
58
  const currentValue = isControlled ? controlledValue ?? null : uncontrolledValue;
58
59
  const [isOpen, setIsOpen] = react.useState(false);
59
60
  const [viewMonth, setViewMonth] = react.useState(
60
- currentValue ?? adapter.today(displayTimezone)
61
+ () => currentValue ?? adapter.today(displayTimezone)
61
62
  );
62
63
  const [focusedDate, setFocusedDate] = react.useState(
63
- currentValue ?? adapter.today(displayTimezone)
64
+ () => currentValue ?? adapter.today(displayTimezone)
64
65
  );
65
66
  useChangeEffect(isOpen, onOpenChange);
66
67
  const viewMonthStart = react.useMemo(() => adapter.startOfMonth(viewMonth), [viewMonth, adapter]);
@@ -151,10 +152,15 @@ function DatePickerRoot({
151
152
  return /* @__PURE__ */ jsxRuntime.jsx(DatePickerContext.Provider, { value: contextValue, children });
152
153
  }
153
154
  var DatePickerInput = react.forwardRef(
154
- function DatePickerInput2({ format: formatProp, onClick, onBlur, onKeyDown, ...props }, ref) {
155
+ function DatePickerInput2({ format: formatProp, name, onClick, onBlur, onKeyDown, ...props }, ref) {
155
156
  const ctx = useDatePickerContext("DatePicker.Input");
156
157
  const displayFormat = formatProp ?? ctx.displayFormat;
157
158
  const [inputText, setInputText] = react.useState(null);
159
+ const isComposingRef = react.useRef(false);
160
+ react.useEffect(() => {
161
+ if (isComposingRef.current) return;
162
+ setInputText(null);
163
+ }, [ctx.value]);
158
164
  let formattedValue = "";
159
165
  if (ctx.value) {
160
166
  try {
@@ -171,47 +177,62 @@ var DatePickerInput = react.forwardRef(
171
177
  },
172
178
  [ctx, onClick]
173
179
  );
180
+ const commitText = react.useCallback(
181
+ (text) => {
182
+ if (!text) {
183
+ ctx.selectDate(null);
184
+ setInputText(null);
185
+ return true;
186
+ }
187
+ const parsed = core.parseInputValue(text, ctx.adapter);
188
+ if (parsed) {
189
+ ctx.selectDate(parsed);
190
+ setInputText(null);
191
+ return true;
192
+ }
193
+ return false;
194
+ },
195
+ [ctx]
196
+ );
174
197
  const handleBlur = react.useCallback(
175
198
  (e) => {
176
199
  if (inputText !== null) {
177
- const parsed = core.parseInputValue(inputText, ctx.adapter);
178
- if (parsed) {
179
- ctx.selectDate(parsed);
180
- }
200
+ commitText(inputText);
181
201
  setInputText(null);
182
202
  }
183
203
  onBlur?.(e);
184
204
  },
185
- [inputText, displayFormat, ctx, onBlur]
205
+ [inputText, commitText, onBlur]
186
206
  );
187
207
  const handleChange = react.useCallback(
188
208
  (e) => {
189
209
  const text = e.target.value;
190
210
  setInputText(text);
191
- if (!text) {
192
- ctx.selectDate(null);
193
- setInputText(null);
194
- return;
195
- }
196
- const parsed = core.parseInputValue(text, ctx.adapter);
197
- if (parsed) {
198
- ctx.selectDate(parsed);
199
- setInputText(null);
200
- }
211
+ if (isComposingRef.current) return;
212
+ commitText(text);
201
213
  },
202
- [displayFormat, ctx]
214
+ [commitText]
215
+ );
216
+ const handleCompositionStart = react.useCallback(() => {
217
+ isComposingRef.current = true;
218
+ }, []);
219
+ const handleCompositionEnd = react.useCallback(
220
+ (e) => {
221
+ isComposingRef.current = false;
222
+ commitText(e.target.value);
223
+ },
224
+ [commitText]
203
225
  );
204
226
  const handleKeyDown = react.useCallback(
205
227
  (e) => {
206
228
  if (e.key === "Escape") {
207
229
  ctx.close();
208
230
  } else if (e.key === "Enter") {
231
+ if (ctx.isOpen) e.preventDefault();
209
232
  if (inputText !== null) {
210
- const parsed = core.parseInputValue(inputText, ctx.adapter);
211
- if (parsed) {
212
- ctx.selectDate(parsed);
213
- setInputText(null);
214
- }
233
+ commitText(inputText);
234
+ } else if (ctx.isOpen) {
235
+ ctx.selectDate(ctx.focusedDate);
215
236
  }
216
237
  } else if (e.key === "ArrowDown" && !ctx.isOpen) {
217
238
  e.preventDefault();
@@ -219,36 +240,42 @@ var DatePickerInput = react.forwardRef(
219
240
  }
220
241
  onKeyDown?.(e);
221
242
  },
222
- [ctx, inputText, displayFormat, onKeyDown]
243
+ [ctx, inputText, commitText, onKeyDown]
223
244
  );
224
245
  const calendarId = `${ctx.pickerId}-calendar`;
225
- return /* @__PURE__ */ jsxRuntime.jsx(
226
- "input",
227
- {
228
- ref: (node) => {
229
- ctx.referenceRef.current = node;
230
- if (typeof ref === "function") ref(node);
231
- else if (ref) ref.current = node;
232
- },
233
- type: "text",
234
- role: "combobox",
235
- "aria-expanded": ctx.isOpen,
236
- "aria-haspopup": "dialog",
237
- "aria-controls": ctx.isOpen ? calendarId : void 0,
238
- "aria-autocomplete": "none",
239
- autoComplete: "off",
240
- value: displayValue,
241
- disabled: ctx.isDisabled || props.disabled,
242
- readOnly: ctx.isReadOnly,
243
- onChange: handleChange,
244
- onClick: handleClick,
245
- onBlur: handleBlur,
246
- onKeyDown: handleKeyDown,
247
- ...props
248
- }
249
- );
246
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
247
+ /* @__PURE__ */ jsxRuntime.jsx(
248
+ "input",
249
+ {
250
+ ref: (node) => {
251
+ ctx.referenceRef.current = node;
252
+ if (typeof ref === "function") ref(node);
253
+ else if (ref) ref.current = node;
254
+ },
255
+ type: "text",
256
+ role: "combobox",
257
+ "aria-expanded": ctx.isOpen,
258
+ "aria-haspopup": "dialog",
259
+ "aria-controls": ctx.isOpen ? calendarId : void 0,
260
+ "aria-autocomplete": "none",
261
+ autoComplete: "off",
262
+ value: displayValue,
263
+ disabled: ctx.isDisabled || props.disabled,
264
+ readOnly: ctx.isReadOnly,
265
+ onChange: handleChange,
266
+ onClick: handleClick,
267
+ onBlur: handleBlur,
268
+ onKeyDown: handleKeyDown,
269
+ onCompositionStart: handleCompositionStart,
270
+ onCompositionEnd: handleCompositionEnd,
271
+ ...props
272
+ }
273
+ ),
274
+ name ? /* @__PURE__ */ jsxRuntime.jsx("input", { type: "hidden", name, value: ctx.value ?? "" }) : null
275
+ ] });
250
276
  }
251
277
  );
278
+ DatePickerInput.displayName = "DatePicker.Input";
252
279
  var DatePickerTrigger = react.forwardRef(
253
280
  function DatePickerTrigger2({ onClick, children, ...props }, ref) {
254
281
  const ctx = useDatePickerContext("DatePicker.Trigger");
@@ -272,6 +299,7 @@ var DatePickerTrigger = react.forwardRef(
272
299
  tabIndex: 0,
273
300
  "aria-label": ctx.isOpen ? ctx.labels.triggerClose : ctx.labels.triggerOpen,
274
301
  "aria-expanded": ctx.isOpen,
302
+ "aria-haspopup": "dialog",
275
303
  "aria-controls": ctx.isOpen ? calendarId : void 0,
276
304
  disabled: ctx.isDisabled || props.disabled,
277
305
  onClick: handleClick,
@@ -302,6 +330,8 @@ var DatePickerTrigger = react.forwardRef(
302
330
  );
303
331
  }
304
332
  );
333
+ DatePickerTrigger.displayName = "DatePicker.Trigger";
334
+ var POPOVER_MIDDLEWARE = [react$1.offset(4), react$1.flip(), react$1.shift({ padding: 8 })];
305
335
  function usePopover({
306
336
  isOpen,
307
337
  close,
@@ -313,7 +343,7 @@ function usePopover({
313
343
  const { refs, floatingStyles, isPositioned } = react$1.useFloating({
314
344
  open: isOpen,
315
345
  placement,
316
- middleware: [react$1.offset(4), react$1.flip(), react$1.shift({ padding: 8 })],
346
+ middleware: POPOVER_MIDDLEWARE,
317
347
  whileElementsMounted: react$1.autoUpdate
318
348
  });
319
349
  react.useEffect(() => {
@@ -360,6 +390,24 @@ function usePopover({
360
390
  document.addEventListener("keydown", handleKeyDown);
361
391
  return () => document.removeEventListener("keydown", handleKeyDown);
362
392
  }, [isOpen, close]);
393
+ react.useEffect(() => {
394
+ if (!isOpen) return;
395
+ function handleFocusOut(e) {
396
+ const next = e.relatedTarget;
397
+ const floating = floatingRef.current;
398
+ const reference = referenceRef.current;
399
+ if (!next) return;
400
+ const insideFloating = floating?.contains(next) ?? false;
401
+ const insideReference = reference?.contains(next) ?? false;
402
+ if (!insideFloating && !insideReference) {
403
+ close();
404
+ }
405
+ }
406
+ const node = floatingRef.current;
407
+ if (!node) return;
408
+ node.addEventListener("focusout", handleFocusOut);
409
+ return () => node.removeEventListener("focusout", handleFocusOut);
410
+ }, [isOpen, close, referenceRef]);
363
411
  const setFloatingRef = react.useCallback(
364
412
  (node) => {
365
413
  floatingRef.current = node;
@@ -421,20 +469,36 @@ var srOnly = {
421
469
  function DatePickerCalendar({
422
470
  classNames,
423
471
  onTitleClick,
472
+ showWeekNumber = false,
473
+ fixedWeeks = false,
424
474
  ...props
425
475
  }) {
426
476
  const ctx = useDatePickerContext("DatePicker.Calendar");
427
477
  const gridRef = react.useRef(null);
428
478
  const [announcement, setAnnouncement] = react.useState("");
429
479
  const { adapter, viewMonth, focusedDate, weekStartsOn, disabled, locale, displayTimezone } = ctx;
430
- const weekdays = core.getWeekdayNames(locale, weekStartsOn);
431
- const weeks = core.getCalendarDays(viewMonth, adapter, {
432
- weekStartsOn,
433
- selected: ctx.value,
434
- focusedDate,
435
- disabled,
436
- timezone: displayTimezone
437
- });
480
+ const weekdays = react.useMemo(() => core.getWeekdayNames(locale, weekStartsOn), [locale, weekStartsOn]);
481
+ const weeks = react.useMemo(
482
+ () => core.getCalendarDays(viewMonth, adapter, {
483
+ weekStartsOn,
484
+ selected: ctx.value,
485
+ focusedDate,
486
+ disabled,
487
+ timezone: displayTimezone,
488
+ fixedWeeks
489
+ }),
490
+ [
491
+ viewMonth,
492
+ adapter,
493
+ weekStartsOn,
494
+ ctx.value,
495
+ focusedDate,
496
+ disabled,
497
+ displayTimezone,
498
+ fixedWeeks
499
+ ]
500
+ );
501
+ const thursdayIndex = weekStartsOn === 0 ? 4 : 3;
438
502
  const year = adapter.getYear(viewMonth);
439
503
  const month = adapter.getMonth(viewMonth);
440
504
  const title = core.formatMonthYear(year, month, locale);
@@ -514,6 +578,15 @@ function DatePickerCalendar({
514
578
  }
515
579
  if (newFocused) {
516
580
  e.preventDefault();
581
+ const skipStep = e.key === "ArrowLeft" || e.key === "ArrowUp" || e.key === "PageUp" || e.key === "Home" ? -1 : 1;
582
+ let attempts = 0;
583
+ while (core.isDateDisabled(newFocused, disabled, adapter) && attempts < 42) {
584
+ newFocused = adapter.addDays(newFocused, skipStep);
585
+ attempts++;
586
+ }
587
+ if (attempts >= 42) {
588
+ return;
589
+ }
517
590
  ctx.setFocusedDate(newFocused);
518
591
  if (!adapter.isSameMonth(newFocused, viewMonth)) {
519
592
  ctx.setViewMonth(newFocused);
@@ -561,62 +634,170 @@ function DatePickerCalendar({
561
634
  ref: gridRef,
562
635
  role: "grid",
563
636
  "aria-label": title,
637
+ "aria-rowcount": weeks.length + 1,
638
+ "aria-colcount": 7,
564
639
  className: classNames?.grid,
565
640
  onKeyDown: handleKeyDown,
566
641
  children: [
567
- /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsx("tr", { role: "row", children: weekdays.map((day) => /* @__PURE__ */ jsxRuntime.jsx(
568
- "th",
569
- {
570
- role: "columnheader",
571
- abbr: day.full,
572
- scope: "col",
573
- className: classNames?.weekdayHeader,
574
- children: day.short
575
- },
576
- day.short
577
- )) }) }),
578
- /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: weeks.map((week, weekIndex) => /* @__PURE__ */ jsxRuntime.jsx("tr", { role: "row", className: classNames?.gridRow, children: week.map((day) => {
579
- const dayClasses = [
580
- classNames?.day,
581
- day.isSelected && classNames?.daySelected,
582
- day.isToday && classNames?.dayToday,
583
- day.isDisabled && classNames?.dayDisabled,
584
- !day.isCurrentMonth && classNames?.dayOutsideMonth
585
- ].filter(Boolean).join(" ") || void 0;
586
- return /* @__PURE__ */ jsxRuntime.jsx(
587
- "td",
642
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { role: "row", "aria-rowindex": 1, children: [
643
+ showWeekNumber ? /* @__PURE__ */ jsxRuntime.jsx("th", { scope: "col", "aria-hidden": "true", className: classNames?.weekNumberHeader, children: "#" }) : null,
644
+ weekdays.map((day, colIndex) => /* @__PURE__ */ jsxRuntime.jsx(
645
+ "th",
588
646
  {
589
- role: "gridcell",
590
- "aria-selected": day.isSelected || void 0,
591
- "aria-disabled": day.isDisabled || void 0,
592
- "aria-current": day.isToday ? "date" : void 0,
593
- className: classNames?.gridCell,
594
- children: /* @__PURE__ */ jsxRuntime.jsx(
595
- "button",
647
+ role: "columnheader",
648
+ abbr: day.full,
649
+ scope: "col",
650
+ "aria-colindex": colIndex + 1,
651
+ className: classNames?.weekdayHeader,
652
+ children: day.short
653
+ },
654
+ day.short
655
+ ))
656
+ ] }) }),
657
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: weeks.map((week, weekIndex) => /* @__PURE__ */ jsxRuntime.jsxs(
658
+ "tr",
659
+ {
660
+ role: "row",
661
+ "aria-rowindex": weekIndex + 2,
662
+ className: classNames?.gridRow,
663
+ children: [
664
+ showWeekNumber ? /* @__PURE__ */ jsxRuntime.jsx(
665
+ "th",
596
666
  {
597
- type: "button",
598
- tabIndex: day.isFocused ? 0 : -1,
599
- disabled: day.isDisabled,
600
- "data-focused": day.isFocused || void 0,
601
- "data-selected": day.isSelected || void 0,
602
- "data-today": day.isToday || void 0,
603
- "data-outside-month": !day.isCurrentMonth || void 0,
604
- className: dayClasses,
605
- onClick: () => handleDayClick(day),
606
- "aria-label": safeFormatFullDate(day.isoString, locale),
607
- children: day.dayNumber
667
+ scope: "row",
668
+ "aria-hidden": "true",
669
+ className: classNames?.weekNumber,
670
+ "data-week-number": true,
671
+ children: core.getISOWeekNumber(week[thursdayIndex].isoString)
608
672
  }
609
- )
610
- },
611
- day.isoString
612
- );
613
- }) }, weekIndex)) })
673
+ ) : null,
674
+ week.map((day, colIndex) => {
675
+ const dayClasses = [
676
+ classNames?.day,
677
+ day.isSelected && classNames?.daySelected,
678
+ day.isToday && classNames?.dayToday,
679
+ day.isDisabled && classNames?.dayDisabled,
680
+ !day.isCurrentMonth && classNames?.dayOutsideMonth
681
+ ].filter(Boolean).join(" ") || void 0;
682
+ return /* @__PURE__ */ jsxRuntime.jsx(
683
+ "td",
684
+ {
685
+ role: "gridcell",
686
+ "aria-colindex": colIndex + 1,
687
+ "aria-selected": day.isSelected || void 0,
688
+ "aria-disabled": day.isDisabled || void 0,
689
+ "aria-current": day.isToday ? "date" : void 0,
690
+ className: classNames?.gridCell,
691
+ children: /* @__PURE__ */ jsxRuntime.jsx(
692
+ "button",
693
+ {
694
+ type: "button",
695
+ tabIndex: day.isFocused ? 0 : -1,
696
+ disabled: day.isDisabled,
697
+ "data-focused": day.isFocused || void 0,
698
+ "data-selected": day.isSelected || void 0,
699
+ "data-today": day.isToday || void 0,
700
+ "data-outside-month": !day.isCurrentMonth || void 0,
701
+ className: dayClasses,
702
+ onClick: () => handleDayClick(day),
703
+ "aria-label": safeFormatFullDate(day.isoString, locale),
704
+ children: day.dayNumber
705
+ }
706
+ )
707
+ },
708
+ day.isoString
709
+ );
710
+ })
711
+ ]
712
+ },
713
+ weekIndex
714
+ )) })
614
715
  ]
615
716
  }
616
717
  ),
617
718
  /* @__PURE__ */ jsxRuntime.jsx("div", { role: "status", "aria-live": "polite", "aria-atomic": "true", style: srOnly, children: announcement })
618
719
  ] });
619
720
  }
721
+ function isRangeFullyDisabled(start, end, rules, adapter) {
722
+ for (const rule of rules) {
723
+ if ("before" in rule && adapter.isBefore(end, rule.before)) return true;
724
+ if ("after" in rule && adapter.isAfter(start, rule.after)) return true;
725
+ }
726
+ return false;
727
+ }
728
+ function useGridState(opts) {
729
+ const { initialIndex, disabledFlags, onSelect, onPageUp, onPageDown, onEscape } = opts;
730
+ const gridRef = react.useRef(null);
731
+ const [focusedIndex, setFocusedIndex] = react.useState(initialIndex);
732
+ const handleKeyDown = (e) => {
733
+ let next = null;
734
+ let step = 1;
735
+ switch (e.key) {
736
+ case "ArrowLeft":
737
+ next = Math.max(0, focusedIndex - 1);
738
+ step = -1;
739
+ break;
740
+ case "ArrowRight":
741
+ next = Math.min(11, focusedIndex + 1);
742
+ break;
743
+ case "ArrowUp":
744
+ next = Math.max(0, focusedIndex - 3);
745
+ step = -1;
746
+ break;
747
+ case "ArrowDown":
748
+ next = Math.min(11, focusedIndex + 3);
749
+ break;
750
+ case "Home":
751
+ next = focusedIndex - focusedIndex % 3;
752
+ step = -1;
753
+ break;
754
+ case "End":
755
+ next = focusedIndex - focusedIndex % 3 + 2;
756
+ break;
757
+ case "PageUp":
758
+ e.preventDefault();
759
+ onPageUp();
760
+ return;
761
+ case "PageDown":
762
+ e.preventDefault();
763
+ onPageDown();
764
+ return;
765
+ case "Enter":
766
+ case " ":
767
+ e.preventDefault();
768
+ onSelect(focusedIndex);
769
+ return;
770
+ case "Escape":
771
+ onEscape();
772
+ return;
773
+ default:
774
+ return;
775
+ }
776
+ if (next === null) return;
777
+ e.preventDefault();
778
+ if (disabledFlags) {
779
+ let attempts = 0;
780
+ while (next >= 0 && next < 12 && disabledFlags[next] && attempts < 12) {
781
+ next += step;
782
+ attempts++;
783
+ }
784
+ if (next < 0 || next >= 12 || disabledFlags[next]) return;
785
+ }
786
+ if (next !== focusedIndex) setFocusedIndex(next);
787
+ };
788
+ react.useEffect(() => {
789
+ if (!disabledFlags || !disabledFlags[focusedIndex]) return;
790
+ const firstEnabled = disabledFlags.findIndex((d) => !d);
791
+ if (firstEnabled !== -1 && firstEnabled !== focusedIndex) {
792
+ setFocusedIndex(firstEnabled);
793
+ }
794
+ }, [disabledFlags, focusedIndex]);
795
+ react.useEffect(() => {
796
+ const btn = gridRef.current?.querySelector('[data-focused="true"]');
797
+ btn?.focus({ preventScroll: true });
798
+ }, [focusedIndex]);
799
+ return { gridRef, focusedIndex, handleKeyDown };
800
+ }
620
801
  function DatePickerMonthGrid({
621
802
  classNames,
622
803
  onSelect,
@@ -624,15 +805,18 @@ function DatePickerMonthGrid({
624
805
  ...props
625
806
  }) {
626
807
  const ctx = useDatePickerContext("DatePicker.MonthGrid");
627
- const { adapter, viewMonth, locale } = ctx;
808
+ const { adapter, viewMonth, locale, displayTimezone } = ctx;
628
809
  const currentYear = adapter.getYear(viewMonth);
629
810
  const currentMonth = adapter.getMonth(viewMonth);
630
- const todayMonth = adapter.getMonth(adapter.today());
631
- const todayYear = adapter.getYear(adapter.today());
811
+ const [today, setToday] = react.useState(null);
812
+ react.useEffect(() => {
813
+ setToday(adapter.today(displayTimezone));
814
+ }, [adapter, displayTimezone]);
815
+ const todayMonth = today !== null ? adapter.getMonth(today) : -1;
816
+ const todayYear = today !== null ? adapter.getYear(today) : -1;
632
817
  const navigateYear = react.useCallback(
633
818
  (direction) => {
634
- const newDate = adapter.addYears(viewMonth, direction);
635
- ctx.setViewMonth(newDate);
819
+ ctx.setViewMonth(adapter.addYears(viewMonth, direction));
636
820
  },
637
821
  [adapter, viewMonth, ctx]
638
822
  );
@@ -645,12 +829,13 @@ function DatePickerMonthGrid({
645
829
  },
646
830
  [currentYear, ctx, onSelect]
647
831
  );
648
- const months = Array.from({ length: 12 }, (_, i) => ({
649
- index: i,
650
- name: core.getMonthName(i, locale),
651
- isSelected: i === currentMonth,
652
- isCurrent: i === todayMonth && currentYear === todayYear
653
- }));
832
+ const { gridRef, focusedIndex, handleKeyDown } = useGridState({
833
+ initialIndex: currentMonth,
834
+ onSelect: handleMonthSelect,
835
+ onPageUp: () => navigateYear(-1),
836
+ onPageDown: () => navigateYear(1),
837
+ onEscape: ctx.close
838
+ });
654
839
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.root, ...props, children: [
655
840
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.header, children: [
656
841
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -678,30 +863,37 @@ function DatePickerMonthGrid({
678
863
  /* @__PURE__ */ jsxRuntime.jsx(
679
864
  "div",
680
865
  {
866
+ ref: gridRef,
681
867
  role: "grid",
682
868
  "aria-label": `${currentYear} months`,
683
869
  className: classNames?.grid,
684
870
  style: { display: "grid", gridTemplateColumns: "repeat(3, 1fr)" },
685
- children: months.map((m) => {
686
- const monthClass = [
871
+ onKeyDown: handleKeyDown,
872
+ children: Array.from({ length: 12 }, (_, i) => {
873
+ const isSelected = i === currentMonth;
874
+ const isCurrent = i === todayMonth && currentYear === todayYear;
875
+ const isFocused = i === focusedIndex;
876
+ const cls = [
687
877
  classNames?.month,
688
- m.isSelected && classNames?.monthSelected,
689
- m.isCurrent && classNames?.monthCurrent
878
+ isSelected && classNames?.monthSelected,
879
+ isCurrent && classNames?.monthCurrent
690
880
  ].filter(Boolean).join(" ") || void 0;
691
881
  return /* @__PURE__ */ jsxRuntime.jsx(
692
882
  "button",
693
883
  {
694
884
  type: "button",
695
885
  role: "gridcell",
696
- "aria-selected": m.isSelected || void 0,
697
- "aria-current": m.isCurrent ? "date" : void 0,
698
- "data-selected": m.isSelected || void 0,
699
- "data-current": m.isCurrent || void 0,
700
- className: monthClass,
701
- onClick: () => handleMonthSelect(m.index),
702
- children: m.name
886
+ tabIndex: isFocused ? 0 : -1,
887
+ "aria-selected": isSelected || void 0,
888
+ "aria-current": isCurrent ? "date" : void 0,
889
+ "data-selected": isSelected || void 0,
890
+ "data-current": isCurrent || void 0,
891
+ "data-focused": isFocused || void 0,
892
+ className: cls,
893
+ onClick: () => handleMonthSelect(i),
894
+ children: core.getMonthName(i, locale)
703
895
  },
704
- m.index
896
+ i
705
897
  );
706
898
  })
707
899
  }
@@ -710,38 +902,38 @@ function DatePickerMonthGrid({
710
902
  }
711
903
  function DatePickerYearGrid({ classNames, onSelect, ...props }) {
712
904
  const ctx = useDatePickerContext("DatePicker.YearGrid");
713
- const { adapter, viewMonth } = ctx;
905
+ const { adapter, viewMonth, displayTimezone } = ctx;
714
906
  const currentYear = adapter.getYear(viewMonth);
715
- const todayYear = adapter.getYear(adapter.today());
907
+ const [today, setToday] = react.useState(null);
908
+ react.useEffect(() => {
909
+ setToday(adapter.today(displayTimezone));
910
+ }, [adapter, displayTimezone]);
911
+ const todayYear = today !== null ? adapter.getYear(today) : -1;
716
912
  const decadeStart = currentYear - currentYear % 12;
717
913
  const navigateDecade = react.useCallback(
718
914
  (direction) => {
719
- const newDate = adapter.addYears(viewMonth, direction * 12);
720
- ctx.setViewMonth(newDate);
915
+ ctx.setViewMonth(adapter.addYears(viewMonth, direction * 12));
721
916
  },
722
917
  [adapter, viewMonth, ctx]
723
918
  );
724
919
  const handleYearSelect = react.useCallback(
725
- (year) => {
920
+ (indexInDecade) => {
921
+ const year = decadeStart + indexInDecade;
726
922
  const currentMonth = adapter.getMonth(viewMonth);
727
923
  const target = new Date(Date.UTC(year, currentMonth, 1)).toISOString();
728
924
  ctx.setViewMonth(target);
729
925
  ctx.setFocusedDate(target);
730
926
  onSelect?.();
731
927
  },
732
- [adapter, viewMonth, ctx, onSelect]
733
- );
734
- const years = react.useMemo(
735
- () => Array.from({ length: 12 }, (_, i) => {
736
- const year = decadeStart + i;
737
- return {
738
- value: year,
739
- isSelected: year === currentYear,
740
- isCurrent: year === todayYear
741
- };
742
- }),
743
- [decadeStart, currentYear, todayYear]
744
- );
928
+ [adapter, viewMonth, ctx, onSelect, decadeStart]
929
+ );
930
+ const { gridRef, focusedIndex, handleKeyDown } = useGridState({
931
+ initialIndex: currentYear - decadeStart,
932
+ onSelect: handleYearSelect,
933
+ onPageUp: () => navigateDecade(-1),
934
+ onPageDown: () => navigateDecade(1),
935
+ onEscape: ctx.close
936
+ });
745
937
  const rangeLabel = `${decadeStart}\u2013${decadeStart + 11}`;
746
938
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.root, ...props, children: [
747
939
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.header, children: [
@@ -770,30 +962,38 @@ function DatePickerYearGrid({ classNames, onSelect, ...props }) {
770
962
  /* @__PURE__ */ jsxRuntime.jsx(
771
963
  "div",
772
964
  {
965
+ ref: gridRef,
773
966
  role: "grid",
774
967
  "aria-label": rangeLabel,
775
968
  className: classNames?.grid,
776
969
  style: { display: "grid", gridTemplateColumns: "repeat(3, 1fr)" },
777
- children: years.map((y) => {
778
- const yearClass = [
970
+ onKeyDown: handleKeyDown,
971
+ children: Array.from({ length: 12 }, (_, i) => {
972
+ const year = decadeStart + i;
973
+ const isSelected = year === currentYear;
974
+ const isCurrent = year === todayYear;
975
+ const isFocused = i === focusedIndex;
976
+ const cls = [
779
977
  classNames?.year,
780
- y.isSelected && classNames?.yearSelected,
781
- y.isCurrent && classNames?.yearCurrent
978
+ isSelected && classNames?.yearSelected,
979
+ isCurrent && classNames?.yearCurrent
782
980
  ].filter(Boolean).join(" ") || void 0;
783
981
  return /* @__PURE__ */ jsxRuntime.jsx(
784
982
  "button",
785
983
  {
786
984
  type: "button",
787
985
  role: "gridcell",
788
- "aria-selected": y.isSelected || void 0,
789
- "aria-current": y.isCurrent ? "date" : void 0,
790
- "data-selected": y.isSelected || void 0,
791
- "data-current": y.isCurrent || void 0,
792
- className: yearClass,
793
- onClick: () => handleYearSelect(y.value),
794
- children: y.value
986
+ tabIndex: isFocused ? 0 : -1,
987
+ "aria-selected": isSelected || void 0,
988
+ "aria-current": isCurrent ? "date" : void 0,
989
+ "data-selected": isSelected || void 0,
990
+ "data-current": isCurrent || void 0,
991
+ "data-focused": isFocused || void 0,
992
+ className: cls,
993
+ onClick: () => handleYearSelect(i),
994
+ children: year
795
995
  },
796
- y.value
996
+ i
797
997
  );
798
998
  })
799
999
  }
@@ -866,8 +1066,7 @@ function DatePickerPreset({
866
1066
  "button",
867
1067
  {
868
1068
  type: "button",
869
- role: "option",
870
- "aria-selected": isActive,
1069
+ "aria-pressed": isActive,
871
1070
  "data-active": isActive || void 0,
872
1071
  disabled: ctx.isDisabled,
873
1072
  onClick: handleClick,
@@ -904,6 +1103,17 @@ function useRangePickerContext(componentName) {
904
1103
  return context;
905
1104
  }
906
1105
  var EMPTY_RANGE = { start: null, end: null };
1106
+ var SR_ONLY = {
1107
+ position: "absolute",
1108
+ width: 1,
1109
+ height: 1,
1110
+ padding: 0,
1111
+ margin: -1,
1112
+ overflow: "hidden",
1113
+ clip: "rect(0, 0, 0, 0)",
1114
+ whiteSpace: "nowrap",
1115
+ border: 0
1116
+ };
907
1117
  function RangePickerRoot({
908
1118
  value: controlledValue,
909
1119
  defaultValue,
@@ -930,11 +1140,13 @@ function RangePickerRoot({
930
1140
  const [isOpen, setIsOpen] = react.useState(false);
931
1141
  const [selectingTarget, setSelectingTarget] = react.useState("start");
932
1142
  const [hoverDate, setHoverDate] = react.useState(null);
1143
+ const [announcement, setAnnouncement] = react.useState("");
1144
+ const announce = react.useCallback((message) => setAnnouncement(message), []);
933
1145
  const [viewMonth, setViewMonth] = react.useState(
934
- currentValue.start ?? adapter.today(displayTimezone)
1146
+ () => currentValue.start ?? adapter.today(displayTimezone)
935
1147
  );
936
1148
  const [focusedDate, setFocusedDate] = react.useState(
937
- currentValue.start ?? adapter.today(displayTimezone)
1149
+ () => currentValue.start ?? adapter.today(displayTimezone)
938
1150
  );
939
1151
  useChangeEffect(isOpen, onOpenChange);
940
1152
  const viewMonthStart = react.useMemo(() => adapter.startOfMonth(viewMonth), [viewMonth, adapter]);
@@ -1032,7 +1244,8 @@ function RangePickerRoot({
1032
1244
  isDisabled,
1033
1245
  isReadOnly: readOnly,
1034
1246
  pickerId,
1035
- labels: mergedLabels
1247
+ labels: mergedLabels,
1248
+ announce
1036
1249
  }),
1037
1250
  [
1038
1251
  currentValue,
@@ -1055,10 +1268,14 @@ function RangePickerRoot({
1055
1268
  isDisabled,
1056
1269
  readOnly,
1057
1270
  pickerId,
1058
- mergedLabels
1271
+ mergedLabels,
1272
+ announce
1059
1273
  ]
1060
1274
  );
1061
- return /* @__PURE__ */ jsxRuntime.jsx(RangePickerContext.Provider, { value: contextValue, children });
1275
+ return /* @__PURE__ */ jsxRuntime.jsxs(RangePickerContext.Provider, { value: contextValue, children: [
1276
+ children,
1277
+ /* @__PURE__ */ jsxRuntime.jsx("div", { role: "status", "aria-live": "polite", "aria-atomic": "true", style: SR_ONLY, children: announcement })
1278
+ ] });
1062
1279
  }
1063
1280
  var RangePickerInput = react.forwardRef(
1064
1281
  function RangePickerInput2({ part, format: formatProp, onClick, onKeyDown, ...props }, ref) {
@@ -1084,6 +1301,8 @@ var RangePickerInput = react.forwardRef(
1084
1301
  (e) => {
1085
1302
  if (e.key === "Escape") {
1086
1303
  ctx.close();
1304
+ } else if (e.key === "Enter" && ctx.isOpen) {
1305
+ e.preventDefault();
1087
1306
  } else if (e.key === "ArrowDown" && !ctx.isOpen) {
1088
1307
  e.preventDefault();
1089
1308
  ctx.open();
@@ -1120,6 +1339,7 @@ var RangePickerInput = react.forwardRef(
1120
1339
  );
1121
1340
  }
1122
1341
  );
1342
+ RangePickerInput.displayName = "RangePicker.Input";
1123
1343
  function RangePickerPopover({ children, ...props }) {
1124
1344
  const ctx = useRangePickerContext("RangePicker.Popover");
1125
1345
  const calendarId = `${ctx.pickerId}-calendar`;
@@ -1155,25 +1375,15 @@ function safeFormatFullDate2(iso, locale) {
1155
1375
  return iso;
1156
1376
  }
1157
1377
  }
1158
- var srOnly2 = {
1159
- position: "absolute",
1160
- width: "1px",
1161
- height: "1px",
1162
- padding: 0,
1163
- margin: "-1px",
1164
- overflow: "hidden",
1165
- clip: "rect(0, 0, 0, 0)",
1166
- whiteSpace: "nowrap",
1167
- border: 0
1168
- };
1169
1378
  function RangePickerCalendar({
1170
1379
  classNames,
1171
1380
  selectionMode = "range",
1381
+ showWeekNumber = false,
1382
+ fixedWeeks = false,
1172
1383
  ...props
1173
1384
  }) {
1174
1385
  const ctx = useRangePickerContext("RangePicker.Calendar");
1175
1386
  const gridRef = react.useRef(null);
1176
- const [announcement, setAnnouncement] = react.useState("");
1177
1387
  const {
1178
1388
  adapter,
1179
1389
  viewMonth,
@@ -1186,15 +1396,30 @@ function RangePickerCalendar({
1186
1396
  displayTimezone
1187
1397
  } = ctx;
1188
1398
  const { locale } = ctx;
1189
- const weekdays = core.getWeekdayNames(locale, weekStartsOn);
1190
- const weeks = core.getCalendarDays(viewMonth, adapter, {
1191
- weekStartsOn,
1192
- focusedDate,
1193
- disabled,
1194
- range: value,
1195
- rangeHover: hoverDate,
1196
- timezone: displayTimezone
1197
- });
1399
+ const weekdays = react.useMemo(() => core.getWeekdayNames(locale, weekStartsOn), [locale, weekStartsOn]);
1400
+ const weeks = react.useMemo(
1401
+ () => core.getCalendarDays(viewMonth, adapter, {
1402
+ weekStartsOn,
1403
+ focusedDate,
1404
+ disabled,
1405
+ range: value,
1406
+ rangeHover: hoverDate,
1407
+ timezone: displayTimezone,
1408
+ fixedWeeks
1409
+ }),
1410
+ [
1411
+ viewMonth,
1412
+ adapter,
1413
+ weekStartsOn,
1414
+ focusedDate,
1415
+ disabled,
1416
+ value,
1417
+ hoverDate,
1418
+ displayTimezone,
1419
+ fixedWeeks
1420
+ ]
1421
+ );
1422
+ const thursdayIndex = weekStartsOn === 0 ? 4 : 3;
1198
1423
  const year = adapter.getYear(viewMonth);
1199
1424
  const month = adapter.getMonth(viewMonth);
1200
1425
  const title = core.formatMonthYear(year, month, locale);
@@ -1210,7 +1435,7 @@ function RangePickerCalendar({
1210
1435
  ctx.setFocusedDate(adapter.startOfMonth(newMonth));
1211
1436
  const y = adapter.getYear(newMonth);
1212
1437
  const m = adapter.getMonth(newMonth);
1213
- setAnnouncement(core.formatMonthYear(y, m, locale));
1438
+ ctx.announce(core.formatMonthYear(y, m, locale));
1214
1439
  },
1215
1440
  [adapter, viewMonth, ctx, locale]
1216
1441
  );
@@ -1222,15 +1447,27 @@ function RangePickerCalendar({
1222
1447
  const range = { start: weekStart, end: weekEnd };
1223
1448
  ctx.setRange(range);
1224
1449
  ctx.close();
1225
- setAnnouncement(
1226
- `${safeFormatFullDate2(weekStart, locale)} \u2013 ${safeFormatFullDate2(weekEnd, locale)}`
1450
+ ctx.announce(
1451
+ `${ctx.labels.rangeSelected}: ${safeFormatFullDate2(weekStart, locale)} \u2013 ${safeFormatFullDate2(weekEnd, locale)}`
1227
1452
  );
1228
1453
  } else {
1454
+ const wasPickingStart = selectingTarget === "start";
1455
+ const previousStart = value.start;
1229
1456
  ctx.selectDate(iso);
1230
- setAnnouncement(safeFormatFullDate2(iso, locale));
1457
+ const formatted = safeFormatFullDate2(iso, locale);
1458
+ if (wasPickingStart) {
1459
+ ctx.announce(`${formatted}. ${ctx.labels.selectingEnd}`);
1460
+ } else if (previousStart) {
1461
+ const [start, end] = adapter.isBefore(iso, previousStart) ? [iso, previousStart] : [previousStart, iso];
1462
+ ctx.announce(
1463
+ `${ctx.labels.rangeSelected}: ${safeFormatFullDate2(start, locale)} \u2013 ${safeFormatFullDate2(end, locale)}`
1464
+ );
1465
+ } else {
1466
+ ctx.announce(formatted);
1467
+ }
1231
1468
  }
1232
1469
  },
1233
- [selectionMode, adapter, weekStartsOn, ctx, locale]
1470
+ [selectionMode, adapter, weekStartsOn, ctx, locale, selectingTarget, value.start]
1234
1471
  );
1235
1472
  const handleDayClick = react.useCallback(
1236
1473
  (day) => {
@@ -1294,6 +1531,13 @@ function RangePickerCalendar({
1294
1531
  }
1295
1532
  if (newFocused) {
1296
1533
  e.preventDefault();
1534
+ const skipStep = e.key === "ArrowLeft" || e.key === "ArrowUp" || e.key === "PageUp" || e.key === "Home" ? -1 : 1;
1535
+ let attempts = 0;
1536
+ while (core.isDateDisabled(newFocused, disabled, adapter) && attempts < 42) {
1537
+ newFocused = adapter.addDays(newFocused, skipStep);
1538
+ attempts++;
1539
+ }
1540
+ if (attempts >= 42) return;
1297
1541
  ctx.setFocusedDate(newFocused);
1298
1542
  if (!adapter.isSameMonth(newFocused, viewMonth)) {
1299
1543
  ctx.setViewMonth(newFocused);
@@ -1346,67 +1590,93 @@ function RangePickerCalendar({
1346
1590
  ref: gridRef,
1347
1591
  role: "grid",
1348
1592
  "aria-label": title,
1349
- "aria-multiselectable": "true",
1593
+ "aria-rowcount": weeks.length + 1,
1594
+ "aria-colcount": 7,
1350
1595
  className: classNames?.grid,
1351
1596
  onKeyDown: handleKeyDown,
1352
1597
  children: [
1353
- /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsx("tr", { role: "row", children: weekdays.map((day) => /* @__PURE__ */ jsxRuntime.jsx(
1354
- "th",
1355
- {
1356
- role: "columnheader",
1357
- abbr: day.full,
1358
- scope: "col",
1359
- className: classNames?.weekdayHeader,
1360
- children: day.short
1361
- },
1362
- day.short
1363
- )) }) }),
1364
- /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: weeks.map((week, weekIndex) => /* @__PURE__ */ jsxRuntime.jsx("tr", { role: "row", className: classNames?.gridRow, children: week.map((day) => {
1365
- const dayClasses = [
1366
- classNames?.day,
1367
- day.isRangeStart && classNames?.dayRangeStart,
1368
- day.isRangeEnd && classNames?.dayRangeEnd,
1369
- day.isInRange && classNames?.dayInRange,
1370
- day.isToday && classNames?.dayToday,
1371
- day.isDisabled && classNames?.dayDisabled,
1372
- !day.isCurrentMonth && classNames?.dayOutsideMonth
1373
- ].filter(Boolean).join(" ") || void 0;
1374
- const isSelected = selectionMode === "week" ? day.isRangeStart || day.isRangeEnd || day.isInRange : day.isRangeStart || day.isRangeEnd;
1375
- return /* @__PURE__ */ jsxRuntime.jsx(
1376
- "td",
1598
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { role: "row", "aria-rowindex": 1, children: [
1599
+ showWeekNumber ? /* @__PURE__ */ jsxRuntime.jsx("th", { scope: "col", "aria-hidden": "true", className: classNames?.weekNumberHeader, children: "#" }) : null,
1600
+ weekdays.map((day, colIndex) => /* @__PURE__ */ jsxRuntime.jsx(
1601
+ "th",
1377
1602
  {
1378
- role: "gridcell",
1379
- "aria-selected": isSelected || void 0,
1380
- "aria-disabled": day.isDisabled || void 0,
1381
- "aria-current": day.isToday ? "date" : void 0,
1382
- className: classNames?.gridCell,
1383
- children: /* @__PURE__ */ jsxRuntime.jsx(
1384
- "button",
1603
+ role: "columnheader",
1604
+ abbr: day.full,
1605
+ scope: "col",
1606
+ "aria-colindex": colIndex + 1,
1607
+ className: classNames?.weekdayHeader,
1608
+ children: day.short
1609
+ },
1610
+ day.short
1611
+ ))
1612
+ ] }) }),
1613
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: weeks.map((week, weekIndex) => /* @__PURE__ */ jsxRuntime.jsxs(
1614
+ "tr",
1615
+ {
1616
+ role: "row",
1617
+ "aria-rowindex": weekIndex + 2,
1618
+ className: classNames?.gridRow,
1619
+ children: [
1620
+ showWeekNumber ? /* @__PURE__ */ jsxRuntime.jsx(
1621
+ "th",
1385
1622
  {
1386
- type: "button",
1387
- tabIndex: day.isFocused ? 0 : -1,
1388
- disabled: day.isDisabled,
1389
- "data-focused": day.isFocused || void 0,
1390
- "data-range-start": day.isRangeStart || void 0,
1391
- "data-range-end": day.isRangeEnd || void 0,
1392
- "data-in-range": day.isInRange || void 0,
1393
- "data-today": day.isToday || void 0,
1394
- "data-outside-month": !day.isCurrentMonth || void 0,
1395
- className: dayClasses,
1396
- onClick: () => handleDayClick(day),
1397
- onMouseEnter: () => handleDayMouseEnter(day),
1398
- "aria-label": safeFormatFullDate2(day.isoString, locale),
1399
- children: day.dayNumber
1623
+ scope: "row",
1624
+ "aria-hidden": "true",
1625
+ className: classNames?.weekNumber,
1626
+ "data-week-number": true,
1627
+ children: core.getISOWeekNumber(week[thursdayIndex].isoString)
1400
1628
  }
1401
- )
1402
- },
1403
- day.isoString
1404
- );
1405
- }) }, weekIndex)) })
1629
+ ) : null,
1630
+ week.map((day, colIndex) => {
1631
+ const dayClasses = [
1632
+ classNames?.day,
1633
+ day.isRangeStart && classNames?.dayRangeStart,
1634
+ day.isRangeEnd && classNames?.dayRangeEnd,
1635
+ day.isInRange && classNames?.dayInRange,
1636
+ day.isToday && classNames?.dayToday,
1637
+ day.isDisabled && classNames?.dayDisabled,
1638
+ !day.isCurrentMonth && classNames?.dayOutsideMonth
1639
+ ].filter(Boolean).join(" ") || void 0;
1640
+ const isSelected = selectionMode === "week" ? day.isRangeStart || day.isRangeEnd || day.isInRange : day.isRangeStart || day.isRangeEnd;
1641
+ return /* @__PURE__ */ jsxRuntime.jsx(
1642
+ "td",
1643
+ {
1644
+ role: "gridcell",
1645
+ "aria-colindex": colIndex + 1,
1646
+ "aria-selected": isSelected || void 0,
1647
+ "aria-disabled": day.isDisabled || void 0,
1648
+ "aria-current": day.isToday ? "date" : void 0,
1649
+ className: classNames?.gridCell,
1650
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1651
+ "button",
1652
+ {
1653
+ type: "button",
1654
+ tabIndex: day.isFocused ? 0 : -1,
1655
+ disabled: day.isDisabled,
1656
+ "data-focused": day.isFocused || void 0,
1657
+ "data-range-start": day.isRangeStart || void 0,
1658
+ "data-range-end": day.isRangeEnd || void 0,
1659
+ "data-in-range": day.isInRange || void 0,
1660
+ "data-today": day.isToday || void 0,
1661
+ "data-outside-month": !day.isCurrentMonth || void 0,
1662
+ className: dayClasses,
1663
+ onClick: () => handleDayClick(day),
1664
+ onMouseEnter: () => handleDayMouseEnter(day),
1665
+ "aria-label": safeFormatFullDate2(day.isoString, locale),
1666
+ children: day.dayNumber
1667
+ }
1668
+ )
1669
+ },
1670
+ day.isoString
1671
+ );
1672
+ })
1673
+ ]
1674
+ },
1675
+ weekIndex
1676
+ )) })
1406
1677
  ]
1407
1678
  }
1408
- ),
1409
- /* @__PURE__ */ jsxRuntime.jsx("div", { role: "status", "aria-live": "polite", "aria-atomic": "true", style: srOnly2, children: announcement })
1679
+ )
1410
1680
  ] });
1411
1681
  }
1412
1682
  function RangePickerPresets({ classNames, children, ...props }) {
@@ -1464,41 +1734,33 @@ function RangePickerPreset({
1464
1734
  ...props
1465
1735
  }) {
1466
1736
  const ctx = useRangePickerContext("RangePicker.Preset");
1737
+ const resolved = react.useMemo(() => {
1738
+ if (directRange) return directRange;
1739
+ if (presetKey)
1740
+ return resolvePreset(presetKey, ctx.adapter.today(ctx.displayTimezone), ctx.adapter);
1741
+ return null;
1742
+ }, [directRange, presetKey, ctx.adapter, ctx.displayTimezone]);
1467
1743
  const handleClick = react.useCallback(
1468
1744
  (e) => {
1469
1745
  if (ctx.isDisabled || ctx.isReadOnly) return;
1470
- let resolved;
1471
- if (directRange) {
1472
- resolved = directRange;
1473
- } else if (presetKey) {
1474
- resolved = resolvePreset(presetKey, ctx.adapter.today(), ctx.adapter);
1475
- } else {
1476
- return;
1477
- }
1746
+ if (!resolved) return;
1478
1747
  ctx.setRange(resolved);
1479
1748
  ctx.close();
1480
1749
  onClick?.(e);
1481
1750
  },
1482
- [ctx, presetKey, directRange, onClick]
1751
+ [ctx, resolved, onClick]
1483
1752
  );
1484
- const isActive = (() => {
1485
- if (!ctx.value.start || !ctx.value.end) return false;
1486
- let target;
1487
- if (directRange) {
1488
- target = directRange;
1489
- } else if (presetKey) {
1490
- target = resolvePreset(presetKey, ctx.adapter.today(), ctx.adapter);
1491
- } else {
1753
+ const isActive = react.useMemo(() => {
1754
+ if (!ctx.value.start || !ctx.value.end || !resolved || !resolved.start || !resolved.end) {
1492
1755
  return false;
1493
1756
  }
1494
- return target.start !== null && target.end !== null && ctx.adapter.isSameDay(ctx.value.start, target.start) && ctx.adapter.isSameDay(ctx.value.end, target.end);
1495
- })();
1757
+ return ctx.adapter.isSameDay(ctx.value.start, resolved.start) && ctx.adapter.isSameDay(ctx.value.end, resolved.end);
1758
+ }, [ctx.value.start, ctx.value.end, ctx.adapter, resolved]);
1496
1759
  return /* @__PURE__ */ jsxRuntime.jsx(
1497
1760
  "button",
1498
1761
  {
1499
1762
  type: "button",
1500
- role: "option",
1501
- "aria-selected": isActive,
1763
+ "aria-pressed": isActive,
1502
1764
  "data-active": isActive || void 0,
1503
1765
  disabled: ctx.isDisabled,
1504
1766
  onClick: handleClick,
@@ -1531,9 +1793,6 @@ function useTimePickerContext(componentName) {
1531
1793
  }
1532
1794
  return context;
1533
1795
  }
1534
- function getDefaultIso() {
1535
- return core.DateFnsAdapter.today();
1536
- }
1537
1796
  function TimePickerRoot({
1538
1797
  value: controlledValue,
1539
1798
  defaultValue,
@@ -1544,6 +1803,7 @@ function TimePickerRoot({
1544
1803
  displayTimezone,
1545
1804
  disabled = false,
1546
1805
  readOnly = false,
1806
+ filterTime,
1547
1807
  labels: labelsProp,
1548
1808
  children
1549
1809
  }) {
@@ -1557,21 +1817,21 @@ function TimePickerRoot({
1557
1817
  defaultValue ?? null
1558
1818
  );
1559
1819
  const currentValue = isControlled ? controlledValue ?? null : uncontrolledValue;
1560
- const baseIso = currentValue ?? getDefaultIso();
1561
- const currentTime = react.useMemo(
1562
- () => displayTimezone ? core.getTimeInTimezone(baseIso, displayTimezone) : core.getTime(baseIso),
1563
- [baseIso, displayTimezone]
1564
- );
1820
+ const currentTime = react.useMemo(() => {
1821
+ if (!currentValue) return { hours: 0, minutes: 0, seconds: 0 };
1822
+ return displayTimezone ? core.getTimeInTimezone(currentValue, displayTimezone) : core.getTime(currentValue);
1823
+ }, [currentValue, displayTimezone]);
1565
1824
  const setTime = react.useCallback(
1566
1825
  (partial) => {
1567
1826
  if (disabled || readOnly) return;
1568
- const newIso = displayTimezone ? core.setTimeInTimezone(baseIso, partial, displayTimezone) : core.setTime(baseIso, partial);
1827
+ const base = currentValue ?? core.DateFnsAdapter.today(displayTimezone);
1828
+ const newIso = displayTimezone ? core.setTimeInTimezone(base, partial, displayTimezone) : core.setTime(base, partial);
1569
1829
  if (!isControlled) {
1570
1830
  setUncontrolledValue(newIso);
1571
1831
  }
1572
1832
  onChange?.(newIso);
1573
1833
  },
1574
- [disabled, readOnly, baseIso, isControlled, onChange, displayTimezone]
1834
+ [disabled, readOnly, currentValue, displayTimezone, isControlled, onChange]
1575
1835
  );
1576
1836
  const contextValue = react.useMemo(
1577
1837
  () => ({
@@ -1585,7 +1845,8 @@ function TimePickerRoot({
1585
1845
  isReadOnly: readOnly,
1586
1846
  currentTime,
1587
1847
  pickerId,
1588
- labels: mergedLabels
1848
+ labels: mergedLabels,
1849
+ filterTime
1589
1850
  }),
1590
1851
  [
1591
1852
  currentValue,
@@ -1598,7 +1859,8 @@ function TimePickerRoot({
1598
1859
  readOnly,
1599
1860
  currentTime,
1600
1861
  pickerId,
1601
- mergedLabels
1862
+ mergedLabels,
1863
+ filterTime
1602
1864
  ]
1603
1865
  );
1604
1866
  return /* @__PURE__ */ jsxRuntime.jsx(TimePickerContext.Provider, { value: contextValue, children });
@@ -1607,6 +1869,9 @@ var TimePickerInput = react.forwardRef(
1607
1869
  function TimePickerInput2({ onBlur, onKeyDown, ...props }, ref) {
1608
1870
  const ctx = useTimePickerContext("TimePicker.Input");
1609
1871
  const [inputText, setInputText] = react.useState(null);
1872
+ react.useEffect(() => {
1873
+ setInputText(null);
1874
+ }, [ctx.value]);
1610
1875
  const displayValue = inputText !== null ? inputText : core.formatTimeString(ctx.currentTime, ctx.withSeconds);
1611
1876
  const commitInput = react.useCallback(() => {
1612
1877
  if (inputText === null) return;
@@ -1655,6 +1920,7 @@ var TimePickerInput = react.forwardRef(
1655
1920
  );
1656
1921
  }
1657
1922
  );
1923
+ TimePickerInput.displayName = "TimePicker.Input";
1658
1924
  function useListboxNavigation({
1659
1925
  items,
1660
1926
  onSelect,
@@ -1702,17 +1968,41 @@ function useListboxNavigation({
1702
1968
  }
1703
1969
  function TimePickerHourList({ classNames, ...props }) {
1704
1970
  const ctx = useTimePickerContext("TimePicker.HourList");
1705
- const { format, currentTime, isDisabled, isReadOnly } = ctx;
1706
- const hours = core.generateHours(format);
1971
+ const { format, step, currentTime, isDisabled, isReadOnly, filterTime } = ctx;
1972
+ const hours = react.useMemo(() => core.generateHours(format), [format]);
1707
1973
  const selectedHourDisplay = format === "12h" ? core.to12Hour(currentTime.hours).hours12 : currentTime.hours;
1708
1974
  const currentPeriod = format === "12h" ? core.to12Hour(currentTime.hours).period : null;
1975
+ const fullyDisabledHours24 = react.useMemo(() => {
1976
+ if (!filterTime) return null;
1977
+ const disabled = /* @__PURE__ */ new Set();
1978
+ for (let h = 0; h < 24; h++) {
1979
+ let allRejected = true;
1980
+ for (let m = 0; m < 60; m += step) {
1981
+ if (!filterTime(h, m)) {
1982
+ allRejected = false;
1983
+ break;
1984
+ }
1985
+ }
1986
+ if (allRejected) disabled.add(h);
1987
+ }
1988
+ return disabled;
1989
+ }, [filterTime, step]);
1990
+ const isHourDisabled = react.useCallback(
1991
+ (hourDisplay) => {
1992
+ if (!fullyDisabledHours24) return false;
1993
+ const hours24 = format === "12h" && currentPeriod ? core.to24Hour(hourDisplay, currentPeriod) : hourDisplay;
1994
+ return fullyDisabledHours24.has(hours24);
1995
+ },
1996
+ [fullyDisabledHours24, format, currentPeriod]
1997
+ );
1709
1998
  const handleSelect = react.useCallback(
1710
1999
  (hourDisplay) => {
1711
2000
  if (isDisabled || isReadOnly) return;
2001
+ if (isHourDisabled(hourDisplay)) return;
1712
2002
  const hours24 = format === "12h" && currentPeriod ? core.to24Hour(hourDisplay, currentPeriod) : hourDisplay;
1713
2003
  ctx.setTime({ hours: hours24 });
1714
2004
  },
1715
- [format, currentPeriod, ctx, isDisabled, isReadOnly]
2005
+ [format, currentPeriod, ctx, isDisabled, isReadOnly, isHourDisabled]
1716
2006
  );
1717
2007
  const { listRef, handleKeyDown } = useListboxNavigation({
1718
2008
  items: hours,
@@ -1730,13 +2020,14 @@ function TimePickerHourList({ classNames, ...props }) {
1730
2020
  ...props,
1731
2021
  children: hours.map((hour) => {
1732
2022
  const isSelected = hour === selectedHourDisplay;
2023
+ const isHourFullyDisabled = isHourDisabled(hour);
1733
2024
  const optionClass = [classNames?.option, isSelected && classNames?.optionSelected].filter(Boolean).join(" ") || void 0;
1734
2025
  return /* @__PURE__ */ jsxRuntime.jsx(
1735
2026
  "li",
1736
2027
  {
1737
2028
  role: "option",
1738
2029
  "aria-selected": isSelected,
1739
- "aria-disabled": isDisabled || void 0,
2030
+ "aria-disabled": isDisabled || isHourFullyDisabled || void 0,
1740
2031
  "aria-label": ctx.labels.hourOption(hour),
1741
2032
  "data-selected": isSelected || void 0,
1742
2033
  tabIndex: isSelected ? 0 : -1,
@@ -1753,14 +2044,22 @@ function TimePickerHourList({ classNames, ...props }) {
1753
2044
  }
1754
2045
  function TimePickerMinuteList({ classNames, ...props }) {
1755
2046
  const ctx = useTimePickerContext("TimePicker.MinuteList");
1756
- const { step, currentTime, isDisabled, isReadOnly } = ctx;
1757
- const minutes = core.generateMinutes(step);
2047
+ const { step, currentTime, isDisabled, isReadOnly, filterTime } = ctx;
2048
+ const minutes = react.useMemo(() => core.generateMinutes(step), [step]);
2049
+ const isMinuteDisabled = react.useCallback(
2050
+ (minute) => {
2051
+ if (!filterTime) return false;
2052
+ return filterTime(currentTime.hours, minute);
2053
+ },
2054
+ [filterTime, currentTime.hours]
2055
+ );
1758
2056
  const handleSelect = react.useCallback(
1759
2057
  (minute) => {
1760
2058
  if (isDisabled || isReadOnly) return;
2059
+ if (isMinuteDisabled(minute)) return;
1761
2060
  ctx.setTime({ minutes: minute });
1762
2061
  },
1763
- [ctx, isDisabled, isReadOnly]
2062
+ [ctx, isDisabled, isReadOnly, isMinuteDisabled]
1764
2063
  );
1765
2064
  const { listRef, handleKeyDown } = useListboxNavigation({
1766
2065
  items: minutes,
@@ -1778,13 +2077,14 @@ function TimePickerMinuteList({ classNames, ...props }) {
1778
2077
  ...props,
1779
2078
  children: minutes.map((minute) => {
1780
2079
  const isSelected = minute === currentTime.minutes;
2080
+ const isMinuteFullyDisabled = isMinuteDisabled(minute);
1781
2081
  const optionClass = [classNames?.option, isSelected && classNames?.optionSelected].filter(Boolean).join(" ") || void 0;
1782
2082
  return /* @__PURE__ */ jsxRuntime.jsx(
1783
2083
  "li",
1784
2084
  {
1785
2085
  role: "option",
1786
2086
  "aria-selected": isSelected,
1787
- "aria-disabled": isDisabled || void 0,
2087
+ "aria-disabled": isDisabled || isMinuteFullyDisabled || void 0,
1788
2088
  "aria-label": ctx.labels.minuteOption(minute),
1789
2089
  "data-selected": isSelected || void 0,
1790
2090
  tabIndex: isSelected ? 0 : -1,
@@ -1801,29 +2101,70 @@ function TimePickerMinuteList({ classNames, ...props }) {
1801
2101
  }
1802
2102
  function TimePickerAmPmToggle({ classNames, ...props }) {
1803
2103
  const ctx = useTimePickerContext("TimePicker.AmPmToggle");
1804
- if (ctx.format !== "12h") return null;
1805
- const { period, hours12 } = core.to12Hour(ctx.currentTime.hours);
2104
+ const amRef = react.useRef(null);
2105
+ const pmRef = react.useRef(null);
1806
2106
  const setPeriod = react.useCallback(
1807
2107
  (newPeriod) => {
1808
2108
  if (ctx.isDisabled || ctx.isReadOnly) return;
2109
+ const { hours12 } = core.to12Hour(ctx.currentTime.hours);
1809
2110
  const newHours24 = core.to24Hour(hours12, newPeriod);
1810
2111
  ctx.setTime({ hours: newHours24 });
1811
2112
  },
1812
- [hours12, ctx]
2113
+ [ctx]
1813
2114
  );
2115
+ if (ctx.format !== "12h") return null;
2116
+ const { period } = core.to12Hour(ctx.currentTime.hours);
2117
+ const focusOther = (target) => {
2118
+ (target === "AM" ? amRef : pmRef).current?.focus();
2119
+ };
2120
+ const handleKeyDown = (e, target) => {
2121
+ switch (e.key) {
2122
+ case "ArrowRight":
2123
+ case "ArrowDown":
2124
+ case "ArrowLeft":
2125
+ case "ArrowUp": {
2126
+ e.preventDefault();
2127
+ const next = target === "AM" ? "PM" : "AM";
2128
+ setPeriod(next);
2129
+ focusOther(next);
2130
+ break;
2131
+ }
2132
+ case "Home": {
2133
+ e.preventDefault();
2134
+ setPeriod("AM");
2135
+ focusOther("AM");
2136
+ break;
2137
+ }
2138
+ case "End": {
2139
+ e.preventDefault();
2140
+ setPeriod("PM");
2141
+ focusOther("PM");
2142
+ break;
2143
+ }
2144
+ case " ":
2145
+ case "Enter": {
2146
+ e.preventDefault();
2147
+ setPeriod(target);
2148
+ break;
2149
+ }
2150
+ }
2151
+ };
1814
2152
  const renderButton = (target) => {
1815
2153
  const isSelected = period === target;
1816
2154
  const optionClass = [classNames?.option, isSelected && classNames?.optionSelected].filter(Boolean).join(" ") || void 0;
1817
2155
  return /* @__PURE__ */ jsxRuntime.jsx(
1818
2156
  "button",
1819
2157
  {
2158
+ ref: target === "AM" ? amRef : pmRef,
1820
2159
  type: "button",
1821
2160
  role: "radio",
1822
2161
  "aria-checked": isSelected,
2162
+ tabIndex: isSelected ? 0 : -1,
1823
2163
  "data-selected": isSelected || void 0,
1824
2164
  disabled: ctx.isDisabled,
1825
2165
  className: optionClass,
1826
2166
  onClick: () => setPeriod(target),
2167
+ onKeyDown: (e) => handleKeyDown(e, target),
1827
2168
  children: target
1828
2169
  }
1829
2170
  );
@@ -1850,9 +2191,6 @@ var TimePicker = Object.assign(TimePickerRoot, {
1850
2191
  MinuteList: TimePickerMinuteList,
1851
2192
  AmPmToggle: TimePickerAmPmToggle
1852
2193
  });
1853
- function getDefaultIso2() {
1854
- return core.DateFnsAdapter.today();
1855
- }
1856
2194
  function DateTimePickerRoot({
1857
2195
  value: controlledValue,
1858
2196
  defaultValue,
@@ -1861,6 +2199,8 @@ function DateTimePickerRoot({
1861
2199
  onCalendarNavigate,
1862
2200
  format = "24h",
1863
2201
  step = 1,
2202
+ withSeconds = false,
2203
+ filterTime,
1864
2204
  disabled = false,
1865
2205
  readOnly = false,
1866
2206
  weekStartsOn = 0,
@@ -1888,10 +2228,10 @@ function DateTimePickerRoot({
1888
2228
  const currentValue = isControlled ? controlledValue ?? null : uncontrolledValue;
1889
2229
  const [isOpen, setIsOpen] = react.useState(false);
1890
2230
  const [viewMonth, setViewMonth] = react.useState(
1891
- currentValue ?? adapter.today(displayTimezone)
2231
+ () => currentValue ?? adapter.today(displayTimezone)
1892
2232
  );
1893
2233
  const [focusedDate, setFocusedDate] = react.useState(
1894
- currentValue ?? adapter.today(displayTimezone)
2234
+ () => currentValue ?? adapter.today(displayTimezone)
1895
2235
  );
1896
2236
  useChangeEffect(isOpen, onOpenChange);
1897
2237
  const viewMonthStart = react.useMemo(() => adapter.startOfMonth(viewMonth), [viewMonth, adapter]);
@@ -1901,11 +2241,10 @@ function DateTimePickerRoot({
1901
2241
  () => Array.isArray(disabled) ? disabled : [],
1902
2242
  [disabled]
1903
2243
  );
1904
- const baseIso = currentValue ?? getDefaultIso2();
1905
- const currentTime = react.useMemo(
1906
- () => displayTimezone ? core.getTimeInTimezone(baseIso, displayTimezone) : core.getTime(baseIso),
1907
- [baseIso, displayTimezone]
1908
- );
2244
+ const currentTime = react.useMemo(() => {
2245
+ if (!currentValue) return { hours: 0, minutes: 0, seconds: 0 };
2246
+ return displayTimezone ? core.getTimeInTimezone(currentValue, displayTimezone) : core.getTime(currentValue);
2247
+ }, [currentValue, displayTimezone]);
1909
2248
  const updateValue = react.useCallback(
1910
2249
  (next) => {
1911
2250
  if (isDisabled || readOnly) return;
@@ -1931,11 +2270,11 @@ function DateTimePickerRoot({
1931
2270
  );
1932
2271
  const setTime = react.useCallback(
1933
2272
  (partial) => {
1934
- const base = currentValue ?? getDefaultIso2();
2273
+ const base = currentValue ?? adapter.today(displayTimezone);
1935
2274
  const merged = displayTimezone ? core.setTimeInTimezone(base, partial, displayTimezone) : core.setTime(base, partial);
1936
2275
  updateValue(merged);
1937
2276
  },
1938
- [currentValue, updateValue, displayTimezone]
2277
+ [currentValue, updateValue, displayTimezone, adapter]
1939
2278
  );
1940
2279
  const open = react.useCallback(() => {
1941
2280
  if (isDisabled || readOnly) return;
@@ -2002,25 +2341,28 @@ function DateTimePickerRoot({
2002
2341
  setTime,
2003
2342
  format,
2004
2343
  step,
2005
- withSeconds: false,
2344
+ withSeconds,
2006
2345
  displayTimezone,
2007
2346
  isDisabled,
2008
2347
  isReadOnly: readOnly,
2009
2348
  currentTime,
2010
2349
  pickerId,
2011
- labels: mergedTimeLabels
2350
+ labels: mergedTimeLabels,
2351
+ filterTime
2012
2352
  }),
2013
2353
  [
2014
2354
  currentValue,
2015
2355
  setTime,
2016
2356
  format,
2017
2357
  step,
2358
+ withSeconds,
2018
2359
  displayTimezone,
2019
2360
  isDisabled,
2020
2361
  readOnly,
2021
2362
  currentTime,
2022
2363
  pickerId,
2023
- mergedTimeLabels
2364
+ mergedTimeLabels,
2365
+ filterTime
2024
2366
  ]
2025
2367
  );
2026
2368
  return /* @__PURE__ */ jsxRuntime.jsx(DatePickerContext.Provider, { value: dateContext, children: /* @__PURE__ */ jsxRuntime.jsx(TimePickerContext.Provider, { value: timeContext, children }) });
@@ -2049,6 +2391,8 @@ var DateTimePickerInput = react.forwardRef(
2049
2391
  (e) => {
2050
2392
  if (e.key === "Escape") {
2051
2393
  ctx.close();
2394
+ } else if (e.key === "Enter" && ctx.isOpen) {
2395
+ e.preventDefault();
2052
2396
  } else if (e.key === "ArrowDown" && !ctx.isOpen) {
2053
2397
  e.preventDefault();
2054
2398
  ctx.open();
@@ -2084,6 +2428,7 @@ var DateTimePickerInput = react.forwardRef(
2084
2428
  );
2085
2429
  }
2086
2430
  );
2431
+ DateTimePickerInput.displayName = "DateTimePicker.Input";
2087
2432
 
2088
2433
  // src/components/DateTimePicker/index.ts
2089
2434
  var DateTimePicker = Object.assign(DateTimePickerRoot, {
@@ -2102,7 +2447,7 @@ function MonthPickerRoot(props) {
2102
2447
  }
2103
2448
  function MonthPickerGrid({ classNames, ...props }) {
2104
2449
  const ctx = useDatePickerContext("MonthPicker.Grid");
2105
- const { adapter, viewMonth, locale, value, displayTimezone, labels } = ctx;
2450
+ const { adapter, viewMonth, locale, value, displayTimezone, labels, disabled } = ctx;
2106
2451
  const currentYear = adapter.getYear(viewMonth);
2107
2452
  const [valueYear, valueMonthZeroBased] = react.useMemo(() => {
2108
2453
  if (!value) return [null, null];
@@ -2113,9 +2458,19 @@ function MonthPickerGrid({ classNames, ...props }) {
2113
2458
  return [null, null];
2114
2459
  }
2115
2460
  }, [value, adapter, displayTimezone]);
2116
- const today = adapter.today(displayTimezone);
2117
- const todayYear = adapter.getYear(today);
2118
- const todayMonth = adapter.getMonth(today);
2461
+ const [today, setToday] = react.useState(null);
2462
+ react.useEffect(() => {
2463
+ setToday(adapter.today(displayTimezone));
2464
+ }, [adapter, displayTimezone]);
2465
+ const todayYear = today !== null ? adapter.getYear(today) : -1;
2466
+ const todayMonth = today !== null ? adapter.getMonth(today) : -1;
2467
+ const monthDisabledFlags = react.useMemo(
2468
+ () => Array.from({ length: 12 }, (_, i) => {
2469
+ const monthStart = new Date(Date.UTC(currentYear, i, 1)).toISOString();
2470
+ return isRangeFullyDisabled(monthStart, adapter.endOfMonth(monthStart), disabled, adapter);
2471
+ }),
2472
+ [currentYear, disabled, adapter]
2473
+ );
2119
2474
  const navigateYear = react.useCallback(
2120
2475
  (direction) => {
2121
2476
  ctx.setViewMonth(adapter.addYears(viewMonth, direction));
@@ -2124,17 +2479,23 @@ function MonthPickerGrid({ classNames, ...props }) {
2124
2479
  );
2125
2480
  const handleMonthSelect = react.useCallback(
2126
2481
  (monthIndex) => {
2482
+ if (monthDisabledFlags[monthIndex]) return;
2127
2483
  const target = new Date(Date.UTC(currentYear, monthIndex, 1)).toISOString();
2128
2484
  ctx.selectDate(target);
2129
2485
  },
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
- }));
2486
+ [currentYear, ctx, monthDisabledFlags]
2487
+ );
2488
+ const naturalIndex = valueYear === currentYear && valueMonthZeroBased !== null ? valueMonthZeroBased : adapter.getMonth(viewMonth);
2489
+ const firstEnabled = monthDisabledFlags.findIndex((d) => !d);
2490
+ const initialIndex = monthDisabledFlags[naturalIndex] ? firstEnabled === -1 ? naturalIndex : firstEnabled : naturalIndex;
2491
+ const { gridRef, focusedIndex, handleKeyDown } = useGridState({
2492
+ initialIndex,
2493
+ disabledFlags: monthDisabledFlags,
2494
+ onSelect: handleMonthSelect,
2495
+ onPageUp: () => navigateYear(-1),
2496
+ onPageDown: () => navigateYear(1),
2497
+ onEscape: ctx.close
2498
+ });
2138
2499
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.root, ...props, children: [
2139
2500
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.header, children: [
2140
2501
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -2159,37 +2520,57 @@ function MonthPickerGrid({ classNames, ...props }) {
2159
2520
  }
2160
2521
  )
2161
2522
  ] }),
2162
- /* @__PURE__ */ jsxRuntime.jsx("div", { role: "grid", "aria-label": `${currentYear} months`, className: classNames?.grid, children: Array.from({ length: 4 }, (_, rowIndex) => /* @__PURE__ */ jsxRuntime.jsx(
2523
+ /* @__PURE__ */ jsxRuntime.jsx(
2163
2524
  "div",
2164
2525
  {
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
- )) })
2526
+ ref: gridRef,
2527
+ role: "grid",
2528
+ "aria-label": `${currentYear} months`,
2529
+ className: classNames?.grid,
2530
+ onKeyDown: handleKeyDown,
2531
+ children: Array.from({ length: 4 }, (_, rowIndex) => /* @__PURE__ */ jsxRuntime.jsx(
2532
+ "div",
2533
+ {
2534
+ role: "row",
2535
+ className: classNames?.gridRow,
2536
+ style: { display: "grid", gridTemplateColumns: "repeat(3, 1fr)" },
2537
+ children: Array.from({ length: 3 }, (_2, col) => {
2538
+ const i = rowIndex * 3 + col;
2539
+ const isSelected = valueYear === currentYear && valueMonthZeroBased === i;
2540
+ const isCurrent = todayYear === currentYear && todayMonth === i;
2541
+ const isFocused = i === focusedIndex;
2542
+ const isDisabled = monthDisabledFlags[i] ?? false;
2543
+ const cls = [
2544
+ classNames?.month,
2545
+ isSelected && classNames?.monthSelected,
2546
+ isCurrent && classNames?.monthCurrent,
2547
+ isDisabled && classNames?.monthDisabled
2548
+ ].filter(Boolean).join(" ") || void 0;
2549
+ return /* @__PURE__ */ jsxRuntime.jsx(
2550
+ "button",
2551
+ {
2552
+ type: "button",
2553
+ role: "gridcell",
2554
+ tabIndex: isFocused ? 0 : -1,
2555
+ disabled: isDisabled,
2556
+ "aria-selected": isSelected || void 0,
2557
+ "aria-disabled": isDisabled || void 0,
2558
+ "aria-current": isCurrent ? "date" : void 0,
2559
+ "data-selected": isSelected || void 0,
2560
+ "data-current": isCurrent || void 0,
2561
+ "data-focused": isFocused || void 0,
2562
+ className: cls,
2563
+ onClick: () => handleMonthSelect(i),
2564
+ children: core.getMonthName(i, locale)
2565
+ },
2566
+ i
2567
+ );
2568
+ })
2569
+ },
2570
+ rowIndex
2571
+ ))
2572
+ }
2573
+ )
2193
2574
  ] });
2194
2575
  }
2195
2576
 
@@ -2206,7 +2587,7 @@ function YearPickerRoot(props) {
2206
2587
  }
2207
2588
  function YearPickerGrid({ classNames, ...props }) {
2208
2589
  const ctx = useDatePickerContext("YearPicker.Grid");
2209
- const { adapter, viewMonth, value, displayTimezone, labels } = ctx;
2590
+ const { adapter, viewMonth, value, displayTimezone, labels, disabled } = ctx;
2210
2591
  const currentYear = adapter.getYear(viewMonth);
2211
2592
  const decadeStart = currentYear - currentYear % 12;
2212
2593
  const valueYear = react.useMemo(() => {
@@ -2217,7 +2598,20 @@ function YearPickerGrid({ classNames, ...props }) {
2217
2598
  return null;
2218
2599
  }
2219
2600
  }, [value, adapter, displayTimezone]);
2220
- const todayYear = adapter.getYear(adapter.today(displayTimezone));
2601
+ const [today, setToday] = react.useState(null);
2602
+ react.useEffect(() => {
2603
+ setToday(adapter.today(displayTimezone));
2604
+ }, [adapter, displayTimezone]);
2605
+ const todayYear = today !== null ? adapter.getYear(today) : -1;
2606
+ const yearDisabledFlags = react.useMemo(
2607
+ () => Array.from({ length: 12 }, (_, i) => {
2608
+ const year = decadeStart + i;
2609
+ const yearStart = new Date(Date.UTC(year, 0, 1)).toISOString();
2610
+ const yearEnd = new Date(Date.UTC(year, 11, 31, 23, 59, 59, 999)).toISOString();
2611
+ return isRangeFullyDisabled(yearStart, yearEnd, disabled, adapter);
2612
+ }),
2613
+ [decadeStart, disabled, adapter]
2614
+ );
2221
2615
  const navigateDecade = react.useCallback(
2222
2616
  (direction) => {
2223
2617
  ctx.setViewMonth(adapter.addYears(viewMonth, direction * 12));
@@ -2225,19 +2619,24 @@ function YearPickerGrid({ classNames, ...props }) {
2225
2619
  [adapter, viewMonth, ctx]
2226
2620
  );
2227
2621
  const handleYearSelect = react.useCallback(
2228
- (year) => {
2622
+ (indexInDecade) => {
2623
+ if (yearDisabledFlags[indexInDecade]) return;
2624
+ const year = decadeStart + indexInDecade;
2229
2625
  const target = new Date(Date.UTC(year, 0, 1)).toISOString();
2230
2626
  ctx.selectDate(target);
2231
2627
  },
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
- };
2628
+ [ctx, decadeStart, yearDisabledFlags]
2629
+ );
2630
+ const naturalIndex = valueYear !== null && valueYear >= decadeStart && valueYear <= decadeStart + 11 ? valueYear - decadeStart : currentYear - decadeStart;
2631
+ const firstEnabled = yearDisabledFlags.findIndex((d) => !d);
2632
+ const initialIndex = yearDisabledFlags[naturalIndex] ? firstEnabled === -1 ? naturalIndex : firstEnabled : naturalIndex;
2633
+ const { gridRef, focusedIndex, handleKeyDown } = useGridState({
2634
+ initialIndex,
2635
+ disabledFlags: yearDisabledFlags,
2636
+ onSelect: handleYearSelect,
2637
+ onPageUp: () => navigateDecade(-1),
2638
+ onPageDown: () => navigateDecade(1),
2639
+ onEscape: ctx.close
2241
2640
  });
2242
2641
  const rangeLabel = `${decadeStart}\u2013${decadeStart + 11}`;
2243
2642
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames?.root, ...props, children: [
@@ -2264,37 +2663,58 @@ function YearPickerGrid({ classNames, ...props }) {
2264
2663
  }
2265
2664
  )
2266
2665
  ] }),
2267
- /* @__PURE__ */ jsxRuntime.jsx("div", { role: "grid", "aria-label": rangeLabel, className: classNames?.grid, children: Array.from({ length: 4 }, (_, rowIndex) => /* @__PURE__ */ jsxRuntime.jsx(
2666
+ /* @__PURE__ */ jsxRuntime.jsx(
2268
2667
  "div",
2269
2668
  {
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
- )) })
2669
+ ref: gridRef,
2670
+ role: "grid",
2671
+ "aria-label": rangeLabel,
2672
+ className: classNames?.grid,
2673
+ onKeyDown: handleKeyDown,
2674
+ children: Array.from({ length: 4 }, (_, rowIndex) => /* @__PURE__ */ jsxRuntime.jsx(
2675
+ "div",
2676
+ {
2677
+ role: "row",
2678
+ className: classNames?.gridRow,
2679
+ style: { display: "grid", gridTemplateColumns: "repeat(3, 1fr)" },
2680
+ children: Array.from({ length: 3 }, (_2, col) => {
2681
+ const i = rowIndex * 3 + col;
2682
+ const year = decadeStart + i;
2683
+ const isSelected = year === valueYear;
2684
+ const isCurrent = year === todayYear;
2685
+ const isFocused = i === focusedIndex;
2686
+ const isDisabled = yearDisabledFlags[i] ?? false;
2687
+ const cls = [
2688
+ classNames?.year,
2689
+ isSelected && classNames?.yearSelected,
2690
+ isCurrent && classNames?.yearCurrent,
2691
+ isDisabled && classNames?.yearDisabled
2692
+ ].filter(Boolean).join(" ") || void 0;
2693
+ return /* @__PURE__ */ jsxRuntime.jsx(
2694
+ "button",
2695
+ {
2696
+ type: "button",
2697
+ role: "gridcell",
2698
+ tabIndex: isFocused ? 0 : -1,
2699
+ disabled: isDisabled,
2700
+ "aria-selected": isSelected || void 0,
2701
+ "aria-disabled": isDisabled || void 0,
2702
+ "aria-current": isCurrent ? "date" : void 0,
2703
+ "data-selected": isSelected || void 0,
2704
+ "data-current": isCurrent || void 0,
2705
+ "data-focused": isFocused || void 0,
2706
+ className: cls,
2707
+ onClick: () => handleYearSelect(i),
2708
+ children: year
2709
+ },
2710
+ i
2711
+ );
2712
+ })
2713
+ },
2714
+ rowIndex
2715
+ ))
2716
+ }
2717
+ )
2298
2718
  ] });
2299
2719
  }
2300
2720
 
@@ -2515,7 +2935,7 @@ function useRangePicker(options = {}) {
2515
2935
  adapter
2516
2936
  };
2517
2937
  }
2518
- function getDefaultIso3() {
2938
+ function getDefaultIso() {
2519
2939
  return core.DateFnsAdapter.today();
2520
2940
  }
2521
2941
  function useTimePicker(options = {}) {
@@ -2533,7 +2953,7 @@ function useTimePicker(options = {}) {
2533
2953
  defaultValue ?? null
2534
2954
  );
2535
2955
  const currentValue = isControlled ? controlledValue ?? null : uncontrolledValue;
2536
- const baseIso = currentValue ?? getDefaultIso3();
2956
+ const baseIso = currentValue ?? getDefaultIso();
2537
2957
  const currentTime = react.useMemo(
2538
2958
  () => displayTimezone ? core.getTimeInTimezone(baseIso, displayTimezone) : core.getTime(baseIso),
2539
2959
  [baseIso, displayTimezone]
@@ -2584,6 +3004,22 @@ function useTimePicker(options = {}) {
2584
3004
  };
2585
3005
  }
2586
3006
 
3007
+ Object.defineProperty(exports, "DEFAULT_DATEPICKER_LABELS", {
3008
+ enumerable: true,
3009
+ get: function () { return core.DEFAULT_DATEPICKER_LABELS; }
3010
+ });
3011
+ Object.defineProperty(exports, "DEFAULT_DATETIMEPICKER_LABELS", {
3012
+ enumerable: true,
3013
+ get: function () { return core.DEFAULT_DATETIMEPICKER_LABELS; }
3014
+ });
3015
+ Object.defineProperty(exports, "DEFAULT_RANGEPICKER_LABELS", {
3016
+ enumerable: true,
3017
+ get: function () { return core.DEFAULT_RANGEPICKER_LABELS; }
3018
+ });
3019
+ Object.defineProperty(exports, "DEFAULT_TIMEPICKER_LABELS", {
3020
+ enumerable: true,
3021
+ get: function () { return core.DEFAULT_TIMEPICKER_LABELS; }
3022
+ });
2587
3023
  Object.defineProperty(exports, "DateFnsAdapter", {
2588
3024
  enumerable: true,
2589
3025
  get: function () { return core.DateFnsAdapter; }