@adia-ai/web-components 0.8.42 → 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 (186) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/MIGRATION.md +130 -80
  3. package/USAGE.md +1 -1
  4. package/color/index.js +1 -1
  5. package/components/action-list/action-item.a2ui.json +3 -8
  6. package/components/action-list/action-item.yaml +10 -16
  7. package/components/action-list/action-list.class.js +5 -61
  8. package/components/action-list/action-list.d.ts +1 -3
  9. package/components/avatar/avatar.class.js +12 -1
  10. package/components/button/button.d.ts +4 -0
  11. package/components/button/button.yaml +1 -1
  12. package/components/calendar-picker/calendar-picker.a2ui.json +1 -1
  13. package/components/calendar-picker/calendar-picker.class.js +2 -3
  14. package/components/calendar-picker/calendar-picker.css +16 -18
  15. package/components/calendar-picker/calendar-picker.yaml +2 -2
  16. package/components/card/card.css +3 -3
  17. package/components/chart/chart.a2ui.json +29 -18
  18. package/components/chart/chart.class.js +1308 -266
  19. package/components/chart/chart.css +151 -1
  20. package/components/chart/chart.d.ts +9 -9
  21. package/components/chart/chart.yaml +63 -31
  22. package/components/chart-legend/chart-legend.a2ui.json +20 -14
  23. package/components/chart-legend/chart-legend.class.js +79 -58
  24. package/components/chart-legend/chart-legend.css +65 -1
  25. package/components/chart-legend/chart-legend.d.ts +5 -5
  26. package/components/chart-legend/chart-legend.examples.md +4 -4
  27. package/components/chart-legend/chart-legend.yaml +50 -20
  28. package/components/check/check.css +9 -0
  29. package/components/color-area/color-area.a2ui.json +1 -1
  30. package/components/color-area/color-area.class.js +2 -2
  31. package/components/color-area/color-area.css +4 -7
  32. package/components/color-area/color-area.d.ts +1 -1
  33. package/components/color-area/color-area.yaml +3 -3
  34. package/components/color-input/color-input.a2ui.json +1 -1
  35. package/components/color-input/color-input.class.js +1 -1
  36. package/components/color-input/color-input.yaml +2 -2
  37. package/components/combobox/combobox.a2ui.json +3 -0
  38. package/components/combobox/combobox.class.js +9 -8
  39. package/components/combobox/combobox.css +25 -4
  40. package/components/combobox/combobox.yaml +6 -0
  41. package/components/command/command.css +17 -1
  42. package/components/context-menu/context-menu.d.ts +2 -0
  43. package/components/drilldown/drilldown.a2ui.json +3 -0
  44. package/components/drilldown/drilldown.css +8 -1
  45. package/components/drilldown/drilldown.yaml +2 -0
  46. package/components/field/field.class.js +5 -5
  47. package/components/heatmap/heatmap.a2ui.json +5 -0
  48. package/components/heatmap/heatmap.d.ts +2 -0
  49. package/components/heatmap/heatmap.yaml +8 -0
  50. package/components/index.js +0 -4
  51. package/components/input/input.a2ui.json +4 -4
  52. package/components/input/input.class.js +20 -7
  53. package/components/input/input.css +6 -0
  54. package/components/input/input.yaml +15 -11
  55. package/components/integration-card/integration-card.d.ts +2 -0
  56. package/components/menu/menu-item.a2ui.json +3 -8
  57. package/components/menu/menu-item.yaml +13 -21
  58. package/components/menu/menu.class.js +5 -27
  59. package/components/menu/menu.d.ts +1 -3
  60. package/components/noodles/noodles.a2ui.json +0 -15
  61. package/components/noodles/noodles.yaml +0 -12
  62. package/components/number-format/number-format.class.js +10 -0
  63. package/components/pagination/pagination.class.js +88 -4
  64. package/components/pane/pane.a2ui.json +2 -12
  65. package/components/pane/pane.class.js +5 -30
  66. package/components/pane/pane.d.ts +3 -5
  67. package/components/pane/pane.yaml +7 -19
  68. package/components/popover/popover.d.ts +2 -0
  69. package/components/progress/progress.a2ui.json +1 -1
  70. package/components/progress/progress.class.js +13 -8
  71. package/components/progress/progress.d.ts +1 -1
  72. package/components/progress/progress.yaml +2 -2
  73. package/components/radio/radio.css +7 -0
  74. package/components/range/range.class.js +9 -1
  75. package/components/search/search.css +10 -1
  76. package/components/segment/segment.a2ui.json +1 -2
  77. package/components/segment/segment.css +10 -0
  78. package/components/segment/segment.yaml +1 -2
  79. package/components/segmented/segmented.a2ui.json +1 -3
  80. package/components/segmented/segmented.class.js +2 -2
  81. package/components/segmented/segmented.yaml +2 -4
  82. package/components/select/select.a2ui.json +5 -1
  83. package/components/select/select.class.js +14 -14
  84. package/components/select/select.css +20 -1
  85. package/components/select/select.yaml +7 -1
  86. package/components/step-progress/step-progress.yaml +1 -1
  87. package/components/stream/stream.d.ts +2 -0
  88. package/components/switch/switch.css +8 -0
  89. package/components/table/table.a2ui.json +6 -1
  90. package/components/table/table.class.js +18 -1
  91. package/components/table/table.css +31 -6
  92. package/components/table/table.d.ts +3 -1
  93. package/components/table/table.examples.md +2 -2
  94. package/components/table/table.yaml +9 -1
  95. package/components/table-toolbar/table-toolbar.a2ui.json +67 -2
  96. package/components/table-toolbar/table-toolbar.class.js +492 -17
  97. package/components/table-toolbar/table-toolbar.css +311 -0
  98. package/components/table-toolbar/table-toolbar.d.ts +17 -1
  99. package/components/table-toolbar/table-toolbar.yaml +220 -15
  100. package/components/tag/tag.class.js +13 -1
  101. package/components/text/text.a2ui.json +3 -3
  102. package/components/text/text.d.ts +5 -3
  103. package/components/text/text.yaml +3 -3
  104. package/components/textarea/textarea.class.js +9 -1
  105. package/components/textarea/textarea.css +4 -0
  106. package/components/time-picker/time-picker.yaml +3 -2
  107. package/components/tour/tour.d.ts +10 -0
  108. package/components/tree/tree.yaml +1 -1
  109. package/components/upload/upload.class.js +16 -0
  110. package/components/upload/upload.d.ts +7 -0
  111. package/components/upload/upload.yaml +7 -3
  112. package/core/a11y.d.ts +22 -0
  113. package/core/a11y.js +64 -0
  114. package/core/anchor.js +9 -5
  115. package/core/element.js +1 -1
  116. package/core/icons.js +2 -5
  117. package/custom-elements.json +142 -594
  118. package/dist/host.min.css +1 -1
  119. package/dist/host.sheet.js +1 -1
  120. package/dist/theme-provider.min.js +1 -1
  121. package/dist/web-components.min.css +1 -1
  122. package/dist/web-components.min.js +122 -119
  123. package/dist/web-components.sheet.js +1 -1
  124. package/index.d.ts +0 -15
  125. package/package.json +1 -1
  126. package/patterns/access-requests/access-requests.examples.html +1 -1
  127. package/patterns/admin-keys-and-export/admin-keys-and-export.examples.html +7 -7
  128. package/patterns/agent-cost/agent-cost.examples.html +6 -6
  129. package/patterns/agent-memory/agent-memory.examples.html +3 -3
  130. package/patterns/agent-prompt-library/agent-prompt-library.examples.html +8 -8
  131. package/patterns/agent-tool-call/agent-tool-call.examples.html +1 -1
  132. package/patterns/approvals/approvals.examples.html +6 -6
  133. package/patterns/audit-log/audit-log.examples.html +1 -1
  134. package/patterns/bulk-action-toolbar/bulk-action-toolbar.examples.html +4 -4
  135. package/patterns/chart-in-card/chart-in-card.examples.html +124 -10
  136. package/patterns/chart-in-card/chart-in-card.examples.js +37 -0
  137. package/patterns/comments-and-collaboration/comments-and-collaboration.examples.html +5 -5
  138. package/patterns/conversion-funnel/conversion-funnel.html +2 -2
  139. package/patterns/data-tables-inline-edit-and-tree/data-tables-inline-edit-and-tree.examples.html +2 -2
  140. package/patterns/diff-review/diff-review.examples.html +6 -6
  141. package/patterns/editor-shell/editor-shell.examples.html +3 -3
  142. package/patterns/export-flow/export-flow.examples.html +2 -2
  143. package/patterns/filter-bar/filter-bar.examples.html +2 -2
  144. package/patterns/forms-conditional-and-autocomplete/forms-conditional-and-autocomplete.examples.html +2 -2
  145. package/patterns/inline-dialog/inline-dialog.examples.html +2 -2
  146. package/patterns/marketing-engagement/marketing-engagement.examples.html +4 -4
  147. package/patterns/notifications-bell-and-digest/notifications-bell-and-digest.examples.html +1 -1
  148. package/patterns/permissions-matrix/permissions-matrix.examples.html +1 -1
  149. package/patterns/permissions-sharing/permissions-sharing.examples.html +2 -2
  150. package/patterns/profile-public-and-verification/profile-public-and-verification.examples.html +4 -4
  151. package/patterns/search-discovery/search-discovery.examples.html +2 -2
  152. package/styles/colors/semantics/features.css +8 -1
  153. package/styles/components.css +0 -4
  154. package/styles/type/roles.css +29 -9
  155. package/traits/arrow-grid-nav/arrow-grid-nav.js +1 -1
  156. package/traits/resize-observer/resize-observer.js +1 -1
  157. package/components/color-picker/color-picker.a2ui.json +0 -148
  158. package/components/color-picker/color-picker.class.js +0 -46
  159. package/components/color-picker/color-picker.css +0 -14
  160. package/components/color-picker/color-picker.d.ts +0 -99
  161. package/components/color-picker/color-picker.examples.md +0 -19
  162. package/components/color-picker/color-picker.js +0 -17
  163. package/components/color-picker/color-picker.yaml +0 -175
  164. package/components/datetime-picker/datetime-picker.a2ui.json +0 -256
  165. package/components/datetime-picker/datetime-picker.class.js +0 -58
  166. package/components/datetime-picker/datetime-picker.css +0 -11
  167. package/components/datetime-picker/datetime-picker.d.ts +0 -87
  168. package/components/datetime-picker/datetime-picker.examples.md +0 -25
  169. package/components/datetime-picker/datetime-picker.js +0 -17
  170. package/components/datetime-picker/datetime-picker.yaml +0 -254
  171. package/components/progress-row/progress-row.a2ui.json +0 -107
  172. package/components/progress-row/progress-row.class.js +0 -118
  173. package/components/progress-row/progress-row.css +0 -60
  174. package/components/progress-row/progress-row.d.ts +0 -24
  175. package/components/progress-row/progress-row.examples.md +0 -7
  176. package/components/progress-row/progress-row.js +0 -17
  177. package/components/progress-row/progress-row.yaml +0 -85
  178. package/components/toggle-group/toggle-group.a2ui.json +0 -148
  179. package/components/toggle-group/toggle-group.class.js +0 -195
  180. package/components/toggle-group/toggle-group.css +0 -127
  181. package/components/toggle-group/toggle-group.d.ts +0 -50
  182. package/components/toggle-group/toggle-group.examples.md +0 -33
  183. package/components/toggle-group/toggle-group.js +0 -18
  184. package/components/toggle-group/toggle-group.yaml +0 -135
  185. package/components/toggle-group/toggle-option.a2ui.json +0 -86
  186. package/components/toggle-group/toggle-option.yaml +0 -75
