@astryxdesign/core 0.7.1-canary.655fc29 → 0.7.1-canary.666ad2f

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.
Files changed (78) hide show
  1. package/dist/Calendar/Calendar.d.ts.map +1 -1
  2. package/dist/Calendar/Calendar.js +185 -31
  3. package/dist/DateInput/MonthScroller.d.ts.map +1 -1
  4. package/dist/DateInput/MonthScroller.js +12 -4
  5. package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
  6. package/dist/PowerSearch/PowerSearchEditPopover.js +6 -0
  7. package/dist/Resizable/ResizeHandle.d.ts +1 -1
  8. package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
  9. package/dist/Resizable/ResizeHandle.js +35 -7
  10. package/dist/Resizable/useResizable.d.ts +23 -0
  11. package/dist/Resizable/useResizable.d.ts.map +1 -1
  12. package/dist/Resizable/useResizable.js +78 -0
  13. package/dist/Selector/Selector.d.ts.map +1 -1
  14. package/dist/Selector/Selector.js +8 -0
  15. package/dist/SideNav/SideNav.d.ts.map +1 -1
  16. package/dist/SideNav/SideNav.js +10 -2
  17. package/dist/Slider/Slider.d.ts.map +1 -1
  18. package/dist/Slider/Slider.js +8 -4
  19. package/dist/TreeList/TreeList.d.ts.map +1 -1
  20. package/dist/TreeList/TreeList.js +6 -3
  21. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  22. package/dist/TreeList/TreeListItem.js +7 -1
  23. package/dist/hooks/useTreeFocus.d.ts +21 -0
  24. package/dist/hooks/useTreeFocus.d.ts.map +1 -1
  25. package/dist/hooks/useTreeFocus.js +40 -2
  26. package/dist/utils/plainDate.d.ts +12 -0
  27. package/dist/utils/plainDate.d.ts.map +1 -1
  28. package/dist/utils/plainDate.js +50 -6
  29. package/package.json +3 -3
  30. package/src/AlertDialog/AlertDialog.test.tsx +3 -14
  31. package/src/AlertDialog/__tests__/AlertDialog.a11y.chromium.spec.ts +115 -0
  32. package/src/AlertDialog/__tests__/AlertDialog.a11y.renders.tsx +52 -0
  33. package/src/AlertDialog/__tests__/AlertDialog.a11y.states.ts +68 -0
  34. package/src/AlertDialog/__tests__/AlertDialog.a11y.test.tsx +144 -0
  35. package/src/Calendar/Calendar.spec.md +20 -24
  36. package/src/Calendar/Calendar.test.tsx +423 -0
  37. package/src/Calendar/Calendar.tsx +250 -40
  38. package/src/Carousel/Carousel.test.tsx +54 -71
  39. package/src/Carousel/__tests__/Carousel.a11y.chromium.spec.ts +192 -0
  40. package/src/Carousel/__tests__/Carousel.a11y.known-failures.ts +27 -0
  41. package/src/Carousel/__tests__/Carousel.a11y.renders.tsx +39 -0
  42. package/src/Carousel/__tests__/Carousel.a11y.states.ts +139 -0
  43. package/src/Carousel/__tests__/Carousel.a11y.test.tsx +127 -0
  44. package/src/ComplexSelector/ComplexSelector.spec.md +14 -9
  45. package/src/DateInput/DateInput.spec.md +13 -15
  46. package/src/DateInput/DateInputTouch.test.tsx +22 -0
  47. package/src/DateInput/MonthScroller.tsx +12 -4
  48. package/src/MultiSelector/MultiSelector.spec.md +1 -1
  49. package/src/PowerSearch/PowerSearchEditPopover.test.tsx +78 -0
  50. package/src/PowerSearch/PowerSearchEditPopover.tsx +6 -0
  51. package/src/Resizable/ResizeHandle.test.tsx +274 -2
  52. package/src/Resizable/ResizeHandle.tsx +45 -13
  53. package/src/Resizable/useResizable.ts +95 -0
  54. package/src/Selector/Selector.test.tsx +52 -0
  55. package/src/Selector/Selector.tsx +8 -0
  56. package/src/SideNav/SideNav.test.tsx +35 -0
  57. package/src/SideNav/SideNav.tsx +16 -2
  58. package/src/Slider/Slider.spec.md +1 -1
  59. package/src/Slider/Slider.test.tsx +63 -0
  60. package/src/Slider/Slider.tsx +16 -4
  61. package/src/Slider/__tests__/Slider.a11y.chromium.spec.ts +112 -0
  62. package/src/Slider/__tests__/Slider.a11y.known-failures.ts +12 -0
  63. package/src/Slider/__tests__/Slider.a11y.renders.tsx +42 -0
  64. package/src/Slider/__tests__/Slider.a11y.states.ts +106 -0
  65. package/src/Slider/__tests__/Slider.a11y.test.tsx +105 -0
  66. package/src/TreeList/TreeList.spec.md +12 -18
  67. package/src/TreeList/TreeList.test.tsx +72 -0
  68. package/src/TreeList/TreeList.tsx +10 -6
  69. package/src/TreeList/TreeListItem.tsx +5 -0
  70. package/src/TreeList/__tests__/TreeList.a11y.chromium.spec.ts +125 -0
  71. package/src/TreeList/__tests__/TreeList.a11y.renders.tsx +44 -0
  72. package/src/TreeList/__tests__/TreeList.a11y.states.ts +164 -0
  73. package/src/TreeList/__tests__/TreeList.a11y.test.tsx +138 -0
  74. package/src/TreeList/__tests__/TreeListRowNames.a11y.chromium.spec.ts +82 -0
  75. package/src/hooks/useTreeFocus.test.tsx +37 -0
  76. package/src/hooks/useTreeFocus.ts +55 -2
  77. package/src/utils/plainDate.test.ts +48 -0
  78. package/src/utils/plainDate.ts +53 -6
