gridsmith-ui 0.30.2 → 0.31.0

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 (75) hide show
  1. package/CHANGELOG.md +53 -1
  2. package/dist/charts.d.ts +114 -1
  3. package/dist/charts.js +1113 -206
  4. package/dist/charts.js.map +1 -1
  5. package/dist/chat.js +20 -12
  6. package/dist/chat.js.map +1 -1
  7. package/dist/{chunk-MIUSSYVE.js → chunk-2Q4HOGEI.js} +16 -51
  8. package/dist/chunk-2Q4HOGEI.js.map +1 -0
  9. package/dist/{chunk-SIWERRP2.js → chunk-3J67NISC.js} +13 -14
  10. package/dist/chunk-3J67NISC.js.map +1 -0
  11. package/dist/{chunk-L6HWACK7.js → chunk-53F7GVBQ.js} +6 -3
  12. package/dist/chunk-53F7GVBQ.js.map +1 -0
  13. package/dist/{chunk-USHW523V.js → chunk-BKYWKMUN.js} +73 -699
  14. package/dist/chunk-BKYWKMUN.js.map +1 -0
  15. package/dist/{chunk-K6IDOCGE.js → chunk-EKWXPSTI.js} +10 -10
  16. package/dist/{chunk-K6IDOCGE.js.map → chunk-EKWXPSTI.js.map} +1 -1
  17. package/dist/{chunk-ACLKDE5W.js → chunk-GP2COVJC.js} +23 -11
  18. package/dist/chunk-GP2COVJC.js.map +1 -0
  19. package/dist/{chunk-BZRZENBS.js → chunk-GT6M74IZ.js} +6 -6
  20. package/dist/{chunk-BZRZENBS.js.map → chunk-GT6M74IZ.js.map} +1 -1
  21. package/dist/{chunk-ZCDA74RQ.js → chunk-HUNEJIKV.js} +3 -3
  22. package/dist/{chunk-ZCDA74RQ.js.map → chunk-HUNEJIKV.js.map} +1 -1
  23. package/dist/{chunk-ZASGFL5D.js → chunk-LE72KHIE.js} +3 -4
  24. package/dist/chunk-LE72KHIE.js.map +1 -0
  25. package/dist/{chunk-SQHKC4HA.js → chunk-OFACAHRV.js} +8 -7
  26. package/dist/chunk-OFACAHRV.js.map +1 -0
  27. package/dist/{chunk-4HJDOZ2M.js → chunk-OWQ7M6R7.js} +75 -15
  28. package/dist/chunk-OWQ7M6R7.js.map +1 -0
  29. package/dist/{chunk-TYNDXMBV.js → chunk-QOMP4D6V.js} +3 -3
  30. package/dist/{chunk-TYNDXMBV.js.map → chunk-QOMP4D6V.js.map} +1 -1
  31. package/dist/chunk-U7WBI4AV.js +285 -0
  32. package/dist/chunk-U7WBI4AV.js.map +1 -0
  33. package/dist/chunk-V2ARGHJN.js +755 -0
  34. package/dist/chunk-V2ARGHJN.js.map +1 -0
  35. package/dist/chunk-V73LHNRL.js +51 -0
  36. package/dist/chunk-V73LHNRL.js.map +1 -0
  37. package/dist/{chunk-WWN6CQKI.js → chunk-XSQD3UGY.js} +47 -7
  38. package/dist/chunk-XSQD3UGY.js.map +1 -0
  39. package/dist/{chunk-HVGHGBKV.js → chunk-YFHTREH5.js} +6 -7
  40. package/dist/chunk-YFHTREH5.js.map +1 -0
  41. package/dist/data-grid.js +10 -11
  42. package/dist/data-grid.js.map +1 -1
  43. package/dist/editor-mentions.js +2 -3
  44. package/dist/editor-mentions.js.map +1 -1
  45. package/dist/editor.js +3 -5
  46. package/dist/editor.js.map +1 -1
  47. package/dist/icons.js +1 -1
  48. package/dist/index.d.ts +91 -16
  49. package/dist/index.js +1055 -640
  50. package/dist/index.js.map +1 -1
  51. package/dist/kanban.js +9 -11
  52. package/dist/kanban.js.map +1 -1
  53. package/dist/resizable.js +9 -11
  54. package/dist/resizable.js.map +1 -1
  55. package/dist/virtualized.js +10 -11
  56. package/dist/virtualized.js.map +1 -1
  57. package/package.json +1 -1
  58. package/dist/chunk-4HJDOZ2M.js.map +0 -1
  59. package/dist/chunk-6C2IUMY7.js +0 -46
  60. package/dist/chunk-6C2IUMY7.js.map +0 -1
  61. package/dist/chunk-7Q46JXSK.js +0 -51
  62. package/dist/chunk-7Q46JXSK.js.map +0 -1
  63. package/dist/chunk-ACLKDE5W.js.map +0 -1
  64. package/dist/chunk-HVGHGBKV.js.map +0 -1
  65. package/dist/chunk-L6HWACK7.js.map +0 -1
  66. package/dist/chunk-MIUSSYVE.js.map +0 -1
  67. package/dist/chunk-NKUCS67T.js +0 -13
  68. package/dist/chunk-NKUCS67T.js.map +0 -1
  69. package/dist/chunk-SIWERRP2.js.map +0 -1
  70. package/dist/chunk-SQHKC4HA.js.map +0 -1
  71. package/dist/chunk-USHW523V.js.map +0 -1
  72. package/dist/chunk-WWN6CQKI.js.map +0 -1
  73. package/dist/chunk-YL5KD5RC.js +0 -49
  74. package/dist/chunk-YL5KD5RC.js.map +0 -1
  75. package/dist/chunk-ZASGFL5D.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,46 +1,46 @@
1
1
  "use client";
2
- import { useAppShell, REACT_VERSION_PROBLEM, Breadcrumbs, useCrumbFollow, crumbGoes, useCornerInset, NAVBAR_INSET_PRIORITY, useOverlayMark, useDirtyGuard, useScrollbarInset, DrawerFooterCtx, OverlayMarkScope, OverlayCloseCtx, insetLessScrollbar, DiscardConfirm, OVERLAY_ROOT_ATTR, OverlayMarkCtx, useCompact, useElementWidth, DrawerPanel, useCloseSettle, besideRoom, PANEL_INSET_PRIORITY, Drawer, useRememberedWidth, pxOf, ResizeEdge, LAUNCHER_INSET_PRIORITY, defaultMaxWidth } from './chunk-K6IDOCGE.js';
3
- export { AppShell, Breadcrumbs, Drawer, SkipLink, useAppShell, useUnsavedChanges } from './chunk-K6IDOCGE.js';
4
- import { Skeleton, TITLE_TEXT } from './chunk-6C2IUMY7.js';
5
- export { Skeleton } from './chunk-6C2IUMY7.js';
2
+ import { useAppShell, REACT_VERSION_PROBLEM, Breadcrumbs, useCrumbFollow, crumbGoes, useCornerInset, NAVBAR_INSET_PRIORITY, useOverlayMark, useDirtyGuard, useScrollbarInset, DrawerFooterCtx, OverlayMarkScope, OverlayCloseCtx, insetLessScrollbar, DiscardConfirm, OVERLAY_ROOT_ATTR, OverlayMarkCtx, useCompact, useElementWidth, DrawerPanel, useCloseSettle, besideRoom, PANEL_INSET_PRIORITY, Drawer, useRememberedWidth, pxOf, ResizeEdge, LAUNCHER_INSET_PRIORITY, defaultMaxWidth } from './chunk-EKWXPSTI.js';
3
+ export { AppShell, Breadcrumbs, Drawer, SkipLink, useAppShell, useUnsavedChanges } from './chunk-EKWXPSTI.js';
4
+ import { Skeleton, TITLE_TEXT, SegmentedControl } from './chunk-U7WBI4AV.js';
5
+ export { SegmentedControl, Skeleton } from './chunk-U7WBI4AV.js';
6
6
  import { useScrollEdges, EDGE_FADE } from './chunk-3BRQRKDO.js';
7
- import { Avatar, foldForSearch, useActiveOptionInView, shownMentions, matchMentions, mentionOptionId, MentionList, initialsOf } from './chunk-ZASGFL5D.js';
8
- export { Avatar } from './chunk-ZASGFL5D.js';
9
- import { Link } from './chunk-SQHKC4HA.js';
10
- export { CodeBlock, Divider, Link } from './chunk-SQHKC4HA.js';
11
- import { DropdownMenu } from './chunk-HVGHGBKV.js';
12
- export { DropdownMenu } from './chunk-HVGHGBKV.js';
7
+ import { Avatar, foldForSearch, useActiveOptionInView, shownMentions, matchMentions, mentionOptionId, MentionList, initialsOf } from './chunk-LE72KHIE.js';
8
+ export { Avatar } from './chunk-LE72KHIE.js';
9
+ import { Link } from './chunk-OFACAHRV.js';
10
+ export { CodeBlock, Divider, Link } from './chunk-OFACAHRV.js';
11
+ import { DropdownMenu } from './chunk-YFHTREH5.js';
12
+ export { DropdownMenu } from './chunk-YFHTREH5.js';
13
13
  import { useFillHeight, FillContext } from './chunk-DBHVJ7Z3.js';
14
- import { Badge } from './chunk-BZRZENBS.js';
15
- export { Badge } from './chunk-BZRZENBS.js';
16
- import { useKeyboardScroll } from './chunk-ACLKDE5W.js';
17
- import { localeCache, startOfDay, addDays, useWeekStart, useDateWords, localeDayFirst, dayKey, moveDay, addMonths, DateSegmentFields, getMonthGrid, isSameMonth, isSameDay, DatePicker, timeSegments, pad2, inferColumnKinds, useCardsSwitch, CARDS_BELOW, kindOfRenderedText, Checkbox, CardValue, SortIndicator, startOfWeek, endOfWeek, addMonthsClamped, isExcludedDay, CutCell, CutEmail, stepValue, separatorMovesOn, TIME_SEPARATORS, typeDigit, getEventsForDay, layoutWeek, isWeekend, getWeekDays, isBarEvent, layoutPieces, dayPieces, groupEventsByDay, timeToFraction } from './chunk-SIWERRP2.js';
18
- export { Checkbox, CheckboxGroup, CheckboxGroupItem, DatePicker, HoverCard, TextOverflow } from './chunk-SIWERRP2.js';
19
- import { useWholeListCheck, pagerPart, Pagination, Input, CHECK_FILL, Dropdown, FilterRowContext, SWITCH_KNOB_ON, SWITCH_KNOB_OFF, SWITCH_KNOB_MOTION, SWITCH_TRACK_ON, SWITCH_TRACK_OFF, useFieldControl, useField, OutsideField, NumberInput, InlineFieldError, Label, AddAnotherRowCtx, Field } from './chunk-USHW523V.js';
20
- export { Dropdown, Field, InlineFieldError, Input, Label, NumberInput, Pagination, Popover, Textarea, Toggle, usePagination } from './chunk-USHW523V.js';
21
- import { useShellPreferences, PREFERENCE_HOUSE_DEFAULTS, formatRelativeTime, thresholdOf, toMillis, formatFullTime, datedMillis, preferenceActionKey, DAY, HOUR, MINUTE } from './chunk-TYNDXMBV.js';
22
- export { PreferencesContext, formatFullTime, formatRelativeTime, preferenceActionKey, usePreferences } from './chunk-TYNDXMBV.js';
14
+ import { Badge } from './chunk-GT6M74IZ.js';
15
+ export { Badge } from './chunk-GT6M74IZ.js';
16
+ import { useKeyboardScroll } from './chunk-GP2COVJC.js';
17
+ import { localeCache, startOfDay, addDays, useWeekStart, useDateWords, localeDayFirst, dayKey, moveDay, addMonths, DateSegmentFields, getMonthGrid, isSameMonth, isSameDay, DatePicker, timeSegments, pad2, inferColumnKinds, useCardsSwitch, CARDS_BELOW, kindOfRenderedText, Checkbox, CardValue, SortIndicator, startOfWeek, endOfWeek, addMonthsClamped, isExcludedDay, CutCell, CutEmail, stepValue, separatorMovesOn, TIME_SEPARATORS, typeDigit, getEventsForDay, layoutWeek, isWeekend, getWeekDays, isBarEvent, layoutPieces, dayPieces, groupEventsByDay, timeToFraction } from './chunk-3J67NISC.js';
18
+ export { Checkbox, CheckboxGroup, CheckboxGroupItem, DatePicker, HoverCard, TextOverflow } from './chunk-3J67NISC.js';
19
+ import { useWholeListCheck, pagerPart, Pagination, NumberInput } from './chunk-V2ARGHJN.js';
20
+ export { NumberInput, Pagination, Popover, usePagination } from './chunk-V2ARGHJN.js';
21
+ import { useShellPreferences, PREFERENCE_HOUSE_DEFAULTS, formatRelativeTime, thresholdOf, toMillis, formatFullTime, datedMillis, preferenceActionKey, DAY, HOUR, MINUTE } from './chunk-QOMP4D6V.js';
22
+ export { PreferencesContext, formatFullTime, formatRelativeTime, preferenceActionKey, usePreferences } from './chunk-QOMP4D6V.js';
23
23
  import { useFormatLocale, useHydrated, useToday, LocaleContext } from './chunk-LJYSVZMI.js';
24
24
  export { useFormatLocale, useHydrated } from './chunk-LJYSVZMI.js';
25
+ import { Input, CHECK_FILL, Dropdown, FilterRowContext, SWITCH_KNOB_ON, SWITCH_KNOB_OFF, SWITCH_KNOB_MOTION, SWITCH_TRACK_ON, SWITCH_TRACK_OFF, useFieldControl, useField, OutsideField, InlineFieldError, Label, AddAnotherRowCtx, Field } from './chunk-BKYWKMUN.js';
26
+ export { Dropdown, Field, InlineFieldError, Input, Label, Textarea, Toggle } from './chunk-BKYWKMUN.js';
25
27
  import { formatLocale, localeChoice } from './chunk-7QLXYRPD.js';
26
28
  import { useInputModality, inputModality } from './chunk-7RKC5C7G.js';
27
29
  export { INPUT_MODALITY_ATTR, inputModality, installInputModality, useInputModality } from './chunk-7RKC5C7G.js';
28
30
  import { FormChangeCtx, useReportedChange, useOptionalFormContext, FormCtx } from './chunk-72SDAX54.js';
29
31
  export { useFormContext } from './chunk-72SDAX54.js';
30
- import { Button, CardSurfaceCtx, useScrollsInside, SplitPaneCtx, RowsCtx, PageActionsCtx, ControlSizeContext, triggerType, SurfaceBoundary, Spinner, useFramesItself, TableCardCtx } from './chunk-MIUSSYVE.js';
31
- export { Button, ButtonSizeContext, ControlSizeContext, Spinner } from './chunk-MIUSSYVE.js';
32
- import { elementType } from './chunk-NKUCS67T.js';
32
+ import { CardSurfaceCtx, useScrollsInside, SplitPaneCtx, RowsCtx, PageActionsCtx, SurfaceBoundary, useFramesItself, TableCardCtx } from './chunk-V73LHNRL.js';
33
+ import { Button, elementType, ControlSizeContext, triggerType, Spinner } from './chunk-2Q4HOGEI.js';
34
+ export { Button, ButtonSizeContext, ControlSizeContext, Spinner } from './chunk-2Q4HOGEI.js';
33
35
  import { useBreakpoint, readBreakpointPx } from './chunk-ZHUVLBCC.js';
34
36
  export { BreakpointWidthProvider, useBreakpoint } from './chunk-ZHUVLBCC.js';
35
- import { useTextOverflow } from './chunk-ZCDA74RQ.js';
36
- export { useTextOverflow } from './chunk-ZCDA74RQ.js';
37
- import { AutoTooltip, useRevealFocus } from './chunk-WWN6CQKI.js';
38
- export { Tooltip, useRevealFocus } from './chunk-WWN6CQKI.js';
39
- import { Icon, slotIcon, warnOnce } from './chunk-L6HWACK7.js';
40
- import { mergeRefs } from './chunk-7Q46JXSK.js';
41
- import { focusAfterSelfRemoval, FOCUSABLE, useFocusKeptInList, useLatest, FIELD, ZOOM_KEYS, rovingTabStop, useOverlayLayer, useFocusTrap, useFocusBeforeOpen, rememberOpener, focusInitial, OVERLAY_CLOSE_ATTR, usePopupFocus, usePosition, CONTROL_ANCESTOR, useGridKeyboard, focusAfterRemoval, useNonModalDialog, useReturnFocus } from './chunk-4HJDOZ2M.js';
42
- export { useGridKeyboard } from './chunk-4HJDOZ2M.js';
43
- import { cn, FOCUS_RING, GLASS_SURFACE, FOCUS_INSET, PEER_FOCUS_RING, GLASS_BACKDROP, FOCUS_ROOM, lockBodyScroll, unlockBodyScroll, OVERLAY_SURFACE, GLASS_OVERLAY, ERROR_HAS_FOCUS_RING, FIELD_HAS_FOCUS_RING, GROUP_FOCUS_RING, ERROR_ACTIVE_RING, ACTIVE_RING, ERROR_FOCUS_WITHIN_RING, FIELD_FOCUS_WITHIN_RING, GLASS_DROPDOWN, ERROR_FOCUS_RING, FIELD_FOCUS_RING, atLeastFocusRoom } from './chunk-YL5KD5RC.js';
37
+ import { useTextOverflow } from './chunk-HUNEJIKV.js';
38
+ export { useTextOverflow } from './chunk-HUNEJIKV.js';
39
+ import { AutoTooltip, mergeRefs, useRevealFocus } from './chunk-XSQD3UGY.js';
40
+ export { Tooltip, useRevealFocus } from './chunk-XSQD3UGY.js';
41
+ import { Icon, slotIcon, warnOnce } from './chunk-53F7GVBQ.js';
42
+ import { cn, FOCUS_RING, focusAfterSelfRemoval, FOCUSABLE, GLASS_SURFACE, useFocusKeptInList, FOCUS_INSET, useLatest, PEER_FOCUS_RING, GLASS_BACKDROP, FIELD, ZOOM_KEYS, rovingTabStop, FOCUS_ROOM, useOverlayLayer, useFocusTrap, useFocusBeforeOpen, rememberOpener, focusInitial, lockBodyScroll, unlockBodyScroll, OVERLAY_CLOSE_ATTR, OVERLAY_SURFACE, GLASS_OVERLAY, usePopupFocus, usePosition, ERROR_HAS_FOCUS_RING, FIELD_HAS_FOCUS_RING, CONTROL_ANCESTOR, GROUP_FOCUS_RING, useGridKeyboard, ERROR_ACTIVE_RING, ACTIVE_RING, ERROR_FOCUS_WITHIN_RING, FIELD_FOCUS_WITHIN_RING, focusAfterRemoval, GLASS_DROPDOWN, ERROR_FOCUS_RING, FIELD_FOCUS_RING, atLeastFocusRoom, useNonModalDialog, useReturnFocus } from './chunk-OWQ7M6R7.js';
43
+ export { useGridKeyboard } from './chunk-OWQ7M6R7.js';
44
44
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
45
45
  import { forwardRef, useRef, useId, useEffect, useState, createContext, useCallback, useLayoutEffect, useMemo, useContext, isValidElement, memo, useImperativeHandle, useSyncExternalStore, Children, Fragment as Fragment$1, cloneElement, useReducer } from 'react';
46
46
  import { createPortal, flushSync } from 'react-dom';