@@ -6,16 +6,20 @@ component: TableToolbar
6
6
  category: agent
7
7
  version: 1
8
8
  description: >-
9
- Header / companion bar for a sibling table-ui. Renders title + count badge,
10
- filter / sort / columns popovers, and a search input all wired to the
11
- target table via an [for] id-ref. Modeled on chart-legend-ui's [for] binding
12
- pattern. Drop next to (or above) any table-ui to add the standard data-grid
13
- toolbar without re-implementing search, filter, sort, or column visibility.
14
- Filter rows auto-pick a primitive per column: 50 distinct values → multi-
15
- select (searchable when 12 options), id-like keys free-text contains. The
16
- column descriptor's `filter` field overrides the auto-detect: `'select'`
17
- forces multi-select even on high-cardinality columns; `'text'` forces a
18
- contains input even on small enums.
9
+ Header / companion bar for a sibling table-ui. Renders an optional leading
10
+ [slot="scope"] region, title + count badge, an optional "Showing X–Y of N"
11
+ range summary, filter / sort / columns popovers, a search input, an
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 values → multi-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.
19
23
  # Per ADR-0027 — primitives that programmatically create other primitives
20
24
  # do NOT auto-import them. Consumer (or demo shell) must explicitly import.
21
25
  composes:
@@ -26,6 +30,7 @@ composes:
26
30
  - field-ui
