@adia-ai/web-components 0.6.32 → 0.6.33

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 CHANGED
@@ -1,5 +1,27 @@
1
1
  # Changelog — @adia-ai/web-components
2
2
 
3
+ ## [0.6.33] — 2026-05-23
4
+
5
+ ### Changed — `dist/icons-manifest.js` comment header updated (FB-53 §1 companion)
6
+
7
+ - **`dist/icons-manifest.js`** is regenerated as a companion to the web-modules co-emit (see `@adia-ai/web-modules` [Unreleased] entry). The manifest file content is byte-identical except for the header comment, which now mentions co-location with both `web-components.min.js` AND `web-modules/everything.min.js`. `dist/web-components.min.js` rebuilt as part of the same bundle pass; no functional change. Files: `dist/icons-manifest.js`, `dist/web-components.min.js`.
8
+
9
+ ### Fixed — `<table-ui raw>` is now a true visual-only passthrough (FB-53 §2)
10
+
11
+ - **`<table-ui raw>` previously applied the chrome reset but still ran the full data lifecycle**, so wrapping a consumer-authored native `<table>` with no `.data` set produced a phantom `[data-empty]` "No data" overlay above the consumer's content. Root cause: `class.js` `render()` had no `this.raw` short-circuit; `#renderOverlays` injected the empty-state placeholder unconditionally whenever `this.#data.length === 0`. Fix: `render()` now early-returns when `this.raw` is set — no header injection, no body reconciliation, no overlays, no aggregation, no pagination. Raw is now the documented "consumer owns the body shape" mode. yaml description expanded with the full semantic. NEW 4 vitest cases in `table.test.js` (lock the contract: no `[data-empty]`, no `[data-header]`, no `[data-body]`, no rowgroups; non-raw default still injects them); 18/18 passing. Files: `components/table/class.js`, `components/table/table.yaml`, `components/table/table.test.js`.
12
+
13
+ ### Fixed — `<field-ui>` no longer reserves row-gap for an absent message row (FB-54)
14
+
15
+ - **`field.css` `:scope` declared `row-gap: var(--field-gap)` unconditionally**, so when no `[slot="hint"]` and no `[slot="error"]` were present, the grid's zero-content message row still reserved `--field-gap` (~8 px) between itself and the control row. Result: `<field-ui inline>` rendered ~7-8 px taller than non-field-ui siblings in a `<row-ui align="center">`, visually "lifting" the label relative to sibling control text. Fix: move `row-gap: var(--field-gap)` into a `:has(> [slot="hint"], > [slot="error"])` guard so the gap is reserved only when a message slot is actually present. Existing consumers with hints/errors get the same rendering; consumers without get a tighter, sibling-matching height. `:has()` is baseline (Chromium 105 / Safari 15.4 / Firefox 121 — well within ADR-0007's floor). NEW source-grep regression tests in `field.test.js` lock the contract (19/19 passing). Files: `components/field/field.css`, `components/field/field.test.js`.
16
+
17
+ ### Changed — `<tabs-ui>` row height now anchors to `var(--a-size)` (was hardcoded `2.25rem`)
18
+
19
+ - **`--tabs-button-height` retargeted from the hardcoded `2.25rem` (36 px, equivalent to `[size="lg"]`) to `var(--a-size)`.** Tabs now respond to the universal `[size]` attribute system like `button-ui`, `select-ui`, `nav-item-ui`, and other row-family primitives — `<tabs-ui size="sm">` → 24 px, default → 30 px, `<tabs-ui size="lg">` → 36 px (all at default `--a-density: 1`). Default-rendered tabs visibly shrink from 36 px → 30 px. **Behavior change** for consumers depending on lg-by-default tabs: set `[size="lg"]` on the host (or override `--tabs-button-height: var(--a-size-lg)`) to restore. Caught by NEW `check:size-wiring` audit (commit `2d931b980`). Files: `components/tabs/tabs.css`, `components/tabs/tabs.examples.html`.
20
+
21
+ ### Maintenance — `<kbd-ui>` exempted from size-wiring audit
22
+
23
+ - **`scripts/release/check-size-wiring.mjs`** EXEMPT + YAML_SIZE_EXEMPT: added `kbd-ui` to both — `--kbd-height: 1.25rem` is intentional (keyboard-key cap matching small-text rhythm, not row-family) and the `size:` prop drives font-size scale, parallel to existing exemptions for `badge`, `tag`, `icon`. Pre-existing technical debt surfaced by the new audit in v0.6.32; not a v0.6.33-introduced violation. Files: `scripts/release/check-size-wiring.mjs`.
24
+
3
25
  ## [0.6.32] — 2026-05-23