@@ -1 +1 @@
1
- {"version":3,"file":"Calendar.d.ts","sourceRoot":"","sources":["../../src/Calendar/Calendar.tsx"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAkD5C,YAAY,EACV,aAAa,EACb,SAAS,EACT,aAAa,EACb,SAAS,GACV,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EACV,aAAa,EACb,SAAS,EACT,aAAa,EACb,SAAS,EACV,MAAM,oBAAoB,CAAC;AAK5B,+CAA+C;AAE/C,MAAM,WAAW,cAAc;IAC7B,wEAAwE;IACxE,UAAU,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAC;CAC3C;AAID,UAAU,iBAAkB,SAAQ,IAAI,CACtC,SAAS,CAAC,cAAc,CAAC,EACzB,UAAU,GAAG,cAAc,CAC5B;IACC,kDAAkD;IAClD,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC,qDAAqD;IACrD,SAAS,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IACtC,+CAA+C;IAC/C,cAAc,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;IAEvB,4CAA4C;IAC5C,GAAG,CAAC,EAAE,aAAa,CAAC;IAEpB,4CAA4C;IAC5C,GAAG,CAAC,EAAE,aAAa,CAAC;IAEpB;;;OAGG;IACH,eAAe,CAAC,EAAE,aAAa,CAAC,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,CAAC;IAEzD;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,aAAa,CAAC;IAE1B,yDAAyD;IACzD,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,aAAa,KAAK,IAAI,CAAC;IAEvD;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B;;;;OAIG;IACH,YAAY,CAAC,EAAE,SAAS,GAAG,aAAa,CAAC;CAC1C;AAID,UAAU,mBAAoB,SAAQ,iBAAiB;IACrD,qBAAqB;IACrB,IAAI,CAAC,EAAE,QAAQ,CAAC;IAEhB,mFAAmF;IACnF,KAAK,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IAE7B,0CAA0C;IAC1C,YAAY,CAAC,EAAE,aAAa,CAAC;IAE7B,qCAAqC;IACrC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,IAAI,KAAK,IAAI,CAAC;CAC9D;AAED,UAAU,kBAAmB,SAAQ,iBAAiB;IACpD,qBAAqB;IACrB,IAAI,EAAE,OAAO,CAAC;IAEd,8DAA8D;IAC9D,KAAK,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IAEzB,0CAA0C;IAC1C,YAAY,CAAC,EAAE,SAAS,CAAC;IAEzB,sCAAsC;IACtC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;CACvC;AAED,MAAM,MAAM,aAAa,GAAG,mBAAmB,GAAG,kBAAkB,CAAC;AAMrE;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,EAAC,GAAG,EAAE,GAAG,KAAK,EAAC,EAAE,aAAa,+BAsXtD;yBAtXe,QAAQ"}
1
+ {"version":3,"file":"Calendar.d.ts","sourceRoot":"","sources":["../../src/Calendar/Calendar.tsx"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAsD5C,YAAY,EACV,aAAa,EACb,SAAS,EACT,aAAa,EACb,SAAS,GACV,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EACV,aAAa,EACb,SAAS,EACT,aAAa,EACb,SAAS,EACV,MAAM,oBAAoB,CAAC;AAS5B,+CAA+C;AAC/C,MAAM,WAAW,cAAc;IAC7B,wEAAwE;IACxE,UAAU,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAC;CAC3C;AAID,UAAU,iBAAkB,SAAQ,IAAI,CACtC,SAAS,CAAC,cAAc,CAAC,EACzB,UAAU,GAAG,cAAc,CAC5B;IACC,kDAAkD;IAClD,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC,qDAAqD;IACrD,SAAS,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IACtC,+CAA+C;IAC/C,cAAc,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;IAEvB,4CAA4C;IAC5C,GAAG,CAAC,EAAE,aAAa,CAAC;IAEpB,4CAA4C;IAC5C,GAAG,CAAC,EAAE,aAAa,CAAC;IAEpB;;;OAGG;IACH,eAAe,CAAC,EAAE,aAAa,CAAC,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,CAAC;IAEzD;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,aAAa,CAAC;IAE1B,yDAAyD;IACzD,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,aAAa,KAAK,IAAI,CAAC;IAEvD;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B;;;;OAIG;IACH,YAAY,CAAC,EAAE,SAAS,GAAG,aAAa,CAAC;CAC1C;AAID,UAAU,mBAAoB,SAAQ,iBAAiB;IACrD,qBAAqB;IACrB,IAAI,CAAC,EAAE,QAAQ,CAAC;IAEhB,mFAAmF;IACnF,KAAK,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IAE7B,0CAA0C;IAC1C,YAAY,CAAC,EAAE,aAAa,CAAC;IAE7B,qCAAqC;IACrC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,IAAI,KAAK,IAAI,CAAC;CAC9D;AAED,UAAU,kBAAmB,SAAQ,iBAAiB;IACpD,qBAAqB;IACrB,IAAI,EAAE,OAAO,CAAC;IAEd,8DAA8D;IAC9D,KAAK,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IAEzB,0CAA0C;IAC1C,YAAY,CAAC,EAAE,SAAS,CAAC;IAEzB,sCAAsC;IACtC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;CACvC;AAED,MAAM,MAAM,aAAa,GAAG,mBAAmB,GAAG,kBAAkB,CAAC;AAqBrE;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,EAAC,GAAG,EAAE,GAAG,KAAK,EAAC,EAAE,aAAa,+BAghBtD;yBAhhBe,QAAQ"}
@@ -24,7 +24,7 @@ import { Icon } from "../Icon/index.js";
24
24
  import { useAnnounce, useGridFocus } from "../hooks/index.js";
25
25
  import { useCalendarDays, useCalendarConstraints } from "./hooks/index.js";
26
26
  import { calendarStyles, monthGridStyles, dayCellStyles, dayCellTheme } from "./styles.js";
27
- import { plainDateFromISO, plainDateToISO, plainDateToDate, plainDateToday, plainDateSetFirstOfMonth, plainDateAddMonths, plainDateAddDays, plainDateIsBefore, plainDateIsEqual, plainDateGetWeekNumber, plainDateFormat, DATE_FORMAT_WITH_WEEKDAY, DATE_FORMAT_MONTH_YEAR } from "../utils/plainDate.js";
27
+ import { plainDateFromISO, plainDateToISO, plainDateToDate, plainDateToday, plainDateSetFirstOfMonth, plainDateAddMonths, plainDateAddDays, plainDateDiffDays, getDaysInMonth, plainDateIsAfter, plainDateIsBefore, plainDateIsEqual, plainDateGetWeekNumber, plainDateFormat, plainDateWeekFieldName, DATE_FORMAT_WITH_WEEKDAY, DATE_FORMAT_MONTH_YEAR } from "../utils/plainDate.js";
28
28
  import { mergeProps, composeEventHandlers, rtlStyles } from "../utils/index.js";
29
29
  import { focusOutlineProps } from "../utils/focusOutline.stylex.js";
30
30
  import { interactionOverlayStyles } from "../utils/interactionOverlay.stylex.js";
@@ -39,6 +39,9 @@ import { computeDayCellState, computeRangeRounding, computePreviewRounding, comp
39
39
  import { normalizeDayOfWeek } from "../utils/dateTypes.js";
40
40
  import { themeProps } from "../utils/themeProps.js";
41
41
  import { useLocale, useTranslator } from "../i18n/index.js";
42
+ import { VisuallyHidden } from "../VisuallyHidden/index.js";
43
+
44
+ /** How a month navigation moves the focused day: by its row offset in days, or by whole months. */
42
45
 
43
46
  /** Imperative handle for Calendar handleRef */
44
47
 
@@ -46,6 +49,22 @@ import { useLocale, useTranslator } from "../i18n/index.js";
46
49
 
47
50
  // ─── Mode-specific Props (discriminated union) ────────────────
48
51
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
52
+ /** The first day of `month`'s month that `isDisabled` allows, or null. */
53
+ function firstEnabledDayOfMonth(month, isDisabled) {
54
+ const days = getDaysInMonth(month.year, month.month);
55
+ for (let day = 1; day <= days; day += 1) {
56
+ const pd = {
57
+ year: month.year,
58
+ month: month.month,
59
+ day
60
+ };
61
+ if (!isDisabled(pd)) {
62
+ return pd;
63
+ }
64
+ }
65
+ return null;
66
+ }
67
+
49
68
  // =============================================================================
50
69
  // Main Component
51
70
  // =============================================================================
@@ -157,6 +176,30 @@ export function Calendar({
157
176
  });
158
177
  }, [baseMonth, numberOfMonths]);
159
178
 
179
+ // A pending target is claimed by the grid that shows its month, or dropped
180
+ // by the next focus move that is not onto the target day: when a controlled
181
+ // owner refuses the month change, no grid shows the target's month, and a
182
+ // stale target would later steal focus (and Enter) when that month does
183
+ // appear. Only a grid showing the target's month receives it; any other
184
+ // focus change (an arrow to another day, the month header's buttons, or out
185
+ // of the calendar) drops it (see onBlur below).
186
+ const claimablePendingFocus = useMemo(() => {
187
+ if (pendingFocus == null) {
188
+ return null;
189
+ }
190
+ const target = plainDateFromISO(pendingFocus);
191
+ return visibleMonths.some(pd => pd.year === target.year && pd.month === target.month) ? pendingFocus : null;
192
+ }, [pendingFocus, visibleMonths]);
193
+ const handleRootBlur = useCallback(e => {
194
+ // Only the move onto the target day itself keeps the target: an arrow
195
+ // to any other day, a move to the header buttons, or focus leaving the
196
+ // calendar all drop it.
197
+ const movesToTarget = e.relatedTarget instanceof Element && e.relatedTarget.getAttribute('data-date') === pendingFocus;
198
+ if (pendingFocus != null && !movesToTarget) {
199
+ setPendingFocus(null);
200
+ }
201
+ }, [pendingFocus]);
202
+
160
203
  // Format month header
161
204
  const monthYearLabel = useMemo(() => {
162
205
  if (numberOfMonths === 1) {
@@ -185,6 +228,8 @@ export function Calendar({
185
228
  }, [monthYearLabel, announce]);
186
229
 
187
230
  // Determine if prev/next navigation is possible based on min/max
231
+ const minDateForKeys = useMemo(() => min ? plainDateFromISO(min) : null, [min]);
232
+ const maxDateForKeys = useMemo(() => max ? plainDateFromISO(max) : null, [max]);
188
233
  const canNavigatePrevious = useMemo(() => {
189
234
  if (!min) {
190
235
  return true;
@@ -204,23 +249,109 @@ export function Calendar({
204
249
  }, [max, baseMonth, numberOfMonths]);
205
250
 
206
251
  // Navigation handlers
207
- const navigateMonth = useCallback((delta, focusedDate, offset) => {
208
- const newPd = plainDateAddMonths(baseMonth, delta);
209
- const newISO = plainDateToISO(newPd);
252
+ // The same constraint view the grids use, so a key can resolve its target
253
+ // before the month changes.
254
+ const rootRangeAnchor = useMemo(() => mode === 'range' && rangeSelectionStart ? plainDateFromISO(rangeSelectionStart) : null, [mode, rangeSelectionStart]);
255
+ const {
256
+ isDateDisabled: isRootDateDisabled
257
+ } = useCalendarConstraints({
258
+ min,
259
+ max,
260
+ dateConstraints,
261
+ maxRangeSpan,
262
+ minRangeSpan,
263
+ rangeAnchor: rootRangeAnchor
264
+ });
265
+ const navigateMonth = useCallback((delta, focusedDate, offset, unit = 'days') => {
266
+ const monthIndex = pd => pd.year * 12 + pd.month;
267
+ const firstShown = monthIndex(baseMonth);
268
+ const lastShown = firstShown + numberOfMonths - 1;
269
+
270
+ // The Previous/Next buttons move the month with no focused day.
271
+ if (!focusedDate) {
272
+ if (delta < 0 && !canNavigatePrevious || delta > 0 && !canNavigateNext) {
273
+ return;
274
+ }
275
+ const newPd = plainDateAddMonths(baseMonth, delta);
276
+ if (onFocusDateChange) {
277
+ onFocusDateChange(plainDateToISO(newPd));
278
+ } else {
279
+ setInternalFocusDate(newPd);
280
+ }
281
+ return;
282
+ }
210
283
 
211
- // Calculate target focus date if a focused date was provided
212
- if (focusedDate) {
213
- const currentPd = plainDateFromISO(focusedDate);
214
- const daysToMove = offset ?? 7;
215
- const targetPd = plainDateAddDays(currentPd, delta * daysToMove);
216
- setPendingFocus(plainDateToISO(targetPd));
284
+ // A key resolves its TARGET day first and decides by that, so a
285
+ // boundary blocks only out-of-range targets, never the month movement
286
+ // itself (ArrowUp from February 3 reaches January 27 at a January
287
+ // minimum), and a direction with no enabled day left changes nothing
288
+ // rather than dropping focus.
289
+ const currentPd = plainDateFromISO(focusedDate);
290
+ const inRange = pd => !(minDateForKeys && plainDateIsBefore(pd, minDateForKeys)) && !(maxDateForKeys && plainDateIsAfter(pd, maxDateForKeys));
291
+ let targetPd;
292
+ if (unit === 'months') {
293
+ // A page targets the same day of the month a month away (clamped to
294
+ // that month's length). Past `min`/`max` nothing moves; a month with
295
+ // no enabled day is no destination either, the same as an arrow with
296
+ // no enabled day in its direction; a target disabled by a constraint
297
+ // lands on that month's first enabled day instead.
298
+ const candidate = plainDateAddMonths(currentPd, delta);
299
+ if (!inRange(candidate)) {
300
+ return;
301
+ }
302
+ if (isRootDateDisabled(candidate)) {
303
+ const firstEnabled = firstEnabledDayOfMonth(candidate, isRootDateDisabled);
304
+ if (firstEnabled == null) {
305
+ return;
306
+ }
307
+ targetPd = firstEnabled;
308
+ } else {
309
+ targetPd = candidate;
310
+ }
311
+ } else {
312
+ // Walk the arrow's direction to the first enabled day, up to a year
313
+ // away; past `min`/`max` or with every day disabled, nothing moves.
314
+ const step = delta * (offset ?? 7);
315
+ let candidate = plainDateAddDays(currentPd, step);
316
+ let found = null;
317
+ while (Math.abs(plainDateDiffDays(currentPd, candidate)) <= 366) {
318
+ if (!inRange(candidate)) {
319
+ return;
320
+ }
321
+ if (!isRootDateDisabled(candidate)) {
322
+ found = candidate;
323
+ break;
324
+ }
325
+ candidate = plainDateAddDays(candidate, step);
326
+ }
327
+ if (found == null) {
328
+ return;
329
+ }
330
+ targetPd = found;
217
331
  }
332
+
333
+ // Focus moves to the target; the months change only when the target's
334
+ // month is not already shown. The view shifts the least it can in the
335
+ // key's direction, so the target month becomes the first (backwards)
336
+ // or last (forwards) month shown.
337
+ const resolved = targetPd;
338
+ const targetMonth = monthIndex(resolved);
339
+ setPendingFocus(plainDateToISO(resolved));
340
+ if (targetMonth >= firstShown && targetMonth <= lastShown) {
341
+ return;
342
+ }
343
+ const targetFirst = {
344
+ year: resolved.year,
345
+ month: resolved.month,
346
+ day: 1
347
+ };
348
+ const newPd = targetMonth < firstShown ? targetFirst : plainDateAddMonths(targetFirst, -(numberOfMonths - 1));
218
349
  if (onFocusDateChange) {
219
- onFocusDateChange(newISO);
350
+ onFocusDateChange(plainDateToISO(newPd));
220
351
  } else {
221
352
  setInternalFocusDate(newPd);
222
353
  }
223
- }, [baseMonth, onFocusDateChange]);
354
+ }, [baseMonth, canNavigateNext, canNavigatePrevious, isRootDateDisabled, maxDateForKeys, minDateForKeys, numberOfMonths, onFocusDateChange]);
224
355
 
225
356
  // Escape key handler to cancel range selection
226
357
  const handleCalendarKeyDown = useCallback(e => {
@@ -294,6 +425,7 @@ export function Calendar({
294
425
  return /*#__PURE__*/_jsxs("div", {
295
426
  ref: ref,
296
427
  ...rest,
428
+ onBlur: composeEventHandlers(rest.onBlur, handleRootBlur),
297
429
  ...mergeProps(themeProps('calendar', {
298
430
  mode
299
431
  }), stylex.props(calendarStyles.calendar, xstyle), className, style),
@@ -365,9 +497,9 @@ export function Calendar({
365
497
  onDayClick: handleDayClick,
366
498
  onDayHover: date => setHoveredDate(date ? plainDateToISO(date) : null),
367
499
  today: today,
368
- onNavigatePrevious: (focusedDate, offset) => navigateMonth(-1, focusedDate, offset),
369
- onNavigateNext: (focusedDate, offset) => navigateMonth(1, focusedDate, offset),
370
- pendingFocus: pendingFocus,
500
+ onNavigatePrevious: (focusedDate, offset, unit) => navigateMonth(-1, focusedDate, offset, unit),
501
+ onNavigateNext: (focusedDate, offset, unit) => navigateMonth(1, focusedDate, offset, unit),
502
+ pendingFocus: claimablePendingFocus,
371
503
  onPendingFocusHandled: () => setPendingFocus(null)
372
504
  }, `${month.year}-${month.month}`))
373
505
  })]
@@ -501,13 +633,13 @@ function MonthGrid({
501
633
  const handlePageUp = useCallback(() => {
502
634
  const focusedDate = getFocusedDate();
503
635
  if (focusedDate) {
504
- onNavigatePrevious?.(focusedDate, 7);
636
+ onNavigatePrevious?.(focusedDate, 1, 'months');
505
637
  }
506
638
  }, [getFocusedDate, onNavigatePrevious]);
507
639
  const handlePageDown = useCallback(() => {
508
640
  const focusedDate = getFocusedDate();
509
641
  if (focusedDate) {
510
- onNavigateNext?.(focusedDate, 7);
642
+ onNavigateNext?.(focusedDate, 1, 'months');
511
643
  }
512
644
  }, [getFocusedDate, onNavigateNext]);
513
645
 
@@ -536,27 +668,38 @@ function MonthGrid({
536
668
  onPageDown: handlePageDown
537
669
  });
538
670
 
539
- // Handle pending focus after month navigation
671
+ // Handle pending focus after month navigation. Every mounted month grid
672
+ // sees the same pending date; only the grid showing that date's month acts,
673
+ // so a neighbouring grid's fallback never overrides the right target. The
674
+ // target is the real day in this month, never an outside-day copy; when it
675
+ // is disabled, the month's first enabled day takes focus instead.
540
676
  useEffect(() => {
541
677
  if (!pendingFocus || !gridRef.current) {
542
678
  return;
543
679
  }
544
- const buttons = gridRef.current.querySelectorAll('button:not([disabled])');
545
680
  const targetPd = plainDateFromISO(pendingFocus);
681
+ if (targetPd.year !== year || targetPd.month !== month.month) {
682
+ return;
683
+ }
546
684
  const targetIso = plainDateToISO(targetPd);
547
- let targetButton = null;
548
- for (const button of buttons) {
549
- if (button.getAttribute('data-date') === targetIso) {
550
- targetButton = button;
551
- break;
685
+ const buttons = Array.from(gridRef.current.querySelectorAll('button[data-date]:not([disabled]):not([aria-disabled="true"])'));
686
+
687
+ // The real target day, else the month's first enabled day, else — in a
688
+ // month with no enabled day — the first day button that can still take
689
+ // focus (a borrowed adjacent-month day), so focus stays in the grid
690
+ // rather than dropping to the page.
691
+ const targetButton = buttons.find(button => button.getAttribute('data-date') === targetIso) ?? buttons[0] ?? gridRef.current.querySelector('button[data-date]:not([disabled])') ?? null;
692
+ if (targetButton != null) {
693
+ // The target becomes this grid's one Tab stop before it takes focus,
694
+ // the way the hook's own arrow moves do (FR2): a programmatic focus
695
+ // alone would leave the stop on the day the grid mounted with.
696
+ for (const button of gridRef.current.querySelectorAll('button[data-date]')) {
697
+ button.tabIndex = button === targetButton ? 0 : -1;
552
698
  }
699
+ targetButton.focus();
553
700
  }
554
- if (!targetButton && buttons.length > 0) {
555
- targetButton = buttons[0];
556
- }
557
- targetButton?.focus();
558
701
  onPendingFocusHandled?.();
559
- }, [pendingFocus, gridRef, onPendingFocusHandled]);
702
+ }, [pendingFocus, gridRef, month.month, onPendingFocusHandled, year]);
560
703
 
561
704
  // Parse selection
562
705
  let selectedDate = null;
@@ -613,8 +756,19 @@ function MonthGrid({
613
756
  children: [/*#__PURE__*/_jsxs("div", {
614
757
  role: "row",
615
758
  ...stylex.props(monthGridStyles.weekRow),
616
- children: [hasWeekNumbers && /*#__PURE__*/_jsx("div", {
617
- ...stylex.props(monthGridStyles.dayName, monthGridStyles.weekNumberHeader)
759
+ children: [hasWeekNumbers &&
760
+ /*#__PURE__*/
761
+ // The corner above the week-number column is a header cell like
762
+ // the weekday names beside it (APG grid: every cell in a row is a
763
+ // gridcell, columnheader or rowheader). Its name is the locale's
764
+ // own word for the week field, as visually hidden text so the
765
+ // column stays blank and the header is not empty to axe.
766
+ _jsx("div", {
767
+ role: "columnheader",
768
+ ...stylex.props(monthGridStyles.dayName, monthGridStyles.weekNumberHeader),
769
+ children: /*#__PURE__*/_jsx(VisuallyHidden, {
770
+ children: plainDateWeekFieldName(locale)
771
+ })
618
772
  }), dayNameHeaders.map(({
619
773
  dayOfWeek,
620
774
  name
@@ -1 +1 @@
1
- {"version":3,"file":"MonthScroller.d.ts","sourceRoot":"","sources":["../../src/DateInput/MonthScroller.tsx"],"names":[],"mappings":"AAoDA,OAAO,EACL,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,KAAK,SAAS,EAQf,MAAM,UAAU,CAAC;AA6MlB,MAAM,WAAW,mBAAmB;IAClC,0DAA0D;IAC1D,aAAa,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,cAAc,KAAK,IAAI,CAAC;CACxE;AAED,MAAM,WAAW,kBAAkB;IACjC,yEAAyE;IACzE,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAAC;IACxD,+CAA+C;IAC/C,aAAa,EAAE,MAAM,CAAC;IACtB,8CAA8C;IAC9C,aAAa,EAAE,MAAM,CAAC;IACtB,iCAAiC;IACjC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,+DAA+D;IAC/D,oBAAoB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD,+BAA+B;IAC/B,YAAY,EAAE,SAAS,GAAG,IAAI,CAAC;IAC/B,4CAA4C;IAC5C,KAAK,EAAE,SAAS,CAAC;IACjB,2DAA2D;IAC3D,cAAc,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,OAAO,CAAC;IAC7C,gCAAgC;IAChC,YAAY,EAAE,SAAS,CAAC;IACxB,kCAAkC;IAClC,QAAQ,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;CAC1C;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,EAC5B,SAAS,EACT,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,oBAAoB,EACpB,YAAY,EACZ,KAAK,EACL,cAAc,EACd,YAAY,EACZ,QAAQ,GACT,EAAE,kBAAkB,+BAkapB;yBA7ae,aAAa"}
1
+ {"version":3,"file":"MonthScroller.d.ts","sourceRoot":"","sources":["../../src/DateInput/MonthScroller.tsx"],"names":[],"mappings":"AAoDA,OAAO,EACL,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,KAAK,SAAS,EAQf,MAAM,UAAU,CAAC;AA6MlB,MAAM,WAAW,mBAAmB;IAClC,0DAA0D;IAC1D,aAAa,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,cAAc,KAAK,IAAI,CAAC;CACxE;AAED,MAAM,WAAW,kBAAkB;IACjC,yEAAyE;IACzE,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAAC;IACxD,+CAA+C;IAC/C,aAAa,EAAE,MAAM,CAAC;IACtB,8CAA8C;IAC9C,aAAa,EAAE,MAAM,CAAC;IACtB,iCAAiC;IACjC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,+DAA+D;IAC/D,oBAAoB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD,+BAA+B;IAC/B,YAAY,EAAE,SAAS,GAAG,IAAI,CAAC;IAC/B,4CAA4C;IAC5C,KAAK,EAAE,SAAS,CAAC;IACjB,2DAA2D;IAC3D,cAAc,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,OAAO,CAAC;IAC7C,gCAAgC;IAChC,YAAY,EAAE,SAAS,CAAC;IACxB,kCAAkC;IAClC,QAAQ,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;CAC1C;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,EAC5B,SAAS,EACT,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,oBAAoB,EACpB,YAAY,EACZ,KAAK,EACL,cAAc,EACd,YAAY,EACZ,QAAQ,GACT,EAAE,kBAAkB,+BAyapB;yBApbe,aAAa"}
@@ -439,12 +439,16 @@ export function MonthScroller({
439
439
  // Column within the displayed week, which is what Home/End move to —
440
440
  // not the day of the month.
441
441
  const column = (plainDateDayOfWeek(date) - weekStartsOn + 7) % 7;
442
+ // The horizontal arrows follow the reading direction (APG grid): under
443
+ // RTL the next day sits to the left.
444
+ const forward = isRTL ? 'ArrowLeft' : 'ArrowRight';
445
+ const backward = isRTL ? 'ArrowRight' : 'ArrowLeft';
442
446
  switch (event.key) {
443
- case 'ArrowLeft':
447
+ case backward:
444
448
  event.preventDefault();
445
449
  moveFocus(date, -1);
446
450
  break;
447
- case 'ArrowRight':
451
+ case forward:
448
452
  event.preventDefault();
449
453
  moveFocus(date, 1);
450
454
  break;
@@ -467,7 +471,7 @@ export function MonthScroller({
467
471
  default:
468
472
  break;
469
473
  }
470
- }, [moveFocus, weekStartsOn]);
474
+ }, [isRTL, moveFocus, weekStartsOn]);
471
475
 
472
476
  // The focused day may have been in an unmounted pane a frame ago; focus it
473
477
  // once it exists, and only in response to a key press so a scroll never
@@ -477,7 +481,10 @@ export function MonthScroller({
477
481
  if (!shouldRestoreFocusRef.current || focusedISO == null) {
478
482
  return;
479
483
  }
480
- const target = scrollerRef.current?.querySelector(`[data-date="${focusedISO}"]`);
484
+ // A date can be mounted twice: as a real day in its own pane and as a
485
+ // dimmed, disabled spill copy at the edge of a neighbouring pane. Only
486
+ // the real day may take focus, or Enter and Space would select nothing.
487
+ const target = scrollerRef.current?.querySelector(`[data-date="${focusedISO}"]:not([data-outside])`);
481
488
  if (target != null) {
482
489
  shouldRestoreFocusRef.current = false;
483
490
  // preventScroll: the scroller is already snapping to this month; a
@@ -605,6 +612,7 @@ function MonthPane({
605
612
  children: /*#__PURE__*/_jsx("button", {
606
613
  type: "button",
607
614
  "data-date": day.iso,
615
+ "data-outside": day.isOutside ? '' : undefined,
608
616
  tabIndex: day.iso === tabbableISO ? 0 : -1,
609
617
  "aria-label": plainDateFormat(day.date, DATE_FORMAT_WITH_WEEKDAY, locale),
610
618
  "aria-disabled": isDisabled || undefined,
@@ -1 +1 @@
1
- {"version":3,"file":"PowerSearchEditPopover.d.ts","sourceRoot":"","sources":["../../src/PowerSearch/PowerSearchEditPopover.tsx"],"names":[],"mappings":"AAIA;;;;;;;;GAQG;AAEH,OAAO,KAA0D,MAAM,OAAO,CAAC;AAe/E,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EACV,iBAAiB,EACjB,aAAa,EAId,MAAM,SAAS,CAAC;AA+DjB,MAAM,WAAW,2BAA2B;IAC1C,MAAM,EAAE,cAAc,CAAC;IACvB,uCAAuC;IACvC,MAAM,EAAE,aAAa,CAAC;IACtB,qDAAqD;IACrD,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC;IACxB,6EAA6E;IAC7E,MAAM,EAAE,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,KAAK,IAAI,CAAC;IACnD,wDAAwD;IACxD,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,kDAAkD;IAClD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,6DAA6D;IAC7D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uCAAuC;IACvC,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AA0hBD,wBAAgB,sBAAsB,CAAC,EACrC,MAAM,EACN,MAAM,EAAE,aAAa,EACrB,IAAI,EACJ,MAAM,EACN,QAAQ,EACR,eAAe,EAAE,wBAAwB,EACzC,YAAY,EACZ,UAAkB,GACnB,EAAE,2BAA2B,qBAgS7B"}
1
+ {"version":3,"file":"PowerSearchEditPopover.d.ts","sourceRoot":"","sources":["../../src/PowerSearch/PowerSearchEditPopover.tsx"],"names":[],"mappings":"AAIA;;;;;;;;GAQG;AAEH,OAAO,KAA0D,MAAM,OAAO,CAAC;AAe/E,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EACV,iBAAiB,EACjB,aAAa,EAId,MAAM,SAAS,CAAC;AA+DjB,MAAM,WAAW,2BAA2B;IAC1C,MAAM,EAAE,cAAc,CAAC;IACvB,uCAAuC;IACvC,MAAM,EAAE,aAAa,CAAC;IACtB,qDAAqD;IACrD,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC;IACxB,6EAA6E;IAC7E,MAAM,EAAE,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,KAAK,IAAI,CAAC;IACnD,wDAAwD;IACxD,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,kDAAkD;IAClD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,6DAA6D;IAC7D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uCAAuC;IACvC,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AA0hBD,wBAAgB,sBAAsB,CAAC,EACrC,MAAM,EACN,MAAM,EAAE,aAAa,EACrB,IAAI,EACJ,MAAM,EACN,QAAQ,EACR,eAAe,EAAE,wBAAwB,EACzC,YAAY,EACZ,UAAkB,GACnB,EAAE,2BAA2B,qBAsS7B"}
@@ -570,6 +570,12 @@ export function PowerSearchEditPopover({
570
570
  if (isImeKeyEvent(e.nativeEvent)) {
571
571
  return;
572
572
  }
573
+ // A focused button owns its Enter: Cancel cancels, Delete deletes, and
574
+ // the date picker's calendar button opens the calendar. Enter in a
575
+ // text or number value field still saves.
576
+ if (e.key === 'Enter' && e.target instanceof HTMLButtonElement) {
577
+ return;
578
+ }
573
579
  if (e.key === 'Enter' && !isSaveDisabled && !e.defaultPrevented) {
574
580
  e.preventDefault();
575
581
  handleSave();
@@ -1,6 +1,6 @@
1
1
  import type { ReactNode } from 'react';
2
2
  import type { BaseProps } from '../BaseProps';
3
- import type { ResizableProps } from './useResizable';
3
+ import { type ResizableProps } from './useResizable';
4
4
  type PillPlacement = 'start' | 'end' | 'center' | 'auto';
5
5
  export interface ResizeHandleProps extends Omit<BaseProps<HTMLDivElement>, 'style'> {
6
6
  ref?: React.Ref<HTMLDivElement>;
@@ -1 +1 @@
1
- {"version":3,"file":"ResizeHandle.d.ts","sourceRoot":"","sources":["../../src/Resizable/ResizeHandle.tsx"],"names":[],"mappings":"AAmCA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,OAAO,CAAC;AAErC,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAW5C,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,gBAAgB,CAAC;AAQnD,KAAK,aAAa,GAAG,OAAO,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;AAyOzD,MAAM,WAAW,iBAAkB,SAAQ,IAAI,CAC7C,SAAS,CAAC,cAAc,CAAC,EACzB,OAAO,CACR;IACC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAEhC;;;OAGG;IACH,SAAS,CAAC,EAAE,YAAY,GAAG,UAAU,CAAC;IAEtC;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAEhC;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;;OAIG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAE1B;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC;IAE9B;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,6CAA6C;IAC7C,SAAS,CAAC,EAAE,cAAc,CAAC;IAE3B,yDAAyD;IACzD,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,YAAY,CAAC,EAC3B,SAAwB,EACxB,QAAQ,EAAE,YAAuB,EACjC,UAAkB,EAClB,UAAkB,EAClB,UAAkB,EAClB,eAAsB,EACtB,aAAsB,EACtB,KAAK,EAAE,cAAc,EACrB,SAAS,EACT,QAAQ,EACR,MAAM,EACN,SAAS,EACT,GAAG,EACH,GAAG,KAAK,EACT,EAAE,iBAAiB,+BAuVnB;yBAtWe,YAAY"}
1
+ {"version":3,"file":"ResizeHandle.d.ts","sourceRoot":"","sources":["../../src/Resizable/ResizeHandle.tsx"],"names":[],"mappings":"AAmCA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,OAAO,CAAC;AAErC,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAW5C,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,gBAAgB,CAAC;AAQxB,KAAK,aAAa,GAAG,OAAO,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;AAyOzD,MAAM,WAAW,iBAAkB,SAAQ,IAAI,CAC7C,SAAS,CAAC,cAAc,CAAC,EACzB,OAAO,CACR;IACC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAEhC;;;OAGG;IACH,SAAS,CAAC,EAAE,YAAY,GAAG,UAAU,CAAC;IAEtC;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAEhC;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;;OAIG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAE1B;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC;IAE9B;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,6CAA6C;IAC7C,SAAS,CAAC,EAAE,cAAc,CAAC;IAE3B,yDAAyD;IACzD,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AA2BD;;;;;;;;;;;;;;GAcG;AAEH,wBAAgB,YAAY,CAAC,EAC3B,SAAwB,EACxB,QAAQ,EAAE,YAAuB,EACjC,UAAkB,EAClB,UAAkB,EAClB,UAAkB,EAClB,eAAsB,EACtB,aAAsB,EACtB,KAAK,EAAE,cAAc,EACrB,SAAS,EACT,QAAQ,EACR,MAAM,EACN,SAAS,EACT,GAAG,EACH,GAAG,KAAK,EACT,EAAE,iBAAiB,+BAyVnB;yBAxWe,YAAY"}
@@ -38,6 +38,7 @@ import "../theme/tokens.stylex.js";
38
38
  import { colorVars, durationVars, easeVars, radiusVars, spacingVars } from "../theme/tokens.stylex.js";
39
39
  import { focusOutlineProps } from "../utils/focusOutline.stylex.js";
40
40
  import { mergeProps, rtlStyles } from "../utils/index.js";
41
+ import { getCollapseControls, getEffectiveMaxPx } from "./useResizable.js";
41
42
  import { themeProps } from "../utils/themeProps.js";
42
43
  import { useTranslator } from "../i18n/index.js";
43
44
  import { useMergedRefs } from "../hooks/useMergedRefs.js";
@@ -263,6 +264,29 @@ const dynamicStyles = {
263
264
  "--x-transform": `translate(-50%, calc(${dir} * (100% + ${spacingVars['--spacing-1']})))` != null ? `translate(-50%, calc(${dir} * (100% + ${spacingVars['--spacing-1']})))` : undefined
264
265
  }]
265
266
  };
267
+ /**
268
+ * Collapse an open pane, or restore a collapsed one to the size it had before
269
+ * it collapsed (APG window splitter: Enter "restores the splitter to its
270
+ * previous position"). The hook's own collapse/expand do exactly that; a
271
+ * hand-built `ResizableProps` (no registered controls) falls back to a resize
272
+ * gesture to the minimum, which also cannot reopen a pane whose minimum sits below its
273
+ * collapse threshold.
274
+ */
275
+ function toggleCollapse(resizable) {
276
+ const controls = getCollapseControls(resizable);
277
+ if (controls) {
278
+ if (resizable._isCollapsed) {
279
+ controls.expand();
280
+ } else {
281
+ controls.collapse();
282
+ }
283
+ return;
284
+ }
285
+ resizable._onResizeStart();
286
+ resizable._onResizeMove(resizable._isCollapsed ? resizable._minSizePx : -resizable._size);
287
+ resizable._onResizeEnd();
288
+ }
289
+
266
290
  /**
267
291
  * Draggable resize handle placed between resizable panels. Renders as a thin
268
292
  * divider line with a wider invisible hit area and optional pill grip indicator.
@@ -278,6 +302,7 @@ const dynamicStyles = {
278
302
  * hasDivider />
279
303
  * ```
280
304
  */
305
+
281
306
  export function ResizeHandle({
282
307
  direction = 'horizontal',
283
308
  position: positionMode = 'inline',
@@ -425,6 +450,9 @@ export function ResizeHandle({
425
450
  case 'End':
426
451
  {
427
452
  e.preventDefault();
453
+ // Only a configured maximum is the region's own limit; the reported
454
+ // stand-in for an unbounded region is where the pane *could* reach,
455
+ // not where End may safely move it (a recorded gap).
428
456
  if (resizable._maxSizePx !== Infinity) {
429
457
  resizable._onResizeStart();
430
458
  resizable._onResizeMove(resizable._maxSizePx - resizable._size);
@@ -436,9 +464,7 @@ export function ResizeHandle({
436
464
  {
437
465
  e.preventDefault();
438
466
  if (resizable._collapsible) {
439
- resizable._onResizeStart();
440
- resizable._onResizeMove(resizable._isCollapsed ? resizable._minSizePx : -resizable._size);
441
- resizable._onResizeEnd();
467
+ toggleCollapse(resizable);
442
468
  }
443
469
  break;
444
470
  }
@@ -450,9 +476,7 @@ export function ResizeHandle({
450
476
  if (isDisabled || !resizable || !resizable._collapsible) {
451
477
  return;
452
478
  }
453
- resizable._onResizeStart();
454
- resizable._onResizeMove(resizable._isCollapsed ? resizable._minSizePx : -resizable._size);
455
- resizable._onResizeEnd();
479
+ toggleCollapse(resizable);
456
480
  }, [isDisabled, resizable]);
457
481
 
458
482
  // --- Cleanup on unmount ---
@@ -482,7 +506,11 @@ export function ResizeHandle({
482
506
  const isCollapsed = resizable?._isCollapsed ?? false;
483
507
  const ariaValueNow = resizable ? isCollapsed ? Math.max(resizable._size, resizable._minSizePx) : resizable._size : undefined;
484
508
  const ariaValueMin = resizable ? resizable._minSizePx : undefined;
485
- const ariaValueMax = resizable && resizable._maxSizePx !== Infinity ? resizable._maxSizePx : undefined;
509
+ // A separator with a value needs a maximum (WAI-ARIA, APG window splitter).
510
+ // Unbounded regions report the basis they can fill; a hand-built props
511
+ // object without it reports only a configured maximum.
512
+ const effectiveMax = resizable ? getEffectiveMaxPx(resizable) ?? (resizable._maxSizePx !== Infinity ? resizable._maxSizePx : undefined) : undefined;
513
+ const ariaValueMax = effectiveMax != null && Number.isFinite(effectiveMax) ? effectiveMax : undefined;
486
514
  const ariaValueText = resizable && isCollapsed ? t('@astryx.resizable.collapsed') : undefined;
487
515
  return /*#__PURE__*/_jsxs("div", {
488
516
  ref: useMergedRefs(ref, handleRef),
@@ -163,6 +163,29 @@ export interface ResizableProps {
163
163
  _direction?: ResizableDirection;
164
164
  _isResizableProps: true;
165
165
  }
166
+ /**
167
+ * Collapse/expand for a region, keyed by the `ResizableProps` object the hook
168
+ * returns. A handle's Enter and double-click use them so the pane restores
169
+ * its previous position (APG window splitter) rather than emulating the
170
+ * toggle with a resize to the minimum.
171
+ *
172
+ * Kept off `ResizableProps` on purpose: that type is exported, so a new field
173
+ * on it — optional or underscored — is a public delta. This module-private
174
+ * map and {@link getCollapseControls} are the package's own channel between
175
+ * the hook and the handle; neither is re-exported.
176
+ */
177
+ type CollapseControls = {
178
+ collapse: () => void;
179
+ expand: () => void;
180
+ };
181
+ /** The reported maximum for `props`, or `undefined` for a hand-built object. */
182
+ export declare function getEffectiveMaxPx(props: ResizableProps): number | undefined;
183
+ /**
184
+ * The collapse/expand pair the hook registered for `props`, or `undefined`
185
+ * for a hand-built `ResizableProps` object (which the handle then toggles
186
+ * with a resize gesture). Internal to the package.
187
+ */
188
+ export declare function getCollapseControls(props: ResizableProps): CollapseControls | undefined;
166
189
  /** Removed keys must fail even when a variable bypasses excess-property checks. */
167
190
  type RejectRemovedPixelBounds<Config> = Config extends unknown ? Config & {
168
191
  minSizePx?: never;
@@ -1 +1 @@
1
- {"version":3,"file":"useResizable.d.ts","sourceRoot":"","sources":["../../src/Resizable/useResizable.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useResizable.d.ts","sourceRoot":"","sources":["../../src/Resizable/useResizable.ts"],"names":[],"mappings":"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"}