@kalyx/react 1.0.0-rc.7 → 1.0.0-rc.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,40 @@
1
1
  # @kalyx/react
2
2
 
3
+ ## 1.0.0-rc.9
4
+
5
+ ### Patch Changes
6
+
7
+ - 1a77283: docs: clarify `TimePicker.filterTime` polarity. The predicate returns `true` to mark a slot **unselectable** — same polarity as MUI X's `shouldDisableTime`, and the **inverse** of react-datepicker's `filterTime` (which returns `true` to _keep_ a slot). Earlier JSDoc/changelog called it "equivalent to react-datepicker's `filterTime`", which is misleading because the polarity is reversed; react-datepicker migrators must invert their predicate. No runtime behavior change — JSDoc, the published package description (≤16 KB), and docs only.
8
+
9
+ ## 1.0.0-rc.8
10
+
11
+ ### Minor Changes
12
+
13
+ - 0d3b845: `TimePicker.Root` gains a programmatic **`filterTime`** prop — `(hours: number, minutes: number) => boolean` returning `true` for any slot that should be unselectable. Equivalent to `react-datepicker`'s `filterTime` and MUI X's `shouldDisableTime`, covering use cases the static `step` prop can't (business-hours-only, lunch breaks, blackout slots, per-day variations).
14
+
15
+ ```tsx
16
+ <TimePicker
17
+ value={time}
18
+ onChange={setTime}
19
+ step={15}
20
+ // Business hours only: 09:00–11:45 and 13:00–17:45 (no lunch slot)
21
+ filterTime={(h, m) => h < 9 || h >= 18 || h === 12}
22
+ >
23
+ <TimePicker.Input />
24
+ <TimePicker.HourList />
25
+ <TimePicker.MinuteList />
26
+ </TimePicker>
27
+ ```
28
+
29
+ Behavior:
30
+ - **`MinuteList`** — minutes for which `filterTime(currentHour, minute)` returns `true` get `aria-disabled="true"` and reject click/Enter.
31
+ - **`HourList`** — an hour is marked `aria-disabled="true"` only when `filterTime` returns `true` for **every** step minute within it. Hours with at least one open minute remain selectable.
32
+ - 12-hour mode — the predicate always receives 24-hour values (`0`–`23`) regardless of the picker's display format.
33
+
34
+ **Note**: `DateTimePicker` does not yet wire this through — combine `DatePicker.Root` + `TimePicker.Root` manually if you need both date and time-slot filtering in the same picker.
35
+
36
+ Bundle ceiling raised 15 → 16 KB (PR #N follows the 12→13→14→15 cadence — each raise tied to a documented feature; CLAUDE.md §2 records the chain). Measured 15.01 KB ESM / 15.16 KB CJS at this commit, ~4× smaller than react-datepicker.
37
+
3
38
  ## 1.0.0-rc.7
4
39
 
5
40
  ### Minor Changes
package/dist/index.cjs CHANGED
@@ -1792,6 +1792,7 @@ function TimePickerRoot({
1792
1792
  displayTimezone,
1793
1793
  disabled = false,
1794
1794
  readOnly = false,
1795
+ filterTime,
1795
1796
  labels: labelsProp,
1796
1797
  children
1797
1798
  }) {
@@ -1833,7 +1834,8 @@ function TimePickerRoot({
1833
1834
  isReadOnly: readOnly,
1834
1835
  currentTime,
1835
1836
  pickerId,
1836
- labels: mergedLabels
1837
+ labels: mergedLabels,
1838
+ filterTime
1837
1839
  }),
1838
1840
  [
1839
1841
  currentValue,
@@ -1846,7 +1848,8 @@ function TimePickerRoot({
1846
1848
  readOnly,
1847
1849
  currentTime,
1848
1850
  pickerId,
1849
- mergedLabels
1851
+ mergedLabels,
1852
+ filterTime
1850
1853
  ]
1851
1854
  );
1852
1855
  return /* @__PURE__ */ jsxRuntime.jsx(TimePickerContext.Provider, { value: contextValue, children });
@@ -1951,17 +1954,41 @@ function useListboxNavigation({
1951
1954
  }
1952
1955
  function TimePickerHourList({ classNames, ...props }) {
1953
1956
  const ctx = useTimePickerContext("TimePicker.HourList");
1954
- const { format, currentTime, isDisabled, isReadOnly } = ctx;
1957
+ const { format, step, currentTime, isDisabled, isReadOnly, filterTime } = ctx;
1955
1958
  const hours = react.useMemo(() => core.generateHours(format), [format]);
1956
1959
  const selectedHourDisplay = format === "12h" ? core.to12Hour(currentTime.hours).hours12 : currentTime.hours;
1957
1960
  const currentPeriod = format === "12h" ? core.to12Hour(currentTime.hours).period : null;
1961
+ const fullyDisabledHours24 = react.useMemo(() => {
1962
+ if (!filterTime) return null;
1963
+ const disabled = /* @__PURE__ */ new Set();
1964
+ for (let h = 0; h < 24; h++) {
1965
+ let allRejected = true;
1966
+ for (let m = 0; m < 60; m += step) {
1967
+ if (!filterTime(h, m)) {
1968
+ allRejected = false;
1969
+ break;
1970
+ }
1971
+ }
1972
+ if (allRejected) disabled.add(h);
1973
+ }
1974
+ return disabled;
1975
+ }, [filterTime, step]);
1976
+ const isHourDisabled = react.useCallback(
1977
+ (hourDisplay) => {
1978
+ if (!fullyDisabledHours24) return false;
1979
+ const hours24 = format === "12h" && currentPeriod ? core.to24Hour(hourDisplay, currentPeriod) : hourDisplay;
1980
+ return fullyDisabledHours24.has(hours24);
1981
+ },
1982
+ [fullyDisabledHours24, format, currentPeriod]
1983
+ );
1958
1984
  const handleSelect = react.useCallback(
1959
1985
  (hourDisplay) => {
1960
1986
  if (isDisabled || isReadOnly) return;
1987
+ if (isHourDisabled(hourDisplay)) return;
1961
1988
  const hours24 = format === "12h" && currentPeriod ? core.to24Hour(hourDisplay, currentPeriod) : hourDisplay;
1962
1989
  ctx.setTime({ hours: hours24 });
1963
1990
  },
1964
- [format, currentPeriod, ctx, isDisabled, isReadOnly]
1991
+ [format, currentPeriod, ctx, isDisabled, isReadOnly, isHourDisabled]
1965
1992
  );
1966
1993
  const { listRef, handleKeyDown } = useListboxNavigation({
1967
1994
  items: hours,
@@ -1979,13 +2006,14 @@ function TimePickerHourList({ classNames, ...props }) {
1979
2006
  ...props,
1980
2007
  children: hours.map((hour) => {
1981
2008
  const isSelected = hour === selectedHourDisplay;
2009
+ const isHourFullyDisabled = isHourDisabled(hour);
1982
2010
  const optionClass = [classNames?.option, isSelected && classNames?.optionSelected].filter(Boolean).join(" ") || void 0;
1983
2011
  return /* @__PURE__ */ jsxRuntime.jsx(
1984
2012
  "li",
1985
2013
  {
1986
2014
  role: "option",
1987
2015
  "aria-selected": isSelected,
1988
- "aria-disabled": isDisabled || void 0,
2016
+ "aria-disabled": isDisabled || isHourFullyDisabled || void 0,
1989
2017
  "aria-label": ctx.labels.hourOption(hour),
1990
2018
  "data-selected": isSelected || void 0,
1991
2019
  tabIndex: isSelected ? 0 : -1,
@@ -2002,14 +2030,22 @@ function TimePickerHourList({ classNames, ...props }) {
2002
2030
  }
2003
2031
  function TimePickerMinuteList({ classNames, ...props }) {
2004
2032
  const ctx = useTimePickerContext("TimePicker.MinuteList");
2005
- const { step, currentTime, isDisabled, isReadOnly } = ctx;
2033
+ const { step, currentTime, isDisabled, isReadOnly, filterTime } = ctx;
2006
2034
  const minutes = react.useMemo(() => core.generateMinutes(step), [step]);
2035
+ const isMinuteDisabled = react.useCallback(
2036
+ (minute) => {
2037
+ if (!filterTime) return false;
2038
+ return filterTime(currentTime.hours, minute);
2039
+ },
2040
+ [filterTime, currentTime.hours]
2041
+ );
2007
2042
  const handleSelect = react.useCallback(
2008
2043
  (minute) => {
2009
2044
  if (isDisabled || isReadOnly) return;
2045
+ if (isMinuteDisabled(minute)) return;
2010
2046
  ctx.setTime({ minutes: minute });
2011
2047
  },
2012
- [ctx, isDisabled, isReadOnly]
2048
+ [ctx, isDisabled, isReadOnly, isMinuteDisabled]
2013
2049
  );
2014
2050
  const { listRef, handleKeyDown } = useListboxNavigation({
2015
2051
  items: minutes,
@@ -2027,13 +2063,14 @@ function TimePickerMinuteList({ classNames, ...props }) {
2027
2063
  ...props,
2028
2064
  children: minutes.map((minute) => {
2029
2065
  const isSelected = minute === currentTime.minutes;
2066
+ const isMinuteFullyDisabled = isMinuteDisabled(minute);
2030
2067
  const optionClass = [classNames?.option, isSelected && classNames?.optionSelected].filter(Boolean).join(" ") || void 0;
2031
2068
  return /* @__PURE__ */ jsxRuntime.jsx(
2032
2069
  "li",
2033
2070
  {
2034
2071
  role: "option",
2035
2072
  "aria-selected": isSelected,
2036
- "aria-disabled": isDisabled || void 0,
2073
+ "aria-disabled": isDisabled || isMinuteFullyDisabled || void 0,
2037
2074
  "aria-label": ctx.labels.minuteOption(minute),
2038
2075
  "data-selected": isSelected || void 0,
2039
2076
  tabIndex: isSelected ? 0 : -1,