gridsmith-ui 0.30.1 → 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 (80) hide show
  1. package/CHANGELOG.md +73 -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 +24 -10
  6. package/dist/chat.js.map +1 -1
  7. package/dist/{chunk-ULCACQSA.js → chunk-2Q4HOGEI.js} +16 -51
  8. package/dist/chunk-2Q4HOGEI.js.map +1 -0
  9. package/dist/{chunk-T52LAFKG.js → chunk-3J67NISC.js} +13 -15
  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-SLRTSBCJ.js → chunk-BKYWKMUN.js} +73 -678
  14. package/dist/chunk-BKYWKMUN.js.map +1 -0
  15. package/dist/{chunk-KMKBOTFR.js → chunk-EKWXPSTI.js} +10 -12
  16. package/dist/chunk-EKWXPSTI.js.map +1 -0
  17. package/dist/{chunk-SJZWEBHL.js → chunk-GP2COVJC.js} +24 -11
  18. package/dist/chunk-GP2COVJC.js.map +1 -0
  19. package/dist/{chunk-OVHBQT3Z.js → chunk-GT6M74IZ.js} +6 -6
  20. package/dist/{chunk-OVHBQT3Z.js.map → chunk-GT6M74IZ.js.map} +1 -1
  21. package/dist/{chunk-IELIF4PJ.js → chunk-HUNEJIKV.js} +3 -3
  22. package/dist/{chunk-IELIF4PJ.js.map → chunk-HUNEJIKV.js.map} +1 -1
  23. package/dist/{chunk-NEUWZUK6.js → chunk-LE72KHIE.js} +3 -4
  24. package/dist/chunk-LE72KHIE.js.map +1 -0
  25. package/dist/{chunk-OX3NNMWG.js → chunk-OFACAHRV.js} +10 -6
  26. package/dist/chunk-OFACAHRV.js.map +1 -0
  27. package/dist/{chunk-R5QXMNW7.js → chunk-OWQ7M6R7.js} +177 -27
  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-UDJOIVKQ.js → chunk-XSQD3UGY.js} +77 -7
  38. package/dist/chunk-XSQD3UGY.js.map +1 -0
  39. package/dist/{chunk-ISONVOA7.js → chunk-YFHTREH5.js} +6 -8
  40. package/dist/chunk-YFHTREH5.js.map +1 -0
  41. package/dist/data-grid.js +10 -13
  42. package/dist/data-grid.js.map +1 -1
  43. package/dist/editor-mentions.js +2 -4
  44. package/dist/editor-mentions.js.map +1 -1
  45. package/dist/editor.js +3 -7
  46. package/dist/editor.js.map +1 -1
  47. package/dist/icons.js +1 -1
  48. package/dist/index.d.ts +104 -16
  49. package/dist/index.js +1075 -654
  50. package/dist/index.js.map +1 -1
  51. package/dist/kanban.js +9 -13
  52. package/dist/kanban.js.map +1 -1
  53. package/dist/resizable.js +9 -13
  54. package/dist/resizable.js.map +1 -1
  55. package/dist/virtualized.js +10 -13
  56. package/dist/virtualized.js.map +1 -1
  57. package/package.json +1 -1
  58. package/dist/chunk-6C2IUMY7.js +0 -46
  59. package/dist/chunk-6C2IUMY7.js.map +0 -1
  60. package/dist/chunk-7Q46JXSK.js +0 -51
  61. package/dist/chunk-7Q46JXSK.js.map +0 -1
  62. package/dist/chunk-HTXP5BMM.js +0 -38
  63. package/dist/chunk-HTXP5BMM.js.map +0 -1
  64. package/dist/chunk-ISONVOA7.js.map +0 -1
  65. package/dist/chunk-KMKBOTFR.js.map +0 -1
  66. package/dist/chunk-L6HWACK7.js.map +0 -1
  67. package/dist/chunk-NEUWZUK6.js.map +0 -1
  68. package/dist/chunk-NKUCS67T.js +0 -13
  69. package/dist/chunk-NKUCS67T.js.map +0 -1
  70. package/dist/chunk-OX3NNMWG.js.map +0 -1
  71. package/dist/chunk-R5QXMNW7.js.map +0 -1
  72. package/dist/chunk-SJZWEBHL.js.map +0 -1
  73. package/dist/chunk-SLRTSBCJ.js.map +0 -1
  74. package/dist/chunk-T52LAFKG.js.map +0 -1
  75. package/dist/chunk-UDJOIVKQ.js.map +0 -1
  76. package/dist/chunk-ULCACQSA.js.map +0 -1
  77. package/dist/chunk-WIUGPZOH.js +0 -88
  78. package/dist/chunk-WIUGPZOH.js.map +0 -1
  79. package/dist/chunk-YL5KD5RC.js +0 -49
  80. package/dist/chunk-YL5KD5RC.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,49 +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-KMKBOTFR.js';
3
- export { AppShell, Breadcrumbs, Drawer, SkipLink, useAppShell, useUnsavedChanges } from './chunk-KMKBOTFR.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-NEUWZUK6.js';
8
- export { Avatar } from './chunk-NEUWZUK6.js';
9
- import { Link } from './chunk-OX3NNMWG.js';
10
- export { CodeBlock, Divider, Link } from './chunk-OX3NNMWG.js';
11
- import { DropdownMenu } from './chunk-ISONVOA7.js';
12
- export { DropdownMenu } from './chunk-ISONVOA7.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 { useKeyboardScroll } from './chunk-SJZWEBHL.js';
15
- import { Badge } from './chunk-OVHBQT3Z.js';
16
- export { Badge } from './chunk-OVHBQT3Z.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-T52LAFKG.js';
18
- export { Checkbox, CheckboxGroup, CheckboxGroupItem, DatePicker, HoverCard, TextOverflow } from './chunk-T52LAFKG.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-SLRTSBCJ.js';
20
- export { Dropdown, Field, InlineFieldError, Input, Label, NumberInput, Pagination, Popover, Textarea, Toggle, usePagination } from './chunk-SLRTSBCJ.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-ULCACQSA.js';
31
- export { Button, ButtonSizeContext, ControlSizeContext, Spinner } from './chunk-ULCACQSA.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 { useRevealFocus } from './chunk-HTXP5BMM.js';
36
- export { useRevealFocus } from './chunk-HTXP5BMM.js';
37
- import { focusAfterSelfRemoval, FOCUSABLE, useFocusKeptInList, FIELD, ZOOM_KEYS, rovingTabStop, useOverlayLayer, useFocusTrap, useFocusBeforeOpen, rememberOpener, focusInitial, OVERLAY_CLOSE_ATTR, usePopupFocus, CONTROL_ANCESTOR, useGridKeyboard, focusAfterRemoval, useNonModalDialog, useReturnFocus } from './chunk-R5QXMNW7.js';
38
- export { useGridKeyboard } from './chunk-R5QXMNW7.js';
39
- import { useTextOverflow } from './chunk-IELIF4PJ.js';
40
- export { useTextOverflow } from './chunk-IELIF4PJ.js';
41
- import { AutoTooltip } from './chunk-UDJOIVKQ.js';
42
- export { Tooltip } from './chunk-UDJOIVKQ.js';
43
- import { Icon, slotIcon, warnOnce } from './chunk-L6HWACK7.js';
44
- import { mergeRefs } from './chunk-7Q46JXSK.js';
45
- import { useLatest, usePosition } from './chunk-WIUGPZOH.js';
46
- 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';
47
44
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
48
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';
49
46
  import { createPortal, flushSync } from 'react-dom';
@@ -133,7 +130,7 @@ function Combobox({ value, onChange: onChangeProp, options, placeholder = "Searc
133
130
  const onChange = useReportedChange(onChangeProp, value);
134
131
  useInputModality();
135
132
  const contextSize = useContext(ControlSizeContext);
136
- const control = useFieldControl({ ...wiring, error: error || void 0 });
133
+ const control = useFieldControl({ ...wiring, "aria-label": ariaLabel, error: error || void 0 }, { component: "Combobox" });
137
134
  const size = sizeProp ?? contextSize ?? "md";
138
135
  const [open, setOpen] = useState(false);
139
136
  useOverlayLayer(open);
@@ -262,8 +259,8 @@ function Combobox({ value, onChange: onChangeProp, options, placeholder = "Searc
262
259
  type: "text",
263
260
  id: control.id,
264
261
  role: "combobox",
265
- "aria-labelledby": wiring["aria-labelledby"],
266
- "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,
267
264
  "aria-describedby": control.describedBy,
268
265
  "aria-invalid": control.invalid || void 0,
269
266
  "aria-required": control.required || void 0,
@@ -424,6 +421,8 @@ function zoneOptions(locale) {
424
421
  return zoneLists(key, build2).options;
425
422
  }
426
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;
427
426
  const locale = useFormatLocale();
428
427
  const zones = zoneOptions(locale);
429
428
  const stored = value ? canonicalTimeZone(value) : "";
@@ -436,7 +435,7 @@ function TimezoneSelect({ value, onChange, placeholder = "Search time zones\u202
436
435
  }
437
436
  return zones.some((o) => o.value === stored) ? zones : [{ value: stored, label: labelOf(stored, /* @__PURE__ */ new Date(), locale), keywords: CITY_OF.get(stored) }, ...zones];
438
437
  }, [zones, stored, mine, locale]);
439
- 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 });
440
439
  }
441
440
  var LONGEST_WAIT = DAY;
