@cueplusplus/ui 0.5.0 → 0.7.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 (69) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/chrome/_drag.js +59 -0
  3. package/dist/chrome/_status-bar-item.js +85 -0
  4. package/dist/chrome/_status-bar.d.ts +15 -0
  5. package/dist/chrome/_status-bar.js +171 -0
  6. package/dist/chrome/app-shell.d.ts +69 -1
  7. package/dist/chrome/app-shell.js +38 -11
  8. package/dist/chrome/index.d.ts +3 -2
  9. package/dist/chrome/status-bar.d.ts +87 -4
  10. package/dist/chrome/status-bar.js +80 -16
  11. package/dist/chrome/title-bar.d.ts +11 -1
  12. package/dist/chrome/title-bar.js +1 -3
  13. package/dist/date/_segments.js +15 -4
  14. package/dist/forms/_chassis.d.ts +104 -4
  15. package/dist/forms/_chassis.js +104 -4
  16. package/dist/forms/input-group.d.ts +8 -3
  17. package/dist/forms/input-group.js +8 -3
  18. package/dist/forms/number-field.d.ts +3 -2
  19. package/dist/forms/number-field.js +3 -2
  20. package/dist/index.d.ts +8 -3
  21. package/dist/index.js +5 -1
  22. package/dist/instruments/_data-row.d.ts +68 -0
  23. package/dist/instruments/_data-row.js +139 -0
  24. package/dist/instruments/data-row.d.ts +94 -0
  25. package/dist/instruments/data-row.js +151 -0
  26. package/dist/instruments/data-tree.d.ts +126 -0
  27. package/dist/instruments/data-tree.js +303 -0
  28. package/dist/instruments/index.d.ts +6 -2
  29. package/dist/instruments/index.js +5 -1
  30. package/dist/instruments/ledger.d.ts +105 -0
  31. package/dist/instruments/ledger.js +114 -0
  32. package/dist/instruments/table.d.ts +108 -2
  33. package/dist/instruments/table.js +165 -39
  34. package/dist/theming/_presets.js +3 -3
  35. package/manifest/components/app-shell.json +45 -4
  36. package/manifest/components/autocomplete.json +2 -0
  37. package/manifest/components/color-field.json +2 -0
  38. package/manifest/components/color-picker.json +2 -0
  39. package/manifest/components/combobox.json +2 -0
  40. package/manifest/components/composer.json +2 -0
  41. package/manifest/components/data-row.json +208 -0
  42. package/manifest/components/data-tree.json +151 -0
  43. package/manifest/components/date-field.json +2 -0
  44. package/manifest/components/date-picker.json +2 -0
  45. package/manifest/components/date-range-picker.json +2 -0
  46. package/manifest/components/env-var-input.json +2 -0
  47. package/manifest/components/input-group.json +4 -2
  48. package/manifest/components/input.json +3 -1
  49. package/manifest/components/ledger.json +187 -0
  50. package/manifest/components/multi-select.json +2 -0
  51. package/manifest/components/musical-time-input.json +2 -0
  52. package/manifest/components/number-field.json +3 -1
  53. package/manifest/components/otp-field.json +2 -0
  54. package/manifest/components/password-input.json +2 -0
  55. package/manifest/components/scrub-input.json +2 -0
  56. package/manifest/components/search-input.json +2 -0
  57. package/manifest/components/select.json +1 -0
  58. package/manifest/components/status-bar.json +146 -12
  59. package/manifest/components/table-scroll-region.json +6 -0
  60. package/manifest/components/table.json +113 -5
  61. package/manifest/components/tags-input.json +2 -0
  62. package/manifest/components/textarea.json +3 -1
  63. package/manifest/components/time-field.json +2 -0
  64. package/manifest/components/title-bar.json +1 -1
  65. package/manifest/components/toggle-group.json +2 -0
  66. package/manifest/components/toggle.json +2 -0
  67. package/manifest/manifest.json +72 -33
  68. package/manifest/tokens.json +1 -1
  69. package/package.json +2 -2