@@ -130,7 +130,7 @@ function Combobox({ value, onChange: onChangeProp, options, placeholder = "Searc
130
130
  const onChange = useReportedChange(onChangeProp, value);
131
131
  useInputModality();
132
132
  const contextSize = useContext(ControlSizeContext);
133
- const control = useFieldControl({ ...wiring, error: error || void 0 });
133
+ const control = useFieldControl({ ...wiring, "aria-label": ariaLabel, error: error || void 0 }, { component: "Combobox" });
134
134
  const size = sizeProp ?? contextSize ?? "md";
135
135
  const [open, setOpen] = useState(false);
136
136
  useOverlayLayer(open);
@@ -259,8 +259,8 @@ function Combobox({ value, onChange: onChangeProp, options, placeholder = "Searc
259
259
  type: "text",
260
260
  id: control.id,
261
261
  role: "combobox",
262
- "aria-labelledby": wiring["aria-labelledby"],
263
- "aria-label": control.labelId ? void 0 : ariaLabel,
262
+ "aria-labelledby": control.shown ? void 0 : wiring["aria-labelledby"],
263
+ "aria-label": control.labelId ? void 0 : control.label,
264
264
  "aria-describedby": control.describedBy,
265
265
  "aria-invalid": control.invalid || void 0,
266
266
  "aria-required": control.required || void 0,
@@ -421,6 +421,8 @@ function zoneOptions(locale) {
421
421
  return zoneLists(key, build2).options;
422
422
  }
423
423
  function TimezoneSelect({ value, onChange, placeholder = "Search time zones\u2026", ...rest }) {
424
+ const control = useFieldControl(rest, { component: "TimezoneSelect" });
425
+ const { "aria-label": _pageLabel, "aria-labelledby": pageLabelledBy, ...comboboxRest } = rest;
424
426
  const locale = useFormatLocale();
425
427
  const zones = zoneOptions(locale);
426
428
  const stored = value ? canonicalTimeZone(value) : "";
@@ -433,7 +435,7 @@ function TimezoneSelect({ value, onChange, placeholder = "Search time zones\u202
433
435
  }
434
436
  return zones.some((o) => o.value === stored) ? zones : [{ value: stored, label: labelOf(stored, /* @__PURE__ */ new Date(), locale), keywords: CITY_OF.get(stored) }, ...zones];
435
437
  }, [zones, stored, mine, locale]);
436
- return /* @__PURE__ */ jsx(Combobox, { value: stored, onChange, options, placeholder, ...rest });
438
+ return /* @__PURE__ */ jsx(Combobox, { value: stored, onChange, options, placeholder, ...comboboxRest, "aria-label": control.shown ? void 0 : control.label, "aria-labelledby": control.shown ? void 0 : pageLabelledBy });
437
439
  }
438
440
  var LONGEST_WAIT = DAY;
439
441
  var noChange = () => () => {
@@ -508,7 +510,7 @@ function RadioGroup({ value, onChange: onChangeProp, options, orientation = "ver
508
510
  const onChange = useReportedChange(onChangeProp, value);
509
511
  const groupRef = useRef(null);
510
512
  const groupId = useId();
511
- const control = useFieldControl(wiring);
513
+ const control = useFieldControl(wiring, { component: "RadioGroup", ownLabel: label ? groupId : void 0 });
512
514
  const enabledOptions = options.filter((o) => !o.disabled);
513
515
  const tabStop = rovingTabStop(options, (o) => o.value === value, (o) => !!o.disabled);
514
516
  function handleKeyDown(e) {
@@ -540,8 +542,8 @@ function RadioGroup({ value, onChange: onChangeProp, options, orientation = "ver
540
542
  ref: groupRef,
541
543
  role: "radiogroup",
542
544
  id: control.id,
543
- "aria-label": label || control.labelId ? void 0 : control.label,
544
- "aria-labelledby": label ? groupId : control.labelId,
545
+ "aria-label": control.labelId ? void 0 : control.label,
546
+ "aria-labelledby": control.labelId,
545
547
  "aria-describedby": control.describedBy,
546
548
  "aria-invalid": control.invalid || void 0,
547
549
  "aria-required": control.required || void 0,
@@ -668,7 +670,7 @@ function DateRangePicker({
668
670
  const presetEdges = useScrollEdges({ enabled: isPhone });
669
671
  useRevealFocus(presetEdges.elRef, isPhone);
670
672
  const generatedId = useId();
671
- const control = useFieldControl({ ...wiring, id, error: error || void 0 }, { requiredInDescription: true });
673
+ const control = useFieldControl({ ...wiring, id, error: error || void 0 }, { requiredInDescription: true, component: "DateRangePicker", ownLabel: label ? true : void 0 });
672
674
  useInputModality();
673
675
  const triggerId = control.id ?? generatedId;
674
676
  const locale = useFormatLocale();
@@ -1560,6 +1562,8 @@ function RecurrencePicker({ value, onChange: onChangeProp, start, label, weekSta
1560
1562
  setCustomOpen(false);
1561
1563
  onChange(presets[Number(id.slice("preset-".length))].rule);
1562
1564
  };
1565
+ const { "aria-label": pageName, "aria-labelledby": pageLabelledBy, ...choiceWiring } = wiring;
1566
+ const named = pageName || pageLabelledBy ? { role: "group", "aria-label": pageName, "aria-labelledby": pageLabelledBy } : {};
1563
1567
  const summary = rule && !noDays(rule) ? describeRecurrence(rule, start, locale) : unreadable ? "Repeats by a rule set in another app. Choose another to replace it." : null;
1564
1568
  const describedBy = [wiring["aria-describedby"], summary ? ids.summary : void 0].filter(Boolean).join(" ") || void 0;
1565
1569
  const order = Array.from({ length: 7 }, (_, i) => (weekStart + i) % 7);
@@ -1571,17 +1575,17 @@ function RecurrencePicker({ value, onChange: onChangeProp, start, label, weekSta
1571
1575
  const monthlyOn = custom?.nth ? nthId(custom.nth) : custom?.day === -1 ? "last" : "day";
1572
1576
  const ends = custom?.until ? "until" : custom?.count !== void 0 ? "count" : untilPending ? "until" : "never";
1573
1577
  if (!hydrated) {
1574
- return /* @__PURE__ */ jsxs("div", { "data-gs": "recurrence-picker", "data-recurrence-pending": "", "aria-busy": "true", className: cn("space-y-ds-2", className), children: [
1575
- /* @__PURE__ */ jsx(Dropdown, { ...wiring, label: field ? void 0 : label ?? "Repeat", options: PENDING_OPTIONS, value: "pending", onChange: () => {
1578
+ return /* @__PURE__ */ jsxs("div", { "data-gs": "recurrence-picker", "data-recurrence-pending": "", "aria-busy": "true", ...named, className: cn("space-y-ds-2", className), children: [
1579
+ /* @__PURE__ */ jsx(Dropdown, { ...choiceWiring, label: field ? void 0 : label ?? "Repeat", options: PENDING_OPTIONS, value: "pending", onChange: () => {
1576
1580
  } }),
1577
1581
  (rule && !noDays(rule) || unreadable) && /* @__PURE__ */ jsx("p", { "aria-hidden": "true", className: "text-xs", children: "\xA0" })
1578
1582
  ] });
1579
1583
  }
1580
- return /* @__PURE__ */ jsxs("div", { "data-gs": "recurrence-picker", className: cn("space-y-ds-2", className), children: [
1584
+ return /* @__PURE__ */ jsxs("div", { "data-gs": "recurrence-picker", ...named, className: cn("space-y-ds-2", className), children: [
1581
1585
  /* @__PURE__ */ jsx(
1582
1586
  Dropdown,
1583
1587
  {
1584
- ...wiring,
1588
+ ...choiceWiring,
1585
1589
  "aria-describedby": describedBy,
1586
1590
  label: field ? void 0 : label ?? "Repeat",
1587
1591
  options,
@@ -2084,7 +2088,7 @@ var running = (item) => item.status === "queued" || item.status === "uploading";
2084
2088
  var failureOf = (err) => err instanceof Error && err.message ? err.message : "The upload didn't work.";
2085
2089
  var percentOf = (value) => typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.min(100, value)) : 0;
2086
2090
  var STILL_UPLOADING = "Wait for your files to finish uploading";
2087
- function FileUpload({ onFiles, files, upload, onCancel, onRetry, onRemove, accept, multiple = true, maxSize, className, error = false, label, id, "aria-label": ariaLabel }) {
2091
+ function FileUpload({ onFiles, files, upload, onCancel, onRetry, onRemove, accept, multiple = true, maxSize, className, error = false, label, id, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy }) {
2088
2092
  const controlled = files !== void 0;
2089
2093
  const [own, setOwn] = useState([]);
2090
2094
  const shown = controlled ? files : own;
@@ -2104,6 +2108,7 @@ function FileUpload({ onFiles, files, upload, onCancel, onRetry, onRemove, accep
2104
2108
  const constraintsId = `${ownId}-constraints`;
2105
2109
  const rejectedId = `${ownId}-rejected`;
2106
2110
  const showLabel = label != null && !field;
2111
+ const control = useFieldControl({ "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy }, { component: "FileUpload", ownLabel: showLabel ? true : void 0 });
2107
2112
  const constraints = fileConstraints(accept, maxSize, multiple);
2108
2113
  const patch = (itemId, change) => setOwn((prev) => prev.map((i) => i.id === itemId ? { ...i, ...change } : i));
2109
2114
  const stop = (itemId) => {
@@ -2315,8 +2320,8 @@ function FileUpload({ onFiles, files, upload, onCancel, onRetry, onRemove, accep
2315
2320
  accept,
2316
2321
  multiple,
2317
2322
  className: "sr-only",
2318
- "aria-label": field ? void 0 : ariaLabel,
2319
- "aria-labelledby": field ? field.labelId : void 0,
2323
+ "aria-label": control.labelId ? void 0 : control.label,
2324
+ "aria-labelledby": control.labelId,
2320
2325
  "aria-describedby": describedBy,
2321
2326
  "aria-invalid": invalid || void 0,
2322
2327
  onChange: (e) => {
@@ -2383,6 +2388,7 @@ function SliderRange({
2383
2388
  error = false,
2384
2389
  label,
2385
2390
  "aria-label": ariaLabel,
2391
+ "aria-labelledby": ariaLabelledBy,
2386
2392
  "aria-describedby": describedByProp,
2387
2393
  trackStyle,
2388
2394
  formatValue
@@ -2391,9 +2397,10 @@ function SliderRange({
2391
2397
  const field = useField();
2392
2398
  const ownId = useId();
2393
2399
  const showLabel = label != null && !field;
2394
- const nameId = field ? field.labelId : showLabel ? `${ownId}-label` : void 0;
2400
+ const control = useFieldControl({ "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy }, { component: "SliderRange", ownLabel: showLabel ? `${ownId}-label` : void 0 });
2401
+ const nameId = control.labelId;
2395
2402
  const describedBy = [...new Set([describedByProp, field?.describedBy].join(" ").split(" ").filter(Boolean))].join(" ") || void 0;
2396
- const thumbName = (part) => nameId ? { "aria-labelledby": part ? `${nameId} ${ownId}-${part}` : nameId } : { "aria-label": ariaLabel ? part ? `${ariaLabel} ${part}` : ariaLabel : void 0 };
2403
+ const thumbName = (part) => nameId ? { "aria-labelledby": part ? `${nameId} ${ownId}-${part}` : nameId } : { "aria-label": control.label ? part ? `${control.label} ${part}` : control.label : void 0 };
2397
2404
  const isRange = Array.isArray(value);
2398
2405
  const low = isRange ? value[0] : value;
2399
2406
  const high = isRange ? value[1] : value;
@@ -2607,7 +2614,7 @@ function TimePicker({
2607
2614
  }) {
2608
2615
  const onChange = useReportedChange(onChangeProp, value);
2609
2616
  const contextSize = useContext(ControlSizeContext);
2610
- const control = useFieldControl({ ...wiring, id, error: error || void 0 }, { requiredInDescription: true });
2617
+ const control = useFieldControl({ ...wiring, id, error: error || void 0 }, { requiredInDescription: true, component: "TimePicker" });
2611
2618
  useInputModality();
2612
2619
  const size = sizeProp ?? contextSize ?? "md";
2613
2620
  const [open, setOpen] = useState(false);
@@ -3293,6 +3300,7 @@ function PinInput({
3293
3300
  id,
3294
3301
  "aria-label": ariaLabel
3295
3302
  }) {
3303
+ const control = useFieldControl({ "aria-label": ariaLabel }, { component: "PinInput" });
3296
3304
  const onChange = useReportedChange(onChangeProp, value);
3297
3305
  useInputModality();
3298
3306
  const contextSize = useContext(ControlSizeContext);
@@ -3350,7 +3358,8 @@ function PinInput({
3350
3358
  "div",
3351
3359
  {
3352
3360
  role: "group",
3353
- "aria-label": ariaLabel ?? "PIN input",
3361
+ "aria-labelledby": control.labelId,
3362
+ "aria-label": control.labelId ? void 0 : control.label ?? "PIN input",
3354
3363
  "aria-invalid": error || void 0,
3355
3364
  className: cn("inline-flex w-fit items-center gap-ds-2", className),
3356
3365
  children: digits.map((digit, i) => /* @__PURE__ */ jsx(
@@ -3442,7 +3451,8 @@ function Rating({
3442
3451
  const onChange = useReportedChange(onChangeProp, value);
3443
3452
  const [hoverValue, setHoverValue] = useState(null);
3444
3453
  const displayValue = hoverValue ?? value;
3445
- const control = useFieldControl({ ...wiring, error: error || void 0 });
3454
+ const control = useFieldControl({ ...wiring, error: error || void 0 }, { component: "Rating" });
3455
+ const valueId = `${useId()}-value`;
3446
3456
  const handleClick = useCallback(
3447
3457
  (starIdx, isLeftHalf) => {
3448
3458
  if (readOnly || !onChangeProp) return;
@@ -3505,13 +3515,13 @@ function Rating({
3505
3515
  i
3506
3516
  );
3507
3517
  });
3508
- return /* @__PURE__ */ jsx(
3518
+ return /* @__PURE__ */ jsxs(
3509
3519
  "div",
3510
3520
  {
3511
3521
  role: readOnly ? "img" : "radiogroup",
3512
3522
  id: control.id,
3513
- "aria-label": readOnly ? `${control.label ?? "Rating"}: ${value} out of ${max}` : control.labelId ? void 0 : control.label ?? `Rating: ${value} out of ${max}`,
3514
- "aria-labelledby": readOnly ? void 0 : control.labelId,
3523
+ "aria-label": control.labelId ? void 0 : readOnly ? `${control.label ?? "Rating"}: ${value} out of ${max}` : control.label ?? `Rating: ${value} out of ${max}`,
3524
+ "aria-labelledby": readOnly && control.labelId ? `${control.labelId} ${valueId}` : control.labelId,
3515
3525
  "aria-describedby": control.describedBy,
3516
3526
  "aria-invalid": readOnly ? void 0 : control.invalid || void 0,
3517
3527
  "aria-required": readOnly ? void 0 : control.required || void 0,
@@ -3524,7 +3534,10 @@ function Rating({
3524
3534
  className
3525
3535
  ),
3526
3536
  onMouseLeave: () => setHoverValue(null),
3527
- children: stars
3537
+ children: [
3538
+ readOnly && control.labelId && /* @__PURE__ */ jsx("span", { id: valueId, hidden: true, children: `${value} out of ${max}` }),
3539
+ stars
3540
+ ]
3528
3541
  }
3529
3542
  );
3530
3543
  }
@@ -3591,28 +3604,103 @@ var DEFAULT_SWATCHES = [
3591
3604
  "#000000",
3592
3605
  "#ffffff"
3593
3606
  ];
3607
+ function parseHex(value) {
3608
+ return /^#?([0-9a-f]{6}|[0-9a-f]{8})$/i.test(value.trim()) ? hexToHsl(value.trim()) : null;
3609
+ }
3610
+ var sameColor = (a, b) => a.trim().toLowerCase() === b.trim().toLowerCase();
3611
+ var NEUTRAL = { h: 0, s: 0, l: 50, a: 1 };
3594
3612
  function ColorPicker({
3595
3613
  value,
3596
3614
  onChange: onChangeProp,
3597
3615
  showAlpha = false,
3598
3616
  swatches = DEFAULT_SWATCHES,
3617
+ swatchesOnly = false,
3599
3618
  disabled = false,
3600
3619
  className,
3601
- error = false,
3602
- "aria-label": ariaLabel
3620
+ ...wiring
3603
3621
  }) {
3622
+ const control = useFieldControl(wiring, { requiredInDescription: !swatchesOnly, component: "ColorPicker" });
3623
+ const named = {
3624
+ id: control.id,
3625
+ "aria-label": control.labelId ? void 0 : control.label,
3626
+ "aria-labelledby": control.labelId,
3627
+ "aria-describedby": control.describedBy,
3628
+ "aria-invalid": control.invalid || void 0
3629
+ };
3604
3630
  const onChange = useReportedChange(onChangeProp, value);
3605
3631
  useInputModality();
3606
- const [hsl, setHsl] = useState(() => hexToHsl(value));
3632
+ const [hsl, setHsl] = useState(() => parseHex(value) ?? NEUTRAL);
3607
3633
  const [hexInput, setHexInput] = useState(value);
3608
3634
  const satLightRef = useRef(null);
3609
3635
  const draggingRef = useRef(false);
3636
+ const swatchRefs = useRef([]);
3610
3637
  const [prevValue, setPrevValue] = useState(value);
3611
3638
  if (value !== prevValue) {
3612
3639
  setPrevValue(value);
3613
- setHsl(hexToHsl(value));
3640
+ setHsl((was) => parseHex(value) ?? was);
3614
3641
  setHexInput(value);
3615
3642
  }
3643
+ const items = swatches.map((s) => typeof s === "string" ? { value: s, label: s } : s);
3644
+ const repeated = items.find((s, i) => items.findIndex((o) => sameColor(o.value, s.value)) !== i)?.value;
3645
+ useEffect(() => {
3646
+ if (repeated) warnOnce("ColorPicker", `two swatches share the colour "${repeated}": give each swatch a colour of its own, so the one chosen is the one shown as chosen.`);
3647
+ }, [repeated]);
3648
+ const chosen = items.findIndex((s) => sameColor(s.value, value));
3649
+ const stop = Math.max(0, chosen);
3650
+ const choose = (index) => {
3651
+ const swatch = items[index];
3652
+ if (!swatch || disabled) return;
3653
+ setHsl((was) => parseHex(swatch.value) ?? was);
3654
+ setHexInput(swatch.value);
3655
+ if (!sameColor(swatch.value, value)) onChange(swatch.value);
3656
+ };
3657
+ const onSwatchKey = (e, index) => {
3658
+ const step = e.key === "ArrowRight" || e.key === "ArrowDown" ? 1 : e.key === "ArrowLeft" || e.key === "ArrowUp" ? -1 : 0;
3659
+ const to = step ? (index + step + items.length) % items.length : e.key === "Home" ? 0 : e.key === "End" ? items.length - 1 : -1;
3660
+ if (to < 0) return;
3661
+ e.preventDefault();
3662
+ swatchRefs.current[to]?.focus();
3663
+ choose(to);
3664
+ };
3665
+ const swatchRow = /* @__PURE__ */ jsx(
3666
+ "div",
3667
+ {
3668
+ role: "radiogroup",
3669
+ ...swatchesOnly ? { ...named, "aria-required": control.required || void 0 } : { "aria-label": "Swatches" },
3670
+ "aria-disabled": disabled || void 0,
3671
+ className: cn("flex flex-wrap gap-ds-2", swatchesOnly && disabled && "opacity-disabled", swatchesOnly && className),
3672
+ children: items.map((swatch, i) => /* @__PURE__ */ jsx(AutoTooltip, { content: swatch.label, children: /* @__PURE__ */ jsx(
3673
+ "button",
3674
+ {
3675
+ ref: (el) => {
3676
+ swatchRefs.current[i] = el;
3677
+ },
3678
+ type: "button",
3679
+ role: "radio",
3680
+ "aria-checked": i === chosen,
3681
+ "aria-label": swatch.label,
3682
+ tabIndex: i === stop ? 0 : -1,
3683
+ disabled,
3684
+ onClick: () => choose(i),
3685
+ onKeyDown: (e) => onSwatchKey(e, i),
3686
+ className: cn(
3687
+ "w-7 h-7 rounded-[var(--ds-radius-sm)] cursor-pointer disabled:cursor-not-allowed",
3688
+ "flex items-center justify-center",
3689
+ "border-[length:var(--ds-border-width)]",
3690
+ "hover:scale-110 transition-transform",
3691
+ FOCUS_RING,
3692
+ // selected is a choice's look: the accent border, never the halo (that is active's) or an outline
3693
+ i === chosen ? "border-[var(--ds-accent-ink)]" : "border-[var(--ds-border-default)]"
3694
+ ),
3695
+ style: { backgroundColor: swatch.value },
3696
+ children: i === chosen && /* @__PURE__ */ jsxs("svg", { "data-swatch-check": "", "aria-hidden": "true", width: 16, height: 16, viewBox: "0 0 24 24", fill: "none", strokeLinecap: "round", strokeLinejoin: "round", className: "pointer-events-none", children: [
3697
+ /* @__PURE__ */ jsx("path", { d: "M20 6 9 17l-5-5", vectorEffect: "non-scaling-stroke", strokeWidth: 6.5, style: { stroke: "var(--ds-text-primary)" } }),
3698
+ /* @__PURE__ */ jsx("path", { d: "M20 6 9 17l-5-5", vectorEffect: "non-scaling-stroke", strokeWidth: 2.5, style: { stroke: "var(--ds-bg-canvas)" } })
3699
+ ] })
3700
+ }
3701
+ ) }, `${swatch.value}-${i}`))
3702
+ }
3703
+ );
3616
3704
  const emitChange = useCallback(
3617
3705
  (newHsl) => {
3618
3706
  setHsl(newHsl);
@@ -3661,23 +3749,24 @@ function ColorPicker({
3661
3749
  },
3662
3750
  [onChange]
3663
3751
  );
3752
+ if (swatchesOnly) return swatchRow;
3664
3753
  return /* @__PURE__ */ jsxs(
3665
3754
  "div",
3666
3755
  {
3667
3756
  role: "group",
3668
- "aria-label": ariaLabel,
3669
- "aria-invalid": error || void 0,
3757
+ ...named,
3670
3758
  className: cn(
3671
3759
  "w-full max-w-64 p-ds-3 space-y-ds-3",
3672
3760
  "bg-[var(--ds-bg-surface)] text-[var(--ds-text-primary)]",
3673
3761
  "border-[length:var(--ds-border-width)]",
3674
- error ? "border-[var(--ds-status-error)]" : "border-[var(--ds-border-default)]",
3762
+ control.invalid ? "border-[var(--ds-status-error)]" : "border-[var(--ds-border-default)]",
3675
3763
  "rounded-[var(--ds-radius-lg)]",
3676
3764
  "shadow-[var(--ds-shadow-md)]",
3677
3765
  disabled && "opacity-disabled pointer-events-none",
3678
3766
  className
3679
3767
  ),
3680
3768
  children: [
3769
+ control.requiredNote,
3681
3770
  /* @__PURE__ */ jsx(
3682
3771
  "div",
3683
3772
  {
@@ -3710,6 +3799,7 @@ function ColorPicker({
3710
3799
  {
3711
3800
  type: "range",
3712
3801
  "aria-label": "Hue",
3802
+ disabled,
3713
3803
  min: 0,
3714
3804
  max: 360,
3715
3805
  value: hsl.h,
@@ -3725,6 +3815,7 @@ function ColorPicker({
3725
3815
  {
3726
3816
  type: "range",
3727
3817
  "aria-label": "Opacity",
3818
+ disabled,
3728
3819
  min: 0,
3729
3820
  max: 100,
3730
3821
  value: Math.round(hsl.a * 100),
@@ -3740,7 +3831,7 @@ function ColorPicker({
3740
3831
  "div",
3741
3832
  {
3742
3833
  className: "w-8 h-8 rounded-[var(--ds-radius-sm)] border-[length:var(--ds-border-width)] border-[var(--ds-border-default)] shrink-0",
3743
- style: { backgroundColor: hslToHex(hsl) }
3834
+ style: { backgroundColor: parseHex(value) ? hslToHex(hsl) : value }
3744
3835
  }
3745
3836
  ),
3746
3837
  /* @__PURE__ */ jsx(
@@ -3748,6 +3839,7 @@ function ColorPicker({
3748
3839
  {
3749
3840
  type: "text",
3750
3841
  "aria-label": "Hex colour",
3842
+ disabled,
3751
3843
  value: hexInput,
3752
3844
  onChange: (e) => handleHexChange(e.target.value),
3753
3845
  className: cn(
@@ -3761,27 +3853,7 @@ function ColorPicker({
3761
3853
  }
3762
3854
  )
3763
3855
  ] }),
3764
- swatches.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-ds-2", children: swatches.map((swatch) => /* @__PURE__ */ jsx(
3765
- "button",
3766
- {
3767
- type: "button",
3768
- onClick: () => {
3769
- const parsed = hexToHsl(swatch);
3770
- emitChange(parsed);
3771
- },
3772
- className: cn(
3773
- "w-7 h-7 rounded-[var(--ds-radius-sm)] cursor-pointer",
3774
- "border-[length:var(--ds-border-width)]",
3775
- "hover:scale-110 transition-transform",
3776
- FOCUS_RING,
3777
- // selected is a choice's look: the accent border, never the halo (that is active's) or an outline
3778
- swatch.toLowerCase() === value.toLowerCase() ? "border-[var(--ds-accent-ink)]" : "border-[var(--ds-border-default)]"
3779
- ),
3780
- style: { backgroundColor: swatch },
3781
- "aria-label": swatch
3782
- },
3783
- swatch
3784
- )) })
3856
+ swatches.length > 0 && swatchRow
3785
3857
  ]
3786
3858
  }
3787
3859
  );
@@ -4513,7 +4585,7 @@ function MentionInput({
4513
4585
  "aria-label": ariaLabel,
4514
4586
  ...wiring
4515
4587
  }) {
4516
- const control = useFieldControl({ ...wiring, error: error || void 0 });
4588
+ const control = useFieldControl({ ...wiring, "aria-label": ariaLabel, error: error || void 0 }, { component: "MentionInput" });
4517
4589
  useInputModality();
4518
4590
  const [internalValue, setInternalValue] = useState("");
4519
4591
  const value = controlledValue ?? internalValue;
@@ -4632,8 +4704,8 @@ function MentionInput({
4632
4704
  {
4633
4705
  ref: inputRef,
4634
4706
  id: control.id,
4635
- "aria-labelledby": wiring["aria-labelledby"],
4636
- "aria-label": control.labelId ? void 0 : ariaLabel,
4707
+ "aria-labelledby": control.shown ? void 0 : wiring["aria-labelledby"],
4708
+ "aria-label": control.labelId ? void 0 : control.label,
4637
4709
  "aria-describedby": control.describedBy,
4638
4710
  "aria-invalid": control.invalid || void 0,
4639
4711
  "aria-required": control.required || void 0,
@@ -5782,6 +5854,131 @@ function rowMarks(row, index, field, id) {
5782
5854
  return typeof v === "string" || typeof v === "number" ? { "data-row-key": String(id) } : { "data-row-index": String(index) };
5783
5855
  }
5784
5856
  var idPart = (id) => String(id).replace(/\s+/g, "_");
5857
+ var an = (word) => /^[aeiou]/i.test(word) ? "an" : "a";
5858
+ var MAX_TREE_DEPTH = 64;
5859
+ var MAX_TREE_ROWS = 1e5;
5860
+ var COPY_BUDGET = 1e3;
5861
+ var isPlainObject = (o) => {
5862
+ const proto = Object.getPrototypeOf(o);
5863
+ return proto === Object.prototype || proto === null;
5864
+ };
5865
+ function isCopy(above, row, field) {
5866
+ if (above === row) return true;
5867
+ if (!Object.is(above[field], row[field])) return false;
5868
+ let left = COPY_BUDGET;
5869
+ const alike = (a, b) => {
5870
+ if (Object.is(a, b)) return true;
5871
+ if (typeof a === "function" && typeof b === "function") return true;
5872
+ if (typeof a !== "object" || typeof b !== "object" || a === null || b === null || --left < 0) return false;
5873
+ if (isValidElement(a) || isValidElement(b)) return isValidElement(a) && isValidElement(b) && a.type === b.type && a.key === b.key && alike(a.props, b.props);
5874
+ if (Array.isArray(a) || Array.isArray(b)) return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((v, i) => alike(v, b[i]));
5875
+ if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
5876
+ if (!isPlainObject(a) || !isPlainObject(b)) return false;
5877
+ const keys = Object.keys(a);
5878
+ return keys.length === Object.keys(b).length && keys.every((k) => Object.hasOwn(b, k) && alike(a[k], b[k]));
5879
+ };
5880
+ return alike(above, row);
5881
+ }
5882
+ function treeOf(data, ids, field, childrenOf) {
5883
+ const byKey = /* @__PURE__ */ new Map();
5884
+ const all = [];
5885
+ const own = /* @__PURE__ */ new Set();
5886
+ let unnamed = false;
5887
+ let repeated = null;
5888
+ let looped = null;
5889
+ let tooDeep = false;
5890
+ let tooMany = false;
5891
+ const named = (row) => {
5892
+ const v = row[field];
5893
+ if (typeof v !== "string" && typeof v !== "number") return false;
5894
+ if (own.has(String(v))) repeated ??= String(v);
5895
+ own.add(String(v));
5896
+ return true;
5897
+ };
5898
+ const roots = data.map((row, i) => ({ row, originalIdx: i, id: ids[i] ?? i, level: 1, parent: null, children: [] }));
5899
+ for (const node of roots) {
5900
+ byKey.set(String(node.id), node);
5901
+ if (!named(node.row)) unnamed = true;
5902
+ }
5903
+ const grow = (node, path) => {
5904
+ all.push(node);
5905
+ if (all.length >= MAX_TREE_ROWS) {
5906
+ tooMany = true;
5907
+ return;
5908
+ }
5909
+ const rows = childrenOf(node.row, { level: node.level, parent: node.parent?.row ?? null });
5910
+ if (!rows?.length) return;
5911
+ if (node.level >= MAX_TREE_DEPTH) {
5912
+ tooDeep = true;
5913
+ return;
5914
+ }
5915
+ path.push(node.row);
5916
+ rows.forEach((row, i) => {
5917
+ const place = `${node.id}/${i}`;
5918
+ if (path.some((above) => isCopy(above, row, field))) {
5919
+ looped ??= typeof row[field] === "string" || typeof row[field] === "number" ? String(row[field]) : place;
5920
+ return;
5921
+ }
5922
+ const hasOwn = named(row);
5923
+ if (!hasOwn) unnamed = true;
5924
+ const v = row[field];
5925
+ const id = hasOwn && !byKey.has(String(v)) ? v : hasOwn ? `${v}~${place}` : place;
5926
+ const child = { row, originalIdx: -1, id, level: node.level + 1, parent: node, children: [] };
5927
+ byKey.set(String(id), child);
5928
+ node.children.push(child);
5929
+ grow(child, path);
5930
+ });
5931
+ path.pop();
5932
+ };
5933
+ for (const node of roots) grow(node, []);
5934
+ return { roots, byKey, all, unnamed, repeated, looped, tooDeep, tooMany };
5935
+ }
5936
+ function keysUnder(node, out = []) {
5937
+ out.push(String(node.id));
5938
+ for (const child of node.children) keysUnder(child, out);
5939
+ return out;
5940
+ }
5941
+ function settleSelection(roots, chosen) {
5942
+ const set = new Set(chosen);
5943
+ const partly = /* @__PURE__ */ new Set();
5944
+ const down = (node, on) => {
5945
+ const now = on || set.has(String(node.id));
5946
+ if (now) set.add(String(node.id));
5947
+ for (const child of node.children) down(child, now);
5948
+ };
5949
+ const up = (node) => {
5950
+ const key = String(node.id);
5951
+ if (!node.children.length) return set.has(key) ? "all" : "none";
5952
+ const states = node.children.map(up);
5953
+ const all = states.every((s) => s === "all");
5954
+ if (all) set.add(key);
5955
+ else set.delete(key);
5956
+ if (!all && states.some((s) => s !== "none")) partly.add(key);
5957
+ return all ? "all" : partly.has(key) ? "some" : "none";
5958
+ };
5959
+ for (const root of roots) down(root, false);
5960
+ for (const root of roots) up(root);
5961
+ return { set, partly };
5962
+ }
5963
+ var TREE_TOGGLE = cn(
5964
+ "relative shrink-0 w-4 h-4 flex items-center justify-center cursor-pointer rounded-[var(--ds-radius-sm)]",
5965
+ "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)]",
5966
+ "before:absolute before:left-1/2 before:top-1/2 before:h-6 before:w-6 before:-translate-x-1/2 before:-translate-y-1/2 before:content-['']",
5967
+ FOCUS_RING
5968
+ );
5969
+ function rowsOfKey(body, ownKey, key) {
5970
+ return key === "" ? [] : Array.from(body.querySelectorAll(`:scope > tr[data-grid-key="${CSS.escape(ownKey + key)}"], :scope > tr[data-panel-of="${CSS.escape(key)}"]`));
5971
+ }
5972
+ var HEAD_KEY = "head:";
5973
+ var TreeHeldCtx = createContext(false);
5974
+ var CARD_TOGGLE = cn(
5975
+ // at least 24 px tall: the card around it is a target too, so its own box must meet the size (WCAG 2.5.8)
5976
+ "inline-flex min-h-[24px] items-center gap-ds-1 text-xs font-medium cursor-pointer",
5977
+ "transition-colors duration-[var(--ds-transition-speed)]",
5978
+ "text-[var(--ds-accent-ink)] hover:bg-[var(--ds-accent-muted)]",
5979
+ "rounded-[var(--ds-radius-sm)] px-ds-1.5 py-ds-0.5 -mx-ds-1.5",
5980
+ FOCUS_RING
5981
+ );
5785
5982
  function TableHeading({ title, description, actions, titleId, descriptionId, inset }) {
5786
5983
  const heading = /* @__PURE__ */ jsx("h3", { id: titleId, className: TITLE_CLASSES, children: title });
5787
5984
  const actionsBlock = actions ? /* @__PURE__ */ jsx("div", { className: HEADER_LAYOUT.actions, children: actions }) : null;
@@ -5819,6 +6016,12 @@ function Table({
5819
6016
  selectable,
5820
6017
  selectedRows = [],
5821
6018
  onSelectionChange,
6019
+ selectedKeys,
6020
+ onSelectedKeysChange,
6021
+ getChildRows,
6022
+ openParents: openParentsProp,
6023
+ defaultOpenParents,
6024
+ onOpenParentsChange,
5822
6025
  expandable,
5823
6026
  renderExpanded,
5824
6027
  rowKey = "id",
@@ -5879,8 +6082,13 @@ function Table({
5879
6082
  },
5880
6083
  [sortKey, sortDir, onSortChange, pager]
5881
6084
  );
6085
+ const { ids: rowIds, repeated } = useMemo(() => rowIdsOf(data, rowKey), [data, rowKey]);
6086
+ const tree = useMemo(() => getChildRows ? treeOf(data, rowIds, rowKey, getChildRows) : null, [data, rowIds, rowKey, getChildRows]);
6087
+ const nested = tree;
6088
+ const idOf = (_row, index) => rowIds[index] ?? index;
5882
6089
  const columnKeys = useMemo(() => columns.map((c) => c.key), [columns]);
5883
- const inferredKinds = useMemo(() => inferColumnKinds(data, columnKeys), [data, columnKeys]);
6090
+ const allRows = useMemo(() => nested ? nested.all.map((n) => n.row) : data, [nested, data]);
6091
+ const inferredKinds = useMemo(() => inferColumnKinds(allRows, columnKeys), [allRows, columnKeys]);
5884
6092
  const kindOf2 = useCallback(
5885
6093
  (col) => col.kind ?? inferredKinds.get(col.key),
5886
6094
  [inferredKinds]
@@ -5888,9 +6096,9 @@ function Table({
5888
6096
  const sortColumn = sortKey ? columns.find((c) => c.key === sortKey) : void 0;
5889
6097
  const sortKind = sortColumn ? kindOf2(sortColumn) : void 0;
5890
6098
  const locale = useFormatLocale();
5891
- const { sortedAll, unsortable } = useMemo(() => {
6099
+ const { sortedAll, unsortable, childOrder } = useMemo(() => {
5892
6100
  const indexed = data.map((row, i) => ({ row, originalIdx: i }));
5893
- if (!sortKey || !sortDir) return { sortedAll: indexed, unsortable: null };
6101
+ if (!sortKey || !sortDir) return { sortedAll: indexed, unsortable: null, childOrder: null };
5894
6102
  const read = (row) => {
5895
6103
  if (sortColumn?.sortValue) return sortableValue(sortColumn.sortValue(row));
5896
6104
  const v = sortableValue(row[sortKey]);
@@ -5898,29 +6106,33 @@ function Table({
5898
6106
  return (sortKind === "number" ? numberOf(v) : sortKind === "date" ? timeOf(v) : void 0) ?? v;
5899
6107
  };
5900
6108
  const valued = indexed.map((entry) => ({ ...entry, value: read(entry.row) }));
5901
- const elements = valued.some((v) => v.value === null);
5902
- const sorted = elements ? indexed : valued.sort((a, b) => {
6109
+ const levels = nested ? nested.all.filter((n) => n.children.length).map((n) => ({ key: String(n.id), rows: n.children.map((c) => ({ c, value: read(c.row) })) })) : [];
6110
+ const elements = valued.some((v) => v.value === null) || levels.some((l) => l.rows.some((r) => r.value === null));
6111
+ const compare = (a, b) => {
5903
6112
  if (a.value === void 0 || b.value === void 0) return a.value === b.value ? 0 : a.value === void 0 ? 1 : -1;
5904
6113
  const cmp = compareValues(a.value, b.value, locale);
5905
6114
  return sortDir === "asc" ? cmp : -cmp;
5906
- });
5907
- return { sortedAll: sorted, unsortable: elements ? sortKey : null };
5908
- }, [data, sortKey, sortDir, sortColumn, sortKind, locale]);
6115
+ };
6116
+ const sorted = elements ? indexed : valued.sort(compare);
6117
+ const childOrder2 = elements || !nested ? null : new Map(levels.map((l) => [l.key, l.rows.sort(compare).map((r) => r.c)]));
6118
+ return { sortedAll: sorted, unsortable: elements ? sortKey : null, childOrder: childOrder2 };
6119
+ }, [data, sortKey, sortDir, sortColumn, sortKind, locale, nested]);
5909
6120
  const canSort = useMemo(() => {
5910
6121
  const can = /* @__PURE__ */ new Set();
5911
- for (const col of columns) if (col.sortValue || data.every((row) => sortableValue(row[col.key]) !== null)) can.add(col.key);
6122
+ for (const col of columns) if (col.sortValue || allRows.every((row) => sortableValue(row[col.key]) !== null)) can.add(col.key);
5912
6123
  return can;
5913
- }, [columns, data]);
6124
+ }, [columns, allRows]);
5914
6125
  useEffect(() => {
5915
6126
  if (unsortable) warnOnce("Table", `the column "${unsortable}" is sortable, but its cells are elements, which can't be compared: give it a sortValue, (row) => the name, number or date it shows (data_display#1).`);
5916
6127
  }, [unsortable]);
5917
6128
  const pageSorts = !!onSortChange;
6129
+ const sortsBy = (col) => col.header.trim() !== "" && (pageSorts ? col.sortable ?? (!!sortable && canSort.has(col.key)) : (col.sortable ?? !!sortable) && canSort.has(col.key));
5918
6130
  const refused = pageSorts ? "" : columns.filter((col) => col.sortable && !canSort.has(col.key)).map((col) => col.key).join(", ");
5919
6131
  useEffect(() => {
5920
6132
  if (refused) warnOnce("Table", `the column "${refused}" is marked sortable, but its cells are elements the Table can't compare, so it draws no sort button: give it a sortValue, (row) => the name, number or date it shows, or sort the rows yourself with sortKey and onSortChange (data_display#1).`);
5921
6133
  }, [refused]);
5922
6134
  useEffect(() => {
5923
- if (pager && data.length !== pager.totalItems) warnOnce("Table", `the pager counts ${pager.totalItems} rows but data holds ${data.length}: give the table and usePagination the same rows, every row of the list, and the table draws the pager's part (Sprint 25.13 A3).`);
6135
+ if (pager && data.length !== pager.totalItems) warnOnce("Table", `the pager counts ${pager.totalItems} rows but data holds ${data.length}: give the table and usePagination the same rows, every row of the list, and the table draws the pager's part.`);
5924
6136
  }, [pager, data.length]);
5925
6137
  const rangeStart = pager ? pager.range[0] : null;
5926
6138
  const rangeEnd2 = pager ? pager.range[1] : null;
@@ -5929,14 +6141,65 @@ function Table({
5929
6141
  [sortedAll, rangeStart, rangeEnd2]
5930
6142
  );
5931
6143
  const drawnStart = rangeStart ? rangeStart - 1 : 0;
6144
+ const openControlled = openParentsProp !== void 0;
6145
+ const [ownOpen, setOwnOpen] = useState(defaultOpenParents ?? []);
6146
+ const open = openControlled ? openParentsProp : ownOpen;
6147
+ const openSet = new Set(open.map(String));
6148
+ const setOpen = (next) => {
6149
+ if (!openControlled) setOwnOpen(next);
6150
+ onOpenParentsChange?.(next);
6151
+ };
6152
+ const toggleOpen = (id) => setOpen(openSet.has(String(id)) ? open.filter((k) => String(k) !== String(id)) : [...open, id]);
6153
+ const visible = nested ? [] : null;
6154
+ if (nested && visible) {
6155
+ const walk = (nodes, first, size) => nodes.forEach((node, i) => {
6156
+ visible.push({ node, pos: first + i, size });
6157
+ if (!node.children.length || !openSet.has(String(node.id))) return;
6158
+ const under = childOrder?.get(String(node.id)) ?? node.children;
6159
+ walk(under, 1, under.length);
6160
+ });
6161
+ walk(drawn.map((d) => nested.roots[d.originalIdx]), drawnStart + 1, data.length);
6162
+ }
6163
+ const visibleKeys = visible ? visible.map((v) => String(v.node.id)) : [];
6164
+ const reportRows = useReportedChange(onSelectionChange, selectedRows);
6165
+ const reportKeys = useReportedChange(onSelectedKeysChange, selectedKeys);
5932
6166
  const selectedSet = new Set(selectedRows);
5933
6167
  const drawnIdx = drawn.map((d) => d.originalIdx);
5934
- const allSelected = drawnIdx.length > 0 && drawnIdx.every((i) => selectedSet.has(i));
5935
- const someSelected = !allSelected && drawnIdx.some((i) => selectedSet.has(i));
6168
+ const keyed = selectedKeys !== void 0 || onSelectedKeysChange !== void 0;
6169
+ const chosen = useMemo(() => {
6170
+ if (!keyed) return null;
6171
+ const keys = (selectedKeys ?? []).map(String);
6172
+ return nested ? settleSelection(nested.roots, keys) : { set: new Set(keys), partly: /* @__PURE__ */ new Set() };
6173
+ }, [keyed, selectedKeys, nested]);
6174
+ const scopeKeys = chosen ? nested ? drawn.flatMap((d) => keysUnder(nested.roots[d.originalIdx])) : drawn.map((d) => String(idOf(d.row, d.originalIdx))) : null;
6175
+ const allSelected = scopeKeys ? scopeKeys.length > 0 && scopeKeys.every((k) => chosen.set.has(k)) : drawnIdx.length > 0 && drawnIdx.every((i) => selectedSet.has(i));
6176
+ const someSelected = !allSelected && (scopeKeys ? scopeKeys.some((k) => chosen.set.has(k)) : drawnIdx.some((i) => selectedSet.has(i)));
6177
+ const emitKeys = (next) => {
6178
+ const settled = nested ? settleSelection(nested.roots, next).set : next;
6179
+ const out = [];
6180
+ const placed = /* @__PURE__ */ new Set();
6181
+ const put = (key) => {
6182
+ if (!settled.has(String(key)) || placed.has(String(key))) return;
6183
+ placed.add(String(key));
6184
+ out.push(key);
6185
+ };
6186
+ for (const key of selectedKeys ?? []) put(key);
6187
+ for (const key of nested ? nested.all.map((n) => n.id) : rowIds) put(key);
6188
+ reportKeys(out);
6189
+ };
5936
6190
  const toggleAll = () => {
6191
+ if (chosen && scopeKeys) {
6192
+ const next = new Set(chosen.set);
6193
+ for (const key of scopeKeys) {
6194
+ if (allSelected) next.delete(key);
6195
+ else next.add(key);
6196
+ }
6197
+ emitKeys(next);
6198
+ return;
6199
+ }
5937
6200
  const shown = new Set(drawnIdx);
5938
- if (allSelected) onSelectionChange?.(selectedRows.filter((r) => !shown.has(r)));
5939
- else onSelectionChange?.([...selectedRows.filter((r) => !shown.has(r)), ...drawnIdx]);
6201
+ if (allSelected) reportRows(selectedRows.filter((r) => !shown.has(r)));
6202
+ else reportRows([...selectedRows.filter((r) => !shown.has(r)), ...drawnIdx]);
5940
6203
  };
5941
6204
  const anchor = useRef(null);
5942
6205
  const toggleRow = (idx, shiftKey2 = false) => {
@@ -5945,22 +6208,81 @@ function Table({
5945
6208
  anchor.current = idx;
5946
6209
  if (shiftKey2 && from >= 0 && to >= 0 && from !== to) {
5947
6210
  const run = drawnIdx.slice(Math.min(from, to), Math.max(from, to) + 1);
5948
- onSelectionChange?.([...selectedRows.filter((r) => !run.includes(r)), ...run]);
6211
+ reportRows([...selectedRows.filter((r) => !run.includes(r)), ...run]);
5949
6212
  return;
5950
6213
  }
5951
- if (selectedSet.has(idx)) onSelectionChange?.(selectedRows.filter((r) => r !== idx));
5952
- else onSelectionChange?.([...selectedRows, idx]);
6214
+ if (selectedSet.has(idx)) reportRows(selectedRows.filter((r) => r !== idx));
6215
+ else reportRows([...selectedRows, idx]);
6216
+ };
6217
+ const anchorKey = useRef(null);
6218
+ const toggleKey = (id, shiftKey2 = false) => {
6219
+ if (!chosen) return;
6220
+ const key = String(id);
6221
+ const node = nested?.byKey.get(key);
6222
+ const order = nested ? visibleKeys : drawn.map((d) => String(idOf(d.row, d.originalIdx)));
6223
+ const from = anchorKey.current === null ? -1 : order.indexOf(anchorKey.current);
6224
+ const to = order.indexOf(key);
6225
+ anchorKey.current = key;
6226
+ const under = (k) => {
6227
+ const n = nested?.byKey.get(k);
6228
+ return n ? keysUnder(n) : [k];
6229
+ };
6230
+ const next = new Set(chosen.set);
6231
+ if (shiftKey2 && from >= 0 && to >= 0 && from !== to) {
6232
+ for (const k of order.slice(Math.min(from, to), Math.max(from, to) + 1)) for (const s of under(k)) next.add(s);
6233
+ } else if (chosen.set.has(key)) {
6234
+ for (const s of under(key)) next.delete(s);
6235
+ for (let p = node?.parent; p; p = p.parent) next.delete(String(p.id));
6236
+ } else {
6237
+ for (const s of under(key)) next.add(s);
6238
+ }
6239
+ emitKeys(next);
6240
+ };
6241
+ const boxOf = (originalIdx, id) => {
6242
+ if (chosen) return { checked: chosen.set.has(String(id)), mixed: chosen.partly.has(String(id)) };
6243
+ return originalIdx < 0 ? null : { checked: selectedSet.has(originalIdx), mixed: false };
5953
6244
  };
6245
+ const toggleBox = (originalIdx, id, shiftKey2 = false) => chosen ? toggleKey(id, shiftKey2) : toggleRow(originalIdx, shiftKey2);
6246
+ useEffect(() => {
6247
+ if (nested && selectable && !keyed) warnOnce("Table", "a tree table (getChildRows) selects by key: give it selectedKeys and onSelectedKeysChange, as a row under another has no index in data, and selectedRows reaches the top-level rows only.");
6248
+ }, [nested, selectable, keyed]);
6249
+ useEffect(() => {
6250
+ if (nested?.unnamed) warnOnce("Table", `a tree table (getChildRows) names its rows by their ${rowKey}: give every row its own, the rows under a row too (rowKey), so an open row, a selection and focus stay on their row.`);
6251
+ }, [nested, rowKey]);
6252
+ useEffect(() => {
6253
+ if (nested?.looped != null) warnOnce("Table", `a tree table's row "${nested.looped}" is under itself: getChildRows gave it, or a copy of it, among the rows under it or under a row below it, so the tree would never end, and it is left out there. Give getChildRows the rows each row holds, by the level and the parent it gives.`);
6254
+ }, [nested, rowKey]);
6255
+ useEffect(() => {
6256
+ if (nested?.tooMany) warnOnce("Table", `a tree table's rows come to more than ${MAX_TREE_ROWS.toLocaleString("en-GB")}, and the rest are drawn with no rows under them: give getChildRows an end, answering by the level it gives and giving no rows past the last level.`);
6257
+ }, [nested]);
6258
+ useEffect(() => {
6259
+ if (nested?.tooDeep) warnOnce("Table", `a tree table's rows go deeper than ${MAX_TREE_DEPTH} levels, and the last is drawn with no rows under it: give getChildRows's rows an end, answering by the level it gives.`);
6260
+ }, [nested]);
6261
+ useEffect(() => {
6262
+ if (nested?.repeated != null) warnOnce("Table", `a tree table (getChildRows) needs ${an(rowKey)} ${rowKey} of its own for each row, one per row across every level: "${nested.repeated}" names more than one, so an open row, a selection and focus may go to the other. Number the levels' rows apart ("task-3", "task-3/step-1").`);
6263
+ }, [nested, rowKey]);
5954
6264
  const expandControlled = expandedRowsProp !== void 0;
5955
6265
  const [ownExpanded, setOwnExpanded] = useState(defaultExpandedRows ?? []);
5956
6266
  const expanded = expandControlled ? expandedRowsProp : ownExpanded;
5957
6267
  const expandedSet = new Set(expanded.map(String));
5958
- const { ids: rowIds, repeated } = useMemo(() => rowIdsOf(data, rowKey), [data, rowKey]);
6268
+ const expandedKey = expanded.map(String).join("");
6269
+ const [revealed, setRevealed] = useState([]);
6270
+ if (nested && expandControlled) {
6271
+ const fresh = [...expandedSet].filter((key) => !revealed.includes(key) && nested.byKey.has(key));
6272
+ const kept = revealed.filter((key) => expandedSet.has(key));
6273
+ if (fresh.length || kept.length !== revealed.length) {
6274
+ setRevealed([...kept, ...fresh]);
6275
+ const shut = [];
6276
+ for (const key of fresh) {
6277
+ for (let p = nested.byKey.get(key)?.parent; p; p = p.parent) if (!openSet.has(String(p.id)) && !shut.includes(p.id)) shut.push(p.id);
6278
+ }
6279
+ if (shut.length && !openControlled) setOwnOpen([...ownOpen, ...shut]);
6280
+ }
6281
+ }
5959
6282
  useEffect(() => {
5960
- if (repeated !== null) warnOnce("Table", `two rows share the ${rowKey} "${repeated}": give each row its own, or name a field that is one (rowKey), so an opened row and focus stay on their row.`);
5961
- }, [repeated, rowKey]);
5962
- const idOf = (_row, index) => rowIds[index] ?? index;
5963
- const marksOf = (row, index) => rowMarks(row, index, rowKey, idOf(row, index));
6283
+ if (repeated !== null && !nested) warnOnce("Table", `two rows share the ${rowKey} "${repeated}": give each row its own, or name a field that is one (rowKey), so an opened row and focus stay on their row.`);
6284
+ }, [repeated, rowKey, nested]);
6285
+ const marksOf = (row, index, id = idOf(row, index)) => rowMarks(row, index < 0 ? String(id) : index, rowKey, id);
5964
6286
  const openedHere = useRef(null);
5965
6287
  const toggleExpand = (id) => {
5966
6288
  const opening = !expandedSet.has(String(id));
@@ -5985,9 +6307,11 @@ function Table({
5985
6307
  }, [ref, rootRef]);
5986
6308
  const tableRef = useRef(null);
5987
6309
  const [pins, setPins] = useState(NO_PINS);
5988
- const lead = (selectable ? 1 : 0) + (expandable ? 1 : 0);
5989
- const endCount = columns.filter((c) => c.pin === "end").length;
5990
- const nameIndex = columns.length > endCount ? lead : -1;
6310
+ const trail = expandable && nested ? 1 : 0;
6311
+ const lead = (selectable ? 1 : 0) + (expandable && !nested ? 1 : 0);
6312
+ const endColumns = columns.filter((c) => c.pin === "end").length;
6313
+ const endCount = endColumns + trail;
6314
+ const nameIndex = columns.length > endColumns ? lead : -1;
5991
6315
  const { ref: scrollRef, elRef: scrollEl, measure: measureScroll, scrollable, edges, fadeProps } = useScrollEdges({
5992
6316
  enabled: !isMobile,
5993
6317
  noFade: { left: lead + (pins.nameFits ? 1 : 0) > 0, right: endCount > 0 }
@@ -6019,7 +6343,9 @@ function Table({
6019
6343
  const nameFits = nameIndex >= 0 && frame > 0 && at + nameWidth + fromRight <= frame / 2;
6020
6344
  if (nameFits) start.push(at);
6021
6345
  const pad4 = (cell, side) => cell ? parseFloat(getComputedStyle(cell)[side]) || 0 : 0;
6022
- const panel = cells.length > lead ? [at + pad4(cells[lead], "paddingLeft"), pad4(cells[cells.length - 1], "paddingRight")] : null;
6346
+ let trailing = 0;
6347
+ for (let i = cells.length - trail; i < cells.length; i++) trailing += widths[i] ?? 0;
6348
+ const panel = cells.length > lead ? [at + pad4(cells[lead], "paddingLeft"), trailing + pad4(cells[cells.length - 1 - trail], "paddingRight")] : null;
6023
6349
  const next = { start, end, nameFits, frame, panel };
6024
6350
  setPins((prev) => samePins(prev, next) ? prev : next);
6025
6351
  };
@@ -6029,7 +6355,7 @@ function Table({
6029
6355
  observer.observe(table2);
6030
6356
  observer.observe(scroller);
6031
6357
  return () => observer.disconnect();
6032
- }, [isMobile, lead, endCount, nameIndex, scrollEl, columns, drawn.length]);
6358
+ }, [isMobile, lead, trail, endCount, nameIndex, scrollEl, columns, drawn.length, visibleKeys.length]);
6033
6359
  const pinnedStart = lead + (pins.nameFits ? 1 : 0);
6034
6360
  const pinStyle = (position, total) => {
6035
6361
  if (position < pinnedStart && pins.start[position] !== void 0) {
@@ -6044,7 +6370,7 @@ function Table({
6044
6370
  return void 0;
6045
6371
  };
6046
6372
  const pinFocus = (position, total) => {
6047
- if (!onRowClick || !pinStyle(position, total)) return void 0;
6373
+ if (!(onRowClick || nested) || !pinStyle(position, total)) return void 0;
6048
6374
  const fromEnd = total - 1 - position;
6049
6375
  return cn(PIN_FOCUS, position === 0 && PIN_FOCUS_START, fromEnd === 0 && PIN_FOCUS_END);
6050
6376
  };
@@ -6061,8 +6387,228 @@ function Table({
6061
6387
  if (kind) next.set(col.key, kind);
6062
6388
  }
6063
6389
  setRenderedKinds((prev) => prev.size === next.size && [...next].every(([k, v]) => prev.get(k) === v) ? prev : next);
6064
- }, [columns, kindOf2, drawn, isMobile]);
6065
- const expandedKey = expanded.map(String).join("");
6390
+ }, [columns, kindOf2, drawn, isMobile, visibleKeys.length]);
6391
+ const [focusKey, setFocusKey] = useState(null);
6392
+ const visibleSet = new Set(visibleKeys);
6393
+ const ownKey = `${headingId}~`;
6394
+ const gridKey = (key) => ownKey + key;
6395
+ const fromGrid = (attr) => attr !== null && attr.startsWith(ownKey) ? attr.slice(ownKey.length) : null;
6396
+ const heldOuter = useContext(TreeHeldCtx);
6397
+ const headKeys = nested ? [...selectable ? [`${HEAD_KEY}select`] : [], ...columns.filter(sortsBy).map((col) => `${HEAD_KEY}${col.key}`)] : [];
6398
+ const visibleJoined = visibleKeys.join("");
6399
+ const [stood, setStood] = useState({ joined: "", keys: [] });
6400
+ if (nested && focusKey !== null && !focusKey.startsWith(HEAD_KEY) && !nested.byKey.has(focusKey)) {
6401
+ const at = stood.keys.indexOf(focusKey);
6402
+ let next = null;
6403
+ for (let i = at - 1; at >= 0 && i >= 0 && next === null; i--) if (visibleSet.has(stood.keys[i])) next = stood.keys[i];
6404
+ for (let i = at + 1; at >= 0 && i < stood.keys.length && next === null; i++) if (visibleSet.has(stood.keys[i])) next = stood.keys[i];
6405
+ setFocusKey(next);
6406
+ }
6407
+ if (nested && stood.joined !== visibleJoined) setStood({ joined: visibleJoined, keys: visibleKeys });
6408
+ let activeKey = visibleKeys[0] ?? "";
6409
+ if (focusKey !== null && headKeys.includes(focusKey)) activeKey = focusKey;
6410
+ else {
6411
+ for (let n = focusKey === null ? void 0 : nested?.byKey.get(focusKey); n; n = n.parent ?? void 0) {
6412
+ if (visibleSet.has(String(n.id))) {
6413
+ activeKey = String(n.id);
6414
+ break;
6415
+ }
6416
+ }
6417
+ }
6418
+ const grid = useGridKeyboard({
6419
+ container: tableRef,
6420
+ active: activeKey,
6421
+ setActive: setFocusKey,
6422
+ keyOf: gridKey,
6423
+ move: (from, e) => {
6424
+ const node = nested?.byKey.get(from);
6425
+ const at = visibleKeys.indexOf(from);
6426
+ if (!node || at < 0 || e.shiftKey) return null;
6427
+ const isOpen = openSet.has(from);
6428
+ switch (e.key) {
6429
+ case "ArrowDown":
6430
+ return visibleKeys[at + 1] ?? from;
6431
+ case "ArrowUp":
6432
+ return visibleKeys[at - 1] ?? headKeys[0] ?? from;
6433
+ case "Home":
6434
+ return visibleKeys[0];
6435
+ case "End":
6436
+ return visibleKeys[visibleKeys.length - 1];
6437
+ case "ArrowRight":
6438
+ if (!node.children.length) return from;
6439
+ if (!isOpen) {
6440
+ toggleOpen(node.id);
6441
+ return from;
6442
+ }
6443
+ return visibleKeys[at + 1] ?? from;
6444
+ case "ArrowLeft":
6445
+ if (node.children.length && isOpen) {
6446
+ toggleOpen(node.id);
6447
+ return from;
6448
+ }
6449
+ return node.parent ? String(node.parent.id) : from;
6450
+ default:
6451
+ return null;
6452
+ }
6453
+ },
6454
+ // Enter and Space do what a click on the row does, onRowClick, else its Details (the second review, N8: on an
6455
+ // expandable table a click opened Details where Enter showed the rows); on a row a click does nothing on, its
6456
+ // selection, else its rows
6457
+ onActivate: (key, e) => {
6458
+ const node = nested?.byKey.get(key);
6459
+ if (!node) return;
6460
+ if (onRowClick) onRowClick(node.row, node.originalIdx);
6461
+ else if (canOpenByClick) toggleExpand(node.id);
6462
+ else if (selectable && boxOf(node.originalIdx, node.id)) toggleBox(node.originalIdx, node.id, e.shiftKey);
6463
+ else if (node.children.length) toggleOpen(node.id);
6464
+ }
6465
+ });
6466
+ const onTreeKeyDown = (e) => {
6467
+ if (e.target instanceof HTMLElement && e.target.parentElement === e.currentTarget) grid.onKeyDown(e);
6468
+ };
6469
+ const onHeadKeyDown = (e) => {
6470
+ if (e.defaultPrevented || e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) return;
6471
+ const key = e.target instanceof HTMLElement ? fromGrid(e.target.getAttribute("data-grid-key")) : null;
6472
+ const at = key === null ? -1 : headKeys.indexOf(key);
6473
+ if (key === null || at < 0) return;
6474
+ let next;
6475
+ switch (e.key) {
6476
+ case "ArrowLeft":
6477
+ next = headKeys[at - 1] ?? key;
6478
+ break;
6479
+ case "ArrowRight":
6480
+ next = headKeys[at + 1] ?? key;
6481
+ break;
6482
+ case "Home":
6483
+ next = headKeys[0];
6484
+ break;
6485
+ case "End":
6486
+ next = headKeys[headKeys.length - 1];
6487
+ break;
6488
+ case "ArrowDown":
6489
+ next = visibleKeys[0] ?? key;
6490
+ break;
6491
+ case "ArrowUp":
6492
+ next = key;
6493
+ break;
6494
+ default:
6495
+ return;
6496
+ }
6497
+ e.preventDefault();
6498
+ setFocusKey(next);
6499
+ grid.focus(next);
6500
+ };
6501
+ const onHeadFocus = (e) => {
6502
+ const key = fromGrid(e.target.getAttribute("data-grid-key"));
6503
+ if (key !== null && headKeys.includes(key)) setFocusKey(key);
6504
+ };
6505
+ const selectAllRef = useRef(null);
6506
+ useLayoutEffect(() => {
6507
+ const input = selectAllRef.current;
6508
+ if (!input) return;
6509
+ if (nested) {
6510
+ input.setAttribute("data-grid-key", gridKey(`${HEAD_KEY}select`));
6511
+ input.tabIndex = !heldOuter && activeKey === `${HEAD_KEY}select` ? 0 : -1;
6512
+ } else if (input.hasAttribute("data-grid-key")) {
6513
+ input.removeAttribute("data-grid-key");
6514
+ input.removeAttribute("tabindex");
6515
+ }
6516
+ });
6517
+ const [rowsBody, setRowsBody] = useState(null);
6518
+ const [held] = useState(() => /* @__PURE__ */ new WeakMap());
6519
+ const stopRef = useRef("");
6520
+ const holding = useRef(null);
6521
+ const stopKey = heldOuter ? "" : activeKey;
6522
+ useLayoutEffect(() => {
6523
+ const was = stopRef.current;
6524
+ stopRef.current = stopKey;
6525
+ if (was === stopKey || !rowsBody || !holding.current) return;
6526
+ holding.current([...rowsOfKey(rowsBody, ownKey, was), ...rowsOfKey(rowsBody, ownKey, stopKey)]);
6527
+ }, [stopKey, rowsBody, ownKey]);
6528
+ const treeShown = !!nested && !isMobile;
6529
+ useLayoutEffect(() => {
6530
+ const body = rowsBody;
6531
+ if (!treeShown || !body) return;
6532
+ const ours = (el) => el.closest("table[role='treegrid']") === body.parentElement;
6533
+ const release = (el) => {
6534
+ const own = held.get(el) ?? "";
6535
+ held.delete(el);
6536
+ if (own === "") el.removeAttribute("tabindex");
6537
+ else el.setAttribute("tabindex", own);
6538
+ };
6539
+ const settle = (tr) => {
6540
+ const grid2 = tr.getAttribute("data-grid-key");
6541
+ const owner = grid2?.startsWith(ownKey) ? grid2.slice(ownKey.length) : tr.getAttribute("data-panel-of");
6542
+ if (owner === null) return;
6543
+ if (owner === stopRef.current) {
6544
+ for (const el of Array.from(tr.querySelectorAll("[tabindex]"))) if (held.has(el)) release(el);
6545
+ return;
6546
+ }
6547
+ for (const el of Array.from(tr.querySelectorAll(FOCUSABLE))) {
6548
+ if (!ours(el)) continue;
6549
+ if (!held.has(el) || el.getAttribute("tabindex") !== "-1") held.set(el, el.getAttribute("tabindex") ?? "");
6550
+ if (el.getAttribute("tabindex") !== "-1") el.setAttribute("tabindex", "-1");
6551
+ }
6552
+ };
6553
+ const touched = (records) => {
6554
+ const rows = /* @__PURE__ */ new Set();
6555
+ for (const r of records) {
6556
+ const target = r.target instanceof Element ? r.target : r.target.parentElement;
6557
+ if (!target) continue;
6558
+ if (r.type === "attributes" && r.attributeName === "tabindex" && target instanceof HTMLElement && held.has(target)) held.set(target, target.getAttribute("tabindex") ?? "");
6559
+ if (target === body) {
6560
+ r.addedNodes.forEach((n) => {
6561
+ if (n instanceof Element) rows.add(n);
6562
+ });
6563
+ continue;
6564
+ }
6565
+ if (target.parentElement === body) {
6566
+ if (r.type === "childList") rows.add(target);
6567
+ continue;
6568
+ }
6569
+ let tr = target.closest("tr");
6570
+ while (tr && tr.parentElement !== body) tr = tr.parentElement?.closest("tr") ?? null;
6571
+ if (tr) rows.add(tr);
6572
+ }
6573
+ return rows;
6574
+ };
6575
+ const sync = (also = []) => {
6576
+ const rows = touched(observer.takeRecords());
6577
+ for (const tr of also) rows.add(tr);
6578
+ for (const tr of rows) settle(tr);
6579
+ observer.takeRecords();
6580
+ };
6581
+ const observer = new MutationObserver((records) => {
6582
+ for (const tr of touched(records)) settle(tr);
6583
+ observer.takeRecords();
6584
+ });
6585
+ observer.observe(body, { childList: true, subtree: true, attributes: true, attributeFilter: ["tabindex", "disabled"] });
6586
+ holding.current = sync;
6587
+ sync(Array.from(body.rows));
6588
+ return () => {
6589
+ observer.disconnect();
6590
+ holding.current = null;
6591
+ for (const el of Array.from(body.querySelectorAll("[tabindex]"))) if (held.has(el)) release(el);
6592
+ };
6593
+ }, [treeShown, rowsBody, held, ownKey]);
6594
+ const focusIn = useRef(null);
6595
+ useLayoutEffect(() => {
6596
+ const was = focusIn.current;
6597
+ if (!nested || was === null || visibleSet.has(was)) return;
6598
+ focusIn.current = null;
6599
+ const now = document.activeElement;
6600
+ if (now && now !== document.body && now.isConnected) return;
6601
+ grid.focus(activeKey);
6602
+ });
6603
+ const onRowsBlur = (e) => {
6604
+ if (e.target.isConnected && !e.currentTarget.contains(e.relatedTarget)) focusIn.current = null;
6605
+ };
6606
+ const onRowFocus = (e) => {
6607
+ const key = fromGrid(e.currentTarget.getAttribute("data-grid-key")) ?? e.currentTarget.getAttribute("data-panel-of");
6608
+ if (key === null) return;
6609
+ focusIn.current = key;
6610
+ setFocusKey(key);
6611
+ };
6066
6612
  const shownBefore = useRef(/* @__PURE__ */ new Set());
6067
6613
  const pendingScroll = useRef(null);
6068
6614
  const scrollFrame = useRef(0);
@@ -6076,6 +6622,32 @@ function Table({
6076
6622
  row.scrollIntoView({ block: "nearest" });
6077
6623
  });
6078
6624
  }, [rootRef]);
6625
+ const addressRow = (key) => {
6626
+ let top = key;
6627
+ let waits = false;
6628
+ const above = [];
6629
+ for (let p = nested?.byKey.get(key)?.parent; p; p = p.parent) above.unshift(p);
6630
+ if (above.length) {
6631
+ top = String(above[0].id);
6632
+ const shut = above.filter((p) => !openSet.has(String(p.id)));
6633
+ if (shut.length && openControlled) {
6634
+ onOpenParentsChange?.([...open, ...shut.map((p) => p.id)]);
6635
+ pendingScroll.current = key;
6636
+ waits = true;
6637
+ }
6638
+ }
6639
+ if (pager) {
6640
+ const position = sortedAll.findIndex((r) => String(idOf(r.row, r.originalIdx)) === top);
6641
+ if (position >= 0 && (position < drawnStart || position >= drawnStart + drawn.length)) {
6642
+ pendingScroll.current = key;
6643
+ pager.setPage(Math.floor(position / pager.pageSize) + 1);
6644
+ return;
6645
+ }
6646
+ }
6647
+ if (waits) return;
6648
+ scrollToRow(key);
6649
+ };
6650
+ const awaitedRow = useRef(null);
6079
6651
  useEffect(() => {
6080
6652
  const now = new Set(expanded.map(String));
6081
6653
  const added = [...now].filter((k) => !shownBefore.current.has(k) && k !== openedHere.current);
@@ -6084,22 +6656,25 @@ function Table({
6084
6656
  if (!expandControlled) return;
6085
6657
  const key = added[added.length - 1];
6086
6658
  if (key === void 0) return;
6087
- if (pager) {
6088
- const position = sortedAll.findIndex((r) => String(idOf(r.row, r.originalIdx)) === key);
6089
- if (position >= 0 && (position < drawnStart || position >= drawnStart + drawn.length)) {
6090
- pendingScroll.current = key;
6091
- pager.setPage(Math.floor(position / pager.pageSize) + 1);
6092
- return;
6093
- }
6659
+ if (tree && !tree.byKey.has(key)) {
6660
+ awaitedRow.current = key;
6661
+ return;
6094
6662
  }
6095
- scrollToRow(key);
6663
+ awaitedRow.current = null;
6664
+ addressRow(key);
6096
6665
  }, [expandedKey]);
6666
+ useEffect(() => {
6667
+ const key = awaitedRow.current;
6668
+ if (key === null || !tree?.byKey.has(key)) return;
6669
+ awaitedRow.current = null;
6670
+ addressRow(key);
6671
+ }, [tree]);
6097
6672
  useEffect(() => {
6098
6673
  const key = pendingScroll.current;
6099
- if (!key || !drawn.some((r) => String(idOf(r.row, r.originalIdx)) === key)) return;
6674
+ if (!key || !(nested ? visibleSet.has(key) : drawn.some((r) => String(idOf(r.row, r.originalIdx)) === key))) return;
6100
6675
  pendingScroll.current = null;
6101
6676
  scrollToRow(key);
6102
- }, [drawnStart, drawn.length, scrollToRow]);
6677
+ }, [drawnStart, drawn.length, visibleKeys.length, scrollToRow]);
6103
6678
  const cardHeading = title ? /* @__PURE__ */ jsx(TableHeading, { title, description, actions, titleId, descriptionId, inset: false }) : null;
6104
6679
  const titledCards = (body) => /* @__PURE__ */ jsxs("div", { ref: listRoot, "data-ds-list": "", "data-gs": "table", className: cn("flex flex-col", listClass), onFocus: noteFocus, onBlur: forgetFocus, children: [
6105
6680
  cardHeading,
@@ -6143,7 +6718,7 @@ function Table({
6143
6718
  if (!row) return;
6144
6719
  const controls = Array.from(row.querySelectorAll(FOCUSABLE));
6145
6720
  const same = was.control === null ? null : controls.find((el) => (el.getAttribute("aria-labelledby") ?? el.getAttribute("aria-label") ?? el.textContent) === was.control);
6146
- (same ?? (row.tabIndex >= 0 ? row : controls[0]))?.focus({ preventScroll: true });
6721
+ (same ?? (row.tabIndex >= 0 || row.hasAttribute("data-grid-key") ? row : controls[0]))?.focus({ preventScroll: true });
6147
6722
  }, [isMobile, rootRef]);
6148
6723
  if (isMobile) {
6149
6724
  if (loading && data.length === 0) {
@@ -6164,289 +6739,363 @@ function Table({
6164
6739
  const cards = /* @__PURE__ */ jsx(TableCardCtx, { value: true, children: /* @__PURE__ */ jsx(RowsCtx, { value: true, children: /* @__PURE__ */ jsxs("div", { ref: setCards, "data-ds-list": title ? void 0 : "", "data-gs": title ? void 0 : "table", className: cn("relative flex flex-col gap-ds-3 overflow-y-auto -m-[4px] p-[4px] scroll-p-[4px]", FOCUS_INSET, title ? "min-h-0" : listClass), onFocus: title ? void 0 : noteFocus, onBlur: title ? void 0 : forgetFocus, children: [
6165
6740
  loading && data.length > 0 && /* @__PURE__ */ jsx(RefreshLine, { className: "inset-x-[4px] rounded-full" }),
6166
6741
  selectable && /* @__PURE__ */ jsx("span", { hidden: true, id: `${headingId}-select`, children: "Select" }),
6167
- drawn.map(({ row, originalIdx }) => {
6168
- const isSelected = selectedSet.has(originalIdx);
6169
- const id = idOf(row, originalIdx);
6170
- const opens = !!expandable && !!renderExpanded;
6171
- const isExpanded = opens && expandedSet.has(String(id));
6172
- const toggleCard = (card) => {
6173
- const closing = isExpanded;
6174
- const inDetail = closing && card.querySelector("[data-card-detail]")?.contains(document.activeElement);
6175
- toggleExpand(id);
6176
- if (inDetail) card.querySelector("[data-card-toggle]")?.focus();
6177
- };
6178
- return /* @__PURE__ */ jsxs(
6179
- "div",
6180
- {
6181
- "data-row": "",
6182
- ...marksOf(row, originalIdx),
6183
- className: cn(
6184
- "border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
6185
- "rounded-[var(--ds-radius-md)] bg-[var(--ds-bg-surface)]",
6186
- "p-ds-4 space-y-ds-3",
6187
- // selected is chosen, not active (D2): the accent border, never the halo
6188
- isSelected && "border-[var(--ds-accent-ink)]",
6189
- onRowClick && ["cursor-pointer hover:bg-[var(--ds-bg-muted)]", FOCUS_RING],
6190
- // with no onRowClick a tap on the card opens and closes it, as a click on the row does; the toggle is the
6191
- // keyboard's way
6192
- !onRowClick && canOpenByClick && "cursor-pointer"
6193
- ),
6194
- onClick: onRowClick ? (e) => {
6195
- if (clickIsCard(e)) onRowClick(row, originalIdx);
6196
- } : canOpenByClick ? (e) => {
6197
- if (clickIsCard(e)) toggleCard(e.currentTarget);
6198
- } : void 0,
6199
- tabIndex: onRowClick ? 0 : void 0,
6200
- onKeyDown: onRowClick ? (e) => {
6201
- if (e.target !== e.currentTarget) return;
6202
- if (e.key === "Enter" || e.key === " ") {
6203
- e.preventDefault();
6204
- onRowClick(row, originalIdx);
6742
+ nested && /* @__PURE__ */ jsx("span", { hidden: true, id: `${headingId}-under`, children: "under" }),
6743
+ nested && /* @__PURE__ */ jsx("span", { hidden: true, id: `${headingId}-rows-under`, children: "Rows under" }),
6744
+ nested ? drawn.map((d) => treeCards(nested.roots[d.originalIdx])) : drawn.map(({ row, originalIdx }) => cardOf(row, originalIdx))
6745
+ ] }) }) });
6746
+ return withControl(title ? titledCards(cards) : cards);
6747
+ }
6748
+ function cardOf(row, originalIdx, node) {
6749
+ const id = node ? node.id : idOf(row, originalIdx);
6750
+ const box = boxOf(originalIdx, id);
6751
+ const isSelected = !!box?.checked;
6752
+ const opens = !!expandable && !!renderExpanded;
6753
+ const isExpanded = opens && expandedSet.has(String(id));
6754
+ const toggleCard = (card) => {
6755
+ const closing = isExpanded;
6756
+ const inDetail = closing && card.querySelector("[data-card-detail]")?.contains(document.activeElement);
6757
+ toggleExpand(id);
6758
+ if (inDetail) card.querySelector("[data-card-toggle]")?.focus();
6759
+ };
6760
+ const under = node?.children.length ?? 0;
6761
+ const isOpen = under > 0 && openSet.has(String(id));
6762
+ return /* @__PURE__ */ jsxs(
6763
+ "div",
6764
+ {
6765
+ "data-row": "",
6766
+ ...marksOf(row, originalIdx, id),
6767
+ className: cn(
6768
+ "border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
6769
+ "rounded-[var(--ds-radius-md)] bg-[var(--ds-bg-surface)]",
6770
+ "p-ds-4 space-y-ds-3",
6771
+ // selected is chosen, not active (D2): the accent border, never the halo
6772
+ isSelected && "border-[var(--ds-accent-ink)]",
6773
+ onRowClick && ["cursor-pointer hover:bg-[var(--ds-bg-muted)]", FOCUS_RING],
6774
+ // with no onRowClick a tap on the card opens and closes it, as a click on the row does; the toggle is the
6775
+ // keyboard's way
6776
+ !onRowClick && canOpenByClick && "cursor-pointer"
6777
+ ),
6778
+ onClick: onRowClick ? (e) => {
6779
+ if (clickIsCard(e)) onRowClick(row, originalIdx);
6780
+ } : canOpenByClick ? (e) => {
6781
+ if (clickIsCard(e)) toggleCard(e.currentTarget);
6782
+ } : void 0,
6783
+ tabIndex: onRowClick ? 0 : void 0,
6784
+ onKeyDown: onRowClick ? (e) => {
6785
+ if (e.target !== e.currentTarget) return;
6786
+ if (e.key === "Enter" || e.key === " ") {
6787
+ e.preventDefault();
6788
+ onRowClick(row, originalIdx);
6789
+ }
6790
+ } : void 0,
6791
+ children: [
6792
+ selectable && box && // "Select" and the row's name, as in the table (T15); a parent with some of its rows chosen is mixed
6793
+ /* @__PURE__ */ jsx(Checkbox, { checked: isSelected, indeterminate: box.mixed, onChange: (_, { shiftKey: shiftKey2 }) => toggleBox(originalIdx, id, shiftKey2), "aria-labelledby": nameIndex >= 0 ? `${headingId}-select ${headingId}-card-name-${idPart(id)}` : `${headingId}-select` }),
6794
+ columns.map((col, c) => /* @__PURE__ */ jsxs("div", { className: "flex justify-between gap-ds-2 text-sm", children: [
6795
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[var(--ds-text-secondary)] font-medium text-xs uppercase tracking-wider", children: col.header }),
6796
+ /* @__PURE__ */ jsx(CardValue, { value: row[col.key], id: lead + c === nameIndex ? `${headingId}-card-name-${idPart(id)}` : void 0 })
6797
+ ] }, col.key)),
6798
+ under > 0 && // item 14: the rows under it, shown and hidden as the Details are, with how many and the row's name
6799
+ /* @__PURE__ */ jsx("div", { className: "pt-ds-1", children: /* @__PURE__ */ jsxs(
6800
+ "button",
6801
+ {
6802
+ type: "button",
6803
+ "data-card-rows-toggle": "",
6804
+ "aria-expanded": isOpen,
6805
+ "aria-labelledby": nameIndex >= 0 ? `${headingId}-card-rows-${idPart(id)} ${headingId}-under ${headingId}-card-name-${idPart(id)}` : `${headingId}-card-rows-${idPart(id)}`,
6806
+ onClick: (e) => {
6807
+ e.stopPropagation();
6808
+ toggleOpen(id);
6809
+ },
6810
+ className: CARD_TOGGLE,
6811
+ children: [
6812
+ /* @__PURE__ */ jsx(Icon, { name: "chevron-right", size: 12, className: cn("transition-transform duration-[var(--ds-transition-speed)]", isOpen && "rotate-90") }),
6813
+ /* @__PURE__ */ jsx("span", { id: `${headingId}-card-rows-${idPart(id)}`, children: `${isOpen ? "Hide" : "Show"} ${under} ${under === 1 ? "row" : "rows"}` })
6814
+ ]
6815
+ }
6816
+ ) }),
6817
+ opens && // Alert's disclosure: a button that says whether it is open, at the card's foot, the detail below it, so
6818
+ // the control the user pressed stays where it was. Its name carries the row's (T15).
6819
+ /* @__PURE__ */ jsxs("div", { className: "pt-ds-1", children: [
6820
+ /* @__PURE__ */ jsxs(
6821
+ "button",
6822
+ {
6823
+ type: "button",
6824
+ "data-card-toggle": "",
6825
+ "aria-expanded": isExpanded,
6826
+ ...nameIndex >= 0 ? { "aria-labelledby": `${headingId}-card-toggle-${idPart(id)} ${headingId}-card-name-${idPart(id)}` } : {},
6827
+ onClick: (e) => {
6828
+ e.stopPropagation();
6829
+ toggleCard(e.currentTarget.closest("[data-row]"));
6830
+ },
6831
+ className: CARD_TOGGLE,
6832
+ children: [
6833
+ /* @__PURE__ */ jsx(Icon, { name: "chevron-right", size: 12, className: cn("transition-transform duration-[var(--ds-transition-speed)]", isExpanded && "rotate-90") }),
6834
+ /* @__PURE__ */ jsx("span", { id: `${headingId}-card-toggle-${idPart(id)}`, children: isExpanded ? "Hide details" : "Show details" })
6835
+ ]
6205
6836
  }
6206
- } : void 0,
6207
- children: [
6208
- selectable && // "Select" and the row's name, as in the table (T15)
6209
- /* @__PURE__ */ jsx(Checkbox, { checked: isSelected, onChange: (_, { shiftKey: shiftKey2 }) => toggleRow(originalIdx, shiftKey2), "aria-labelledby": nameIndex >= 0 ? `${headingId}-select ${headingId}-card-name-${idPart(id)}` : `${headingId}-select` }),
6210
- columns.map((col, c) => /* @__PURE__ */ jsxs("div", { className: "flex justify-between gap-ds-2 text-sm", children: [
6211
- /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[var(--ds-text-secondary)] font-medium text-xs uppercase tracking-wider", children: col.header }),
6212
- /* @__PURE__ */ jsx(CardValue, { value: row[col.key], id: lead + c === nameIndex ? `${headingId}-card-name-${idPart(id)}` : void 0 })
6213
- ] }, col.key)),
6214
- opens && // Alert's disclosure: a button that says whether it is open, at the card's foot, the detail below it, so
6215
- // the control the user pressed stays where it was. Its name carries the row's (T15).
6216
- /* @__PURE__ */ jsxs("div", { className: "pt-ds-1", children: [
6837
+ ),
6838
+ isExpanded && /* @__PURE__ */ jsx("div", { "data-card-detail": "", className: "mt-ds-3 pt-ds-3 border-t-[length:var(--ds-divider-width)] border-[var(--ds-border-subtle)] text-sm text-[var(--ds-text-secondary)]", children: /* @__PURE__ */ jsx(PanelContent, { children: renderExpanded(row, originalIdx) }) })
6839
+ ] })
6840
+ ]
6841
+ },
6842
+ String(id)
6843
+ );
6844
+ }
6845
+ function treeCards(node) {
6846
+ const key = String(node.id);
6847
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [
6848
+ cardOf(node.row, node.originalIdx, node),
6849
+ node.children.length > 0 && openSet.has(key) && /* @__PURE__ */ jsx(
6850
+ "div",
6851
+ {
6852
+ role: "group",
6853
+ "data-card-rows": "",
6854
+ "aria-labelledby": nameIndex >= 0 ? `${headingId}-rows-under ${headingId}-card-name-${idPart(node.id)}` : `${headingId}-rows-under`,
6855
+ className: "ml-ds-5 pl-ds-3 border-l-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)] flex flex-col gap-ds-3",
6856
+ children: (childOrder?.get(key) ?? node.children).map(treeCards)
6857
+ }
6858
+ )
6859
+ ] }, key);
6860
+ }
6861
+ const cellCount = columns.length + lead + trail;
6862
+ const bodyRows = visible ? visible.map((v) => ({ row: v.node.row, originalIdx: v.node.originalIdx, tree: v })) : drawn;
6863
+ const table = (
6864
+ // a tree table is a treegrid (item 14): its rows have levels, and the arrow keys move between them
6865
+ /* @__PURE__ */ jsxs("table", { ref: tableRef, role: nested ? "treegrid" : void 0, "aria-busy": loading || void 0, className: cn("w-full text-sm", PINNED_LAYERS), ...title ? { "aria-labelledby": titleId, "aria-describedby": description ? descriptionId : void 0 } : ariaLabel ? { "aria-label": ariaLabel } : {}, children: [
6866
+ /* @__PURE__ */ jsx("thead", { className: cn("bg-[var(--ds-bg-muted)]", stickyHeader && "sticky top-0 z-10"), onKeyDown: nested ? onHeadKeyDown : void 0, onFocus: nested ? onHeadFocus : void 0, children: /* @__PURE__ */ jsxs("tr", { className: "border-b-[length:var(--ds-divider-width)] border-[var(--ds-border-subtle)] [--gs-row-tint:var(--ds-bg-muted)]", children: [
6867
+ selectable && // data-ds-select-all: the selection's own control, where a BulkActionBar's Clear selection sends focus
6868
+ /* @__PURE__ */ jsx("th", { "data-ds-select-all": "", className: cn("w-10", density === "compact" ? "px-ds-2 py-ds-2" : "px-ds-3 py-ds-3"), style: pinStyle(0, cellCount), children: /* @__PURE__ */ jsx(
6869
+ Checkbox,
6870
+ {
6871
+ ref: selectAllRef,
6872
+ checked: allSelected,
6873
+ indeterminate: someSelected,
6874
+ onChange: toggleAll,
6875
+ "aria-labelledby": `${headingId}-select-all`
6876
+ }
6877
+ ) }),
6878
+ expandable && !trail && /* @__PURE__ */ jsx("th", { className: cn("w-8", density === "compact" ? "px-ds-1 py-ds-2" : "px-ds-2 py-ds-3"), style: pinStyle(selectable ? 1 : 0, cellCount) }),
6879
+ columns.map((col, c) => {
6880
+ const isSortable = sortsBy(col);
6881
+ const isActiveSort = sortKey === col.key && !!sortDir && (pageSorts || unsortable !== col.key);
6882
+ const headerKind = kindOf2(col);
6883
+ const headerAlign = col.align ?? (headerKind === "number" ? "right" : "left");
6884
+ const shrink = (headerKind === "number" || headerKind === "date") && !col.maxWidth;
6885
+ const style = { maxWidth: col.maxWidth, width: col.width, minWidth: col.minWidth, ...pinStyle(lead + c, cellCount) };
6886
+ return /* @__PURE__ */ jsx(
6887
+ "th",
6888
+ {
6889
+ "aria-sort": isSortable && isActiveSort ? sortDir === "asc" ? "ascending" : "descending" : void 0,
6890
+ className: cn(
6891
+ density === "compact" ? "px-ds-3 py-ds-2" : "px-ds-4 py-ds-3",
6892
+ shrink && "w-px whitespace-nowrap",
6893
+ "font-[var(--ds-weight-heading)] text-[var(--ds-text-secondary)]",
6894
+ "text-xs uppercase tracking-wider",
6895
+ headerAlign === "right" ? "text-right" : headerAlign === "center" ? "text-center" : "text-left"
6896
+ ),
6897
+ style,
6898
+ children: isSortable ? (
6899
+ // A47: a button with the column's name, reached with Tab and pressed with Enter or Space
6217
6900
  /* @__PURE__ */ jsxs(
6218
6901
  "button",
6219
6902
  {
6220
6903
  type: "button",
6221
- "data-card-toggle": "",
6222
- "aria-expanded": isExpanded,
6223
- ...nameIndex >= 0 ? { "aria-labelledby": `${headingId}-card-toggle-${idPart(id)} ${headingId}-card-name-${idPart(id)}` } : {},
6224
- onClick: (e) => {
6225
- e.stopPropagation();
6226
- toggleCard(e.currentTarget.closest("[data-row]"));
6227
- },
6904
+ onClick: () => handleSort(col.key),
6905
+ ...nested ? { "data-grid-key": gridKey(`${HEAD_KEY}${col.key}`), tabIndex: heldOuter ? -1 : grid.tabIndexOf(`${HEAD_KEY}${col.key}`) } : {},
6228
6906
  className: cn(
6229
- // at least 24 px tall: the card around it is a target too, so its own box must meet the size (WCAG 2.5.8)
6230
- "inline-flex min-h-[24px] items-center gap-ds-1 text-xs font-medium cursor-pointer",
6231
- "transition-colors duration-[var(--ds-transition-speed)]",
6232
- "text-[var(--ds-accent-ink)] hover:bg-[var(--ds-accent-muted)]",
6233
- "rounded-[var(--ds-radius-sm)] px-ds-1.5 py-ds-0.5 -mx-ds-1.5",
6907
+ "inline-flex items-center gap-ds-1 uppercase tracking-wider cursor-pointer select-none",
6908
+ "[font:inherit] text-inherit hover:text-[var(--ds-text-primary)]",
6909
+ "rounded-[var(--ds-radius-sm)]",
6910
+ // in a right-aligned column the indicator goes before the label, so the label ends where the
6911
+ // column's values end; the sorted column's label in full colour (the look check, 14 October).
6912
+ // Set at the line's top, not its baseline: reversed, the button takes its baseline from the
6913
+ // indicator, which has no text, and stood 2 px above the other headers' labels
6914
+ headerAlign === "right" && "flex-row-reverse",
6915
+ "align-top",
6916
+ isActiveSort && "text-[var(--ds-text-primary)]",
6234
6917
  FOCUS_RING
6235
6918
  ),
6236
6919
  children: [
6237
- /* @__PURE__ */ jsx(Icon, { name: "chevron-right", size: 12, className: cn("transition-transform duration-[var(--ds-transition-speed)]", isExpanded && "rotate-90") }),
6238
- /* @__PURE__ */ jsx("span", { id: `${headingId}-card-toggle-${idPart(id)}`, children: isExpanded ? "Hide details" : "Show details" })
6920
+ col.header,
6921
+ /* @__PURE__ */ jsx(SortIndicator, { sorted: isActiveSort ? sortDir : false })
6239
6922
  ]
6240
6923
  }
6241
- ),
6242
- isExpanded && /* @__PURE__ */ jsx("div", { "data-card-detail": "", className: "mt-ds-3 pt-ds-3 border-t-[length:var(--ds-divider-width)] border-[var(--ds-border-subtle)] text-sm text-[var(--ds-text-secondary)]", children: /* @__PURE__ */ jsx(PanelContent, { children: renderExpanded(row, originalIdx) }) })
6243
- ] })
6244
- ]
6245
- },
6246
- String(id)
6247
- );
6248
- })
6249
- ] }) }) });
6250
- return withControl(title ? titledCards(cards) : cards);
6251
- }
6252
- const cellCount = columns.length + lead;
6253
- const table = /* @__PURE__ */ jsxs("table", { ref: tableRef, "aria-busy": loading || void 0, className: cn("w-full text-sm", PINNED_LAYERS), ...title ? { "aria-labelledby": titleId, "aria-describedby": description ? descriptionId : void 0 } : ariaLabel ? { "aria-label": ariaLabel } : {}, children: [
6254
- /* @__PURE__ */ jsx("thead", { className: cn("bg-[var(--ds-bg-muted)]", stickyHeader && "sticky top-0 z-10"), children: /* @__PURE__ */ jsxs("tr", { className: "border-b-[length:var(--ds-divider-width)] border-[var(--ds-border-subtle)] [--gs-row-tint:var(--ds-bg-muted)]", children: [
6255
- selectable && // data-ds-select-all: the selection's own control, where a BulkActionBar's Clear selection sends focus
6256
- /* @__PURE__ */ jsx("th", { "data-ds-select-all": "", className: cn("w-10", density === "compact" ? "px-ds-2 py-ds-2" : "px-ds-3 py-ds-3"), style: pinStyle(0, cellCount), children: /* @__PURE__ */ jsx(
6257
- Checkbox,
6924
+ )
6925
+ ) : /* @__PURE__ */ jsx("span", { className: "inline-flex items-center gap-ds-1", children: col.header })
6926
+ },
6927
+ col.key
6928
+ );
6929
+ }),
6930
+ trail > 0 && /* @__PURE__ */ jsx("th", { className: cn("w-8", density === "compact" ? "px-ds-1 py-ds-2" : "px-ds-2 py-ds-3"), style: pinStyle(cellCount - 1, cellCount) })
6931
+ ] }) }),
6932
+ /* @__PURE__ */ jsx("tbody", { ref: nested ? setRowsBody : void 0, onKeyDown: nested ? onTreeKeyDown : void 0, onBlur: nested ? onRowsBlur : void 0, children: /* @__PURE__ */ jsx(RowsCtx, { value: true, children: loading && data.length === 0 ? (
6933
+ // the rows' shape on a first load (item 16), each cell's bar where its text will be; the rows are hidden
6934
+ // from a screen reader, which hears the status beside the table
6935
+ Array.from({ length: SKELETON_ROWS }, (_, r) => /* @__PURE__ */ jsxs("tr", { "data-table-skeleton": "", className: "border-b-[length:var(--ds-divider-width)] border-[var(--ds-border-subtle)] last:border-b-0", children: [
6936
+ selectable && /* @__PURE__ */ jsx("td", { "aria-hidden": true, className: cn("w-10", density === "compact" ? "px-ds-2 py-ds-2" : "px-ds-3 py-ds-3"), children: /* @__PURE__ */ jsx(SkeletonLine, { width: "16px" }) }),
6937
+ expandable && /* @__PURE__ */ jsx("td", { "aria-hidden": true, className: cn("w-8", density === "compact" ? "px-ds-1 py-ds-2" : "px-ds-2 py-ds-3") }),
6938
+ columns.map((col, c) => /* @__PURE__ */ jsx("td", { className: density === "compact" ? "px-ds-3 py-ds-2" : "px-ds-4 py-ds-3", children: /* @__PURE__ */ jsx("div", { "aria-hidden": true, children: /* @__PURE__ */ jsx(SkeletonLine, { width: SKELETON_WIDTHS[(r + c) % SKELETON_WIDTHS.length], end: (col.align ?? (kindOf2(col) === "number" ? "right" : "left")) === "right" }) }) }, col.key))
6939
+ ] }, `skeleton-${r}`))
6940
+ ) : data.length === 0 && emptyState ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx(
6941
+ "td",
6258
6942
  {
6259
- checked: allSelected,
6260
- indeterminate: someSelected,
6261
- onChange: toggleAll,
6262
- "aria-labelledby": `${headingId}-select-all`
6943
+ colSpan: cellCount,
6944
+ className: "px-ds-4 py-ds-10 text-center text-sm text-[var(--ds-text-muted)]",
6945
+ children: emptyState
6263
6946
  }
6264
- ) }),
6265
- expandable && /* @__PURE__ */ jsx("th", { className: cn("w-8", density === "compact" ? "px-ds-1 py-ds-2" : "px-ds-2 py-ds-3"), style: pinStyle(selectable ? 1 : 0, cellCount) }),
6266
- columns.map((col, c) => {
6267
- const isSortable = col.header.trim() !== "" && (pageSorts ? col.sortable ?? (!!sortable && canSort.has(col.key)) : (col.sortable ?? !!sortable) && canSort.has(col.key));
6268
- const isActiveSort = sortKey === col.key && !!sortDir && (pageSorts || unsortable !== col.key);
6269
- const headerKind = kindOf2(col);
6270
- const headerAlign = col.align ?? (headerKind === "number" ? "right" : "left");
6271
- const shrink = (headerKind === "number" || headerKind === "date") && !col.maxWidth;
6272
- const style = { maxWidth: col.maxWidth, width: col.width, minWidth: col.minWidth, ...pinStyle(lead + c, cellCount) };
6273
- return /* @__PURE__ */ jsx(
6274
- "th",
6275
- {
6276
- "aria-sort": isSortable && isActiveSort ? sortDir === "asc" ? "ascending" : "descending" : void 0,
6277
- className: cn(
6278
- density === "compact" ? "px-ds-3 py-ds-2" : "px-ds-4 py-ds-3",
6279
- shrink && "w-px whitespace-nowrap",
6280
- "font-[var(--ds-weight-heading)] text-[var(--ds-text-secondary)]",
6281
- "text-xs uppercase tracking-wider",
6282
- headerAlign === "right" ? "text-right" : headerAlign === "center" ? "text-center" : "text-left"
6283
- ),
6284
- style,
6285
- children: isSortable ? (
6286
- // A47: a button with the column's name, reached with Tab and pressed with Enter or Space
6287
- /* @__PURE__ */ jsxs(
6288
- "button",
6289
- {
6290
- type: "button",
6291
- onClick: () => handleSort(col.key),
6292
- className: cn(
6293
- "inline-flex items-center gap-ds-1 uppercase tracking-wider cursor-pointer select-none",
6294
- "[font:inherit] text-inherit hover:text-[var(--ds-text-primary)]",
6295
- "rounded-[var(--ds-radius-sm)]",
6296
- // in a right-aligned column the indicator goes before the label, so the label ends where the
6297
- // column's values end; the sorted column's label in full colour (the look check, 14 October).
6298
- // Set at the line's top, not its baseline: reversed, the button takes its baseline from the
6299
- // indicator, which has no text, and stood 2 px above the other headers' labels
6300
- headerAlign === "right" && "flex-row-reverse",
6301
- "align-top",
6302
- isActiveSort && "text-[var(--ds-text-primary)]",
6303
- FOCUS_RING
6304
- ),
6305
- children: [
6306
- col.header,
6307
- /* @__PURE__ */ jsx(SortIndicator, { sorted: isActiveSort ? sortDir : false })
6308
- ]
6309
- }
6310
- )
6311
- ) : /* @__PURE__ */ jsx("span", { className: "inline-flex items-center gap-ds-1", children: col.header })
6312
- },
6313
- col.key
6314
- );
6315
- })
6316
- ] }) }),
6317
- /* @__PURE__ */ jsx("tbody", { children: /* @__PURE__ */ jsx(RowsCtx, { value: true, children: loading && data.length === 0 ? (
6318
- // the rows' shape on a first load (item 16), each cell's bar where its text will be; the rows are hidden
6319
- // from a screen reader, which hears the status beside the table
6320
- Array.from({ length: SKELETON_ROWS }, (_, r) => /* @__PURE__ */ jsxs("tr", { "data-table-skeleton": "", className: "border-b-[length:var(--ds-divider-width)] border-[var(--ds-border-subtle)] last:border-b-0", children: [
6321
- selectable && /* @__PURE__ */ jsx("td", { "aria-hidden": true, className: cn("w-10", density === "compact" ? "px-ds-2 py-ds-2" : "px-ds-3 py-ds-3"), children: /* @__PURE__ */ jsx(SkeletonLine, { width: "16px" }) }),
6322
- expandable && /* @__PURE__ */ jsx("td", { "aria-hidden": true, className: cn("w-8", density === "compact" ? "px-ds-1 py-ds-2" : "px-ds-2 py-ds-3") }),
6323
- columns.map((col, c) => /* @__PURE__ */ jsx("td", { className: density === "compact" ? "px-ds-3 py-ds-2" : "px-ds-4 py-ds-3", children: /* @__PURE__ */ jsx("div", { "aria-hidden": true, children: /* @__PURE__ */ jsx(SkeletonLine, { width: SKELETON_WIDTHS[(r + c) % SKELETON_WIDTHS.length], end: (col.align ?? (kindOf2(col) === "number" ? "right" : "left")) === "right" }) }) }, col.key))
6324
- ] }, `skeleton-${r}`))
6325
- ) : data.length === 0 && emptyState ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx(
6326
- "td",
6327
- {
6328
- colSpan: cellCount,
6329
- className: "px-ds-4 py-ds-10 text-center text-sm text-[var(--ds-text-muted)]",
6330
- children: emptyState
6331
- }
6332
- ) }) : drawn.map(({ row, originalIdx }, i) => {
6333
- const isSelected = selectedSet.has(originalIdx);
6334
- const id = idOf(row, originalIdx);
6335
- const isExpanded = expandedSet.has(String(id));
6336
- const clickable = !!onRowClick || canOpenByClick;
6337
- return /* @__PURE__ */ jsxs(Fragment$1, { children: [
6338
- /* @__PURE__ */ jsxs(
6339
- "tr",
6340
- {
6341
- ...marksOf(row, originalIdx),
6342
- className: cn(
6343
- // an opened row's divider is under its panel (A6)
6344
- !(expandable && isExpanded && renderExpanded) && "border-b-[length:var(--ds-divider-width)] border-[var(--ds-border-subtle)] last:border-b-0",
6345
- // the row's background and its tint change together, so its pinned cells and the rest fade as one
6346
- // (roots.css registers --gs-row-tint as a colour so it can)
6347
- "transition-[background-color,--gs-row-tint]",
6348
- // the row's tint, as a variable too, so its pinned cells draw it over their solid ground (A35)
6349
- isSelected ? "bg-[var(--ds-accent-subtle)] [--gs-row-tint:var(--ds-accent-subtle)]" : striped && i % 2 === 1 ? "bg-[var(--ds-bg-muted)]/50 [--gs-row-tint:color-mix(in_oklab,var(--ds-bg-muted)_50%,transparent)]" : "hover:bg-[var(--ds-bg-muted)] hover:[--gs-row-tint:var(--ds-bg-muted)]",
6350
- clickable && "cursor-pointer",
6351
- // an outline inside the row, clear of the frame's rounded corners (D8): it shows over the pinned
6352
- // cells, whose solid ground hid the inset shadow the row drew before (the review of 30 September)
6353
- onRowClick && FOCUS_INSET
6354
- ),
6355
- onClick: clickable ? (e) => {
6356
- if (!clickIsRows(e)) return;
6357
- if (onRowClick) onRowClick(row, originalIdx);
6358
- else toggleExpand(id);
6359
- } : void 0,
6360
- tabIndex: onRowClick ? 0 : void 0,
6361
- onKeyDown: onRowClick ? (e) => {
6362
- if (e.target !== e.currentTarget) return;
6363
- if (e.key === "Enter" || e.key === " ") {
6364
- e.preventDefault();
6365
- onRowClick(row, originalIdx);
6366
- }
6367
- } : void 0,
6368
- children: [
6369
- selectable && /* @__PURE__ */ jsx("td", { className: cn("w-10", density === "compact" ? "px-ds-2 py-ds-2" : "px-ds-3 py-ds-3", pinFocus(0, cellCount)), style: pinStyle(0, cellCount), children: /* @__PURE__ */ jsx(Checkbox, { checked: isSelected, onChange: (_, { shiftKey: shiftKey2 }) => toggleRow(originalIdx, shiftKey2), "aria-labelledby": nameIndex >= 0 ? `${headingId}-select ${headingId}-name-${idPart(id)}` : `${headingId}-select` }) }),
6370
- expandable && /* @__PURE__ */ jsx("td", { className: cn("w-8", density === "compact" ? "px-ds-1 py-ds-2" : "px-ds-2 py-ds-3", pinFocus(selectable ? 1 : 0, cellCount)), style: pinStyle(selectable ? 1 : 0, cellCount), children: /* @__PURE__ */ jsx(AutoTooltip, { content: "Details", children: /* @__PURE__ */ jsx(
6371
- "button",
6372
- {
6373
- type: "button",
6374
- onClick: () => toggleExpand(id),
6375
- className: cn("text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] cursor-pointer", FOCUS_RING),
6376
- "aria-expanded": isExpanded,
6377
- ...nameIndex >= 0 ? { "aria-labelledby": `${headingId}-details ${headingId}-name-${idPart(id)}` } : { "aria-label": "Details" },
6378
- children: /* @__PURE__ */ jsx(
6379
- Icon,
6380
- {
6381
- name: "chevron-right",
6382
- size: 14,
6383
- className: cn("transition-transform", isExpanded && "rotate-90")
6384
- }
6385
- )
6386
- }
6387
- ) }) }),
6388
- columns.map((col, c) => {
6389
- const truncLines = col.truncate === true ? 1 : col.truncate ? col.truncate.lines : 0;
6390
- const kind = kindOf2(col);
6391
- const alignClass = col.align ? col.align === "right" ? "text-right" : col.align === "center" ? "text-center" : "text-left" : kind === "number" ? "text-right" : "text-left";
6392
- const cellContent = row[col.key];
6393
- const shrink = (kind === "number" || kind === "date") && !col.maxWidth;
6394
- const whole2 = !kind && renderedKinds.has(col.key);
6395
- let inner;
6396
- if (truncLines > 0) {
6397
- inner = /* @__PURE__ */ jsx(CutCell, { lines: truncLines, children: cellContent });
6398
- } else if (kind === "email") {
6399
- inner = /* @__PURE__ */ jsx(CutEmail, { children: cellContent });
6400
- } else {
6401
- inner = cellContent;
6402
- }
6403
- const align = col.align ?? (kind === "number" ? "right" : "left");
6404
- if (align !== "left" && cellContent !== null && cellContent !== void 0 && typeof cellContent !== "string" && typeof cellContent !== "number") {
6405
- inner = /* @__PURE__ */ jsx("div", { className: cn("flex items-center", align === "center" ? "justify-center" : "justify-end"), children: inner });
6406
- }
6407
- return /* @__PURE__ */ jsx(
6408
- "td",
6409
- {
6410
- id: lead > 0 && lead + c === nameIndex ? `${headingId}-name-${idPart(id)}` : void 0,
6411
- "data-col": col.key,
6412
- className: cn(
6413
- density === "compact" ? "px-ds-3 py-ds-2" : "px-ds-4 py-ds-3",
6414
- "text-[var(--ds-text-primary)]",
6415
- alignClass,
6416
- shrink && "w-px whitespace-nowrap",
6417
- whole2 && "whitespace-nowrap",
6418
- kind === "identifier" && "[overflow-wrap:anywhere]",
6419
- pinFocus(lead + c, cellCount)
6420
- ),
6421
- style: col.maxWidth || col.width || col.minWidth || c + lead < pinnedStart || c >= columns.length - endCount ? { maxWidth: col.maxWidth, width: col.width, minWidth: col.minWidth, ...pinStyle(lead + c, cellCount) } : void 0,
6422
- children: inner
6423
- },
6424
- col.key
6425
- );
6426
- })
6427
- ]
6428
- }
6429
- ),
6430
- expandable && isExpanded && renderExpanded && /* @__PURE__ */ jsx("tr", { className: "border-b-[length:var(--ds-divider-width)] border-[var(--ds-border-subtle)] last:border-b-0", children: /* @__PURE__ */ jsx(
6431
- "td",
6947
+ ) }) : bodyRows.map(({ row, originalIdx, tree: place }, i) => {
6948
+ const node = place?.node;
6949
+ const id = node ? node.id : idOf(row, originalIdx);
6950
+ const box = boxOf(originalIdx, id);
6951
+ const isSelected = !!box?.checked;
6952
+ const isExpanded = expandedSet.has(String(id));
6953
+ const clickable = !!onRowClick || canOpenByClick;
6954
+ const isParent = !!node?.children.length;
6955
+ const isOpen = isParent && openSet.has(String(id));
6956
+ const treeRow = place ? {
6957
+ "aria-level": place.node.level,
6958
+ "aria-setsize": place.size,
6959
+ "aria-posinset": place.pos,
6960
+ "aria-expanded": isParent ? isOpen : void 0,
6961
+ "data-grid-key": gridKey(String(id)),
6962
+ onFocus: onRowFocus
6963
+ } : {};
6964
+ const nameRef = node ? `${headingId}-tree-name-${idPart(id)}` : `${headingId}-name-${idPart(id)}`;
6965
+ const detailsAt = trail ? cellCount - 1 : selectable ? 1 : 0;
6966
+ const details = expandable && /* @__PURE__ */ jsx("td", { className: cn("w-8", density === "compact" ? "px-ds-1 py-ds-2" : "px-ds-2 py-ds-3", pinFocus(detailsAt, cellCount)), style: pinStyle(detailsAt, cellCount), children: /* @__PURE__ */ jsx(AutoTooltip, { content: "Details", children: /* @__PURE__ */ jsx(
6967
+ "button",
6432
6968
  {
6433
- colSpan: cellCount,
6434
- className: cn("py-ds-4 text-sm text-[var(--ds-text-primary)]", !pins.panel && (density === "compact" ? "px-ds-3" : "px-ds-4")),
6435
- style: pins.panel ? { paddingLeft: pins.panel[0], paddingRight: pins.panel[1] } : void 0,
6969
+ type: "button",
6970
+ onClick: () => toggleExpand(id),
6971
+ className: cn("text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] cursor-pointer", FOCUS_RING),
6972
+ "aria-expanded": isExpanded,
6973
+ ...nameIndex >= 0 ? { "aria-labelledby": `${headingId}-details ${nameRef}` } : { "aria-label": "Details" },
6436
6974
  children: /* @__PURE__ */ jsx(
6437
- "div",
6975
+ Icon,
6438
6976
  {
6439
- "data-row-panel": "",
6440
- className: "sticky",
6441
- style: pins.panel ? { left: pins.panel[0], maxWidth: pins.frame ? pins.frame - pins.panel[0] - pins.panel[1] : void 0 } : { left: 0 },
6442
- children: /* @__PURE__ */ jsx(PanelContent, { children: renderExpanded(row, originalIdx) })
6977
+ name: "chevron-right",
6978
+ size: 14,
6979
+ className: cn("transition-transform", isExpanded && "rotate-90")
6443
6980
  }
6444
6981
  )
6445
6982
  }
6446
- ) })
6447
- ] }, String(id));
6448
- }) }) })
6449
- ] });
6983
+ ) }) });
6984
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [
6985
+ /* @__PURE__ */ jsxs(
6986
+ "tr",
6987
+ {
6988
+ ...marksOf(row, originalIdx, id),
6989
+ ...treeRow,
6990
+ className: cn(
6991
+ // an opened row's divider is under its panel (A6)
6992
+ !(expandable && isExpanded && renderExpanded) && "border-b-[length:var(--ds-divider-width)] border-[var(--ds-border-subtle)] last:border-b-0",
6993
+ // the row's background and its tint change together, so its pinned cells and the rest fade as one
6994
+ // (roots.css registers --gs-row-tint as a colour so it can)
6995
+ "transition-[background-color,--gs-row-tint]",
6996
+ // the row's tint, as a variable too, so its pinned cells draw it over their solid ground (A35)
6997
+ isSelected ? "bg-[var(--ds-accent-subtle)] [--gs-row-tint:var(--ds-accent-subtle)]" : striped && i % 2 === 1 ? "bg-[var(--ds-bg-muted)]/50 [--gs-row-tint:color-mix(in_oklab,var(--ds-bg-muted)_50%,transparent)]" : "hover:bg-[var(--ds-bg-muted)] hover:[--gs-row-tint:var(--ds-bg-muted)]",
6998
+ clickable && "cursor-pointer",
6999
+ // an outline inside the row, clear of the frame's rounded corners (D8): it shows over the pinned
7000
+ // cells, whose solid ground hid the inset shadow the row drew before (the review of 30 September)
7001
+ (onRowClick || place) && FOCUS_INSET
7002
+ ),
7003
+ onClick: clickable ? (e) => {
7004
+ if (!clickIsRows(e)) return;
7005
+ if (onRowClick) onRowClick(row, originalIdx);
7006
+ else toggleExpand(id);
7007
+ } : void 0,
7008
+ tabIndex: place ? heldOuter ? -1 : grid.tabIndexOf(String(id)) : onRowClick ? 0 : void 0,
7009
+ onKeyDown: onRowClick && !place ? (e) => {
7010
+ if (e.target !== e.currentTarget) return;
7011
+ if (e.key === "Enter" || e.key === " ") {
7012
+ e.preventDefault();
7013
+ onRowClick(row, originalIdx);
7014
+ }
7015
+ } : void 0,
7016
+ children: [
7017
+ selectable && /* @__PURE__ */ jsx("td", { className: cn("w-10", density === "compact" ? "px-ds-2 py-ds-2" : "px-ds-3 py-ds-3", pinFocus(0, cellCount)), style: pinStyle(0, cellCount), children: box && /* @__PURE__ */ jsx(Checkbox, { checked: isSelected, indeterminate: box.mixed, onChange: (_, { shiftKey: shiftKey2 }) => toggleBox(originalIdx, id, shiftKey2), "aria-labelledby": nameIndex >= 0 ? `${headingId}-select ${nameRef}` : `${headingId}-select` }) }),
7018
+ !trail && details,
7019
+ columns.map((col, c) => {
7020
+ const truncLines = col.truncate === true ? 1 : col.truncate ? col.truncate.lines : 0;
7021
+ const kind = kindOf2(col);
7022
+ const alignClass = col.align ? col.align === "right" ? "text-right" : col.align === "center" ? "text-center" : "text-left" : kind === "number" ? "text-right" : "text-left";
7023
+ const cellContent = row[col.key];
7024
+ const shrink = (kind === "number" || kind === "date") && !col.maxWidth;
7025
+ const whole2 = !kind && renderedKinds.has(col.key);
7026
+ let inner;
7027
+ if (truncLines > 0) {
7028
+ inner = /* @__PURE__ */ jsx(CutCell, { lines: truncLines, children: cellContent });
7029
+ } else if (kind === "email") {
7030
+ inner = /* @__PURE__ */ jsx(CutEmail, { children: cellContent });
7031
+ } else {
7032
+ inner = cellContent;
7033
+ }
7034
+ const align = col.align ?? (kind === "number" ? "right" : "left");
7035
+ if (align !== "left" && cellContent !== null && cellContent !== void 0 && typeof cellContent !== "string" && typeof cellContent !== "number") {
7036
+ inner = /* @__PURE__ */ jsx("div", { className: cn("flex items-center", align === "center" ? "justify-center" : "justify-end"), children: inner });
7037
+ }
7038
+ if (node && c === 0) {
7039
+ const nameId = nameRef;
7040
+ const under = node.children.length;
7041
+ inner = /* @__PURE__ */ jsxs("div", { "data-tree-indent": "", className: cn("flex items-center gap-ds-2", align === "center" ? "justify-center" : align === "right" && "justify-end"), style: node.level > 1 ? { paddingLeft: `calc(var(--ds-space-3) * ${node.level - 1})` } : void 0, children: [
7042
+ isParent ? (
7043
+ // named by its own words and then the row's name, which it points to: the cell's text stays the name
7044
+ /* @__PURE__ */ jsx("button", { type: "button", tabIndex: -1, "data-tree-toggle": "", id: `${nameId}-toggle`, "aria-label": `${isOpen ? "Hide" : "Show"} ${under} ${under === 1 ? "row" : "rows"} under`, "aria-labelledby": `${nameId}-toggle ${nameId}`, className: TREE_TOGGLE, onClick: (e) => {
7045
+ toggleOpen(id);
7046
+ e.currentTarget.closest("tr")?.focus();
7047
+ }, children: /* @__PURE__ */ jsx(Icon, { name: "chevron-right", size: 14, className: cn("transition-transform", isOpen && "rotate-90") }) })
7048
+ ) : /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "shrink-0 w-4 h-4" }),
7049
+ /* @__PURE__ */ jsx("div", { id: nameId, className: "min-w-0", children: inner })
7050
+ ] });
7051
+ }
7052
+ return /* @__PURE__ */ jsx(
7053
+ "td",
7054
+ {
7055
+ id: !node && lead > 0 && lead + c === nameIndex ? `${headingId}-name-${idPart(id)}` : void 0,
7056
+ "data-col": col.key,
7057
+ className: cn(
7058
+ density === "compact" ? "px-ds-3 py-ds-2" : "px-ds-4 py-ds-3",
7059
+ "text-[var(--ds-text-primary)]",
7060
+ alignClass,
7061
+ shrink && "w-px whitespace-nowrap",
7062
+ whole2 && "whitespace-nowrap",
7063
+ kind === "identifier" && "[overflow-wrap:anywhere]",
7064
+ pinFocus(lead + c, cellCount)
7065
+ ),
7066
+ style: col.maxWidth || col.width || col.minWidth || c + lead < pinnedStart || c >= columns.length - endColumns ? { maxWidth: col.maxWidth, width: col.width, minWidth: col.minWidth, ...pinStyle(lead + c, cellCount) } : void 0,
7067
+ children: inner
7068
+ },
7069
+ col.key
7070
+ );
7071
+ }),
7072
+ trail > 0 && details
7073
+ ]
7074
+ }
7075
+ ),
7076
+ expandable && isExpanded && renderExpanded && // in a tree table the panel's controls join its row's in the Tab order, focus in it makes its row the
7077
+ // stop, and the panel is at its row's level, so a screen reader never places it a level apart (item 14)
7078
+ /* @__PURE__ */ jsx("tr", { "data-panel-of": place ? String(id) : void 0, "aria-level": place ? place.node.level : void 0, onFocus: place ? onRowFocus : void 0, className: "border-b-[length:var(--ds-divider-width)] border-[var(--ds-border-subtle)] last:border-b-0", children: /* @__PURE__ */ jsx(
7079
+ "td",
7080
+ {
7081
+ colSpan: cellCount,
7082
+ className: cn("py-ds-4 text-sm text-[var(--ds-text-primary)]", !pins.panel && (density === "compact" ? "px-ds-3" : "px-ds-4")),
7083
+ style: pins.panel ? { paddingLeft: pins.panel[0], paddingRight: pins.panel[1] } : void 0,
7084
+ children: /* @__PURE__ */ jsx(
7085
+ "div",
7086
+ {
7087
+ "data-row-panel": "",
7088
+ className: "sticky",
7089
+ style: pins.panel ? { left: pins.panel[0], maxWidth: pins.frame ? pins.frame - pins.panel[0] - pins.panel[1] : void 0 } : { left: 0 },
7090
+ children: place ? /* @__PURE__ */ jsx(TreeHeldCtx, { value: String(id) !== stopKey, children: /* @__PURE__ */ jsx(PanelContent, { children: renderExpanded(row, originalIdx) }) }) : /* @__PURE__ */ jsx(PanelContent, { children: renderExpanded(row, originalIdx) })
7091
+ }
7092
+ )
7093
+ }
7094
+ ) })
7095
+ ] }, String(id));
7096
+ }) }) })
7097
+ ] })
7098
+ );
6450
7099
  const region = scrollable || regionFocused ? { tabIndex: 0, role: "region", ...title ? { "aria-labelledby": titleId } : { "aria-label": ariaLabel ?? "Table" } } : {};
6451
7100
  return withControl(
6452
7101
  /* @__PURE__ */ jsxs(
@@ -6462,7 +7111,7 @@ function Table({
6462
7111
  loading && data.length > 0 && /* @__PURE__ */ jsx(RefreshLine, { className: "inset-x-0 rounded-t-[inherit]" }),
6463
7112
  loading && data.length === 0 && /* @__PURE__ */ jsx("span", { role: "status", className: "sr-only", children: LOADING_LABEL }),
6464
7113
  title && /* @__PURE__ */ jsx(TableHeading, { title, description, actions, titleId, descriptionId, inset: true }),
6465
- selectable && /* @__PURE__ */ jsx("span", { hidden: true, id: `${headingId}-select-all`, children: "Select all rows shown" }),
7114
+ selectable && /* @__PURE__ */ jsx("span", { hidden: true, id: `${headingId}-select-all`, children: nested ? pager ? "Select all rows on this page" : "Select all rows" : "Select all rows shown" }),
6466
7115
  selectable && /* @__PURE__ */ jsx("span", { hidden: true, id: `${headingId}-select`, children: "Select" }),
6467
7116
  expandable && /* @__PURE__ */ jsx("span", { hidden: true, id: `${headingId}-details`, children: "Details" }),
6468
7117
  /* @__PURE__ */ jsx(
@@ -6488,266 +7137,32 @@ function Table({
6488
7137
  )
6489
7138
  );
6490
7139
  }
6491
- function optionsKeyForFit(options) {
6492
- return options.map((o) => `${o.value}:${o.label}`).join("|");
6493
- }
6494
- var sizes2 = {
6495
- xs: "text-xs px-ds-2",
6496
- sm: "text-xs px-ds-3",
6497
- md: "text-sm px-ds-3",
6498
- lg: "text-base px-ds-3"
6499
- };
6500
- var containerPadding = {
6501
- xs: "p-[2px] gap-[2px]",
6502
- sm: "p-ds-1 gap-ds-0.5",
6503
- md: "p-ds-1 gap-ds-0.5",
6504
- lg: "p-ds-1 gap-ds-0.5"
6505
- };
6506
- var indicatorInset = {
6507
- xs: "2px",
6508
- sm: "4px",
6509
- md: "4px",
6510
- lg: "4px"
6511
- };
6512
- function segmentRadius(inset, bordered) {
6513
- return `max(var(--ds-radius-sm), calc(var(--ds-radius-md) - ${inset}${bordered ? " - var(--ds-border-width)" : ""}))`;
6514
- }
6515
- function SegmentedControl({
6516
- value,
6517
- onChange: onChangeProp,
6518
- options,
6519
- size: sizeProp,
6520
- variant = "default",
6521
- fullWidth = false,
6522
- onHoverChange,
6523
- className,
6524
- "aria-label": ariaLabel,
6525
- maxOptions = 6,
6526
- ...wiring
6527
- }) {
6528
- const onChange = useReportedChange(onChangeProp, value);
6529
- const contextSize = useContext(ControlSizeContext);
6530
- const control = useFieldControl(wiring);
6531
- const named = !ariaLabel && control.labelId ? { "aria-labelledby": control.labelId } : {};
6532
- const size = sizeProp ?? contextSize ?? "md";
6533
- const containerRef = useRef(null);
6534
- const [indicator, setIndicator] = useState({ left: 0, width: 0 });
6535
- const parentRef = useRef(null);
6536
- const neededRef = useRef(0);
6537
- const [overflows, setOverflows] = useState(false);
6538
- const measureFit = useCallback(() => {
6539
- const row = containerRef.current;
6540
- if (row) {
6541
- parentRef.current = row.parentElement;
6542
- if (row.scrollWidth > 0) neededRef.current = row.scrollWidth;
6543
- }
6544
- const parent = parentRef.current;
6545
- const needed = neededRef.current;
6546
- if (!parent || !needed) return;
6547
- const style = getComputedStyle(parent);
6548
- const available = parent.clientWidth - (parseFloat(style.paddingLeft) || 0) - (parseFloat(style.paddingRight) || 0);
6549
- if (available <= 0) return;
6550
- setOverflows((prev) => prev ? needed > available - 8 : needed > available);
6551
- }, []);
6552
- useLayoutEffect(() => {
6553
- measureFit();
6554
- const parent = parentRef.current;
6555
- if (!parent || typeof ResizeObserver === "undefined") return;
6556
- const observer = new ResizeObserver(measureFit);
6557
- observer.observe(parent);
6558
- return () => observer.disconnect();
6559
- }, [measureFit, optionsKeyForFit(options)]);
6560
- const collapsed = options.length > maxOptions || overflows;
6561
- const updateIndicator = useCallback(() => {
6562
- if (!containerRef.current) return;
6563
- const active = containerRef.current.querySelector("[data-active='true']");
6564
- if (active) {
6565
- setIndicator({
6566
- left: active.offsetLeft,
6567
- width: active.offsetWidth
6568
- });
6569
- }
6570
- }, []);
6571
- const optionsKey = options.map((o) => `${o.value}:${o.label}`).join("|");
6572
- useLayoutEffect(() => {
6573
- updateIndicator();
6574
- }, [value, optionsKey, updateIndicator]);
6575
- useEffect(() => {
6576
- const fonts = document.fonts;
6577
- if (!fonts?.ready) return;
6578
- let live = true;
6579
- fonts.ready.then(() => {
6580
- if (live) updateIndicator();
6581
- });
6582
- return () => {
6583
- live = false;
6584
- };
6585
- }, [updateIndicator]);
6586
- const [animate, setAnimate] = useState(false);
6587
- const select = useCallback((v) => {
6588
- setAnimate(true);
6589
- onChange(v);
6590
- }, [onChange]);
6591
- useLayoutEffect(() => {
6592
- if (collapsed || !containerRef.current || typeof ResizeObserver === "undefined") return;
6593
- const observer = new ResizeObserver(updateIndicator);
6594
- observer.observe(containerRef.current);
6595
- containerRef.current.querySelectorAll("button").forEach((b) => observer.observe(b));
6596
- return () => observer.disconnect();
6597
- }, [updateIndicator, optionsKey, collapsed]);
6598
- const containerHeight = {
6599
- xs: "h-ds-6",
6600
- sm: "h-[var(--ds-control-height-sm)]",
6601
- md: "h-[var(--ds-control-height-md)]",
6602
- lg: "h-[var(--ds-control-height-lg)]"
6603
- };
6604
- const tabStop = rovingTabStop(options, (o) => o.value === value, (o) => !!o.disabled);
6605
- const handleKeyDown = (e) => {
6606
- const enabled = options.filter((o) => !o.disabled);
6607
- if (enabled.length === 0) return;
6608
- const idx = Math.max(0, enabled.findIndex((o) => o.value === value));
6609
- let next = null;
6610
- if (e.key === "ArrowRight" || e.key === "ArrowDown") next = (idx + 1) % enabled.length;
6611
- else if (e.key === "ArrowLeft" || e.key === "ArrowUp") next = (idx - 1 + enabled.length) % enabled.length;
6612
- else if (e.key === "Home") next = 0;
6613
- else if (e.key === "End") next = enabled.length - 1;
6614
- if (next === null) return;
6615
- e.preventDefault();
6616
- select(enabled[next].value);
6617
- const buttons = containerRef.current?.querySelectorAll('[role="radio"]');
6618
- buttons?.forEach((b) => {
6619
- if (b.dataset.value === enabled[next].value) b.focus();
6620
- });
6621
- };
6622
- if (collapsed) {
6623
- return /* @__PURE__ */ jsx(
6624
- Dropdown,
6625
- {
6626
- "aria-label": ariaLabel,
6627
- ...wiring,
6628
- size: size === "md" || size === "lg" ? "md" : "sm",
6629
- options: options.filter((o) => !o.disabled).map((o) => ({ id: o.value, label: o.label })),
6630
- value,
6631
- onChange: (v) => onChange(v),
6632
- className: cn("inline-block", fullWidth ? "w-full" : "w-auto", className)
6633
- }
6634
- );
6635
- }
6636
- return /* @__PURE__ */ jsxs(
6637
- "div",
6638
- {
6639
- ref: containerRef,
6640
- role: "radiogroup",
6641
- id: control.id,
6642
- "aria-label": ariaLabel,
6643
- ...named,
6644
- "aria-describedby": control.describedBy,
6645
- "aria-invalid": control.invalid || void 0,
6646
- "aria-required": control.required || void 0,
6647
- onKeyDown: handleKeyDown,
6648
- className: cn(
6649
- "relative items-center",
6650
- // Sprint 25.14 A21: in a Field's column a track that isn't full width keeps its own width; w-fit, not self-start,
6651
- // so a row or a centred column still aligns it (the review of 30 September)
6652
- fullWidth ? "flex w-full" : "inline-flex w-fit",
6653
- containerPadding[size],
6654
- containerHeight[size],
6655
- "bg-[var(--ds-bg-muted)]",
6656
- // The owner's review of 1 October 2026: the frame's corner is a field's, so the two change together with the
6657
- // theme's radius; the segments inside sit concentric with it (SEGMENT_RADIUS)
6658
- "rounded-[var(--ds-radius-md)]",
6659
- // The frame's edge is a button's: each segment is a labelled button, so the frame isn't what identifies the
6660
- // control, and WCAG 1.4.11 asks no 3:1 of it
6661
- variant === "default" && "border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
6662
- control.invalid && "border-[length:var(--ds-border-width)] border-[var(--ds-status-error)]",
6663
- className
6664
- ),
6665
- style: { "--ds-segment-radius": segmentRadius(indicatorInset[size], variant === "default" || !!control.invalid) },
6666
- children: [
6667
- /* @__PURE__ */ jsx(
6668
- "div",
6669
- {
6670
- className: cn(
6671
- "absolute rounded-[var(--ds-segment-radius)]",
6672
- "bg-[var(--ds-bg-surface)]",
6673
- // The selected segment is a kind of button, so it takes a secondary button's surface and edge, as the frame
6674
- // takes a button's edge (the owner, 3 October 2026: the 3:1 strong edge it kept from Sprint 25.14 drew the
6675
- // darker outline 25.19 took off the fields). It shows its state as the raised surface on the muted track, with
6676
- // the default variant's shadow and its label's stronger colour. While its segment has keyboard focus the shadow
6677
- // steps aside, as a button's does, so the segment's outline sits clear (Sprint 30, D7)
6678
- "border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
6679
- variant === "default" && "shadow-[var(--ds-shadow-sm)]",
6680
- "[[role=radiogroup]:has([aria-checked=true]:focus-visible)_&]:shadow-none",
6681
- "transition-all motion-reduce:transition-none"
6682
- ),
6683
- "data-animate": animate ? "true" : "false",
6684
- style: {
6685
- left: indicator.left,
6686
- width: indicator.width,
6687
- top: indicatorInset[size],
6688
- bottom: indicatorInset[size],
6689
- transitionDuration: animate ? "var(--ds-transition-speed)" : "0ms",
6690
- transitionTimingFunction: "var(--ds-transition-ease)"
6691
- }
6692
- }
6693
- ),
6694
- options.map((opt, i) => /* @__PURE__ */ jsx(
6695
- "button",
6696
- {
6697
- type: "button",
6698
- role: "radio",
6699
- "aria-checked": opt.value === value,
6700
- "data-active": opt.value === value,
6701
- "data-value": opt.value,
6702
- tabIndex: i === tabStop ? 0 : -1,
6703
- disabled: opt.disabled,
6704
- onClick: () => !opt.disabled && select(opt.value),
6705
- onMouseEnter: onHoverChange && !opt.disabled ? () => onHoverChange(opt.value) : void 0,
6706
- onMouseLeave: onHoverChange ? () => onHoverChange(null) : void 0,
6707
- className: cn(
6708
- "relative z-10 inline-flex items-center justify-center self-stretch font-medium whitespace-nowrap",
6709
- fullWidth && "flex-1",
6710
- "rounded-[var(--ds-segment-radius)]",
6711
- "transition-colors cursor-pointer",
6712
- FOCUS_RING,
6713
- sizes2[size],
6714
- opt.value === value ? "text-[var(--ds-text-primary)]" : "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-secondary)]",
6715
- opt.disabled && "opacity-disabled cursor-not-allowed"
6716
- ),
6717
- children: opt.label
6718
- },
6719
- opt.value
6720
- ))
6721
- ]
6722
- }
6723
- );
6724
- }
6725
7140
 
6726
7141
  // ../../showcase/src/components/Calendar/event-colours.ts
6727
7142
  function eventColours(category) {
6728
7143
  const colour = category?.color;
6729
7144
  if (colour === void 0 || colour === "accent" || colour === "var(--ds-accent-primary)") return ACCENT;
6730
- if (colour === "neutral") return NEUTRAL;
6731
- if (typeof colour === "number") return Number.isInteger(colour) && colour >= 1 ? supporting(colour) : NEUTRAL;
7145
+ if (colour === "neutral") return NEUTRAL2;
7146
+ if (typeof colour === "number") return Number.isInteger(colour) && colour >= 1 ? supporting(colour) : NEUTRAL2;
6732
7147
  const named = SUPPORTING_TOKEN.exec(colour.trim());
6733
7148
  if (named) return supporting(Number(named[1]));
6734
7149
  const chart = CHART_TOKEN.exec(colour.trim());
6735
7150
  if (chart) return Number(chart[1]) === 1 ? ACCENT : supporting(Number(chart[1]) - 1);
6736
- return { ...NEUTRAL, edge: colour };
7151
+ return { ...NEUTRAL2, edge: colour };
6737
7152
  }
6738
7153
  var SUPPORTING_TOKEN = /^var\(\s*--ds-palette-(\d+)-[a-z-]+\s*\)$/;
6739
7154
  var CHART_TOKEN = /^var\(\s*--ds-chart-(\d+)\s*\)$/;
6740
7155
  var supporting = (n) => ({
6741
- fill: `var(--ds-palette-${n}-bg, ${NEUTRAL.fill})`,
6742
- text: `var(--ds-palette-${n}-text, ${NEUTRAL.text})`,
6743
- edge: `var(--ds-palette-${n}-chart, ${NEUTRAL.edge})`
7156
+ fill: `var(--ds-palette-${n}-bg, ${NEUTRAL2.fill})`,
7157
+ text: `var(--ds-palette-${n}-text, ${NEUTRAL2.text})`,
7158
+ edge: `var(--ds-palette-${n}-chart, ${NEUTRAL2.edge})`
6744
7159
  });
6745
7160
  var ACCENT = {
6746
7161
  fill: "var(--ds-accent-bg, var(--ds-accent-subtle))",
6747
7162
  text: "var(--ds-accent-text, var(--ds-text-primary))",
6748
7163
  edge: "var(--ds-chart-1)"
6749
7164
  };
6750
- var NEUTRAL = {
7165
+ var NEUTRAL2 = {
6751
7166
  fill: "var(--ds-bg-muted)",
6752
7167
  text: "var(--ds-text-primary)",
6753
7168
  edge: "var(--ds-control-edge-strong)"
@@ -8846,9 +9261,9 @@ function ExpandableDescription({ children, className }) {
8846
9261
  }
8847
9262
  );
8848
9263
  }
8849
- function HorizontalLayout({ items, minItemWidth, className }) {
9264
+ function HorizontalLayout({ items, minItemWidth, label, className }) {
8850
9265
  const [row, setRow] = useState(null);
8851
- useKeyboardScroll(row, "Timeline", true, false, "x");
9266
+ useKeyboardScroll(row, label, true, false, "x");
8852
9267
  return /* @__PURE__ */ jsx("div", { ref: setRow, "data-timeline-row": "", className: cn("relative flex items-start overflow-x-auto snap-x", FOCUS_INSET, className), children: items.map((item, i) => {
8853
9268
  const isFirst = i === 0;
8854
9269
  const isLast = i === items.length - 1;
@@ -8927,7 +9342,7 @@ function VerticalLayout({ items, className }) {
8927
9342
  ] })
8928
9343
  ] }, i)) });
8929
9344
  }
8930
- function Timeline({ items: allItems, orientation = "vertical", minItemWidth = 200, className, ref, pager }) {
9345
+ function Timeline({ items: allItems, orientation = "vertical", minItemWidth = 200, className, ref, pager, "aria-label": ariaLabel = "Timeline" }) {
8931
9346
  useScrollsInside();
8932
9347
  useWholeListCheck("Timeline", allItems.length, pager);
8933
9348
  const items = pagerPart(allItems, pager);
@@ -8940,7 +9355,7 @@ function Timeline({ items: allItems, orientation = "vertical", minItemWidth = 20
8940
9355
  }, [ref]);
8941
9356
  const fits = width === 0 || width >= items.length * minItemWidth;
8942
9357
  const horizontal = orientation === "horizontal" || orientation === "responsive" && fits;
8943
- const list2 = /* @__PURE__ */ jsx("div", { ref: setRefs, "data-ds-list": "", "data-timeline-orientation": horizontal ? "horizontal" : "vertical", className: pager ? void 0 : className, children: /* @__PURE__ */ jsx(RowsCtx, { value: true, children: horizontal ? /* @__PURE__ */ jsx(HorizontalLayout, { items, minItemWidth }) : /* @__PURE__ */ jsx(VerticalLayout, { items }) }) });
9358
+ const list2 = /* @__PURE__ */ jsx("div", { ref: setRefs, "data-ds-list": "", "data-timeline-orientation": horizontal ? "horizontal" : "vertical", className: pager ? void 0 : className, children: /* @__PURE__ */ jsx(RowsCtx, { value: true, children: horizontal ? /* @__PURE__ */ jsx(HorizontalLayout, { items, minItemWidth, label: ariaLabel }) : /* @__PURE__ */ jsx(VerticalLayout, { items }) }) });
8944
9359
  if (!pager) return list2;
8945
9360
  return /* @__PURE__ */ jsxs("div", { "data-gs-paged": "", className: cn("flex flex-col min-w-0", className), children: [
8946
9361
  list2,
@@ -17895,6 +18310,6 @@ function DangerZone({ title = "Danger zone", description, children, className })
17895
18310
  ] });
17896
18311
  }
17897
18312
 
17898
- export { Accordion, ActionPanel, ActionPanelGroup, AddAnother, Alert, AsidePanel, AvatarGroup, BottomNav, BulkActionBar, Calendar, Card, CardContent, CardDescription, CardFooter, CardGrid, CardHeader, CardTitle, Carousel, ChatMessageList, ChatPanel, Collapsible, CollapsibleContent, CollapsibleTrigger, ColorPicker, Combobox, CommandPalette, ContextMenu, CornerPanel, DEFAULT_DATE_RANGE_PRESETS, DangerZone, DataList, DateRangePicker, DescriptionList, Dialog, Dock, EmptyState, EntityCell, FileUpload, FlowSaved, FormWrapper, Gantt, Kbd, Launcher, LocaleProvider, MentionInput, MobileHeaderMenu, NavItem, NavMenu, Navbar, NotificationCenter, PageActions, PageContainer, PageHeader, PinInput, Progress, RadioGroup, Rating, RecurrencePicker, RecurrenceScopeDialog, RelativeTime, ScrollArea, SectionHeading, SegmentedControl, SettingsLayout, Sheet, Sidebar, SidebarNav, SliderRange, StackedList, StatsCard, StatusDot, Stepper, TIME_ZOOMS, Table, TableToolbar, Tabs, TabsContent, TabsList, TabsTrigger, Tag, TagInput, TimeBar, TimePicker, TimeRow, TimeScaleHeader, TimeViewport, Timeline, TimezoneSelect, Toast, ToastProvider, Tour, TransferList, TreeView, browserTimeZone, canonicalTimeZone, countEdited, dateOfDay, dayOf, deleteOccurrence, describeRecurrence, endDayOf, fitLabel, fitLabels, formatDateRange, nthOf, occurrenceOn, occurrences, parseRRule, readFlowProgress, readRecurrence, recurrencePresets, sameRecurrence, saveOccurrence, timeExtent, timeZoneOffset, toRRule, useBrowserTimeZone, useContentWidth, useDestructiveAction, useFittedLabels, useFlowSteps, useRelativeTimeFormat, useSidebar, useTextMeasure, useTimeScale, useTimeViewport, useTimeViewportLayout, useToast, useToastOptional };
18313
+ export { Accordion, ActionPanel, ActionPanelGroup, AddAnother, Alert, AsidePanel, AvatarGroup, BottomNav, BulkActionBar, Calendar, Card, CardContent, CardDescription, CardFooter, CardGrid, CardHeader, CardTitle, Carousel, ChatMessageList, ChatPanel, Collapsible, CollapsibleContent, CollapsibleTrigger, ColorPicker, Combobox, CommandPalette, ContextMenu, CornerPanel, DEFAULT_DATE_RANGE_PRESETS, DangerZone, DataList, DateRangePicker, DescriptionList, Dialog, Dock, EmptyState, EntityCell, FileUpload, FlowSaved, FormWrapper, Gantt, Kbd, Launcher, LocaleProvider, MentionInput, MobileHeaderMenu, NavItem, NavMenu, Navbar, NotificationCenter, PageActions, PageContainer, PageHeader, PinInput, Progress, RadioGroup, Rating, RecurrencePicker, RecurrenceScopeDialog, RelativeTime, ScrollArea, SectionHeading, SettingsLayout, Sheet, Sidebar, SidebarNav, SliderRange, StackedList, StatsCard, StatusDot, Stepper, TIME_ZOOMS, Table, TableToolbar, Tabs, TabsContent, TabsList, TabsTrigger, Tag, TagInput, TimeBar, TimePicker, TimeRow, TimeScaleHeader, TimeViewport, Timeline, TimezoneSelect, Toast, ToastProvider, Tour, TransferList, TreeView, browserTimeZone, canonicalTimeZone, countEdited, dateOfDay, dayOf, deleteOccurrence, describeRecurrence, endDayOf, fitLabel, fitLabels, formatDateRange, nthOf, occurrenceOn, occurrences, parseRRule, readFlowProgress, readRecurrence, recurrencePresets, sameRecurrence, saveOccurrence, timeExtent, timeZoneOffset, toRRule, useBrowserTimeZone, useContentWidth, useDestructiveAction, useFittedLabels, useFlowSteps, useRelativeTimeFormat, useSidebar, useTextMeasure, useTimeScale, useTimeViewport, useTimeViewportLayout, useToast, useToastOptional };
17899
18314
  //# sourceMappingURL=index.js.map
17900
18315
  //# sourceMappingURL=index.js.map