@adia-ai/web-components 0.8.43 → 0.8.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/components/avatar/avatar.class.js +12 -1
- package/components/button/button.d.ts +4 -0
- package/components/chart/chart.a2ui.json +26 -0
- package/components/chart/chart.class.js +1304 -222
- package/components/chart/chart.css +151 -1
- package/components/chart/chart.d.ts +6 -0
- package/components/chart/chart.yaml +57 -0
- package/components/chart-legend/chart-legend.a2ui.json +17 -2
- package/components/chart-legend/chart-legend.class.js +69 -4
- package/components/chart-legend/chart-legend.css +65 -1
- package/components/chart-legend/chart-legend.d.ts +4 -2
- package/components/chart-legend/chart-legend.examples.md +4 -4
- package/components/chart-legend/chart-legend.yaml +43 -3
- 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/heatmap/heatmap.a2ui.json +5 -0
- package/components/heatmap/heatmap.d.ts +2 -0
- package/components/heatmap/heatmap.yaml +8 -0
- package/components/input/input.a2ui.json +2 -2
- package/components/input/input.class.js +12 -7
- package/components/input/input.yaml +6 -5
- package/components/integration-card/integration-card.d.ts +2 -0
- 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/popover/popover.d.ts +2 -0
- package/components/progress/progress.class.js +11 -6
- package/components/range/range.class.js +9 -1
- package/components/select/select.a2ui.json +5 -1
- package/components/select/select.class.js +14 -14
- package/components/select/select.css +14 -1
- package/components/select/select.yaml +7 -1
- package/components/stream/stream.d.ts +2 -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 +28 -4
- package/components/table-toolbar/table-toolbar.class.js +346 -11
- package/components/table-toolbar/table-toolbar.css +265 -0
- package/components/table-toolbar/table-toolbar.d.ts +5 -1
- package/components/table-toolbar/table-toolbar.yaml +130 -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/tour/tour.d.ts +10 -0
- 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/custom-elements.json +73 -29
- 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 +119 -119
- package/dist/web-components.sheet.js +1 -1
- 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 +114 -0
- 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/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/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/type/roles.css +29 -9
|
@@ -23,8 +23,12 @@ props:
|
|
|
23
23
|
reflect: true
|
|
24
24
|
items:
|
|
25
25
|
description: >-
|
|
26
|
-
JSON array of {key, label, slot?, pct?} legend items.
|
|
27
|
-
over [for] when both are provided.
|
|
26
|
+
JSON array of {key, label, slot?, pct?, deemphasized?} legend items.
|
|
27
|
+
Takes precedence over [for] when both are provided. `deemphasized`
|
|
28
|
+
(REQ-R-007) mutes the row's swatch/label to the same muted-neutral
|
|
29
|
+
tier chart-ui's own internal legend uses for a de-emphasized series
|
|
30
|
+
(e.g. `composed@1:1`'s secondary line) — mirrored automatically from
|
|
31
|
+
a `[for]`-bound chart's own `.legendData`, or settable explicitly here.
|
|
28
32
|
type: string
|
|
29
33
|
default: ''
|
|
30
34
|
shape:
|
|
@@ -38,7 +42,11 @@ props:
|
|
|
38
42
|
- dashed
|
|
39
43
|
reflect: true
|
|
40
44
|
position:
|
|
41
|
-
description:
|
|
45
|
+
description: >-
|
|
46
|
+
Layout hint — drives flex-direction. Actual placement follows DOM
|
|
47
|
+
order. Superseded by a resolved `ratio` bucket (below) whenever one
|
|
48
|
+
is in effect; this is the pre-ADR-0074 fallback for a legend with
|
|
49
|
+
neither an explicit `ratio` nor a `[for]`-bound chart.
|
|
42
50
|
type: string
|
|
43
51
|
default: bottom
|
|
44
52
|
enum:
|
|
@@ -47,6 +55,26 @@ props:
|
|
|
47
55
|
- left
|
|
48
56
|
- right
|
|
49
57
|
reflect: true
|
|
58
|
+
ratio:
|
|
59
|
+
description: >-
|
|
60
|
+
Chart 2.0 legend layout bucket (ADR-0074, gh#1624 plan step 5,
|
|
61
|
+
REQ-R-005): 3:2 renders a horizontal row, 1:1 a wrapped 2-column
|
|
62
|
+
grid, 2:3 a vertical stacked list. An explicit value PINS the
|
|
63
|
+
bucket regardless of any bound chart. Unset (default) auto-mirrors
|
|
64
|
+
the `[for]`-bound chart's own resolved `data-ratio-resolved` bucket
|
|
65
|
+
— the legend never runs its own container-query classification,
|
|
66
|
+
since its own box is typically a thin strip with no studied aspect
|
|
67
|
+
of its own. With neither a pin nor a bound chart, no bucket
|
|
68
|
+
resolves and `position`'s flex-direction rules apply unchanged. The
|
|
69
|
+
resolved bucket reflects read-only onto the host as
|
|
70
|
+
`data-ratio-resolved`, mirroring chart-ui's own convention.
|
|
71
|
+
type: string
|
|
72
|
+
default: ""
|
|
73
|
+
enum:
|
|
74
|
+
- "3:2"
|
|
75
|
+
- "1:1"
|
|
76
|
+
- "2:3"
|
|
77
|
+
reflect: true
|
|
50
78
|
interactive:
|
|
51
79
|
description: >-
|
|
52
80
|
Canonical opt-in spelling (ADR-0063, gh#1563) — the deprecated
|
|
@@ -107,6 +135,8 @@ a2ui:
|
|
|
107
135
|
reason: 'Composition contract.'
|
|
108
136
|
- rule: 'position attribute (top|bottom|left|right) places legend relative to its chart; interactive="false" disables interactivity.'
|
|
109
137
|
reason: 'Layout + interaction knobs.'
|
|
138
|
+
- rule: 'ratio attribute (3:2|1:1|2:3, ADR-0074) selects the legend layout shape (row/grid/list); left unset it auto-mirrors a [for]-bound chart''s own resolved ratio.'
|
|
139
|
+
reason: 'Ratio-responsive layout contract, gh#1624 plan step 5.'
|
|
110
140
|
anti_patterns: []
|
|
111
141
|
examples:
|
|
112
142
|
- name: standalone-with-items
|
|
@@ -119,6 +149,16 @@ examples:
|
|
|
119
149
|
"items": "[{\"key\":\"revenue\",\"label\":\"Revenue\",\"slot\":0},{\"key\":\"users\",\"label\":\"Users\",\"slot\":1}]"
|
|
120
150
|
}
|
|
121
151
|
]
|
|
152
|
+
- name: ratio-pinned-grid
|
|
153
|
+
description: Standalone legend pinned to the 1:1 (2-col grid) layout bucket.
|
|
154
|
+
a2ui: >-
|
|
155
|
+
[
|
|
156
|
+
{
|
|
157
|
+
"id": "legend", "component": "ChartLegend",
|
|
158
|
+
"ratio": "1:1",
|
|
159
|
+
"items": "[{\"key\":\"revenue\",\"label\":\"Revenue\",\"slot\":0},{\"key\":\"users\",\"label\":\"Users\",\"slot\":1},{\"key\":\"churn\",\"label\":\"Churn\",\"slot\":2},{\"key\":\"nps\",\"label\":\"NPS\",\"slot\":3}]"
|
|
160
|
+
}
|
|
161
|
+
]
|
|
122
162
|
- name: mirror-chart-by-id
|
|
123
163
|
description: Legend mirrors a named chart's series data.
|
|
124
164
|
a2ui: >-
|
|
@@ -339,6 +339,9 @@
|
|
|
339
339
|
"--combobox-match-bg": {
|
|
340
340
|
"description": "Background for `<mark>` spans that highlight matched substrings"
|
|
341
341
|
},
|
|
342
|
+
"--combobox-min-width": {
|
|
343
|
+
"description": "Intrinsic min-width floor on the host (default `20ch`, matching input-ui's own `--input-min-width` UA-baseline derivation) so a combobox in a shrinking flex/grid cell doesn't crush to unusable width. Set to `0` to opt back into full collapse (gh#1633)."
|
|
344
|
+
},
|
|
342
345
|
"--combobox-option-bg-active": {
|
|
343
346
|
"description": "Background for the currently active (arrow-navigated) option"
|
|
344
347
|
},
|
|
@@ -41,6 +41,7 @@ import { UIFormElement } from '../../core/form.js';
|
|
|
41
41
|
import { anchorPopover } from '../../core/anchor.js';
|
|
42
42
|
import { untracked } from '../../core/signals.js';
|
|
43
43
|
import { logicalSlotted } from '../../core/logical-children.js';
|
|
44
|
+
import { syncAutoAriaLabel } from '../../core/a11y.js';
|
|
44
45
|
|
|
45
46
|
function escapeHTML(s) {
|
|
46
47
|
return String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
@@ -103,6 +104,10 @@ export class UICombobox extends UIFormElement {
|
|
|
103
104
|
#anchorCleanup = null;
|
|
104
105
|
#rafId = null;
|
|
105
106
|
#instanceId = `combobox-${++cbInstanceSeq}`;
|
|
107
|
+
// gh#1647: tracks the aria-label VALUE this element itself last derived
|
|
108
|
+
// from `placeholder` (undefined when none) — value-tracking guard
|
|
109
|
+
// ported from tag.class.js/select.class.js (gh#1644/#1646).
|
|
110
|
+
#lastAutoAriaLabel = undefined;
|
|
106
111
|
|
|
107
112
|
// ── Stable handler refs (so removeEventListener finds them) ──
|
|
108
113
|
#onInputEvent = () => this.#handleInput();
|
|
@@ -176,14 +181,10 @@ export class UICombobox extends UIFormElement {
|
|
|
176
181
|
this.#labelEl.style.display = this.label ? '' : 'none';
|
|
177
182
|
}
|
|
178
183
|
|
|
179
|
-
// aria-label fallback when no [label] is set
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
this.setAttribute('aria-label', this.placeholder);
|
|
184
|
-
} else {
|
|
185
|
-
this.removeAttribute('aria-label');
|
|
186
|
-
}
|
|
184
|
+
// aria-label fallback when no [label] is set (gh#1647) — never clobber
|
|
185
|
+
// a consumer's own aria-label (value-tracking guard, gh#1644/#1646).
|
|
186
|
+
const ariaSource = this.label ? '' : (this.placeholder || '');
|
|
187
|
+
this.#lastAutoAriaLabel = syncAutoAriaLabel(this, this.#lastAutoAriaLabel, ariaSource);
|
|
187
188
|
|
|
188
189
|
// Toggle clear-button visibility when [clearable].
|
|
189
190
|
if (this.#clearBtn) {
|
|
@@ -6,6 +6,15 @@
|
|
|
6
6
|
--combobox-height: var(--a-size);
|
|
7
7
|
--combobox-px: var(--a-ui-px);
|
|
8
8
|
--combobox-radius: var(--a-radius);
|
|
9
|
+
/* gh#1633 / ADIA2-9136 (adiav2#12791) — the host's own `min-width: 0`
|
|
10
|
+
below (the ellipsis-reset trick, kept for [data-input] inside this
|
|
11
|
+
floor) had no intrinsic lower bound, so a combobox in a shrinking
|
|
12
|
+
flex/grid cell crushed to unusable width — the same gap already
|
|
13
|
+
fixed on --input-min-width (this family's reference; see input.css,
|
|
14
|
+
gh#781) and on --select-min-width (same fix, select.css). Same
|
|
15
|
+
ch-based UA-baseline derivation. Override to 0 to opt back into
|
|
16
|
+
full collapse. */
|
|
17
|
+
--combobox-min-width: 20ch;
|
|
9
18
|
|
|
10
19
|
/* ── Typography ── */
|
|
11
20
|
--combobox-font-size: var(--a-ui-size);
|
|
@@ -62,7 +71,11 @@
|
|
|
62
71
|
flex-direction: column;
|
|
63
72
|
gap: var(--combobox-gap);
|
|
64
73
|
position: relative;
|
|
65
|
-
min-width
|
|
74
|
+
/* Floored, not zeroed — see --combobox-min-width above (gh#1633).
|
|
75
|
+
[data-input]'s own `min-width: 0` further down stays 0: that one
|
|
76
|
+
exists so its value text can ellipsis-truncate INSIDE this floor,
|
|
77
|
+
not to guard the host's own collapse. */
|
|
78
|
+
min-width: var(--combobox-min-width);
|
|
66
79
|
/* font-family floor on the host — internal input/options inherit it. */
|
|
67
80
|
font-family: var(--combobox-font-family, var(--a-font-family-ui));
|
|
68
81
|
font-size: var(--combobox-font-size);
|
|
@@ -326,9 +339,17 @@ combobox-ui [data-listbox] [role="group"] [data-group-label] {
|
|
|
326
339
|
white-space: nowrap;
|
|
327
340
|
}
|
|
328
341
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
342
|
+
/* Scoped to `[data-listbox]`'s own markers — gh#1700, same shape as
|
|
343
|
+
gh#1697/#1698. The input span (`[data-input]`) toggles the SAME
|
|
344
|
+
`data-empty` attribute for an unrelated reason (gating its own
|
|
345
|
+
`::before` placeholder pseudo) — a bare, unscoped `combobox-ui
|
|
346
|
+
[data-empty]` selector matched BOTH uses, and (being (0,1,1)) outranked
|
|
347
|
+
`[data-input]`'s own (0,1,0) padding/color/font-size, one property away
|
|
348
|
+
from repeating the command-ui leak. Scoping to the listbox container
|
|
349
|
+
removes the collision at its root. */
|
|
350
|
+
combobox-ui [data-listbox] [data-empty],
|
|
351
|
+
combobox-ui [data-listbox] [data-loading],
|
|
352
|
+
combobox-ui [data-listbox] [data-footer] {
|
|
332
353
|
padding: var(--a-space-2) var(--a-ui-px);
|
|
333
354
|
color: var(--a-ui-text-muted);
|
|
334
355
|
font-size: var(--a-ui-size);
|
|
@@ -257,6 +257,12 @@ tokens:
|
|
|
257
257
|
tinted selected row.
|
|
258
258
|
--combobox-match-bg:
|
|
259
259
|
description: Background for `<mark>` spans that highlight matched substrings
|
|
260
|
+
--combobox-min-width:
|
|
261
|
+
description: >-
|
|
262
|
+
Intrinsic min-width floor on the host (default `20ch`, matching
|
|
263
|
+
input-ui's own `--input-min-width` UA-baseline derivation) so a
|
|
264
|
+
combobox in a shrinking flex/grid cell doesn't crush to unusable
|
|
265
|
+
width. Set to `0` to opt back into full collapse (gh#1633).
|
|
260
266
|
requiredIcons:
|
|
261
267
|
- caret-down
|
|
262
268
|
- x-circle
|
|
@@ -221,7 +221,23 @@
|
|
|
221
221
|
}
|
|
222
222
|
|
|
223
223
|
/* ── Empty state ── */
|
|
224
|
-
[data-empty]
|
|
224
|
+
/* Scoped to `[slot="list"]`'s own `[data-empty]` — gh#1697. The search
|
|
225
|
+
input (`& > header [slot="input"]`) toggles the SAME `data-empty`
|
|
226
|
+
attribute for an unrelated reason (gating its own `::before`
|
|
227
|
+
placeholder pseudo, per REQ above) — a bare, unscoped `[data-empty]`
|
|
228
|
+
selector matched BOTH uses, so this rule's `text-align: center` leaked
|
|
229
|
+
onto the input span whenever it was empty, centering the placeholder
|
|
230
|
+
(and, structurally, any text a consumer's own script inserted while
|
|
231
|
+
the attribute was still present). `padding`/`color` never leaked in
|
|
232
|
+
practice — `& > header [slot="input"]`'s own compound selector
|
|
233
|
+
(header-descendant + attribute, (0,1,1)) already outranks the bare
|
|
234
|
+
`[data-empty]` (0,1,0) for those two properties — but `text-align` had
|
|
235
|
+
no competing declaration anywhere else in this file, so the leaked
|
|
236
|
+
value won by default with nothing to lose against. Scoping the
|
|
237
|
+
selector to the list container removes the collision at its root
|
|
238
|
+
instead of only patching the one property a screenshot happened to
|
|
239
|
+
catch. */
|
|
240
|
+
[slot="list"] [data-empty] {
|
|
225
241
|
padding: var(--command-py) var(--command-px);
|
|
226
242
|
color: var(--command-empty-fg);
|
|
227
243
|
text-align: center;
|
|
@@ -56,6 +56,8 @@ export type ContextMenuSelectEvent = CustomEvent<ContextMenuSelectEventDetail>;
|
|
|
56
56
|
export class UIContextMenu extends UIElement {
|
|
57
57
|
/** CSS selector(s) for target element(s). Empty = use default-slot child. */
|
|
58
58
|
for: string;
|
|
59
|
+
/** Long-press duration (ms) on touch devices to open the menu. */
|
|
60
|
+
longPressMs: number;
|
|
59
61
|
/** Programmatic open state. Set true to open at target center. */
|
|
60
62
|
open: boolean;
|
|
61
63
|
|
|
@@ -53,6 +53,11 @@
|
|
|
53
53
|
"component": {
|
|
54
54
|
"const": "Heatmap"
|
|
55
55
|
},
|
|
56
|
+
"data": {
|
|
57
|
+
"description": "Cell data as a JSON array of `{ r, c, v, label? }` objects, or set the `.data` property directly with an already-parsed array. Parsed on connect if the attribute is present and `.data` hasn't already been set programmatically.",
|
|
58
|
+
"type": "string",
|
|
59
|
+
"default": ""
|
|
60
|
+
},
|
|
56
61
|
"noLegend": {
|
|
57
62
|
"description": "Hide the Less/More legend strip",
|
|
58
63
|
"type": "boolean",
|
|
@@ -27,6 +27,8 @@ export class UIHeatmap extends UIElement {
|
|
|
27
27
|
colorScheme: 'accent' | 'success' | 'warning' | 'danger' | 'info' | 'data-ramp';
|
|
28
28
|
/** Column count */
|
|
29
29
|
cols: number;
|
|
30
|
+
/** Cell data as a JSON array of `{ r, c, v, label? }` objects, or set the `.data` property directly with an already-parsed array. Parsed on connect if the attribute is present and `.data` hasn't already been set programmatically. */
|
|
31
|
+
data: string;
|
|
30
32
|
/** Hide the Less/More legend strip */
|
|
31
33
|
noLegend: boolean;
|
|
32
34
|
/** Row count */
|
|
@@ -25,6 +25,14 @@ props:
|
|
|
25
25
|
enum:
|
|
26
26
|
- square
|
|
27
27
|
- wide
|
|
28
|
+
data:
|
|
29
|
+
description: >-
|
|
30
|
+
Cell data as a JSON array of `{ r, c, v, label? }` objects, or set the
|
|
31
|
+
`.data` property directly with an already-parsed array. Parsed on
|
|
32
|
+
connect if the attribute is present and `.data` hasn't already been
|
|
33
|
+
set programmatically.
|
|
34
|
+
type: string
|
|
35
|
+
default: ""
|
|
28
36
|
colorScheme:
|
|
29
37
|
description: Color ramp
|
|
30
38
|
type: string
|
|
@@ -166,7 +166,7 @@
|
|
|
166
166
|
},
|
|
167
167
|
{
|
|
168
168
|
"fix": "{\"component\": \"CalendarPicker\", \"precision\": \"minute\"}\n",
|
|
169
|
-
"why": "Same gap as \"date\" — no native datetime picker rendered. Use\nCalendarPicker with precision=\"minute\" for combined date + time\ninput (DatetimePicker was a deprecated thin alias, cut in 0.8.43,\ngh#1617 — CalendarPicker is the sole
|
|
169
|
+
"why": "Same gap as \"date\" — no native datetime picker rendered. Use\nCalendarPicker with precision=\"minute\" for combined date + time\ninput (DatetimePicker was a deprecated thin alias, cut in 0.8.43,\ngh#1617 — CalendarPicker is the sole A2UI component now).\n",
|
|
170
170
|
"wrong": "{\"component\": \"Input\", \"type\": \"datetime-local\"}\n"
|
|
171
171
|
},
|
|
172
172
|
{
|
|
@@ -186,7 +186,7 @@
|
|
|
186
186
|
},
|
|
187
187
|
{
|
|
188
188
|
"fix": "{\"component\": \"ColorInput\"}\n",
|
|
189
|
-
"why": "No native color swatch/picker rendered — just a plain text\nsurface. Use ColorInput for a compact form-bearing swatch +\npopover, or ColorArea directly for the full OKLCH picker surface\n(ColorPicker was a deprecated thin alias, cut in 0.8.43, gh#1617 —\nColorArea is the sole
|
|
189
|
+
"why": "No native color swatch/picker rendered — just a plain text\nsurface. Use ColorInput for a compact form-bearing swatch +\npopover, or ColorArea directly for the full OKLCH picker surface\n(ColorPicker was a deprecated thin alias, cut in 0.8.43, gh#1617 —\nColorArea is the sole A2UI component now).\n",
|
|
190
190
|
"wrong": "{\"component\": \"Input\", \"type\": \"color\"}\n"
|
|
191
191
|
},
|
|
192
192
|
{
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
import { UIFormElement } from '../../core/form.js';
|
|
54
54
|
import { isIconName, whenIconRegistryReady } from '../../core/icons.js';
|
|
55
55
|
import { logicalSlotted } from '../../core/logical-children.js';
|
|
56
|
+
import { syncAutoAriaLabel } from '../../core/a11y.js';
|
|
56
57
|
|
|
57
58
|
const renderAffix = (v) => isIconName(v)
|
|
58
59
|
? `<icon-ui name="${v}"></icon-ui>`
|
|
@@ -106,6 +107,12 @@ export class UIInput extends UIFormElement {
|
|
|
106
107
|
#cachedGroup = '';
|
|
107
108
|
#cachedSepFor = null;
|
|
108
109
|
static #labelSeq = 0;
|
|
110
|
+
// gh#1647: tracks the aria-label VALUE this element itself last derived
|
|
111
|
+
// from `placeholder` (undefined when none), so re-renders refresh/clear
|
|
112
|
+
// OUR name without ever clobbering a consumer's own aria-label —
|
|
113
|
+
// value-tracking guard ported from tag.class.js/select.class.js
|
|
114
|
+
// (gh#1644/#1646).
|
|
115
|
+
#lastAutoAriaLabel = undefined;
|
|
109
116
|
|
|
110
117
|
// Hold-to-repeat tuning. Initial delay before autorepeat begins, and the
|
|
111
118
|
// interval between repeats. Values match the cadence of the native
|
|
@@ -471,13 +478,11 @@ export class UIInput extends UIFormElement {
|
|
|
471
478
|
// Re-resolve prefix/suffix from the live property each render (FEEDBACK-93).
|
|
472
479
|
this.#syncAffixSlots();
|
|
473
480
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
this.removeAttribute('aria-label');
|
|
480
|
-
}
|
|
481
|
+
// A11y (gh#1647): a visible `label` already names the field; otherwise
|
|
482
|
+
// fall back to `placeholder` — but never clobber a consumer's own
|
|
483
|
+
// aria-label (value-tracking guard, gh#1644/#1646).
|
|
484
|
+
const ariaSource = this.label ? '' : (this.placeholder || '');
|
|
485
|
+
this.#lastAutoAriaLabel = syncAutoAriaLabel(this, this.#lastAutoAriaLabel, ariaSource);
|
|
481
486
|
|
|
482
487
|
if (this.#isNumberMode) {
|
|
483
488
|
const n = this.valueAsNumber;
|
|
@@ -264,9 +264,10 @@ a2ui:
|
|
|
264
264
|
<input type="password"> for masking (per ADR-0025).
|
|
265
265
|
- >-
|
|
266
266
|
Wrap <input-ui> in <field-ui label="…" hint="…" error="…"> for
|
|
267
|
-
the canonical stacked label / hint / error chrome.
|
|
268
|
-
|
|
269
|
-
|
|
267
|
+
the canonical stacked label / hint / error chrome. input-ui itself
|
|
268
|
+
only supports inline [label] / [error] (gh#1669 — [hint] is a
|
|
269
|
+
field-ui-only prop; input-ui has no equivalent) — use field-ui
|
|
270
|
+
when a hint line is needed.
|
|
270
271
|
- >-
|
|
271
272
|
Form participation is implicit via UIFormElement. Set [name] for
|
|
272
273
|
FormData submission; [required] / [disabled] / [readonly]
|
|
@@ -309,7 +310,7 @@ anti_patterns:
|
|
|
309
310
|
Same gap as "date" — no native datetime picker rendered. Use
|
|
310
311
|
CalendarPicker with precision="minute" for combined date + time
|
|
311
312
|
input (DatetimePicker was a deprecated thin alias, cut in 0.8.43,
|
|
312
|
-
gh#1617 — CalendarPicker is the sole
|
|
313
|
+
gh#1617 — CalendarPicker is the sole A2UI component now).
|
|
313
314
|
fix: |
|
|
314
315
|
{"component": "CalendarPicker", "precision": "minute"}
|
|
315
316
|
- wrong: |
|
|
@@ -342,7 +343,7 @@ anti_patterns:
|
|
|
342
343
|
surface. Use ColorInput for a compact form-bearing swatch +
|
|
343
344
|
popover, or ColorArea directly for the full OKLCH picker surface
|
|
344
345
|
(ColorPicker was a deprecated thin alias, cut in 0.8.43, gh#1617 —
|
|
345
|
-
ColorArea is the sole
|
|
346
|
+
ColorArea is the sole A2UI component now).
|
|
346
347
|
fix: |
|
|
347
348
|
{"component": "ColorInput"}
|
|
348
349
|
- wrong: |
|
|
@@ -42,6 +42,8 @@ export class UIIntegrationCard extends UIElement {
|
|
|
42
42
|
description: string;
|
|
43
43
|
/** Disables the action button. */
|
|
44
44
|
disabled: boolean;
|
|
45
|
+
/** When `status="error"`, the message to show below the description. Ignored for other statuses. */
|
|
46
|
+
errorMessage: string;
|
|
45
47
|
/** Logo URL (preferred — renders as <img>) or icon name (renders as <icon-ui>). URLs are sniffed by presence of `/`. */
|
|
46
48
|
logo: string;
|
|
47
49
|
/** Display name shown as the card title. Required. */
|
|
@@ -41,11 +41,6 @@
|
|
|
41
41
|
"type": "boolean",
|
|
42
42
|
"default": false
|
|
43
43
|
},
|
|
44
|
-
"port-size": {
|
|
45
|
-
"description": "Port indicator dot size in pixels",
|
|
46
|
-
"type": "number",
|
|
47
|
-
"default": 10
|
|
48
|
-
},
|
|
49
44
|
"portSize": {
|
|
50
45
|
"description": "Port indicator size",
|
|
51
46
|
"type": "number",
|
|
@@ -56,21 +51,11 @@
|
|
|
56
51
|
"type": "boolean",
|
|
57
52
|
"default": false
|
|
58
53
|
},
|
|
59
|
-
"show-ports": {
|
|
60
|
-
"description": "Show port indicator dots. Defaults to true when editable",
|
|
61
|
-
"type": "boolean",
|
|
62
|
-
"default": false
|
|
63
|
-
},
|
|
64
54
|
"showPorts": {
|
|
65
55
|
"description": "Show port indicators",
|
|
66
56
|
"type": "boolean",
|
|
67
57
|
"default": false
|
|
68
58
|
},
|
|
69
|
-
"stroke-width": {
|
|
70
|
-
"description": "Stroke width in pixels",
|
|
71
|
-
"type": "number",
|
|
72
|
-
"default": 2
|
|
73
|
-
},
|
|
74
59
|
"strokeWidth": {
|
|
75
60
|
"description": "Line stroke width",
|
|
76
61
|
"type": "number",
|
|
@@ -30,10 +30,6 @@ props:
|
|
|
30
30
|
description: Allow interactive creation/deletion of connections via drag
|
|
31
31
|
type: boolean
|
|
32
32
|
default: false
|
|
33
|
-
port-size:
|
|
34
|
-
description: Port indicator dot size in pixels
|
|
35
|
-
type: number
|
|
36
|
-
default: 10
|
|
37
33
|
portSize:
|
|
38
34
|
description: Port indicator size
|
|
39
35
|
type: number
|
|
@@ -44,19 +40,11 @@ props:
|
|
|
44
40
|
type: boolean
|
|
45
41
|
default: false
|
|
46
42
|
reflect: true
|
|
47
|
-
show-ports:
|
|
48
|
-
description: Show port indicator dots. Defaults to true when editable
|
|
49
|
-
type: boolean
|
|
50
|
-
default: false
|
|
51
43
|
showPorts:
|
|
52
44
|
description: Show port indicators
|
|
53
45
|
type: boolean
|
|
54
46
|
default: false
|
|
55
47
|
attribute: show-ports
|
|
56
|
-
stroke-width:
|
|
57
|
-
description: Stroke width in pixels
|
|
58
|
-
type: number
|
|
59
|
-
default: 2
|
|
60
48
|
strokeWidth:
|
|
61
49
|
description: Line stroke width
|
|
62
50
|
type: number
|
|
@@ -87,6 +87,16 @@ export class UINumberFormat extends UIElement {
|
|
|
87
87
|
this.textContent = text;
|
|
88
88
|
// Mirror raw value via aria-label so screen readers can read the
|
|
89
89
|
// underlying number alongside the formatted glyphs.
|
|
90
|
+
//
|
|
91
|
+
// gh#1647 judgment call (triage-corrections comment, 2026-08-18):
|
|
92
|
+
// this aria-label is intentional component-owned chrome, not a
|
|
93
|
+
// consumer-overridable name — it mirrors the RAW underlying value
|
|
94
|
+
// ("42% (raw: 0.42)") specifically for a11y clarity, so a consumer
|
|
95
|
+
// override would likely defeat that purpose. Deliberately NOT ported
|
|
96
|
+
// to the value-tracking `#lastAutoAriaLabel` guard (core/a11y.js)
|
|
97
|
+
// that the other five gh#1647 primitives got — kept unconditional,
|
|
98
|
+
// auto-deriving on every render. Revisit only if a stated consumer
|
|
99
|
+
// need for overriding it surfaces.
|
|
90
100
|
if (text && Number.isFinite(Number(this.value))) {
|
|
91
101
|
this.setAttribute('aria-label', `${text} (raw: ${this.value})`);
|
|
92
102
|
} else {
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
24
|
import { UIElement } from '../../core/element.js';
|
|
25
|
+
import { KEY_MAP } from '../../core/template.js';
|
|
25
26
|
|
|
26
27
|
export class UIPagination extends UIElement {
|
|
27
28
|
static properties = {
|
|
@@ -48,10 +49,32 @@ export class UIPagination extends UIElement {
|
|
|
48
49
|
|
|
49
50
|
connected() {
|
|
50
51
|
if (!this.#nav) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
// Adopt a pre-existing server-rendered <nav slot="nav"> instead of
|
|
53
|
+
// unconditionally appending a fresh one — first-connect adoption
|
|
54
|
+
// (gh#1687). Steady-state reconcile is unaffected; this only guards
|
|
55
|
+
// the construction-time #nav field being null on upgrade.
|
|
56
|
+
this.#nav = this.querySelector(':scope > nav[slot="nav"]');
|
|
57
|
+
if (this.#nav) {
|
|
58
|
+
// Adopting an existing nav is only half the fix — reconcile()'s
|
|
59
|
+
// own keyed-diff (element.js) keys off a WeakMap-shaped
|
|
60
|
+
// `parent[KEY_MAP]` populated by THIS element's own prior render
|
|
61
|
+
// calls, which a freshly-parsed SSR fragment never has. Without
|
|
62
|
+
// seeding it here, the very first render treats every adopted
|
|
63
|
+
// child as unrecognized, stamps a full set of BRAND NEW nodes
|
|
64
|
+
// alongside them, and never removes the orphaned originals —
|
|
65
|
+
// silently doubling the button count inside the (correctly
|
|
66
|
+
// singular) adopted <nav>. Positionally zip the adopted
|
|
67
|
+
// children against the SAME key order #buildRange would
|
|
68
|
+
// produce right now; a shape mismatch (stale/hand-edited SSR
|
|
69
|
+
// markup) is left unseeded so reconcile falls back to its
|
|
70
|
+
// normal rebuild-from-scratch behavior instead of miskeying.
|
|
71
|
+
this.#seedKeyMapFromAdoptedNav();
|
|
72
|
+
} else {
|
|
73
|
+
this.#nav = document.createElement('nav');
|
|
74
|
+
this.#nav.setAttribute('slot', 'nav');
|
|
75
|
+
this.#nav.setAttribute('aria-label', 'Pagination');
|
|
76
|
+
this.appendChild(this.#nav);
|
|
77
|
+
}
|
|
55
78
|
}
|
|
56
79
|
|
|
57
80
|
if (!this.#bound) {
|
|
@@ -69,6 +92,67 @@ export class UIPagination extends UIElement {
|
|
|
69
92
|
this.#bound = false;
|
|
70
93
|
}
|
|
71
94
|
|
|
95
|
+
// Review finding (PR #1692) — the tag/marker a given item.type produces
|
|
96
|
+
// via #createItem(): ellipsis is a bare <span data-ellipsis>; prev/next/
|
|
97
|
+
// page are all <button-ui> distinguished by their own data-* marker
|
|
98
|
+
// (page additionally carries data-value). A same-LENGTH adopted child
|
|
99
|
+
// that doesn't match this shape (e.g. a plain <span> standing in for a
|
|
100
|
+
// page-cell <button-ui>) would still get SEEDED by a count-only check,
|
|
101
|
+
// then #updateItem() — which only sets attributes, never replaces the
|
|
102
|
+
// element — leaves it permanently the wrong tag. Checking shape here
|
|
103
|
+
// is what makes the fallback in #seedKeyMapFromAdoptedNav trustworthy.
|
|
104
|
+
#childMatchesItem(el, item) {
|
|
105
|
+
if (item.type === 'ellipsis') {
|
|
106
|
+
return el.tagName === 'SPAN' && el.hasAttribute('data-ellipsis');
|
|
107
|
+
}
|
|
108
|
+
if (el.tagName !== 'BUTTON-UI') return false;
|
|
109
|
+
if (item.type === 'prev') return el.hasAttribute('data-prev');
|
|
110
|
+
if (item.type === 'next') return el.hasAttribute('data-next');
|
|
111
|
+
return el.hasAttribute('data-page') && el.dataset.value === String(item.value);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// gh#1687 — see the call site's comment in connected(). Positionally
|
|
115
|
+
// zips the adopted nav's existing children against the key order the
|
|
116
|
+
// very next render() will compute, seeding reconcile()'s own
|
|
117
|
+
// `parent[KEY_MAP]` (element.js) so that render finds every item
|
|
118
|
+
// already present instead of stamping a duplicate full set.
|
|
119
|
+
#seedKeyMapFromAdoptedNav() {
|
|
120
|
+
const page = Math.max(1, Math.min(this.page, this.total));
|
|
121
|
+
const total = Math.max(1, this.total);
|
|
122
|
+
const siblings = Math.max(0, this.siblings);
|
|
123
|
+
const range = this.#buildRange(page, total, siblings);
|
|
124
|
+
const children = [...this.#nav.children];
|
|
125
|
+
const shapeMatches = children.length === range.length
|
|
126
|
+
&& range.every((item, i) => this.#childMatchesItem(children[i], item));
|
|
127
|
+
if (!shapeMatches) {
|
|
128
|
+
// Review finding (PR #1692, two rounds) — a bare return here left
|
|
129
|
+
// the mismatch case worse off than doing nothing: reconcile()'s
|
|
130
|
+
// own cleanup loop only removes entries it finds IN
|
|
131
|
+
// `parent[KEY_MAP]`, so an unseeded map means every existing
|
|
132
|
+
// child is invisible to it — the next render() then stamps a
|
|
133
|
+
// full fresh set AND the stale adopted children survive alongside
|
|
134
|
+
// it (reproduced: 3 stale placeholder buttons + a correct fresh
|
|
135
|
+
// 9-item render = 12 nodes). Length alone isn't a reliable
|
|
136
|
+
// correlation signal either — a same-length fixture with the
|
|
137
|
+
// WRONG tag/marker per position (e.g. N bare <span>s standing in
|
|
138
|
+
// for N <button-ui> page-cells) would pass a count-only check and
|
|
139
|
+
// get seeded, then #updateItem() — attribute-only, never a tag
|
|
140
|
+
// swap — leaves it permanently wrong. Either kind of mismatch
|
|
141
|
+
// means the adopted markup can't be trusted to correlate with the
|
|
142
|
+
// next render's key order at all — abandon adoption for real:
|
|
143
|
+
// clear the stale children so the fallback is an actual clean
|
|
144
|
+
// rebuild, the same shape #nav would be in if it had just been
|
|
145
|
+
// freshly created.
|
|
146
|
+
this.#nav.replaceChildren();
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const map = new Map();
|
|
150
|
+
for (let i = 0; i < range.length; i++) {
|
|
151
|
+
map.set(range[i].key, children[i]);
|
|
152
|
+
}
|
|
153
|
+
this.#nav[KEY_MAP] = map;
|
|
154
|
+
}
|
|
155
|
+
|
|
72
156
|
render() {
|
|
73
157
|
if (!this.#nav) return;
|
|
74
158
|
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
import { UIElement } from '../../core/element.js';
|
|
14
14
|
|
|
15
15
|
export class UIPopover extends UIElement {
|
|
16
|
+
/** Size the content panel to the trigger's width instead of max-content. Opt-in — the default (unset) keeps every existing consumer's content-sized panel unchanged. */
|
|
17
|
+
matchWidth: boolean;
|
|
16
18
|
/** Pixel offset between the anchor and the popover. Renamed from `[gap]` (gh#1335) — a raw-px number can't share a name with the global Scale-grammar `[gap]` attribute (ADR-0053). */
|
|
17
19
|
offset: number;
|
|
18
20
|
/** Controls visibility via showPopover()/hidePopover() */
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
*/
|
|
29
29
|
|
|
30
30
|
import { UIElement } from '../../core/element.js';
|
|
31
|
+
import { syncAutoAriaLabel } from '../../core/a11y.js';
|
|
31
32
|
|
|
32
33
|
export class UIProgress extends UIElement {
|
|
33
34
|
static properties = {
|
|
@@ -42,6 +43,10 @@ export class UIProgress extends UIElement {
|
|
|
42
43
|
#fill = null;
|
|
43
44
|
#labelEl = null;
|
|
44
45
|
#metaEl = null;
|
|
46
|
+
// gh#1647: tracks the aria-label VALUE this element itself last derived
|
|
47
|
+
// from `label`/`meta` (undefined when none) — value-tracking guard
|
|
48
|
+
// ported from tag.class.js/select.class.js (gh#1644/#1646).
|
|
49
|
+
#lastAutoAriaLabel = undefined;
|
|
45
50
|
|
|
46
51
|
connected() {
|
|
47
52
|
this.setAttribute('role', 'progressbar');
|
|
@@ -111,12 +116,12 @@ export class UIProgress extends UIElement {
|
|
|
111
116
|
}
|
|
112
117
|
}
|
|
113
118
|
|
|
114
|
-
// ARIA: label the control so screen readers announce "CPU, 42 percent"
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
119
|
+
// ARIA: label the control so screen readers announce "CPU, 42 percent"
|
|
120
|
+
// — never clobbering a consumer's own aria-label (gh#1647,
|
|
121
|
+
// value-tracking guard ported from tag.class.js/select.class.js,
|
|
122
|
+
// gh#1644/#1646).
|
|
123
|
+
const ariaSource = this.label ? (this.meta ? `${this.label}, ${this.meta}` : this.label) : '';
|
|
124
|
+
this.#lastAutoAriaLabel = syncAutoAriaLabel(this, this.#lastAutoAriaLabel, ariaSource);
|
|
120
125
|
}
|
|
121
126
|
|
|
122
127
|
disconnected() {
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
27
|
import { UIFormElement } from '../../core/form.js';
|
|
28
|
+
import { syncAutoAriaLabel } from '../../core/a11y.js';
|
|
28
29
|
|
|
29
30
|
export class UIRange extends UIFormElement {
|
|
30
31
|
// Opt out of UIFormElement's per-control `label` deprecation warning.
|
|
@@ -52,6 +53,10 @@ export class UIRange extends UIFormElement {
|
|
|
52
53
|
#dragging = false;
|
|
53
54
|
#startX = 0;
|
|
54
55
|
#startVal = 0;
|
|
56
|
+
// gh#1647: tracks the aria-label VALUE this element itself last derived
|
|
57
|
+
// from `label` (undefined when none) — value-tracking guard ported from
|
|
58
|
+
// tag.class.js/select.class.js (gh#1644/#1646).
|
|
59
|
+
#lastAutoAriaLabel = undefined;
|
|
55
60
|
|
|
56
61
|
connected() {
|
|
57
62
|
super.connected();
|
|
@@ -96,7 +101,10 @@ export class UIRange extends UIFormElement {
|
|
|
96
101
|
this.setAttribute('aria-valuemax', this.max);
|
|
97
102
|
this.setAttribute('aria-valuenow', this.value);
|
|
98
103
|
this.setAttribute('aria-valuetext', `${this.value}${this.suffix ? ' ' + this.suffix : ''}`);
|
|
99
|
-
|
|
104
|
+
|
|
105
|
+
// A11y (gh#1647): never clobber a consumer's own aria-label
|
|
106
|
+
// (value-tracking guard, gh#1644/#1646).
|
|
107
|
+
this.#lastAutoAriaLabel = syncAutoAriaLabel(this, this.#lastAutoAriaLabel, this.label || '');
|
|
100
108
|
|
|
101
109
|
this.syncValue(String(this.value));
|
|
102
110
|
}
|