27
31
  - select-ui
28
32
  - input-ui
33
+ - menu-ui
29
34
  - menu-item-ui
30
35
  - icon-ui
31
36
  - check-ui
@@ -43,6 +48,63 @@ props:
43
48
  description: Optional count badge value shown next to the title. When unset, falls back to the row count of the bound table.
44
49
  type: string
45
50
  default: ""
51
+ rangeStart:
52
+ description: >-
53
+ 1-indexed first row number of the current page, for the "Showing X–Y
54
+ of N" range summary (e.g. `1` in "Showing 1–25 of 320"). Renders
55
+ alongside — never in place of — [text]/[count]; the range summary is
56
+ its own cluster, independent of the title cluster. All three of
57
+ rangeStart/rangeEnd/rangeTotal must be set (> 0) for the summary to
58
+ render; omit all three (the default) to opt out entirely.
59
+ type: number
60
+ default: 0
61
+ reflect: true
62
+ attribute: range-start
63
+ rangeEnd:
64
+ description: >-
65
+ 1-indexed last row number of the current page, for the range summary
66
+ (e.g. `25` in "Showing 1–25 of 320"). See rangeStart.
67
+ type: number
68
+ default: 0
69
+ reflect: true
70
+ attribute: range-end
71
+ rangeTotal:
72
+ description: >-
73
+ Total row count across all pages, for the range summary (e.g. `320`
74
+ in "Showing 1–25 of 320"). Distinct from pagination-ui's `total`
75
+ (total PAGES) — this is total ROWS; deliberately not spelled `total`
76
+ to avoid that cross-sibling name collision (ADR-0063 B5). See
77
+ rangeStart.
78
+ type: number
79
+ default: 0
80
+ reflect: true
81
+ attribute: range-total
82
+ pageSize:
83
+ description: >-
84
+ Current rows-per-page value, shown as the selected option in the
85
+ page-size select. Falls back to the first entry of pageSizeOptions
86
+ when unset (0) or not present among the options. Applied directly to
87
+ the bound table's own [paginate] prop (rows-per-page), the same way
88
+ filter/sort/columns changes are applied to the target — mirror this
89
+ prop only to reflect state to URL / persistence / analytics.
90
+ type: number
91
+ default: 0
92
+ reflect: true
93
+ attribute: page-size
94
+ pageSizeOptions:
95
+ description: >-
96
+ Rows-per-page choices rendered as `<option>`s in the page-size select
97
+ (e.g. `[10, 25, 50, 100]`). JS property (`.pageSizeOptions = [...]`),
98
+ or a declarative JSON-array `page-size-options="[10,25,50]"` attribute
99
+ hydrated once at connect — the same dynamic-array pattern table-ui's
100
+ own `columns`/`data` props use. Empty (the default) hides the
101
+ page-size select entirely — additive opt-in, no footprint on existing
102
+ consumers.
103
+ type: array
104
+ items:
105
+ type: number
106
+ default: []
107
+ dynamic: true # JS-set collection prop with a custom setter — deliberately not in static properties
46
108
  noFilter:
