@adia-ai/web-components 0.6.22 → 0.6.24
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 +24 -0
- package/README.md +1 -1
- package/components/row/class.js +18 -2
- package/components/row/row.a2ui.json +6 -1
- package/components/row/row.d.ts +3 -1
- package/components/row/row.yaml +11 -1
- package/components/slider/class.js +15 -0
- package/components/slider/slider.test.js +39 -0
- package/package.json +1 -1
- package/styles/typography.css +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Changelog — @adia-ai/web-components
|
|
2
2
|
|
|
3
|
+
## [0.6.24] — 2026-05-22
|
|
4
|
+
|
|
5
|
+
### Fixed — `slider-ui` label and suffix template snapshot trap (FB-45)
|
|
6
|
+
|
|
7
|
+
- **`[slot="label"]` and `[slot="suffix"]` now re-sync in `render()`**, resolving the snapshot trap where `label=${expr}` via the template engine rendered the literal `{{p:N}}` placeholder instead of the resolved value. Both spans are updated on every reactive cycle; a missing label span is also created if absent at connection time. File: `components/slider/class.js`.
|
|
8
|
+
|
|
9
|
+
### Fixed — `toast-ui` demo shell missing `feed.css` import
|
|
10
|
+
|
|
11
|
+
- **`toast.html` now imports `feed/feed.css` + `feed/feed.js`.** `<toast-ui>` renders via `<feed-ui>` — the demo shell was not loading feed's stylesheet, so feed items rendered without background, typography, or spacing. Files: `components/toast/toast.html`.
|
|
12
|
+
|
|
13
|
+
## [0.6.23] — 2026-05-22
|
|
14
|
+
|
|
15
|
+
### Added — `row-ui` `wrap-at="bp"` responsive wrap (`components/row/`)
|
|
16
|
+
|
|
17
|
+
- **`wrap-at="bp"`** enables `flex-wrap: wrap` from the named breakpoint upward (`xs/sm/md/lg/xl`). Complements the boolean `[wrap]` (always-wrap, unchanged) for the common "single row on desktop, wraps on mobile" toolbar pattern: `<row-ui gap="2 3@md" wrap-at="sm">`. Uses inline `flex-wrap` to avoid reactive re-entrancy from toggling the boolean attribute inside `render()`. File: `components/row/row.js` + `components/row/row.yaml`.
|
|
18
|
+
|
|
19
|
+
### Added — `--a-ui-2xs: 11px;` UI typography scale slot (`styles/typography.css`)
|
|
20
|
+
|
|
21
|
+
- **`--a-ui-2xs`** (11px) slots cleanly into the existing UI scale between `--a-ui-tiny` (10px) and `--a-ui-xs` (12px). Currently unused — pre-staged for upcoming responsive UI-text variants that need a denser-than-`xs` step. File: `styles/typography.css`.
|
|
22
|
+
|
|
23
|
+
### Documentation — `components/` examples third sweep (§428/§432)
|
|
24
|
+
|
|
25
|
+
- **22 `components/*/*.examples.html` files** updated with @bp annotations + missing prop / token tables across the docs sweep arc (badge, block, button, card, check, code, col, fields, grid, image, link, list, modal, nav family, pane, select, stat, stream, table, tabs, tag, text-ui, toggle-group, tooltip, tree). Picks up the v0.6.22 `@bp` substrate in living demos so consumers see responsive composition shapes inline. Source journal sections: §428 (third sweep + token tables) + §432 (collision fix).
|
|
26
|
+
|
|
3
27
|
## [0.6.22] — 2026-05-22
|
|
4
28
|
|
|
5
29
|
### Added — `@bp` responsive attribute system (`core/responsive.js`)
|
package/README.md
CHANGED
|
@@ -215,7 +215,7 @@ class MyPanel extends UIElement {
|
|
|
215
215
|
The `el` parameter is the element instance — every signal-backed property is reactively read.
|
|
216
216
|
|
|
217
217
|
Full authoring contract: [`docs/specs/component-token-contract.md`](../../docs/specs/component-token-contract.md).
|
|
218
|
-
The `adia-ui-
|
|
218
|
+
The `adia-ui-authoring` skill encodes the 20 non-negotiable rules.
|
|
219
219
|
|
|
220
220
|
## Card-n / drawer-ui composition parity
|
|
221
221
|
|
package/components/row/class.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
import { UIElement } from '../../core/element.js';
|
|
22
22
|
import { draggable } from '../../traits/draggable/draggable.js';
|
|
23
|
-
import { parseResponsive, breakpoint } from '../../core/responsive.js';
|
|
23
|
+
import { parseResponsive, breakpoint, BP_NAMES } from '../../core/responsive.js';
|
|
24
24
|
|
|
25
25
|
export class UIRow extends UIElement {
|
|
26
26
|
static properties = {
|
|
@@ -29,6 +29,9 @@ export class UIRow extends UIElement {
|
|
|
29
29
|
gap: { type: String, default: 'md', reflect: true },
|
|
30
30
|
grow: { type: Boolean, default: false, reflect: true },
|
|
31
31
|
wrap: { type: Boolean, default: false, reflect: true },
|
|
32
|
+
// wrap-at="sm" = enable flex-wrap from sm upward; complements the
|
|
33
|
+
// boolean [wrap] (always-wrap) without modifying it.
|
|
34
|
+
wrapAt: { type: String, default: '', reflect: true, attribute: 'wrap-at' },
|
|
32
35
|
draggable: { type: Boolean, default: false, reflect: true },
|
|
33
36
|
};
|
|
34
37
|
static template = () => null;
|
|
@@ -53,7 +56,8 @@ export class UIRow extends UIElement {
|
|
|
53
56
|
const gap = this.gap;
|
|
54
57
|
const align = this.align;
|
|
55
58
|
const justify = this.justify;
|
|
56
|
-
const
|
|
59
|
+
const wrapAt = this.wrapAt;
|
|
60
|
+
const anyR = gap?.includes('@') || align?.includes('@') || justify?.includes('@') || !!wrapAt;
|
|
57
61
|
const bp = anyR ? breakpoint.value : '';
|
|
58
62
|
|
|
59
63
|
if (gap?.includes('@')) {
|
|
@@ -73,6 +77,18 @@ export class UIRow extends UIElement {
|
|
|
73
77
|
} else {
|
|
74
78
|
this.style.removeProperty('--row-justify');
|
|
75
79
|
}
|
|
80
|
+
|
|
81
|
+
// wrap-at="sm" enables flex-wrap from the named breakpoint upward.
|
|
82
|
+
// Uses inline style rather than toggling the [wrap] attribute to avoid
|
|
83
|
+
// reactive re-entrancy. Falls back to '' (CSS [wrap] handles the
|
|
84
|
+
// always-wrap case; this only fires when wrap-at is set).
|
|
85
|
+
if (wrapAt) {
|
|
86
|
+
const activeIdx = BP_NAMES.indexOf(bp);
|
|
87
|
+
const targetIdx = BP_NAMES.indexOf(wrapAt);
|
|
88
|
+
this.style.flexWrap = (targetIdx !== -1 && activeIdx >= targetIdx) ? 'wrap' : '';
|
|
89
|
+
} else {
|
|
90
|
+
this.style.flexWrap = '';
|
|
91
|
+
}
|
|
76
92
|
}
|
|
77
93
|
}
|
|
78
94
|
|
|
@@ -42,9 +42,14 @@
|
|
|
42
42
|
"default": "start"
|
|
43
43
|
},
|
|
44
44
|
"wrap": {
|
|
45
|
-
"description": "Enable flex wrap",
|
|
45
|
+
"description": "Enable flex-wrap unconditionally. For viewport-responsive wrapping use [wrap-at] instead.",
|
|
46
46
|
"type": "boolean",
|
|
47
47
|
"default": false
|
|
48
|
+
},
|
|
49
|
+
"wrapAt": {
|
|
50
|
+
"description": "Enable flex-wrap from the named breakpoint upward (xs/sm/md/lg/xl). `wrap-at=\"sm\"` = no-wrap on xs, wrap from sm up. Complements the boolean [wrap] (always-wrap) without conflicting with it.",
|
|
51
|
+
"type": "string",
|
|
52
|
+
"default": ""
|
|
48
53
|
}
|
|
49
54
|
},
|
|
50
55
|
"required": [
|
package/components/row/row.d.ts
CHANGED
|
@@ -30,6 +30,8 @@ export class UIRow extends UIElement {
|
|
|
30
30
|
grow: boolean;
|
|
31
31
|
/** Main-axis justify (start/center/end/between/space-between/space-around). Accepts `@bp` notation: justify="start between@md". */
|
|
32
32
|
justify: string;
|
|
33
|
-
/** Enable flex wrap */
|
|
33
|
+
/** Enable flex-wrap unconditionally. For viewport-responsive wrapping use [wrap-at] instead. */
|
|
34
34
|
wrap: boolean;
|
|
35
|
+
/** Enable flex-wrap from the named breakpoint upward (xs/sm/md/lg/xl). `wrap-at="sm"` = no-wrap on xs, wrap from sm up. Complements the boolean [wrap] (always-wrap) without conflicting with it. */
|
|
36
|
+
wrapAt: string;
|
|
35
37
|
}
|
package/components/row/row.yaml
CHANGED
|
@@ -44,9 +44,19 @@ props:
|
|
|
44
44
|
type: string
|
|
45
45
|
default: start
|
|
46
46
|
wrap:
|
|
47
|
-
description:
|
|
47
|
+
description: >-
|
|
48
|
+
Enable flex-wrap unconditionally. For viewport-responsive wrapping use
|
|
49
|
+
[wrap-at] instead.
|
|
48
50
|
type: boolean
|
|
49
51
|
default: false
|
|
52
|
+
wrapAt:
|
|
53
|
+
description: >-
|
|
54
|
+
Enable flex-wrap from the named breakpoint upward (xs/sm/md/lg/xl).
|
|
55
|
+
`wrap-at="sm"` = no-wrap on xs, wrap from sm up. Complements the
|
|
56
|
+
boolean [wrap] (always-wrap) without conflicting with it.
|
|
57
|
+
type: string
|
|
58
|
+
default: ""
|
|
59
|
+
attribute: wrap-at
|
|
50
60
|
events: {}
|
|
51
61
|
slots:
|
|
52
62
|
default:
|
|
@@ -149,6 +149,21 @@ export class UISlider extends UIFormElement {
|
|
|
149
149
|
const valueEl = this.querySelector('[slot="value"]');
|
|
150
150
|
if (valueEl) valueEl.textContent = this.#format(this.value);
|
|
151
151
|
|
|
152
|
+
// §FB-45: label and suffix are read in connected() but may be set later
|
|
153
|
+
// by the template engine (which first writes {{p:N}} then resolves).
|
|
154
|
+
// Re-sync both slots on every render so reactive bindings work.
|
|
155
|
+
const labelEl = this.querySelector('[slot="label"]');
|
|
156
|
+
if (labelEl) {
|
|
157
|
+
labelEl.textContent = this.label;
|
|
158
|
+
} else if (this.label) {
|
|
159
|
+
const header = this.querySelector('[slot="header"]');
|
|
160
|
+
if (header) { const s = document.createElement('span'); s.slot = 'label'; s.textContent = this.label; header.prepend(s); }
|
|
161
|
+
}
|
|
162
|
+
if (this.label) this.setAttribute('aria-label', this.label);
|
|
163
|
+
|
|
164
|
+
const suffixEl = this.querySelector('[slot="suffix"]');
|
|
165
|
+
if (suffixEl) suffixEl.textContent = this.suffix;
|
|
166
|
+
|
|
152
167
|
this.setAttribute('aria-valuenow', this.value);
|
|
153
168
|
this.setAttribute('aria-valuetext', `${this.#format(this.value)}${this.suffix ? ' ' + this.suffix : ''}`);
|
|
154
169
|
this.syncValue(String(this.value));
|
|
@@ -105,4 +105,43 @@ describe('slider-ui', () => {
|
|
|
105
105
|
await tick();
|
|
106
106
|
expect(s.getAttribute('aria-valuenow')).toBe('80');
|
|
107
107
|
});
|
|
108
|
+
|
|
109
|
+
// §FB-45 (color-app, 2026-05-22) — template engine snapshot trap.
|
|
110
|
+
// connected() stamped label/suffix once; render() didn't re-sync them.
|
|
111
|
+
// Template engine writes {{p:N}} first, then resolves to the real value —
|
|
112
|
+
// simulated here by setAttribute('label', placeholder) then setting the
|
|
113
|
+
// real value. Both spans must reflect the final value, not the placeholder.
|
|
114
|
+
it('re-syncs [slot="label"] when label attribute changes after connection', async () => {
|
|
115
|
+
const s = mount('<slider-ui value="50" min="0" max="100"></slider-ui>');
|
|
116
|
+
await tick();
|
|
117
|
+
// No label initially — span absent
|
|
118
|
+
expect(s.querySelector('[slot="label"]')).toBeNull();
|
|
119
|
+
|
|
120
|
+
// Simulate template engine writing placeholder then real value
|
|
121
|
+
s.setAttribute('label', '{{p:7}}');
|
|
122
|
+
await tick();
|
|
123
|
+
s.setAttribute('label', 'Volume');
|
|
124
|
+
await tick();
|
|
125
|
+
|
|
126
|
+
const labelEl = s.querySelector('[slot="label"]');
|
|
127
|
+
expect(labelEl).not.toBeNull();
|
|
128
|
+
expect(labelEl.textContent).toBe('Volume');
|
|
129
|
+
expect(s.getAttribute('aria-label')).toBe('Volume');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('re-syncs [slot="suffix"] when suffix attribute changes after connection', async () => {
|
|
133
|
+
const s = mount('<slider-ui value="50" min="0" max="100" label="Volume" suffix="%"></slider-ui>');
|
|
134
|
+
await tick();
|
|
135
|
+
const suffixEl = s.querySelector('[slot="suffix"]');
|
|
136
|
+
expect(suffixEl).not.toBeNull();
|
|
137
|
+
expect(suffixEl.textContent).toBe('%');
|
|
138
|
+
|
|
139
|
+
// Simulate placeholder → resolved suffix
|
|
140
|
+
s.setAttribute('suffix', '{{p:8}}');
|
|
141
|
+
await tick();
|
|
142
|
+
s.setAttribute('suffix', 'rem');
|
|
143
|
+
await tick();
|
|
144
|
+
|
|
145
|
+
expect(s.querySelector('[slot="suffix"]').textContent).toBe('rem');
|
|
146
|
+
});
|
|
108
147
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adia-ai/web-components",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.24",
|
|
4
4
|
"description": "AdiaUI web components \u2014 vanilla custom elements. A2UI runtime (renderer, registry, streams, wiring) lives in @adia-ai/a2ui-runtime.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "./index.d.ts",
|