@adia-ai/web-components 0.8.43 → 0.8.44

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 (93) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/components/avatar/avatar.class.js +12 -1
  3. package/components/button/button.d.ts +4 -0
  4. package/components/chart/chart.a2ui.json +26 -0
  5. package/components/chart/chart.class.js +1304 -222
  6. package/components/chart/chart.css +151 -1
  7. package/components/chart/chart.d.ts +6 -0
  8. package/components/chart/chart.yaml +57 -0
  9. package/components/chart-legend/chart-legend.a2ui.json +17 -2
  10. package/components/chart-legend/chart-legend.class.js +69 -4
  11. package/components/chart-legend/chart-legend.css +65 -1
  12. package/components/chart-legend/chart-legend.d.ts +4 -2
  13. package/components/chart-legend/chart-legend.examples.md +4 -4
  14. package/components/chart-legend/chart-legend.yaml +43 -3
  15. package/components/combobox/combobox.a2ui.json +3 -0
  16. package/components/combobox/combobox.class.js +9 -8
  17. package/components/combobox/combobox.css +25 -4
  18. package/components/combobox/combobox.yaml +6 -0
  19. package/components/command/command.css +17 -1
  20. package/components/context-menu/context-menu.d.ts +2 -0
  21. package/components/heatmap/heatmap.a2ui.json +5 -0
  22. package/components/heatmap/heatmap.d.ts +2 -0
  23. package/components/heatmap/heatmap.yaml +8 -0
  24. package/components/input/input.a2ui.json +2 -2
  25. package/components/input/input.class.js +12 -7
  26. package/components/input/input.yaml +6 -5
  27. package/components/integration-card/integration-card.d.ts +2 -0
  28. package/components/noodles/noodles.a2ui.json +0 -15
  29. package/components/noodles/noodles.yaml +0 -12
  30. package/components/number-format/number-format.class.js +10 -0
  31. package/components/pagination/pagination.class.js +88 -4
  32. package/components/popover/popover.d.ts +2 -0
  33. package/components/progress/progress.class.js +11 -6
  34. package/components/range/range.class.js +9 -1
  35. package/components/select/select.a2ui.json +5 -1
  36. package/components/select/select.class.js +14 -14
  37. package/components/select/select.css +14 -1
  38. package/components/select/select.yaml +7 -1
  39. package/components/stream/stream.d.ts +2 -0
  40. package/components/table/table.a2ui.json +6 -1
  41. package/components/table/table.class.js +18 -1
  42. package/components/table/table.css +31 -6
  43. package/components/table/table.d.ts +3 -1
  44. package/components/table/table.examples.md +2 -2
  45. package/components/table/table.yaml +9 -1
  46. package/components/table-toolbar/table-toolbar.a2ui.json +28 -4
  47. package/components/table-toolbar/table-toolbar.class.js +346 -11
  48. package/components/table-toolbar/table-toolbar.css +265 -0
  49. package/components/table-toolbar/table-toolbar.d.ts +5 -1
  50. package/components/table-toolbar/table-toolbar.yaml +130 -15
  51. package/components/tag/tag.class.js +13 -1
  52. package/components/text/text.a2ui.json +3 -3
  53. package/components/text/text.d.ts +5 -3
  54. package/components/text/text.yaml +3 -3
  55. package/components/textarea/textarea.class.js +9 -1
  56. package/components/tour/tour.d.ts +10 -0
  57. package/core/a11y.d.ts +22 -0
  58. package/core/a11y.js +64 -0
  59. package/core/anchor.js +9 -5
  60. package/core/element.js +1 -1
  61. package/custom-elements.json +73 -29
  62. package/dist/host.min.css +1 -1
  63. package/dist/host.sheet.js +1 -1
  64. package/dist/theme-provider.min.js +1 -1
  65. package/dist/web-components.min.css +1 -1
  66. package/dist/web-components.min.js +119 -119
  67. package/dist/web-components.sheet.js +1 -1
  68. package/package.json +1 -1
  69. package/patterns/access-requests/access-requests.examples.html +1 -1
  70. package/patterns/admin-keys-and-export/admin-keys-and-export.examples.html +7 -7
  71. package/patterns/agent-cost/agent-cost.examples.html +6 -6
  72. package/patterns/agent-memory/agent-memory.examples.html +3 -3
  73. package/patterns/agent-prompt-library/agent-prompt-library.examples.html +8 -8
  74. package/patterns/agent-tool-call/agent-tool-call.examples.html +1 -1
  75. package/patterns/approvals/approvals.examples.html +6 -6
  76. package/patterns/audit-log/audit-log.examples.html +1 -1
  77. package/patterns/bulk-action-toolbar/bulk-action-toolbar.examples.html +4 -4
  78. package/patterns/chart-in-card/chart-in-card.examples.html +114 -0
  79. package/patterns/chart-in-card/chart-in-card.examples.js +37 -0
  80. package/patterns/comments-and-collaboration/comments-and-collaboration.examples.html +5 -5
  81. package/patterns/data-tables-inline-edit-and-tree/data-tables-inline-edit-and-tree.examples.html +2 -2
  82. package/patterns/diff-review/diff-review.examples.html +6 -6
  83. package/patterns/export-flow/export-flow.examples.html +2 -2
  84. package/patterns/filter-bar/filter-bar.examples.html +2 -2
  85. package/patterns/forms-conditional-and-autocomplete/forms-conditional-and-autocomplete.examples.html +2 -2
  86. package/patterns/inline-dialog/inline-dialog.examples.html +2 -2
  87. package/patterns/marketing-engagement/marketing-engagement.examples.html +4 -4
  88. package/patterns/notifications-bell-and-digest/notifications-bell-and-digest.examples.html +1 -1
  89. package/patterns/permissions-matrix/permissions-matrix.examples.html +1 -1
  90. package/patterns/permissions-sharing/permissions-sharing.examples.html +2 -2
  91. package/patterns/profile-public-and-verification/profile-public-and-verification.examples.html +4 -4
  92. package/patterns/search-discovery/search-discovery.examples.html +2 -2
  93. package/styles/type/roles.css +29 -9