4
26
 
5
27
  ### Fixed (P0) — `everything.js` CDN bundle now includes all primitives (FB-51)
@@ -33,10 +33,19 @@
33
33
  "control"
34
34
  "message";
35
35
  column-gap: var(--field-gap);
36
- row-gap: var(--field-gap);
37
36
  align-items: center;
38
37
  }
39
38
 
39
+ /* Gate `row-gap` on actual message presence — otherwise the grid
40
+ reserves --field-gap (~8px) between the control row and a
41
+ zero-content message row, making field-ui[inline] visibly taller
42
+ (~7-8 px) than non-field-ui siblings in a `<row-ui align="center">`.
43
+ FB-54 (v0.6.32) — closes the trap for compact filter bars that
44
+ mix field-ui with bare primitives. */
45
+ :scope:has(> [slot="hint"], > [slot="error"]) {
46
+ row-gap: var(--field-gap);
47
+ }
48
+
40
49
  /* Stacked + (trailing or action) → 2-col */
41
50
  :scope:has(> :is([slot="trailing"], [slot="action"])) {
42
51
  grid-template-columns: minmax(0, 1fr) auto;
@@ -1,4 +1,7 @@
1
1
  import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { readFileSync } from 'node:fs';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { dirname, resolve } from 'node:path';
2
5
  import '../../core/element.js';
3
6
  import './field.js';
4
7
  // Preload <input-ui> at module top so the error-mirror test can rely on
@@ -8,6 +11,9 @@ import './field.js';
8
11
  // it to top-level removes that race entirely.
9
12
  import '../input/input.js';
10
13
 
14
+ const HERE = dirname(fileURLToPath(import.meta.url));
15
+ const FIELD_CSS = readFileSync(resolve(HERE, 'field.css'), 'utf8');
16
+
11
17
  const tick = () => new Promise((r) => queueMicrotask(r));
12
18
 
13
19
  function mount(html) {
@@ -200,4 +206,30 @@ describe('field-ui', () => {
200
206
  const f = mount('<field-ui label="X"><input /></field-ui>');
201
207
  expect(hasAlignProp(f)).toBe(true);
202
208
  });
209
+
210
+ // ── FB-54 row-gap presence guard ──────────────────────────────────
211
+ //
212
+ // field-ui grid's :scope previously declared `row-gap: var(--field-gap)`
213
+ // unconditionally. Without a hint/error slot the message row collapses
214
+ // to 0px content, but the gap is still reserved — so field-ui[inline]
215
+ // is ~7-8 px taller than non-field-ui siblings in a row-ui[align=center].
216
+ //
217
+ // The fix moves `row-gap` into a `:has(> [slot="hint"], > [slot="error"])`
218
+ // guard. jsdom doesn't evaluate @scope rules in getComputedStyle, so the
219
+ // contract is locked by source-grep — same recipe as
220
+ // description-list / text-ui / tag-ui contract pins.
221
+
222
+ it('CSS source contract: base :scope does NOT declare unconditional row-gap', () => {
223
+ // Match the `:scope { ... }` block (no chained attribute/pseudo selectors).
224
+ // Within that block, `row-gap` must NOT appear.
225
+ const scopeBlock = FIELD_CSS.match(/:scope\s*\{[^}]+\}/);
226
+ expect(scopeBlock).not.toBeNull();
227
+ expect(scopeBlock[0]).not.toMatch(/row-gap\s*:/);
228
+ });
229
+
230
+ it('CSS source contract: `:scope:has(> [slot="hint"], > [slot="error"])` declares row-gap', () => {
231
+ expect(FIELD_CSS).toMatch(
232
+ /:scope:has\(>\s*\[slot="hint"\]\s*,\s*>\s*\[slot="error"\]\)\s*\{[^}]*row-gap:\s*var\(--field-gap\)/
233
+ );
234
+ });
203
235
  });
