@adia-ai/web-components 0.8.30 → 0.8.32
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 +59 -0
- package/components/accordion/accordion.d.ts +2 -2
- package/components/action-list/action-list.class.js +6 -2
- package/components/action-list/action-list.d.ts +1 -1
- package/components/agent-artifact/agent-artifact.d.ts +1 -1
- package/components/agent-feedback-bar/agent-feedback-bar.d.ts +2 -2
- package/components/agent-questions/agent-questions.d.ts +1 -1
- package/components/agent-reasoning/agent-reasoning.d.ts +3 -3
- package/components/agent-suggestions/agent-suggestions.d.ts +1 -1
- package/components/agent-trace/agent-trace.d.ts +1 -1
- package/components/alert/alert.d.ts +2 -2
- package/components/anchor-bar/anchor-bar.d.ts +2 -2
- package/components/avatar/avatar.class.js +0 -1
- package/components/avatar/avatar.css +16 -2
- package/components/badge/badge.css +30 -2
- package/components/breadcrumb/breadcrumb.css +15 -3
- package/components/button/button.d.ts +1 -1
- package/components/calendar-grid/calendar-grid.d.ts +2 -2
- package/components/card/card.css +21 -0
- package/components/card/card.yaml +9 -0
- package/components/chart/chart.d.ts +4 -4
- package/components/chart-legend/chart-legend.d.ts +1 -1
- package/components/chat-thread/chat-input.css +1 -1
- package/components/chat-thread/chat-input.js +9 -1
- package/components/chat-thread/chat-thread.class.js +6 -0
- package/components/chat-thread/chat-thread.d.ts +2 -2
- package/components/check/check.css +3 -3
- package/components/command/command.d.ts +2 -2
- package/components/context-menu/context-menu.d.ts +3 -3
- package/components/datetime-picker/datetime-picker.d.ts +5 -5
- package/components/demo-toggle/demo-toggle.d.ts +1 -1
- package/components/drawer/drawer.d.ts +2 -2
- package/components/heatmap/heatmap.d.ts +5 -5
- package/components/index.js +1 -0
- package/components/inline-edit/inline-edit.d.ts +4 -4
- package/components/input/input.a2ui.json +4 -1
- package/components/input/input.css +18 -0
- package/components/input/input.yaml +11 -1
- package/components/integration-card/integration-card.d.ts +4 -4
- package/components/link/link.d.ts +1 -1
- package/components/list/list-item.a2ui.json +6 -0
- package/components/list/list-item.yaml +13 -0
- package/components/list/list.class.js +28 -7
- package/components/list/list.d.ts +1 -1
- package/components/list-window/list-window.d.ts +5 -5
- package/components/menu/menu.css +2 -2
- package/components/menu/menu.d.ts +1 -1
- package/components/modal/modal.d.ts +1 -1
- package/components/nav/nav.css +8 -0
- package/components/nav/nav.d.ts +1 -1
- package/components/nav-group/nav-group.css +58 -9
- package/components/nav-group/nav-group.d.ts +1 -1
- package/components/nav-item/nav-item.css +7 -4
- package/components/nav-item/nav-item.d.ts +1 -1
- package/components/noodles/noodles.d.ts +3 -3
- package/components/option-card/option-card.css +1 -1
- package/components/pagination/pagination.d.ts +1 -1
- package/components/pane/pane.d.ts +1 -1
- package/components/password-strength/password-strength.d.ts +1 -1
- package/components/pipeline-status/pipeline-status.a2ui.json +14 -8
- package/components/pipeline-status/pipeline-status.d.ts +5 -4
- package/components/pipeline-status/pipeline-status.yaml +17 -11
- package/components/radio/radio.a2ui.json +4 -3
- package/components/radio/radio.css +2 -2
- package/components/radio/radio.yaml +7 -14
- package/components/radio-group/radio-group.a2ui.json +118 -0
- package/components/radio-group/radio-group.class.js +75 -0
- package/components/radio-group/radio-group.css +31 -0
- package/components/radio-group/radio-group.d.ts +20 -0
- package/components/radio-group/radio-group.examples.md +37 -0
- package/components/radio-group/radio-group.js +17 -0
- package/components/radio-group/radio-group.yaml +114 -0
- package/components/segmented/segmented.a2ui.json +5 -0
- package/components/segmented/segmented.class.js +20 -2
- package/components/segmented/segmented.yaml +5 -0
- package/components/stat/stat.css +12 -8
- package/components/stat/stat.js +66 -1
- package/components/stat/stat.yaml +3 -0
- package/components/stepper/stepper-item.a2ui.json +5 -0
- package/components/stepper/stepper-item.yaml +11 -0
- package/components/stepper/stepper.a2ui.json +38 -3
- package/components/stepper/stepper.class.js +199 -2
- package/components/stepper/stepper.css +30 -0
- package/components/stepper/stepper.d.ts +28 -1
- package/components/stepper/stepper.yaml +76 -2
- package/components/stream/stream.d.ts +3 -3
- package/components/swatch/swatch.class.js +1 -1
- package/components/swatch/swatch.d.ts +1 -1
- package/components/swiper/swiper.d.ts +3 -3
- package/components/table/table.a2ui.json +2 -2
- package/components/table/table.d.ts +7 -7
- package/components/table/table.yaml +9 -1
- package/components/table-toolbar/table-toolbar.d.ts +4 -4
- package/components/tabs/tabs.css +11 -3
- package/components/tabs/tabs.d.ts +1 -1
- package/components/tag/tag.a2ui.json +1 -1
- package/components/tag/tag.css +78 -13
- package/components/tag/tag.d.ts +12 -8
- package/components/tag/tag.examples.md +4 -4
- package/components/tag/tag.yaml +18 -7
- package/components/tags-input/tags-input.d.ts +4 -4
- package/components/timeline/timeline.d.ts +1 -1
- package/components/toc/toc.d.ts +1 -1
- package/components/toggle-group/toggle-group.d.ts +1 -1
- package/components/toggle-scheme/toggle-scheme.d.ts +1 -1
- package/components/tour/tour.d.ts +4 -4
- package/components/tree/tree.d.ts +1 -1
- package/core/icons-phosphor.js +129 -71
- package/core/provider.js +84 -23
- 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 +89 -87
- package/dist/web-components.sheet.js +1 -1
- package/package.json +1 -1
- package/styles/api/sizing-fallback.css +32 -0
- package/styles/api/sizing.css +7 -3
- package/styles/components.css +1 -0
- package/styles/tokens.css +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,64 @@
|
|
|
1
1
|
# Changelog — @adia-ai/web-components
|
|
2
2
|
|
|
3
|
+
## [0.8.32] — 2026-08-09
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **`router-ui` wires the shipped `view-transition` trait into every route/content swap it performs (gh#815 slice 1).** The trait shipped with zero consumers; `router-ui` is the shell-semantic swap site every consumer (site's docs router, `admin-shell`'s documented content pattern, embedded-app's tab routers) ultimately routes through. Both of its swap paths now ride `this.startTransition(callback)`: the fetched-content path (`#loadContent`'s `innerHTML` replace) and a new `setRoutePath(path)` method for content-less consumers whose own CSS reveals children via `router-ui[data-route-path="…"]` selectors (previously those callers hand-rolled a bare `setAttribute`). Sensible defaults (`--a-duration`/`--a-easing` from `foundation/motion.css`) apply only when a consumer hasn't set its own `data-view-transition-duration`/`-easing`. No reduced-motion or no-API fallback reimplemented here — both are owned by `view-transition.js` and simply inherited. Verified live (Playwright, CDP screencast frame capture — real rasterized pixels, not a DOM proxy) against a throwaway `admin-shell` + `router-ui` fixture: the wired swap cross-fades over ~150–200ms with zero blank/background frames in every captured frame, versus a hard single-frame cut with the raw `setAttribute` path (negative control); the configured `--vt-duration` measured live as `250ms` (the `--a-duration` default) via `getComputedStyle()`.
|
|
8
|
+
PR #819 review (CodeRabbit): a real `document.startViewTransition()` callback runs asynchronously — `startTransition()` returns before it fires — so `#loadContent` now re-checks the nav token INSIDE the callback (never paints a superseded route) and awaits `transition.updateCallbackDone` before importing the route's setup module or emitting `route-loaded` (so setup code runs against the actually-painted DOM, not the previous route's). Verified with a rapid-fire triple-navigate against the real embedded-app (Labs → History → Briefing, no waits between clicks): exactly one panel visible at the end, matching the last click.
|
|
9
|
+
- **New `radio-group-ui` primitive — programmatic question↔group association for radio-ui sets (gh#729).** The canonical questionnaire shape (`radio.yaml`'s own example, shipped in PR #726) rendered a question `Text` plus bare `radio-ui` options sharing `[name=]` with no accessible link between them — no `radiogroup` role, no `aria-labelledby` — because `radio-ui` already supports ancestor `fieldset`/`[role="radiogroup"]` grouping (`radio.class.js:48`) but a plain layout container (`Column`) can't carry a role. Audited existing primitives first (`toggle-group-ui`/`toggle-option-ui`, `segmented-ui`/`segment-ui` — the established group-wrapper + bare-option-child pattern) before building: a composite primitive is the right semantic level, not a new wire-level `role` prop on a general container (which would also cut against the reserved-keys discipline from gh#653/PR #684). `radio-group-ui`'s own `[label]` prop is the group's accessible name — mints a label element, sets `role="radiogroup"` + `aria-labelledby` on the host; exclusivity and arrow-key navigation are unchanged, since `radio-ui`'s existing `closest('fieldset, [role="radiogroup"]')` lookup now resolves to the wrapper instead of falling through to `this.parentElement`. `radio.yaml`'s canonical example and a2ui rules updated to the new shape; registered in `packages/a2ui/runtime/registry.js` and `packages/web-components/components/index.js`/`styles/components.css` barrels.
|
|
10
|
+
- **`stepper-ui` gains an opt-in `interactive` mode — click-to-navigate + roving-tabindex keyboard nav** (gh#711 S1). Primitive audit (per `adia-author`'s mandatory §0 gate) found `stepper-ui` already covers the mock's labeled 3-state (complete/current/upcoming) progression shape — the operator ruling's premise that "none exists among ~126" didn't hold; a duplicate primitive would have violated the audit-before-authoring rule. Extended the existing primitive instead: `[interactive]` (absent by default — a bare stepper stays a read-only progress display) adds `role="button"` + roving `tabindex` + `aria-current="step"` to `<stepper-item-ui>` children, wires click + keyboard (arrow keys / Home / End / Enter / Space, orientation-aware), and fires a `change` event with `{ step }` — the stepper never drives navigation itself, the host reacts. Demo: new "Click-to-navigate" section in `stepper.examples.html`.
|
|
11
|
+
- **`stepper-ui` gains a pre-mutation veto + per-item `disabled` — the two host-controlled gates a review of the above pass asked for** (gh#711 S1). Before `#select()` mutates `step`, a new cancelable `step-request` event fires with `{ step, previous }`; `preventDefault()` vetoes the navigation — no mutation, no `change`, focus/roving-tabindex untouched. A host that never listens for it keeps the exact pre-veto uncontrolled behavior (`change` still fires unconditionally after every landed navigation) — additive, not a breaking change. Separately, `stepper-item-ui` gains a reflected `disabled` boolean (S2's phase-gating mechanism): a disabled step is skipped in the roving tabindex/arrow order, unclickable, and carries `aria-disabled`, independent of its `status`. Demo: new "Veto + phase-gating (disabled)" section showing both gates together — a conditional veto (block skip-ahead jumps) and an unconditional gate (`disabled` lifted once a phase is reached).
|
|
12
|
+
- **`scripts/build/dts-codegen.mjs` emits event-specific `addEventListener` overloads BEFORE the generic `HTMLElementEventMap` fallback**, not after (same review pass). TS overload resolution picks the first matching signature; several custom event names (`change` chief among them) are also literal keys of the built-in `HTMLElementEventMap`, so the generic overload — listed first — silently shadowed every typed custom-event overload it preceded, making them dead code no caller could actually resolve to. Regenerating with the reordered codegen touched every `.d.ts` with a custom `events:` block (58 files across `web-components` + `web-modules`) — reordering only, no type or behavior change, confirmed by a clean `npm run typecheck`.
|
|
13
|
+
|
|
14
|
+
### ⚠ BEHAVIOR CHANGE
|
|
15
|
+
|
|
16
|
+
- **`tag-ui`'s default render is now the `solid` look — bare `<tag-ui text="…">` is a stamped, high-contrast pill, not quiet chrome (gh#794).** Family variants (`accent`/`info`/`success`/`warning`/`danger`) already rendered solid unconditionally; the family-less `default` variant was the one holdout, requiring an explicit `tone="solid"` to get the inverse-stamp look. That asymmetry is gone — `:scope[variant="default"]` now carries the same `--md-sys-color-neutral-on-surface` bg / `--md-sys-color-neutral-background` fg pair the family variants use, so a bare tag with zero attributes renders solid by default. The pre-#794 quiet-chrome look (`--a-bg-muted` bg / neutral-on-surface fg) is still fully reachable — set `tone="muted"` explicitly (mirrors the existing muted opt-out family variants already had). `tone="solid"` remains valid and is now a no-op restatement of the default for the `default` variant.
|
|
17
|
+
**Blast radius** — every in-repo bare `<tag-ui>` call site (no `tone=`) was audited (`apps/`, `site/`, `catalog/`, `playgrounds/`, `packages/`). Family-variant call sites (`variant="info"` etc.) are unaffected — they were already solid. For the family-less `default` variant, four surfaces needed an explicit `tone="muted"` to keep their existing quiet register (citation/footnote strips and dense per-card category labels, where a wall of solid pills reads as noise — gh#778's oversized-pill concern): the gen-ui-feed agent-citations chips (`apps/genui/app/gen-ui-feed/gen-ui-feed.contents.html`), the `v050-agent-blocks` "Sources:" citation chips (`catalog/ui-patterns/v050-agent-blocks/v050-agent-blocks.contents.html`), the kanban-board-3col category tags (`catalog/ui-patterns/app/kanban-board-3col/kanban-board-3col.contents.html`), and the `a2ui-pattern-library` stage-name reference chips (`site/pages/patterns/a2ui-pattern-library.html`). Every other bare-tag surface (patient-visit header chip, registration source/department chips, saas-admin filter-bar removable chips, the embedded-app coding-card status tags, and every component/pattern `.examples.html` demo page showing the primitive's own default) was judged fine with the new solid default — those are single prominent chips or filter/status pills where the chip-IS-the-state look is the intent, not dense chip walls. `tag.yaml`'s `[tone]` prop docs + a2ui rules updated with the new default + the `tone="muted"` escape hatch; `tag.examples.html` demo updated to match.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- **`avatar-ui`'s icon variant renders off-center when the avatar itself is placed in a container's `slot="icon"` (gh#809).** Light DOM has no shadow boundary and this repo's Light-DOM stance treats `slot=` as decorative metadata (positioning by tag + ancestor + DOM order, AGENTS.md) — `avatar-ui`'s own internal icon child (auto-created from the `icon` attribute) was marked `slot="icon"` for avatar's own CSS, sharing that attribute value with the very common consumer idiom of placing `<avatar-ui slot="icon">` inside a container that also targets `[slot="icon"]` for its own layout (e.g. `list-item-ui`'s icon-column rule, `admin-dashboard`'s Notifications rows being the reported instance). `list-item.css`'s `:scope [slot="icon"] { margin-inline-end: … }` — meant for the outer avatar — leaked onto the nested `icon-ui` too, pushing the flex-centered glyph 6px left of center at `size="sm"` (measured live via Playwright: `deltaX -6px`, initials/image variants unaffected). Fixed at the root rather than with a specificity arms race: the inner `icon-ui` no longer carries `slot="icon"` at all (`avatar.class.js`), and `avatar.css` selects it by tag (`:scope > icon-ui`) — with no shared attribute to hang off, the ancestor collision is structurally impossible rather than merely out-specificity'd. Verified live post-fix at sm/md/lg × circle/square, plus the reported admin-dashboard Notifications tab directly: all icon-variant deltas ≤0.008px (initials/image variants re-measured unchanged).
|
|
22
|
+
- **`stat-ui`'s trend delta renders a real Phosphor arrow icon instead of a CSS `::before` unicode triangle (gh#805).** `stat.css` baked `content: "\25B2"`/`"\25BC"` directly into the `[slot="change"]::before` pseudo-element — a raw glyph outside the icon system entirely (wrong stroke style, no size/weight ladder, invisible to `check-required-icons`). Fixed at the anatomy level, matching the precedent `table-ui`'s sort-direction icon already set (a class-rendered `icon-ui` child, not a CSS-only mask): `stat.js`'s `connected()` now builds a `icon-ui[data-trend-icon]` + text-span pair inside `[slot="change"]` (replacing the old bare-text write), and `render()` sets `name="arrow-up"`/`"arrow-down"` per `[trend]`, hidden for `neutral`. No `slot="icon"` attribute on the new child — that value is reserved for stat's own outer icon region, and an ancestor's `[slot="icon"]` descendant selector would otherwise leak margin/positioning onto it (the gh#809/PR#810 collision shape); spacing rides `[slot="change"]`'s existing flex `gap`, not margin, for the same reason. Color comes for free via `currentColor` (icon.css's default) inheriting the existing `--stat-up-fg`/`--stat-down-fg` rules — zero new raw colors. Sizing: `--a-icon-size: 1em` on the new child, scoped to the `--stat-change-size` font size. `stat.yaml`/`stat.js` gain a `requiredIcons: [arrow-up, arrow-down]` declaration (`check:required-icons` gate). The generated icon+text pair OWNS `[slot="change"]`'s content — same overwrite contract `#valueEl`/`#labelEl` already apply — so `render()` normalizes to exactly `[icon, text]` whenever a consumer-authored slot, an orphaned loading skeleton, or any other deviation is found, instead of appending after it (CodeRabbit review on PR #813: the original version could double-render the delta or leave stale skeleton markup after a disconnect/reconnect cycle). Verified live (headless Playwright, dev server): `stat.html`'s own demo matrix confirms both `arrow-up`/`arrow-down` resolve to a real `<svg>`, change-row height measures 13px identically before/after the fix (no layout shift), the admin-dashboard KPI row (the reported surface) renders proper Phosphor arrows in place of the triangles, and a consumer-populated `[slot="change"]` plus a loading→reconnect cycle both normalize to exactly `[icon, text]` with no duplicate or stale nodes.
|
|
23
|
+
- **`nav-group-ui`'s selected-header background no longer double-marks the selection in the expanded rail (gh#806, PR #797/gh#795 regression).** gh#795's fix added `background: var(--nav-group-bg-selected)` to both `:scope[selected] > [slot="header"]` and `:scope[data-selected-within] > [slot="header"]` unconditionally — correct for the collapsed rail (children `display:none`, the header is the sole selection signal) but wrong expanded: the selected CHILD already paints its own `nav-item-ui[selected]` pill, so the header background painted a second, redundant highlight above it (operator screenshot, `/site/examples/profile-security`, ACCOUNT group header over an already-highlighted Profile & Security child). The background declaration now lives ONLY inside the two collapsed-rail contexts the file already uses — `nav-ui[collapsed] &` and `@container sidebar (max-width: 96px)` (admin-sidebar's own collapse path drives the former; both are covered since the file's other collapsed rules already branch on both) — while the `color` declaration stays unscoped, applying in both modes as before (subtle text-weight change, no double-marking risk). Ruling on a directly-`[selected]` group header in expanded mode: NO background there either — `nav-group.class.js`'s own doc comment on the `selected` prop defines it as "marks the group that CONTAINS the selected route," the same semantic `data-selected-within` carries (JS-derived vs. consumer-set, not a distinct "IS the destination" leaf state) — so both attributes get identical scoped treatment, one selected-state grammar. Verified live (dev server + headless Playwright, `/site/components/admin-shell` and `/site/examples/profile-security`): expanded — header `background-color` is `rgba(0,0,0,0)` for both `[selected]`-direct and `[data-selected-within]` groups while the selected child's pill remains `oklch(0.598 0.0316 225.06 / 0.2)`; collapsed — gh#795's fix is intact and unregressed, header `background-color`/`border-radius` still measure identical to the selected item's (`oklch(0.598 0.0316 225.06 / 0.2)` / `9.324px` both rows, both attribute paths).
|
|
24
|
+
- **`nav-group-ui`'s selected header gets a real container background, matching `nav-item-ui` (gh#795).** `nav-group.css`'s `:scope[selected] > [slot="header"]` rule set only `color` — no `background` declaration existed anywhere in the file (confirmed by grep: zero `--nav-group-bg-selected` hits pre-fix), while `nav-item-ui[selected]` (`nav-item.css:15-18`) paints `background: var(--nav-item-bg-selected)` alongside `color`. The selected group header sat on a transparent fill in BOTH expanded and collapsed rails — not a collapsed-only gap; the collapsed square-footprint contract (`admin-shell.collapsed.css`'s `min-width`/`min-height` floor + the shared `border-radius`) already supplied the box shape, it just had nothing painted into it. New `--nav-group-bg-selected` token (mirrors `--nav-item-bg-selected` → `var(--a-bg-selected)`) and a `background` declaration now apply to both `:scope[selected] > [slot="header"]` and `:scope[data-selected-within] > [slot="header"]` (the group-contains-the-selected-route case, `#syncHeaderWeight`, gh#501) — one selected-state grammar for both row types, matching `nav-item-ui`'s existing unscoped `[selected]` rule that already applies identically in expanded and collapsed contexts. The icon-fill chain (gh#459/#501/#522) was untouched — verified intact in the investigation (`weight="fill"` + correct color already landed; the "outline" look was a contrast illusion from the missing background, not a wiring gap). Verified live (dev server + Playwright): selected `nav-item-ui` and selected `nav-group-ui[slot="header"]` compute the identical `background-color`/`border-radius` in both expanded and collapsed modes; screenshots confirm the group header now reads as a filled selection pill, same as the item rows beside it.
|
|
25
|
+
- **`card-ui > section[bleed] > table-ui` now drops the table's chrome automatically, closing a 14-instance repo-wide bug (gh#796).** `table.yaml`'s own `raw`/`frameless` docs already named `<card-ui><section bleed>` as the canonical case for embedding a table inside a card's own frame, but nothing made it automatic — a card-anatomy investigation (gh#796 findings) found 14 instances across 10 files (`admin-dashboard`, `members`, `billing`, `gen-ui-feed`, four `catalog/ui-patterns` demos, `table-toolbar` playground) carrying neither attribute, plus one dead inline-style workaround (`type-scale-review.examples.html`) that hand-rolled `border`/`border-radius` but missed `box-shadow` entirely. `card.css` now mirrors the existing `chart-ui` bleed auto-grow precedent: `& > section[bleed] > table-ui:not([raw]):not([frameless])` resets background/box-shadow/border-radius to match `[raw]`'s own reset — no markup edit needed on any of the 14 sites. Scoped to `[bleed]` sections only (not every card section): a plain `<section>` keeps its own padding/margin, so a nested table's frame there reads as an intentional nested surface (e.g. `table.examples.html`'s own demo cards), not a doubled edge — confirmed live via headless Playwright against an isolated fixture (standalone table keeps its 3-sided perimeter; `section[bleed]` table goes chromeless; plain-`section` table keeps chrome) and against the admin-dashboard audit-log card. An explicit `[raw]`/`[frameless]` on the table instance still applies and is honored. `card.yaml`/`table.yaml` cross-reference the new automatic behavior. `scripts/audit/audit-card-structure.mjs` (part of `npm run check`) gained a regression net that fails if the CSS rule is ever edited away or narrowed.
|
|
26
|
+
- **`api/sizing.css`'s bare `[size]` font-size fallback no longer out-competes size-aware components' own `font-size` (gh#790).** A layer-order defect, not a specificity one: the fallback shipped bundled into `sizing.css`'s `@import … layer(utilities)`, and ADR-0038's `utilities` layer beats `components` by layer order regardless of selector specificity, so no `:where()`/`:not()` wrap inside `sizing.css` could ever have fixed it — same bug class as gh#651/gh#766 (an unlayered/later-layer rule beating a layered/earlier-layer one unconditionally; restated in ADR-0038's own `resets.css` `ul/ol[role="list"]` lesson). Split the single `[size] { font-size: var(--a-body-size); }` declaration into new `styles/api/sizing-fallback.css`, imported `layer(elements)` — one layer BELOW `components`, the same relationship `type/elements.css`'s `:where(h1)` etc. already have to component/`[variant]` rules — so `size="md"` (the documented default) now renders identically to no attribute on every size-aware component, while `sizing.css`'s `[size="sm"/"lg"]` token-setting rules stay in `utilities` unchanged. Verified live (Playwright, new `scripts/qa/sizing-fallback-probe.mjs`) on `button-ui`, `kbd-ui`, `tag-ui`, `badge-ui`, `input-ui`, `nav-item-ui`: no-attr-vs-`size="md"` computed `font-size`/padding now match on all six (was a 13px→14px flip on `button-ui`/`kbd-ui`/`nav-item-ui`; `tag-ui`/`badge-ui` were already unaffected); `sm`/`lg` still differ from `md` wherever a component implements a size ladder in `font-size` (`tag-ui`/`badge-ui` don't — a pre-existing, unrelated gh#789 design choice, not touched by this fix).
|
|
27
|
+
- **`icons-phosphor.js`'s zero-config auto-install resolves `@phosphor-icons/core` under npm/yarn-hoisted layouts, not just pnpm's nested one — and a genuine zero-icon failure now fails loudly while a working manual setup gets silence (gh#786, third strike after gh#287/gh#340).** The single entry-relative glob `../node_modules/@phosphor-icons/core/…` only holds under pnpm (which populates a package's own local `node_modules`); under npm/yarn-classic workspace hoisting the transitive dep lands as a *sibling* in the shared `node_modules`, so the glob was structurally guaranteed to match zero files and printed an alarming failure-shaped warning on every dev-server load — with no consumer config able to prevent it (`optimizeDeps.exclude`/`server.fs.allow` address different failure modes; path arithmetic isn't configurable). Fixed at the semantic level with a candidate-glob ladder — three static globs probed in order, first non-empty installed: `../node_modules/…` (pnpm-nested, gh#287's fix, pnpm consumers unaffected), `../../../@phosphor-icons/core/…` (npm/yarn-hoisted consumer install, gh#786's layout), `../../../node_modules/…` (npm-shaped monorepo checkout, gh#340's layout); losing candidates transform to `{}` at build time, zero runtime cost. The all-zero case no longer installs empty maps (which marked the registry "ready" and muted `<icon-ui>`'s own unwired diagnostic): the verdict defers one tick — if the consumer's manual registration wired the registry (the reporting consumer's setup) nothing prints at all; if the registry is genuinely unwired, one `console.error` names the three probed layouts and the three remedies. Proven by `scripts/qa/phosphor-glob-layout-probe.mjs` (real `vite build --ssr` against five scaffolded layouts: pnpm-nested, npm-hoisted, repo-npm-shaped, phosphor-missing negative control, phosphor-missing + manual registration) plus `core/icons-phosphor.test.js` (vitest, real glob transform against the live checkout layout).
|
|
28
|
+
- **`tag-ui`/`badge-ui` `size="sm"|"lg"` now actually resizes the pill (gh#779).** Both yamls have long documented `size` as riding "the universal `[size]` attribute system" (the same mechanism `button-ui` reads — `button.css:81-84`), but `tag.css`/`badge.css` hardcoded `--tag-px`/`--tag-py`/`--tag-font` and `--badge-px`/`--badge-py`/`--badge-font-size` with zero `[size]` selectors — `size="sm"` and `size="lg"` rendered pixel-identical to the default. No comment, ADR, or yaml note found for the inertness being deliberate, so the prop was wired rather than removed. NOT a direct alias of `--a-ui-px`/`--a-ui-size` (the tokens `button-ui` reads) — tag/badge's own `md` baseline predates and differs from that system's `md` (6px/12px here vs 8px/13px there); aliasing directly would have inflated every existing default tag/badge, the exact failure mode gh#778 independently reported. Instead each component keeps its own `md` token values unchanged (verified pixel-identical: `--tag-px`/`--badge-px` still resolve to `--a-space-1-5` = 6px @ d=1, font still `--a-ui-sm` = 12px) and gets a component-owned `sm`/`lg` ladder (`--a-space-1`/`--a-space-2` for padding, `--a-ui-xs`/`--a-ui-md` for font-size) applied only under explicit `[size="sm"]`/`[size="lg"]` — no attribute, no change. 8 new tests (`tag.test.js`, new `badge.test.js`) assert the CSS source shape (happy-dom doesn't evaluate `@scope` via `getComputedStyle()` — same constraint documented in `tag.test.js`'s header); confirmed live via headless Playwright against the dev server.
|
|
29
|
+
- **`input-ui` no longer collapses to near-zero width as an empty, placeholder-only flex item (gh#781).** The host is `display: block`, which gives it no width once it becomes a flex-row child (`row-ui` or any `display:flex` parent — a block-level child's "fill available width" behavior doesn't apply once its parent is flex); `[slot="field"]` has no intrinsic width of its own; and the one child that could drive content-based sizing, the empty contenteditable `[slot="text"]`, renders its placeholder via a deliberately out-of-flow `::before` (load-bearing for caret positioning — unchanged) that contributes zero intrinsic width. New `--input-min-width: 20ch` token (mirrors the native `<input>`'s historic intrinsic-width baseline) floors `:scope`'s `min-width`, so an empty `<input-ui placeholder="…">` stays legible in a flex row with no explicit sizing — confirmed live (dev server + Playwright): an unsized instance measures ~176px (20ch at the default font/scale) instead of near-zero, while a `flex: 1` sibling still grows past the floor to fill the row and an explicit `width` above the floor still constrains normally (`--input-min-width` is a floor, not a fixed size — a consumer wanting narrower than 20ch overrides `--input-min-width` down, same model as `step-progress-ui`'s existing `--step-progress-min-width`). Documented in `input.yaml`'s top-level description + `tokens` block; `input.examples.html` gains a flex-row demo artifact + CSS-tokens table row.
|
|
30
|
+
- **`breadcrumb-ui`'s current crumb ellipsizes instead of hard-clipping mid-word (gh#780).** `[data-item]`'s truncation quartet (`overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: var(--breadcrumb-max-width)`) lived alongside `display: inline-flex` — per the CSS Overflow spec, `text-overflow: ellipsis` never paints on a flex container's own box, so `overflow: hidden` still clipped long labels but the `…` glyph never rendered. Item content is a bare text node (or an `icon-ui` child for icon-leading crumbs) directly inside `[data-item]`, not a nested wrapper — rather than restructure the light-DOM child shape, `[data-item]` now defaults to `display: inline-block` (where ellipsis is spec-defined to work) and opts back into `inline-flex` only when an `icon-ui` child is actually present, via `[data-item]:has(> icon-ui)`. Text-only crumbs get the ellipsis fix; icon-leading crumbs keep their flex baseline-alignment unchanged. Verified live against the dev server (headless Playwright): a label exceeding `--breadcrumb-max-width` (12rem) now measures `scrollWidth > clientWidth` with `text-overflow: ellipsis` computed and the `…` glyph visible in a screenshot; icon-leading and overflow-popover examples unaffected.
|
|
31
|
+
- **`nav-group-ui`'s collapsed-rail header is a true square again, matching `nav-item-ui` (gh#767).** Two independent defects, both traced to a live probe on `/site/patterns/admin-sidebar` (`demo-nav-3`, collapsed to a 48px rail): (1) `nav-group.css`'s collapsed-mode `align-self: stretch` (two copies — `nav-ui[collapsed] &` and the duplicate inside `@container sidebar (max-width: 96px)`) fought `admin-shell.collapsed.css`'s FEEDBACK-39 square-footprint contract (`align-items: center` at the `nav-ui` level + a `min-width`/`min-height` floor), stretching the group header to the full rail width (measured 64×30) instead of the 30×30 square `nav-item-ui` already got. Both rules were dead code in expanded mode (redundant with the ambient flex-column `align-items: stretch` default) and actively harmful in collapsed mode under admin-shell — removed; the header now falls through to whatever `align-items` the ambient `nav-ui` sets, same as `nav-item-ui` always did. (2) `nav-item.css` pinned collapsed icon size to a literal `18px`; `nav-group.css` referenced `--nav-group-icon-font-size`, a token defined nowhere in the codebase — guaranteed-invalid per the CSS custom-properties spec, so it silently fell back to `icon.css`'s ambient-font-size fallback instead of a deterministic size, drifting with `[scale]`/`[verse]` tiers. Both now consume one shared `--nav-icon-size-collapsed` token (defined in `nav.css`, the nav family's shared-token home, alongside `--nav-width-collapsed` et al.) in their respective `@container sidebar (max-width: 96px)` blocks. The identical dead-token defect in each component's unconditional (non-collapsed) icon-size rule — `nav-item.css`'s own `--nav-item-icon-font-size` reference — was removed too (same guaranteed-invalid shape, same fallback result, now explicit instead of accidental). Verified via a headless probe against the real dev preview: collapsed `nav-group-ui [slot="header"]` measures 30×30 (was 64×30), both icons compute `--a-icon-size: 18px`; expanded mode unchanged (both rows still stretch full-width; icon sizes still ride the ambient-font fallback identically to before).
|
|
32
|
+
- **`list-item-ui`/`list-ui`/`action-item-ui`/`action-list-ui` adopt a consumer-authored `role` instead of stamping over it (gh#753).** `connected()` on all four set `role` unconditionally — a consumer-authored `role="button"` on a clickable row (the embedded-app population-health views' `cohort.js`/`alert-queue.js`/`followup-debt.js`/`visit-grid.js`, which all author `<list-item-ui role="button">` rows) was silently overwritten to `listitem`/`list` on upgrade. Each now stamps only when `role` is absent (`if (!this.hasAttribute('role')) …`); `disconnected()` never touched `role` on any of the four, so the adopted role already survived disconnect/reconnect with no extra change needed. Estate sweep (`grep setAttribute('role'` across `connected()`) found ~70 other unconditional role stamps; only these four share the exact clobber shape (a generic collection-item/list role on a light-DOM host whose internal query logic is tag-based, not role-based, so overriding the role can't break the primitive's own behavior). The rest stamp a role that IS the primitive's own ARIA-widget contract (e.g. `tabs-ui`'s `tablist`, `tree-ui`'s `tree`, `radio-ui`'s `radio`, `nav-item-ui`'s `link`) — overriding those would break the widget pattern itself, so they're intentionally left unconditional. 8 new tests (`list.test.js`, new `action-list.test.js`, + a `cohort.js` DOM regression test in `apps/embedded-app`).
|
|
33
|
+
- **A non-selectable `list-ui` no longer strips consumer-set `tabindex` off its rows** (gh#746). The `!selectable` branch of `render()` cleared `aria-selected`/`tabindex` from every child unconditionally — cleanup meant for a `selectable` toggle-off, but it also clobbered focusability a CONSUMER manages on its own rows (the patient-visit rail's roving tabindex was silently stripped on the list's connect render). The list now tracks whether it stamped selection chrome and only clears what it stamped; toggling `selectable` off still cleans up exactly as before. 2 new tests.
|
|
34
|
+
- **`stepper-ui` reflects a baked `[disabled]` on first paint and re-renders on a child `[disabled]` flip, with no host poke needed** (gh#736, found in PR #735's review). Two related gaps: (1) during static innerHTML parsing, `stepper-ui`'s own first `render()` can fire while its `stepper-item-ui` children aren't appended yet (confirmed: `render()` sees zero items on that pass) — the parent's `render()` now falls back to `item.hasAttribute('disabled')` alongside the property read, and a scoped `MutationObserver` (childList + subtree, `attributeFilter: ['disabled']` — the `nav-group-ui` gh#501 idiom) re-renders once children actually land, so the baked state is correct before any real paint (microtasks always drain first). (2) a child's `[disabled]` flip after mount now re-renders the parent automatically via the same observer — hosts no longer poke `stepper.render()` manually. Removed the now-unnecessary post-boot `stepper.render()` poke from `apps/embedded-app`'s `adia-embed-labs.js` (marked with the upgrade-race comment from PR #735) — the app works without it, proven by its own vitest suite plus 2 new stepper tests (26 total, was 24).
|
|
35
|
+
- **`chat-input-ui` composer no longer shows a resting border alongside the focus ring when hovered while focused (gh#718).** The Safari-17.x hover workaround (`chat-input-ui:not([disabled]):hover`, moved outside `@scope` for engine compat) picked up an extra type-selector specificity point the scoped `:focus-within` override didn't have — `(0,2,1)` vs `(0,2,0)` — so hover's `border-color` silently beat focus's `border-color: transparent` whenever both states were true (the common case: click into the textarea leaves the pointer sitting on it). Fixed at the contract level by excluding the focused state explicitly: `chat-input-ui:not([disabled]):hover:not(:focus-within)`, rather than relying on a specificity race.
|
|
36
|
+
Swept every component carrying the same Safari-workaround comment (23 files) for the identical latent gap: found and fixed 3 more instances of the same shape — `option-card-ui:not([checked]):not([disabled]):hover`, `radio-ui:not([disabled]):hover [slot="dot"]` (plus its `[checked]` variant), and `check-ui:not([disabled]):hover [slot="box"]` (plus its `[checked]`/`[indeterminate]` variant) — all now exclude `:focus-visible`. Also found and fixed one hover-vs-focus-visible custom-property race in `menu-item-ui[variant="danger"]:hover`, where the `[variant="danger"]` attribute selector tied the hover rule's specificity with `:scope:focus-visible`'s and won the tie-break on type-selector count. `input-ui`/`textarea-ui`'s nested-control pattern was confirmed safe (their hover selector targets a descendant slot with fewer class-selectors than the focus rule, so focus always wins) — verified computationally per file, not assumed from the shared comment.
|
|
37
|
+
|
|
38
|
+
### Maintenance
|
|
39
|
+
|
|
40
|
+
- **`dist/` bundles rebuilt** in this cut's window (6 file(s)) — regenerated from the source changes described above, not independent edits.
|
|
41
|
+
|
|
42
|
+
## [0.8.31] — 2026-08-07
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
- **tabs-ui vertical: contents regain clearance from the active-indicator line** (gh#707). gh#705 moved tab spacing from button padding to the strip gap — right for horizontal (the indicator rides the bottom edge), but vertical's indicator rides the inline-start edge, where that padding WAS the separation. Vertical tab-buttons now carry `padding-inline-start: var(--a-space-3)`; horizontal stays padding-free.
|
|
46
|
+
- **`tabs-ui` strip gap owns the button spacing, not `tab-button` padding (gh#705).** `[slot="tab-button"]` carried `padding-inline: var(--tabs-button-px)` (8px); the horizontal padding moves to the strip: `--tabs-gap` goes from `--a-space-1` (4px) to `--a-space-3` (12px = 4px + one padding unit), and `--tabs-button-px` is retired (no consumers outside the component — grepped clean). Hit-target: `--tabs-button-height` (`var(--a-size)`, 24/30/36px at sm/md/lg) already clears the 24px WCAG 2.5.8 AA minimum on its own; text-labeled buttons (the documented anatomy — `tab-ui`'s `[text]`+optional `[icon]`) keep width comfortably above 24px, so no compensating tap-target padding was added — accepted, not compensated. Demo doc table (`tabs.examples.html`) and its derived site page updated.
|
|
47
|
+
- **`swatch-ui` observes `auto-contrast`, matching its own yaml/CSS/demo** (gh#702). `autoContrast` declared no `attribute:` override, so the element watched `autocontrast` — the demo's `auto-contrast` attribute never flipped the property and the OKLab legibility probe never ran from markup. First surfaced by PR #700's blast-radius sweep (the estate's only other camelCase boolean); with the a2ui adapter now writing kebab-case, the override also keeps `autoContrast` live on the a2ui path.
|
|
48
|
+
- **`pipeline-status-ui` yaml matched its class again** (gh#677). yaml documented `complete`/`message`/`stage`; the class implements `stage`/`message`/`status`. The class is the source of truth (source wins), so yaml now documents `status` (idle/active/completed/error enum) and drops the phantom `complete` boolean; sidecars regenerated.
|
|
49
|
+
- **`chat-input-ui` no longer lets the raw `<textarea-ui>` `submit` event bubble past its own re-dispatch (gh#676).** `chat-input-ui` re-emits a richer `submit` (with `text`/`model`/`attachments`), but the original raw event kept bubbling too — any ancestor listener attached directly to `chat-input-ui` (e.g. `chat-composer`'s forwarding listener) received both, doubling agent turns per Enter. Fixed at the semantic boundary: `stopPropagation()` on the incoming `submit` before re-dispatching, so every `chat-input-ui` consumer gets exactly one event, not per-consumer workarounds.
|
|
50
|
+
- **`chat-thread-ui` stops the inner `chat-input-ui` `submit` event at its own re-dispatch boundary (gh#690)** — the third boundary in the same bug class as gh#676 (fixed in PR #681 for `chat-input-ui` and `chat-composer`, but this one was missed since no in-estate consumer sat on it yet). Same fix: `stopPropagation()` on the incoming `submit` before re-dispatching `chat-thread-ui`'s own `submit`, so a listener on `chat-thread-ui` gets exactly one event per Enter.
|
|
51
|
+
- **`list-item-ui`'s `render()` no longer re-stamps over a `[slot="content"]` row on a post-connect prop change (gh#689).** `#stamp()` already refused to touch a content-slotted row at connect; `render()` lacked the same gate, so setting `.icon`/`.text`/`.description` after connect stamped icon/span elements into a row the consumer fully owns via `[slot="content"]` — silently contradicting the yaml's "stamps NOTHING" claim. `render()` now gates on `[slot="content"]` identically to `#stamp()`, making that claim true again.
|
|
52
|
+
|
|
53
|
+
### Added
|
|
54
|
+
- **`segmented-ui allow-empty`** (gh#693) — opts out of the connect-time self-select of the first enabled segment when `value` is falsy. Without it (the default, unchanged), an empty `value` silently picks the first segment; with it, a falsy `value` renders every segment unselected until a real value arrives, for path-bound consumers whose bound value can be a genuine empty state (e.g. no disposition recorded yet). The group stays keyboard-reachable in the empty state (first enabled segment gets `tabindex="0"`, mirroring `list-ui`'s roving-tabindex fallback).
|
|
55
|
+
|
|
56
|
+
### Maintenance
|
|
57
|
+
- **`audit-static-properties-vs-yaml` now catches class→yaml drift, not just yaml→class** (gh#677). The existing audit only asked "does yaml promise a prop the class doesn't deliver?" — it couldn't see the opposite: a class renames/adds a runtime prop and yaml keeps documenting a stale or absent name (exactly the `pipeline-status-ui` shape above, where `status` was fully invisible to yaml). New reverse-direction pass flags any class.js `static properties` entry with zero yaml match as critical, closing the blind spot; also fixes a co-located-multi-class false-positive risk (`toggle-group.class.js`, `tree.class.js`) by scoping the static-properties scan to the yaml-named class's own brace-balanced block instead of the first match in the file. Swept clean across all 175 scanned component yamls — zero other drift instances.
|
|
58
|
+
- **`list-item.yaml` declares its real anatomy** (gh#653): the `text` and `content` slots are consumed by `list.class.js` (a consumer-provided `[slot="text"]` is never overwritten; `[slot="content"]` suppresses all stamping) and styled by `list.css`, but were undeclared in the yaml SoT — so the derived catalog and every wire producer reading it could not see them. Docs-vs-source drift, repaired on the source's side; sidecars regenerated.
|
|
59
|
+
- **`components/` touched in this release window** (19 file(s), e.g. `chat-thread/chat-input.js`) — carried by the entries above.
|
|
60
|
+
- **`dist/` bundles rebuilt** in this cut's window (4 file(s)) — regenerated from the source changes described above, not independent edits.
|
|
61
|
+
|
|
3
62
|
## [0.8.30] — 2026-08-07
|
|
4
63
|
|
|
5
64
|
### Maintenance
|
|
@@ -38,12 +38,12 @@ plus a divider between them when open) — the composition to reach for
|
|
|
38
38
|
when nesting the accordion inside <card-ui>. */
|
|
39
39
|
variant: 'flat' | 'contained';
|
|
40
40
|
|
|
41
|
+
addEventListener(type: 'toggle', listener: (ev: AccordionToggleEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
41
42
|
addEventListener<K extends keyof HTMLElementEventMap>(
|
|
42
43
|
type: K,
|
|
43
44
|
listener: (this: UIAccordion, ev: HTMLElementEventMap[K]) => unknown,
|
|
44
45
|
options?: boolean | AddEventListenerOptions,
|
|
45
46
|
): void;
|
|
46
|
-
addEventListener(type: 'toggle', listener: (ev: AccordionToggleEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
export interface AccordionItemToggleEventDetail {
|
|
@@ -63,10 +63,10 @@ accordion-ui's [variant] — set this directly on ONE item only to
|
|
|
63
63
|
opt it out of (`flat`) or into (`contained`) the host's posture. */
|
|
64
64
|
variant: 'flat' | 'contained';
|
|
65
65
|
|
|
66
|
+
addEventListener(type: 'toggle', listener: (ev: AccordionItemToggleEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
66
67
|
addEventListener<K extends keyof HTMLElementEventMap>(
|
|
67
68
|
type: K,
|
|
68
69
|
listener: (this: UIAccordionItem, ev: HTMLElementEventMap[K]) => unknown,
|
|
69
70
|
options?: boolean | AddEventListenerOptions,
|
|
70
71
|
): void;
|
|
71
|
-
addEventListener(type: 'toggle', listener: (ev: AccordionItemToggleEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
72
72
|
}
|
|
@@ -37,7 +37,9 @@ export class UIActionList extends UIElement {
|
|
|
37
37
|
#bound = false;
|
|
38
38
|
|
|
39
39
|
connected() {
|
|
40
|
-
|
|
40
|
+
// Adopt-or-stamp (gh#753): a consumer-authored role survives connect —
|
|
41
|
+
// only stamp when absent.
|
|
42
|
+
if (!this.hasAttribute('role')) this.setAttribute('role', 'list');
|
|
41
43
|
if (!this.#bound) {
|
|
42
44
|
this.#bound = true;
|
|
43
45
|
this.addEventListener('click', this.#onClick);
|
|
@@ -100,7 +102,9 @@ export class UIActionItem extends UIElement {
|
|
|
100
102
|
static template = () => null;
|
|
101
103
|
|
|
102
104
|
connected() {
|
|
103
|
-
|
|
105
|
+
// Adopt-or-stamp (gh#753): a consumer-authored role survives connect —
|
|
106
|
+
// only stamp "listitem" when the host has no author-set role at all.
|
|
107
|
+
if (!this.hasAttribute('role')) this.setAttribute('role', 'listitem');
|
|
104
108
|
if (!this.hasAttribute('tabindex')) this.setAttribute('tabindex', '0');
|
|
105
109
|
this.#stamp();
|
|
106
110
|
this.#syncAria();
|
|
@@ -25,12 +25,12 @@ export type ActionListActionEvent = CustomEvent<ActionListActionEventDetail>;
|
|
|
25
25
|
|
|
26
26
|
export class UIActionList extends UIElement {
|
|
27
27
|
|
|
28
|
+
addEventListener(type: 'action', listener: (ev: ActionListActionEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
28
29
|
addEventListener<K extends keyof HTMLElementEventMap>(
|
|
29
30
|
type: K,
|
|
30
31
|
listener: (this: UIActionList, ev: HTMLElementEventMap[K]) => unknown,
|
|
31
32
|
options?: boolean | AddEventListenerOptions,
|
|
32
33
|
): void;
|
|
33
|
-
addEventListener(type: 'action', listener: (ev: ActionListActionEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
export class UIActionItem extends UIElement {
|
|
@@ -31,10 +31,10 @@ export class UIAgentArtifact extends UIElement {
|
|
|
31
31
|
/** neutral | accent | warning | danger */
|
|
32
32
|
tone: string;
|
|
33
33
|
|
|
34
|
+
addEventListener(type: 'artifact-toggle', listener: (ev: AgentArtifactArtifactToggleEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
34
35
|
addEventListener<K extends keyof HTMLElementEventMap>(
|
|
35
36
|
type: K,
|
|
36
37
|
listener: (this: UIAgentArtifact, ev: HTMLElementEventMap[K]) => unknown,
|
|
37
38
|
options?: boolean | AddEventListenerOptions,
|
|
38
39
|
): void;
|
|
39
|
-
addEventListener(type: 'artifact-toggle', listener: (ev: AgentArtifactArtifactToggleEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
40
40
|
}
|
|
@@ -35,11 +35,11 @@ export class UIAgentFeedbackBar extends UIElement {
|
|
|
35
35
|
/** Save button text; empty hides it. */
|
|
36
36
|
saveLabel: string;
|
|
37
37
|
|
|
38
|
+
addEventListener(type: 'feedback-rate', listener: (ev: AgentFeedbackBarFeedbackRateEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
39
|
+
addEventListener(type: 'feedback-save', listener: (ev: AgentFeedbackBarFeedbackSaveEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
38
40
|
addEventListener<K extends keyof HTMLElementEventMap>(
|
|
39
41
|
type: K,
|
|
40
42
|
listener: (this: UIAgentFeedbackBar, ev: HTMLElementEventMap[K]) => unknown,
|
|
41
43
|
options?: boolean | AddEventListenerOptions,
|
|
42
44
|
): void;
|
|
43
|
-
addEventListener(type: 'feedback-rate', listener: (ev: AgentFeedbackBarFeedbackRateEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
44
|
-
addEventListener(type: 'feedback-save', listener: (ev: AgentFeedbackBarFeedbackSaveEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
45
45
|
}
|
|
@@ -38,10 +38,10 @@ export class UIAgentQuestions extends UIElement {
|
|
|
38
38
|
/** Submit button label (multi mode). */
|
|
39
39
|
submitLabel: string;
|
|
40
40
|
|
|
41
|
+
addEventListener(type: 'questions-answer', listener: (ev: AgentQuestionsQuestionsAnswerEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
41
42
|
addEventListener<K extends keyof HTMLElementEventMap>(
|
|
42
43
|
type: K,
|
|
43
44
|
listener: (this: UIAgentQuestions, ev: HTMLElementEventMap[K]) => unknown,
|
|
44
45
|
options?: boolean | AddEventListenerOptions,
|
|
45
46
|
): void;
|
|
46
|
-
addEventListener(type: 'questions-answer', listener: (ev: AgentQuestionsQuestionsAnswerEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
47
47
|
}
|
|
@@ -43,12 +43,12 @@ export class UIAgentReasoning extends UIElement {
|
|
|
43
43
|
/** Drives the status icon — idle, active (spinner), done (check), error (warning). */
|
|
44
44
|
status: 'idle' | 'active' | 'done' | 'error';
|
|
45
45
|
|
|
46
|
+
addEventListener(type: 'reasoning-finish', listener: (ev: AgentReasoningReasoningFinishEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
47
|
+
addEventListener(type: 'reasoning-step-toggle', listener: (ev: AgentReasoningReasoningStepToggleEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
48
|
+
addEventListener(type: 'reasoning-toggle', listener: (ev: AgentReasoningReasoningToggleEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
46
49
|
addEventListener<K extends keyof HTMLElementEventMap>(
|
|
47
50
|
type: K,
|
|
48
51
|
listener: (this: UIAgentReasoning, ev: HTMLElementEventMap[K]) => unknown,
|
|
49
52
|
options?: boolean | AddEventListenerOptions,
|
|
50
53
|
): void;
|
|
51
|
-
addEventListener(type: 'reasoning-finish', listener: (ev: AgentReasoningReasoningFinishEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
52
|
-
addEventListener(type: 'reasoning-step-toggle', listener: (ev: AgentReasoningReasoningStepToggleEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
53
|
-
addEventListener(type: 'reasoning-toggle', listener: (ev: AgentReasoningReasoningToggleEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
54
54
|
}
|
|
@@ -37,10 +37,10 @@ export class UIAgentSuggestions extends UIElement {
|
|
|
37
37
|
/** outline | ghost | subtle */
|
|
38
38
|
variant: string;
|
|
39
39
|
|
|
40
|
+
addEventListener(type: 'suggestion-select', listener: (ev: AgentSuggestionsSuggestionSelectEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
40
41
|
addEventListener<K extends keyof HTMLElementEventMap>(
|
|
41
42
|
type: K,
|
|
42
43
|
listener: (this: UIAgentSuggestions, ev: HTMLElementEventMap[K]) => unknown,
|
|
43
44
|
options?: boolean | AddEventListenerOptions,
|
|
44
45
|
): void;
|
|
45
|
-
addEventListener(type: 'suggestion-select', listener: (ev: AgentSuggestionsSuggestionSelectEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
46
46
|
}
|
|
@@ -25,10 +25,10 @@ export class UIAgentTrace extends UIElement {
|
|
|
25
25
|
/** Fallback summary label. */
|
|
26
26
|
label: string;
|
|
27
27
|
|
|
28
|
+
addEventListener(type: 'trace-toggle', listener: (ev: AgentTraceTraceToggleEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
28
29
|
addEventListener<K extends keyof HTMLElementEventMap>(
|
|
29
30
|
type: K,
|
|
30
31
|
listener: (this: UIAgentTrace, ev: HTMLElementEventMap[K]) => unknown,
|
|
31
32
|
options?: boolean | AddEventListenerOptions,
|
|
32
33
|
): void;
|
|
33
|
-
addEventListener(type: 'trace-toggle', listener: (ev: AgentTraceTraceToggleEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
34
34
|
}
|
|
@@ -51,11 +51,11 @@ export class UIAlert extends UIElement {
|
|
|
51
51
|
/** Semantic color variant. */
|
|
52
52
|
variant: 'default' | 'info' | 'success' | 'warning' | 'danger' | 'muted' | 'neutral';
|
|
53
53
|
|
|
54
|
+
addEventListener(type: 'close', listener: (ev: AlertCloseEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
55
|
+
addEventListener(type: 'dunning-action', listener: (ev: AlertDunningActionEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
54
56
|
addEventListener<K extends keyof HTMLElementEventMap>(
|
|
55
57
|
type: K,
|
|
56
58
|
listener: (this: UIAlert, ev: HTMLElementEventMap[K]) => unknown,
|
|
57
59
|
options?: boolean | AddEventListenerOptions,
|
|
58
60
|
): void;
|
|
59
|
-
addEventListener(type: 'close', listener: (ev: AlertCloseEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
60
|
-
addEventListener(type: 'dunning-action', listener: (ev: AlertDunningActionEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
61
61
|
}
|
|
@@ -25,11 +25,11 @@ export class UIAnchorBar extends UIElement {
|
|
|
25
25
|
/** false (default) anchors to the nearest scroll container via `position: sticky` — safe for content with ResizeObserver-driven children (e.g. <toolbar-ui>'s overflow measurement never sees a display:none reset). true anchors to the browser viewport via `position: fixed` + Popover API top-layer promotion — note the native Popover UA stylesheet applies `display:none` while closed, which DOES reset ResizeObserver-driven children on first open; prefer the default for content wrapping <toolbar-ui>. */
|
|
26
26
|
viewport: boolean;
|
|
27
27
|
|
|
28
|
+
addEventListener(type: 'close', listener: (ev: AnchorBarCloseEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
29
|
+
addEventListener(type: 'open', listener: (ev: AnchorBarOpenEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
28
30
|
addEventListener<K extends keyof HTMLElementEventMap>(
|
|
29
31
|
type: K,
|
|
30
32
|
listener: (this: UIAnchorBar, ev: HTMLElementEventMap[K]) => unknown,
|
|
31
33
|
options?: boolean | AddEventListenerOptions,
|
|
32
34
|
): void;
|
|
33
|
-
addEventListener(type: 'close', listener: (ev: AnchorBarCloseEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
34
|
-
addEventListener(type: 'open', listener: (ev: AnchorBarOpenEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
35
35
|
}
|
|
@@ -80,7 +80,6 @@ export class UIAvatar extends UIElement {
|
|
|
80
80
|
if (this.#initialsEl) { this.#initialsEl.remove(); this.#initialsEl = null; }
|
|
81
81
|
if (!this.#iconEl) {
|
|
82
82
|
this.#iconEl = document.createElement('icon-ui');
|
|
83
|
-
this.#iconEl.setAttribute('slot', 'icon');
|
|
84
83
|
this.appendChild(this.#iconEl);
|
|
85
84
|
}
|
|
86
85
|
this.#iconEl.setAttribute('name', this.icon);
|
|
@@ -49,8 +49,22 @@
|
|
|
49
49
|
line-height: 1;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
/* ── Icon (non-person avatar) ──
|
|
53
|
-
|
|
52
|
+
/* ── Icon (non-person avatar) ──
|
|
53
|
+
Selected by tag (`:scope > icon-ui`), not `[slot="icon"]` — this
|
|
54
|
+
repo's Light-DOM stance treats `slot=` as decorative metadata,
|
|
55
|
+
positioning is by tag + ancestor + DOM order (AGENTS.md). That
|
|
56
|
+
matters here beyond style: avatar-ui is commonly placed AS a
|
|
57
|
+
container's own `slot="icon"` child (list-item-ui rows, e.g. the
|
|
58
|
+
admin-dashboard Notifications list — see issue 809), and Light DOM has
|
|
59
|
+
no shadow boundary, so an ancestor's `[slot="icon"]` selector
|
|
60
|
+
(meant for the outer avatar-ui) was ALSO matching avatar's inner
|
|
61
|
+
icon-ui when both carried the same attribute — list-item.css's
|
|
62
|
+
`margin-inline-end` (icon-to-text gap) leaked onto the inner
|
|
63
|
+
icon-ui and pushed the flex-centered glyph off-center. The inner
|
|
64
|
+
icon-ui no longer carries `slot="icon"` at all (avatar.class.js)
|
|
65
|
+
— a direct-child tag selector needs no shared attribute to hang
|
|
66
|
+
off, so the collision is impossible rather than out-specificity'd. */
|
|
67
|
+
:scope > icon-ui {
|
|
54
68
|
color: var(--avatar-fg);
|
|
55
69
|
--a-icon-size: calc(var(--avatar-size) * 0.55);
|
|
56
70
|
display: inline-flex;
|
|
@@ -5,10 +5,21 @@
|
|
|
5
5
|
--badge-fg: var(--md-sys-color-neutral-on-surface);
|
|
6
6
|
--badge-radius: var(--a-radius-md);
|
|
7
7
|
--badge-px: var(--a-space-1-5);
|
|
8
|
-
--badge-py:
|
|
8
|
+
--badge-py: var(--a-space-0-5);
|
|
9
9
|
--badge-gap: var(--a-space-1);
|
|
10
10
|
--badge-font-size: var(--a-ui-sm);
|
|
11
11
|
--badge-font-family: var(--a-font-family-ui);
|
|
12
|
+
|
|
13
|
+
/* ── Size tiers (gh#779) — md (above) is the pixel-identical default.
|
|
14
|
+
Component-owned ladder, not a direct alias of the universal
|
|
15
|
+
--a-ui-px/--a-ui-size tokens — see tag.css's identical note (badge
|
|
16
|
+
and tag share this md baseline and this exact reasoning). */
|
|
17
|
+
--badge-px-sm: var(--a-space-1);
|
|
18
|
+
--badge-px-lg: var(--a-space-2);
|
|
19
|
+
--badge-py-sm: var(--a-space-px);
|
|
20
|
+
--badge-py-lg: var(--a-space-1);
|
|
21
|
+
--badge-font-size-sm: var(--a-ui-xs);
|
|
22
|
+
--badge-font-size-lg: var(--a-ui-md);
|
|
12
23
|
text-align: start; /* §text-align-reset — blocks inheritance from centered ancestors */
|
|
13
24
|
}
|
|
14
25
|
|
|
@@ -187,5 +198,22 @@
|
|
|
187
198
|
--badge-border: var(--md-sys-color-neutral-outline);
|
|
188
199
|
}
|
|
189
200
|
|
|
190
|
-
/* Size
|
|
201
|
+
/* ── Size tiers (gh#779) — re-point the active tokens to the matching
|
|
202
|
+
tier; the token defaults at :where(:scope) above ARE the md tier, so
|
|
203
|
+
[size="md"] is declared only for explicitness (mirrors kbd.css). */
|
|
204
|
+
:scope[size="sm"] {
|
|
205
|
+
--badge-px: var(--badge-px-sm);
|
|
206
|
+
--badge-py: var(--badge-py-sm);
|
|
207
|
+
--badge-font-size: var(--badge-font-size-sm);
|
|
208
|
+
}
|
|
209
|
+
:scope[size="md"] {
|
|
210
|
+
--badge-px: var(--a-space-1-5);
|
|
211
|
+
--badge-py: var(--a-space-0-5);
|
|
212
|
+
--badge-font-size: var(--a-ui-sm);
|
|
213
|
+
}
|
|
214
|
+
:scope[size="lg"] {
|
|
215
|
+
--badge-px: var(--badge-px-lg);
|
|
216
|
+
--badge-py: var(--badge-py-lg);
|
|
217
|
+
--badge-font-size: var(--badge-font-size-lg);
|
|
218
|
+
}
|
|
191
219
|
}
|
|
@@ -42,10 +42,17 @@
|
|
|
42
42
|
user-select: none;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
/* Items
|
|
45
|
+
/* Items — plain-text crumbs stay a normal inline-block box so
|
|
46
|
+
`text-overflow: ellipsis` actually paints: per the CSS Overflow
|
|
47
|
+
spec, the ellipsis glyph never renders on a flex container's own
|
|
48
|
+
box, only on a block box with a single fragmented inline
|
|
49
|
+
formatting context. Icon-leading crumbs opt back into inline-flex
|
|
50
|
+
below (scoped via :has(), see the icon-leading rule) so icon
|
|
51
|
+
baseline-alignment survives without reinstating the incompatible
|
|
52
|
+
flex+ellipsis combination on text-only items. */
|
|
46
53
|
[data-item] {
|
|
47
|
-
display: inline-
|
|
48
|
-
align
|
|
54
|
+
display: inline-block;
|
|
55
|
+
vertical-align: middle;
|
|
49
56
|
overflow: hidden;
|
|
50
57
|
text-overflow: ellipsis;
|
|
51
58
|
white-space: nowrap;
|
|
@@ -53,6 +60,11 @@
|
|
|
53
60
|
color: var(--breadcrumb-fg);
|
|
54
61
|
}
|
|
55
62
|
|
|
63
|
+
[data-item]:has(> icon-ui) {
|
|
64
|
+
display: inline-flex;
|
|
65
|
+
align-items: center;
|
|
66
|
+
}
|
|
67
|
+
|
|
56
68
|
/* Links */
|
|
57
69
|
a[data-item] {
|
|
58
70
|
text-decoration: none;
|
|
@@ -35,10 +35,10 @@ export class UIButton extends UIElement {
|
|
|
35
35
|
For **inline navigation** (Terms of Service, Privacy Policy, footer links, "Sign in" / "Sign up" cross-page affordances) use `<link-ui>` instead — it carries proper `<a href>` semantics, keyboard handling (Enter only, no Space), middle-click open-new-tab, and screen-reader announces "link" instead of "button". Mixing navigation and action affordances under the same primitive is a category error fixed at this junction. */
|
|
36
36
|
variant: 'default' | 'solid' | 'outline' | 'ghost' | 'primary';
|
|
37
37
|
|
|
38
|
+
addEventListener(type: 'press', listener: (ev: ButtonPressEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
38
39
|
addEventListener<K extends keyof HTMLElementEventMap>(
|
|
39
40
|
type: K,
|
|
40
41
|
listener: (this: UIButton, ev: HTMLElementEventMap[K]) => unknown,
|
|
41
42
|
options?: boolean | AddEventListenerOptions,
|
|
42
43
|
): void;
|
|
43
|
-
addEventListener(type: 'press', listener: (ev: ButtonPressEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
44
44
|
}
|
|
@@ -33,11 +33,11 @@ export class UICalendarGrid extends UIElement {
|
|
|
33
33
|
/** Selected date in ISO format (YYYY-MM-DD). */
|
|
34
34
|
value: string;
|
|
35
35
|
|
|
36
|
+
addEventListener(type: 'change', listener: (ev: CalendarGridChangeEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
37
|
+
addEventListener(type: 'input', listener: (ev: CalendarGridInputEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
36
38
|
addEventListener<K extends keyof HTMLElementEventMap>(
|
|
37
39
|
type: K,
|
|
38
40
|
listener: (this: UICalendarGrid, ev: HTMLElementEventMap[K]) => unknown,
|
|
39
41
|
options?: boolean | AddEventListenerOptions,
|
|
40
42
|
): void;
|
|
41
|
-
addEventListener(type: 'change', listener: (ev: CalendarGridChangeEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
42
|
-
addEventListener(type: 'input', listener: (ev: CalendarGridInputEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
43
43
|
}
|
package/components/card/card.css
CHANGED
|
@@ -364,6 +364,27 @@
|
|
|
364
364
|
min-height: 0;
|
|
365
365
|
}
|
|
366
366
|
|
|
367
|
+
/* Chrome-drop for bleed tables (gh#796) — a table-ui directly inside a
|
|
368
|
+
[bleed] section renders chromeless automatically, matching [raw]'s
|
|
369
|
+
reset (table.css:167-171: background none, box-shadow none,
|
|
370
|
+
border-radius 0). table.yaml's own raw/frameless docs already name
|
|
371
|
+
`<card-ui><section bleed>` as the canonical case for dropping a
|
|
372
|
+
table's chrome (the card owns the frame); 14 existing instances across
|
|
373
|
+
the repo carried neither attribute (gh#796 findings), so defaulting it
|
|
374
|
+
here removes an attribute nobody remembers to add rather than relying
|
|
375
|
+
on every author opting in. An explicit [raw] or [frameless] on the
|
|
376
|
+
instance still wins — `:not([raw]):not([frameless])` only lets this
|
|
377
|
+
rule fire in their absence.
|
|
378
|
+
Scoped to [bleed] sections only, not every card section: a plain
|
|
379
|
+
`<section>` keeps its own padding/margin, so a table's own frame there
|
|
380
|
+
reads as an intentional nested surface (see table.examples.html's own
|
|
381
|
+
demo cards, which use plain sections on purpose), not a doubled edge. */
|
|
382
|
+
& > section[bleed] > table-ui:not([raw]):not([frameless]) {
|
|
383
|
+
background: none;
|
|
384
|
+
box-shadow: none;
|
|
385
|
+
border-radius: 0;
|
|
386
|
+
}
|
|
387
|
+
|
|
367
388
|
/* ═══════ Footer ═══════
|
|
368
389
|
Margin-based inset. Flex row for actions.
|
|
369
390
|
[divider] adds top border.
|
|
@@ -137,6 +137,15 @@ a2ui:
|
|
|
137
137
|
row 2 without needing slot="description".
|
|
138
138
|
- Multiple <section> siblings are allowed and stack vertically. [bleed] on a section removes its margin for edge-to-edge
|
|
139
139
|
content (tables, charts); [padding] adds a canvas-scrim background for hero regions.
|
|
140
|
+
- >-
|
|
141
|
+
When neither [raw] nor [frameless] is present, a <table-ui> placed
|
|
142
|
+
directly inside a <section bleed> automatically renders chromeless
|
|
143
|
+
(background/box-shadow/border-radius reset; card.css, gh#796): the card
|
|
144
|
+
already owns the frame, so the table doesn't need its own. table.yaml's
|
|
145
|
+
[raw]/[frameless] docs describe the same composition; an explicit [raw] or
|
|
146
|
+
[frameless] on the table still applies and is honored. A plain
|
|
147
|
+
(non-bleed) <section> is unaffected — its own padding keeps a nested
|
|
148
|
+
table's frame legible as an intentional nested surface.
|
|
140
149
|
- '[grow] on a section makes it fill the card''s remaining height (flex:1 — same semantics as col-ui[grow]/row-ui[grow]).
|
|
141
150
|
The card becomes a flex column so the section absorbs the leftover space after the header/footer. Requires the card to
|
|
142
151
|
have a definite height (inline height, grid row track, or flex parent) — a content-sized card has nothing to fill.
|
|
@@ -49,13 +49,13 @@ export class UIChart extends UIElement {
|
|
|
49
49
|
/** Y-axis key(s), comma-separated for multi-series */
|
|
50
50
|
y: string;
|
|
51
51
|
|
|
52
|
+
addEventListener(type: 'chart-hover', listener: (ev: ChartHoverEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
53
|
+
addEventListener(type: 'chart-leave', listener: (ev: ChartLeaveEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
54
|
+
addEventListener(type: 'chart-select', listener: (ev: ChartSelectEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
55
|
+
addEventListener(type: 'legend-update', listener: (ev: ChartLegendUpdateEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
52
56
|
addEventListener<K extends keyof HTMLElementEventMap>(
|
|
53
57
|
type: K,
|
|
54
58
|
listener: (this: UIChart, ev: HTMLElementEventMap[K]) => unknown,
|
|
55
59
|
options?: boolean | AddEventListenerOptions,
|
|
56
60
|
): void;
|
|
57
|
-
addEventListener(type: 'chart-hover', listener: (ev: ChartHoverEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
58
|
-
addEventListener(type: 'chart-leave', listener: (ev: ChartLeaveEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
59
|
-
addEventListener(type: 'chart-select', listener: (ev: ChartSelectEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
60
|
-
addEventListener(type: 'legend-update', listener: (ev: ChartLegendUpdateEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
61
61
|
}
|
|
@@ -37,10 +37,10 @@ export class UIChartLegend extends UIElement {
|
|
|
37
37
|
/** When set, rows are non-interactive <span>s (no click, no toggle). */
|
|
38
38
|
static: boolean;
|
|
39
39
|
|
|
40
|
+
addEventListener(type: 'toggle', listener: (ev: ChartLegendToggleEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
40
41
|
addEventListener<K extends keyof HTMLElementEventMap>(
|
|
41
42
|
type: K,
|
|
42
43
|
listener: (this: UIChartLegend, ev: HTMLElementEventMap[K]) => unknown,
|
|
43
44
|
options?: boolean | AddEventListenerOptions,
|
|
44
45
|
): void;
|
|
45
|
-
addEventListener(type: 'toggle', listener: (ev: ChartLegendToggleEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
46
46
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
Two rules — one on the host (paint the hover affordance), one on the
|
|
7
7
|
inner textarea-ui's [slot="text"] (suppress the textarea-ui hover
|
|
8
8
|
bg/border/color so it doesn't compound with the host's). */
|
|
9
|
-
chat-input-ui:not([disabled]):hover {
|
|
9
|
+
chat-input-ui:not([disabled]):hover:not(:focus-within) {
|
|
10
10
|
background: var(--chat-input-bg-hover);
|
|
11
11
|
border-color: var(--chat-input-border-hover);
|
|
12
12
|
color: var(--chat-input-fg-hover);
|
|
@@ -181,7 +181,15 @@ class UIChatInput extends UIElement {
|
|
|
181
181
|
this.model = this.#modelEl?.value || '';
|
|
182
182
|
};
|
|
183
183
|
|
|
184
|
-
#onSubmit = () => {
|
|
184
|
+
#onSubmit = (e) => {
|
|
185
|
+
// Stop the raw <textarea-ui> 'submit' event here, at the re-dispatch
|
|
186
|
+
// boundary — this component re-emits its own 'submit' below with
|
|
187
|
+
// richer detail (text/model/attachments). Letting the raw event keep
|
|
188
|
+
// bubbling past this point means every ancestor listening for
|
|
189
|
+
// 'submit' (e.g. chat-composer's forwarding listener, attached
|
|
190
|
+
// directly to this element) receives it TWICE: once as the raw event
|
|
191
|
+
// bubbling through, once as the semantic re-dispatch (gh#676).
|
|
192
|
+
if (e?.type === 'submit') e.stopPropagation();
|
|
185
193
|
if (this.disabled || this.loading) return;
|
|
186
194
|
const text = this.value;
|
|
187
195
|
if (!text && !this.#attachments.length) return;
|
|
@@ -93,6 +93,12 @@ export class UIChatThread extends UIElement {
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
#onSubmit = (e) => {
|
|
96
|
+
// Stop the inner chat-input-ui 'submit' event at this re-dispatch
|
|
97
|
+
// boundary — this component re-emits its own 'submit' below. Letting
|
|
98
|
+
// the inner event keep bubbling means any ancestor listening for
|
|
99
|
+
// 'submit' on chat-thread-ui receives it TWICE (same bug class as
|
|
100
|
+
// gh#676 / gh#681).
|
|
101
|
+
if (e?.type === 'submit') e.stopPropagation();
|
|
96
102
|
if (this.streaming) return;
|
|
97
103
|
const { text, model } = e.detail || {};
|
|
98
104
|
if (!text) return;
|
|
@@ -32,12 +32,12 @@ export class UIChatThread extends UIElement {
|
|
|
32
32
|
/** Component property: streaming. */
|
|
33
33
|
streaming: boolean;
|
|
34
34
|
|
|
35
|
+
addEventListener(type: 'submit', listener: (ev: ChatThreadSubmitEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
35
36
|
addEventListener<K extends keyof HTMLElementEventMap>(
|
|
36
37
|
type: K,
|
|
37
38
|
listener: (this: UIChatThread, ev: HTMLElementEventMap[K]) => unknown,
|
|
38
39
|
options?: boolean | AddEventListenerOptions,
|
|
39
40
|
): void;
|
|
40
|
-
addEventListener(type: 'submit', listener: (ev: ChatThreadSubmitEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
export interface ChatInputSubmitEventDetail {
|
|
@@ -71,10 +71,10 @@ hides the model picker.
|
|
|
71
71
|
/** Textarea placeholder. Defaults to "Type a message...". */
|
|
72
72
|
placeholder: string;
|
|
73
73
|
|
|
74
|
+
addEventListener(type: 'submit', listener: (ev: ChatInputSubmitEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
74
75
|
addEventListener<K extends keyof HTMLElementEventMap>(
|
|
75
76
|
type: K,
|
|
76
77
|
listener: (this: UIChatInput, ev: HTMLElementEventMap[K]) => unknown,
|
|
77
78
|
options?: boolean | AddEventListenerOptions,
|
|
78
79
|
): void;
|
|
79
|
-
addEventListener(type: 'submit', listener: (ev: ChatInputSubmitEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
80
80
|
}
|