@kalyx/react 1.0.0-rc.10 → 1.0.0-rc.12

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,62 @@
1
1
  # @kalyx/react
2
2
 
3
+ ## 1.0.0-rc.12
4
+
5
+ ### Patch Changes
6
+
7
+ - e5bd203: test(ssr): cover controlled value at a DST boundary with displayTimezone across all 7 pickers
8
+
9
+ The existing `renderToString` smoke tests only exercised the default `value=null` (or generic non-DST) path. They missed the highest-risk hydration scenario: a controlled value rendered on a DST transition day (2026-03-08 US Eastern spring-forward) while `displayTimezone="America/New_York"` forces the calendar/highlighting/time rows to map UTC ↔ civil time across the seam.
10
+
11
+ Each picker now has one new determinism test inside its `SSR safety` describe that renders the same tree twice via `renderToString` and asserts byte-identical output. Any accidental clock-read or non-deterministic `Intl` path during render would surface as a string diff.
12
+ - `DatePicker` — 2026-03-08 day cell + popover + calendar
13
+ - `RangePicker` — range straddling the DST seam
14
+ - `TimePicker` — value at 02:00 EST → 03:00 EDT
15
+ - `DateTimePicker` — full date + time tree (highest hydration surface)
16
+ - `MonthPicker` — March 2026 month grid
17
+ - `YearPicker` — 2026 decade grid
18
+ - `WeekPicker` — week containing the spring-forward day
19
+
20
+ No production code changed; the suite goes from 314 → 321 picker tests and locks the current SSR-deterministic behaviour against future regressions.
21
+
22
+ - Updated dependencies [0556886]
23
+ - @kalyx/core@1.0.0-rc.12
24
+
25
+ ## 1.0.0-rc.11
26
+
27
+ ### Patch Changes
28
+
29
+ - 19ac1c0: fix(core): allow `generateMinutes` step values up to 60
30
+
31
+ `generateMinutes(step)` rejected any step above 30, which prevented legitimate cases like `step=45` (quarter-and-three-quarters past the hour) and `step=60` (on-the-hour only). The slot-generation loop already works for any 1–60 integer, so the upper bound is now 60 with the same error message format. Steps `0`, `61+`, and negative values still throw. No callers in `@kalyx/react` relied on the previous narrower bound.
32
+
33
+ - eafc3c1: fix(input): drop stale typed text when the parent re-sets value externally
34
+
35
+ `<DatePicker.Input>` and `<TimePicker.Input>` keep half-typed text in a local `inputText` state while the user is editing — without it, parse-failed input would vanish on every keystroke. The state was reset only when the user committed via blur/Enter, which left a real gap:
36
+
37
+ If the value changed from anywhere else (parent re-rendered with a new `value`, a calendar click, a `Preset`, a custom-Hook `setRange`, an HourList option), the Input kept rendering the user's stale text. Source-of-truth and visible value diverged silently.
38
+
39
+ A `useEffect` keyed on `ctx.value` now resets `inputText` whenever the source-of-truth changes. The Input goes back to formatting the new value normally. For DatePicker the reset is skipped while an IME composition is in flight (Korean/Japanese/Chinese), so an in-flight character is never wiped mid-stroke.
40
+
41
+ Impact: `DatePicker`, `MonthPicker`, `YearPicker` (the last two reuse `DatePickerInput`), and `TimePicker` all get the fix. `RangePicker`/`WeekPicker`/`DateTimePicker` Inputs are read-only or non-editable and already track context directly, so they were never affected.
42
+
43
+ - 23bc187: docs(i18n): translate Korean intro and stop bumping demo apps on every patch
44
+ - `apps/docs-site/i18n/ko/.../intro.md` is now fully translated to Korean. Previously the file lived in the `i18n/ko/` tree but the body was the verbatim English copy, so Korean docs visitors saw English content on the landing page.
45
+ - `.changeset/config.json` `ignore` now includes the two demo workspaces (`@kalyx/docs`, `docs-site`). Changesets used to bump them on every release because they depend on `@kalyx/react`, polluting their CHANGELOG with `Updated dependencies` entries and adding noise to release PRs. Demo apps aren't published — they don't need versioning.
46
+
47
+ - c8a6609: fix(rangepicker): announce next selection target and final range to screen readers
48
+
49
+ `<RangePicker.Calendar>` now announces context-aware messages through its existing `role="status"` live region:
50
+ - After the first click (start), it announces `<formatted-date>. Now select end date.` so screen-reader users know the next click commits the other endpoint.
51
+ - After the second click (end), it announces `Range selected: <start> – <end>` instead of just the bare date — matching the swap-if-before behaviour so the announcement always reflects what was committed.
52
+ - Week-mode commits now share the same `Range selected: ...` prefix for consistency.
53
+
54
+ The two new strings are wired through `RangePickerLabels.selectingEnd` and `RangePickerLabels.rangeSelected` with English defaults, and they are fully overridable via the existing `labels` prop for i18n. `@kalyx/core` gets a `minor` bump because `RangePickerLabels` gained required fields (with defaults supplied by `DEFAULT_RANGEPICKER_LABELS`); any consumer constructing a literal `RangePickerLabels` from scratch will need to add the two keys.
55
+
56
+ - Updated dependencies [19ac1c0]
57
+ - Updated dependencies [c8a6609]
58
+ - @kalyx/core@1.0.0-rc.11
59
+
3
60
  ## 1.0.0-rc.10