@@ -0,0 +1,126 @@
1
+ import * as React from "react";
2
+ //#region src/instruments/data-tree.d.ts
3
+ /** What a row knows about itself when its content is a function. */
4
+ interface DataTreeRowState {
5
+ /** Depth from the roots; `0` for a root. */
6
+ level: number;
7
+ /** Whether the node declares children at all. */
8
+ hasChildren: boolean;
9
+ /** Whether those children are currently shown. */
10
+ expanded: boolean;
11
+ /** Whether this row is the selected one. */
12
+ selected: boolean;
13
+ }
14
+ /** One node of the tree: an identity, a row's worth of content, and children. */
15
+ interface DataTreeItem {
16
+ /** Stable identity: what selection, expansion and focus are keyed on. */
17
+ id: string;
18
+ /**
19
+ * The row's content — normally `DataRow` slots, in the same order and with
20
+ * the same tracks as the other idioms drawing this dataset.
21
+ *
22
+ * A function receives the row's own state, for the cases where the content
23
+ * differs by it: a count that only shows while a node is folded, a different
24
+ * marker for an expanded parent.
25
+ */
26
+ row: React.ReactNode | ((state: DataTreeRowState) => React.ReactNode);
27
+ /**
28
+ * Nested nodes. An empty array still makes the row a parent — an empty folder
29
+ * is a folder, and `aria-expanded` on it is the truth.
30
+ */
31
+ children?: readonly DataTreeItem[];
32
+ /** Grey the row out; it stays reachable by keyboard but cannot be selected. */
33
+ disabled?: boolean;
34
+ }
35
+ interface DataTreeProps extends Omit<React.ComponentPropsWithoutRef<"div">, "onSelect"> {
36
+ /** The roots, in display order. */
37
+ items: readonly DataTreeItem[];
38
+ /** Expanded ids when controlled. Pass with `onExpandedChange`. */
39
+ expanded?: readonly string[];
40
+ /** Initially expanded ids when uncontrolled. */
41
+ defaultExpanded?: readonly string[];
42
+ /** Called with the whole next expanded set whenever a row is folded or unfolded. */
43
+ onExpandedChange?: (expanded: string[]) => void;
44
+ /** Selected id when controlled. Pass with `onSelect`. */
45
+ selected?: string | null;
46
+ /** Initially selected id when uncontrolled. */
47
+ defaultSelected?: string | null;
48
+ /** Called with the id of the row the user selected. */
49
+ onSelect?: (id: string) => void;
50
+ /**
51
+ * Draw the depth guides. Defaults to `true`.
52
+ *
53
+ * Roots never draw one — there is no ancestor to draw — so a flat tree draws
54
+ * none at all whatever this is set to.
55
+ */
56
+ guides?: boolean;
57
+ /** Accessible name for the tree. Required in practice — name what it lists. */
58
+ "aria-label"?: string;
59
+ }
60
+ /**
61
+ * A hierarchy of multi-column rows: the tree idiom over the `DataRow` anatomy.
62
+ *
63
+ * ## The ARIA pattern, chosen before the component was written
64
+ *
65
+ * `role="treegrid"` is **rejected** for this foundation. It is the semantically
66
+ * exact answer — tabular *and* hierarchical — and it is the one with the worst
67
+ * real-world assistive-technology record: Adrian Roselli's survey of expando
68
+ * tables calls its support poor and notes that the APG's own worked example
69
+ * carries unresolved issues; fancytree's maintainer describes treegrid support
70
+ * as unproven ground; MUI X shipped hierarchical rows with no `aria-level` /
71
+ * `aria-expanded` / `aria-setsize` at all and retrofitted them later. Treegrid
72
+ * earns its cost when cells are individually interactive or editable, because
73
+ * then a reader genuinely needs two-dimensional cell navigation. This surface is
74
+ * read-and-navigate: you move down rows, fold a branch, and open the thing a row
75
+ * names.
76
+ *
77
+ * So the pattern is `role="tree"` with `role="treeitem"` rows — VS Code's
78
+ * choice and Carbon's, and the best-supported composite pattern in ARIA:
79
+ *
80
+ * - A **flat DOM** with `aria-level` / `aria-posinset` / `aria-setsize` per row
81
+ * rather than nested `role="group"` wrappers.
82
+ * - **Roving tabindex**: the tree is one Tab stop and the arrows move inside it.
83
+ * - The **APG tree keyboard map**: Up/Down between visible rows, Home/End to the
84
+ * ends, Right to unfold then to descend, Left to fold then to ascend,
85
+ * Enter/Space to select.
86
+ * - Expansion lives on the **row**, as `aria-expanded` on the `treeitem` — that
87
+ * is what tree semantics mean by a parent, and it is why the chevron here is
88
+ * `role="presentation"` rather than a button. (The other half of this trade
89
+ * is `Table.GroupRow`, where the table's semantics own the grouping and a real
90
+ * `<button aria-expanded>` owns the disclosure. Two patterns, each internally
91
+ * consistent; what would be wrong is half of each.)
92
+ *
93
+ * **The documented trade**: columns are not independently navigable. A row's
94
+ * multi-column content lives *inside* the treeitem, so a screen-reader user
95
+ * hears the row as one string rather than moving cell to cell through it. For a
96
+ * dense browse-and-open surface that is the better bargain — `tree` works today
97
+ * on every screen reader, `treegrid` does not — but a surface whose cells are
98
+ * genuinely editable has outgrown this component and wants a real treegrid from
99
+ * a library that maintains one.
100
+ *
101
+ * ## Everything else
102
+ *
103
+ * Rows are the `DataRow` chassis: same padding, same hairline, same hover wash,
104
+ * same inset focus ring, same `group/data-row`, so `DataRow.Actions` reveals in
105
+ * a tree exactly as it does in a ledger. The row element itself is the
106
+ * treeitem — one element owns focus, expansion, selection and the click reading
107
+ * — and the caller's `DataRow.*` slots are its content.
108
+ *
109
+ * A tree whose items declare no children anywhere is a flat list: no chevrons,
110
+ * no spacer column where a chevron would be, no guides, and `aria-expanded` on
111
+ * nothing. A leaf row that carried `aria-expanded="false"` would tell a screen
112
+ * reader it has children it does not have.
113
+ *
114
+ * @example
115
+ * <DataTree
116
+ * aria-label="Patch"
117
+ * items={nodes}
118
+ * defaultExpanded={["inputs"]}
119
+ * selected={selected}
120
+ * onSelect={setSelected}
121
+ * className="[--cue-data-row-cols:1fr_8rem_auto]"
122
+ * />
123
+ */
124
+ declare const DataTree: React.ForwardRefExoticComponent<DataTreeProps & React.RefAttributes<HTMLDivElement>>;
125
+ //#endregion
126
+ export { DataTree, DataTreeItem, DataTreeProps, DataTreeRowState };
@@ -0,0 +1,303 @@
1
+ "use client";
2
+ import { cn } from "../lib/cn.js";
3
+ import { flattenTree } from "../chrome/_tree.js";
4
+ import { ChevronRightGlyph } from "../chrome/_glyphs.js";
5
+ import { activateRowFromClick, dataRowVariants } from "./_data-row.js";
6
+ import * as React from "react";
7
+ import { jsx, jsxs } from "react/jsx-runtime";
8
+ //#region src/instruments/data-tree.tsx
9
+ /**
10
+ * The hierarchy guides, drawn as a background rather than as characters or
11
+ * borders.
12
+ *
13
+ * One hairline per ancestor level, from a `repeating-linear-gradient` whose
14
+ * period is the indent step and whose painted width is `depth × step` — so the
15
+ * rules stop exactly where the row's own content begins. Three properties of
16
+ * this mechanism are the reason it is not box-drawing glyphs and not a stack of
17
+ * `border-left` spacers:
18
+ *
19
+ * - It is a `background-image`, so the row's hover and selection
20
+ * `background-color` cannot cover it, move it or switch it off. A guide drawn
21
+ * with borders on spacer elements has to be re-tinted for every row state.
22
+ * - It costs no DOM. A tree of two thousand visible rows adds no elements per
23
+ * level, which is what makes a virtualized variant of this row viable later.
24
+ * - It is a picture of structure, not a picture of a tree: `├` and `└` in a mono
25
+ * grid are characters a screen reader may read out and a proportional font
26
+ * destroys.
27
+ *
28
+ * The step is `--cue-data-tree-indent` (default `1.25rem`), a local custom
29
+ * property so a `@container` rule can tighten the tree at the same breakpoint it
30
+ * sheds a column — and switch the guides off entirely at the width where an
31
+ * indent is more expensive than it is informative.
32
+ */
33
+ const DATA_TREE_GUIDES = [
34
+ "bg-no-repeat",
35
+ "[background-image:repeating-linear-gradient(to_right,var(--cue-hair)_0,var(--cue-hair)_1px,transparent_1px,transparent_var(--cue-data-tree-indent,1.25rem))]",
36
+ "[background-size:calc(var(--depth)_*_var(--cue-data-tree-indent,1.25rem))_100%]",
37
+ "[background-position:var(--cue-pad-row-x)_0]"
38
+ ].join(" ");
39
+ /**
40
+ * `DataTreeItem` as the shape `flattenTree` walks, plus a way back.
41
+ *
42
+ * `flattenTree` (`src/chrome/_tree.ts`) is the outline algorithm this library
43
+ * already has, and re-implementing "which rows are visible, at what level, in
44
+ * what position of what set" for a second tree is how two trees start
45
+ * disagreeing about what a collapse hides. It reads `id`, `children` and
46
+ * `disabled`; its `TreeItem` also *declares* a `label`, which a data row does
47
+ * not have — its content is columns. So the walk maps to that shape with a null
48
+ * label and hands back an index from id to the caller's own item, which is what
49
+ * the row renderer reads.
50
+ *
51
+ * @param items The roots, in display order.
52
+ * @returns The outline `flattenTree` accepts, and the items keyed by id.
53
+ */
54
+ function outlineOf(items) {
55
+ const byId = /* @__PURE__ */ new Map();
56
+ const walk = (nodes) => nodes.map((node) => {
57
+ byId.set(node.id, node);
58
+ const outlineNode = {
59
+ id: node.id,
60
+ label: null,
61
+ disabled: node.disabled
62
+ };
63
+ if (node.children !== void 0) outlineNode.children = walk(node.children);
64
+ return outlineNode;
65
+ });
66
+ return {
67
+ outline: walk(items),
68
+ byId
69
+ };
70
+ }
71
+ /**
72
+ * A hierarchy of multi-column rows: the tree idiom over the `DataRow` anatomy.
73
+ *
74
+ * ## The ARIA pattern, chosen before the component was written
75
+ *
76
+ * `role="treegrid"` is **rejected** for this foundation. It is the semantically
77
+ * exact answer — tabular *and* hierarchical — and it is the one with the worst
78
+ * real-world assistive-technology record: Adrian Roselli's survey of expando
79
+ * tables calls its support poor and notes that the APG's own worked example
80
+ * carries unresolved issues; fancytree's maintainer describes treegrid support
81
+ * as unproven ground; MUI X shipped hierarchical rows with no `aria-level` /
82
+ * `aria-expanded` / `aria-setsize` at all and retrofitted them later. Treegrid
83
+ * earns its cost when cells are individually interactive or editable, because
84
+ * then a reader genuinely needs two-dimensional cell navigation. This surface is
85
+ * read-and-navigate: you move down rows, fold a branch, and open the thing a row
86
+ * names.
87
+ *
88
+ * So the pattern is `role="tree"` with `role="treeitem"` rows — VS Code's
89
+ * choice and Carbon's, and the best-supported composite pattern in ARIA:
90
+ *
91
+ * - A **flat DOM** with `aria-level` / `aria-posinset` / `aria-setsize` per row
92
+ * rather than nested `role="group"` wrappers.
93
+ * - **Roving tabindex**: the tree is one Tab stop and the arrows move inside it.
94
+ * - The **APG tree keyboard map**: Up/Down between visible rows, Home/End to the
95
+ * ends, Right to unfold then to descend, Left to fold then to ascend,
96
+ * Enter/Space to select.
97
+ * - Expansion lives on the **row**, as `aria-expanded` on the `treeitem` — that
98
+ * is what tree semantics mean by a parent, and it is why the chevron here is
99
+ * `role="presentation"` rather than a button. (The other half of this trade
100
+ * is `Table.GroupRow`, where the table's semantics own the grouping and a real
101
+ * `<button aria-expanded>` owns the disclosure. Two patterns, each internally
102
+ * consistent; what would be wrong is half of each.)
103
+ *
104
+ * **The documented trade**: columns are not independently navigable. A row's
105
+ * multi-column content lives *inside* the treeitem, so a screen-reader user
106
+ * hears the row as one string rather than moving cell to cell through it. For a
107
+ * dense browse-and-open surface that is the better bargain — `tree` works today
108
+ * on every screen reader, `treegrid` does not — but a surface whose cells are
109
+ * genuinely editable has outgrown this component and wants a real treegrid from
110
+ * a library that maintains one.
111
+ *
112
+ * ## Everything else
113
+ *
114
+ * Rows are the `DataRow` chassis: same padding, same hairline, same hover wash,
115
+ * same inset focus ring, same `group/data-row`, so `DataRow.Actions` reveals in
116
+ * a tree exactly as it does in a ledger. The row element itself is the
117
+ * treeitem — one element owns focus, expansion, selection and the click reading
118
+ * — and the caller's `DataRow.*` slots are its content.
119
+ *
120
+ * A tree whose items declare no children anywhere is a flat list: no chevrons,
121
+ * no spacer column where a chevron would be, no guides, and `aria-expanded` on
122
+ * nothing. A leaf row that carried `aria-expanded="false"` would tell a screen
123
+ * reader it has children it does not have.
124
+ *
125
+ * @example
126
+ * <DataTree
127
+ * aria-label="Patch"
128
+ * items={nodes}
129
+ * defaultExpanded={["inputs"]}
130
+ * selected={selected}
131
+ * onSelect={setSelected}
132
+ * className="[--cue-data-row-cols:1fr_8rem_auto]"
133
+ * />
134
+ */
135
+ const DataTree = React.forwardRef(function DataTree({ className, items, expanded: expandedProp, defaultExpanded, onExpandedChange, selected: selectedProp, defaultSelected = null, onSelect, guides = true, onKeyDown, ...elementProps }, ref) {
136
+ const [uncontrolledExpanded, setUncontrolledExpanded] = React.useState(defaultExpanded ?? []);
137
+ const expandedList = expandedProp ?? uncontrolledExpanded;
138
+ const expandedSet = React.useMemo(() => new Set(expandedList), [expandedList]);
139
+ const [uncontrolledSelected, setUncontrolledSelected] = React.useState(defaultSelected);
140
+ const selected = selectedProp === void 0 ? uncontrolledSelected : selectedProp;
141
+ const { outline, byId } = React.useMemo(() => outlineOf(items), [items]);
142
+ const rows = React.useMemo(() => flattenTree(outline, expandedSet), [outline, expandedSet]);
143
+ const hierarchical = React.useMemo(() => {
144
+ for (const item of byId.values()) if (item.children !== void 0) return true;
145
+ return false;
146
+ }, [byId]);
147
+ const [activeId, setActiveId] = React.useState(null);
148
+ const activeRowId = activeId !== null && rows.some((row) => row.item.id === activeId) ? activeId : rows[0]?.item.id ?? null;
149
+ const rowRefs = React.useRef(/* @__PURE__ */ new Map());
150
+ const registerRow = React.useCallback((id, element) => {
151
+ if (element === null) rowRefs.current.delete(id);
152
+ else rowRefs.current.set(id, element);
153
+ }, []);
154
+ const focusRow = React.useCallback((id) => {
155
+ setActiveId(id);
156
+ rowRefs.current.get(id)?.focus();
157
+ }, []);
158
+ const setExpanded = React.useCallback((id, next) => {
159
+ const current = expandedProp ?? uncontrolledExpanded;
160
+ const result = next ? current.includes(id) ? [...current] : [...current, id] : current.filter((entry) => entry !== id);
161
+ if (expandedProp === void 0) setUncontrolledExpanded(result);
162
+ onExpandedChange?.(result);
163
+ }, [
164
+ expandedProp,
165
+ onExpandedChange,
166
+ uncontrolledExpanded
167
+ ]);
168
+ const select = React.useCallback((row) => {
169
+ if (byId.get(row.item.id)?.disabled === true) return;
170
+ if (selectedProp === void 0) setUncontrolledSelected(row.item.id);
171
+ onSelect?.(row.item.id);
172
+ }, [
173
+ byId,
174
+ onSelect,
175
+ selectedProp
176
+ ]);
177
+ /**
178
+ * The APG tree keyboard map.
179
+ *
180
+ * A near-copy of `Tree`'s handler in `src/chrome/tree.tsx`, and deliberately
181
+ * so: the two trees paint different rows (a label with a glyph gutter there,
182
+ * columns with a CSS indent here) and share `flattenTree`, which is the half
183
+ * that is actually hard. Extracting the twenty lines below into a third module
184
+ * would have to thread the row registry, the expansion setter and the
185
+ * selection through it, and the seam would be wider than the code. If a third
186
+ * tree ever appears, that is the moment to hoist it.
187
+ */
188
+ const handleKeyDown = React.useCallback((event) => {
189
+ onKeyDown?.(event);
190
+ if (event.defaultPrevented) return;
191
+ if (event.target.getAttribute("data-slot") !== "data-tree-row") return;
192
+ const index = rows.findIndex((row) => row.item.id === activeRowId);
193
+ const row = rows[index];
194
+ if (row === void 0) return;
195
+ const move = (target) => {
196
+ if (target === void 0) return;
197
+ event.preventDefault();
198
+ focusRow(target.item.id);
199
+ };
200
+ switch (event.key) {
201
+ case "ArrowDown":
202
+ move(rows[index + 1]);
203
+ return;
204
+ case "ArrowUp":
205
+ move(rows[index - 1]);
206
+ return;
207
+ case "Home":
208
+ move(rows[0]);
209
+ return;
210
+ case "End":
211
+ move(rows[rows.length - 1]);
212
+ return;
213
+ case "ArrowRight": {
214
+ event.preventDefault();
215
+ if (row.hasChildren && !row.expanded) {
216
+ setExpanded(row.item.id, true);
217
+ return;
218
+ }
219
+ const child = rows[index + 1];
220
+ if (row.expanded && child?.parentId === row.item.id) focusRow(child.item.id);
221
+ return;
222
+ }
223
+ case "ArrowLeft":
224
+ event.preventDefault();
225
+ if (row.expanded) setExpanded(row.item.id, false);
226
+ else if (row.parentId !== null) focusRow(row.parentId);
227
+ return;
228
+ case "Enter":
229
+ case " ":
230
+ event.preventDefault();
231
+ select(row);
232
+ return;
233
+ }
234
+ }, [
235
+ activeRowId,
236
+ focusRow,
237
+ onKeyDown,
238
+ rows,
239
+ select,
240
+ setExpanded
241
+ ]);
242
+ return /* @__PURE__ */ jsx("div", {
243
+ ref,
244
+ ...elementProps,
245
+ role: "tree",
246
+ "data-slot": "data-tree",
247
+ className: cn("flex flex-col font-mono text-(length:--cue-text-ui) text-fg", className),
248
+ onKeyDown: handleKeyDown,
249
+ children: rows.map((row) => {
250
+ const item = byId.get(row.item.id);
251
+ if (item === void 0) return null;
252
+ const isSelected = selected === item.id;
253
+ const state = {
254
+ level: row.level,
255
+ hasChildren: row.hasChildren,
256
+ expanded: row.expanded,
257
+ selected: isSelected
258
+ };
259
+ return /* @__PURE__ */ jsxs("div", {
260
+ ref: (element) => registerRow(item.id, element),
261
+ role: "treeitem",
262
+ tabIndex: activeRowId === item.id ? 0 : -1,
263
+ "aria-level": row.level + 1,
264
+ "aria-posinset": row.position,
265
+ "aria-setsize": row.setSize,
266
+ "aria-expanded": row.hasChildren ? row.expanded : void 0,
267
+ "aria-selected": isSelected,
268
+ "aria-disabled": item.disabled === true ? true : void 0,
269
+ "data-slot": "data-tree-row",
270
+ "data-selected": isSelected ? "" : void 0,
271
+ "data-disabled": item.disabled === true ? "" : void 0,
272
+ style: { "--depth": row.level },
273
+ onFocus: () => setActiveId(item.id),
274
+ onClick: (event) => activateRowFromClick(event, () => select(row)),
275
+ className: cn(dataRowVariants({
276
+ layout: "flex",
277
+ indent: true,
278
+ interactive: true
279
+ }), guides && row.level > 0 ? DATA_TREE_GUIDES : null),
280
+ children: [hierarchical ? row.hasChildren ? /* @__PURE__ */ jsx("span", {
281
+ role: "presentation",
282
+ "aria-hidden": "true",
283
+ "data-slot": "data-tree-toggle",
284
+ onClick: (event) => {
285
+ event.stopPropagation();
286
+ setExpanded(item.id, !row.expanded);
287
+ },
288
+ className: "flex size-icon-sm shrink-0 cursor-pointer items-center justify-center text-fg-subtle",
289
+ children: /* @__PURE__ */ jsx(ChevronRightGlyph, { className: cn("size-icon-sm transition-transform duration-150 motion-reduce:transition-none", row.expanded ? "rotate-90" : null) })
290
+ }) : /* @__PURE__ */ jsx("span", {
291
+ "aria-hidden": "true",
292
+ className: "size-icon-sm shrink-0"
293
+ }) : null, /* @__PURE__ */ jsx("span", {
294
+ "data-slot": "data-tree-row-content",
295
+ className: "grid min-w-0 flex-1 items-center gap-(--cue-space-3) [grid-template-columns:var(--cue-data-row-cols,1fr_auto)]",
296
+ children: typeof item.row === "function" ? item.row(state) : item.row
297
+ })]
298
+ }, item.id);
299
+ })
300
+ });
301
+ });
302
+ //#endregion
303
+ export { DataTree };
@@ -1,13 +1,17 @@
1
+ import { activateRowFromClick, activateRowFromKeyDown } from "./_data-row.js";
1
2
  import { AppWindowFrame, AppWindowFrameProps } from "./app-window-frame.js";
