@adia-ai/web-components 0.5.17 → 0.5.19
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/USAGE.md +68 -0
- package/components/command/USAGE.md +160 -0
- package/components/command/class.js +23 -6
- package/components/command/command.a2ui.json +5 -1
- package/components/command/command.d.ts +2 -0
- package/components/command/command.test.js +99 -0
- package/components/command/command.yaml +9 -1
- package/components/description-list/description-list.a2ui.json +3 -2
- package/components/description-list/description-list.css +16 -0
- package/components/description-list/description-list.d.ts +3 -2
- package/components/description-list/description-list.test.js +72 -0
- package/components/description-list/description-list.yaml +10 -1
- package/components/swatch/class.js +109 -14
- package/components/swatch/swatch.test.js +242 -10
- package/components/tree/class.js +15 -4
- package/components/tree/tree.a2ui.json +13 -1
- package/components/tree/tree.d.ts +6 -0
- package/components/tree/tree.test.js +103 -0
- package/components/tree/tree.yaml +14 -1
- package/core/template.test.js +101 -1
- package/package.json +1 -1
|
@@ -15,7 +15,11 @@ composes:
|
|
|
15
15
|
props: {}
|
|
16
16
|
events:
|
|
17
17
|
tree-select:
|
|
18
|
-
description:
|
|
18
|
+
description: >
|
|
19
|
+
Fired when an item is selected.
|
|
20
|
+
detail: { item, text, value, ctrlKey, metaKey, shiftKey }.
|
|
21
|
+
Modifier flags mirror the originating MouseEvent / KeyboardEvent;
|
|
22
|
+
all default false for programmatic select() calls.
|
|
19
23
|
detail:
|
|
20
24
|
item:
|
|
21
25
|
type: object
|
|
@@ -26,6 +30,15 @@ events:
|
|
|
26
30
|
value:
|
|
27
31
|
type: string
|
|
28
32
|
description: Item value attribute.
|
|
33
|
+
ctrlKey:
|
|
34
|
+
type: boolean
|
|
35
|
+
description: Ctrl key held during activation (false for programmatic select()).
|
|
36
|
+
metaKey:
|
|
37
|
+
type: boolean
|
|
38
|
+
description: Meta (Cmd) key held during activation (false for programmatic select()).
|
|
39
|
+
shiftKey:
|
|
40
|
+
type: boolean
|
|
41
|
+
description: Shift key held during activation (false for programmatic select()).
|
|
29
42
|
slots:
|
|
30
43
|
default (tree-item-ui children):
|
|
31
44
|
description: "Child content region for the `default (tree-item-ui children)` slot."
|
package/core/template.test.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
16
|
-
import { html, stamp } from './template.js';
|
|
16
|
+
import { html, stamp, repeat } from './template.js';
|
|
17
17
|
|
|
18
18
|
describe('html template — §250 (v0.5.11) ?attr=${bool} silent-failure trap', () => {
|
|
19
19
|
let container;
|
|
@@ -179,3 +179,103 @@ describe('html template — FB-40 (v0.5.16) apostrophe in HTML comments does NOT
|
|
|
179
179
|
expect(received).not.toBeNull();
|
|
180
180
|
});
|
|
181
181
|
});
|
|
182
|
+
|
|
183
|
+
describe('html template — FB-47 (v0.5.19) stamp() cache + repeat() keyed reuse', () => {
|
|
184
|
+
// FEEDBACK-47 claimed `stamp()` unconditionally calls `mount()` →
|
|
185
|
+
// `replaceChildren()`, defeating `repeat()` keyed reconciliation. Source
|
|
186
|
+
// inspection (template.js:94-102) shows `stamp()` has a per-container
|
|
187
|
+
// cache keyed on `result.strings`; `mount()` runs ONLY on cache miss
|
|
188
|
+
// (first stamp on this container OR new template strings). These tests
|
|
189
|
+
// verify the cache behavior empirically against the same-template
|
|
190
|
+
// re-stamp + repeat()-same-key paths.
|
|
191
|
+
|
|
192
|
+
let container;
|
|
193
|
+
|
|
194
|
+
beforeEach(() => {
|
|
195
|
+
container = document.createElement('div');
|
|
196
|
+
document.body.appendChild(container);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
afterEach(() => {
|
|
200
|
+
container.remove();
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('stamp() with same template strings reuses the mounted instance (no replaceChildren)', () => {
|
|
204
|
+
// Build a template factory whose strings literal is reference-stable
|
|
205
|
+
// (the tagged-template caller — closure captures `strings` once).
|
|
206
|
+
const make = (value) => html`<div class="probe">${value}</div>`;
|
|
207
|
+
|
|
208
|
+
stamp(make('first'), container);
|
|
209
|
+
const firstDiv = container.querySelector('.probe');
|
|
210
|
+
expect(firstDiv).not.toBeNull();
|
|
211
|
+
expect(firstDiv.textContent).toBe('first');
|
|
212
|
+
|
|
213
|
+
// Mark the existing node so we can prove it survives the re-stamp.
|
|
214
|
+
firstDiv.dataset.marker = 'preserved';
|
|
215
|
+
|
|
216
|
+
stamp(make('second'), container);
|
|
217
|
+
const secondDiv = container.querySelector('.probe');
|
|
218
|
+
expect(secondDiv).toBe(firstDiv); // ✅ same node
|
|
219
|
+
expect(secondDiv.dataset.marker).toBe('preserved'); // ✅ no replaceChildren
|
|
220
|
+
expect(secondDiv.textContent).toBe('second'); // ✅ value updated in-place
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('repeat() with same key preserves wrapper children across re-renders', () => {
|
|
224
|
+
// FB-47's central reproduction: same-key repeat() should preserve the
|
|
225
|
+
// per-item wrapper-span AND the child element stamped into it on the
|
|
226
|
+
// prior render. Both the outer template AND the per-item template must
|
|
227
|
+
// share strings references across renders (single tagged-template-literal
|
|
228
|
+
// sites) — JS spec gives each source site its own strings array, so
|
|
229
|
+
// inline-duplicating the html`` between stamp calls would force a
|
|
230
|
+
// cache miss and defeat preservation. Real consumers naturally satisfy
|
|
231
|
+
// this by having one render function called repeatedly.
|
|
232
|
+
const items = [{ id: 'a', label: 'first' }];
|
|
233
|
+
const tplFn = (item) => html`<div class="probe" data-id=${item.id}>${item.label}</div>`;
|
|
234
|
+
const renderAll = (its) => html`${repeat(its, (x) => x.id, tplFn)}`;
|
|
235
|
+
|
|
236
|
+
stamp(renderAll(items), container);
|
|
237
|
+
const firstDiv = container.querySelector('.probe[data-id="a"]');
|
|
238
|
+
expect(firstDiv).not.toBeNull();
|
|
239
|
+
|
|
240
|
+
// Append a manually-added child that the framework should NOT destroy.
|
|
241
|
+
const probe = document.createElement('span');
|
|
242
|
+
probe.id = 'fb47-manual-child';
|
|
243
|
+
probe.textContent = 'sentinel';
|
|
244
|
+
firstDiv.appendChild(probe);
|
|
245
|
+
|
|
246
|
+
// Re-stamp with the same items array (same key 'a').
|
|
247
|
+
items[0] = { id: 'a', label: 'second' };
|
|
248
|
+
stamp(renderAll(items), container);
|
|
249
|
+
|
|
250
|
+
const secondDiv = container.querySelector('.probe[data-id="a"]');
|
|
251
|
+
expect(secondDiv).toBe(firstDiv); // ✅ wrapper reused
|
|
252
|
+
expect(secondDiv.querySelector('#fb47-manual-child')).not.toBeNull();
|
|
253
|
+
expect(secondDiv.firstChild.nodeValue).toBe('second'); // text updated in-place
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it('Array.isArray() branch in applyValue does replace children on every re-render (the actual root cause)', () => {
|
|
257
|
+
// FB-47's empirical observation IS valid for `.map()` — the
|
|
258
|
+
// Array.isArray() branch in applyValue() (template.js:232-244) does
|
|
259
|
+
// unconditional `container.replaceChildren()` + allocates fresh wrapper
|
|
260
|
+
// spans per item. Switching consumers to repeat() avoids this. This
|
|
261
|
+
// test pins the current behavior so any future "make .map() smart"
|
|
262
|
+
// change is intentional, not accidental. Use a single render function
|
|
263
|
+
// (one tagged-template-literal site for the outer template) so the
|
|
264
|
+
// observed re-creation is isolated to the .map() Array branch, not
|
|
265
|
+
// a cache miss from inline-duplicated source sites.
|
|
266
|
+
const items = [{ id: 'a', label: 'first' }];
|
|
267
|
+
const tplFn = (item) => html`<div class="probe" data-id=${item.id}>${item.label}</div>`;
|
|
268
|
+
const renderAll = (its) => html`${its.map(tplFn)}`;
|
|
269
|
+
|
|
270
|
+
stamp(renderAll(items), container);
|
|
271
|
+
const firstDiv = container.querySelector('.probe[data-id="a"]');
|
|
272
|
+
expect(firstDiv).not.toBeNull();
|
|
273
|
+
|
|
274
|
+
items[0] = { id: 'a', label: 'second' };
|
|
275
|
+
stamp(renderAll(items), container);
|
|
276
|
+
|
|
277
|
+
const secondDiv = container.querySelector('.probe[data-id="a"]');
|
|
278
|
+
expect(secondDiv).not.toBe(firstDiv); // ✅ NEW node — .map() re-creates
|
|
279
|
+
expect(secondDiv.textContent).toBe('second');
|
|
280
|
+
});
|
|
281
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adia-ai/web-components",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.19",
|
|
4
4
|
"description": "AdiaUI web components — 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",
|