@adia-ai/web-components 0.8.43 → 0.8.45

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 (113) hide show
  1. package/CHANGELOG.md +54 -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.a2ui.json +8 -3
  21. package/components/context-menu/context-menu.class.js +46 -5
  22. package/components/context-menu/context-menu.d.ts +8 -3
  23. package/components/context-menu/context-menu.examples.md +2 -2
  24. package/components/context-menu/context-menu.yaml +22 -5
  25. package/components/heatmap/heatmap.a2ui.json +5 -0
  26. package/components/heatmap/heatmap.d.ts +2 -0
  27. package/components/heatmap/heatmap.yaml +8 -0
  28. package/components/input/input.a2ui.json +2 -2
  29. package/components/input/input.class.js +12 -7
  30. package/components/input/input.yaml +6 -5
  31. package/components/integration-card/integration-card.d.ts +2 -0
  32. package/components/nav/nav.a2ui.json +2 -2
  33. package/components/nav/nav.css +1 -1
  34. package/components/nav/nav.d.ts +1 -1
  35. package/components/nav/nav.yaml +14 -3
  36. package/components/nav-group/nav-group.css +37 -3
  37. package/components/noodles/noodles.a2ui.json +0 -15
  38. package/components/noodles/noodles.yaml +0 -12
  39. package/components/number-format/number-format.class.js +10 -0
  40. package/components/pagination/pagination.class.js +140 -26
  41. package/components/popover/popover.d.ts +2 -0
  42. package/components/progress/progress.class.js +11 -6
  43. package/components/range/range.class.js +9 -1
  44. package/components/search/search.class.js +39 -5
  45. package/components/select/select.a2ui.json +10 -1
  46. package/components/select/select.class.js +34 -14
  47. package/components/select/select.css +39 -1
  48. package/components/select/select.d.ts +2 -0
  49. package/components/select/select.yaml +19 -1
  50. package/components/stream/stream.d.ts +2 -0
  51. package/components/table/cell-types.js +9 -0
  52. package/components/table/table.a2ui.json +6 -1
  53. package/components/table/table.class.js +265 -34
  54. package/components/table/table.css +38 -10
  55. package/components/table/table.d.ts +3 -1
  56. package/components/table/table.examples.md +2 -2
  57. package/components/table/table.yaml +15 -2
  58. package/components/table-toolbar/table-toolbar.a2ui.json +43 -4
  59. package/components/table-toolbar/table-toolbar.class.js +407 -28
  60. package/components/table-toolbar/table-toolbar.css +299 -0
  61. package/components/table-toolbar/table-toolbar.d.ts +15 -1
  62. package/components/table-toolbar/table-toolbar.yaml +162 -15
  63. package/components/tag/tag.class.js +13 -1
  64. package/components/text/text.a2ui.json +3 -3
  65. package/components/text/text.d.ts +5 -3
  66. package/components/text/text.yaml +3 -3
  67. package/components/textarea/textarea.class.js +9 -1
  68. package/components/tour/tour.d.ts +10 -0
  69. package/core/a11y.d.ts +22 -0
  70. package/core/a11y.js +64 -0
  71. package/core/anchor.js +9 -5
  72. package/core/data-stream.js +37 -2
  73. package/core/element.js +1 -1
  74. package/core/index.d.ts +1 -0
  75. package/core/index.js +1 -0
  76. package/core/provider.d.ts +9 -13
  77. package/core/provider.js +9 -113
  78. package/core/store.d.ts +46 -0
  79. package/core/store.js +89 -0
  80. package/custom-elements.json +127 -33
  81. package/dist/host.min.css +1 -1
  82. package/dist/host.sheet.js +1 -1
  83. package/dist/theme-provider.min.js +1 -1
  84. package/dist/web-components.min.css +1 -1
  85. package/dist/web-components.min.js +119 -119
  86. package/dist/web-components.sheet.js +1 -1
  87. package/package.json +1 -1
  88. package/patterns/access-requests/access-requests.examples.html +1 -1
  89. package/patterns/admin-keys-and-export/admin-keys-and-export.examples.html +7 -7
  90. package/patterns/agent-cost/agent-cost.examples.html +6 -6
  91. package/patterns/agent-memory/agent-memory.examples.html +3 -3
  92. package/patterns/agent-prompt-library/agent-prompt-library.examples.html +8 -8
  93. package/patterns/agent-tool-call/agent-tool-call.examples.html +1 -1
  94. package/patterns/approvals/approvals.examples.html +6 -6
  95. package/patterns/audit-log/audit-log.examples.html +1 -1
  96. package/patterns/bulk-action-toolbar/bulk-action-toolbar.examples.html +4 -4
  97. package/patterns/chart-in-card/chart-in-card.examples.html +114 -0
  98. package/patterns/chart-in-card/chart-in-card.examples.js +37 -0
  99. package/patterns/comments-and-collaboration/comments-and-collaboration.examples.html +5 -5
  100. package/patterns/data-tables-inline-edit-and-tree/data-tables-inline-edit-and-tree.examples.html +2 -2
  101. package/patterns/diff-review/diff-review.examples.html +6 -6
  102. package/patterns/export-flow/export-flow.examples.html +2 -2
  103. package/patterns/filter-bar/filter-bar.examples.html +2 -2
  104. package/patterns/forms-conditional-and-autocomplete/forms-conditional-and-autocomplete.examples.html +2 -2
  105. package/patterns/inline-dialog/inline-dialog.examples.html +2 -2
  106. package/patterns/marketing-engagement/marketing-engagement.examples.html +4 -4
  107. package/patterns/notifications-bell-and-digest/notifications-bell-and-digest.examples.html +1 -1
  108. package/patterns/permissions-matrix/permissions-matrix.examples.html +1 -1
  109. package/patterns/permissions-sharing/permissions-sharing.examples.html +2 -2
  110. package/patterns/profile-public-and-verification/profile-public-and-verification.examples.html +4 -4
  111. package/patterns/search-discovery/search-discovery.examples.html +2 -2
  112. package/styles/api/sizing.css +46 -0
  113. 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,185 @@
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
+ /* ═══════ Actions-leading icon-only reduction ═══════ (gh#1748) — extends
167
+ the SAME icon-only collapse above to a plain <button-ui> a consumer
168
+ slots into [slot="actions-leading"] (gh#1649, e.g. the app-owned
169
+ Filter/Columns triggers on the ratified adiav2 row), so it reads as
170
+ part of the same toolbar affordance cluster rather than staying
171
+ full-width while its native siblings compact. A separate rule block
172
+ (rather than folding into [data-toolbar-btn]'s own selector list above)
173
+ keeps that block's literal text byte-identical for its own
174
+ source-assertion tests. Same breakpoint/pin pair, same
175
+ aria-label-survives guarantee (button-ui's own REQ-X-001 stamp,
176
+ unaffected here) — light-DOM real insertion point (no shadow
177
+ boundary), so a plain descendant tag selector reaches it directly, no
178
+ `::slotted()` needed. Literal `40rem` bound to
179
+ --table-toolbar-bp-icon-only's default, same as the native block. */
180
+ @container table-toolbar (max-width: 40rem) {
181
+ :scope:is(:not([stage]), [stage=""]) [data-actions-leading] button-ui[text]::after {
182
+ content: none;
183
+ }
184
+ :scope:is(:not([stage]), [stage=""]) [data-actions-leading] button-ui {
185
+ --button-px: 0;
186
+
187
+ width: var(--button-height);
188
+ }
189
+ }
190
+
191
+ :scope:is([stage="icon-only"], [stage="overflow"]) [data-actions-leading] button-ui[text]::after {
192
+ content: none;
193
+ }
194
+ :scope:is([stage="icon-only"], [stage="overflow"]) [data-actions-leading] button-ui {
195
+ --button-px: 0;
196
+
197
+ width: var(--button-height);
198
+ }
199
+
200
+ /* ═══════ Range-summary conditional compact/hide ═══════ (ADR-0076
201
+ REQ-S-003, step 4) — at the SAME icon-only breakpoint as above:
202
+ - [data-page-size] present AND visible (no [hidden]) → its own
203
+ pagination context already carries the range info, so [data-summary]
204
+ hides entirely (its info is genuinely redundant here).
205
+ - [data-page-size] absent/hidden → [data-summary] is the toolbar's
206
+ ONLY visible range context, so it never fully disappears; instead
207
+ it swaps its full text for the short form (see [data-summary-compact]
208
+ below, "1–25/320" vs "Showing 1–25 of 320").
209
+ Which branch applies is a pure :has()/:not() container-width read — no
210
+ JS drives this (REQ-M-002); #updateSummary() populates BOTH
211
+ [data-summary-text] and [data-summary-compact] together whenever a
212
+ valid range exists, and CSS alone decides which one paints. */
213
+ @container table-toolbar (max-width: 40rem) {
214
+ :scope:is(:not([stage]), [stage=""]):has([data-page-size]:not([hidden])) [data-summary] {
215
+ display: none;
216
+ }
217
+ :scope:is(:not([stage]), [stage=""]):not(:has([data-page-size]:not([hidden]))) [data-summary-text] {
218
+ display: none;
219
+ }
220
+ /* :not([hidden]) — reviewer finding on PR #1674: without it, this
221
+ author-origin `display: inline` OUTRANKS the UA-origin
222
+ `[hidden] { display: none }` rule regardless of specificity (origin
223
+ comes before specificity in the cascade), so a JS-hidden
224
+ [data-summary-compact] (#updateSummary()'s empty-slot/loading
225
+ branches, which set compact.hidden = true) would still paint —
226
+ stale compact text alongside the empty-state content after a
227
+ positive range transitions to range-total="0" in icon-only mode.
228
+ Same class of gotcha this file already reinforces explicitly for
229
+ every other JS-toggled node ([data-toolbar-btn][hidden],
230
+ [data-search][hidden], [data-toolbar-overflow][hidden], etc). */
231
+ :scope:is(:not([stage]), [stage=""]):not(:has([data-page-size]:not([hidden]))) [data-summary-compact]:not([hidden]) {
232
+ display: inline;
233
+ }
234
+ }
235
+
236
+ :scope:is([stage="icon-only"], [stage="overflow"]):has([data-page-size]:not([hidden])) [data-summary] {
237
+ display: none;
238
+ }
239
+ :scope:is([stage="icon-only"], [stage="overflow"]):not(:has([data-page-size]:not([hidden]))) [data-summary-text] {
240
+ display: none;
241
+ }
242
+ :scope:is([stage="icon-only"], [stage="overflow"]):not(:has([data-page-size]:not([hidden]))) [data-summary-compact]:not([hidden]) {
243
+ display: inline;
41
244
  }
