@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 +22 -0
- package/components/field/field.css +10 -1
- package/components/field/field.test.js +32 -0
- package/components/table/class.js +9 -0
- package/components/table/table.a2ui.json +1 -1
- package/components/table/table.d.ts +1 -1
- package/components/table/table.test.js +53 -0
- package/components/table/table.yaml +13 -1
- package/components/tabs/tabs.css +1 -1
- package/dist/icons-manifest.js +3 -3
- package/dist/web-components.min.css +1 -1
- package/dist/web-components.min.js +15 -15
- package/package.json +1 -1
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": "
|
|
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
|
-
/**
|
|
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:
|
|
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
|
package/components/tabs/tabs.css
CHANGED
|
@@ -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:
|
|
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);
|
package/dist/icons-manifest.js
CHANGED
|
@@ -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
|
|
3
|
-
// without a bundler. icons-phosphor.js
|
|
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": [
|