@@ -26,6 +26,30 @@
26
26
  --table-toolbar-search-min: 14rem;
27
27
  --table-toolbar-search-max: 22rem;
28
28
 
29
+ /* ── Search-tight stage (ADR-0076, REQ-S-002) ──
30
+ Stepped-down search field extents applied under the
31
+ search-tight/icon-only/overflow @container query below (or the
32
+ matching [stage] pin). Public so a consumer can retune the
33
+ compacted width without forking component CSS, same posture as
34
+ every other --table-toolbar-* token. */
35
+ --table-toolbar-search-tight-min: 9rem;
36
+ --table-toolbar-search-tight-max: 14rem;
37
+
38
+ /* ── Compaction breakpoints (ADR-0076, REQ-M-006) ──
39
+ Normative default pixel widths, token-indirected so a consumer can
40
+ retune without forking component CSS. Boundaries are inclusive on
41
+ the narrower side (max-width semantics) — a container at exactly
42
+ the boundary width renders the narrower of the two adjacent
43
+ stages. MUST stay monotonically decreasing
44
+ (bp-search-tight > bp-icon-only > bp-overflow); no stage-specific
45
+ @container rules exist yet (step 1 lands only this substrate —
46
+ steps 2/3/5 land each stage's own CSS). Same three thresholds
47
+ data-stage-resolved's ResizeObserver (step 7) will classify
48
+ against — one source of truth, never a second hardcoded copy. */
49
+ --table-toolbar-bp-search-tight: 56rem;
50
+ --table-toolbar-bp-icon-only: 40rem;
51
+ --table-toolbar-bp-overflow: 26rem;
52
+
29
53
  /* Popover tokens removed — popover escapes to top layer and
30
54
  cannot inherit @scope tokens. Raw --a-* tokens are used
31
55
  directly in the unscoped popover rules below (see comment
@@ -38,6 +62,151 @@
38
62
  box-sizing: border-box;
39
63
  display: block;
40
64
  color: var(--md-sys-color-neutral-on-surface);
65
+ /* Width-only compaction measurement substrate (ADR-0076, REQ-M-001).
66
+ inline-size containment only — never `size` — per chart.css's own
67
+ documented size-containment trap (container-type: size forces
68
+ block-size containment, collapsing any height-less element; chart
69
+ needs it only because it queries aspect-ratio, table-toolbar's axis
70
+ is width alone). Steps 3/5 land icon-only/overflow's own CSS
71
+ against this same container. */
72
+ container-type: inline-size;
73
+ container-name: table-toolbar;
74
+ }
75
+
76
+ /* ═══════ Search-tight stage ═══════ (ADR-0076, REQ-S-002/REQ-O-001, step 2)
77
+ Auto-snap: the container query fires the step-down once the toolbar's
78
+ own inline-size crosses --table-toolbar-bp-search-tight (56rem
79
+ default). `max-width` is inclusive-on-the-narrower-side by construction
80
+ (REQ-M-006) — a container at exactly the boundary width already renders
81
+ search-tight. `:not([stage])`/`[stage=""]` together cover "no pin" —
82
+ an explicit [stage="full"] pin must NOT auto-snap even inside a narrow
83
+ container (REQ-O-001, the pin wins), so the auto-snap selector
84
+ excludes every OTHER pinned value too.
85
+
86
+ BUG (caught in review, CodeRabbit on PR #1672, verified live in a real
87
+ browser before fixing): the token reassignment below targets
88
+ `[data-toolbar]`, a DESCENDANT of `:scope` — never `:scope` itself. A
89
+ container query only matches elements whose NEAREST ANCESTOR
90
+ establishes the named container; the container element is never its
91
+ own ancestor, so `@container table-toolbar (...) { :scope { ... } }`
92
+ can never match the host that declared `container-type` in the first
93
+ place — it's permanently-dead CSS, regardless of the container's
94
+ actual width. The custom properties still cascade correctly from
95
+ `[data-toolbar]` down to `[data-search]` (a further descendant), so
96
+ targeting the wrapper one level down is both the fix and the reason
97
+ it works: normal inheritance, no container-query involvement needed
98
+ for the property to reach its consumer.
99
+
100
+ CSS custom properties cannot be referenced inside an `@container` size
101
+ query's condition (only `style()` container queries read custom
102
+ properties, and those query computed values, not literal breakpoints)
103
+ — so the boundary below is the literal `56rem`, bound in this comment
104
+ to --table-toolbar-bp-search-tight's own default one line above; the
105
+ table-toolbar.test.js source-assertion suite pins the two values equal
106
+ so this literal can never silently drift from the token. */
107
+ @container table-toolbar (max-width: 56rem) {
108
+ :scope:is(:not([stage]), [stage=""]) > [data-toolbar] {
109
+ --table-toolbar-search-min: var(--table-toolbar-search-tight-min);
110
+ --table-toolbar-search-max: var(--table-toolbar-search-tight-max);
111
+ }
112
+ }
113
+
114
+ /* Explicit pin (REQ-O-001) — forces the search-tight extents regardless
115
+ of the container's actual measured width. Also applies at
116
+ [stage="icon-only"]/[stage="overflow"] (steps 3/5's own, MORE compact
117
+ pins): each later stage is strictly more compact than the last
118
+ (REQ-S), so the search field never widens back out just because a
119
+ tighter stage hasn't landed its own CSS yet. */
120
+ :scope:is([stage="search-tight"], [stage="icon-only"], [stage="overflow"]) {
121
+ --table-toolbar-search-min: var(--table-toolbar-search-tight-min);
122
+ --table-toolbar-search-max: var(--table-toolbar-search-tight-max);
123
+ }
124
+
125
+ /* ═══════ Icon-only stage ═══════ (ADR-0076, REQ-S-003/REQ-M-003/REQ-M-004,
126
+ step 3) — [data-controls] buttons drop their rendered label; the range
127
+ summary's own compaction/hide (REQ-S-003, step 4) rides the SAME
128
+ breakpoint just below, since both are decided by the same container
129
+ crossing --table-toolbar-bp-icon-only.
130
+
131
+ ::after content:none overrides button.css's own `:scope[text]::after {
132
+ content: attr(text) }` generated-content rule (REQ-M-003) — aria-label
133
+ is untouched (button-ui stamps it from [text] unconditionally in its
134
+ own render(), REQ-X-001), so the accessible name survives regardless of
135
+ the CSS-hidden visual label. The selector below is deliberately more
136
+ specific than button.css's own (adds the `:scope` ancestor + `[text]`)
137
+ so it wins the cascade outright rather than relying on scope-proximity
138
+ tie-breaking, which would resolve in button.css's OWN favor at equal
139
+ specificity (its rule is proximity-0 to its own host). Geometry mirrors
140
+ button-ui's own [data-icon-only] treatment exactly (REQ-M-004) —
141
+ --button-px/width are button-ui's own exposed tokens, set here from the
142
+ table-toolbar side the same way [data-sort-row][data-active] overrides
143
+ menu-item-ui's tokens above. Literal `40rem` bound to
144
+ --table-toolbar-bp-icon-only's default (same duplication rationale +
145
+ source-assertion test as the search-tight boundary above). */
146
+ @container table-toolbar (max-width: 40rem) {
147
+ :scope:is(:not([stage]), [stage=""]) [data-toolbar-btn][text]::after {
148
+ content: none;
149
+ }
150
+ :scope:is(:not([stage]), [stage=""]) [data-toolbar-btn] {
151
+ --button-px: 0;
152
+
153
+ width: var(--button-height);
154
+ }
155
+ }
156
+
157
+ :scope:is([stage="icon-only"], [stage="overflow"]) [data-toolbar-btn][text]::after {
158
+ content: none;
159
+ }
160
+ :scope:is([stage="icon-only"], [stage="overflow"]) [data-toolbar-btn] {
161
+ --button-px: 0;
162
+
163
+ width: var(--button-height);
164
+ }
165
+
166
+ /* ═══════ Range-summary conditional compact/hide ═══════ (ADR-0076
167
+ REQ-S-003, step 4) — at the SAME icon-only breakpoint as above:
168
+ - [data-page-size] present AND visible (no [hidden]) → its own
169
+ pagination context already carries the range info, so [data-summary]
170
+ hides entirely (its info is genuinely redundant here).
171
+ - [data-page-size] absent/hidden → [data-summary] is the toolbar's
172
+ ONLY visible range context, so it never fully disappears; instead
173
+ it swaps its full text for the short form (see [data-summary-compact]
174
+ below, "1–25/320" vs "Showing 1–25 of 320").
175
+ Which branch applies is a pure :has()/:not() container-width read — no
176
+ JS drives this (REQ-M-002); #updateSummary() populates BOTH
177
+ [data-summary-text] and [data-summary-compact] together whenever a
178
+ valid range exists, and CSS alone decides which one paints. */
179
+ @container table-toolbar (max-width: 40rem) {
180
+ :scope:is(:not([stage]), [stage=""]):has([data-page-size]:not([hidden])) [data-summary] {
181
+ display: none;
182
+ }
183
+ :scope:is(:not([stage]), [stage=""]):not(:has([data-page-size]:not([hidden]))) [data-summary-text] {
184
+ display: none;
185
+ }
186
+ /* :not([hidden]) — reviewer finding on PR #1674: without it, this
187
+ author-origin `display: inline` OUTRANKS the UA-origin
188
+ `[hidden] { display: none }` rule regardless of specificity (origin
189
+ comes before specificity in the cascade), so a JS-hidden
190
+ [data-summary-compact] (#updateSummary()'s empty-slot/loading
191
+ branches, which set compact.hidden = true) would still paint —
192
+ stale compact text alongside the empty-state content after a
193
+ positive range transitions to range-total="0" in icon-only mode.
194
+ Same class of gotcha this file already reinforces explicitly for
195
+ every other JS-toggled node ([data-toolbar-btn][hidden],
196
+ [data-search][hidden], [data-toolbar-overflow][hidden], etc). */
197
+ :scope:is(:not([stage]), [stage=""]):not(:has([data-page-size]:not([hidden]))) [data-summary-compact]:not([hidden]) {
198
+ display: inline;
199
+ }
200
+ }
201
+
202
+ :scope:is([stage="icon-only"], [stage="overflow"]):has([data-page-size]:not([hidden])) [data-summary] {
203
+ display: none;
204
+ }
205
+ :scope:is([stage="icon-only"], [stage="overflow"]):not(:has([data-page-size]:not([hidden]))) [data-summary-text] {
206
+ display: none;
207
+ }
208
+ :scope:is([stage="icon-only"], [stage="overflow"]):not(:has([data-page-size]:not([hidden]))) [data-summary-compact]:not([hidden]) {
209
+ display: inline;
41
210
  }