42
245
 
43
246
  [data-toolbar] {
@@ -116,6 +319,26 @@
116
319
  line-height: var(--a-font-leading-snug);
117
320
  }
118
321
 
322
+ /* Short-form compact text ("1–25/320", ADR-0076 REQ-S-003) — hidden by
323
+ default (`display: none`, not just [hidden]) since it only ever paints
324
+ inside the icon-only-without-page-size branch above; that branch is the
325
+ ONLY place this flips to `display: inline`. Same typography as
326
+ [data-summary-text] — it's the same information, just terser. */
327
+ [data-summary-compact] {
328
+ display: none;
329
+ font-size: var(--table-toolbar-summary-size);
330
+ color: var(--table-toolbar-summary-fg);
331
+ line-height: var(--a-font-leading-snug);
332
+ }
333
+
334
+ /* ═══════ Empty-state slot ═══════ (ADR-0076 REQ-E — renders in
335
+ [data-summary]'s own position, alongside [data-summary-text]; only one
336
+ of the two is ever visible at a time, per #updateSummary(). Plain
337
+ author-supplied markup (REQ-E-003) — no forced typography here, unlike
338
+ [data-summary-text] above, which styles OUR OWN generated text. */
339
+
340
+ [data-summary-empty][hidden] { display: none; }
341
+
119
342
  /* ═══════ Controls cluster ═══════ */