2
3
  import { CopyButton, CopyButtonProps } from "./copy-button.js";
4
+ import { DataRow, DataRowDisclosure, DataRowRootProps, DataRowSlotProps } from "./data-row.js";
5
+ import { DataTree, DataTreeItem, DataTreeProps, DataTreeRowState } from "./data-tree.js";
3
6
  import { GroupBar, GroupBarItem, GroupBarProps, GroupBarTone } from "./group-bar.js";
7
+ import { Ledger, LedgerEmptyProps, LedgerGroupProps, LedgerRootProps, LedgerTierProps } from "./ledger.js";
4
8
  import { Meter, MeterProps, MeterProvenance, MeterTone } from "./meter.js";
5
9
  import { Progress, ProgressProps } from "./progress.js";
6
10
  import { SeamCell, SeamCellProps, SeamGrid, SeamGridProps, SeamGround } from "./seam-grid.js";
7
11
  import { Sparkline, SparklineProps } from "./sparkline.js";
8
12
  import { Stat, StatProps, StatTone } from "./stat.js";
9
- import { Table, TableCaptionProps, TableCellProps, TableHeadProps, TableRootProps, TableRowProps, TableScrollRegion, TableScrollRegionProps, TableSectionProps } from "./table.js";
13
+ import { Table, TableCaptionProps, TableCellProps, TableGroupRowProps, TableHeadProps, TableRootProps, TableRowProps, TableScrollRegion, TableScrollRegionProps, TableSectionProps } from "./table.js";
10
14
  import { TerminalFrame, TerminalFrameLine, TerminalFrameProps, TerminalLine, TerminalLineTone, TerminalScript, TerminalStep } from "./terminal-frame.js";