@@ -541,6 +541,15 @@ export class UITable extends UIElement {
541
541
  // ── Render ─────────────────────────────────────────────────────────────────
542
542
 
543
543
  render() {
544
+ // Raw mode: visual-only passthrough. The consumer authored the entire
545
+ // body shape (native <table>, custom row rendering, etc.) and only wants
546
+ // table-ui's CSS tokens (chrome reset via :scope[raw]). The data
547
+ // lifecycle (header injection, body reconciliation, overlays, aggregation,
548
+ // pagination) MUST NOT run — otherwise `.data === []` injects a
549
+ // [data-empty] overlay above the consumer's content (FB-53 §2). Raw is
550
+ // the explicit "consumer owns layout" mode.
551
+ if (this.raw) return;
552
+
544
553
  const visCols = this.#visibleColumns;
545
554
 
546
555
  // Set grid template
@@ -52,7 +52,7 @@
52
52
  "default": 0
53
53
  },
54
54
  "raw": {
55
- "description": "Raw mode",
55
+ "description": "Visual-only passthrough — applies `<table-ui>`'s chrome reset (background / border / border-radius all transparent) AND short-circuits the data lifecycle entirely. The consumer owns the body shape: no header injection, no row reconciliation from `.data`, no empty-state / loading overlays, no aggregation or pagination footers. Use raw when embedding `<table-ui>` inside surfaces that supply their own chrome (e.g. `<card-ui><section bleed>`), or when wrapping a consumer-authored native `<table>` for design-token styling without the framework's data semantics. Pre-v0.6.33 (FB-53 §2) `raw` was visual-only and the data lifecycle still ran — wrapping a `.data`-unset native table produced a phantom \"No data\" overlay. v0.6.33+ matches the documented contract.",
56
56
  "type": "boolean",
57
57
  "default": false
58
58
  },
@@ -98,7 +98,7 @@ export class UITable extends UIElement {
98
98
  loading: boolean;
99
99
  /** Rows per page. 0 = show all rows without pagination. When > 0, renders a pagination bar below the table. */
100
100
  paginate: number;
101
- /** Raw mode */
101
+ /** Visual-only passthrough — applies `<table-ui>`'s chrome reset (background / border / border-radius all transparent) AND short-circuits the data lifecycle entirely. The consumer owns the body shape: no header injection, no row reconciliation from `.data`, no empty-state / loading overlays, no aggregation or pagination footers. Use raw when embedding `<table-ui>` inside surfaces that supply their own chrome (e.g. `<card-ui><section bleed>`), or when wrapping a consumer-authored native `<table>` for design-token styling without the framework's data semantics. Pre-v0.6.33 (FB-53 §2) `raw` was visual-only and the data lifecycle still ran — wrapping a `.data`-unset native table produced a phantom "No data" overlay. v0.6.33+ matches the documented contract. */
102
102
  raw: boolean;
103
103
  /** Global search/filter string. Filters visible rows across all columns using case-insensitive substring matching. Resets to page 1 on change. */
104
104
  search: string;
@@ -283,3 +283,56 @@ describe('table-ui — FB-47 keyboard: Enter activates focused body cell', () =>
283
283
  expect(cellClickFired).toBe(false);
284
284
  });
285
285
  });