120
343
 
121
344
  [data-controls] {
@@ -125,8 +348,63 @@
125
348
  flex: 0 0 auto;
126
349
  }
127
350
 
351
+ /* ADR-0076 REQ-C-003 — with [chrome-only] set, all three buttons carry
352
+ [hidden] (see #updateControlVisibility()); this collapses the cluster
353
+ to zero footprint via the same :empty{display:none} shape [data-scope]/
354
+ [data-actions]/[data-actions-leading] already use above, defensively
355
+ covering the case where no button children are stamped at all. */
356
+ [data-controls]:empty { display: none; }
357
+
128
358
  [data-toolbar-btn][hidden] { display: none; }
129
359
 
360
+ /* ═══════ Overflow stage ═══════ (ADR-0076 REQ-S-004/REQ-X-003/004,
361
+ PLAN step 5) — collapses Filter/Sort/Columns into one "More" trigger
362
+ at the tightest breakpoint. The three granular buttons STAY in the
363
+ DOM (never created/destroyed across a stage transition — the simplest
364
+ way to keep REQ-X-004's tab-order guarantee correct); CSS alone
365
+ decides which cluster paints (REQ-M-002). [data-toolbar-overflow] is
366
+ the <menu-ui> wrapper stamped by #mkOverflowTrigger() — hiding/showing
367
+ it hides/shows its [slot="trigger"] button together with its (closed)
368
+ menu-item-ui children in one rule, matching how every other stage
369
+ toggle in this file targets one wrapper element rather than each
370
+ child individually. Literal `26rem` bound to
371
+ --table-toolbar-bp-overflow's own default (same duplication +
372
+ source-assertion discipline as the search-tight/icon-only boundaries
373
+ above). */
374
+ [data-toolbar-overflow] { display: none; }
375
+
376
+ @container table-toolbar (max-width: 26rem) {
377
+ :scope:is(:not([stage]), [stage=""]) [data-toolbar-btn] {
378
+ display: none;
379
+ }
380
+ :scope:is(:not([stage]), [stage=""]) [data-toolbar-overflow] {
381
+ display: inline-flex;
382
+ }
383
+ }
384
+
385
+ :scope[stage="overflow"] [data-toolbar-btn] {
386
+ display: none;
387
+ }
388
+ :scope[stage="overflow"] [data-toolbar-overflow] {
389
+ display: inline-flex;
390
+ }
391
+
392
+ /* [hidden] (per-kind chrome-only/no-* suppression, #updateControlVisibility())
393
+ must win over the stage-driven `display: inline-flex` rules above —
394
+ both for the reason `[data-toolbar-btn][hidden]`/`[data-search][hidden]`
395
+ etc reinforce it above (the browser's own UA-origin `[hidden]{display:
396
+ none}` rule is LOWER origin priority than any author rule, so an
397
+ unreinforced [hidden] would lose to author CSS) AND because this
398
+ selector must match or exceed the (0,3,0) specificity of the
399
+ `:scope … [data-toolbar-overflow]` show-rules above to win the tie —
400
+ `:scope [data-toolbar-overflow][hidden]` (0,3,0), placed after both in
401
+ source order, satisfies that. This covers the whole-trigger-suppressed
402
+ case (chrome-only, or all three no-* set) — the per-menu-item-ui
403
+ hidden state inside the (closed) menu needs no CSS help since
404
+ [hidden]'s UA default applies cleanly to a plain child with no
405
+ conflicting author rule. */
406
+ :scope [data-toolbar-overflow][hidden] { display: none; }
407
+
130
408
  /* ═══════ Search ═══════ */