47
109
  description: Hide the Filter popover button. Filter is shown by default; set to opt out.
48
110
  type: boolean
@@ -67,6 +129,53 @@ props:
67
129
  default: false
68
130
  reflect: true
69
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
70
179
  placeholder:
71
180
  description: Placeholder text for the search input.
72
181
  type: string
@@ -88,9 +197,38 @@ events:
88
197
  description: "Sort state changed. Detail: { sortState }."
89
198
  columns-change:
90
199
  description: "Column visibility changed. Detail: { hiddenColumns }."
200
+ page-size-change:
201
+ description: "Page-size select changed. Detail: { pageSize }."
91
202
  slots:
203
+ scope:
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.
92
230
  actions:
93
- 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.
94
232
  states:
95
233
  - name: idle
96
234
  description: Default, ready for interaction.
@@ -114,10 +252,15 @@ tokens:
114
252
  description: Title font size
115
253
  --table-toolbar-title-weight:
116
254
  description: Title font weight
255
+ --table-toolbar-summary-fg:
256
+ description: Range-summary ("Showing X–Y of N") text color
257
+ --table-toolbar-summary-size:
258
+ description: Range-summary font size
117
259
  requiredIcons:
118
260
  - arrow-up
119
261
  - arrow-down
120
262
  - caret-up-down