42
211
 
43
212
  [data-toolbar] {
@@ -116,6 +285,26 @@
116
285
  line-height: var(--a-font-leading-snug);
117
286
  }
118
287
 
288
+ /* Short-form compact text ("1–25/320", ADR-0076 REQ-S-003) — hidden by
289
+ default (`display: none`, not just [hidden]) since it only ever paints
290
+ inside the icon-only-without-page-size branch above; that branch is the
291
+ ONLY place this flips to `display: inline`. Same typography as
292
+ [data-summary-text] — it's the same information, just terser. */
293
+ [data-summary-compact] {
294
+ display: none;
295
+ font-size: var(--table-toolbar-summary-size);
296
+ color: var(--table-toolbar-summary-fg);
297
+ line-height: var(--a-font-leading-snug);
298
+ }
299
+
300
+ /* ═══════ Empty-state slot ═══════ (ADR-0076 REQ-E — renders in
301
+ [data-summary]'s own position, alongside [data-summary-text]; only one
302
+ of the two is ever visible at a time, per #updateSummary(). Plain
303
+ author-supplied markup (REQ-E-003) — no forced typography here, unlike
304
+ [data-summary-text] above, which styles OUR OWN generated text. */
305
+
306
+ [data-summary-empty][hidden] { display: none; }
307
+
119
308
  /* ═══════ Controls cluster ═══════ */
