@adia-ai/web-components 0.5.14 → 0.5.16
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 +234 -0
- package/components/badge/class.js +5 -1
- package/components/button/class.js +5 -1
- package/components/code/class.js +42 -5
- package/components/code/code.a2ui.json +1 -1
- package/components/code/code.d.ts +27 -0
- package/components/code/code.yaml +7 -1
- package/components/heatmap/heatmap.css +1 -1
- package/components/list/list.css +6 -6
- package/components/nav-group/nav-group.css +2 -2
- package/components/page/page.css +1 -1
- package/components/segmented/class.js +22 -0
- package/components/select/select.css +2 -2
- package/components/slider/class.js +60 -6
- package/components/slider/slider.a2ui.json +10 -4
- package/components/slider/slider.css +142 -52
- package/components/slider/slider.test.js +18 -15
- package/components/slider/slider.yaml +8 -4
- package/components/stepper/stepper.css +24 -24
- package/components/swatch/class.js +92 -1
- package/components/swatch/swatch.css +2 -2
- package/components/swatch/swatch.test.js +269 -0
- package/components/swiper/class.js +247 -54
- package/components/swiper/swiper.a2ui.json +19 -0
- package/components/swiper/swiper.css +87 -21
- package/components/swiper/swiper.d.ts +6 -0
- package/components/swiper/swiper.yaml +18 -0
- package/components/tag/class.js +6 -1
- package/components/text/text.a2ui.json +16 -2
- package/components/text/text.d.ts +28 -45
- package/components/text/text.yaml +25 -1
- package/components/toolbar/toolbar.css +1 -1
- package/package.json +1 -1
- package/styles/colors/semantics.css +7 -84
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,240 @@ runtime ships in the sibling `@adia-ai/a2ui-runtime` package
|
|
|
11
11
|
|
|
12
12
|
_No pending changes._
|
|
13
13
|
|
|
14
|
+
## [0.5.16] - 2026-05-16
|
|
15
|
+
|
|
16
|
+
### v0.5.16 §331 — `<swatch-ui>` chrome-slot wrapper-span deep-walk (FB-39; P1)
|
|
17
|
+
|
|
18
|
+
Closes FB-39. P1 lifecycle gap that FB-38's "C1.3 unblocked" close-out missed: §326 (v0.5.15) fixed the late-direct-append case but NOT the template-render wrapper-span case. AdiaUI's `html\`\`` template engine wraps each interpolated child fragment in a `<span style="display: contents">` (per `core/template.js:212` `wrap()`). When consumer writes `html\`<swatch-ui>${badgeFrag}</swatch-ui>\``, the `<span slot="chrome">` ends up nested INSIDE the wrapper, not as a direct child. `#stamp()`'s `Array.from(this.children).filter(n => n.getAttribute?.('slot') === 'chrome')` returned `[]`; `innerHTML = ''` wiped the wrapper spans + chrome content; `#absorbChromeSlot()` ran on destroyed DOM.
|
|
19
|
+
|
|
20
|
+
Blocked Tokens Studio's C1.3 dogfood migration (chrome elements silently destroyed) + >95% of AdiaUI consumers using `html\`\`` templates.
|
|
21
|
+
|
|
22
|
+
#### Fixed — `components/swatch/class.js` (dual-site deep-walk)
|
|
23
|
+
|
|
24
|
+
- `#stamp()`'s chrome extraction extends from direct-children-only to also walk `querySelectorAll('[slot="chrome"]')` on non-chrome wrapper-span children. Catches both static-HTML (direct child) AND template-render (nested in wrapper) paths.
|
|
25
|
+
- `#absorbChromeSlot()` gains a parallel deep-walk for wrapper-spanned chrome children that arrive AFTER `#stamp()` (parent template re-render path). Hoists nested chrome out of any non-internal wrapper into the canonical sibling-of-tile slot via `this.insertBefore(child, this.#badgeEl)`.
|
|
26
|
+
- Wrapper spans stay in place post-hoist (`display: contents` makes them invisible to CSS layout; removing them risks interfering with the parent template engine's diff tracking via the `PART` symbol).
|
|
27
|
+
|
|
28
|
+
#### Added — `components/swatch/swatch.test.js` (6 → 9 cases)
|
|
29
|
+
|
|
30
|
+
3 NEW vitest cases under "FB-39 / §331":
|
|
31
|
+
- `hoists chrome child from inside wrapper span at #stamp() time (template-interpolation path)` — single-wrapper-single-chrome case.
|
|
32
|
+
- `handles MULTIPLE chrome children nested in separate wrapper spans` — multi-`${...}` template interpolation; each fragment lands in its own wrapper.
|
|
33
|
+
- `hoists chrome from late-arriving wrapper span (post-stamp template re-render)` — parent re-renders + appends a new wrapper-span carrying chrome after `#stamp()` already ran.
|
|
34
|
+
|
|
35
|
+
Total `swatch.test.js`: 9/9 passing (4 §325 + 2 §326 + 3 §331).
|
|
36
|
+
|
|
37
|
+
#### Migration
|
|
38
|
+
|
|
39
|
+
Consumers using `<swatch-ui>` with chrome-slot composition via `html\`\`` templates (`<swatch-ui>${badge}${dot}</swatch-ui>`) now get chrome children placed at canonical position post-§331. The consumer-side workaround (skip chrome composition or use bare `<swatch-ui>` without chrome) can be retired. No consumer code changes required — the §331 fix is invisible to consumers writing canonical templates.
|
|
40
|
+
|
|
41
|
+
### v0.5.x (Unreleased) §330 — `slider-ui` "Next" design + `segmented-ui` initial-render fix
|
|
42
|
+
|
|
43
|
+
**`slider-ui`** — full visual redesign to iOS/macOS-style pill slider. Pill-shaped track (`var(--a-toggle-size)` = 16/20/24px for sm/md/lg), primary-color progressive fill (`--a-primary-bg`), white chrome thumb (`--a-chrome-light`) with 2:1 width:height ratio and 2px internal padding on all sides. Mathematical geometry documented inline in CSS: `fill_width(p) = t + p·(W−t)`, `thumb_center(p) = t/2 + p·(W−t)`; fill starts at thumb-width (p=0) and grows incrementally to full track width (p=1). Two-layer thumb architecture: transparent full-height container provides grab area; `::before` renders the inward-padded white pill. `class.js` `#valueFromX()` maps full track width to [min,max]; `#valueFromX()` simplified back to direct rect ratio. `data-dragging` CSS transition lock kills the `left` transition during pointer moves for instant visual feedback. `slider.examples.html` gains Sizes section (lg/md/sm/14px/12px) + updated CSS token table.
|
|
44
|
+
|
|
45
|
+
**`segmented-ui`** — initial rendering race fix. Added `ResizeObserver` on self + `document.fonts.ready` promise in `connected()`; both call `#updateIndicator()` when layout stabilizes so the animated pill positions correctly after font load or resize. Previously the indicator computed once at mount and stayed misaligned — only corrected on next user interaction.
|
|
46
|
+
|
|
47
|
+
Files: `slider.css`, `slider.class.js`, `slider.examples.html`, `slider.yaml`, `segmented.class.js`.
|
|
48
|
+
|
|
49
|
+
### Added
|
|
50
|
+
- §333 `<color-input>` gains canonical demo-shell loader pair to restore catalog symmetry with the other 92 primitives. NEW `color-input.html` (52-line shell pattern matching `color-picker.html`: tokens base + composed primitive CSS + composed primitive JS per ADR-0027 + `fetch('./color-input.examples.html')`) + NEW `color-input.examples.html` (7 sections: default · initial values · format hex vs oklch · popover placement · §313 generation constraints `max-chroma`/`max-l`/`min-l`/`hue-drift-max`/`base-hue` · disabled · inside a form). Closes gemini-review refactor-backlog P3-1.
|
|
51
|
+
- §332 `<swiper-ui>` gains `chrome="toolbar"` layout mode with companion `label` + `counter` props. Default chrome (overlay paddles + centered dots below) is unchanged. Toolbar chrome stamps a header row (label on the left, paddles on the right) above the track and a footer row (counter on the left, dots on the right) below — supports gallery-style compositions where consumers want the chrome out of the slide area. `#stampToolbarChrome()` builds the head + foot rows; `#updateCounter()` writes "N / M" (page-based, snap-aware) into `[data-swiper-counter]` on every scroll. New examples at `/site/components/swiper` § chrome (photo gallery / featured products / autoplay testimonials). Counter is page-based so it tracks the dot count correctly for multi-column + center-snap.
|
|
52
|
+
- §329 `<code-ui>` gains a `<template>`-child authoring path — wrap the source in `<template>…</template>` and write literal `<` / `>` without entity-escaping. `#stampBlock` reads `template.innerHTML`, dedents the wrapper column via a new static `#dedent()` helper, and feeds the result to CodeMirror. The HTML parser treats `<template>` content as inert (it lives in a `DocumentFragment`, not rendered), so embedded `<nav-ui>` / `<button-ui>` / etc. don't get upgraded inside the template. Three authoring paths now in priority order: (1) `[text]` attribute, (2) `<template>` child, (3) escaped textContent (back-compat — existing examples unchanged). Demo at `/site/components/code` § "escape-free authoring via <template>"; dogfooded on `patterns/app-nav` structure example. (Carry-forward from worktree §318.)
|
|
53
|
+
|
|
54
|
+
### Fixed
|
|
55
|
+
- §332 `<swiper-ui>` — comprehensive consumer-feedback hardening pass (9 distinct fixes consolidated). (a) **Click-and-drag preempted by native HTML5 image-drag**: `<img>` inside slide cards fired `dragstart` before our 5px pointermove threshold was reached, suspending pointer events. Fix: `dragstart` listener gated on `this.#drag !== null` (set by `#onPointerDown`) so genuine drag-out of intentionally draggable slide content stays available. (b) **Pagination dots ignored `--swiper-columns`**: stamping one dot per slide regardless of `slides-per-view`. New `#getPageCount()` returns `max(1, slides − cols + 1)` for start/end snap (reads `--swiper-columns` via `getComputedStyle(track)`); `ResizeObserver` on the host re-stamps when container queries shift the cv across breakpoints. `next/prev/play` cap at last page; dot labels read "Page N" (was "Slide N", misleading once dot-index ≠ slide-index). (c) **CSS `container-type` on the wrong element** — `container-type: inline-size` was on `[data-swiper-track]` but `@container` rules targeted `:scope` (the host = track's PARENT, not a descendant). Container queries match descendants of the container, so the rules fell through to whatever further-up ancestor had a container set (the docs shell) and incorrectly stamped `cols=2-3` on every swiper. Moved `container-type: inline-size; container-name: swiper;` to host; `@container swiper (...)` rules now target `:scope > [data-swiper-track]` (a real descendant). JS `#getColumns()` reads from the track via `getComputedStyle` so both `[slides-per-view]` (cascades from host) and `@container` (writes on track) converge. (d) **Snap-aware active tracking** — IntersectionObserver "leftmost intersecting" couldn't distinguish the last two snap positions for center/end snap (both kept the last two slides >50% visible). Replaced with `scroll` listener + `#computeActiveIndex()` that walks every slide, computes its snap target (`left` / `left + width/2 − trackWidth/2` / `left + width − trackWidth` per snap mode), clamps to `[0, maxScroll]`, and picks the closest target to `scrollLeft`. `#pageToSlideIndex` / `#slideToPageIndex` map between page and slide indices (identity for start/center; end-snap shifts by `cols − 1`); snap-aware `goTo()` scrolls to the computed target so dot clicks land without a "scroll to left edge, then scroll-snap re-correct" jank step. `#getPageCount()` returns `slides.length` for `snap="center"` (each slide is its own page; 4-slides-@-cols=2 now stamps 4 dots, not 3). (e) **Loop / autoplay demos didn't visibly demonstrate looping** — at responsive widths (≥960px → cols=3) the 3-slide demos had `pageCount=1`, paddles produced no visible effect. Pinned demos to `slides-per-view="1"`; added a new combined "loop + autoplay" example. (f) **`gap` only accepted token indices despite docs claiming "any CSS length"**: `render()` blindly wrapped `gap` in `var(--a-space-${gap})`. For `gap="1rem"` that became `var(--a-space-1rem)` (undefined token) → property invalidated → CSS gap fell back to 0; three demo variants (`gap=0/1rem/2rem`) all rendered identically. Fix: numeric-only strings resolve to space tokens, anything else used as a raw CSS length. (g) **Dot CSS lost in toolbar chrome**: `:scope > [data-swiper-dots] > button` (direct-child combinator) stopped matching when dots moved inside `[data-swiper-foot]`; buttons fell back to browser defaults (wide gray bars). Relaxed all five dot rules to `:scope [data-swiper-dots]` (descendant combinator). (h) **Active dot rendered much larger than inactive** — `background: <color>` shorthand in `:hover` and `[aria-current="true"]` rules silently RESET `background-clip` back to its initial `border-box`, so the colored area filled the full 1rem outer hitbox and `border-radius: 100rem` rounded that into a ~16px circle (vs the 6px content-box-clipped circle for inactive). Fix: longhand `background-color: <color>` so `background-clip: content-box` from the base rule is preserved. Active dot now has a deliberate +4px (`--a-space-1`) size delta via NEW `--swiper-dot-size-active` token; padding back-computed so the 1rem hitbox stays invariant. Focus ring moved from outer `var(--a-focus-ring)` to inset 2px shadow so focus doesn't change footprint either. **Pitfall worth knowing for future CSS work: `background: <color>` shorthand resets `background-clip`/`background-origin`/`background-position`/etc. to initial — use `background-color: <color>` to preserve them.**
|
|
56
|
+
- §327 P2 correctness — `<code-ui language="…">` (every `/site/patterns/*` page) rendered as flow-wrapped plain text with chrome header below the content + no syntax highlighting. Root cause: the `textContent` key in `static properties` of `code/class.js`, `button/class.js`, `tag/class.js`, `badge/class.js` made `UIElement.installProps()` shadow the native `HTMLElement.prototype.textContent` setter. `this.textContent = ''` in `code-ui`'s `#stampBlock` became a signal-write no-op — original authored HTML-escaped source stayed in the element above the stamped chrome, and CodeMirror mounted with an empty doc (1 cm-line, 19.5px tall editor below leftover authored text). Same hazard on `<tag-ui>` produced visible empty pills across every `/site/traits/*` page's API table (the `chip()` helper in `traits/_api-table.js` and 3 other call sites all do `t.textContent = text` after `createElement('tag-ui')`). Fix: removed the `textContent` entry from each of the 4 class.js files. Yaml + `.d.ts` entries kept (renderer routes via `setAttribute('text', …)` for non-text-bearing tags; doesn't depend on JS prop declarations). New drift class #7 (native-DOM-accessor collision). **Carry-forward note:** initially staged as §316 on a worktree branch (2026-05-15) before v0.5.14 cut; §316-§318 were claimed by parallel peer work that shipped via v0.5.15. Renumbered to §327 on re-apply to main against v0.5.15 HEAD (2026-05-16).
|
|
57
|
+
|
|
58
|
+
### Docs
|
|
59
|
+
- §328 source-side polish: re-flowed 6 one-line `<code-ui>` blocks across `stat`/`pagination`/`rating`/`count-up`/`theme-panel`/`theming` USAGE sections — multi-attribute single tags now author each attribute on its own line for readability + consistency with how the rest of the corpus is authored. (Carry-forward from worktree §317.)
|
|
60
|
+
|
|
61
|
+
## [0.5.15] - 2026-05-16
|
|
62
|
+
|
|
63
|
+
> **Scope note:** §325 + §326 were originally planned as a separate v0.5.16 cycle (see retired `docs/plans/0.5.16-release-plan.md`); absorbed into v0.5.15 since both landed within the same release window (same-day commits prior to bump). The peer's v0.5.16 plan-doc explicitly anticipated this absorption option ("Hermes's call per `/lockstep-release`"). v0.5.16 plan-doc marked SHIPPED-IN-V0.5.15.
|
|
64
|
+
|
|
65
|
+
### v0.5.15 §326 — `<swatch-ui>` `[slot="chrome"]` lost in template contexts (FB-37; P1)
|
|
66
|
+
|
|
67
|
+
Closes FB-37. Pre-§326, `<swatch-ui>` consumed via any `html\`\`` template engine (or any engine that batches child appends separately from element creation) lost its `[slot="chrome"]` children entirely. Root cause: `#stamp()` is gated by a `#stamped` once-only guard + scans `this.children` for chrome slots at `connectedCallback` time. Template engines create the host via `createElement` + connect it FIRST (children empty), then batch-append children AFTER. `#stamp()`'s chrome scan returns `[]`; the `#stamped` guard prevents re-runs; chrome children landed at the END of host's children (after `data-copy`) instead of the canonical sibling-of-tile position. Blocked Tokens Studio's C1.3 dogfood migration (9 failing Playwright tests root-caused to this) — and >95% of AdiaUI consumers using `html\`\`` templates.
|
|
68
|
+
|
|
69
|
+
#### Fixed — `components/swatch/class.js`
|
|
70
|
+
|
|
71
|
+
- NEW `#absorbChromeSlot()` helper. Idempotent. Walks `this.children`; for any child with `slot="chrome"` that's not one of the internal stamped elements + not in the canonical position, calls `this.insertBefore(child, this.#badgeEl)` to move it to the slot between `#tileEl` + `#badgeEl`.
|
|
72
|
+
- `#syncCore()` calls `#absorbChromeSlot()` at the top of every render pass. Late-arriving chrome children (template-render path) get picked up on the next reactive cycle; pre-positioned chrome children (static-HTML path) are no-op-moves.
|
|
73
|
+
- `#stamp()`'s once-only eager scan stays intact for the static-HTML path; `#absorbChromeSlot()` is the safety net for late-append.
|
|
74
|
+
|
|
75
|
+
#### Added — `components/swatch/swatch.test.js`
|
|
76
|
+
|
|
77
|
+
- 2 NEW vitest cases under "FB-37 / §326":
|
|
78
|
+
- `absorbs chrome child appended AFTER connectedCallback (template-render path)` — mimics the bug's exact timing (create + connect with empty children → late append → property mutation triggers render → fix picks up the chrome child).
|
|
79
|
+
- `still handles chrome child appended BEFORE connect (static-HTML path)` — regression check that §314's original behavior still works.
|
|
80
|
+
- Total suite: 6 cases (4 from §325 + 2 from §326).
|
|
81
|
+
|
|
82
|
+
#### Migration
|
|
83
|
+
|
|
84
|
+
Consumers using `<swatch-ui>` via `html\`\`` template engines (lit-html, AdiaUI's own `html\`\``, Preact, Vue, etc.) get the §314 chrome-slot feature working as documented in v0.5.13's `swatch.yaml slots:` block. No consumer-side changes required — the §326 fix is invisible to consumers writing canonical templates.
|
|
85
|
+
|
|
86
|
+
### v0.5.15 §325 — `<swatch-ui>` default-slot wiped by label-attr (FB-36)
|
|
87
|
+
|
|
88
|
+
Closes FB-36 + FB-36-followup. Pre-§325, `<swatch-ui label="500"><span>tip</span></swatch-ui>` written as indented multi-line HTML dropped the default-slot child entirely; only `label="500"` rendered. Root cause: `#stamp()`'s `Array.from(this.childNodes).filter(...)` captured pure-whitespace text nodes from the indentation; those landed in `#labelEl.firstChild`; `#syncCore()`'s "stale text-node" branch (designed for runtime `el.label = 'X'` mutations on attr-only swatches) then matched the whitespace + did `textContent = this.label`, wiping both the whitespace AND the consumer's `<span>`.
|
|
89
|
+
|
|
90
|
+
Pre-§325 the yaml spec ("Use the default slot for richer content" — `swatch.yaml:40`) and the implementation contradicted each other; post-§325 they agree.
|
|
91
|
+
|
|
92
|
+
#### Fixed — `components/swatch/class.js`
|
|
93
|
+
|
|
94
|
+
- `#stamp()`'s `slotted` filter chain gains `&& !(n.nodeType === 3 && !n.textContent.trim())` — strips pure-whitespace text nodes from the capture. `#labelEl.firstChild` now becomes the consumer's element (or non-whitespace text), so `#syncCore`'s stale-text-node branch fires only for genuinely attr-driven cases.
|
|
95
|
+
|
|
96
|
+
#### Added — `components/swatch/swatch.test.js`
|
|
97
|
+
|
|
98
|
+
- NEW 4-case vitest suite covering FB-36's 3 input axes (default-slot present/absent × label-attr present/absent × runtime mutation). Locks the §325 fix structurally — future regressions of any of the 4 paths fail CI.
|
|
99
|
+
|
|
100
|
+
#### Migration
|
|
101
|
+
|
|
102
|
+
`<swatch-ui label="500"><span>tip</span></swatch-ui>` now behaves per yaml spec: default-slot wins, label-attr drops. Consumers using the workaround `.label=` property binding (per FB-36-followup) to bypass the bug can switch back to the canonical `[label]` attribute form post-upgrade.
|
|
103
|
+
|
|
104
|
+
### v0.5.15 §324 — Yaml `enum_descriptions:` codegen extension (v0.6.0 FB-23 §3 retire carry-forward, PATCH-eligible)
|
|
105
|
+
|
|
106
|
+
Closes the v0.6.0 carry-forward "Codegen yaml `enum:` per-member descriptions (FB-23 §3 retire)" from the RESPONSE-29 carry-forwards table. v0.5.15 ships the additive codegen surface on PATCH cadence; v0.6.0 no longer needs to carry this item.
|
|
107
|
+
|
|
108
|
+
**The surface change:** `scripts/schemas/component.yaml.schema.json` `Prop` definition gains an optional `enum_descriptions:` object field — when present alongside `enum:`, dts-codegen emits an exported type alias (e.g. `UITextVariant`) above the class declaration with per-variant JSDoc carrying each description. IDE hover surfaces the disambiguation at authoring time without requiring hand-authored .d.ts.
|
|
109
|
+
|
|
110
|
+
#### Added
|
|
111
|
+
|
|
112
|
+
- `scripts/schemas/component.yaml.schema.json` — NEW optional `enum_descriptions: { type: object, additionalProperties: { type: string } }` field per prop. Backward-compatible: existing yamls without the field continue to emit inline enum unions.
|
|
113
|
+
- `scripts/build/components.mjs` — forwards `enum_descriptions:` through `compileProp()` into the a2ui.json sidecar.
|
|
114
|
+
- `scripts/build/dts-codegen.mjs` — `emitEnumTypeAlias()` helper detects props with both `enum:` and `enum_descriptions:`; emits a typed union alias `${className}${PascalCase(propName)}` with per-variant JSDoc; class property references the alias instead of inlining the union.
|
|
115
|
+
|
|
116
|
+
#### Changed — `components/text/text.yaml`
|
|
117
|
+
|
|
118
|
+
- `variant:` prop gains 12-entry `enum_descriptions:` block (one description per variant). Prop `description:` block also gains the FB-23 §2 ARIA-role disclaimer that previously lived in the hand-authored .d.ts.
|
|
119
|
+
|
|
120
|
+
#### Changed — `scripts/build/dts-codegen.mjs` `HAND_AUTHORED_DTS`
|
|
121
|
+
|
|
122
|
+
- `text` removed from skip-list. `text.d.ts` now codegen-driven; per-variant JSDoc surfaces via the new `enum_descriptions:` field. Net effect: 1 of 3 remaining `HAND_AUTHORED_DTS` entries closed (`toast` + `feed` remain — both carry runtime API the yaml shape can't express).
|
|
123
|
+
|
|
124
|
+
#### Regenerated — `components/text/text.d.ts`
|
|
125
|
+
|
|
126
|
+
- Replaces hand-authored .d.ts with codegen output. `UITextVariant` type alias preserved as exported type; per-variant JSDoc preserved verbatim. Net result: zero consumer-facing diff in the .d.ts surface; codegen takes over maintenance.
|
|
127
|
+
|
|
128
|
+
### v0.5.15 §322 — NEW audit slot 32: `check-dead-constants.mjs`
|
|
129
|
+
|
|
130
|
+
Graduates the FB-35 bug class (declared-but-unread module-level consts) to a CI gate. Walks every `.js` file under `packages/<pkg>/`; extracts SCREAMING_SNAKE_CASE / PascalCase `const NAME = ...;` declarations at module level; flags zero-reference cases. Skips `export const NAME` (consumed externally) + test/spec/examples/config files.
|
|
131
|
+
|
|
132
|
+
First sweep surfaced + removed 2 real dead constants:
|
|
133
|
+
- `packages/a2ui/validator/validator.js:38` — `WIRING_WEIGHTS` (vestigial from planned weighted-scoring system; safe deletion).
|
|
134
|
+
- `packages/llm/adapters/openai.js:10` — `DEFAULT_MAX_TOKENS = 4096` (replaced by explicit `opts.max_tokens` plumbing).
|
|
135
|
+
|
|
136
|
+
### v0.5.15 §323 — NEW audit slot 33: `check-hover-guard-discipline.mjs`
|
|
137
|
+
|
|
138
|
+
Graduates the `analyze-css` bare-`:hover` discipline class to a CI gate. Detects `:hover` rules on host primitives + interactive slots/roles that lack a `:not([disabled])` guard AND set visual properties (background/color/border/box-shadow/etc.). Skips cursor-only / outline-style-only rule bodies.
|
|
139
|
+
|
|
140
|
+
First sweep surfaced 19 findings across 11 components — all baselined in EXPECTED_DRIFT for cycle-by-cycle close:
|
|
141
|
+
- **Real drift** (rely on `pointer-events: none` for click-suppression but hover styles still apply): action-list (4×), menu (1×), nav-item (2×), nav-group (1×).
|
|
142
|
+
- **Slotted-trigger drift** (mitigated via parent guard rules; cosmetic-OK but worth tightening): select (5×), calendar-picker (1×), range (2×), tag (1×).
|
|
143
|
+
- **Decorative-pop hover** (intentional): avatar (1×). Long-term whitelist candidate.
|
|
144
|
+
- **@scope-Safari-17.x-workaround**: action-list (3 of 4) + nav-item (2) + rating (1) — guard can't live inside @scope; lift alongside the hover rule.
|
|
145
|
+
|
|
146
|
+
#### Removed — dead module-level constants
|
|
147
|
+
|
|
148
|
+
- `packages/a2ui/validator/validator.js` — `WIRING_WEIGHTS` constant (zero refs).
|
|
149
|
+
- `packages/llm/adapters/openai.js` — `DEFAULT_MAX_TOKENS` constant (zero refs).
|
|
150
|
+
|
|
151
|
+
### v0.5.15 §319 — NEW audit slot 29: `check-exports-map-resolves.mjs`
|
|
152
|
+
|
|
153
|
+
Graduates the FB-32 bug class to a CI gate. Closes the recurring drift where a package.json `exports` map entry resolves (via wildcard expansion or static path) to a file that doesn't exist on disk — consumer TS imports fail with `TS2882` even though Vite/Node can fall through `node_modules/<pkg>/<path>` directly.
|
|
154
|
+
|
|
155
|
+
#### Added — `scripts/release/check-exports-map-resolves.mjs`
|
|
156
|
+
|
|
157
|
+
- ~270 LOC. Walks every `packages/<pkg>/package.json` `exports` map. For each STATIC entry, verifies the referenced file path exists. For each WILDCARD entry matching the FB-32 shape (`./X/*` key + `./X/*/*.<ext>` value), enumerates filesystem candidates that would match the wildcard + verifies each resolved path exists.
|
|
158
|
+
- Skips wildcard expansions where an explicit non-wildcard entry overrides the resolution (Node exports-map resolution algorithm precedence).
|
|
159
|
+
- Skips CSS-only / no-JS components (header / footer / section / aside / various web-modules shells) — these intentionally ship no `.js`.
|
|
160
|
+
- Result: 9 packages / 52 static + 39 wildcard exports entries / 59 resolutions verified / 0 unresolved.
|
|
161
|
+
|
|
162
|
+
### v0.5.15 §320 — NEW audit slot 30: `check-composes-passthrough.mjs`
|
|
163
|
+
|
|
164
|
+
Graduates the FB-33 bug class to a CI gate. Closes the recurring drift where a form-bearing primitive that programmatically creates ANOTHER form-bearing primitive in its lifecycle (light-DOM composition) doesn't expose / forward the composed primitive's documented prop + event-detail surface.
|
|
165
|
+
|
|
166
|
+
#### Added — `scripts/release/check-composes-passthrough.mjs`
|
|
167
|
+
|
|
168
|
+
- ~270 LOC. Static `COMPOSITION_PAIRS` list declares the required pass-through surface per pair. For each pair: verifies composing primitive's yaml `composes:` block lists the composed primitive; verifies yaml `props:` block exposes every required prop; verifies yaml `events:` block declares each event with required detail keys.
|
|
169
|
+
- Initial pair: `color-input → color-picker` (8 props + 12 event-detail keys across `change` + `input`).
|
|
170
|
+
- Adding a new pair: any new primitive that programmatically wraps a form-bearing primitive declares its contract in `COMPOSITION_PAIRS`; the audit fires at PR time.
|
|
171
|
+
- Companion to slot 23 (`check-form-bearing-dts.mjs`): that audit checks .d.ts type-coverage; this audit checks yaml-declared shape.
|
|
172
|
+
|
|
173
|
+
### v0.5.15 §321 — NEW audit slot 31: `check-token-name-canonical-shape.mjs`
|
|
174
|
+
|
|
175
|
+
Graduates the stepper / list state-first naming drift class to a CI gate. Closes the recurring drift where a component-local CSS token uses state-first (`--stepper-active-bg`) or color-infix-state (`--link-color-hover`) naming instead of the canonical property-first state-suffix shape (`--<comp>(-<sub>)*-<prop>(-<state>)?`).
|
|
176
|
+
|
|
177
|
+
#### Added — `scripts/release/check-token-name-canonical-shape.mjs`
|
|
178
|
+
|
|
179
|
+
- ~210 LOC. Extracts `--<comp>-*` declarations from each component CSS file. Classifies each by segment shape: canonical-prop / canonical-prop-state / canonical-leaf / canonical-domain / drift-state-first / drift-infix-color-state.
|
|
180
|
+
- Vocabularies (PROP_TOKENS + STATE_TOKENS) capture the catalog's recognized property + state terms (bg/fg/border/ring/shadow/color/outline/size + hover/active/selected/disabled/focus/checked/done/...).
|
|
181
|
+
- Result: 92 components / 1809 component-local tokens audited / 0 drift findings.
|
|
182
|
+
- `link.css` `--link-color-hover` not flagged at v0.5.15 ship: `color` is a recognized PROP_TOKEN + `hover` is a recognized STATE_TOKEN — the shape matches canonical. A future, stricter audit (alternative-property-naming) could flag the `color` vs `fg` divergence; deferred to v0.6.0 BREAKING window since link.css's `--link-color-*` are documented public API.
|
|
183
|
+
|
|
184
|
+
### v0.5.15 §316 — Naming-convention drift fix (stepper + list state-first → property-first)
|
|
185
|
+
|
|
186
|
+
`<stepper-ui>` and `<list-ui>` declared internal tokens with state-first naming (`--stepper-active-bg`, `--list-selected-fg`, etc.) — the catalog convention is property-first (`--<comp>-{prop}-{state}`). 18 token names renamed; usage sites updated in-place. `<link-ui>` skipped — its `--link-color-*` tokens are documented public API per `link.yaml tokens:` block; renaming is BREAKING and defers to v0.6.0.
|
|
187
|
+
|
|
188
|
+
#### Changed — `components/stepper/`, `components/list/`
|
|
189
|
+
|
|
190
|
+
- `stepper.css` — 12 tokens renamed: `--stepper-{active,done}-{bg,border,fg}` + `--stepper-item-{active,done}-{bg,border,fg}` → `--stepper-{bg,border,fg}-{active,done}` + `--stepper-item-{bg,border,fg}-{active,done}`.
|
|
191
|
+
- `list.css` — 3 tokens renamed: `--list-selected-{bg,fg,border}` → `--list-{bg,fg,border}-selected`.
|
|
192
|
+
|
|
193
|
+
### v0.5.15 §316 — Hand-rolled transition-duration literals → token references (swatch + heatmap + page)
|
|
194
|
+
|
|
195
|
+
Three components hand-rolled `100ms ease-out` / `120ms ease-out` / `0.1s ease-out` / `150ms ease` instead of routing through the canonical `var(--a-duration-fast)` + `var(--a-easing*)` token chain. Theme designers couldn't swap transition cadence centrally.
|
|
196
|
+
|
|
197
|
+
#### Changed — `components/swatch/`, `components/heatmap/`, `components/page/`
|
|
198
|
+
|
|
199
|
+
- `swatch.css:294,311` — `100ms ease-out` + `120ms ease-out` → `var(--a-duration-fast) var(--a-easing-out)`.
|
|
200
|
+
- `heatmap.css:80` — `0.1s ease-out` → `var(--a-duration-fast) var(--a-easing-out)`.
|
|
201
|
+
- `page.css:61` — `150ms ease` → `var(--a-duration-fast) var(--a-easing)`.
|
|
202
|
+
|
|
203
|
+
### v0.5.15 §317 — NEW audit slot 27: `check-raw-fg-in-component-css.mjs`
|
|
204
|
+
|
|
205
|
+
Graduates the §313/§315/§316 bug class to a CI gate. Closes the recurring drift class where component CSS rules bypass their own `--<comp>-<family>-<state>` semantic-layer token and reference the raw global `--a-<family>-<state>` token directly. The audit slot found 5 additional bypass instances across the codebase (nav-group + select + toolbar) — all routed through the component-local semantic layer in this commit.
|
|
206
|
+
|
|
207
|
+
#### Added — `scripts/release/check-raw-fg-in-component-css.mjs`
|
|
208
|
+
|
|
209
|
+
- ~190 LOC audit script. Parses each `<comp>.css` for the `:where(:scope) { ... }` declaration block + the set of `--<comp>-*` tokens defined therein. Scans the remainder for `<standard-css-property>: var(--a-<family>-<state>?)` usage where the parallel `--<comp>-<family>-<state>?` is defined — emits a finding per bypass.
|
|
210
|
+
- Whitelisted CSS properties: `color`, `background`, `background-color`, `border` (+ all sub-axis variants), `outline`, `fill`, `stroke`, `caret-color`, `accent-color`, `text-decoration-color`, `column-rule-color`.
|
|
211
|
+
- Distinguishes declaration sites (`--<comp>-X: var(--a-X)`) from usage sites mechanically — declaration sites are NOT flagged (those are correct semantic-layer aliasing).
|
|
212
|
+
- Companion to slot 25 (`check-component-css-vs-styles-tokens.mjs`): that audit catches orphan references (`var(--a-X)` where `--a-X` doesn't exist); this audit catches the inverse (`var(--a-X)` where it exists but `--<comp>-X` should be preferred).
|
|
213
|
+
- EXPECTED_FINDINGS baseline: 0 entries at v0.5.15 ship (sweep landed every concrete bypass).
|
|
214
|
+
|
|
215
|
+
#### Fixed — 5 component CSS bypass sites
|
|
216
|
+
|
|
217
|
+
- `nav-group.css:243,252` — `background: var(--a-bg-hover)` → `var(--nav-group-bg-hover)`.
|
|
218
|
+
- `select.css:199` — listbox container `color: var(--a-fg)` → `var(--select-fg)`.
|
|
219
|
+
- `select.css:319` — hint slot fallback `var(--a-fg-muted)` → `var(--select-fg-muted)`.
|
|
220
|
+
- `toolbar.css:107` — overflow popover `color: var(--a-fg)` → `var(--toolbar-fg)`.
|
|
221
|
+
|
|
222
|
+
### v0.5.15 §317 — NEW audit slot 28: `check-form-bearing-events-symmetric.mjs`
|
|
223
|
+
|
|
224
|
+
Graduates the FB-31/§315 bug class to a CI gate. Closes the recurring drift class where UIFormElement-extending primitives emit only `input` (or only `complete`/`search`/`pick`) without the canonical `change` event with `detail.value`. The audit confirms all 17 form-bearing primitives are now symmetric post-v0.5.14 §315.
|
|
225
|
+
|
|
226
|
+
#### Added — `scripts/release/check-form-bearing-events-symmetric.mjs`
|
|
227
|
+
|
|
228
|
+
- ~145 LOC audit script. Walks every `class.js` for classes that `extends UIFormElement`. For each, verifies a `dispatchEvent(new CustomEvent('change', { detail: { value: ... } }))` occurs (statically or via dynamic-name dispatch + nearby `'change'` string literal + `detail = { value: ... }` construction).
|
|
229
|
+
- Heuristic limitations documented: false negatives possible if event name is computed via complex variable assignment; manual review at addition time required.
|
|
230
|
+
- ALLOW_LIST mechanism for primitives where the commit model legitimately omits `change` (none at v0.5.15 ship).
|
|
231
|
+
- Companion to slot 23 (`check-form-bearing-dts.mjs`): that audit verifies typed `<Comp>ChangeEvent` exists in `.d.ts`; this audit verifies the runtime dispatch.
|
|
232
|
+
|
|
233
|
+
### v0.5.15 §318 — ADR-0028: L3 state-matrix orphan retirement (78 unused tokens deleted)
|
|
234
|
+
|
|
235
|
+
ADR-0028 ratifies the deletion of 78 orphaned L3 state-matrix tokens from `semantics.css`. The `maintain-tokens` skill audit during v0.5.14 §315 surfaced that 73 of ~80 L3 `--a-{family}-{prop}-{state}` tokens have zero consumers across the entire codebase. The per-primitive `--<comp>-*` token pattern (validated across v0.5.13/14/15 sweeps) is the actual consumer-facing API.
|
|
236
|
+
|
|
237
|
+
#### Removed — `styles/colors/semantics.css`
|
|
238
|
+
|
|
239
|
+
- 78 `--a-{accent,primary,brand,info,success,warning,danger,fg,border}-{fg,bg,border}-{active,selected,disabled,invalid}` declarations deleted. ~80 LOC reduction.
|
|
240
|
+
- Token count dropped 1301 → 1217 (-6.5%).
|
|
241
|
+
- Kept (have consumers): `--a-bg-{active,selected,disabled,invalid}`, `--a-fg-{selected,disabled}`, `--a-accent-bg-active`, `--a-primary-bg-active` (transitive via accent-bg-active).
|
|
242
|
+
- Kept (consumer-facing hover state): all `--a-*-hover` variants — hover is the only state where the generic-L3 layer is load-bearing.
|
|
243
|
+
|
|
244
|
+
#### Added — `.brain/adrs/0028-l3-state-matrix-orphan-retirement.md`
|
|
245
|
+
|
|
246
|
+
- ADR documents the audit-driven discovery, the retention rationale for the 7 used tokens, the deletion rationale for the 78 orphans, alternatives considered (keep all / delete only family-specific / add audit), and the per-primitive convention as the canonical consumer-facing override surface.
|
|
247
|
+
|
|
14
248
|
## [0.5.14] - 2026-05-15
|
|
15
249
|
|
|
16
250
|
### v0.5.14 — Form-control hover-fg token unification (precursor to §315; commit `178418525`)
|
|
@@ -40,7 +40,11 @@ const STATUS_MAP = {
|
|
|
40
40
|
export class UIBadge extends UIElement {
|
|
41
41
|
static properties = {
|
|
42
42
|
text: { type: String, default: '', reflect: true },
|
|
43
|
-
|
|
43
|
+
// NOTE: `textContent` is intentionally NOT declared as a reactive
|
|
44
|
+
// prop — `installProps()` would override the native DOM setter, so
|
|
45
|
+
// `el.textContent = '…'` would silently no-op. Yaml keeps the prop
|
|
46
|
+
// entry for renderer metadata, but the JS class must not declare
|
|
47
|
+
// it. v0.5.x §327.
|
|
44
48
|
variant: { type: String, default: 'default', reflect: true },
|
|
45
49
|
size: { type: String, default: 'md', reflect: true },
|
|
46
50
|
icon: { type: String, default: '', reflect: true },
|
|
@@ -19,7 +19,11 @@ import { getIcon } from '../../core/icons.js';
|
|
|
19
19
|
export class UIButton extends UIElement {
|
|
20
20
|
static properties = {
|
|
21
21
|
text: { type: String, default: '', reflect: true },
|
|
22
|
-
|
|
22
|
+
// NOTE: `textContent` is intentionally NOT declared as a reactive
|
|
23
|
+
// prop — `installProps()` would override the native DOM setter, so
|
|
24
|
+
// `el.textContent = '…'` would silently no-op instead of updating
|
|
25
|
+
// child text. Yaml keeps the prop entry for renderer metadata, but
|
|
26
|
+
// the JS class must not declare it. v0.5.x §327.
|
|
23
27
|
variant: { type: String, default: 'solid', reflect: true },
|
|
24
28
|
color: { type: String, default: '', reflect: true },
|
|
25
29
|
size: { type: String, default: 'md', reflect: true },
|
package/components/code/class.js
CHANGED
|
@@ -62,7 +62,13 @@ export class UICode extends UIElement {
|
|
|
62
62
|
language: { type: String, default: '', reflect: true },
|
|
63
63
|
inline: { type: Boolean, default: false, reflect: true },
|
|
64
64
|
text: { type: String, default: '', reflect: true },
|
|
65
|
-
|
|
65
|
+
// NOTE: `textContent` is intentionally NOT declared as a reactive prop.
|
|
66
|
+
// `installProps()` would override the native DOM setter with a signal
|
|
67
|
+
// setter, breaking `this.textContent = ''` in #stampBlock (the wipe
|
|
68
|
+
// becomes a no-op, leaving the authored HTML-escaped source visible
|
|
69
|
+
// above the stamped chrome and feeding an empty doc to CodeMirror).
|
|
70
|
+
// Initial content is read via the native getter in #stampBlock; live
|
|
71
|
+
// updates flow through the reactive `text` property. v0.5.x §327.
|
|
66
72
|
lineNumbers: { type: Boolean, default: false, reflect: true, attribute: 'line-numbers' },
|
|
67
73
|
editable: { type: Boolean, default: false, reflect: true },
|
|
68
74
|
bare: { type: Boolean, default: false, reflect: true },
|
|
@@ -132,10 +138,25 @@ export class UICode extends UIElement {
|
|
|
132
138
|
}
|
|
133
139
|
|
|
134
140
|
#stampBlock() {
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
|
|
141
|
+
// Content-source priority:
|
|
142
|
+
// 1. `text` attribute — reactive, programmatic.
|
|
143
|
+
// 2. `<template>` child — escape-free authoring path. The HTML parser
|
|
144
|
+
// treats `<template>` content as inert (DocumentFragment, not
|
|
145
|
+
// rendered); we read `template.innerHTML` to recover the literal
|
|
146
|
+
// source. Author can write `<code-ui language="html"><template>
|
|
147
|
+
// <nav-ui>…</nav-ui></template></code-ui>` without `<`/`>`
|
|
148
|
+
// escaping. Indent is normalized via #dedent() so the inner block
|
|
149
|
+
// doesn't carry the wrapper's indentation. (v0.5.x §329)
|
|
150
|
+
// 3. Authored textContent — original path, requires HTML entities
|
|
151
|
+
// for `<` and `>` (same constraint as `<pre><code>`).
|
|
152
|
+
// The block is rebuilt from semantic elements so whitespace in source
|
|
153
|
+
// HTML doesn't leak into the rendered <code>.
|
|
154
|
+
const tmpl = this.querySelector(':scope > template');
|
|
155
|
+
const raw = this.text
|
|
156
|
+
? this.text.trim()
|
|
157
|
+
: tmpl
|
|
158
|
+
? UICode.#dedent(tmpl.innerHTML)
|
|
159
|
+
: (this.textContent || '').trim();
|
|
139
160
|
this.textContent = '';
|
|
140
161
|
|
|
141
162
|
// Header — omitted in [bare] mode. Consumers that need zero chrome
|
|
@@ -206,6 +227,22 @@ export class UICode extends UIElement {
|
|
|
206
227
|
this.appendChild(pre);
|
|
207
228
|
}
|
|
208
229
|
|
|
230
|
+
/** Normalize indentation pulled from a `<template>` child. Strips
|
|
231
|
+
* the common leading whitespace from every non-empty line so the
|
|
232
|
+
* inner block reads as if it were authored at column 0. Equivalent
|
|
233
|
+
* in spirit to Python's `textwrap.dedent` or a tagged-template
|
|
234
|
+
* `dedent` helper. (v0.5.x §329) */
|
|
235
|
+
static #dedent(s) {
|
|
236
|
+
const trimmed = s.replace(/^\n+|\n+$/g, '');
|
|
237
|
+
if (!trimmed) return '';
|
|
238
|
+
const lines = trimmed.split('\n');
|
|
239
|
+
const indents = lines
|
|
240
|
+
.filter((l) => l.trim().length > 0)
|
|
241
|
+
.map((l) => l.match(/^[ \t]*/)[0].length);
|
|
242
|
+
const min = indents.length ? Math.min(...indents) : 0;
|
|
243
|
+
return min > 0 ? lines.map((l) => l.slice(min)).join('\n') : trimmed;
|
|
244
|
+
}
|
|
245
|
+
|
|
209
246
|
async #mountEditor() {
|
|
210
247
|
if (this.#pendingMount || this.#cmView) return;
|
|
211
248
|
const gen = this.#mountGen;
|
|
@@ -156,7 +156,7 @@
|
|
|
156
156
|
],
|
|
157
157
|
"slots": {
|
|
158
158
|
"default": {
|
|
159
|
-
"description": "Raw text fallback when the text property is not set"
|
|
159
|
+
"description": "Raw text fallback when the text property is not set. Two authoring shapes:\n(a) HTML-entity-escaped text (`<nav-ui>`) — same constraint as `<pre><code>`,\nsince the HTML parser treats unescaped tags as child elements; or\n(b) a `<template>` child whose `innerHTML` carries the literal source —\nescape-free authoring path. Indentation is dedented from the wrapper\ncolumn so the template's inner block reads at column 0. (v0.5.x §329)\n"
|
|
160
160
|
}
|
|
161
161
|
},
|
|
162
162
|
"states": [
|
|
@@ -40,6 +40,33 @@ export interface CodeLanguageLoadErrorEventDetail {
|
|
|
40
40
|
}
|
|
41
41
|
export type CodeLanguageLoadErrorEvent = CustomEvent<CodeLanguageLoadErrorEventDetail>;
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* `<code-ui>` — syntax-highlighted code block (CodeMirror 6 under the hood).
|
|
45
|
+
*
|
|
46
|
+
* Three content-source paths in priority order:
|
|
47
|
+
*
|
|
48
|
+
* 1. `[text]` attribute — reactive, programmatic; updates re-render the block.
|
|
49
|
+
*
|
|
50
|
+
* 2. `<template>` child — escape-free authoring path (v0.5.x §329). Author
|
|
51
|
+
* the source with literal `<` / `>` (the HTML parser treats `<template>`
|
|
52
|
+
* content as inert; it lives in a `DocumentFragment` instead of being
|
|
53
|
+
* upgraded into DOM elements). The wrapper indent is dedented so the
|
|
54
|
+
* inner block reads at column 0.
|
|
55
|
+
*
|
|
56
|
+
* ```html
|
|
57
|
+
* <code-ui language="html">
|
|
58
|
+
* <template>
|
|
59
|
+
* <nav-ui>
|
|
60
|
+
* <nav-item-ui text="Home"></nav-item-ui>
|
|
61
|
+
* </nav-ui>
|
|
62
|
+
* </template>
|
|
63
|
+
* </code-ui>
|
|
64
|
+
* ```
|
|
65
|
+
*
|
|
66
|
+
* 3. Authored textContent — original path. HTML samples require entity
|
|
67
|
+
* escaping (`<`/`>`/`&`), same constraint as `<pre><code>`,
|
|
68
|
+
* since the parser treats unescaped tags as child elements.
|
|
69
|
+
*/
|
|
43
70
|
export class UICode extends UIFormElement {
|
|
44
71
|
/** CodeMirror language slug — `javascript` / `typescript` / `html` / `css` / `json` / `markdown` / `python` etc. */
|
|
45
72
|
language: string;
|
|
@@ -93,7 +93,13 @@ events:
|
|
|
93
93
|
description: The underlying load failure.
|
|
94
94
|
slots:
|
|
95
95
|
default:
|
|
96
|
-
description:
|
|
96
|
+
description: |
|
|
97
|
+
Raw text fallback when the text property is not set. Two authoring shapes:
|
|
98
|
+
(a) HTML-entity-escaped text (`<nav-ui>`) — same constraint as `<pre><code>`,
|
|
99
|
+
since the HTML parser treats unescaped tags as child elements; or
|
|
100
|
+
(b) a `<template>` child whose `innerHTML` carries the literal source —
|
|
101
|
+
escape-free authoring path. Indentation is dedented from the wrapper
|
|
102
|
+
column so the template's inner block reads at column 0. (v0.5.x §329)
|
|
97
103
|
states:
|
|
98
104
|
- name: idle
|
|
99
105
|
description: Default, ready for interaction.
|
package/components/list/list.css
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
:where(:scope) {
|
|
3
3
|
/* ── Colors ── */
|
|
4
4
|
--list-divider-color: var(--a-border-subtle);
|
|
5
|
-
--list-selected
|
|
6
|
-
--list-selected
|
|
7
|
-
--list-selected
|
|
5
|
+
--list-bg-selected: var(--a-accent-muted);
|
|
6
|
+
--list-fg-selected: var(--a-accent-strong);
|
|
7
|
+
--list-border-selected: var(--a-accent-strong);
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
:scope {
|
|
@@ -68,9 +68,9 @@
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
:scope[selectable] > [role="listitem"][aria-selected="true"] {
|
|
71
|
-
background: var(--list-selected
|
|
72
|
-
color: var(--list-selected
|
|
73
|
-
border-inline-start-color: var(--list-selected
|
|
71
|
+
background: var(--list-bg-selected);
|
|
72
|
+
color: var(--list-fg-selected);
|
|
73
|
+
border-inline-start-color: var(--list-border-selected);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
:scope[selectable] > [role="listitem"]:focus-visible {
|
|
@@ -240,7 +240,7 @@ nav-group-ui [slot="popover"] [role="option"] {
|
|
|
240
240
|
}
|
|
241
241
|
|
|
242
242
|
nav-group-ui [slot="popover"] [role="option"]:hover {
|
|
243
|
-
background: var(--
|
|
243
|
+
background: var(--nav-group-bg-hover);
|
|
244
244
|
color: var(--nav-group-fg-hover);
|
|
245
245
|
}
|
|
246
246
|
|
|
@@ -249,7 +249,7 @@ nav-group-ui [slot="popover"] [role="option"]:hover {
|
|
|
249
249
|
nav-group-ui [slot="popover"] [role="option"][aria-current="page"],
|
|
250
250
|
nav-group-ui [slot="popover"] [role="option"][aria-selected="true"] {
|
|
251
251
|
position: relative;
|
|
252
|
-
background: var(--
|
|
252
|
+
background: var(--nav-group-bg-hover);
|
|
253
253
|
color: var(--nav-group-fg-selected);
|
|
254
254
|
font-weight: var(--a-weight-medium);
|
|
255
255
|
}
|
package/components/page/page.css
CHANGED
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
top: 0;
|
|
59
59
|
z-index: 1;
|
|
60
60
|
background: var(--page-sticky-bg);
|
|
61
|
-
transition: border-color
|
|
61
|
+
transition: border-color var(--a-duration-fast) var(--a-easing), box-shadow var(--a-duration-fast) var(--a-easing);
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
:scope[data-header-stuck] > :where(header, header-ui) {
|
|
@@ -36,6 +36,8 @@ export class UISegmented extends UIFormElement {
|
|
|
36
36
|
#indicator = null;
|
|
37
37
|
#bound = false;
|
|
38
38
|
#transitionRaf = null;
|
|
39
|
+
#resizeObs = null;
|
|
40
|
+
#fontWait = null;
|
|
39
41
|
|
|
40
42
|
connected() {
|
|
41
43
|
super.connected();
|
|
@@ -52,6 +54,20 @@ export class UISegmented extends UIFormElement {
|
|
|
52
54
|
const first = this.querySelector('segment-ui:not([disabled])');
|
|
53
55
|
if (first) this.value = first.value || first.getAttribute('value') || '';
|
|
54
56
|
}
|
|
57
|
+
|
|
58
|
+
// Recalculate indicator when layout shifts (font load, resize, content change)
|
|
59
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
60
|
+
this.#resizeObs = new ResizeObserver(() => this.#updateIndicator(this.#segments));
|
|
61
|
+
this.#resizeObs.observe(this);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Also wait for fonts to settle — a common source of initial-layout drift
|
|
65
|
+
if (document.fonts?.ready) {
|
|
66
|
+
this.#fontWait = document.fonts.ready.then(() => {
|
|
67
|
+
this.#fontWait = null;
|
|
68
|
+
this.#updateIndicator(this.#segments);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
55
71
|
}
|
|
56
72
|
|
|
57
73
|
disconnected() {
|
|
@@ -60,6 +76,12 @@ export class UISegmented extends UIFormElement {
|
|
|
60
76
|
cancelAnimationFrame(this.#transitionRaf);
|
|
61
77
|
this.#transitionRaf = null;
|
|
62
78
|
}
|
|
79
|
+
this.#resizeObs?.disconnect();
|
|
80
|
+
this.#resizeObs = null;
|
|
81
|
+
if (this.#fontWait) {
|
|
82
|
+
// Promise can't be cancelled; the then-handler checks this.isConnected
|
|
83
|
+
this.#fontWait = null;
|
|
84
|
+
}
|
|
63
85
|
this.removeEventListener('click', this.#handleClick);
|
|
64
86
|
this.removeEventListener('keydown', this.#handleKeydown);
|
|
65
87
|
this.#indicator = null;
|
|
@@ -196,7 +196,7 @@ select-ui [slot="listbox"] {
|
|
|
196
196
|
overflow-y: auto;
|
|
197
197
|
font-family: inherit;
|
|
198
198
|
font-size: var(--a-ui-size);
|
|
199
|
-
color: var(--
|
|
199
|
+
color: var(--select-fg);
|
|
200
200
|
|
|
201
201
|
/* Positioned by JS (#positionListbox) — fixed to viewport */
|
|
202
202
|
width: max-content;
|
|
@@ -316,6 +316,6 @@ select-ui > [slot="hint"] {
|
|
|
316
316
|
display: block;
|
|
317
317
|
margin-top: var(--select-hint-mt, var(--a-space-1));
|
|
318
318
|
font-size: var(--select-hint-size, var(--a-ui-xs));
|
|
319
|
-
color: var(--select-hint-fg, var(--
|
|
319
|
+
color: var(--select-hint-fg, var(--select-fg-muted));
|
|
320
320
|
line-height: var(--select-hint-lh, 1.4);
|
|
321
321
|
}
|