@kalyx/react 1.0.0-rc.6 → 1.0.0-rc.8

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.d.cts CHANGED
@@ -82,13 +82,28 @@ interface DatePickerCalendarClassNames {
82
82
  dayDisabled?: string;
83
83
  dayOutsideMonth?: string;
84
84
  weekdayHeader?: string;
85
+ /** Header cell at the top of the week-number column. Rendered only when `showWeekNumber` is set. */
86
+ weekNumberHeader?: string;
87
+ /** Each row's week-number cell. Rendered only when `showWeekNumber` is set. */
88
+ weekNumber?: string;
85
89
  }
86
90
  interface DatePickerCalendarProps extends Omit<HTMLAttributes<HTMLDivElement>, 'role'> {
87
91
  classNames?: DatePickerCalendarClassNames;
88
92
  /** Called when the title ("January 2026") is clicked. Useful for switching to Month/Year views. */
89
93
  onTitleClick?: () => void;
94
+ /**
95
+ * Render an ISO 8601 week-number column on the left of the grid (1–53).
96
+ * The column is a `<th scope="row">`; it doesn't participate in the WAI-ARIA grid
97
+ * data region, so keyboard navigation across the date cells is unaffected.
98
+ */
99
+ showWeekNumber?: boolean;
100
+ /**
101
+ * Always render 6 weeks (42 cells), even when the month fits in 4 or 5 rows. Useful for
102
+ * popover layouts that need a fixed height across month navigation.
103
+ */
104
+ fixedWeeks?: boolean;
90
105
  }
91
- declare function DatePickerCalendar({ classNames, onTitleClick, ...props }: DatePickerCalendarProps): react_jsx_runtime.JSX.Element;
106
+ declare function DatePickerCalendar({ classNames, onTitleClick, showWeekNumber, fixedWeeks, ...props }: DatePickerCalendarProps): react_jsx_runtime.JSX.Element;
92
107
 
93
108
  interface DatePickerMonthGridClassNames {
94
109
  root?: string;
@@ -322,6 +337,10 @@ interface RangePickerCalendarClassNames {
322
337
  dayRangeEnd?: string;
323
338
  dayInRange?: string;
324
339
  weekdayHeader?: string;
340
+ /** Header cell at the top of the week-number column. Rendered only when `showWeekNumber` is set. */
341
+ weekNumberHeader?: string;
342
+ /** Each row's week-number cell. Rendered only when `showWeekNumber` is set. */
343
+ weekNumber?: string;
325
344
  }
326
345
  /**
327
346
  * Selection mode for the calendar grid.
@@ -333,8 +352,18 @@ interface RangePickerCalendarProps extends Omit<HTMLAttributes<HTMLDivElement>,
333
352
  classNames?: RangePickerCalendarClassNames;
334
353
  /** @default 'range' */
335
354
  selectionMode?: RangePickerCalendarSelectionMode;
355
+ /**
356
+ * Render an ISO 8601 week-number column on the left of the grid (1–53).
357
+ * The column is a `<th scope="row">`; it doesn't participate in the WAI-ARIA grid
358
+ * data region, so keyboard navigation across the date cells is unaffected.
359
+ */
360
+ showWeekNumber?: boolean;
361
+ /**
362
+ * Always render 6 weeks (42 cells), even when the month fits in 4 or 5 rows.
363
+ */
364
+ fixedWeeks?: boolean;
336
365
  }
337
- declare function RangePickerCalendar({ classNames, selectionMode, ...props }: RangePickerCalendarProps): react_jsx_runtime.JSX.Element;
366
+ declare function RangePickerCalendar({ classNames, selectionMode, showWeekNumber, fixedWeeks, ...props }: RangePickerCalendarProps): react_jsx_runtime.JSX.Element;
338
367
 
339
368
  /** Predefined preset keys. Custom ranges are also supported. */
340
369
  type PresetKey = 'today' | 'yesterday' | 'last7days' | 'last30days' | 'thisWeek' | 'lastWeek' | 'thisMonth' | 'lastMonth' | 'thisYear';
