@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.
- package/CHANGELOG.md +44 -0
- package/MIGRATION.md +130 -80
- package/USAGE.md +1 -1
- package/color/index.js +1 -1
- package/components/action-list/action-item.a2ui.json +3 -8
- package/components/action-list/action-item.yaml +10 -16
- package/components/action-list/action-list.class.js +5 -61
- package/components/action-list/action-list.d.ts +1 -3
- package/components/avatar/avatar.class.js +12 -1
- package/components/button/button.d.ts +4 -0
- package/components/button/button.yaml +1 -1
- package/components/calendar-picker/calendar-picker.a2ui.json +1 -1
- package/components/calendar-picker/calendar-picker.class.js +2 -3
- package/components/calendar-picker/calendar-picker.css +16 -18
- package/components/calendar-picker/calendar-picker.yaml +2 -2
- package/components/card/card.css +3 -3
- package/components/chart/chart.a2ui.json +29 -18
- package/components/chart/chart.class.js +1308 -266
- package/components/chart/chart.css +151 -1
- package/components/chart/chart.d.ts +9 -9
- package/components/chart/chart.yaml +63 -31
- package/components/chart-legend/chart-legend.a2ui.json +20 -14
- package/components/chart-legend/chart-legend.class.js +79 -58
- package/components/chart-legend/chart-legend.css +65 -1
- package/components/chart-legend/chart-legend.d.ts +5 -5
- package/components/chart-legend/chart-legend.examples.md +4 -4
- package/components/chart-legend/chart-legend.yaml +50 -20
- package/components/check/check.css +9 -0
- package/components/color-area/color-area.a2ui.json +1 -1
- package/components/color-area/color-area.class.js +2 -2
- package/components/color-area/color-area.css +4 -7
- package/components/color-area/color-area.d.ts +1 -1
- package/components/color-area/color-area.yaml +3 -3
- package/components/color-input/color-input.a2ui.json +1 -1
- package/components/color-input/color-input.class.js +1 -1
- package/components/color-input/color-input.yaml +2 -2
- package/components/combobox/combobox.a2ui.json +3 -0
- package/components/combobox/combobox.class.js +9 -8
- package/components/combobox/combobox.css +25 -4
- package/components/combobox/combobox.yaml +6 -0
- package/components/command/command.css +17 -1
- package/components/context-menu/context-menu.d.ts +2 -0
- package/components/drilldown/drilldown.a2ui.json +3 -0
- package/components/drilldown/drilldown.css +8 -1
- package/components/drilldown/drilldown.yaml +2 -0
- package/components/field/field.class.js +5 -5
- package/components/heatmap/heatmap.a2ui.json +5 -0
- package/components/heatmap/heatmap.d.ts +2 -0
- package/components/heatmap/heatmap.yaml +8 -0
- package/components/index.js +0 -4
- package/components/input/input.a2ui.json +4 -4
- package/components/input/input.class.js +20 -7
- package/components/input/input.css +6 -0
- package/components/input/input.yaml +15 -11
- package/components/integration-card/integration-card.d.ts +2 -0
- package/components/menu/menu-item.a2ui.json +3 -8
- package/components/menu/menu-item.yaml +13 -21
- package/components/menu/menu.class.js +5 -27
- package/components/menu/menu.d.ts +1 -3
- package/components/noodles/noodles.a2ui.json +0 -15
- package/components/noodles/noodles.yaml +0 -12
- package/components/number-format/number-format.class.js +10 -0
- package/components/pagination/pagination.class.js +88 -4
- package/components/pane/pane.a2ui.json +2 -12
- package/components/pane/pane.class.js +5 -30
- package/components/pane/pane.d.ts +3 -5
- package/components/pane/pane.yaml +7 -19
- package/components/popover/popover.d.ts +2 -0
- package/components/progress/progress.a2ui.json +1 -1
- package/components/progress/progress.class.js +13 -8
- package/components/progress/progress.d.ts +1 -1
- package/components/progress/progress.yaml +2 -2
- package/components/radio/radio.css +7 -0
- package/components/range/range.class.js +9 -1
- package/components/search/search.css +10 -1
- package/components/segment/segment.a2ui.json +1 -2
- package/components/segment/segment.css +10 -0
- package/components/segment/segment.yaml +1 -2
- package/components/segmented/segmented.a2ui.json +1 -3
- package/components/segmented/segmented.class.js +2 -2
- package/components/segmented/segmented.yaml +2 -4
- package/components/select/select.a2ui.json +5 -1
- package/components/select/select.class.js +14 -14
- package/components/select/select.css +20 -1
- package/components/select/select.yaml +7 -1
- package/components/step-progress/step-progress.yaml +1 -1
- package/components/stream/stream.d.ts +2 -0
- package/components/switch/switch.css +8 -0
- package/components/table/table.a2ui.json +6 -1
- package/components/table/table.class.js +18 -1
- package/components/table/table.css +31 -6
- package/components/table/table.d.ts +3 -1
- package/components/table/table.examples.md +2 -2
- package/components/table/table.yaml +9 -1
- package/components/table-toolbar/table-toolbar.a2ui.json +67 -2
- package/components/table-toolbar/table-toolbar.class.js +492 -17
- package/components/table-toolbar/table-toolbar.css +311 -0
- package/components/table-toolbar/table-toolbar.d.ts +17 -1
- package/components/table-toolbar/table-toolbar.yaml +220 -15
- package/components/tag/tag.class.js +13 -1
- package/components/text/text.a2ui.json +3 -3
- package/components/text/text.d.ts +5 -3
- package/components/text/text.yaml +3 -3
- package/components/textarea/textarea.class.js +9 -1
- package/components/textarea/textarea.css +4 -0
- package/components/time-picker/time-picker.yaml +3 -2
- package/components/tour/tour.d.ts +10 -0
- package/components/tree/tree.yaml +1 -1
- package/components/upload/upload.class.js +16 -0
- package/components/upload/upload.d.ts +7 -0
- package/components/upload/upload.yaml +7 -3
- package/core/a11y.d.ts +22 -0
- package/core/a11y.js +64 -0
- package/core/anchor.js +9 -5
- package/core/element.js +1 -1
- package/core/icons.js +2 -5
- package/custom-elements.json +142 -594
- package/dist/host.min.css +1 -1
- package/dist/host.sheet.js +1 -1
- package/dist/theme-provider.min.js +1 -1
- package/dist/web-components.min.css +1 -1
- package/dist/web-components.min.js +122 -119
- package/dist/web-components.sheet.js +1 -1
- package/index.d.ts +0 -15
- package/package.json +1 -1
- package/patterns/access-requests/access-requests.examples.html +1 -1
- package/patterns/admin-keys-and-export/admin-keys-and-export.examples.html +7 -7
- package/patterns/agent-cost/agent-cost.examples.html +6 -6
- package/patterns/agent-memory/agent-memory.examples.html +3 -3
- package/patterns/agent-prompt-library/agent-prompt-library.examples.html +8 -8
- package/patterns/agent-tool-call/agent-tool-call.examples.html +1 -1
- package/patterns/approvals/approvals.examples.html +6 -6
- package/patterns/audit-log/audit-log.examples.html +1 -1
- package/patterns/bulk-action-toolbar/bulk-action-toolbar.examples.html +4 -4
- package/patterns/chart-in-card/chart-in-card.examples.html +124 -10
- package/patterns/chart-in-card/chart-in-card.examples.js +37 -0
- package/patterns/comments-and-collaboration/comments-and-collaboration.examples.html +5 -5
- package/patterns/conversion-funnel/conversion-funnel.html +2 -2
- package/patterns/data-tables-inline-edit-and-tree/data-tables-inline-edit-and-tree.examples.html +2 -2
- package/patterns/diff-review/diff-review.examples.html +6 -6
- package/patterns/editor-shell/editor-shell.examples.html +3 -3
- package/patterns/export-flow/export-flow.examples.html +2 -2
- package/patterns/filter-bar/filter-bar.examples.html +2 -2
- package/patterns/forms-conditional-and-autocomplete/forms-conditional-and-autocomplete.examples.html +2 -2
- package/patterns/inline-dialog/inline-dialog.examples.html +2 -2
- package/patterns/marketing-engagement/marketing-engagement.examples.html +4 -4
- package/patterns/notifications-bell-and-digest/notifications-bell-and-digest.examples.html +1 -1
- package/patterns/permissions-matrix/permissions-matrix.examples.html +1 -1
- package/patterns/permissions-sharing/permissions-sharing.examples.html +2 -2
- package/patterns/profile-public-and-verification/profile-public-and-verification.examples.html +4 -4
- package/patterns/search-discovery/search-discovery.examples.html +2 -2
- package/styles/colors/semantics/features.css +8 -1
- package/styles/components.css +0 -4
- package/styles/type/roles.css +29 -9
- package/traits/arrow-grid-nav/arrow-grid-nav.js +1 -1
- package/traits/resize-observer/resize-observer.js +1 -1
- package/components/color-picker/color-picker.a2ui.json +0 -148
- package/components/color-picker/color-picker.class.js +0 -46
- package/components/color-picker/color-picker.css +0 -14
- package/components/color-picker/color-picker.d.ts +0 -99
- package/components/color-picker/color-picker.examples.md +0 -19
- package/components/color-picker/color-picker.js +0 -17
- package/components/color-picker/color-picker.yaml +0 -175
- package/components/datetime-picker/datetime-picker.a2ui.json +0 -256
- package/components/datetime-picker/datetime-picker.class.js +0 -58
- package/components/datetime-picker/datetime-picker.css +0 -11
- package/components/datetime-picker/datetime-picker.d.ts +0 -87
- package/components/datetime-picker/datetime-picker.examples.md +0 -25
- package/components/datetime-picker/datetime-picker.js +0 -17
- package/components/datetime-picker/datetime-picker.yaml +0 -254
- package/components/progress-row/progress-row.a2ui.json +0 -107
- package/components/progress-row/progress-row.class.js +0 -118
- package/components/progress-row/progress-row.css +0 -60
- package/components/progress-row/progress-row.d.ts +0 -24
- package/components/progress-row/progress-row.examples.md +0 -7
- package/components/progress-row/progress-row.js +0 -17
- package/components/progress-row/progress-row.yaml +0 -85
- package/components/toggle-group/toggle-group.a2ui.json +0 -148
- package/components/toggle-group/toggle-group.class.js +0 -195
- package/components/toggle-group/toggle-group.css +0 -127
- package/components/toggle-group/toggle-group.d.ts +0 -50
- package/components/toggle-group/toggle-group.examples.md +0 -33
- package/components/toggle-group/toggle-group.js +0 -18
- package/components/toggle-group/toggle-group.yaml +0 -135
- package/components/toggle-group/toggle-option.a2ui.json +0 -86
- 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
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
146
|
-
persistence / analytics. The toolbar already wires
|
|
147
|
-
into the bound table — you don't need to manually
|
|
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
|
-
|
|
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
|
|
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).
|
|
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
|
|
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).
|
|
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).
|
|
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
|
|
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
|
-
|
|
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 `<
|
|
179
|
-
|
|
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
|
|
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).
|
|
71
|
-
#
|
|
72
|
-
#
|
|
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
|
+
}
|