11
15
  import { TwoStepButton, TwoStepButtonProps, TwoStepTone } from "./two-step-button.js";
12
16
  import { UsageChart, UsageChartPoint, UsageChartProps, UsageChartTone } from "./usage-chart.js";
13
- export { AppWindowFrame, type AppWindowFrameProps, CopyButton, type CopyButtonProps, GroupBar, type GroupBarItem, type GroupBarProps, type GroupBarTone, Meter, type MeterProps, type MeterProvenance, type MeterTone, Progress, type ProgressProps, SeamCell, type SeamCellProps, SeamGrid, type SeamGridProps, type SeamGround, Sparkline, type SparklineProps, Stat, type StatProps, type StatTone, Table, type TableCaptionProps, type TableCellProps, type TableHeadProps, type TableRootProps, type TableRowProps, TableScrollRegion, type TableScrollRegionProps, type TableSectionProps, TerminalFrame, type TerminalFrameLine, type TerminalFrameProps, type TerminalLine, type TerminalLineTone, type TerminalScript, type TerminalStep, TwoStepButton, type TwoStepButtonProps, type TwoStepTone, UsageChart, type UsageChartPoint, type UsageChartProps, type UsageChartTone };
17
+ export { AppWindowFrame, type AppWindowFrameProps, CopyButton, type CopyButtonProps, DataRow, type DataRowDisclosure, type DataRowRootProps, type DataRowSlotProps, DataTree, type DataTreeItem, type DataTreeProps, type DataTreeRowState, GroupBar, type GroupBarItem, type GroupBarProps, type GroupBarTone, Ledger, type LedgerEmptyProps, type LedgerGroupProps, type LedgerRootProps, type LedgerTierProps, Meter, type MeterProps, type MeterProvenance, type MeterTone, Progress, type ProgressProps, SeamCell, type SeamCellProps, SeamGrid, type SeamGridProps, type SeamGround, Sparkline, type SparklineProps, Stat, type StatProps, type StatTone, Table, type TableCaptionProps, type TableCellProps, type TableGroupRowProps, type TableHeadProps, type TableRootProps, type TableRowProps, TableScrollRegion, type TableScrollRegionProps, type TableSectionProps, TerminalFrame, type TerminalFrameLine, type TerminalFrameProps, type TerminalLine, type TerminalLineTone, type TerminalScript, type TerminalStep, TwoStepButton, type TwoStepButtonProps, type TwoStepTone, UsageChart, type UsageChartPoint, type UsageChartProps, type UsageChartTone, activateRowFromClick, activateRowFromKeyDown };
@@ -1,6 +1,10 @@
1
+ import { activateRowFromClick, activateRowFromKeyDown } from "./_data-row.js";
1
2
  import { AppWindowFrame } from "./app-window-frame.js";