@@ -466,12 +495,19 @@ interface TimePickerRootProps {
466
495
  disabled?: boolean;
467
496
  /** Read-only */
468
497
  readOnly?: boolean;
498
+ /**
499
+ * Programmatic per-slot disable predicate. Returns `true` for any `(hours, minutes)` pair
500
+ * that should be unselectable. Equivalent to react-datepicker's `filterTime`. Use cases:
501
+ * business hours, lunch breaks, blackout slots. Hours are disabled only when the predicate
502
+ * returns `true` for every step within the hour.
503
+ */
504
+ filterTime?: (hours: number, minutes: number) => boolean;
469
505
  /** Override ARIA labels (defaults to English) */
470
506
  labels?: Partial<TimePickerLabels>;
471
507
  /** Child components */
472
508
  children: ReactNode;
473
509
  }
474
- declare function TimePickerRoot({ value: controlledValue, defaultValue, onChange, format, step, withSeconds, displayTimezone, disabled, readOnly, labels: labelsProp, children, }: TimePickerRootProps): react_jsx_runtime.JSX.Element;
510
+ declare function TimePickerRoot({ value: controlledValue, defaultValue, onChange, format, step, withSeconds, displayTimezone, disabled, readOnly, filterTime, labels: labelsProp, children, }: TimePickerRootProps): react_jsx_runtime.JSX.Element;
475
511
 