131
409
 
132
410
  [data-search] {
@@ -140,6 +418,27 @@
140
418
 
141
419
  [data-search][hidden] { display: none; }
142
420
 
421
+ /* ═══════ Actions-leading slot ═══════ (gh#1649 — app-owned triggers, e.g.
422
+ custom Filter/Columns buttons, that land BEFORE the page-size select)
423
+ Carries the SAME margin-inline-start:auto as [data-search]/[data-page-size]
424
+ so the trailing group still pushes to the far edge even when EVERY
425
+ native affordance ahead of it is dropped ([no-search] + [no-filter]/
426
+ [no-sort]/[no-columns], the adiav2 shape) and this slot becomes the
427
+ first visible member of the trailing cluster. Multiple auto-margin
428
+ siblings in one flex row is safe — only the first one with unclaimed
429
+ free space to its left actually receives it (same note as [data-page-size]
430
+ below). */
431
+
432
+ [data-actions-leading] {
433
+ display: inline-flex;
434
+ align-items: center;
435
+ gap: var(--table-toolbar-cluster-gap);
436
+ flex: 0 0 auto;
437
+ margin-inline-start: auto;
438
+ }
439
+
440
+ [data-actions-leading]:empty { display: none; }
441
+
143
442
  /* ═══════ Page-size select ═══════ (gh#1615)
144
443
  Carries the SAME margin-inline-start:auto as [data-search] so the
145
444
  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
  *
@@ -17,8 +17,15 @@ export type TableToolbarFilterChangeEvent = CustomEvent<unknown>;
17
17
  export type TableToolbarPageSizeChangeEvent = CustomEvent<unknown>;
18
18
  export type TableToolbarSearchEvent = CustomEvent<unknown>;
19
19
  export type TableToolbarSortChangeEvent = CustomEvent<unknown>;
20
+ export type TableToolbarToolbarColumnsSetEvent = CustomEvent<unknown>;
21
+ export type TableToolbarToolbarFilterClearEvent = CustomEvent<unknown>;
22
+ export type TableToolbarToolbarFilterSetEvent = CustomEvent<unknown>;
23
+ export type TableToolbarToolbarPaginateEvent = CustomEvent<unknown>;
24
+ export type TableToolbarToolbarSearchEvent = CustomEvent<unknown>;
20
25
 
21
26
  export class UITableToolbar extends UIElement {
27
+ /** 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. */
28
+ chromeOnly: boolean;
22
29
  /** Optional count badge value shown next to the title. When unset, falls back to the row count of the bound table. */
23
30
  count: string;
24
31
  /** id-ref of the table-ui to control. Falls back to the first sibling table-ui within the same parent when omitted. */
@@ -43,6 +50,8 @@ export class UITableToolbar extends UIElement {
43
50
  rangeStart: number;
44
51
  /** 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
52
  rangeTotal: number;
53
+ /** 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). */
54
+ stage: '' | 'full' | 'search-tight' | 'icon-only' | 'overflow';
46
55
  /** Title text shown on the left. */
47
56
  text: string;
48
57
  /** Toolbar visual variant. `default` renders bare on parent surface; `card` adds the same chrome as a card-ui header. */
@@ -53,6 +62,11 @@ export class UITableToolbar extends UIElement {
53
62
  addEventListener(type: 'page-size-change', listener: (ev: TableToolbarPageSizeChangeEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
54
63
  addEventListener(type: 'search', listener: (ev: TableToolbarSearchEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
55
64
  addEventListener(type: 'sort-change', listener: (ev: TableToolbarSortChangeEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
65
+ addEventListener(type: 'toolbar-columns-set', listener: (ev: TableToolbarToolbarColumnsSetEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
66
+ addEventListener(type: 'toolbar-filter-clear', listener: (ev: TableToolbarToolbarFilterClearEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
67
+ addEventListener(type: 'toolbar-filter-set', listener: (ev: TableToolbarToolbarFilterSetEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
68
+ addEventListener(type: 'toolbar-paginate', listener: (ev: TableToolbarToolbarPaginateEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
69
+ addEventListener(type: 'toolbar-search', listener: (ev: TableToolbarToolbarSearchEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
56
70
  addEventListener<K extends keyof HTMLElementEventMap>(
57
71
  type: K,
58
72
  listener: (this: UITableToolbar, ev: HTMLElementEventMap[K]) => unknown,
@@ -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
@@ -150,11 +199,63 @@ events:
150
199
  description: "Column visibility changed. Detail: { hiddenColumns }."
151
200
  page-size-change:
152
201
  description: "Page-size select changed. Detail: { pageSize }."
202
+ toolbar-search:
203
+ description: >-
204
+ gh#1764/#1780, ADR-0079 (events-only interaction contract) — dispatched
205
+ directly at the resolved [for] target (not bubbled from this element)
206
+ in place of the pre-#1780 direct `.search =` write. table-ui listens
207
+ for this on itself. Detail: { value }.
208
+ toolbar-filter-set:
209
+ description: >-
210
+ gh#1764/#1780, ADR-0079 — dispatched directly at the resolved [for]
211
+ target in place of the pre-#1780 direct `target.setFilter()` call.
212
+ table-ui listens for this on itself. Detail: { key, value, op }; a
213
+ null `value` clears that one column's filter.
214
+ toolbar-filter-clear:
215
+ description: >-
216
+ gh#1764/#1780, ADR-0079 — dispatched directly at the resolved [for]
217
+ target in place of the pre-#1780 direct `target.clearFilters()` call.
218
+ table-ui listens for this on itself. No detail.
219
+ toolbar-columns-set:
220
+ description: >-
221
+ gh#1764/#1780, ADR-0079 — dispatched directly at the resolved [for]
222
+ target in place of the pre-#1780 direct `target.columns =` write.
223
+ table-ui listens for this on itself. Detail: { columns }.
224
+ toolbar-paginate:
225
+ description: >-
226
+ gh#1764/#1780, ADR-0079 — dispatched directly at the resolved [for]
227
+ target in place of the pre-#1780 direct `target.paginate =` write.
228
+ table-ui listens for this on itself. Detail: { pageSize }.
153
229
  slots:
154
230
  scope:
155
231
  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.
232
+ empty:
233
+ description: >-
234
+ Renders in place of the range-summary text — the same [data-summary]
235
+ region — when [range-total] is EXPLICITLY present and equal to "0"
236
+ (checked via hasAttribute, never the computed value alone, so an
237
+ unset range-total during loading never flashes this content;
238
+ ADR-0076, REQ-E-001). Mutually exclusive with a positive
239
+ [range-total]: the normal "Showing X-Y of N" text always wins
240
+ whenever range-total is a positive number. Empty (the default)
241
+ preserves today's exact behavior — [data-summary] hides on a
242
+ confirmed range-total="0" exactly as before this slot existed.
243
+ Plain author-supplied markup (text, an empty-state-ui composite, an
244
+ icon+text pair) — table-toolbar does not template or constrain its
245
+ shape. Naming matches chart-ui's own existing `empty` slot
246
+ convention.
247
+ actions-leading:
248
+ description: >-
249
+ Region rendered AFTER the search input and BEFORE the native
250
+ page-size select (gh#1649) — for app-owned trigger buttons (e.g.
251
+ custom Filter / Columns triggers that open the consumer's own
252
+ drawer instead of the native filter/columns popovers) that must
253
+ land ahead of the page-size select rather than after it, unlike
254
+ [slot="actions"]. Same real-insertion-point pattern as [slot="scope"]
255
+ / [slot="actions"] — positioning is CSS by DOM order, not a template
256
+ stamp. Empty (the default) has zero footprint on existing consumers.
156
257
  actions:
157
- description: Trailing action area — primary buttons (e.g. "New row") rendered after the search input.
258
+ description: Trailing action area — primary buttons (e.g. "New row") rendered after the search input, [slot="actions-leading"], and the page-size select.
158
259
  states:
159
260
  - name: idle
160
261
  description: Default, ready for interaction.
@@ -186,6 +287,7 @@ requiredIcons:
186
287
  - arrow-up
187
288
  - arrow-down
188
289
  - caret-up-down
290
+ - dots-three-vertical
189
291
  a2ui:
190
292
  rules:
191
293
  - >-
@@ -223,6 +325,44 @@ a2ui:
223
325
  + [page-size-options] for a rows-per-page select; it applies
224
326
  directly to the bound table's [paginate] prop the same way
225
327
  filter/sort/columns changes apply directly to the target.
328
+ - >-
329
+ Use [slot="actions-leading"] (gh#1649) for app-owned trigger
330
+ buttons that must land BEFORE the page-size select — e.g. custom
331
+ Filter / Columns triggers that open the consumer's own drawer
332
+ instead of the native filter/columns popovers (drop [no-filter]
333
+ [no-columns] to hide the native ones when doing this). [slot="actions"]
334
+ stays the trailing region AFTER the page-size select, for a
335
+ primary CTA.
336
+ - >-
337
+ Use [chrome-only] (ADR-0076, ADIA2-9123) instead of stacking
338
+ [no-filter] [no-sort] [no-columns] [no-search] individually when an
339
+ app owns ALL FOUR affordances itself (its own drawer/menu). Pure OR,
340
+ absolute while set — clearing an individual no-* attribute does NOT
341
+ re-enable that one control; drop [chrome-only] entirely and set the
342
+ three you still want off explicitly instead. Combine with
343
+ [slot="actions-leading"] for the app's own Filter/Columns triggers.
344
+ - >-
345
+ Use <span slot="empty"> (or any markup) inside a table-toolbar-ui
346
+ that also sets [range-total="0"] to show a "no results" message in
347
+ the range-summary's own position, without a layout shift versus the
348
+ normal "Showing X-Y of N" text (ADR-0076, REQ-E). Only fires on an
349
+ EXPLICIT range-total="0" — omitting range-* entirely (loading) never
350
+ shows it.
351
+ - >-
352
+ A plain <button-ui> slotted into [slot="actions-leading"] (e.g. the
353
+ app-owned Filter/Columns triggers above) already collapses to
354
+ icon-only at the SAME breakpoint as the native Filter/Sort/Columns
355
+ buttons (gh#1748) — no consumer JS/CSS required. Read the host's
356
+ [data-stage-resolved] attribute (ADR-0076 REQ-M-005, ADIA2-9123
357
+ S4-ii) only for compaction BEYOND plain icon-only reduction — e.g.
358
+ hiding a slotted button entirely at the overflow stage, or reacting
359
+ to the stage in a consumer's own drawer/menu — without re-deriving
360
+ the same breakpoints yourself. Informational only — mirrors the
361
+ CSS-decided compaction stage (full | search-tight | icon-only |
362
+ overflow), never drives table-toolbar's own rendering. Reports the
363
+ pinned [stage] value when set, the live width classification
364
+ otherwise; updates on both a live resize and a [stage] attribute
365
+ change.
226
366
  anti_patterns: []
227
367
  examples:
228
368
  - name: members-toolbar
@@ -237,13 +377,20 @@ examples:
237
377
  ]
238
378
  - name: adiav2-list-page-toolbar-row
239
379
  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.
380
+ The ratified adiav2 list-page toolbar row (ADIA2-9123, gh#1649): scope
381
+ menu, range summary, app-owned Filter/Columns triggers (their own
382
+ filter drawer, not the native popovers [no-filter]/[no-columns]/
383
+ [no-sort]/[no-search] drop the native affordances), page-size select,
384
+ and a primary CTA — [slot="actions-leading"] carries the Filter/Columns
385
+ triggers so they land BEFORE the page-size select, while [slot="actions"]
386
+ carries the CTA so it lands after.
243
387
  a2ui: >-
244
388
  [
245
389
  {"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]},
390
+ {"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"]},
391
+ {"id": "btn-filter", "component": "Button", "text": "Filter", "variant": "ghost", "slot": "actions-leading"},
392
+ {"id": "btn-columns", "component": "Button", "text": "Columns", "variant": "ghost", "slot": "actions-leading"},
393
+ {"id": "btn-new", "component": "Button", "text": "New account", "variant": "primary", "slot": "actions"},
247
394
  {"id": "card", "component": "Card", "children": ["sec"]},
248
395
  {"id": "sec", "component": "Section", "bleed": true, "children": ["tbl"]},
249
396
  {"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');