286
+
287
+ /**
288
+ * Raw mode: visual-only passthrough — consumer owns the body shape.
289
+ *
290
+ * Pre-v0.6.33 (FB-53 §2), `<table-ui raw>` only applied the CSS chrome
291
+ * reset (background/border/radius transparent) but the data lifecycle
292
+ * still ran. With `.data === []` (the natural state when wrapping a
293
+ * consumer-authored native <table>), `#renderOverlays` injected a
294
+ * [data-empty] "No data" placeholder above the consumer's content.
295
+ *
296
+ * v0.6.33 short-circuits `render()` at the top when `this.raw` is set:
297
+ * no header injection, no body reconciliation, no overlays, no
298
+ * aggregation, no pagination.
299
+ */
300
+ describe('table-ui — raw mode passthrough (FB-53 §2)', () => {
301
+ beforeEach(() => { document.body.innerHTML = ''; });
302
+
303
+ it('raw mode does NOT inject [data-empty] overlay when .data is empty', async () => {
304
+ const el = mount('<table-ui raw><table><tr><td>foo</td></tr></table></table-ui>');
305
+ // No .data set → empty by default. Without the raw early-return,
306
+ // #renderOverlays would create the [data-empty] placeholder.
307
+ await raf();
308
+ expect(el.querySelector(':scope > [data-empty]')).toBeNull();
309
+ // Consumer's native <table> survives untouched.
310
+ expect(el.querySelector(':scope > table')).not.toBeNull();
311
+ });
312
+
313
+ it('raw mode does NOT inject [data-header] or [data-body] rowgroups', async () => {
314
+ const el = mount('<table-ui raw><table><tr><td>x</td></tr></table></table-ui>');
315
+ await raf();
316
+ expect(el.querySelector(':scope > [data-header]')).toBeNull();
317
+ expect(el.querySelector(':scope > [data-body]')).toBeNull();
318
+ });
319
+
320
+ it('raw mode preserves consumer markup even when .columns + .data are set', async () => {
321
+ const el = mount('<table-ui raw><table id="consumer"><tr><td>consumer-owned</td></tr></table></table-ui>');
322
+ el.columns = COLS;
323
+ el.data = ROWS;
324
+ await raf();
325
+ // The consumer's native <table> is still the only table-shaped child.
326
+ expect(el.querySelector('#consumer')).not.toBeNull();
327
+ expect(el.querySelectorAll(':scope > [role="rowgroup"]').length).toBe(0);
328
+ });
329
+
330
+ it('non-raw mode (default) still injects header + body rowgroups', async () => {
331
+ const el = mount('<table-ui></table-ui>');
332
+ el.columns = COLS;
333
+ el.data = ROWS;
334
+ await raf();
335
+ expect(el.querySelector(':scope > [data-header]')).not.toBeNull();
336
+ expect(el.querySelector(':scope > [data-body]')).not.toBeNull();
337
+ });
338
+ });
@@ -55,7 +55,19 @@ props:
55
55
  type: number
56
56
  default: 0
57
57
  raw:
58
- description: Raw mode
58
+ description: >-
59
+ Visual-only passthrough — applies `<table-ui>`'s chrome reset
60
+ (background / border / border-radius all transparent) AND
61
+ short-circuits the data lifecycle entirely. The consumer
62
+ owns the body shape: no header injection, no row reconciliation
63
+ from `.data`, no empty-state / loading overlays, no aggregation
64
+ or pagination footers. Use raw when embedding `<table-ui>` inside
65
+ surfaces that supply their own chrome (e.g. `<card-ui><section
66
+ bleed>`), or when wrapping a consumer-authored native `<table>`
67
+ for design-token styling without the framework's data semantics.
68
+ Pre-v0.6.33 (FB-53 §2) `raw` was visual-only and the data lifecycle
69
+ still ran — wrapping a `.data`-unset native table produced a
70
+ phantom "No data" overlay. v0.6.33+ matches the documented contract.
59
71
  type: boolean
60
72
  default: false
61
73
  reflect: true
@@ -4,7 +4,7 @@
4
4
  --tabs-border: transparent;
5
5
  --tabs-bg: transparent;
6
6
  --tabs-radius: var(--a-radius-md);
7
- --tabs-button-height: 2.25rem;
7
+ --tabs-button-height: var(--a-size);
8
8
  --tabs-button-px: var(--a-space-2);
9
9
  --tabs-font-size: var(--a-ui-md);
10
10
  --tabs-gap: var(--a-space-1);
@@ -1,7 +1,7 @@
1
1
  // Auto-generated by scripts/build/bundle-js.mjs — do not edit.
2
- // Co-located with web-components.min.js so CDN consumers can load icon data
3
- // without a bundler. icons-phosphor.js fetches this file then lazy-loads
4
- // individual SVGs from the `cdn` URL below.
2
+ // Co-located with web-components.min.js + web-modules/everything.min.js
3
+ // so CDN consumers can load icon data without a bundler. icons-phosphor.js
4
+ // fetches this file then lazy-loads individual SVGs from the `cdn` URL below.
5
5
  export default {
6
6
  "cdn": "https://cdn.jsdelivr.net/npm/@phosphor-icons/core@2.1.1/assets/",
7
7
  "regular": [