4
61
 
5
62
  ### Minor Changes
package/dist/index.cjs CHANGED
@@ -157,6 +157,10 @@ var DatePickerInput = react.forwardRef(
157
157
  const displayFormat = formatProp ?? ctx.displayFormat;
158
158
  const [inputText, setInputText] = react.useState(null);
159
159
  const isComposingRef = react.useRef(false);
160
+ react.useEffect(() => {
161
+ if (isComposingRef.current) return;
162
+ setInputText(null);
163
+ }, [ctx.value]);
160
164
  let formattedValue = "";
161
165
  if (ctx.value) {
162
166
  try {
@@ -1099,6 +1103,17 @@ function useRangePickerContext(componentName) {
1099
1103
  return context;
1100
1104
  }
1101
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
+ };
1102
1117
  function RangePickerRoot({
1103
1118
  value: controlledValue,
1104
1119
  defaultValue,
@@ -1125,6 +1140,8 @@ function RangePickerRoot({
1125
1140
  const [isOpen, setIsOpen] = react.useState(false);
1126
1141
  const [selectingTarget, setSelectingTarget] = react.useState("start");
1127
1142
  const [hoverDate, setHoverDate] = react.useState(null);
1143
+ const [announcement, setAnnouncement] = react.useState("");
1144
+ const announce = react.useCallback((message) => setAnnouncement(message), []);
1128
1145
  const [viewMonth, setViewMonth] = react.useState(
1129
1146
  () => currentValue.start ?? adapter.today(displayTimezone)
1130
1147
  );
@@ -1227,7 +1244,8 @@ function RangePickerRoot({
1227
1244
  isDisabled,
1228
1245
  isReadOnly: readOnly,
1229
1246
  pickerId,
1230
- labels: mergedLabels
1247
+ labels: mergedLabels,
1248
+ announce
1231
1249
  }),
1232
1250
  [
1233
1251
  currentValue,
@@ -1250,10 +1268,14 @@ function RangePickerRoot({
1250
1268
  isDisabled,
1251
1269
  readOnly,
1252
1270
  pickerId,
1253
- mergedLabels
1271
+ mergedLabels,
1272
+ announce
1254
1273
  ]
1255
1274
  );
1256
- 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
+ ] });
1257
1279
  }
1258
1280
  var RangePickerInput = react.forwardRef(
1259
1281
  function RangePickerInput2({ part, format: formatProp, onClick, onKeyDown, ...props }, ref) {
@@ -1353,17 +1375,6 @@ function safeFormatFullDate2(iso, locale) {
1353
1375
  return iso;
1354
1376
  }
1355
1377
  }
1356
- var srOnly2 = {
1357
- position: "absolute",
1358
- width: "1px",
1359
- height: "1px",
1360
- padding: 0,
1361
- margin: "-1px",
1362
- overflow: "hidden",
1363
- clip: "rect(0, 0, 0, 0)",
1364
- whiteSpace: "nowrap",
1365
- border: 0
1366
- };
1367
1378
  function RangePickerCalendar({
1368
1379
  classNames,
1369
1380
  selectionMode = "range",
@@ -1373,7 +1384,6 @@ function RangePickerCalendar({
1373
1384
  }) {
1374
1385
  const ctx = useRangePickerContext("RangePicker.Calendar");
1375
1386
  const gridRef = react.useRef(null);
1376
- const [announcement, setAnnouncement] = react.useState("");
1377
1387
  const {
1378
1388
  adapter,
1379
1389
  viewMonth,
@@ -1425,7 +1435,7 @@ function RangePickerCalendar({
1425
1435
  ctx.setFocusedDate(adapter.startOfMonth(newMonth));
1426
1436
  const y = adapter.getYear(newMonth);
1427
1437
  const m = adapter.getMonth(newMonth);
1428
- setAnnouncement(core.formatMonthYear(y, m, locale));
1438
+ ctx.announce(core.formatMonthYear(y, m, locale));
1429
1439
  },
1430
1440
  [adapter, viewMonth, ctx, locale]
1431
1441
  );
@@ -1437,15 +1447,27 @@ function RangePickerCalendar({
1437
1447
  const range = { start: weekStart, end: weekEnd };
1438
1448
  ctx.setRange(range);
1439
1449
  ctx.close();
1440
- setAnnouncement(
1441
- `${safeFormatFullDate2(weekStart, locale)} \u2013 ${safeFormatFullDate2(weekEnd, locale)}`
1450
+ ctx.announce(
1451
+ `${ctx.labels.rangeSelected}: ${safeFormatFullDate2(weekStart, locale)} \u2013 ${safeFormatFullDate2(weekEnd, locale)}`
1442
1452
  );
1443
1453
  } else {
1454
+ const wasPickingStart = selectingTarget === "start";
1455
+ const previousStart = value.start;
1444
1456
  ctx.selectDate(iso);
1445
- 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
+ }
1446
1468
  }
1447
1469
  },
1448
- [selectionMode, adapter, weekStartsOn, ctx, locale]
1470
+ [selectionMode, adapter, weekStartsOn, ctx, locale, selectingTarget, value.start]
1449
1471
  );
1450
1472
  const handleDayClick = react.useCallback(
1451
1473
  (day) => {
@@ -1654,8 +1676,7 @@ function RangePickerCalendar({
1654
1676
  )) })
1655
1677
  ]
1656
1678
  }
1657
- ),
1658
- /* @__PURE__ */ jsxRuntime.jsx("div", { role: "status", "aria-live": "polite", "aria-atomic": "true", style: srOnly2, children: announcement })
1679
+ )
1659
1680
  ] });
1660
1681
  }
1661
1682
  function RangePickerPresets({ classNames, children, ...props }) {
@@ -1848,6 +1869,9 @@ var TimePickerInput = react.forwardRef(
1848
1869
  function TimePickerInput2({ onBlur, onKeyDown, ...props }, ref) {
1849
1870
  const ctx = useTimePickerContext("TimePicker.Input");
1850
1871
  const [inputText, setInputText] = react.useState(null);
1872
+ react.useEffect(() => {
1873
+ setInputText(null);
1874
+ }, [ctx.value]);
1851
1875
  const displayValue = inputText !== null ? inputText : core.formatTimeString(ctx.currentTime, ctx.withSeconds);
1852
1876
  const commitInput = react.useCallback(() => {
1853
1877
  if (inputText === null) return;