263
+ - dots-three-vertical
121
264
  a2ui:
122
265
  rules:
123
266
  - >-
@@ -142,10 +285,52 @@ a2ui:
142
285
  when content is markup (a <span> + <badge-ui>, etc.).
143
286
  - >-
144
287
  Listen for toolbar events (`search`, `filter-change`,
145
- `sort-change`, `columns-change`) only to mirror state to URL /
146
- persistence / analytics. The toolbar already wires its changes
147
- into the bound table — you don't need to manually update the
148
- table.
288
+ `sort-change`, `columns-change`, `page-size-change`) only to mirror
289
+ state to URL / persistence / analytics. The toolbar already wires
290
+ its changes into the bound table — you don't need to manually
291
+ update the table.
292
+ - >-
293
+ Use [slot="scope"] for a leading scope/view switcher (an org's
294
+ teams, a saved view) rendered before [text]/[count]. Use
295
+ [range-start] / [range-end] / [range-total] together for a
296
+ "Showing X–Y of N" summary — it renders independently of, and
297
+ alongside, [text]/[count], never in place of them. Use [page-size]
298
+ + [page-size-options] for a rows-per-page select; it applies
299
+ directly to the bound table's [paginate] prop the same way
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.
149
334
  anti_patterns: []
150
335
  examples:
151
336
  - name: members-toolbar
@@ -158,6 +343,26 @@ examples:
158
343
  {"id": "sec", "component": "Section", "bleed": true, "children": ["tbl"]},
159
344
  {"id": "tbl", "component": "Table", "id": "members", "sortable": true, "raw": true}
160
345
  ]
346
+ - name: adiav2-list-page-toolbar-row
347
+ description: >-
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.
355
+ a2ui: >-
356
+ [
357
+ {"id": "root", "component": "Column", "gap": "3", "children": ["bar", "card"]},
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"},
362
+ {"id": "card", "component": "Card", "children": ["sec"]},
363
+ {"id": "sec", "component": "Section", "bleed": true, "children": ["tbl"]},
364
+ {"id": "tbl", "component": "Table", "id": "accounts", "sortable": true, "raw": true}
365
+ ]
161
366
  keywords:
162
367
  - table-toolbar
163
368
  - data-grid
@@ -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.
@@ -18,6 +18,7 @@
18
18
  */
19
19
 
20
20
  import { UIFormElement } from '../../core/form.js';
21
+ import { syncAutoAriaLabel } from '../../core/a11y.js';
21
22
 