120
309
 
121
310
  [data-controls] {
@@ -125,8 +314,63 @@
125
314
  flex: 0 0 auto;
126
315
  }
127
316
 
317
+ /* ADR-0076 REQ-C-003 — with [chrome-only] set, all three buttons carry
318
+ [hidden] (see #updateControlVisibility()); this collapses the cluster
319
+ to zero footprint via the same :empty{display:none} shape [data-scope]/
320
+ [data-actions]/[data-actions-leading] already use above, defensively
321
+ covering the case where no button children are stamped at all. */
322
+ [data-controls]:empty { display: none; }
323
+
128
324
  [data-toolbar-btn][hidden] { display: none; }
129
325
 
326
+ /* ═══════ Overflow stage ═══════ (ADR-0076 REQ-S-004/REQ-X-003/004,
327
+ PLAN step 5) — collapses Filter/Sort/Columns into one "More" trigger
328
+ at the tightest breakpoint. The three granular buttons STAY in the
329
+ DOM (never created/destroyed across a stage transition — the simplest
330
+ way to keep REQ-X-004's tab-order guarantee correct); CSS alone
331
+ decides which cluster paints (REQ-M-002). [data-toolbar-overflow] is
332
+ the <menu-ui> wrapper stamped by #mkOverflowTrigger() — hiding/showing
333
+ it hides/shows its [slot="trigger"] button together with its (closed)
334
+ menu-item-ui children in one rule, matching how every other stage
335
+ toggle in this file targets one wrapper element rather than each
336
+ child individually. Literal `26rem` bound to
337
+ --table-toolbar-bp-overflow's own default (same duplication +
338
+ source-assertion discipline as the search-tight/icon-only boundaries
339
+ above). */
340
+ [data-toolbar-overflow] { display: none; }
341
+
342
+ @container table-toolbar (max-width: 26rem) {
343
+ :scope:is(:not([stage]), [stage=""]) [data-toolbar-btn] {
344
+ display: none;
345
+ }
346
+ :scope:is(:not([stage]), [stage=""]) [data-toolbar-overflow] {
347
+ display: inline-flex;
348
+ }
349
+ }
350
+
351
+ :scope[stage="overflow"] [data-toolbar-btn] {
352
+ display: none;
353
+ }
354
+ :scope[stage="overflow"] [data-toolbar-overflow] {
355
+ display: inline-flex;
356
+ }
357
+
358
+ /* [hidden] (per-kind chrome-only/no-* suppression, #updateControlVisibility())
359
+ must win over the stage-driven `display: inline-flex` rules above —
360
+ both for the reason `[data-toolbar-btn][hidden]`/`[data-search][hidden]`
361
+ etc reinforce it above (the browser's own UA-origin `[hidden]{display:
362
+ none}` rule is LOWER origin priority than any author rule, so an
363
+ unreinforced [hidden] would lose to author CSS) AND because this
364
+ selector must match or exceed the (0,3,0) specificity of the
365
+ `:scope … [data-toolbar-overflow]` show-rules above to win the tie —
366
+ `:scope [data-toolbar-overflow][hidden]` (0,3,0), placed after both in
367
+ source order, satisfies that. This covers the whole-trigger-suppressed
368
+ case (chrome-only, or all three no-* set) — the per-menu-item-ui
369
+ hidden state inside the (closed) menu needs no CSS help since
370
+ [hidden]'s UA default applies cleanly to a plain child with no
371
+ conflicting author rule. */
372
+ :scope [data-toolbar-overflow][hidden] { display: none; }
373
+
130
374
  /* ═══════ Search ═══════ */
131
375
 
