@adia-ai/web-components 0.8.21 → 0.8.22
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 +11 -0
- package/components/combobox/combobox.a2ui.json +1 -1
- package/components/combobox/combobox.class.js +14 -4
- package/components/combobox/combobox.css +52 -4
- package/components/combobox/combobox.yaml +5 -1
- package/components/select/select.class.js +14 -3
- package/dist/theme-provider.min.js +1 -1
- package/dist/web-components.min.css +1 -1
- package/dist/web-components.min.js +2 -2
- package/dist/web-components.sheet.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog — @adia-ai/web-components
|
|
2
2
|
|
|
3
|
+
## [0.8.22] — 2026-07-29
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- **`select-ui` action rows no longer read as selected in an uncommitted select** (`components/select/`; independent-review finding, 2026-07-29). Action rows stamped `data-value=""`, and since `value` defaults to `''`, the aria-selected sync marked every command row selected whenever nothing was committed — the default state of a user menu, so screen readers announced "Log out, selected" and the row rendered at selected weight. Action rows now carry no `data-value` at all, both aria-selected paths (initial stamp + sync loop) skip them, and the regression test covers the sync loop the original tests missed.
|
|
7
|
+
|
|
8
|
+
### Changed
|
|
9
|
+
- **`combobox-ui` single-select rows carry a radio indicator** (`components/combobox/`; operator ruling 2026-07-27, extended). `select-ui` adopted the indicator treatment in 0.8.17 — "selection rides the control, not a row tint" — but combobox was left tinting the committed row with `--a-ui-bg-selected`, so two sibling single-select surfaces disagreed about what *selected* looks like (the same divergence class gh#474 fixed between `form-popover-ui` and `select-ui`). Rows now stamp `[data-single-option]` + a leading `[data-radio]` (bordered circle, primary ring + centre dot when committed), mirroring select-ui's structure and CSS exactly. `--combobox-option-bg-selected` / `-fg-selected` remain consumer-overridable API, but their DEFAULTS move to the plain row colors — set `--combobox-option-bg-selected` to re-enable a tinted row. Hover/active feedback is restated on the selected row, since the 2-attribute selected rule outweighs the single-attribute `[data-active]` one (the trap select-ui hit when its tint was removed). Tests 15 → 17.
|
|
10
|
+
|
|
11
|
+
### Maintenance
|
|
12
|
+
- **`dist/` bundles rebuilt** in this cut's window (4 file(s)) — regenerated from the source changes described above, not independent edits.
|
|
13
|
+
|
|
3
14
|
## [0.8.21] — 2026-07-28
|
|
4
15
|
|
|
5
16
|
### Fixed
|
|
@@ -342,7 +342,7 @@
|
|
|
342
342
|
"description": "Background for the currently active (arrow-navigated) option"
|
|
343
343
|
},
|
|
344
344
|
"--combobox-option-bg-selected": {
|
|
345
|
-
"description": "Background for the currently committed option"
|
|
345
|
+
"description": "Background for the currently committed option. Defaults to `transparent`: selection is shown by the leading radio indicator rather than a row tint, matching select-ui. Set this to re-enable a tinted selected row."
|
|
346
346
|
},
|
|
347
347
|
"--combobox-option-fg-active": {
|
|
348
348
|
"description": "Foreground for the currently active option"
|
|
@@ -394,10 +394,20 @@ export class UICombobox extends UIFormElement {
|
|
|
394
394
|
el.dataset.value = opt.value ?? '';
|
|
395
395
|
if (opt.disabled) el.setAttribute('aria-disabled', 'true');
|
|
396
396
|
if (opt.value === this.value) el.setAttribute('aria-selected', 'true');
|
|
397
|
-
//
|
|
398
|
-
//
|
|
399
|
-
//
|
|
400
|
-
|
|
397
|
+
// Selection rides a RADIO indicator, not a row tint (operator ruling
|
|
398
|
+
// 2026-07-27, the same one select-ui's single-select rows follow) —
|
|
399
|
+
// combobox was the last single-select surface still speaking the old
|
|
400
|
+
// language, which made two sibling controls disagree about what
|
|
401
|
+
// "selected" looks like. [data-single-option] is the structural hook
|
|
402
|
+
// CSS keys off, mirroring select-ui exactly.
|
|
403
|
+
//
|
|
404
|
+
// Single wrapping span for the LABEL: [role="option"] is display:flex,
|
|
405
|
+
// and a bare <mark> + text-run pair become SEPARATE flex items — flex
|
|
406
|
+
// drops the whitespace between them ("Ada Lovelace" → "AdaLovelace").
|
|
407
|
+
el.setAttribute('data-single-option', '');
|
|
408
|
+
el.innerHTML =
|
|
409
|
+
`<span data-radio aria-hidden="true"></span>` +
|
|
410
|
+
`<span data-option-label>${this.#renderOptionLabel(opt.label ?? opt.value)}</span>`;
|
|
401
411
|
el.addEventListener('click', this.#onOptionClick);
|
|
402
412
|
el.addEventListener('mouseenter', this.#onOptionMouseEnter);
|
|
403
413
|
return el;
|
|
@@ -41,8 +41,12 @@
|
|
|
41
41
|
--combobox-option-py: var(--a-space-1);
|
|
42
42
|
--combobox-option-bg-active: var(--a-ui-bg-hover);
|
|
43
43
|
--combobox-option-fg-active: var(--a-ui-text-hover);
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
/* Selection rides the RADIO indicator, not a row tint (operator ruling
|
|
45
|
+
2026-07-27) — so these DEFAULT to the plain row colors, matching
|
|
46
|
+
select-ui. Both remain consumer-overridable API: set
|
|
47
|
+
--combobox-option-bg-selected to re-enable a tinted selected row. */
|
|
48
|
+
--combobox-option-bg-selected: transparent;
|
|
49
|
+
--combobox-option-fg-selected: var(--a-ui-text-subtle);
|
|
46
50
|
|
|
47
51
|
/* ── Match highlight ── */
|
|
48
52
|
--combobox-match-bg: var(--md-sys-color-primary-container);
|
|
@@ -242,10 +246,54 @@ combobox-ui [data-listbox] [role="option"][data-active] {
|
|
|
242
246
|
}
|
|
243
247
|
|
|
244
248
|
combobox-ui [data-listbox] [role="option"][aria-selected="true"] {
|
|
245
|
-
background: var(--
|
|
246
|
-
color: var(--
|
|
249
|
+
background: var(--combobox-option-bg-selected);
|
|
250
|
+
color: var(--combobox-option-fg-selected);
|
|
247
251
|
font-weight: var(--a-ui-weight);
|
|
248
252
|
}
|
|
253
|
+
/* Hover/active feedback restated on the selected row: the 2-attribute rule
|
|
254
|
+
above outweighs the single-attribute [data-active] rule, so without this a
|
|
255
|
+
selected row would stop responding to keyboard/pointer highlight (the same
|
|
256
|
+
trap select-ui hit when its row tint was removed). */
|
|
257
|
+
combobox-ui [data-listbox] [role="option"][aria-selected="true"][data-active] {
|
|
258
|
+
background: var(--a-ui-bg-hover);
|
|
259
|
+
color: var(--a-ui-text-hover);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/* ── Radio indicator (mirrors select-ui's single-select rows) ── */
|
|
263
|
+
combobox-ui [data-listbox] [role="option"][data-single-option] {
|
|
264
|
+
gap: var(--a-space-2);
|
|
265
|
+
}
|
|
266
|
+
combobox-ui [data-listbox] [role="option"][data-single-option] [data-radio] {
|
|
267
|
+
flex-shrink: 0;
|
|
268
|
+
display: inline-flex;
|
|
269
|
+
align-items: center;
|
|
270
|
+
justify-content: center;
|
|
271
|
+
width: 1rem;
|
|
272
|
+
height: 1rem;
|
|
273
|
+
border: 1px solid var(--a-ui-border);
|
|
274
|
+
border-radius: 50%;
|
|
275
|
+
background: var(--md-sys-color-neutral-surface-bright);
|
|
276
|
+
transition: border-color var(--a-duration-fast) var(--a-easing);
|
|
277
|
+
}
|
|
278
|
+
combobox-ui [data-listbox] [role="option"][data-single-option] [data-radio]::after {
|
|
279
|
+
content: '';
|
|
280
|
+
width: 0.5rem;
|
|
281
|
+
height: 0.5rem;
|
|
282
|
+
border-radius: 50%;
|
|
283
|
+
background: var(--md-sys-color-primary);
|
|
284
|
+
transform: scale(0);
|
|
285
|
+
transition: transform var(--a-duration-fast) var(--a-easing);
|
|
286
|
+
}
|
|
287
|
+
combobox-ui [data-listbox] [role="option"][data-single-option][aria-selected="true"] [data-radio] {
|
|
288
|
+
border-color: var(--md-sys-color-primary);
|
|
289
|
+
}
|
|
290
|
+
combobox-ui [data-listbox] [role="option"][data-single-option][aria-selected="true"] [data-radio]::after {
|
|
291
|
+
transform: scale(1);
|
|
292
|
+
}
|
|
293
|
+
combobox-ui [data-listbox] [role="option"][data-single-option] [data-option-label] {
|
|
294
|
+
flex: 1;
|
|
295
|
+
min-width: 0;
|
|
296
|
+
}
|
|
249
297
|
|
|
250
298
|
combobox-ui [data-listbox] [role="option"][aria-disabled="true"] {
|
|
251
299
|
color: var(--a-ui-text-disabled);
|
|
@@ -247,7 +247,11 @@ tokens:
|
|
|
247
247
|
--combobox-option-fg-active:
|
|
248
248
|
description: Foreground for the currently active option
|
|
249
249
|
--combobox-option-bg-selected:
|
|
250
|
-
description:
|
|
250
|
+
description: >-
|
|
251
|
+
Background for the currently committed option. Defaults to
|
|
252
|
+
`transparent`: selection is shown by the leading radio indicator
|
|
253
|
+
rather than a row tint, matching select-ui. Set this to re-enable a
|
|
254
|
+
tinted selected row.
|
|
251
255
|
--combobox-match-bg:
|
|
252
256
|
description: Background for `<mark>` spans that highlight matched substrings
|
|
253
257
|
requiredIcons:
|
|
@@ -541,8 +541,12 @@ export class UISelect extends UIFormElement {
|
|
|
541
541
|
? new Set(this.value.split(',').map((s) => s.trim()).filter(Boolean))
|
|
542
542
|
: null;
|
|
543
543
|
for (const opt of this.#listbox.querySelectorAll('[role="option"]')) {
|
|
544
|
+
// Action rows have no data-value (they are commands — see the
|
|
545
|
+
// stamping note); null must never compare equal to a value, and
|
|
546
|
+
// `null === ''` is already false, but the explicit guard keeps a
|
|
547
|
+
// future `v ?? ''` normalization from resurrecting the leak.
|
|
544
548
|
const v = opt.getAttribute('data-value');
|
|
545
|
-
const selected = selSet ? selSet.has(v) : v === this.value;
|
|
549
|
+
const selected = v !== null && (selSet ? selSet.has(v) : v === this.value);
|
|
546
550
|
if (selected) opt.setAttribute('aria-selected', 'true');
|
|
547
551
|
else opt.removeAttribute('aria-selected');
|
|
548
552
|
}
|
|
@@ -766,12 +770,19 @@ export class UISelect extends UIFormElement {
|
|
|
766
770
|
}
|
|
767
771
|
const el = document.createElement('div');
|
|
768
772
|
el.setAttribute('role', 'option');
|
|
769
|
-
|
|
773
|
+
// Action rows carry NO [data-value]: they are commands, not values.
|
|
774
|
+
// Stamping `opt.value || ''` gave them data-value="" — and since
|
|
775
|
+
// `this.value` ALSO defaults to '', the aria-selected sync marked
|
|
776
|
+
// every action row selected whenever nothing was committed (the
|
|
777
|
+
// default state of a user menu — reviewer finding, 2026-07-29).
|
|
778
|
+
// With the attribute absent, getAttribute() returns null and no
|
|
779
|
+
// value comparison can ever match.
|
|
780
|
+
if (!opt.action) el.setAttribute('data-value', opt.value || '');
|
|
770
781
|
// §FB-46: [multiple] value is comma-separated — use Set membership.
|
|
771
782
|
const selSet = this.multiple
|
|
772
783
|
? new Set(this.value.split(',').map((s) => s.trim()).filter(Boolean))
|
|
773
784
|
: null;
|
|
774
|
-
const isSelected = selSet ? selSet.has(opt.value) : opt.value === this.value;
|
|
785
|
+
const isSelected = !opt.action && (selSet ? selSet.has(opt.value) : opt.value === this.value);
|
|
775
786
|
// SPEC-040 — multi-select option rows render a leading checkbox
|
|
776
787
|
// indicator (CSS-driven via [data-multi-option]); the `check` icon
|
|
777
788
|
// shows when aria-selected="true".
|