476
512
  interface TimePickerHourListClassNames {
477
513
  root?: string;
package/dist/index.d.ts CHANGED
@@ -82,13 +82,28 @@ interface DatePickerCalendarClassNames {
82
82
  dayDisabled?: string;
83
83
  dayOutsideMonth?: string;
84
84
  weekdayHeader?: string;
85
+ /** Header cell at the top of the week-number column. Rendered only when `showWeekNumber` is set. */
86
+ weekNumberHeader?: string;
87
+ /** Each row's week-number cell. Rendered only when `showWeekNumber` is set. */
88
+ weekNumber?: string;
85
89
  }
86
90
  interface DatePickerCalendarProps extends Omit<HTMLAttributes<HTMLDivElement>, 'role'> {
87
91
  classNames?: DatePickerCalendarClassNames;
88
92
  /** Called when the title ("January 2026") is clicked. Useful for switching to Month/Year views. */
89
93
  onTitleClick?: () => void;
94
+ /**
95
+ * Render an ISO 8601 week-number column on the left of the grid (1–53).
96
+ * The column is a `<th scope="row">`; it doesn't participate in the WAI-ARIA grid
97
+ * data region, so keyboard navigation across the date cells is unaffected.
98
+ */
99
+ showWeekNumber?: boolean;
100
+ /**
101
+ * Always render 6 weeks (42 cells), even when the month fits in 4 or 5 rows. Useful for
102
+ * popover layouts that need a fixed height across month navigation.
103
+ */
104
+ fixedWeeks?: boolean;
90
105
  }
91
- declare function DatePickerCalendar({ classNames, onTitleClick, ...props }: DatePickerCalendarProps): react_jsx_runtime.JSX.Element;
106
+ declare function DatePickerCalendar({ classNames, onTitleClick, showWeekNumber, fixedWeeks, ...props }: DatePickerCalendarProps): react_jsx_runtime.JSX.Element;
92
107
 
93
108
  interface DatePickerMonthGridClassNames {
94
109
  root?: string;
@@ -322,6 +337,10 @@ interface RangePickerCalendarClassNames {
322
337
  dayRangeEnd?: string;
323
338
  dayInRange?: string;
324
339
  weekdayHeader?: string;
340
+ /** Header cell at the top of the week-number column. Rendered only when `showWeekNumber` is set. */
341
+ weekNumberHeader?: string;
342
+ /** Each row's week-number cell. Rendered only when `showWeekNumber` is set. */
343
+ weekNumber?: string;
325
344
  }
326
345
  /**
327
346
  * Selection mode for the calendar grid.
@@ -333,8 +352,18 @@ interface RangePickerCalendarProps extends Omit<HTMLAttributes<HTMLDivElement>,
333
352
  classNames?: RangePickerCalendarClassNames;
334
353
  /** @default 'range' */
335
354
  selectionMode?: RangePickerCalendarSelectionMode;
355
+ /**
356
+ * Render an ISO 8601 week-number column on the left of the grid (1–53).
357
+ * The column is a `<th scope="row">`; it doesn't participate in the WAI-ARIA grid
358
+ * data region, so keyboard navigation across the date cells is unaffected.
359
+ */
360
+ showWeekNumber?: boolean;
361
+ /**
362
+ * Always render 6 weeks (42 cells), even when the month fits in 4 or 5 rows.
363
+ */
364
+ fixedWeeks?: boolean;
336
365
  }
337
- declare function RangePickerCalendar({ classNames, selectionMode, ...props }: RangePickerCalendarProps): react_jsx_runtime.JSX.Element;
366
+ declare function RangePickerCalendar({ classNames, selectionMode, showWeekNumber, fixedWeeks, ...props }: RangePickerCalendarProps): react_jsx_runtime.JSX.Element;
338
367
 
339
368
  /** Predefined preset keys. Custom ranges are also supported. */
340
369
  type PresetKey = 'today' | 'yesterday' | 'last7days' | 'last30days' | 'thisWeek' | 'lastWeek' | 'thisMonth' | 'lastMonth' | 'thisYear';
@@ -466,12 +495,19 @@ interface TimePickerRootProps {
466
495
  disabled?: boolean;
467
496
  /** Read-only */
468
497
  readOnly?: boolean;
498
+ /**
499
+ * Programmatic per-slot disable predicate. Returns `true` for any `(hours, minutes)` pair
500
+ * that should be unselectable. Equivalent to react-datepicker's `filterTime`. Use cases:
501
+ * business hours, lunch breaks, blackout slots. Hours are disabled only when the predicate
502
+ * returns `true` for every step within the hour.
503
+ */
504
+ filterTime?: (hours: number, minutes: number) => boolean;
469
505
  /** Override ARIA labels (defaults to English) */
470
506
  labels?: Partial<TimePickerLabels>;
471
507
  /** Child components */
472
508
  children: ReactNode;
473
509
  }
474
- declare function TimePickerRoot({ value: controlledValue, defaultValue, onChange, format, step, withSeconds, displayTimezone, disabled, readOnly, labels: labelsProp, children, }: TimePickerRootProps): react_jsx_runtime.JSX.Element;
510
+ declare function TimePickerRoot({ value: controlledValue, defaultValue, onChange, format, step, withSeconds, displayTimezone, disabled, readOnly, filterTime, labels: labelsProp, children, }: TimePickerRootProps): react_jsx_runtime.JSX.Element;
475
511
 
476
512
  interface TimePickerHourListClassNames {
477
513
  root?: string;
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { createContext, forwardRef, useState, useRef, useCallback, useContext, useId, useMemo, useEffect } from 'react';
3
- import { parseInputValue, formatTimeString, parseTimeString, getTimeInTimezone, getTime, DateFnsAdapter, DEFAULT_DATEPICKER_LABELS, civilMidnightFromUtcDay, getMonthName, getWeekdayNames, getCalendarDays, formatMonthYear, isDateDisabled, DEFAULT_RANGEPICKER_LABELS, DEFAULT_TIMEPICKER_LABELS, setTimeInTimezone, setTime, to12Hour, to24Hour, generateMinutes, generateHours, formatFullDate } from '@kalyx/core';
3
+ import { parseInputValue, formatTimeString, parseTimeString, getTimeInTimezone, getTime, DateFnsAdapter, DEFAULT_DATEPICKER_LABELS, civilMidnightFromUtcDay, getMonthName, getWeekdayNames, getCalendarDays, formatMonthYear, isDateDisabled, getISOWeekNumber, DEFAULT_RANGEPICKER_LABELS, DEFAULT_TIMEPICKER_LABELS, setTimeInTimezone, setTime, to12Hour, to24Hour, generateMinutes, generateHours, formatFullDate } from '@kalyx/core';
4
4
  export { DateFnsAdapter } from '@kalyx/core';
5
5
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
6
6
  import { offset, flip, shift, useFloating, autoUpdate } from '@floating-ui/react';
@@ -464,6 +464,8 @@ var srOnly = {
464
464
  function DatePickerCalendar({
465
465
  classNames,
466
466
  onTitleClick,
467
+ showWeekNumber = false,
468
+ fixedWeeks = false,
467
469
  ...props
468
470
  }) {
469
471
  const ctx = useDatePickerContext("DatePicker.Calendar");
@@ -477,10 +479,21 @@ function DatePickerCalendar({
477
479
  selected: ctx.value,
478
480
  focusedDate,
479
481
  disabled,
480
- timezone: displayTimezone
482
+ timezone: displayTimezone,
483
+ fixedWeeks
481
484
  }),
482
- [viewMonth, adapter, weekStartsOn, ctx.value, focusedDate, disabled, displayTimezone]
485
+ [
486
+ viewMonth,
487
+ adapter,
488
+ weekStartsOn,
489
+ ctx.value,
490
+ focusedDate,
491
+ disabled,
492
+ displayTimezone,
493
+ fixedWeeks
494
+ ]
483
495
  );
496
+ const thursdayIndex = weekStartsOn === 0 ? 4 : 3;
484
497
  const year = adapter.getYear(viewMonth);
485
498
  const month = adapter.getMonth(viewMonth);
486
499
  const title = formatMonthYear(year, month, locale);
@@ -621,61 +634,76 @@ function DatePickerCalendar({
621
634
  className: classNames?.grid,
622
635
  onKeyDown: handleKeyDown,
623
636
  children: [
624
- /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { role: "row", "aria-rowindex": 1, children: weekdays.map((day, colIndex) => /* @__PURE__ */ jsx(
625
- "th",
626
- {
627
- role: "columnheader",
628
- abbr: day.full,
629
- scope: "col",
630
- "aria-colindex": colIndex + 1,
631
- className: classNames?.weekdayHeader,
632
- children: day.short
633
- },
634
- day.short
635
- )) }) }),
636
- /* @__PURE__ */ jsx("tbody", { children: weeks.map((week, weekIndex) => /* @__PURE__ */ jsx(
637
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { role: "row", "aria-rowindex": 1, children: [
638
+ showWeekNumber ? /* @__PURE__ */ jsx("th", { scope: "col", "aria-hidden": "true", className: classNames?.weekNumberHeader, children: "#" }) : null,
639
+ weekdays.map((day, colIndex) => /* @__PURE__ */ jsx(
640
+ "th",
641
+ {
642
+ role: "columnheader",
643
+ abbr: day.full,
644
+ scope: "col",
645
+ "aria-colindex": colIndex + 1,
646
+ className: classNames?.weekdayHeader,
647
+ children: day.short
648
+ },
649
+ day.short
650
+ ))
651
+ ] }) }),
652
+ /* @__PURE__ */ jsx("tbody", { children: weeks.map((week, weekIndex) => /* @__PURE__ */ jsxs(
637
653
  "tr",
638
654
  {
639
655
  role: "row",
640
656
  "aria-rowindex": weekIndex + 2,
641
657
  className: classNames?.gridRow,
642
- children: week.map((day, colIndex) => {
643
- const dayClasses = [
644
- classNames?.day,
645
- day.isSelected && classNames?.daySelected,
646
- day.isToday && classNames?.dayToday,
647
- day.isDisabled && classNames?.dayDisabled,
648
- !day.isCurrentMonth && classNames?.dayOutsideMonth
649
- ].filter(Boolean).join(" ") || void 0;
650
- return /* @__PURE__ */ jsx(
651
- "td",
658
+ children: [
659
+ showWeekNumber ? /* @__PURE__ */ jsx(
660
+ "th",
652
661
  {
653
- role: "gridcell",
654
- "aria-colindex": colIndex + 1,
655
- "aria-selected": day.isSelected || void 0,
656
- "aria-disabled": day.isDisabled || void 0,
657
- "aria-current": day.isToday ? "date" : void 0,
658
- className: classNames?.gridCell,
659
- children: /* @__PURE__ */ jsx(
660
- "button",
661
- {
662
- type: "button",
663
- tabIndex: day.isFocused ? 0 : -1,
664
- disabled: day.isDisabled,
665
- "data-focused": day.isFocused || void 0,
666
- "data-selected": day.isSelected || void 0,
667
- "data-today": day.isToday || void 0,
668
- "data-outside-month": !day.isCurrentMonth || void 0,
669
- className: dayClasses,
670
- onClick: () => handleDayClick(day),
671
- "aria-label": safeFormatFullDate(day.isoString, locale),
672
- children: day.dayNumber
673
- }
674
- )
675
- },
676
- day.isoString
677
- );
678
- })
662
+ scope: "row",
663
+ "aria-hidden": "true",
664
+ className: classNames?.weekNumber,
665
+ "data-week-number": true,
666
+ children: getISOWeekNumber(week[thursdayIndex].isoString)
667
+ }
668
+ ) : null,
669
+ week.map((day, colIndex) => {
670
+ const dayClasses = [
671
+ classNames?.day,
672
+ day.isSelected && classNames?.daySelected,
673
+ day.isToday && classNames?.dayToday,
674
+ day.isDisabled && classNames?.dayDisabled,
675
+ !day.isCurrentMonth && classNames?.dayOutsideMonth
676
+ ].filter(Boolean).join(" ") || void 0;
677
+ return /* @__PURE__ */ jsx(
678
+ "td",
679
+ {
680
+ role: "gridcell",
681
+ "aria-colindex": colIndex + 1,
682
+ "aria-selected": day.isSelected || void 0,
683
+ "aria-disabled": day.isDisabled || void 0,
684
+ "aria-current": day.isToday ? "date" : void 0,
685
+ className: classNames?.gridCell,
686
+ children: /* @__PURE__ */ jsx(
687
+ "button",
688
+ {
689
+ type: "button",
690
+ tabIndex: day.isFocused ? 0 : -1,
691
+ disabled: day.isDisabled,
692
+ "data-focused": day.isFocused || void 0,
693
+ "data-selected": day.isSelected || void 0,
694
+ "data-today": day.isToday || void 0,
695
+ "data-outside-month": !day.isCurrentMonth || void 0,
696
+ className: dayClasses,
697
+ onClick: () => handleDayClick(day),
698
+ "aria-label": safeFormatFullDate(day.isoString, locale),
699
+ children: day.dayNumber
700
+ }
701
+ )
702
+ },
703
+ day.isoString
704
+ );
705
+ })
706
+ ]
679
707
  },
680
708
  weekIndex
681
709
  )) })
@@ -1338,6 +1366,8 @@ var srOnly2 = {
1338
1366
  function RangePickerCalendar({
1339
1367
  classNames,
1340
1368
  selectionMode = "range",
1369
+ showWeekNumber = false,
1370
+ fixedWeeks = false,
1341
1371
  ...props
1342
1372
  }) {
1343
1373
  const ctx = useRangePickerContext("RangePicker.Calendar");
@@ -1363,10 +1393,22 @@ function RangePickerCalendar({
1363
1393
  disabled,
1364
1394
  range: value,
1365
1395
  rangeHover: hoverDate,
1366
- timezone: displayTimezone
1396
+ timezone: displayTimezone,
1397
+ fixedWeeks
1367
1398
  }),
1368
- [viewMonth, adapter, weekStartsOn, focusedDate, disabled, value, hoverDate, displayTimezone]
1399
+ [
1400
+ viewMonth,
1401
+ adapter,
1402
+ weekStartsOn,
1403
+ focusedDate,
1404
+ disabled,
1405
+ value,
1406
+ hoverDate,
1407
+ displayTimezone,
1408
+ fixedWeeks
1409
+ ]
1369
1410
  );
1411
+ const thursdayIndex = weekStartsOn === 0 ? 4 : 3;
1370
1412
  const year = adapter.getYear(viewMonth);
1371
1413
  const month = adapter.getMonth(viewMonth);
1372
1414
  const title = formatMonthYear(year, month, locale);
@@ -1530,67 +1572,82 @@ function RangePickerCalendar({
1530
1572
  className: classNames?.grid,
1531
1573
  onKeyDown: handleKeyDown,
1532
1574
  children: [
1533
- /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { role: "row", "aria-rowindex": 1, children: weekdays.map((day, colIndex) => /* @__PURE__ */ jsx(
1534
- "th",
1535
- {
1536
- role: "columnheader",
1537
- abbr: day.full,
1538
- scope: "col",
1539
- "aria-colindex": colIndex + 1,
1540
- className: classNames?.weekdayHeader,
1541
- children: day.short
1542
- },
1543
- day.short
1544
- )) }) }),
1545
- /* @__PURE__ */ jsx("tbody", { children: weeks.map((week, weekIndex) => /* @__PURE__ */ jsx(
1575
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { role: "row", "aria-rowindex": 1, children: [
1576
+ showWeekNumber ? /* @__PURE__ */ jsx("th", { scope: "col", "aria-hidden": "true", className: classNames?.weekNumberHeader, children: "#" }) : null,
1577
+ weekdays.map((day, colIndex) => /* @__PURE__ */ jsx(
1578
+ "th",
1579
+ {
1580
+ role: "columnheader",
1581
+ abbr: day.full,
1582
+ scope: "col",
1583
+ "aria-colindex": colIndex + 1,
1584
+ className: classNames?.weekdayHeader,
1585
+ children: day.short
1586
+ },
1587
+ day.short
1588
+ ))
1589
+ ] }) }),
1590
+ /* @__PURE__ */ jsx("tbody", { children: weeks.map((week, weekIndex) => /* @__PURE__ */ jsxs(
1546
1591
  "tr",
1547
1592
  {
1548
1593
  role: "row",
1549
1594
  "aria-rowindex": weekIndex + 2,
1550
1595
  className: classNames?.gridRow,
1551
- children: week.map((day, colIndex) => {
1552
- const dayClasses = [
1553
- classNames?.day,
1554
- day.isRangeStart && classNames?.dayRangeStart,
1555
- day.isRangeEnd && classNames?.dayRangeEnd,
1556
- day.isInRange && classNames?.dayInRange,
1557
- day.isToday && classNames?.dayToday,
1558
- day.isDisabled && classNames?.dayDisabled,
1559
- !day.isCurrentMonth && classNames?.dayOutsideMonth
1560
- ].filter(Boolean).join(" ") || void 0;
1561
- const isSelected = selectionMode === "week" ? day.isRangeStart || day.isRangeEnd || day.isInRange : day.isRangeStart || day.isRangeEnd;
1562
- return /* @__PURE__ */ jsx(
1563
- "td",
1596
+ children: [
1597
+ showWeekNumber ? /* @__PURE__ */ jsx(
1598
+ "th",
1564
1599
  {
1565
- role: "gridcell",
1566
- "aria-colindex": colIndex + 1,
1567
- "aria-selected": isSelected || void 0,
1568
- "aria-disabled": day.isDisabled || void 0,
1569
- "aria-current": day.isToday ? "date" : void 0,
1570
- className: classNames?.gridCell,
1571
- children: /* @__PURE__ */ jsx(
1572
- "button",
1573
- {
1574
- type: "button",
1575
- tabIndex: day.isFocused ? 0 : -1,
1576
- disabled: day.isDisabled,
1577
- "data-focused": day.isFocused || void 0,
1578
- "data-range-start": day.isRangeStart || void 0,
1579
- "data-range-end": day.isRangeEnd || void 0,
1580
- "data-in-range": day.isInRange || void 0,
1581
- "data-today": day.isToday || void 0,
1582
- "data-outside-month": !day.isCurrentMonth || void 0,
1583
- className: dayClasses,
1584
- onClick: () => handleDayClick(day),
1585
- onMouseEnter: () => handleDayMouseEnter(day),
1586
- "aria-label": safeFormatFullDate2(day.isoString, locale),
1587
- children: day.dayNumber
1588
- }
1589
- )
1590
- },
1591
- day.isoString
1592
- );
1593
- })
1600
+ scope: "row",
1601
+ "aria-hidden": "true",
1602
+ className: classNames?.weekNumber,
1603
+ "data-week-number": true,
1604
+ children: getISOWeekNumber(week[thursdayIndex].isoString)
1605
+ }
1606
+ ) : null,
1607
+ week.map((day, colIndex) => {
1608
+ const dayClasses = [
1609
+ classNames?.day,
1610
+ day.isRangeStart && classNames?.dayRangeStart,
1611
+ day.isRangeEnd && classNames?.dayRangeEnd,
1612
+ day.isInRange && classNames?.dayInRange,
1613
+ day.isToday && classNames?.dayToday,
1614
+ day.isDisabled && classNames?.dayDisabled,
1615
+ !day.isCurrentMonth && classNames?.dayOutsideMonth
1616
+ ].filter(Boolean).join(" ") || void 0;
1617
+ const isSelected = selectionMode === "week" ? day.isRangeStart || day.isRangeEnd || day.isInRange : day.isRangeStart || day.isRangeEnd;
1618
+ return /* @__PURE__ */ jsx(
1619
+ "td",
1620
+ {
1621
+ role: "gridcell",
1622
+ "aria-colindex": colIndex + 1,
1623
+ "aria-selected": isSelected || void 0,
1624
+ "aria-disabled": day.isDisabled || void 0,
1625
+ "aria-current": day.isToday ? "date" : void 0,
1626
+ className: classNames?.gridCell,
1627
+ children: /* @__PURE__ */ jsx(
1628
+ "button",
1629
+ {
1630
+ type: "button",
1631
+ tabIndex: day.isFocused ? 0 : -1,
1632
+ disabled: day.isDisabled,
1633
+ "data-focused": day.isFocused || void 0,
1634
+ "data-range-start": day.isRangeStart || void 0,
1635
+ "data-range-end": day.isRangeEnd || void 0,
1636
+ "data-in-range": day.isInRange || void 0,
1637
+ "data-today": day.isToday || void 0,
1638
+ "data-outside-month": !day.isCurrentMonth || void 0,
1639
+ className: dayClasses,
1640
+ onClick: () => handleDayClick(day),
1641
+ onMouseEnter: () => handleDayMouseEnter(day),
1642
+ "aria-label": safeFormatFullDate2(day.isoString, locale),
1643
+ children: day.dayNumber
1644
+ }
1645
+ )
1646
+ },
1647
+ day.isoString
1648
+ );
1649
+ })
1650
+ ]
1594
1651
  },
1595
1652
  weekIndex
1596
1653
  )) })
@@ -1734,6 +1791,7 @@ function TimePickerRoot({
1734
1791
  displayTimezone,
1735
1792
  disabled = false,
1736
1793
  readOnly = false,
1794
+ filterTime,
1737
1795
  labels: labelsProp,
1738
1796
  children
1739
1797
  }) {
@@ -1775,7 +1833,8 @@ function TimePickerRoot({
1775
1833
  isReadOnly: readOnly,
1776
1834
  currentTime,
1777
1835
  pickerId,
1778
- labels: mergedLabels
1836
+ labels: mergedLabels,
1837
+ filterTime
1779
1838
  }),
1780
1839
  [
1781
1840
  currentValue,
@@ -1788,7 +1847,8 @@ function TimePickerRoot({
1788
1847
  readOnly,
1789
1848
  currentTime,
1790
1849
  pickerId,
1791
- mergedLabels
1850
+ mergedLabels,
1851
+ filterTime
1792
1852
  ]
1793
1853
  );
1794
1854
  return /* @__PURE__ */ jsx(TimePickerContext.Provider, { value: contextValue, children });
@@ -1893,17 +1953,41 @@ function useListboxNavigation({
1893
1953
  }
1894
1954
  function TimePickerHourList({ classNames, ...props }) {
1895
1955
  const ctx = useTimePickerContext("TimePicker.HourList");
1896
- const { format, currentTime, isDisabled, isReadOnly } = ctx;
1956
+ const { format, step, currentTime, isDisabled, isReadOnly, filterTime } = ctx;
1897
1957
  const hours = useMemo(() => generateHours(format), [format]);
1898
1958
  const selectedHourDisplay = format === "12h" ? to12Hour(currentTime.hours).hours12 : currentTime.hours;
1899
1959
  const currentPeriod = format === "12h" ? to12Hour(currentTime.hours).period : null;
1960
+ const fullyDisabledHours24 = useMemo(() => {
1961
+ if (!filterTime) return null;
1962
+ const disabled = /* @__PURE__ */ new Set();
1963
+ for (let h = 0; h < 24; h++) {
1964
+ let allRejected = true;
1965
+ for (let m = 0; m < 60; m += step) {
1966
+ if (!filterTime(h, m)) {
1967
+ allRejected = false;
1968
+ break;
1969
+ }
1970
+ }
1971
+ if (allRejected) disabled.add(h);
1972
+ }
1973
+ return disabled;
1974
+ }, [filterTime, step]);
1975
+ const isHourDisabled = useCallback(
1976
+ (hourDisplay) => {
1977
+ if (!fullyDisabledHours24) return false;
1978
+ const hours24 = format === "12h" && currentPeriod ? to24Hour(hourDisplay, currentPeriod) : hourDisplay;
1979
+ return fullyDisabledHours24.has(hours24);
1980
+ },
1981
+ [fullyDisabledHours24, format, currentPeriod]
1982
+ );
1900
1983
  const handleSelect = useCallback(
1901
1984
  (hourDisplay) => {
1902
1985
  if (isDisabled || isReadOnly) return;
1986
+ if (isHourDisabled(hourDisplay)) return;
1903
1987
  const hours24 = format === "12h" && currentPeriod ? to24Hour(hourDisplay, currentPeriod) : hourDisplay;
1904
1988
  ctx.setTime({ hours: hours24 });
1905
1989
  },
1906
- [format, currentPeriod, ctx, isDisabled, isReadOnly]
1990
+ [format, currentPeriod, ctx, isDisabled, isReadOnly, isHourDisabled]
1907
1991
  );
1908
1992
  const { listRef, handleKeyDown } = useListboxNavigation({
1909
1993
  items: hours,
@@ -1921,13 +2005,14 @@ function TimePickerHourList({ classNames, ...props }) {
1921
2005
  ...props,
1922
2006
  children: hours.map((hour) => {
1923
2007
  const isSelected = hour === selectedHourDisplay;
2008
+ const isHourFullyDisabled = isHourDisabled(hour);
1924
2009
  const optionClass = [classNames?.option, isSelected && classNames?.optionSelected].filter(Boolean).join(" ") || void 0;
1925
2010
  return /* @__PURE__ */ jsx(
1926
2011
  "li",
1927
2012
  {
1928
2013
  role: "option",
1929
2014
  "aria-selected": isSelected,
1930
- "aria-disabled": isDisabled || void 0,
2015
+ "aria-disabled": isDisabled || isHourFullyDisabled || void 0,
1931
2016
  "aria-label": ctx.labels.hourOption(hour),
1932
2017
  "data-selected": isSelected || void 0,
1933
2018
  tabIndex: isSelected ? 0 : -1,
@@ -1944,14 +2029,22 @@ function TimePickerHourList({ classNames, ...props }) {
1944
2029
  }
1945
2030
  function TimePickerMinuteList({ classNames, ...props }) {
1946
2031
  const ctx = useTimePickerContext("TimePicker.MinuteList");
1947
- const { step, currentTime, isDisabled, isReadOnly } = ctx;
2032
+ const { step, currentTime, isDisabled, isReadOnly, filterTime } = ctx;
1948
2033
  const minutes = useMemo(() => generateMinutes(step), [step]);
2034
+ const isMinuteDisabled = useCallback(
2035
+ (minute) => {
2036
+ if (!filterTime) return false;
2037
+ return filterTime(currentTime.hours, minute);
2038
+ },
2039
+ [filterTime, currentTime.hours]
2040
+ );
1949
2041
  const handleSelect = useCallback(
1950
2042
  (minute) => {
1951
2043
  if (isDisabled || isReadOnly) return;
2044
+ if (isMinuteDisabled(minute)) return;
1952
2045
  ctx.setTime({ minutes: minute });
1953
2046
  },
1954
- [ctx, isDisabled, isReadOnly]
2047
+ [ctx, isDisabled, isReadOnly, isMinuteDisabled]
1955
2048
  );
1956
2049
  const { listRef, handleKeyDown } = useListboxNavigation({
1957
2050
  items: minutes,
@@ -1969,13 +2062,14 @@ function TimePickerMinuteList({ classNames, ...props }) {
1969
2062
  ...props,
1970
2063
  children: minutes.map((minute) => {
1971
2064
  const isSelected = minute === currentTime.minutes;
2065
+ const isMinuteFullyDisabled = isMinuteDisabled(minute);
1972
2066
  const optionClass = [classNames?.option, isSelected && classNames?.optionSelected].filter(Boolean).join(" ") || void 0;
1973
2067
  return /* @__PURE__ */ jsx(
1974
2068
  "li",
1975
2069
  {
1976
2070
  role: "option",
1977
2071
  "aria-selected": isSelected,
1978
- "aria-disabled": isDisabled || void 0,
2072
+ "aria-disabled": isDisabled || isMinuteFullyDisabled || void 0,
1979
2073
  "aria-label": ctx.labels.minuteOption(minute),
1980
2074
  "data-selected": isSelected || void 0,
1981
2075
  tabIndex: isSelected ? 0 : -1,