@adia-ai/web-components 0.7.23 → 0.7.25
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 +56 -25
- package/README.md +2 -2
- package/USAGE.md +60 -15
- package/components/accordion/accordion-item.a2ui.json +30 -1
- package/components/accordion/accordion-item.yaml +33 -0
- package/components/accordion/accordion.a2ui.json +9 -0
- package/components/accordion/accordion.class.js +53 -3
- package/components/accordion/accordion.css +61 -3
- package/components/accordion/accordion.d.ts +12 -0
- package/components/accordion/accordion.yaml +17 -0
- package/components/code/code.class.js +1 -1
- package/components/display-field/display-field.a2ui.json +185 -0
- package/components/display-field/display-field.css +91 -0
- package/components/display-field/display-field.d.ts +26 -0
- package/components/display-field/display-field.examples.md +44 -0
- package/components/display-field/display-field.js +141 -0
- package/components/display-field/display-field.test.js +115 -0
- package/components/display-field/display-field.yaml +191 -0
- package/components/drawer/drawer.class.js +2 -2
- package/components/feed/feed.a2ui.json +1 -1
- package/components/feed/feed.class.js +1 -1
- package/components/feed/feed.d.ts +1 -1
- package/components/feed/feed.yaml +2 -2
- package/components/field/field.class.js +1 -1
- package/components/icon/icon.class.js +34 -1
- package/components/index.js +1 -0
- package/components/loading-overlay/loading-overlay.class.js +1 -1
- package/components/loading-overlay/loading-overlay.yaml +1 -1
- package/components/modal/modal.class.js +1 -1
- package/components/nav/nav.class.js +1 -1
- package/components/nav/nav.examples.md +4 -4
- package/components/popover/popover.class.js +1 -1
- package/components/spinner/spinner.class.js +1 -1
- package/components/spinner/spinner.yaml +1 -1
- package/components/table/table.class.js +42 -11
- package/components/table/table.test.js +53 -0
- package/components/toast/toast.class.js +1 -1
- package/core/icons.js +14 -0
- package/custom-elements.json +5723 -2865
- package/dist/theme-provider.min.js +1 -1
- package/dist/web-components.min.css +1 -1
- package/dist/web-components.min.js +87 -79
- package/dist/web-components.sheet.js +1 -1
- package/package.json +3 -2
- package/styles/components.css +1 -0
- package/traits/CATEGORIES.md +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Hand-authored per SPEC-001 (docs/specs/implementation-ready/SPEC-001-spinner-loader.md).
|
|
1
|
+
# Hand-authored per SPEC-001 (.claude/docs/specs/implementation-ready/SPEC-001-spinner-loader.md).
|
|
2
2
|
# Edit this file; run `npm run build:components` to regenerate spinner.a2ui.json.
|
|
3
3
|
$schema: ../../../../scripts/schemas/component.yaml.schema.json
|
|
4
4
|
name: UISpinner
|
|
@@ -240,6 +240,10 @@ export class UITable extends UIElement {
|
|
|
240
240
|
// ── Public API: expansion ──
|
|
241
241
|
|
|
242
242
|
toggleExpand(index) {
|
|
243
|
+
// A row gated out by `rowExpandable` cannot be toggled — but always allow
|
|
244
|
+
// collapsing one that is already open (e.g. the predicate flipped while a
|
|
245
|
+
// detail pane was showing), so it can't get stuck expanded.
|
|
246
|
+
if (!this.#isRowExpandable(index) && !this.#expanded.has(index)) return;
|
|
243
247
|
if (this.#expanded.has(index)) {
|
|
244
248
|
this.#expanded.delete(index);
|
|
245
249
|
this.dispatchEvent(new CustomEvent('row-collapse', { detail: { index, row: this.#data[index] }, bubbles: true }));
|
|
@@ -255,6 +259,27 @@ export class UITable extends UIElement {
|
|
|
255
259
|
/** @type {((row: object, index: number) => HTMLElement)|null} */
|
|
256
260
|
expandRenderer = null;
|
|
257
261
|
|
|
262
|
+
/**
|
|
263
|
+
* Per-row expand gate. When `expandable` is on and this is set, a row shows
|
|
264
|
+
* the expand caret (and can open a detail pane) only when the predicate
|
|
265
|
+
* returns truthy for that row. Lets heterogeneous rows opt out of the
|
|
266
|
+
* uniform affordance — e.g. `rowExpandable: r => r.status === 'failed'` so a
|
|
267
|
+
* succeeded row carries no caret while a failed one does. The reserved
|
|
268
|
+
* expand column is kept either way, so column alignment never shifts.
|
|
269
|
+
* `null` (default) = every row is expandable (legacy behavior).
|
|
270
|
+
* @type {((row: object, index: number) => boolean)|null}
|
|
271
|
+
*/
|
|
272
|
+
rowExpandable = null;
|
|
273
|
+
|
|
274
|
+
/** True when `expandable` is on AND this row passes the per-row gate. */
|
|
275
|
+
#isRowExpandable(dataIndex) {
|
|
276
|
+
if (!this.expandable) return false;
|
|
277
|
+
if (typeof this.rowExpandable === 'function') {
|
|
278
|
+
return !!this.rowExpandable(this.#data[dataIndex], dataIndex);
|
|
279
|
+
}
|
|
280
|
+
return true;
|
|
281
|
+
}
|
|
282
|
+
|
|
258
283
|
// ── Public API: state persistence ──
|
|
259
284
|
|
|
260
285
|
getState() {
|
|
@@ -601,16 +626,18 @@ export class UITable extends UIElement {
|
|
|
601
626
|
if (existing) this.#updateRow(existing, idx, visCols);
|
|
602
627
|
|
|
603
628
|
// Expand toggle in first cell
|
|
604
|
-
if (this
|
|
629
|
+
if (this.#isRowExpandable(idx)) {
|
|
605
630
|
const isExpanded = this.#expanded.has(idx);
|
|
606
631
|
if (isExpanded) row.setAttribute('data-expanded', '');
|
|
607
632
|
else row.removeAttribute('data-expanded');
|
|
633
|
+
} else {
|
|
634
|
+
row.removeAttribute('data-expanded');
|
|
608
635
|
}
|
|
609
636
|
|
|
610
637
|
bodyChildren.push(row);
|
|
611
638
|
|
|
612
639
|
// Detail row (expansion)
|
|
613
|
-
if (this
|
|
640
|
+
if (this.#isRowExpandable(idx) && this.#expanded.has(idx)) {
|
|
614
641
|
let detail = body.querySelector(`:scope > [data-detail-row][data-for="${idx}"]`);
|
|
615
642
|
if (!detail) {
|
|
616
643
|
detail = document.createElement('div');
|
|
@@ -779,19 +806,23 @@ export class UITable extends UIElement {
|
|
|
779
806
|
|
|
780
807
|
const cells = [];
|
|
781
808
|
|
|
782
|
-
// Expand toggle cell
|
|
809
|
+
// Expand toggle cell. The reserved column cell is always emitted (so the
|
|
810
|
+
// grid stays aligned), but the caret button is stamped only when this row
|
|
811
|
+
// passes the per-row expand gate — non-expandable rows show an empty cell.
|
|
783
812
|
if (this.expandable) {
|
|
784
813
|
const cell = document.createElement('div');
|
|
785
814
|
cell.setAttribute('role', 'gridcell');
|
|
786
815
|
cell.setAttribute('data-expand-col', '');
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
816
|
+
if (this.#isRowExpandable(dataIndex)) {
|
|
817
|
+
const btn = document.createElement('button');
|
|
818
|
+
btn.setAttribute('data-expand-toggle', '');
|
|
819
|
+
btn.setAttribute('aria-label', 'Expand row');
|
|
820
|
+
const icon = document.createElement('icon-ui');
|
|
821
|
+
icon.setAttribute('name', 'caret-right');
|
|
822
|
+
icon.setAttribute('size', 'xs');
|
|
823
|
+
btn.appendChild(icon);
|
|
824
|
+
cell.appendChild(btn);
|
|
825
|
+
}
|
|
795
826
|
cells.push(cell);
|
|
796
827
|
}
|
|
797
828
|
|
|
@@ -336,3 +336,56 @@ describe('table-ui — raw mode passthrough (FB-53 §2)', () => {
|
|
|
336
336
|
expect(el.querySelector(':scope > [data-body]')).not.toBeNull();
|
|
337
337
|
});
|
|
338
338
|
});
|
|
339
|
+
|
|
340
|
+
// ── F6 (upstream): per-row expand gate via `rowExpandable` ───────────────────
|
|
341
|
+
describe('table-ui — F6 per-row expand suppression (rowExpandable)', () => {
|
|
342
|
+
beforeEach(() => { document.body.innerHTML = ''; });
|
|
343
|
+
|
|
344
|
+
const bodyRows = (el) =>
|
|
345
|
+
el.querySelectorAll(':scope > [data-body] > [role="row"]:not([data-detail-row])');
|
|
346
|
+
|
|
347
|
+
it('default (no predicate): every row carries an expand caret', async () => {
|
|
348
|
+
const el = mount('<table-ui expandable></table-ui>');
|
|
349
|
+
el.columns = COLS;
|
|
350
|
+
el.data = ROWS;
|
|
351
|
+
await raf();
|
|
352
|
+
const rows = bodyRows(el);
|
|
353
|
+
expect(rows.length).toBe(2);
|
|
354
|
+
for (const row of rows) {
|
|
355
|
+
expect(row.querySelector('[data-expand-toggle]')).not.toBeNull();
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
it('predicate suppresses the caret on gated rows but keeps the reserved cell', async () => {
|
|
360
|
+
const el = mount('<table-ui expandable></table-ui>');
|
|
361
|
+
el.columns = COLS;
|
|
362
|
+
el.data = ROWS;
|
|
363
|
+
el.rowExpandable = (row) => row.id === 2; // only Bob expandable
|
|
364
|
+
await raf();
|
|
365
|
+
const rows = bodyRows(el);
|
|
366
|
+
|
|
367
|
+
// Alice (id 1) — reserved cell present, NO caret button.
|
|
368
|
+
expect(rows[0].querySelector(':scope > [data-expand-col]')).not.toBeNull();
|
|
369
|
+
expect(rows[0].querySelector('[data-expand-toggle]')).toBeNull();
|
|
370
|
+
|
|
371
|
+
// Bob (id 2) — caret button present.
|
|
372
|
+
expect(rows[1].querySelector('[data-expand-toggle]')).not.toBeNull();
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
it('toggleExpand is a no-op on a gated row (no detail row renders)', async () => {
|
|
376
|
+
const el = mount('<table-ui expandable></table-ui>');
|
|
377
|
+
el.columns = COLS;
|
|
378
|
+
el.data = ROWS;
|
|
379
|
+
el.rowExpandable = (row) => row.id === 2;
|
|
380
|
+
await raf();
|
|
381
|
+
|
|
382
|
+
el.toggleExpand(0); // Alice — gated out
|
|
383
|
+
await raf();
|
|
384
|
+
expect(el.querySelector('[data-detail-row][data-for="0"]')).toBeNull();
|
|
385
|
+
expect(el.expanded).not.toContain(0);
|
|
386
|
+
|
|
387
|
+
el.toggleExpand(1); // Bob — allowed
|
|
388
|
+
await raf();
|
|
389
|
+
expect(el.expanded).toContain(1);
|
|
390
|
+
});
|
|
391
|
+
});
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
/**
|
|
15
15
|
* <toast-ui> — Thin facade over `<feed-ui>` / `UIFeed.post()`.
|
|
16
16
|
*
|
|
17
|
-
* Phase 4 of
|
|
17
|
+
* Phase 4 of `.claude/docs/specs/feed-channel.md` (SPEC-FEED-CHANNEL-001) —
|
|
18
18
|
* toast-ui no longer owns its own per-position container. Both
|
|
19
19
|
* declarative `<toast-ui>` and imperative `UIToast.show()` paths
|
|
20
20
|
* route through `UIFeed`. The element exists for back-compat:
|
package/core/icons.js
CHANGED
|
@@ -135,6 +135,20 @@ let registryReady = false;
|
|
|
135
135
|
let resolveRegistryReady;
|
|
136
136
|
export const whenIconRegistryReady = new Promise((r) => { resolveRegistryReady = r; });
|
|
137
137
|
|
|
138
|
+
/**
|
|
139
|
+
* True when NO icons are registered AND no loaders were ever installed — i.e.
|
|
140
|
+
* the icon subsystem was never wired. The usual cause: the zero-config
|
|
141
|
+
* `icons-phosphor.js` `import.meta.glob('/node_modules/...')` path is a
|
|
142
|
+
* build-time macro that does not transform from inside a published dependency
|
|
143
|
+
* under Vite 8 / rolldown, so `installIconLoaders` never ran and the registry
|
|
144
|
+
* stays empty. Distinct from a single bad icon name (registry non-empty, that
|
|
145
|
+
* one name absent). `<icon-ui>` uses this to fail loud once rather than render
|
|
146
|
+
* blank silently. See `USAGE.md` § Icons.
|
|
147
|
+
*/
|
|
148
|
+
export function iconRegistryUnwired() {
|
|
149
|
+
return registry.size === 0 && !registryReady;
|
|
150
|
+
}
|
|
151
|
+
|
|
138
152
|
/**
|
|
139
153
|
* Install a per-weight loader map and mark the registry ready. The map
|
|
140
154
|
* shape matches `import.meta.glob` output: `{ '/path/to/star.svg': loader }`.
|