132
376
  [data-search] {
@@ -140,6 +384,27 @@
140
384
 
141
385
  [data-search][hidden] { display: none; }
142
386
 
387
+ /* ═══════ Actions-leading slot ═══════ (gh#1649 — app-owned triggers, e.g.
388
+ custom Filter/Columns buttons, that land BEFORE the page-size select)
389
+ Carries the SAME margin-inline-start:auto as [data-search]/[data-page-size]
390
+ so the trailing group still pushes to the far edge even when EVERY
391
+ native affordance ahead of it is dropped ([no-search] + [no-filter]/
392
+ [no-sort]/[no-columns], the adiav2 shape) and this slot becomes the
393
+ first visible member of the trailing cluster. Multiple auto-margin
394
+ siblings in one flex row is safe — only the first one with unclaimed
395
+ free space to its left actually receives it (same note as [data-page-size]
396
+ below). */
397
+
398
+ [data-actions-leading] {
399
+ display: inline-flex;
400
+ align-items: center;
401
+ gap: var(--table-toolbar-cluster-gap);
402
+ flex: 0 0 auto;
403
+ margin-inline-start: auto;
404
+ }
405
+
406
+ [data-actions-leading]:empty { display: none; }
407
+
143
408
  /* ═══════ Page-size select ═══════ (gh#1615)
144
409
  Carries the SAME margin-inline-start:auto as [data-search] so the
145
410
  trailing group (controls/search/page-size/actions) still pushes to the
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `<table-toolbar-ui>` — Header / companion bar for a sibling table-ui. Renders an optional leading [slot="scope"] region, title + count badge, an optional "Showing X–Y of N" range summary, filter / sort / columns popovers, a search input, an optional page-size select, and a trailing [slot="actions"] region — all wired to the target table via an [for] id-ref. Modeled on chart-legend-ui's [for] binding pattern. Drop next to (or above) any table-ui to add the standard data-grid toolbar without re-implementing search, filter, sort, column visibility, or pagination summary/page-size. Filter rows auto-pick a primitive per column: ≤ 50 distinct values → multi-select (searchable when ≥ 12 options), id-like keys → free-text contains. The column descriptor's `filter` field overrides the auto-detect: `'select'` forces multi-select even on high-cardinality columns; `'text'` forces a contains input even on small enums.
2
+ * `<table-toolbar-ui>` — Header / companion bar for a sibling table-ui. Renders an optional leading [slot="scope"] region, title + count badge, an optional "Showing X–Y of N" range summary, filter / sort / columns popovers, a search input, an optional [slot="actions-leading"] region, an optional page-size select, and a trailing [slot="actions"] region — all wired to the target table via an [for] id-ref. Modeled on chart-legend-ui's [for] binding pattern. Drop next to (or above) any table-ui to add the standard data-grid toolbar without re-implementing search, filter, sort, column visibility, or pagination summary/page-size. Filter rows auto-pick a primitive per column: ≤ 50 distinct values → multi-select (searchable when ≥ 12 options), id-like keys → free-text contains. The column descriptor's `filter` field overrides the auto-detect: `'select'` forces multi-select even on high-cardinality columns; `'text'` forces a contains input even on small enums.
3
3
  *
4
4
  * @see https://ui-kit.exe.xyz/site/components/table-toolbar
5
5
  *
@@ -19,6 +19,8 @@ export type TableToolbarSearchEvent = CustomEvent<unknown>;
19
19
  export type TableToolbarSortChangeEvent = CustomEvent<unknown>;
20
20
 
21
21
  export class UITableToolbar extends UIElement {
22
+ /** Suppress all four native controls/search at once (filter, sort, columns, search) as additive sugar over noFilter/noSort/noColumns/ noSearch (ADR-0076, ADIA2-9123). Precedence is pure, absolute OR — while set, all four stay off regardless of any individual no-* attribute's own value, with no partial re-enable path; to re-enable one control, remove chrome-only entirely and set the other three no-* attributes explicitly instead. The four granular attributes are not deprecated or removed — they remain the independently-addressable shipped API; chrome-only never replaces them, it's a convenience preset on top. */
23
+ chromeOnly: boolean;
22
24
  /** Optional count badge value shown next to the title. When unset, falls back to the row count of the bound table. */
23
25
  count: string;
24
26
  /** id-ref of the table-ui to control. Falls back to the first sibling table-ui within the same parent when omitted. */
@@ -43,6 +45,8 @@ export class UITableToolbar extends UIElement {
43
45
  rangeStart: number;
44
46
  /** Total row count across all pages, for the range summary (e.g. `320` in "Showing 1–25 of 320"). Distinct from pagination-ui's `total` (total PAGES) — this is total ROWS; deliberately not spelled `total` to avoid that cross-sibling name collision (ADR-0063 B5). See rangeStart. */
45
47
  rangeTotal: number;
48
+ /** Explicit compaction-stage override (ADR-0076). Unset (the default, empty string) means auto-snap: the toolbar's own inline-size container query picks the nearest stage from its live width against the three --table-toolbar-bp-* breakpoints, snapping discretely, never interpolating. Setting stage explicitly pins that stage's rendering regardless of the container's actual width — overriding the container query, mirroring chart-ui's ratio override shape. Primary use: visual-eval fixtures that need a deterministic stage without resizing a real container, and a consumer embedding the toolbar inside a known-narrow panel who wants to skip auto-detection entirely. All four stages' CSS is now live (ADR-0076): search-tight steps the search field down (step 2); icon-only drops control labels and compacts/hides the range summary (steps 3+4); overflow collapses Filter/Sort/Columns into one "More" trigger routed through the same menu-ui/menu-item-ui pattern used elsewhere in the corpus (step 5). The resolved stage — the pin if set, otherwise the live width classification — also reflects onto the host as `data-stage-resolved` (step 7, informational only, mirrors chart-ui's `data-ratio-resolved` convention — never drives table-toolbar's own rendering, which stays 100% CSS-driven). */
49
+ stage: '' | 'full' | 'search-tight' | 'icon-only' | 'overflow';
46
50
  /** Title text shown on the left. */
47
51
  text: string;
48
52
  /** Toolbar visual variant. `default` renders bare on parent surface; `card` adds the same chrome as a card-ui header. */
@@ -9,16 +9,17 @@ description: >-
9
9
  Header / companion bar for a sibling table-ui. Renders an optional leading
10
10
  [slot="scope"] region, title + count badge, an optional "Showing X–Y of N"
11
11
  range summary, filter / sort / columns popovers, a search input, an
12
- optional page-size select, and a trailing [slot="actions"] region all
13
- wired to the target table via an [for] id-ref. Modeled on chart-legend-ui's
14
- [for] binding pattern. Drop next to (or above) any table-ui to add the
15
- standard data-grid toolbar without re-implementing search, filter, sort,
16
- column visibility, or pagination summary/page-size. Filter rows auto-pick
17
- a primitive per column: 50 distinct values → multi-select (searchable
18
- when 12 options), id-like keys free-text contains. The column
19
- descriptor's `filter` field overrides the auto-detect: `'select'` forces
20
- multi-select even on high-cardinality columns; `'text'` forces a contains
21
- input even on small enums.
12
+ optional [slot="actions-leading"] region, an optional page-size select,
13
+ and a trailing [slot="actions"] region — all wired to the target table via
14
+ an [for] id-ref. Modeled on chart-legend-ui's [for] binding pattern. Drop
15
+ next to (or above) any table-ui to add the standard data-grid toolbar
16
+ without re-implementing search, filter, sort, column visibility, or
17
+ pagination summary/page-size. Filter rows auto-pick a primitive per
18
+ column: 50 distinct valuesmulti-select (searchable when ≥ 12
19
+ options), id-like keys free-text contains. The column descriptor's
20
+ `filter` field overrides the auto-detect: `'select'` forces multi-select
21
+ even on high-cardinality columns; `'text'` forces a contains input even on
22
+ small enums.
22
23
  # Per ADR-0027 — primitives that programmatically create other primitives
23
24
  # do NOT auto-import them. Consumer (or demo shell) must explicitly import.
24
25
  composes:
@@ -29,6 +30,7 @@ composes:
29
30
  - field-ui
30
31
  - select-ui
31
32
  - input-ui
33
+ - menu-ui
32
34
  - menu-item-ui
33
35
  - icon-ui
34
36
  - check-ui
@@ -127,6 +129,53 @@ props:
127
129
  default: false
128
130
  reflect: true
129
131
  attribute: no-search
132
+ stage:
133
+ description: >-
134
+ Explicit compaction-stage override (ADR-0076). Unset (the default,
135
+ empty string) means auto-snap: the toolbar's own inline-size
136
+ container query picks the nearest stage from its live width against
137
+ the three --table-toolbar-bp-* breakpoints, snapping discretely,
138
+ never interpolating. Setting stage explicitly pins that stage's
139
+ rendering regardless of the container's actual width — overriding
140
+ the container query, mirroring chart-ui's ratio override shape.
141
+ Primary use: visual-eval fixtures that need a deterministic stage
142
+ without resizing a real container, and a consumer embedding the
143
+ toolbar inside a known-narrow panel who wants to skip auto-detection
144
+ entirely. All four stages' CSS is now live (ADR-0076): search-tight
145
+ steps the search field down (step 2); icon-only drops control labels
146
+ and compacts/hides the range summary (steps 3+4); overflow collapses
147
+ Filter/Sort/Columns into one "More" trigger routed through the same
148
+ menu-ui/menu-item-ui pattern used elsewhere in the corpus (step 5).
149
+ The resolved stage — the pin if set, otherwise the live width
150
+ classification — also reflects onto the host as
151
+ `data-stage-resolved` (step 7, informational only, mirrors chart-ui's
152
+ `data-ratio-resolved` convention — never drives table-toolbar's own
153
+ rendering, which stays 100% CSS-driven).
154
+ type: string
155
+ default: ""
156
+ enum:
157
+ - ""
158
+ - full
159
+ - search-tight
160
+ - icon-only
161
+ - overflow
162
+ reflect: true
163
+ chromeOnly:
164
+ description: >-
165
+ Suppress all four native controls/search at once (filter, sort,
166
+ columns, search) as additive sugar over noFilter/noSort/noColumns/
167
+ noSearch (ADR-0076, ADIA2-9123). Precedence is pure, absolute OR —
168
+ while set, all four stay off regardless of any individual no-*
169
+ attribute's own value, with no partial re-enable path; to
170
+ re-enable one control, remove chrome-only entirely and set the
171
+ other three no-* attributes explicitly instead. The four granular
172
+ attributes are not deprecated or removed — they remain the
173
+ independently-addressable shipped API; chrome-only never replaces
174
+ them, it's a convenience preset on top.
175
+ type: boolean
176
+ default: false
177
+ reflect: true
178
+ attribute: chrome-only
130
179
  placeholder:
131
180
  description: Placeholder text for the search input.
132
181
  type: string
@@ -153,8 +202,33 @@ events:
153
202
  slots:
154
203
  scope:
155
204
  description: Leading region rendered BEFORE the [text]/[count] title cluster — e.g. a scope/view-switcher menu (an org's teams, a saved view). Positioning is CSS by DOM order, mirroring how [slot="actions"] is a real, author-fillable insertion point rather than template-stamped content.
205
+ empty:
206
+ description: >-
207
+ Renders in place of the range-summary text — the same [data-summary]
208
+ region — when [range-total] is EXPLICITLY present and equal to "0"
209
+ (checked via hasAttribute, never the computed value alone, so an
210
+ unset range-total during loading never flashes this content;
211
+ ADR-0076, REQ-E-001). Mutually exclusive with a positive
212
+ [range-total]: the normal "Showing X-Y of N" text always wins
213
+ whenever range-total is a positive number. Empty (the default)
214
+ preserves today's exact behavior — [data-summary] hides on a
215
+ confirmed range-total="0" exactly as before this slot existed.
216
+ Plain author-supplied markup (text, an empty-state-ui composite, an
217
+ icon+text pair) — table-toolbar does not template or constrain its
218
+ shape. Naming matches chart-ui's own existing `empty` slot
219
+ convention.
220
+ actions-leading:
221
+ description: >-
222
+ Region rendered AFTER the search input and BEFORE the native
223
+ page-size select (gh#1649) — for app-owned trigger buttons (e.g.
224
+ custom Filter / Columns triggers that open the consumer's own
225
+ drawer instead of the native filter/columns popovers) that must
226
+ land ahead of the page-size select rather than after it, unlike
227
+ [slot="actions"]. Same real-insertion-point pattern as [slot="scope"]
228
+ / [slot="actions"] — positioning is CSS by DOM order, not a template
229
+ stamp. Empty (the default) has zero footprint on existing consumers.
156
230
  actions:
157
- description: Trailing action area — primary buttons (e.g. "New row") rendered after the search input.
231
+ description: Trailing action area — primary buttons (e.g. "New row") rendered after the search input, [slot="actions-leading"], and the page-size select.
158
232
  states:
159
233
  - name: idle
160
234
  description: Default, ready for interaction.
@@ -186,6 +260,7 @@ requiredIcons:
186
260
  - arrow-up
187
261
  - arrow-down
188
262
  - caret-up-down
263
+ - dots-three-vertical
189
264
  a2ui:
190
265
  rules:
191
266
  - >-
@@ -223,6 +298,39 @@ a2ui:
223
298
  + [page-size-options] for a rows-per-page select; it applies
224
299
  directly to the bound table's [paginate] prop the same way
225
300
  filter/sort/columns changes apply directly to the target.
301
+ - >-
302
+ Use [slot="actions-leading"] (gh#1649) for app-owned trigger
303
+ buttons that must land BEFORE the page-size select — e.g. custom
304
+ Filter / Columns triggers that open the consumer's own drawer
305
+ instead of the native filter/columns popovers (drop [no-filter]
306
+ [no-columns] to hide the native ones when doing this). [slot="actions"]
307
+ stays the trailing region AFTER the page-size select, for a
308
+ primary CTA.
309
+ - >-
310
+ Use [chrome-only] (ADR-0076, ADIA2-9123) instead of stacking
311
+ [no-filter] [no-sort] [no-columns] [no-search] individually when an
312
+ app owns ALL FOUR affordances itself (its own drawer/menu). Pure OR,
313
+ absolute while set — clearing an individual no-* attribute does NOT
314
+ re-enable that one control; drop [chrome-only] entirely and set the
315
+ three you still want off explicitly instead. Combine with
316
+ [slot="actions-leading"] for the app's own Filter/Columns triggers.
317
+ - >-
318
+ Use <span slot="empty"> (or any markup) inside a table-toolbar-ui
319
+ that also sets [range-total="0"] to show a "no results" message in
320
+ the range-summary's own position, without a layout shift versus the
321
+ normal "Showing X-Y of N" text (ADR-0076, REQ-E). Only fires on an
322
+ EXPLICIT range-total="0" — omitting range-* entirely (loading) never
323
+ shows it.
324
+ - >-
325
+ Read the host's [data-stage-resolved] attribute (ADR-0076 REQ-M-005,
326
+ ADIA2-9123 S4-ii) to compact a consumer's own [slot="actions-leading"]
327
+ content in sympathy with table-toolbar's native regions, without
328
+ re-deriving the same breakpoints yourself. Informational only —
329
+ mirrors the CSS-decided compaction stage (full | search-tight |
330
+ icon-only | overflow), never drives table-toolbar's own rendering.
331
+ Reports the pinned [stage] value when set, the live width
332
+ classification otherwise; updates on both a live resize and a
333
+ [stage] attribute change.
226
334
  anti_patterns: []
227
335
  examples:
228
336
  - name: members-toolbar
@@ -237,13 +345,20 @@ examples:
237
345
  ]
238
346
  - name: adiav2-list-page-toolbar-row
239
347
  description: >-
240
- The ratified adiav2 list-page toolbar row (ADIA2-9123): scope menu,
241
- range summary, filter/columns triggers, page-size select, and a
242
- primary CTA search and sort dropped per that contract.
348
+ The ratified adiav2 list-page toolbar row (ADIA2-9123, gh#1649): scope
349
+ menu, range summary, app-owned Filter/Columns triggers (their own
350
+ filter drawer, not the native popovers [no-filter]/[no-columns]/
351
+ [no-sort]/[no-search] drop the native affordances), page-size select,
352
+ and a primary CTA — [slot="actions-leading"] carries the Filter/Columns
353
+ triggers so they land BEFORE the page-size select, while [slot="actions"]
354
+ carries the CTA so it lands after.
243
355
  a2ui: >-
244
356
  [
245
357
  {"id": "root", "component": "Column", "gap": "3", "children": ["bar", "card"]},
246
- {"id": "bar", "component": "TableToolbar", "for": "accounts", "no-sort": true, "no-search": true, "range-start": 1, "range-end": 25, "range-total": 320, "page-size": 25, "page-size-options": [10, 25, 50, 100]},
358
+ {"id": "bar", "component": "TableToolbar", "for": "accounts", "no-filter": true, "no-sort": true, "no-columns": true, "no-search": true, "range-start": 1, "range-end": 25, "range-total": 320, "page-size": 25, "page-size-options": [10, 25, 50, 100], "children": ["btn-filter", "btn-columns", "btn-new"]},
359
+ {"id": "btn-filter", "component": "Button", "text": "Filter", "variant": "ghost", "slot": "actions-leading"},
360
+ {"id": "btn-columns", "component": "Button", "text": "Columns", "variant": "ghost", "slot": "actions-leading"},
361
+ {"id": "btn-new", "component": "Button", "text": "New account", "variant": "primary", "slot": "actions"},
247
362
  {"id": "card", "component": "Card", "children": ["sec"]},
248
363
  {"id": "sec", "component": "Section", "bleed": true, "children": ["tbl"]},
249
364
  {"id": "tbl", "component": "Table", "id": "accounts", "sortable": true, "raw": true}
@@ -25,6 +25,7 @@
25
25
  */
26
26
 
27
27
  import { UIElement } from '../../core/element.js';
28
+ import { syncAutoAriaLabel } from '../../core/a11y.js';
28
29
 
29
30
  export class UITag extends UIElement {
30
31
  // §154 (v0.5.3): Phosphor icons this primitive auto-stamps (without
@@ -75,6 +76,17 @@ export class UITag extends UIElement {
75
76
 
76
77
  static template = () => null;
77
78
 
79
+ // gh#1644: tracks the aria-label VALUE this element itself last derived
80
+ // from `text` (undefined when none), so re-renders refresh/clear OUR
81
+ // name without ever clobbering a consumer's own aria-label. The
82
+ // compare-against-what-we-last-wrote guard lives in core/a11y.js's
83
+ // `syncAutoAriaLabel` (gh#1647 / gh#1651); this field is its state.
84
+ #lastAutoAriaLabel = undefined;
85
+
86
+ #syncAccessibleName() {
87
+ this.#lastAutoAriaLabel = syncAutoAriaLabel(this, this.#lastAutoAriaLabel, this.text || '');
88
+ }
89
+
78
90
  #onClick = (e) => {
79
91
  if (this.disabled) return;
80
92
  if (e.target.closest('[slot="dismiss"]')) {
@@ -112,7 +124,7 @@ export class UITag extends UIElement {
112
124
  }
113
125
 
114
126
  render() {
115
- if (this.text) this.setAttribute('aria-label', this.text);
127
+ this.#syncAccessibleName();
116
128
 
117
129
  if (this.disabled) {
118
130
  this.setAttribute('aria-disabled', 'true');
@@ -96,10 +96,10 @@
96
96
  "deck": "Sub-title under a `title`. One-line lead, slightly larger than body. Use for the lead sentence after a title.",
97
97
  "display": "Top-level hero / brand display. Tallest visual rank. Use for page-level hero one-liners.",
98
98
  "heading": "Major page heading (visual rank H2). 16-18px / bold. Use for major sub-section dividers.",
99
- "kicker": "Eyebrow text above a `title`. UPPERCASE + small + tracking. Use for content eyebrows (NOT form labels — use `label` for those).",
100
- "label": "Form-control label (above an `<input-ui>` / `<select-ui>` etc). UI-sized + medium-weight. Use for field labels bound to form controls.",
99
+ "kicker": "Eyebrow text above a `title`. UPPERCASE + small + tracking, mono font stack at regular (400) weight. Use for content eyebrows (NOT form labels — use `label` for those).",
100
+ "label": "Form-control label (above an `<input-ui>` / `<select-ui>` etc). UI-sized, mono font stack at regular (400) weight. Use for field labels bound to form controls.",
101
101
  "metric": "Numeric KPI / big-number stat. Bold + large. Use for dashboard metric numbers.",
102
- "section": "Inline form-group / navlist heading (visual rank H4). Small-cap. Use for form group labels, nav list headings.",
102
+ "section": "Inline form-group / navlist heading (visual rank H4). Mono font stack at medium (500) weight, normal casing. Use for form group labels, nav list headings.",
103
103
  "subsection": "Sub-landmark within a section (visual rank H3). 14px / semibold. Use for card titles within a section."
104
104
  }
105
105
  },
@@ -25,13 +25,13 @@ export type UITextVariant =
25
25
  | 'display'
26
26
  /** Annotation under a primary line — smaller + muted. Use for image captions, footnotes. */
27
27
  | 'caption'
28
- /** Form-control label (above an `<input-ui>` / `<select-ui>` etc). UI-sized + medium-weight. Use for field labels bound to form controls. */
28
+ /** Form-control label (above an `<input-ui>` / `<select-ui>` etc). UI-sized, mono font stack at regular (400) weight. Use for field labels bound to form controls. */
29
29
  | 'label'
30
- /** Eyebrow text above a `title`. UPPERCASE + small + tracking. Use for content eyebrows (NOT form labels — use `label` for those). */
30
+ /** Eyebrow text above a `title`. UPPERCASE + small + tracking, mono font stack at regular (400) weight. Use for content eyebrows (NOT form labels — use `label` for those). */
31
31
  | 'kicker'
32
32
  /** Sub-title under a `title`. One-line lead, slightly larger than body. Use for the lead sentence after a title. */
33
33
  | 'deck'
34
- /** Inline form-group / navlist heading (visual rank H4). Small-cap. Use for form group labels, nav list headings. */
34
+ /** Inline form-group / navlist heading (visual rank H4). Mono font stack at medium (500) weight, normal casing. Use for form group labels, nav list headings. */
35
35
  | 'section'
36
36
  /** Numeric KPI / big-number stat. Bold + large. Use for dashboard metric numbers. */
37
37
  | 'metric'
@@ -47,6 +47,8 @@ export class UIText extends UIElement {
47
47
  size: 'sm' | 'md' | 'lg';
48
48
  /** When true, applies stronger emphasis (heavier weight + accent color). Styled via :scope[strong] in text.css. Use instead of variant=heading when you want a single emphasized word inline in body copy. */
49
49
  strong: boolean;
50
+ /** Override text alignment. Note: text-ui defaults to display:inline, so this only takes effect when text-ui is block-like (wrapping or parent display:block/grid). Added v0.6.18 (FB-10). Converged onto the global Enum·semantic `[text-align]` physical vocabulary (gh#1335) — `start`/`end` renamed to `left`/`right`; `justify` kept (a genuine physical alignment value, now also supported by the global grammar). */
51
+ textAlign: 'left' | 'center' | 'right' | 'justify';
50
52
  /** Display text content. The main payload field for Text components extracted from HTML. */
51
53
  textContent: string;
52
54
  /** Single-line truncation with ellipsis. Ignored when `lines` is set. */
@@ -103,10 +103,10 @@ props:
103
103
  title: Page title (visual rank H1). Largest under display. Use at the top of an authoritative page or dialog.
104
104
  heading: Major page heading (visual rank H2). 16-18px / bold. Use for major sub-section dividers.
105
105
  subsection: Sub-landmark within a section (visual rank H3). 14px / semibold. Use for card titles within a section.
106
- section: Inline form-group / navlist heading (visual rank H4). Small-cap. Use for form group labels, nav list headings.
106
+ section: Inline form-group / navlist heading (visual rank H4). Mono font stack at medium (500) weight, normal casing. Use for form group labels, nav list headings.
107
107
  caption: Annotation under a primary line — smaller + muted. Use for image captions, footnotes.
108
- label: Form-control label (above an `<input-ui>` / `<select-ui>` etc). UI-sized + medium-weight. Use for field labels bound to form controls.
109
- kicker: Eyebrow text above a `title`. UPPERCASE + small + tracking. Use for content eyebrows (NOT form labels — use `label` for those).
108
+ label: Form-control label (above an `<input-ui>` / `<select-ui>` etc). UI-sized, mono font stack at regular (400) weight. Use for field labels bound to form controls.
109
+ kicker: Eyebrow text above a `title`. UPPERCASE + small + tracking, mono font stack at regular (400) weight. Use for content eyebrows (NOT form labels — use `label` for those).
110
110
  deck: Sub-title under a `title`. One-line lead, slightly larger than body. Use for the lead sentence after a title.
111
111
  metric: Numeric KPI / big-number stat. Bold + large. Use for dashboard metric numbers.
112
112
  code: Inline monospace code reference. Use for inline code within prose.