2
3
  import { CopyButton } from "./copy-button.js";
4
+ import { DataRow } from "./data-row.js";
5
+ import { DataTree } from "./data-tree.js";
3
6
  import { GroupBar } from "./group-bar.js";
7
+ import { Ledger } from "./ledger.js";
4
8
  import { Meter } from "./meter.js";
5
9
  import { Progress } from "./progress.js";
6
10
  import { SeamCell, SeamGrid } from "./seam-grid.js";
@@ -10,4 +14,4 @@ import { Table, TableScrollRegion } from "./table.js";
10
14
  import { TerminalFrame } from "./terminal-frame.js";
11
15
  import { TwoStepButton } from "./two-step-button.js";
12
16
  import { UsageChart } from "./usage-chart.js";
13
- export { AppWindowFrame, CopyButton, GroupBar, Meter, Progress, SeamCell, SeamGrid, Sparkline, Stat, Table, TableScrollRegion, TerminalFrame, TwoStepButton, UsageChart };
17
+ export { AppWindowFrame, CopyButton, DataRow, DataTree, GroupBar, Ledger, Meter, Progress, SeamCell, SeamGrid, Sparkline, Stat, Table, TableScrollRegion, TerminalFrame, TwoStepButton, UsageChart, activateRowFromClick, activateRowFromKeyDown };
@@ -0,0 +1,105 @@
1
+ import * as React from "react";
2
+ //#region src/instruments/ledger.d.ts
3
+ /** The heading elements a tier or a group may be given. */
4
+ type LedgerHeadingLevel = 2 | 3 | 4;
5
+ interface LedgerRootProps extends React.ComponentPropsWithoutRef<"div"> {
6
+ /**
7
+ * Make the ledger a size container, so a caller's `@container` rules shed
8
+ * columns against the *pane* rather than the viewport. Defaults to `true`.
9
+ *
10
+ * This is the difference between a list that sheds a column when the window
11
+ * narrows and one that also sheds it when a detail drawer opens beside it and
12
+ * takes half the pane — which is the same visual problem and, without a
13
+ * container, an entirely separate piece of code. Switch it off only when an
14
+ * ancestor is already the container the rules name; nesting containers moves
15
+ * which one a bare `@container` query resolves against.
16
+ */
17
+ containment?: boolean;
18
+ }
19
+ interface LedgerTierProps extends React.ComponentPropsWithoutRef<"section"> {
20
+ /**
21
+ * The tier's name. Omitted, the tier draws no heading and reserves no space
22
+ * for one — which is how a single unnamed run of rows uses the same component
23
+ * as a grouped one.
24
+ */
25
+ heading?: React.ReactNode;
26
+ /** How many rows the tier holds, printed beside its name. */
27
+ count?: number;
28
+ /**
29
+ * Dock the heading to the top of the scroll container while the tier's rows
30
+ * scroll past. Defaults to `true`.
31
+ */
32
+ sticky?: boolean;
33
+ /**
34
+ * Which heading element the tier's name is. Defaults to `2`.
35
+ *
36
+ * The outline is the navigation: a screen-reader user moves through a long
37
+ * ledger by heading, so the levels have to describe the real nesting of the
38
+ * page the ledger sits in, not the ledger's own idea of itself.
39
+ */
40
+ headingLevel?: LedgerHeadingLevel;
41
+ /** A qualifier printed after the name — a date, a source, a state. */
42
+ note?: React.ReactNode;
43
+ }
44
+ interface LedgerGroupProps extends React.ComponentPropsWithoutRef<"div"> {
45
+ /**
46
+ * The group's slug, printed in the left gutter.
47
+ *
48
+ * Omitted, the group is a plain block: no gutter, no heading, no indent. A
49
+ * grouped list that has one unnamed run in it should not pay a 7rem empty
50
+ * column for it, and an empty gutter reads as a missing label rather than as
51
+ * an absent one.
52
+ */
53
+ label?: React.ReactNode;
54
+ /** Which heading element the slug is. Defaults to `3`. */
55
+ headingLevel?: 3 | 4;
56
+ /**
57
+ * Dock the slug under the tier heading while the group's rows scroll past.
58
+ * Defaults to `true`.
59
+ *
60
+ * The offset is a custom property, `--cue-ledger-slug-top` (default `2.5rem`):
61
+ * how far down the slug docks depends on how tall the tier heading above it
62
+ * renders, which is a function of the density and the type the *caller*
63
+ * configured. Set it on the ledger — `className="[--cue-ledger-slug-top:2rem]"` —
64
+ * rather than measuring at runtime.
65
+ */
66
+ sticky?: boolean;
67
+ }
68
+ interface LedgerEmptyProps extends React.ComponentPropsWithoutRef<"p"> {}
69
+ /**
70
+ * A grouped run of dense rows under sticky headings: the ledger idiom.
71
+ *
72
+ * The flattest of the three surfaces this package ships over one row model, and
73
+ * the one that does the least: headings, slugs, rows. **Nothing here collapses.**
74
+ * A ledger with a disclosure on it would be a tree with worse semantics, so the
75
+ * component emits no `aria-expanded` anywhere, and a test holds it to that —
76
+ * collapse belongs to `DataTree`, and grouping-with-columns belongs to a
77
+ * grouped `Table`.
78
+ *
79
+ * What it does own is the document outline. Tiers and groups are real `<h2>` /
80
+ * `<h3>` elements at caller-chosen levels, because in a list of two hundred
81
+ * rows the heading list *is* how a screen-reader user navigates.
82
+ *
83
+ * Static markup — no `"use client"`. The rows inside it may be anything;
84
+ * `DataRow` is the intended tenant.
85
+ *
86
+ * @example
87
+ * <Ledger.Root className="[--cue-data-row-cols:1fr_8rem_auto]">
88
+ * <Ledger.Tier heading="Ours" count={12}>
89
+ * <Ledger.Group label="released">
90
+ * <DataRow.Root interactive onActivate={open}>…</DataRow.Root>
91
+ * </Ledger.Group>
92
+ * <Ledger.Group label="lab">
93
+ * <Ledger.Empty>Nothing yet.</Ledger.Empty>
94
+ * </Ledger.Group>
95
+ * </Ledger.Tier>
96
+ * </Ledger.Root>
97
+ */
98
+ declare const Ledger: {
99
+ Root: React.ForwardRefExoticComponent<LedgerRootProps & React.RefAttributes<HTMLDivElement>>;
100
+ Tier: React.ForwardRefExoticComponent<LedgerTierProps & React.RefAttributes<HTMLElement>>;
101
+ Group: React.ForwardRefExoticComponent<LedgerGroupProps & React.RefAttributes<HTMLDivElement>>;
102
+ Empty: React.ForwardRefExoticComponent<LedgerEmptyProps & React.RefAttributes<HTMLParagraphElement>>;
103
+ };
104
+ //#endregion
105
+ export { Ledger, LedgerEmptyProps, LedgerGroupProps, LedgerRootProps, LedgerTierProps };