442
441
  var noChange = () => () => {
@@ -511,7 +510,7 @@ function RadioGroup({ value, onChange: onChangeProp, options, orientation = "ver
511
510
  const onChange = useReportedChange(onChangeProp, value);
512
511
  const groupRef = useRef(null);
513
512
  const groupId = useId();
514
- const control = useFieldControl(wiring);
513
+ const control = useFieldControl(wiring, { component: "RadioGroup", ownLabel: label ? groupId : void 0 });
515
514
  const enabledOptions = options.filter((o) => !o.disabled);
516
515
  const tabStop = rovingTabStop(options, (o) => o.value === value, (o) => !!o.disabled);
517
516
  function handleKeyDown(e) {
@@ -543,8 +542,8 @@ function RadioGroup({ value, onChange: onChangeProp, options, orientation = "ver
543
542
  ref: groupRef,
544
543
  role: "radiogroup",
545
544
  id: control.id,
546
- "aria-label": label || control.labelId ? void 0 : control.label,
547
- "aria-labelledby": label ? groupId : control.labelId,
545
+ "aria-label": control.labelId ? void 0 : control.label,
546
+ "aria-labelledby": control.labelId,
548
547
  "aria-describedby": control.describedBy,
549
548
  "aria-invalid": control.invalid || void 0,
550
549
  "aria-required": control.required || void 0,
@@ -671,7 +670,7 @@ function DateRangePicker({
671
670
  const presetEdges = useScrollEdges({ enabled: isPhone });
672
671
  useRevealFocus(presetEdges.elRef, isPhone);
673
672
  const generatedId = useId();
674
- 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 });
675
674
  useInputModality();
676
675
  const triggerId = control.id ?? generatedId;
677
676
  const locale = useFormatLocale();
@@ -1563,6 +1562,8 @@ function RecurrencePicker({ value, onChange: onChangeProp, start, label, weekSta
1563
1562
  setCustomOpen(false);
1564
1563
  onChange(presets[Number(id.slice("preset-".length))].rule);
1565
1564
  };
1565
+ const { "aria-label": pageName, "aria-labelledby": pageLabelledBy, ...choiceWiring } = wiring;
1566
+ const named = pageName || pageLabelledBy ? { role: "group", "aria-label": pageName, "aria-labelledby": pageLabelledBy } : {};
1566
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;
1567
1568
  const describedBy = [wiring["aria-describedby"], summary ? ids.summary : void 0].filter(Boolean).join(" ") || void 0;
1568
1569
  const order = Array.from({ length: 7 }, (_, i) => (weekStart + i) % 7);
@@ -1574,17 +1575,17 @@ function RecurrencePicker({ value, onChange: onChangeProp, start, label, weekSta
1574
1575
  const monthlyOn = custom?.nth ? nthId(custom.nth) : custom?.day === -1 ? "last" : "day";
1575
1576
  const ends = custom?.until ? "until" : custom?.count !== void 0 ? "count" : untilPending ? "until" : "never";
1576
1577
  if (!hydrated) {
1577
- return /* @__PURE__ */ jsxs("div", { "data-gs": "recurrence-picker", "data-recurrence-pending": "", "aria-busy": "true", className: cn("space-y-ds-2", className), children: [
1578
- /* @__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: () => {
1579
1580
  } }),
1580
1581
  (rule && !noDays(rule) || unreadable) && /* @__PURE__ */ jsx("p", { "aria-hidden": "true", className: "text-xs", children: "\xA0" })
1581
1582
  ] });
1582
1583
  }
1583
- 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: [
1584
1585
  /* @__PURE__ */ jsx(
1585
1586
  Dropdown,
1586
1587
  {
1587
- ...wiring,
1588
+ ...choiceWiring,
1588
1589
  "aria-describedby": describedBy,
1589
1590
  label: field ? void 0 : label ?? "Repeat",
1590
1591
  options,
@@ -2087,7 +2088,7 @@ var running = (item) => item.status === "queued" || item.status === "uploading";
2087
2088
  var failureOf = (err) => err instanceof Error && err.message ? err.message : "The upload didn't work.";
2088
2089
  var percentOf = (value) => typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.min(100, value)) : 0;
2089
2090
  var STILL_UPLOADING = "Wait for your files to finish uploading";
2090
- 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 }) {
2091
2092
  const controlled = files !== void 0;
2092
2093
  const [own, setOwn] = useState([]);
2093
2094
  const shown = controlled ? files : own;
@@ -2107,6 +2108,7 @@ function FileUpload({ onFiles, files, upload, onCancel, onRetry, onRemove, accep
2107
2108
  const constraintsId = `${ownId}-constraints`;
2108
2109
  const rejectedId = `${ownId}-rejected`;
2109
2110
  const showLabel = label != null && !field;
2111
+ const control = useFieldControl({ "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy }, { component: "FileUpload", ownLabel: showLabel ? true : void 0 });
2110
2112
  const constraints = fileConstraints(accept, maxSize, multiple);
2111
2113
  const patch = (itemId, change) => setOwn((prev) => prev.map((i) => i.id === itemId ? { ...i, ...change } : i));
2112
2114
  const stop = (itemId) => {
@@ -2318,8 +2320,8 @@ function FileUpload({ onFiles, files, upload, onCancel, onRetry, onRemove, accep
2318
2320
  accept,
2319
2321
  multiple,
2320
2322
  className: "sr-only",
2321
- "aria-label": field ? void 0 : ariaLabel,
2322
- "aria-labelledby": field ? field.labelId : void 0,
2323
+ "aria-label": control.labelId ? void 0 : control.label,
2324
+ "aria-labelledby": control.labelId,
2323
2325
  "aria-describedby": describedBy,
2324
2326
  "aria-invalid": invalid || void 0,
2325
2327
  onChange: (e) => {
@@ -2386,6 +2388,7 @@ function SliderRange({
2386
2388
  error = false,
2387
2389
  label,
2388
2390
  "aria-label": ariaLabel,
2391
+ "aria-labelledby": ariaLabelledBy,
2389
2392
  "aria-describedby": describedByProp,
2390
2393
  trackStyle,
2391
2394
  formatValue
@@ -2394,9 +2397,10 @@ function SliderRange({
2394
2397
  const field = useField();
2395
2398
  const ownId = useId();
2396
2399
  const showLabel = label != null && !field;
2397
- 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;
2398
2402
  const describedBy = [...new Set([describedByProp, field?.describedBy].join(" ").split(" ").filter(Boolean))].join(" ") || void 0;
2399
- 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 };
2400
2404
  const isRange = Array.isArray(value);
2401
2405
  const low = isRange ? value[0] : value;
2402
2406
  const high = isRange ? value[1] : value;
@@ -2610,7 +2614,7 @@ function TimePicker({
2610
2614
  }) {
2611
2615
  const onChange = useReportedChange(onChangeProp, value);
2612
2616
  const contextSize = useContext(ControlSizeContext);
2613
- 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" });
2614
2618
  useInputModality();
2615
2619
  const size = sizeProp ?? contextSize ?? "md";
2616
2620
  const [open, setOpen] = useState(false);
@@ -3296,6 +3300,7 @@ function PinInput({
3296
3300
  id,
3297
3301
  "aria-label": ariaLabel
3298
3302
  }) {
3303
+ const control = useFieldControl({ "aria-label": ariaLabel }, { component: "PinInput" });
3299
3304
  const onChange = useReportedChange(onChangeProp, value);
3300
3305
  useInputModality();
3301
3306
  const contextSize = useContext(ControlSizeContext);
@@ -3353,7 +3358,8 @@ function PinInput({
3353
3358
  "div",
3354
3359
  {
3355
3360
  role: "group",
3356
- "aria-label": ariaLabel ?? "PIN input",
3361
+ "aria-labelledby": control.labelId,
3362
+ "aria-label": control.labelId ? void 0 : control.label ?? "PIN input",
3357
3363
  "aria-invalid": error || void 0,
3358
3364
  className: cn("inline-flex w-fit items-center gap-ds-2", className),
3359
3365
  children: digits.map((digit, i) => /* @__PURE__ */ jsx(
@@ -3445,7 +3451,8 @@ function Rating({
3445
3451
  const onChange = useReportedChange(onChangeProp, value);
3446
3452
  const [hoverValue, setHoverValue] = useState(null);
3447
3453
  const displayValue = hoverValue ?? value;
3448
- const control = useFieldControl({ ...wiring, error: error || void 0 });
3454
+ const control = useFieldControl({ ...wiring, error: error || void 0 }, { component: "Rating" });
3455
+ const valueId = `${useId()}-value`;
3449
3456
  const handleClick = useCallback(
3450
3457
  (starIdx, isLeftHalf) => {
3451
3458
  if (readOnly || !onChangeProp) return;
@@ -3508,13 +3515,13 @@ function Rating({
3508
3515
  i
3509
3516
  );
3510
3517
  });
3511
- return /* @__PURE__ */ jsx(
3518
+ return /* @__PURE__ */ jsxs(
3512
3519
  "div",
3513
3520
  {
3514
3521
  role: readOnly ? "img" : "radiogroup",
3515
3522
  id: control.id,
3516
- "aria-label": readOnly ? `${control.label ?? "Rating"}: ${value} out of ${max}` : control.labelId ? void 0 : control.label ?? `Rating: ${value} out of ${max}`,
3517
- "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,
3518
3525
  "aria-describedby": control.describedBy,
3519
3526
  "aria-invalid": readOnly ? void 0 : control.invalid || void 0,
3520
3527
  "aria-required": readOnly ? void 0 : control.required || void 0,
@@ -3527,7 +3534,10 @@ function Rating({
3527
3534
  className
3528
3535
  ),
3529
3536
  onMouseLeave: () => setHoverValue(null),
3530
- children: stars
3537
+ children: [
3538
+ readOnly && control.labelId && /* @__PURE__ */ jsx("span", { id: valueId, hidden: true, children: `${value} out of ${max}` }),
3539
+ stars
3540
+ ]
3531
3541
  }
3532
3542
  );
3533
3543
  }
@@ -3594,28 +3604,103 @@ var DEFAULT_SWATCHES = [
3594
3604
  "#000000",
3595
3605
  "#ffffff"
3596
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 };
3597
3612
  function ColorPicker({
3598
3613
  value,
3599
3614
  onChange: onChangeProp,
3600
3615
  showAlpha = false,
3601
3616
  swatches = DEFAULT_SWATCHES,
3617
+ swatchesOnly = false,
3602
3618
  disabled = false,
3603
3619
  className,
3604
- error = false,
3605
- "aria-label": ariaLabel
3620
+ ...wiring
3606
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
+ };
3607
3630
  const onChange = useReportedChange(onChangeProp, value);
3608
3631
  useInputModality();
3609
- const [hsl, setHsl] = useState(() => hexToHsl(value));
3632
+ const [hsl, setHsl] = useState(() => parseHex(value) ?? NEUTRAL);
3610
3633
  const [hexInput, setHexInput] = useState(value);
3611
3634
  const satLightRef = useRef(null);
3612
3635
  const draggingRef = useRef(false);
3636
+ const swatchRefs = useRef([]);
3613
3637
  const [prevValue, setPrevValue] = useState(value);
3614
3638
  if (value !== prevValue) {
3615
3639
  setPrevValue(value);
3616
- setHsl(hexToHsl(value));
3640
+ setHsl((was) => parseHex(value) ?? was);
3617
3641
  setHexInput(value);
3618
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
+ );
3619
3704
  const emitChange = useCallback(
3620
3705
  (newHsl) => {
3621
3706
  setHsl(newHsl);
@@ -3664,23 +3749,24 @@ function ColorPicker({
3664
3749
  },
3665
3750
  [onChange]
3666
3751
  );
3752
+ if (swatchesOnly) return swatchRow;
3667
3753
  return /* @__PURE__ */ jsxs(
3668
3754
  "div",
3669
3755
  {
3670
3756
  role: "group",
3671
- "aria-label": ariaLabel,
3672
- "aria-invalid": error || void 0,
3757
+ ...named,
3673
3758
  className: cn(
3674
3759
  "w-full max-w-64 p-ds-3 space-y-ds-3",
3675
3760
  "bg-[var(--ds-bg-surface)] text-[var(--ds-text-primary)]",
3676
3761
  "border-[length:var(--ds-border-width)]",
3677
- error ? "border-[var(--ds-status-error)]" : "border-[var(--ds-border-default)]",
3762
+ control.invalid ? "border-[var(--ds-status-error)]" : "border-[var(--ds-border-default)]",
3678
3763
  "rounded-[var(--ds-radius-lg)]",
3679
3764
  "shadow-[var(--ds-shadow-md)]",
3680
3765
  disabled && "opacity-disabled pointer-events-none",
3681
3766
  className
3682
3767
  ),
3683
3768
  children: [
3769
+ control.requiredNote,
3684
3770
  /* @__PURE__ */ jsx(
3685
3771
  "div",
3686
3772
  {
@@ -3713,6 +3799,7 @@ function ColorPicker({
3713
3799
  {
3714
3800
  type: "range",
3715
3801
  "aria-label": "Hue",
3802
+ disabled,
3716
3803
  min: 0,
3717
3804
  max: 360,
3718
3805
  value: hsl.h,
@@ -3728,6 +3815,7 @@ function ColorPicker({
3728
3815
  {
3729
3816
  type: "range",
3730
3817
  "aria-label": "Opacity",
3818
+ disabled,
3731
3819
  min: 0,
3732
3820
  max: 100,
3733
3821
  value: Math.round(hsl.a * 100),
@@ -3743,7 +3831,7 @@ function ColorPicker({
3743
3831
  "div",
3744
3832
  {
3745
3833
  className: "w-8 h-8 rounded-[var(--ds-radius-sm)] border-[length:var(--ds-border-width)] border-[var(--ds-border-default)] shrink-0",
3746
- style: { backgroundColor: hslToHex(hsl) }
3834
+ style: { backgroundColor: parseHex(value) ? hslToHex(hsl) : value }
3747
3835
  }
3748
3836
  ),
3749
3837
  /* @__PURE__ */ jsx(
@@ -3751,6 +3839,7 @@ function ColorPicker({
3751
3839
  {
3752
3840
  type: "text",
3753
3841
  "aria-label": "Hex colour",
3842
+ disabled,
3754
3843
  value: hexInput,
3755
3844
  onChange: (e) => handleHexChange(e.target.value),
3756
3845
  className: cn(
@@ -3764,27 +3853,7 @@ function ColorPicker({
3764
3853
  }
3765
3854
  )
3766
3855
  ] }),
3767
- swatches.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-ds-2", children: swatches.map((swatch) => /* @__PURE__ */ jsx(
3768
- "button",
3769
- {
3770
- type: "button",
3771
- onClick: () => {
3772
- const parsed = hexToHsl(swatch);
3773
- emitChange(parsed);
3774
- },
3775
- className: cn(
3776
- "w-7 h-7 rounded-[var(--ds-radius-sm)] cursor-pointer",
3777
- "border-[length:var(--ds-border-width)]",
3778
- "hover:scale-110 transition-transform",
3779
- FOCUS_RING,
3780
- // selected is a choice's look: the accent border, never the halo (that is active's) or an outline
3781
- swatch.toLowerCase() === value.toLowerCase() ? "border-[var(--ds-accent-ink)]" : "border-[var(--ds-border-default)]"
3782
- ),
3783
- style: { backgroundColor: swatch },
3784
- "aria-label": swatch
3785
- },
3786
- swatch
3787
- )) })
3856
+ swatches.length > 0 && swatchRow
3788
3857
  ]
3789
3858
  }
3790
3859
  );
@@ -4516,7 +4585,7 @@ function MentionInput({
4516
4585
  "aria-label": ariaLabel,
4517
4586
  ...wiring
4518
4587
  }) {
4519
- const control = useFieldControl({ ...wiring, error: error || void 0 });
4588
+ const control = useFieldControl({ ...wiring, "aria-label": ariaLabel, error: error || void 0 }, { component: "MentionInput" });
4520
4589
  useInputModality();
4521
4590
  const [internalValue, setInternalValue] = useState("");
4522
4591
  const value = controlledValue ?? internalValue;
@@ -4635,8 +4704,8 @@ function MentionInput({
4635
4704
  {
4636
4705
  ref: inputRef,
4637
4706
  id: control.id,
4638
- "aria-labelledby": wiring["aria-labelledby"],
4639
- "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,
4640
4709
  "aria-describedby": control.describedBy,
4641
4710
  "aria-invalid": control.invalid || void 0,
4642
4711
  "aria-required": control.required || void 0,
@@ -5785,6 +5854,131 @@ function rowMarks(row, index, field, id) {
5785
5854
  return typeof v === "string" || typeof v === "number" ? { "data-row-key": String(id) } : { "data-row-index": String(index) };
5786
5855
  }
5787
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
+ );
5788
5982
  function TableHeading({ title, description, actions, titleId, descriptionId, inset }) {
5789
5983
  const heading = /* @__PURE__ */ jsx("h3", { id: titleId, className: TITLE_CLASSES, children: title });
5790
5984
  const actionsBlock = actions ? /* @__PURE__ */ jsx("div", { className: HEADER_LAYOUT.actions, children: actions }) : null;
@@ -5822,6 +6016,12 @@ function Table({
5822
6016
  selectable,
5823
6017
  selectedRows = [],
5824
6018
  onSelectionChange,
6019
+ selectedKeys,
6020
+ onSelectedKeysChange,
6021
+ getChildRows,
6022
+ openParents: openParentsProp,
6023
+ defaultOpenParents,
6024
+ onOpenParentsChange,
5825
6025
  expandable,
5826
6026
  renderExpanded,
5827
6027
  rowKey = "id",
@@ -5882,8 +6082,13 @@ function Table({
5882
6082
  },
5883
6083
  [sortKey, sortDir, onSortChange, pager]
5884
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;
5885
6089
  const columnKeys = useMemo(() => columns.map((c) => c.key), [columns]);
5886
- 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]);
5887
6092
  const kindOf2 = useCallback(
5888
6093
  (col) => col.kind ?? inferredKinds.get(col.key),
5889
6094
  [inferredKinds]
@@ -5891,9 +6096,9 @@ function Table({
5891
6096
  const sortColumn = sortKey ? columns.find((c) => c.key === sortKey) : void 0;
5892
6097
  const sortKind = sortColumn ? kindOf2(sortColumn) : void 0;
5893
6098
  const locale = useFormatLocale();
5894
- const { sortedAll, unsortable } = useMemo(() => {
6099
+ const { sortedAll, unsortable, childOrder } = useMemo(() => {
5895
6100
  const indexed = data.map((row, i) => ({ row, originalIdx: i }));
5896
- if (!sortKey || !sortDir) return { sortedAll: indexed, unsortable: null };
6101
+ if (!sortKey || !sortDir) return { sortedAll: indexed, unsortable: null, childOrder: null };
5897
6102
  const read = (row) => {
5898
6103
  if (sortColumn?.sortValue) return sortableValue(sortColumn.sortValue(row));
5899
6104
  const v = sortableValue(row[sortKey]);
@@ -5901,29 +6106,33 @@ function Table({
5901
6106
  return (sortKind === "number" ? numberOf(v) : sortKind === "date" ? timeOf(v) : void 0) ?? v;
5902
6107
  };
5903
6108
  const valued = indexed.map((entry) => ({ ...entry, value: read(entry.row) }));
5904
- const elements = valued.some((v) => v.value === null);
5905
- 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) => {
5906
6112
  if (a.value === void 0 || b.value === void 0) return a.value === b.value ? 0 : a.value === void 0 ? 1 : -1;
5907
6113
  const cmp = compareValues(a.value, b.value, locale);
5908
6114
  return sortDir === "asc" ? cmp : -cmp;
5909
- });
5910
- return { sortedAll: sorted, unsortable: elements ? sortKey : null };
5911
- }, [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]);
5912
6120
  const canSort = useMemo(() => {
5913
6121
  const can = /* @__PURE__ */ new Set();
5914
- 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);
5915
6123
  return can;
5916
- }, [columns, data]);
6124
+ }, [columns, allRows]);
5917
6125
  useEffect(() => {
5918
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).`);
5919
6127
  }, [unsortable]);
5920
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));
5921
6130
  const refused = pageSorts ? "" : columns.filter((col) => col.sortable && !canSort.has(col.key)).map((col) => col.key).join(", ");
5922
6131
  useEffect(() => {
5923
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).`);
5924
6133
  }, [refused]);
5925
6134
  useEffect(() => {
5926
- 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.`);
5927
6136
  }, [pager, data.length]);
5928
6137
  const rangeStart = pager ? pager.range[0] : null;
5929
6138
  const rangeEnd2 = pager ? pager.range[1] : null;
@@ -5932,14 +6141,65 @@ function Table({
5932
6141
  [sortedAll, rangeStart, rangeEnd2]
5933
6142
  );
5934
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);
5935
6166
  const selectedSet = new Set(selectedRows);
5936
6167
  const drawnIdx = drawn.map((d) => d.originalIdx);
5937
- const allSelected = drawnIdx.length > 0 && drawnIdx.every((i) => selectedSet.has(i));
5938
- 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
+ };
5939
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
+ }
5940
6200
  const shown = new Set(drawnIdx);
5941
- if (allSelected) onSelectionChange?.(selectedRows.filter((r) => !shown.has(r)));
5942
- 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]);
5943
6203
  };
5944
6204
  const anchor = useRef(null);
5945
6205
  const toggleRow = (idx, shiftKey2 = false) => {
@@ -5948,22 +6208,81 @@ function Table({
5948
6208
  anchor.current = idx;
5949
6209
  if (shiftKey2 && from >= 0 && to >= 0 && from !== to) {
5950
6210
  const run = drawnIdx.slice(Math.min(from, to), Math.max(from, to) + 1);
5951
- onSelectionChange?.([...selectedRows.filter((r) => !run.includes(r)), ...run]);
6211
+ reportRows([...selectedRows.filter((r) => !run.includes(r)), ...run]);
5952
6212
  return;
5953
6213
  }
5954
- if (selectedSet.has(idx)) onSelectionChange?.(selectedRows.filter((r) => r !== idx));
5955
- 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 };
5956
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]);
5957
6264
  const expandControlled = expandedRowsProp !== void 0;
5958
6265
  const [ownExpanded, setOwnExpanded] = useState(defaultExpandedRows ?? []);
5959
6266
  const expanded = expandControlled ? expandedRowsProp : ownExpanded;
5960
6267
  const expandedSet = new Set(expanded.map(String));
5961
- 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
+ }
5962
6282
  useEffect(() => {
5963
- 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.`);
5964
- }, [repeated, rowKey]);
5965
- const idOf = (_row, index) => rowIds[index] ?? index;
5966
- 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);
5967
6286
  const openedHere = useRef(null);
5968
6287
  const toggleExpand = (id) => {
5969
6288
  const opening = !expandedSet.has(String(id));
@@ -5988,9 +6307,11 @@ function Table({
5988
6307
  }, [ref, rootRef]);
5989
6308
  const tableRef = useRef(null);
5990
6309
  const [pins, setPins] = useState(NO_PINS);
5991
- const lead = (selectable ? 1 : 0) + (expandable ? 1 : 0);
5992
- const endCount = columns.filter((c) => c.pin === "end").length;
5993
- 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;
5994
6315
  const { ref: scrollRef, elRef: scrollEl, measure: measureScroll, scrollable, edges, fadeProps } = useScrollEdges({
5995
6316
  enabled: !isMobile,
5996
6317
  noFade: { left: lead + (pins.nameFits ? 1 : 0) > 0, right: endCount > 0 }
@@ -6022,7 +6343,9 @@ function Table({
6022
6343
  const nameFits = nameIndex >= 0 && frame > 0 && at + nameWidth + fromRight <= frame / 2;
6023
6344
  if (nameFits) start.push(at);
6024
6345
  const pad4 = (cell, side) => cell ? parseFloat(getComputedStyle(cell)[side]) || 0 : 0;
6025
- 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;
6026
6349
  const next = { start, end, nameFits, frame, panel };
6027
6350
  setPins((prev) => samePins(prev, next) ? prev : next);
6028
6351
  };
@@ -6032,7 +6355,7 @@ function Table({
6032
6355
  observer.observe(table2);
6033
6356
  observer.observe(scroller);
6034
6357
  return () => observer.disconnect();
6035
- }, [isMobile, lead, endCount, nameIndex, scrollEl, columns, drawn.length]);
6358
+ }, [isMobile, lead, trail, endCount, nameIndex, scrollEl, columns, drawn.length, visibleKeys.length]);
6036
6359
  const pinnedStart = lead + (pins.nameFits ? 1 : 0);
6037
6360
  const pinStyle = (position, total) => {
6038
6361
  if (position < pinnedStart && pins.start[position] !== void 0) {
@@ -6047,7 +6370,7 @@ function Table({
6047
6370
  return void 0;
6048
6371
  };
6049
6372
  const pinFocus = (position, total) => {
6050
- if (!onRowClick || !pinStyle(position, total)) return void 0;
6373
+ if (!(onRowClick || nested) || !pinStyle(position, total)) return void 0;
6051
6374
  const fromEnd = total - 1 - position;
6052
6375
  return cn(PIN_FOCUS, position === 0 && PIN_FOCUS_START, fromEnd === 0 && PIN_FOCUS_END);
6053
6376
  };
@@ -6064,8 +6387,228 @@ function Table({
6064
6387
  if (kind) next.set(col.key, kind);
6065
6388
  }
6066
6389
  setRenderedKinds((prev) => prev.size === next.size && [...next].every(([k, v]) => prev.get(k) === v) ? prev : next);
6067
- }, [columns, kindOf2, drawn, isMobile]);
6068
- 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
+ };
6069
6612
  const shownBefore = useRef(/* @__PURE__ */ new Set());
6070
6613
  const pendingScroll = useRef(null);
6071
6614
  const scrollFrame = useRef(0);
@@ -6079,6 +6622,32 @@ function Table({
6079
6622
  row.scrollIntoView({ block: "nearest" });
6080
6623
  });
6081
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);
6082
6651
  useEffect(() => {
6083
6652
  const now = new Set(expanded.map(String));
6084
6653
  const added = [...now].filter((k) => !shownBefore.current.has(k) && k !== openedHere.current);
@@ -6087,22 +6656,25 @@ function Table({
6087
6656
  if (!expandControlled) return;
6088
6657
  const key = added[added.length - 1];
6089
6658
  if (key === void 0) return;
6090
- if (pager) {
6091
- const position = sortedAll.findIndex((r) => String(idOf(r.row, r.originalIdx)) === key);
6092
- if (position >= 0 && (position < drawnStart || position >= drawnStart + drawn.length)) {
6093
- pendingScroll.current = key;
6094
- pager.setPage(Math.floor(position / pager.pageSize) + 1);
6095
- return;
6096
- }
6659
+ if (tree && !tree.byKey.has(key)) {
6660
+ awaitedRow.current = key;
6661
+ return;
6097
6662
  }
6098
- scrollToRow(key);
6663
+ awaitedRow.current = null;
6664
+ addressRow(key);
6099
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]);
6100
6672
  useEffect(() => {
6101
6673
  const key = pendingScroll.current;
6102
- 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;
6103
6675
  pendingScroll.current = null;
6104
6676
  scrollToRow(key);
6105
- }, [drawnStart, drawn.length, scrollToRow]);
6677
+ }, [drawnStart, drawn.length, visibleKeys.length, scrollToRow]);
6106
6678
  const cardHeading = title ? /* @__PURE__ */ jsx(TableHeading, { title, description, actions, titleId, descriptionId, inset: false }) : null;
6107
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: [
6108
6680
  cardHeading,
@@ -6146,7 +6718,7 @@ function Table({
6146
6718
  if (!row) return;
6147
6719
  const controls = Array.from(row.querySelectorAll(FOCUSABLE));
6148
6720
  const same = was.control === null ? null : controls.find((el) => (el.getAttribute("aria-labelledby") ?? el.getAttribute("aria-label") ?? el.textContent) === was.control);
6149
- (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 });
6150
6722
  }, [isMobile, rootRef]);
6151
6723
  if (isMobile) {
6152
6724
  if (loading && data.length === 0) {
@@ -6167,289 +6739,363 @@ function Table({
6167
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: [
6168
6740
  loading && data.length > 0 && /* @__PURE__ */ jsx(RefreshLine, { className: "inset-x-[4px] rounded-full" }),
6169
6741
  selectable && /* @__PURE__ */ jsx("span", { hidden: true, id: `${headingId}-select`, children: "Select" }),
6170
- drawn.map(({ row, originalIdx }) => {
6171
- const isSelected = selectedSet.has(originalIdx);
6172
- const id = idOf(row, originalIdx);
6173
- const opens = !!expandable && !!renderExpanded;
6174
- const isExpanded = opens && expandedSet.has(String(id));
6175
- const toggleCard = (card) => {
6176
- const closing = isExpanded;
6177
- const inDetail = closing && card.querySelector("[data-card-detail]")?.contains(document.activeElement);
6178
- toggleExpand(id);
6179
- if (inDetail) card.querySelector("[data-card-toggle]")?.focus();
6180
- };
6181
- return /* @__PURE__ */ jsxs(
6182
- "div",
6183
- {
6184
- "data-row": "",
6185
- ...marksOf(row, originalIdx),
6186
- className: cn(
6187
- "border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
6188
- "rounded-[var(--ds-radius-md)] bg-[var(--ds-bg-surface)]",
6189
- "p-ds-4 space-y-ds-3",
6190
- // selected is chosen, not active (D2): the accent border, never the halo
6191
- isSelected && "border-[var(--ds-accent-ink)]",
6192
- onRowClick && ["cursor-pointer hover:bg-[var(--ds-bg-muted)]", FOCUS_RING],
6193
- // with no onRowClick a tap on the card opens and closes it, as a click on the row does; the toggle is the
6194
- // keyboard's way
6195
- !onRowClick && canOpenByClick && "cursor-pointer"
6196
- ),
6197
- onClick: onRowClick ? (e) => {
6198
- if (clickIsCard(e)) onRowClick(row, originalIdx);
6199
- } : canOpenByClick ? (e) => {
6200
- if (clickIsCard(e)) toggleCard(e.currentTarget);
6201
- } : void 0,
6202
- tabIndex: onRowClick ? 0 : void 0,
6203
- onKeyDown: onRowClick ? (e) => {
6204
- if (e.target !== e.currentTarget) return;
6205
- if (e.key === "Enter" || e.key === " ") {
6206
- e.preventDefault();
6207
- 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
+ ]
6208
6836
  }
6209
- } : void 0,
6210
- children: [
6211
- selectable && // "Select" and the row's name, as in the table (T15)
6212
- /* @__PURE__ */ jsx(Checkbox, { checked: isSelected, onChange: (_, { shiftKey: shiftKey2 }) => toggleRow(originalIdx, shiftKey2), "aria-labelledby": nameIndex >= 0 ? `${headingId}-select ${headingId}-card-name-${idPart(id)}` : `${headingId}-select` }),
6213
- columns.map((col, c) => /* @__PURE__ */ jsxs("div", { className: "flex justify-between gap-ds-2 text-sm", children: [
6214
- /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[var(--ds-text-secondary)] font-medium text-xs uppercase tracking-wider", children: col.header }),
6215
- /* @__PURE__ */ jsx(CardValue, { value: row[col.key], id: lead + c === nameIndex ? `${headingId}-card-name-${idPart(id)}` : void 0 })
6216
- ] }, col.key)),
6217
- opens && // Alert's disclosure: a button that says whether it is open, at the card's foot, the detail below it, so
6218
- // the control the user pressed stays where it was. Its name carries the row's (T15).
6219
- /* @__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
6220
6900
  /* @__PURE__ */ jsxs(
6221
6901
  "button",
6222
6902
  {
6223
6903
  type: "button",
6224
- "data-card-toggle": "",
6225
- "aria-expanded": isExpanded,
6226
- ...nameIndex >= 0 ? { "aria-labelledby": `${headingId}-card-toggle-${idPart(id)} ${headingId}-card-name-${idPart(id)}` } : {},
6227
- onClick: (e) => {
6228
- e.stopPropagation();
6229
- toggleCard(e.currentTarget.closest("[data-row]"));
6230
- },
6904
+ onClick: () => handleSort(col.key),
6905
+ ...nested ? { "data-grid-key": gridKey(`${HEAD_KEY}${col.key}`), tabIndex: heldOuter ? -1 : grid.tabIndexOf(`${HEAD_KEY}${col.key}`) } : {},
6231
6906
  className: cn(
6232
- // 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)
6233
- "inline-flex min-h-[24px] items-center gap-ds-1 text-xs font-medium cursor-pointer",
6234
- "transition-colors duration-[var(--ds-transition-speed)]",
6235
- "text-[var(--ds-accent-ink)] hover:bg-[var(--ds-accent-muted)]",
6236
- "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)]",
6237
6917
  FOCUS_RING
6238
6918
  ),
6239
6919
  children: [
6240
- /* @__PURE__ */ jsx(Icon, { name: "chevron-right", size: 12, className: cn("transition-transform duration-[var(--ds-transition-speed)]", isExpanded && "rotate-90") }),
6241
- /* @__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 })
6242
6922
  ]
6243
6923
  }
6244
- ),
6245
- 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) }) })
6246
- ] })
6247
- ]
6248
- },
6249
- String(id)
6250
- );
6251
- })
6252
- ] }) }) });
6253
- return withControl(title ? titledCards(cards) : cards);
6254
- }
6255
- const cellCount = columns.length + lead;
6256
- 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: [
6257
- /* @__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: [
6258
- selectable && // data-ds-select-all: the selection's own control, where a BulkActionBar's Clear selection sends focus
6259
- /* @__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(
6260
- 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",
6261
6942
  {
6262
- checked: allSelected,
6263
- indeterminate: someSelected,
6264
- onChange: toggleAll,
6265
- "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
6266
6946
  }
6267
- ) }),
6268
- 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) }),
6269
- columns.map((col, c) => {
6270
- const isSortable = col.header.trim() !== "" && (pageSorts ? col.sortable ?? (!!sortable && canSort.has(col.key)) : (col.sortable ?? !!sortable) && canSort.has(col.key));
6271
- const isActiveSort = sortKey === col.key && !!sortDir && (pageSorts || unsortable !== col.key);
6272
- const headerKind = kindOf2(col);
6273
- const headerAlign = col.align ?? (headerKind === "number" ? "right" : "left");
6274
- const shrink = (headerKind === "number" || headerKind === "date") && !col.maxWidth;
6275
- const style = { maxWidth: col.maxWidth, width: col.width, minWidth: col.minWidth, ...pinStyle(lead + c, cellCount) };
6276
- return /* @__PURE__ */ jsx(
6277
- "th",
6278
- {
6279
- "aria-sort": isSortable && isActiveSort ? sortDir === "asc" ? "ascending" : "descending" : void 0,
6280
- className: cn(
6281
- density === "compact" ? "px-ds-3 py-ds-2" : "px-ds-4 py-ds-3",
6282
- shrink && "w-px whitespace-nowrap",
6283
- "font-[var(--ds-weight-heading)] text-[var(--ds-text-secondary)]",
6284
- "text-xs uppercase tracking-wider",
6285
- headerAlign === "right" ? "text-right" : headerAlign === "center" ? "text-center" : "text-left"
6286
- ),
6287
- style,
6288
- children: isSortable ? (
6289
- // A47: a button with the column's name, reached with Tab and pressed with Enter or Space
6290
- /* @__PURE__ */ jsxs(
6291
- "button",
6292
- {
6293
- type: "button",
6294
- onClick: () => handleSort(col.key),
6295
- className: cn(
6296
- "inline-flex items-center gap-ds-1 uppercase tracking-wider cursor-pointer select-none",
6297
- "[font:inherit] text-inherit hover:text-[var(--ds-text-primary)]",
6298
- "rounded-[var(--ds-radius-sm)]",
6299
- // in a right-aligned column the indicator goes before the label, so the label ends where the
6300
- // column's values end; the sorted column's label in full colour (the look check, 14 October).
6301
- // Set at the line's top, not its baseline: reversed, the button takes its baseline from the
6302
- // indicator, which has no text, and stood 2 px above the other headers' labels
6303
- headerAlign === "right" && "flex-row-reverse",
6304
- "align-top",
6305
- isActiveSort && "text-[var(--ds-text-primary)]",
6306
- FOCUS_RING
6307
- ),
6308
- children: [
6309
- col.header,
6310
- /* @__PURE__ */ jsx(SortIndicator, { sorted: isActiveSort ? sortDir : false })
6311
- ]
6312
- }
6313
- )
6314
- ) : /* @__PURE__ */ jsx("span", { className: "inline-flex items-center gap-ds-1", children: col.header })
6315
- },
6316
- col.key
6317
- );
6318
- })
6319
- ] }) }),
6320
- /* @__PURE__ */ jsx("tbody", { children: /* @__PURE__ */ jsx(RowsCtx, { value: true, children: loading && data.length === 0 ? (
6321
- // the rows' shape on a first load (item 16), each cell's bar where its text will be; the rows are hidden
6322
- // from a screen reader, which hears the status beside the table
6323
- 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: [
6324
- 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" }) }),
6325
- 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") }),
6326
- 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))
6327
- ] }, `skeleton-${r}`))
6328
- ) : data.length === 0 && emptyState ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx(
6329
- "td",
6330
- {
6331
- colSpan: cellCount,
6332
- className: "px-ds-4 py-ds-10 text-center text-sm text-[var(--ds-text-muted)]",
6333
- children: emptyState
6334
- }
6335
- ) }) : drawn.map(({ row, originalIdx }, i) => {
6336
- const isSelected = selectedSet.has(originalIdx);
6337
- const id = idOf(row, originalIdx);
6338
- const isExpanded = expandedSet.has(String(id));
6339
- const clickable = !!onRowClick || canOpenByClick;
6340
- return /* @__PURE__ */ jsxs(Fragment$1, { children: [
6341
- /* @__PURE__ */ jsxs(
6342
- "tr",
6343
- {
6344
- ...marksOf(row, originalIdx),
6345
- className: cn(
6346
- // an opened row's divider is under its panel (A6)
6347
- !(expandable && isExpanded && renderExpanded) && "border-b-[length:var(--ds-divider-width)] border-[var(--ds-border-subtle)] last:border-b-0",
6348
- // the row's background and its tint change together, so its pinned cells and the rest fade as one
6349
- // (roots.css registers --gs-row-tint as a colour so it can)
6350
- "transition-[background-color,--gs-row-tint]",
6351
- // the row's tint, as a variable too, so its pinned cells draw it over their solid ground (A35)
6352
- 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)]",
6353
- clickable && "cursor-pointer",
6354
- // an outline inside the row, clear of the frame's rounded corners (D8): it shows over the pinned
6355
- // cells, whose solid ground hid the inset shadow the row drew before (the review of 30 September)
6356
- onRowClick && FOCUS_INSET
6357
- ),
6358
- onClick: clickable ? (e) => {
6359
- if (!clickIsRows(e)) return;
6360
- if (onRowClick) onRowClick(row, originalIdx);
6361
- else toggleExpand(id);
6362
- } : void 0,
6363
- tabIndex: onRowClick ? 0 : void 0,
6364
- onKeyDown: onRowClick ? (e) => {
6365
- if (e.target !== e.currentTarget) return;
6366
- if (e.key === "Enter" || e.key === " ") {
6367
- e.preventDefault();
6368
- onRowClick(row, originalIdx);
6369
- }
6370
- } : void 0,
6371
- children: [
6372
- 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` }) }),
6373
- 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(
6374
- "button",
6375
- {
6376
- type: "button",
6377
- onClick: () => toggleExpand(id),
6378
- className: cn("text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)] cursor-pointer", FOCUS_RING),
6379
- "aria-expanded": isExpanded,
6380
- ...nameIndex >= 0 ? { "aria-labelledby": `${headingId}-details ${headingId}-name-${idPart(id)}` } : { "aria-label": "Details" },
6381
- children: /* @__PURE__ */ jsx(
6382
- Icon,
6383
- {
6384
- name: "chevron-right",
6385
- size: 14,
6386
- className: cn("transition-transform", isExpanded && "rotate-90")
6387
- }
6388
- )
6389
- }
6390
- ) }) }),
6391
- columns.map((col, c) => {
6392
- const truncLines = col.truncate === true ? 1 : col.truncate ? col.truncate.lines : 0;
6393
- const kind = kindOf2(col);
6394
- const alignClass = col.align ? col.align === "right" ? "text-right" : col.align === "center" ? "text-center" : "text-left" : kind === "number" ? "text-right" : "text-left";
6395
- const cellContent = row[col.key];
6396
- const shrink = (kind === "number" || kind === "date") && !col.maxWidth;
6397
- const whole2 = !kind && renderedKinds.has(col.key);
6398
- let inner;
6399
- if (truncLines > 0) {
6400
- inner = /* @__PURE__ */ jsx(CutCell, { lines: truncLines, children: cellContent });
6401
- } else if (kind === "email") {
6402
- inner = /* @__PURE__ */ jsx(CutEmail, { children: cellContent });
6403
- } else {
6404
- inner = cellContent;
6405
- }
6406
- const align = col.align ?? (kind === "number" ? "right" : "left");
6407
- if (align !== "left" && cellContent !== null && cellContent !== void 0 && typeof cellContent !== "string" && typeof cellContent !== "number") {
6408
- inner = /* @__PURE__ */ jsx("div", { className: cn("flex items-center", align === "center" ? "justify-center" : "justify-end"), children: inner });
6409
- }
6410
- return /* @__PURE__ */ jsx(
6411
- "td",
6412
- {
6413
- id: lead > 0 && lead + c === nameIndex ? `${headingId}-name-${idPart(id)}` : void 0,
6414
- "data-col": col.key,
6415
- className: cn(
6416
- density === "compact" ? "px-ds-3 py-ds-2" : "px-ds-4 py-ds-3",
6417
- "text-[var(--ds-text-primary)]",
6418
- alignClass,
6419
- shrink && "w-px whitespace-nowrap",
6420
- whole2 && "whitespace-nowrap",
6421
- kind === "identifier" && "[overflow-wrap:anywhere]",
6422
- pinFocus(lead + c, cellCount)
6423
- ),
6424
- 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,
6425
- children: inner
6426
- },
6427
- col.key
6428
- );
6429
- })
6430
- ]
6431
- }
6432
- ),
6433
- 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(
6434
- "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",
6435
6968
  {
6436
- colSpan: cellCount,
6437
- className: cn("py-ds-4 text-sm text-[var(--ds-text-primary)]", !pins.panel && (density === "compact" ? "px-ds-3" : "px-ds-4")),
6438
- 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" },
6439
6974
  children: /* @__PURE__ */ jsx(
6440
- "div",
6975
+ Icon,
6441
6976
  {
6442
- "data-row-panel": "",
6443
- className: "sticky",
6444
- style: pins.panel ? { left: pins.panel[0], maxWidth: pins.frame ? pins.frame - pins.panel[0] - pins.panel[1] : void 0 } : { left: 0 },
6445
- children: /* @__PURE__ */ jsx(PanelContent, { children: renderExpanded(row, originalIdx) })
6977
+ name: "chevron-right",
6978
+ size: 14,
6979
+ className: cn("transition-transform", isExpanded && "rotate-90")
6446
6980
  }
6447
6981
  )
6448
6982
  }
6449
- ) })
6450
- ] }, String(id));
6451
- }) }) })
6452
- ] });
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
+ );
6453
7099
  const region = scrollable || regionFocused ? { tabIndex: 0, role: "region", ...title ? { "aria-labelledby": titleId } : { "aria-label": ariaLabel ?? "Table" } } : {};
6454
7100
  return withControl(
6455
7101
  /* @__PURE__ */ jsxs(
@@ -6465,7 +7111,7 @@ function Table({
6465
7111
  loading && data.length > 0 && /* @__PURE__ */ jsx(RefreshLine, { className: "inset-x-0 rounded-t-[inherit]" }),
6466
7112
  loading && data.length === 0 && /* @__PURE__ */ jsx("span", { role: "status", className: "sr-only", children: LOADING_LABEL }),
6467
7113
  title && /* @__PURE__ */ jsx(TableHeading, { title, description, actions, titleId, descriptionId, inset: true }),
6468
- 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" }),
6469
7115
  selectable && /* @__PURE__ */ jsx("span", { hidden: true, id: `${headingId}-select`, children: "Select" }),
6470
7116
  expandable && /* @__PURE__ */ jsx("span", { hidden: true, id: `${headingId}-details`, children: "Details" }),
6471
7117
  /* @__PURE__ */ jsx(
@@ -6491,266 +7137,32 @@ function Table({
6491
7137
  )
6492
7138
  );
6493
7139
  }
6494
- function optionsKeyForFit(options) {
6495
- return options.map((o) => `${o.value}:${o.label}`).join("|");
6496
- }
6497
- var sizes2 = {
6498
- xs: "text-xs px-ds-2",
6499
- sm: "text-xs px-ds-3",
6500
- md: "text-sm px-ds-3",
6501
- lg: "text-base px-ds-3"
6502
- };
6503
- var containerPadding = {
6504
- xs: "p-[2px] gap-[2px]",
6505
- sm: "p-ds-1 gap-ds-0.5",
6506
- md: "p-ds-1 gap-ds-0.5",
6507
- lg: "p-ds-1 gap-ds-0.5"
6508
- };
6509
- var indicatorInset = {
6510
- xs: "2px",
6511
- sm: "4px",
6512
- md: "4px",
6513
- lg: "4px"
6514
- };
6515
- function segmentRadius(inset, bordered) {
6516
- return `max(var(--ds-radius-sm), calc(var(--ds-radius-md) - ${inset}${bordered ? " - var(--ds-border-width)" : ""}))`;
6517
- }
6518
- function SegmentedControl({
6519
- value,
6520
- onChange: onChangeProp,
6521
- options,
6522
- size: sizeProp,
6523
- variant = "default",
6524
- fullWidth = false,
6525
- onHoverChange,
6526
- className,
6527
- "aria-label": ariaLabel,
6528
- maxOptions = 6,
6529
- ...wiring
6530
- }) {
6531
- const onChange = useReportedChange(onChangeProp, value);
6532
- const contextSize = useContext(ControlSizeContext);
6533
- const control = useFieldControl(wiring);
6534
- const named = !ariaLabel && control.labelId ? { "aria-labelledby": control.labelId } : {};
6535
- const size = sizeProp ?? contextSize ?? "md";
6536
- const containerRef = useRef(null);
6537
- const [indicator, setIndicator] = useState({ left: 0, width: 0 });
6538
- const parentRef = useRef(null);
6539
- const neededRef = useRef(0);
6540
- const [overflows, setOverflows] = useState(false);
6541
- const measureFit = useCallback(() => {
6542
- const row = containerRef.current;
6543
- if (row) {
6544
- parentRef.current = row.parentElement;
6545
- if (row.scrollWidth > 0) neededRef.current = row.scrollWidth;
6546
- }
6547
- const parent = parentRef.current;
6548
- const needed = neededRef.current;
6549
- if (!parent || !needed) return;
6550
- const style = getComputedStyle(parent);
6551
- const available = parent.clientWidth - (parseFloat(style.paddingLeft) || 0) - (parseFloat(style.paddingRight) || 0);
6552
- if (available <= 0) return;
6553
- setOverflows((prev) => prev ? needed > available - 8 : needed > available);
6554
- }, []);
6555
- useLayoutEffect(() => {
6556
- measureFit();
6557
- const parent = parentRef.current;
6558
- if (!parent || typeof ResizeObserver === "undefined") return;
6559
- const observer = new ResizeObserver(measureFit);
6560
- observer.observe(parent);
6561
- return () => observer.disconnect();
6562
- }, [measureFit, optionsKeyForFit(options)]);
6563
- const collapsed = options.length > maxOptions || overflows;
6564
- const updateIndicator = useCallback(() => {
6565
- if (!containerRef.current) return;
6566
- const active = containerRef.current.querySelector("[data-active='true']");
6567
- if (active) {
6568
- setIndicator({
6569
- left: active.offsetLeft,
6570
- width: active.offsetWidth
6571
- });
6572
- }
6573
- }, []);
6574
- const optionsKey = options.map((o) => `${o.value}:${o.label}`).join("|");
6575
- useLayoutEffect(() => {
6576
- updateIndicator();
6577
- }, [value, optionsKey, updateIndicator]);
6578
- useEffect(() => {
6579
- const fonts = document.fonts;
6580
- if (!fonts?.ready) return;
6581
- let live = true;
6582
- fonts.ready.then(() => {
6583
- if (live) updateIndicator();
6584
- });
6585
- return () => {
6586
- live = false;
6587
- };
6588
- }, [updateIndicator]);
6589
- const [animate, setAnimate] = useState(false);
6590
- const select = useCallback((v) => {
6591
- setAnimate(true);
6592
- onChange(v);
6593
- }, [onChange]);
6594
- useLayoutEffect(() => {
6595
- if (collapsed || !containerRef.current || typeof ResizeObserver === "undefined") return;
6596
- const observer = new ResizeObserver(updateIndicator);
6597
- observer.observe(containerRef.current);
6598
- containerRef.current.querySelectorAll("button").forEach((b) => observer.observe(b));
6599
- return () => observer.disconnect();
6600
- }, [updateIndicator, optionsKey, collapsed]);
6601
- const containerHeight = {
6602
- xs: "h-ds-6",
6603
- sm: "h-[var(--ds-control-height-sm)]",
6604
- md: "h-[var(--ds-control-height-md)]",
6605
- lg: "h-[var(--ds-control-height-lg)]"
6606
- };
6607
- const tabStop = rovingTabStop(options, (o) => o.value === value, (o) => !!o.disabled);
6608
- const handleKeyDown = (e) => {
6609
- const enabled = options.filter((o) => !o.disabled);
6610
- if (enabled.length === 0) return;
6611
- const idx = Math.max(0, enabled.findIndex((o) => o.value === value));
6612
- let next = null;
6613
- if (e.key === "ArrowRight" || e.key === "ArrowDown") next = (idx + 1) % enabled.length;
6614
- else if (e.key === "ArrowLeft" || e.key === "ArrowUp") next = (idx - 1 + enabled.length) % enabled.length;
6615
- else if (e.key === "Home") next = 0;
6616
- else if (e.key === "End") next = enabled.length - 1;
6617
- if (next === null) return;
6618
- e.preventDefault();
6619
- select(enabled[next].value);
6620
- const buttons = containerRef.current?.querySelectorAll('[role="radio"]');
6621
- buttons?.forEach((b) => {
6622
- if (b.dataset.value === enabled[next].value) b.focus();
6623
- });
6624
- };
6625
- if (collapsed) {
6626
- return /* @__PURE__ */ jsx(
6627
- Dropdown,
6628
- {
6629
- "aria-label": ariaLabel,
6630
- ...wiring,
6631
- size: size === "md" || size === "lg" ? "md" : "sm",
6632
- options: options.filter((o) => !o.disabled).map((o) => ({ id: o.value, label: o.label })),
6633
- value,
6634
- onChange: (v) => onChange(v),
6635
- className: cn("inline-block", fullWidth ? "w-full" : "w-auto", className)
6636
- }
6637
- );
6638
- }
6639
- return /* @__PURE__ */ jsxs(
6640
- "div",
6641
- {
6642
- ref: containerRef,
6643
- role: "radiogroup",
6644
- id: control.id,
6645
- "aria-label": ariaLabel,
6646
- ...named,
6647
- "aria-describedby": control.describedBy,
6648
- "aria-invalid": control.invalid || void 0,
6649
- "aria-required": control.required || void 0,
6650
- onKeyDown: handleKeyDown,
6651
- className: cn(
6652
- "relative items-center",
6653
- // 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,
6654
- // so a row or a centred column still aligns it (the review of 30 September)
6655
- fullWidth ? "flex w-full" : "inline-flex w-fit",
6656
- containerPadding[size],
6657
- containerHeight[size],
6658
- "bg-[var(--ds-bg-muted)]",
6659
- // The owner's review of 1 October 2026: the frame's corner is a field's, so the two change together with the
6660
- // theme's radius; the segments inside sit concentric with it (SEGMENT_RADIUS)
6661
- "rounded-[var(--ds-radius-md)]",
6662
- // The frame's edge is a button's: each segment is a labelled button, so the frame isn't what identifies the
6663
- // control, and WCAG 1.4.11 asks no 3:1 of it
6664
- variant === "default" && "border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
6665
- control.invalid && "border-[length:var(--ds-border-width)] border-[var(--ds-status-error)]",
6666
- className
6667
- ),
6668
- style: { "--ds-segment-radius": segmentRadius(indicatorInset[size], variant === "default" || !!control.invalid) },
6669
- children: [
6670
- /* @__PURE__ */ jsx(
6671
- "div",
6672
- {
6673
- className: cn(
6674
- "absolute rounded-[var(--ds-segment-radius)]",
6675
- "bg-[var(--ds-bg-surface)]",
6676
- // The selected segment is a kind of button, so it takes a secondary button's surface and edge, as the frame
6677
- // takes a button's edge (the owner, 3 October 2026: the 3:1 strong edge it kept from Sprint 25.14 drew the
6678
- // darker outline 25.19 took off the fields). It shows its state as the raised surface on the muted track, with
6679
- // the default variant's shadow and its label's stronger colour. While its segment has keyboard focus the shadow
6680
- // steps aside, as a button's does, so the segment's outline sits clear (Sprint 30, D7)
6681
- "border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
6682
- variant === "default" && "shadow-[var(--ds-shadow-sm)]",
6683
- "[[role=radiogroup]:has([aria-checked=true]:focus-visible)_&]:shadow-none",
6684
- "transition-all motion-reduce:transition-none"
6685
- ),
6686
- "data-animate": animate ? "true" : "false",
6687
- style: {
6688
- left: indicator.left,
6689
- width: indicator.width,
6690
- top: indicatorInset[size],
6691
- bottom: indicatorInset[size],
6692
- transitionDuration: animate ? "var(--ds-transition-speed)" : "0ms",
6693
- transitionTimingFunction: "var(--ds-transition-ease)"
6694
- }
6695
- }
6696
- ),
6697
- options.map((opt, i) => /* @__PURE__ */ jsx(
6698
- "button",
6699
- {
6700
- type: "button",
6701
- role: "radio",
6702
- "aria-checked": opt.value === value,
6703
- "data-active": opt.value === value,
6704
- "data-value": opt.value,
6705
- tabIndex: i === tabStop ? 0 : -1,
6706
- disabled: opt.disabled,
6707
- onClick: () => !opt.disabled && select(opt.value),
6708
- onMouseEnter: onHoverChange && !opt.disabled ? () => onHoverChange(opt.value) : void 0,
6709
- onMouseLeave: onHoverChange ? () => onHoverChange(null) : void 0,
6710
- className: cn(
6711
- "relative z-10 inline-flex items-center justify-center self-stretch font-medium whitespace-nowrap",
6712
- fullWidth && "flex-1",
6713
- "rounded-[var(--ds-segment-radius)]",
6714
- "transition-colors cursor-pointer",
6715
- FOCUS_RING,
6716
- sizes2[size],
6717
- opt.value === value ? "text-[var(--ds-text-primary)]" : "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-secondary)]",
6718
- opt.disabled && "opacity-disabled cursor-not-allowed"
6719
- ),
6720
- children: opt.label
6721
- },
6722
- opt.value
6723
- ))
6724
- ]
6725
- }
6726
- );
6727
- }
6728
7140
 
6729
7141
  // ../../showcase/src/components/Calendar/event-colours.ts
6730
7142
  function eventColours(category) {
6731
7143
  const colour = category?.color;
6732
7144
  if (colour === void 0 || colour === "accent" || colour === "var(--ds-accent-primary)") return ACCENT;
6733
- if (colour === "neutral") return NEUTRAL;
6734
- 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;
6735
7147
  const named = SUPPORTING_TOKEN.exec(colour.trim());
6736
7148
  if (named) return supporting(Number(named[1]));
6737
7149
  const chart = CHART_TOKEN.exec(colour.trim());
6738
7150
  if (chart) return Number(chart[1]) === 1 ? ACCENT : supporting(Number(chart[1]) - 1);
6739
- return { ...NEUTRAL, edge: colour };
7151
+ return { ...NEUTRAL2, edge: colour };
6740
7152
  }
6741
7153
  var SUPPORTING_TOKEN = /^var\(\s*--ds-palette-(\d+)-[a-z-]+\s*\)$/;
6742
7154
  var CHART_TOKEN = /^var\(\s*--ds-chart-(\d+)\s*\)$/;
6743
7155
  var supporting = (n) => ({
6744
- fill: `var(--ds-palette-${n}-bg, ${NEUTRAL.fill})`,
6745
- text: `var(--ds-palette-${n}-text, ${NEUTRAL.text})`,
6746
- 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})`
6747
7159
  });
6748
7160
  var ACCENT = {
6749
7161
  fill: "var(--ds-accent-bg, var(--ds-accent-subtle))",
6750
7162
  text: "var(--ds-accent-text, var(--ds-text-primary))",
6751
7163
  edge: "var(--ds-chart-1)"
6752
7164
  };
6753
- var NEUTRAL = {
7165
+ var NEUTRAL2 = {
6754
7166
  fill: "var(--ds-bg-muted)",
6755
7167
  text: "var(--ds-text-primary)",
6756
7168
  edge: "var(--ds-control-edge-strong)"
@@ -8849,8 +9261,10 @@ function ExpandableDescription({ children, className }) {
8849
9261
  }
8850
9262
  );
8851
9263
  }
8852
- function HorizontalLayout({ items, minItemWidth, className }) {
8853
- return /* @__PURE__ */ jsx("div", { "data-timeline-row": "", className: cn("relative flex items-start overflow-x-auto snap-x", className), children: items.map((item, i) => {
9264
+ function HorizontalLayout({ items, minItemWidth, label, className }) {
9265
+ const [row, setRow] = useState(null);
9266
+ useKeyboardScroll(row, label, true, false, "x");
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) => {
8854
9268
  const isFirst = i === 0;
8855
9269
  const isLast = i === items.length - 1;
8856
9270
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-1 shrink-0 flex-col items-center snap-start", style: { minWidth: minItemWidth }, children: [
@@ -8928,7 +9342,7 @@ function VerticalLayout({ items, className }) {
8928
9342
  ] })
8929
9343
  ] }, i)) });
8930
9344
  }
8931
- 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" }) {
8932
9346
  useScrollsInside();
8933
9347
  useWholeListCheck("Timeline", allItems.length, pager);
8934
9348
  const items = pagerPart(allItems, pager);
@@ -8941,7 +9355,7 @@ function Timeline({ items: allItems, orientation = "vertical", minItemWidth = 20
8941
9355
  }, [ref]);
8942
9356
  const fits = width === 0 || width >= items.length * minItemWidth;
8943
9357
  const horizontal = orientation === "horizontal" || orientation === "responsive" && fits;
8944
- 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 }) }) });
8945
9359
  if (!pager) return list2;
8946
9360
  return /* @__PURE__ */ jsxs("div", { "data-gs-paged": "", className: cn("flex flex-col min-w-0", className), children: [
8947
9361
  list2,
@@ -12281,7 +12695,7 @@ function useScrollTrack(stopTab, enabled) {
12281
12695
  };
12282
12696
  }
12283
12697
  var SCROLL_TRACK = "overflow-x-auto overflow-y-hidden [scrollbar-width:none] [&::-webkit-scrollbar]:hidden";
12284
- function TabStrip({ overflow, fullWidth, track, className, children }) {
12698
+ function TabStrip({ overflow, fullWidth, track, label, className, children }) {
12285
12699
  const scrolls = overflow === "scroll";
12286
12700
  return /* @__PURE__ */ jsx("div", { className: cn("min-w-0 max-w-full", fullWidth && "w-full", className), children: /* @__PURE__ */ jsx(
12287
12701
  "div",
@@ -12289,7 +12703,7 @@ function TabStrip({ overflow, fullWidth, track, className, children }) {
12289
12703
  ...scrolls ? track : {},
12290
12704
  "data-gs-clip": scrolls ? void 0 : "",
12291
12705
  className: cn("relative -m-[4px] p-[4px] scroll-px-[4px]", scrolls ? SCROLL_TRACK : "overflow-hidden"),
12292
- children: /* @__PURE__ */ jsx("div", { role: "tablist", className: cn(trackClasses("horizontal"), scrolls ? "w-max min-w-full" : "w-full"), children })
12706
+ children: /* @__PURE__ */ jsx("div", { role: "tablist", "aria-label": label, className: cn(trackClasses("horizontal"), scrolls ? "w-max min-w-full" : "w-full"), children })
12293
12707
  }
12294
12708
  ) });
12295
12709
  }
@@ -12450,7 +12864,7 @@ function TabButton({
12450
12864
  return btn;
12451
12865
  }
12452
12866
  function TabsList({ children, className }) {
12453
- const { orientation, fullWidth, overflow, stopTab } = useTabsCtx();
12867
+ const { orientation, fullWidth, overflow, stopTab, label } = useTabsCtx();
12454
12868
  const track = useScrollTrack(stopTab, orientation === "horizontal" && overflow === "scroll");
12455
12869
  if (orientation === "vertical") {
12456
12870
  return /* @__PURE__ */ jsx(
@@ -12458,12 +12872,13 @@ function TabsList({ children, className }) {
12458
12872
  {
12459
12873
  role: "tablist",
12460
12874
  "aria-orientation": "vertical",
12875
+ "aria-label": label,
12461
12876
  className: cn(trackClasses("vertical"), className),
12462
12877
  children
12463
12878
  }
12464
12879
  );
12465
12880
  }
12466
- return /* @__PURE__ */ jsx(TabStrip, { overflow, fullWidth, track, className, children });
12881
+ return /* @__PURE__ */ jsx(TabStrip, { overflow, fullWidth, track, label, className, children });
12467
12882
  }
12468
12883
  function TabsTrigger({ value, children, count, icon, disabled, truncate, className }) {
12469
12884
  const { activeTab, onTabChange, orientation, fullWidth, overflow, activation, stopTab, setFocusedTab } = useTabsCtx();
@@ -12510,6 +12925,7 @@ function TabsRoot(props) {
12510
12925
  fullWidth = false,
12511
12926
  overflow = "scroll",
12512
12927
  activation = "automatic",
12928
+ "aria-label": ariaLabel,
12513
12929
  className
12514
12930
  } = props;
12515
12931
  const isMobile = useBreakpoint("md");
@@ -12533,6 +12949,7 @@ function TabsRoot(props) {
12533
12949
  options: tabs.filter((t) => !t.disabled).map((t) => ({ id: t.id, label: t.label, group: t.group })),
12534
12950
  value: activeTab,
12535
12951
  onChange: (v) => onTabChange(v),
12952
+ "aria-label": ariaLabel ?? "Contents",
12536
12953
  className
12537
12954
  }
12538
12955
  ) })
@@ -12544,6 +12961,7 @@ function TabsRoot(props) {
12544
12961
  {
12545
12962
  role: "tablist",
12546
12963
  "aria-orientation": "vertical",
12964
+ "aria-label": ariaLabel,
12547
12965
  className: cn(trackClasses("vertical"), className),
12548
12966
  children: tabs.map((tab, i) => /* @__PURE__ */ jsxs(Fragment$1, { children: [
12549
12967
  tab.group && tab.group !== tabs[i - 1]?.group && /* @__PURE__ */ jsx(
@@ -12562,9 +12980,9 @@ function TabsRoot(props) {
12562
12980
  }
12563
12981
  );
12564
12982
  }
12565
- return /* @__PURE__ */ jsx(TabStrip, { overflow: arrayOverflow, fullWidth, track, className, children: tabs.map((tab, i) => /* @__PURE__ */ jsx(TabButton, { hasPanel: false, ...tab, orientation: effectiveOrientation, activeTab, onTabChange, fullWidth, isTabStop: i === stopIndex, setFocusedTab, onKeyDown: (e) => handleTabKeyDown(e, tab.id, () => tabs, effectiveOrientation, select) }, tab.id)) });
12983
+ return /* @__PURE__ */ jsx(TabStrip, { overflow: arrayOverflow, fullWidth, track, label: ariaLabel, className, children: tabs.map((tab, i) => /* @__PURE__ */ jsx(TabButton, { hasPanel: false, ...tab, orientation: effectiveOrientation, activeTab, onTabChange, fullWidth, isTabStop: i === stopIndex, setFocusedTab, onKeyDown: (e) => handleTabKeyDown(e, tab.id, () => tabs, effectiveOrientation, select) }, tab.id)) });
12566
12984
  }
12567
- return /* @__PURE__ */ jsx(TabsContext.Provider, { value: { activeTab, onTabChange, orientation: effectiveOrientation, fullWidth, overflow, activation, stopTab, setFocusedTab }, children: /* @__PURE__ */ jsx("div", { className, children: props.children }) });
12985
+ return /* @__PURE__ */ jsx(TabsContext.Provider, { value: { activeTab, onTabChange, orientation: effectiveOrientation, fullWidth, overflow, activation, stopTab, setFocusedTab, label: ariaLabel }, children: /* @__PURE__ */ jsx("div", { className, children: props.children }) });
12568
12986
  }
12569
12987
  var Tabs = Object.assign(TabsRoot, {
12570
12988
  List: TabsList,
@@ -12614,7 +13032,7 @@ function SettingsLayout({
12614
13032
  const stickyTop = shell ? "var(--ds-sticky-offset, 0px)" : "calc(var(--ds-navbar-height) + var(--ds-sticky-offset, 0px))";
12615
13033
  if (layout === "tabs") {
12616
13034
  return /* @__PURE__ */ jsxs("div", { ref: rootRef, "data-settings-layout": "tabs", className: cn("flex flex-col gap-ds-6", className), children: [
12617
- /* @__PURE__ */ jsx("nav", { "aria-label": ariaLabel, children: /* @__PURE__ */ jsx(Tabs, { orientation: "horizontal", mobile: "dropdown", tabs, activeTab: value, onTabChange: onChange }) }),
13035
+ /* @__PURE__ */ jsx("nav", { "aria-label": ariaLabel, children: /* @__PURE__ */ jsx(Tabs, { orientation: "horizontal", mobile: "dropdown", tabs, activeTab: value, onTabChange: onChange, "aria-label": ariaLabel }) }),
12618
13036
  /* @__PURE__ */ jsx("div", { "data-settings-panel": "", className: PANEL_CLASSES, style: PANEL_STYLE, children })
12619
13037
  ] });
12620
13038
  }
@@ -12636,7 +13054,7 @@ function SettingsLayout({
12636
13054
  );
12637
13055
  }
12638
13056
  return /* @__PURE__ */ jsxs("div", { ref: rootRef, "data-settings-layout": "sidebar", className: cn("flex flex-row items-start gap-ds-8", className), children: [
12639
- /* @__PURE__ */ jsx("nav", { "aria-label": ariaLabel, className: "sticky self-start shrink-0", style: { width: `${railWidth}px`, top: stickyTop }, children: /* @__PURE__ */ jsx(Tabs, { orientation: "vertical", tabs, activeTab: value, onTabChange: onChange, className: "w-full" }) }),
13057
+ /* @__PURE__ */ jsx("nav", { "aria-label": ariaLabel, className: "sticky self-start shrink-0", style: { width: `${railWidth}px`, top: stickyTop }, children: /* @__PURE__ */ jsx(Tabs, { orientation: "vertical", tabs, activeTab: value, onTabChange: onChange, "aria-label": ariaLabel, className: "w-full" }) }),
12640
13058
  /* @__PURE__ */ jsx("div", { "data-settings-panel": "", className: cn("flex-1", PANEL_CLASSES), style: PANEL_STYLE, children })
12641
13059
  ] });
12642
13060
  }
@@ -12842,11 +13260,12 @@ function ScrollArea({
12842
13260
  className,
12843
13261
  orientation = "vertical",
12844
13262
  maxHeight,
12845
- maxWidth
13263
+ maxWidth,
13264
+ "aria-label": ariaLabel = "Scrollable content"
12846
13265
  }) {
12847
13266
  const viewportRef = useRef(null);
12848
13267
  const [viewport, setViewport] = useState(null);
12849
- useRevealFocus(viewport);
13268
+ useKeyboardScroll(viewport, ariaLabel, true, false, orientation === "horizontal" ? "x" : orientation === "both" ? "both" : "y");
12850
13269
  const viewportRefs = useCallback((node) => {
12851
13270
  viewportRef.current = node;
12852
13271
  setViewport(node);
@@ -12941,7 +13360,9 @@ function ScrollArea({
12941
13360
  ref: viewportRefs,
12942
13361
  onScroll: updateThumbs,
12943
13362
  className: cn(
12944
- "h-full w-full scroll-p-[4px]",
13363
+ // the frame's corners, so the inset ring follows them rather than being cut by them (as the Table's region)
13364
+ "h-full w-full scroll-p-[4px] rounded-[inherit]",
13365
+ FOCUS_INSET,
12945
13366
  orientation === "vertical" && "overflow-y-auto overflow-x-hidden",
12946
13367
  orientation === "horizontal" && "overflow-x-auto overflow-y-hidden",
12947
13368
  orientation === "both" && "overflow-auto",
@@ -17889,6 +18310,6 @@ function DangerZone({ title = "Danger zone", description, children, className })
17889
18310
  ] });
17890
18311
  }
17891
18312
 
17892
- 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 };
17893
18314
  //# sourceMappingURL=index.js.map
17894
18315
  //# sourceMappingURL=index.js.map