22
23
  export class UITextarea extends UIFormElement {
23
24
  static labelDeprecated = false; // §170 (v0.5.4): label is first-class per textarea.yaml
@@ -32,6 +33,10 @@ export class UITextarea extends UIFormElement {
32
33
  static template = () => null;
33
34
 
34
35
  #textEl = null;
36
+ // gh#1647: tracks the aria-label VALUE this element itself last derived
37
+ // from `label`/`placeholder` (undefined when none) — value-tracking
38
+ // guard ported from tag.class.js/select.class.js (gh#1644/#1646).
39
+ #lastAutoAriaLabel = undefined;
35
40
 
36
41
  connected() {
37
42
  super.connected();
@@ -82,7 +87,10 @@ export class UITextarea extends UIFormElement {
82
87
  const label = this.querySelector('[slot="label"]');
83
88
  if (label && this.label) label.setAttribute('label', this.label);
84
89
 
85
- this.setAttribute('aria-label', this.label || this.placeholder || '');
90
+ // A11y (gh#1647): never clobber a consumer's own aria-label
91
+ // (value-tracking guard, gh#1644/#1646).
92
+ const ariaSource = this.label || this.placeholder || '';
93
+ this.#lastAutoAriaLabel = syncAutoAriaLabel(this, this.#lastAutoAriaLabel, ariaSource);
86
94
  }
87
95
 
88
96
  #onInput = () => {
@@ -37,6 +37,9 @@ textarea-ui:not([disabled]) [slot="text"]:hover {
37
37
  --textarea-label-fg-focus: var(--md-sys-color-neutral-on-surface-variant);
38
38
  --textarea-bg-disabled: var(--a-ui-bg-disabled);
39
39
  --textarea-fg-disabled: var(--a-ui-text-disabled);
40
+ /* Same gap as gh#1595 (input-ui) — rides along here since it's the
41
+ identical shared-field pattern, same root-cause class. */
42
+ --textarea-border-disabled: var(--a-ui-border-disabled);
40
43
  text-align: start; /* §text-align-reset — blocks inheritance from centered ancestors */
41
44
  }
42
45
 
@@ -124,6 +127,7 @@ textarea-ui:not([disabled]) [slot="text"]:hover {
124
127
  :scope[disabled] [slot="text"] {
125
128
  background: var(--textarea-bg-disabled);
126
129
  color: var(--textarea-fg-disabled);
130
+ border-color: var(--textarea-border-disabled);
127
131
  cursor: not-allowed;
128
132
  }
129
133
  }
@@ -175,8 +175,9 @@ a2ui:
175
175
  (`h12` / `h23`) when the surface needs a specific cycle (cron
176
176
  editors, log queries, system surfaces).
177
177
  - >-
178
- For datetime selection use `<datetime-picker-ui>` (SPEC-038) —
179
- it composes this primitive as its time pane.
178
+ For datetime selection use `<calendar-picker-ui precision="minute">`
179
+ (SPEC-038; the former `<datetime-picker-ui>` thin alias was cut in
180
+ 0.8.43, gh#1617) — it composes this primitive as its time pane.
180
181
  anti_patterns:
181
182
  - wrong: |
182
183
  {"component": "TimePicker", "value": "9:30 AM"}
@@ -43,10 +43,20 @@ export class UITour extends UIElement {
43
43
  the spotlight + popover; setting to `false` tears down.
44
44
  */
45
45
  active: boolean;
46
+ /** Start the tour automatically when the element connects. Useful for
47
+ first-run flows gated by a storage flag on the consumer side.
48
+ */
49
+ autoStart: boolean;
46
50
  /** Current step index (0-based). Setting this property advances the
47
51
  tour to that step (updating spotlight + popover position).
48
52
  */
49
53
  step: number;
54
+ /** Optional localStorage key. When set, the tour records its
55
+ completion state (`"done"`) to that key on finish/skip — and
56
+ refuses to auto-start on subsequent loads if the key is "done".
57
+ Useful for "show this tour once per user" flows.
58
+ */
59
+ storageKey: string;
50
60
 
51
61
  addEventListener(type: 'tour-finish', listener: (ev: TourFinishEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
52
62
  addEventListener(type: 'tour-skip', listener: (ev: TourSkipEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
@@ -108,7 +108,7 @@ a2ui:
108
108
  for one-level collapsible groups use <accordion-ui>.
109
109
  - >-
110
110
  Canonical mount: inside <editor-sidebar slot="leading"> →
111
- <pane-ui side="leading" resizable> → <section> → <tree-ui id="…">
111
+ <pane-ui edge="leading" resizable> → <section> → <tree-ui id="…">
112
112
  as the structure / navigator pane of the three-pane editor shell.
113
113
  Pair with a <header> in the same pane (e.g. "Structure",
114
114
  "Layers", "Files").
@@ -93,6 +93,22 @@ export class UIUpload extends UIFormElement {
93
93
  return this.#files;
94
94
  }
95
95
 
96
+ /**
97
+ * Empty the selected file list and return the dropzone to idle — the
98
+ * public counterpart to `open()` (gh#1605). A consumer's own Clear/Reset
99
+ * action can call this without reaching into `[data-dropzone]`'s internal
100
+ * anatomy, which the yaml spec forbids.
101
+ */
102
+ clear() {
103
+ this.#setFiles([]);
104
+ }
105
+
106
+ // ── Form lifecycle ──
107
+
108
+ onFormReset() {
109
+ this.clear();
110
+ }
111
+
96
112
  // ── File picking ──
97
113
 
98
114
  /**
@@ -40,6 +40,13 @@ export class UIUpload extends UIFormElement {
40
40
  */
41
41
  open(): Promise<void>;
42
42
 
43
+ /**
44
+ * gh#1605: empty the selected file list and return the dropzone to idle
45
+ * — the public counterpart to `open()`. Also runs automatically on
46
+ * native form reset (the element is form-associated).
47
+ */
48
+ clear(): void;
49
+
43
50
  addEventListener<K extends keyof HTMLElementEventMap>(
44
51
  type: K,
45
52
  listener: (this: UIUpload, ev: HTMLElementEventMap[K]) => unknown,
@@ -67,9 +67,11 @@ slots: {}
67
67
  # (the audit-slot-vocab-vs-css vocabulary is slot-attr based). Consumers
68
68
  # must NOT reach into `[data-dropzone]` to trigger the picker — that is
69
69
  # what the public `.open()` method and the [compact] trigger mode are for
70
- # (gh#1493). Hint + label are declared as props above (text-only); for an
71
- # avatar / icon / form-element wrapper, compose <field-ui label> around
72
- # the upload.
70
+ # (gh#1493). Emptying the selection is the public `.clear()` method
71
+ # (gh#1605) it also fires on native form reset, since the element is
72
+ # form-associated. Hint + label are declared as props above (text-only);
73
+ # for an avatar / icon / form-element wrapper, compose <field-ui label>
74
+ # around the upload.
73
75
  states:
74
76
  - name: idle
75
77
  description: Default, ready for interaction.
@@ -88,6 +90,8 @@ a2ui:
88
90
  reason: 'Standard file-input knobs.'
89
91
  - rule: 'compact renders an icon-only trigger (no drop-zone chrome) — use in toolbars/dense rows; label becomes the trigger''s aria-label. Programmatic triggering goes through the public .open() method, never the internal [data-dropzone] part.'
90
92
  reason: 'Public trigger API (gh#1493); internal anatomy is not a contract.'
93
+ - rule: 'A consumer''s own Clear/Reset action empties the selection through the public .clear() method — it also runs automatically on native form reset.'
94
+ reason: 'Public reset API (gh#1605); form-associated elements react to form reset without a manual wire-up.'
91
95
  - rule: 'For agent chat attachments use <chat-composer-ui>''s built-in upload affordance instead.'
92
96
  reason: 'Chat composer has its own attachment lane.'
93
97
  anti_patterns: []
package/core/a11y.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * core/a11y.js — shared accessible-name authorship guard.
3
+ *
4
+ * See the runtime module's own JSDoc for the full contract and the usage
5
+ * pattern (gh#1644 / gh#1646 / gh#1647).
6
+ */
7
+
8
+ /**
9
+ * Reconcile an auto-derived `aria-label` against a possible consumer
10
+ * override, using a value-tracking guard rather than a plain boolean flag.
11
+ *
12
+ * @param el - the host element.
13
+ * @param lastAuto - the value this component wrote last time (`undefined`
14
+ * = no active auto-derived value, either never set or consumer-owned).
15
+ * @param source - the label this render would derive; falsy = no auto name.
16
+ * @returns the new `lastAuto` to store back in the caller's own field.
17
+ */
18
+ export function syncAutoAriaLabel(
19
+ el: HTMLElement,
20
+ lastAuto: string | undefined,
21
+ source: string,
22
+ ): string | undefined;
package/core/a11y.js ADDED
@@ -0,0 +1,64 @@
1
+ /**
2
+ * core/a11y.js — shared accessible-name authorship guard.
3
+ *
4
+ * Ports the value-tracking guard proven on `tag.class.js`'s
5
+ * `#syncAccessibleName` (gh#1644 / PR #1645) and `select.class.js`'s
6
+ * `labeledElsewhere` variant (gh#1646 / PR #1648) into one shared helper,
7
+ * so every primitive that derives its own `aria-label` doesn't need to
8
+ * reimplement the compare-against-what-we-last-wrote logic (gh#1647).
9
+ *
10
+ * A plain boolean "did we ever set it" flag isn't enough: a consumer can
11
+ * overwrite `aria-label` directly via `setAttribute` at any point BETWEEN
12
+ * renders, bypassing the flag entirely. This guard instead compares the
13
+ * CURRENT attribute value against the value THIS component last wrote —
14
+ * a mismatch means a consumer has taken ownership, and auto-derivation
15
+ * stops until the source value the caller passes in changes again.
16
+ *
17
+ * Per-component gating (e.g. select-ui's `labeledElsewhere` — don't
18
+ * auto-name when a visible `label` or `aria-labelledby` already names the
19
+ * element, or don't auto-name off a default/placeholder-only value) is
20
+ * the CALLER's job: resolve the source string to '' when no auto name
21
+ * should apply this render, and pass the real derived string otherwise.
22
+ * This helper only ever reconciles "what we'd write" against "what's
23
+ * there now, and did we write it" — it carries no per-component policy.
24
+ *
25
+ * Usage (per component):
26
+ *
27
+ * import { syncAutoAriaLabel } from '../../core/a11y.js';
28
+ *
29
+ * #lastAutoAriaLabel = undefined;
30
+ *
31
+ * #syncAccessibleName() {
32
+ * const source = <derive the label this render, or '' for none>;
33
+ * this.#lastAutoAriaLabel = syncAutoAriaLabel(this, this.#lastAutoAriaLabel, source);
34
+ * }
35
+ *
36
+ * Call `#syncAccessibleName()` early in `render()`, before any other
37
+ * aria-* bookkeeping that might read the (possibly just-changed)
38
+ * `aria-label` attribute.
39
+ *
40
+ * @param {HTMLElement} el
41
+ * @param {string | undefined} lastAuto - the value this component wrote
42
+ * last time (`undefined` = the component has never derived a name for
43
+ * the CURRENT attribute value, either because it never has, or because
44
+ * a consumer has since taken ownership).
45
+ * @param {string} source - the label the component would derive right
46
+ * now. Empty string (or any other falsy value) means "no auto name" —
47
+ * any auto-set label is removed.
48
+ * @returns {string | undefined} the new `lastAuto` value — store this
49
+ * back into the caller's own private field.
50
+ */
51
+ export function syncAutoAriaLabel(el, lastAuto, source) {
52
+ const current = el.getAttribute('aria-label');
53
+ if (lastAuto !== undefined && current !== lastAuto) {
54
+ lastAuto = undefined; // a consumer took ownership between renders
55
+ }
56
+ const authored = current !== null && lastAuto === undefined;
57
+ if (authored) return lastAuto;
58
+ if (source) {
59
+ if (current !== source) el.setAttribute('aria-label', source);
60
+ return source;
61
+ }
62
+ if (current !== null) el.removeAttribute('aria-label');
63
+ return undefined;
64
+ }