@adia-ai/web-components 0.6.48 → 0.6.50
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 +39 -0
- package/components/agent-reasoning/agent-reasoning.class.js +1 -1
- package/components/agent-trace/agent-trace.css +10 -10
- package/components/agent-trace/agent-trace.js +3 -3
- package/components/calendar-grid/calendar-grid.a2ui.json +5 -0
- package/components/calendar-grid/calendar-grid.class.js +8 -1
- package/components/calendar-grid/calendar-grid.d.ts +2 -0
- package/components/calendar-grid/calendar-grid.yaml +8 -0
- package/components/date-range-picker/date-range-picker.class.js +9 -0
- package/components/field/field.test.js +7 -2
- package/components/pane/pane.a2ui.json +3 -0
- package/components/pane/pane.class.js +7 -6
- package/components/pane/pane.css +5 -5
- package/components/pane/pane.yaml +6 -0
- package/components/tree/tree-item.a2ui.json +6 -0
- package/components/tree/tree-item.yaml +13 -1
- package/components/tree/tree.a2ui.json +3 -3
- package/components/tree/tree.class.js +24 -9
- package/components/tree/tree.css +8 -8
- package/components/tree/tree.test.js +52 -0
- package/components/tree/tree.yaml +4 -4
- package/dist/web-components.min.css +1 -1
- package/dist/web-components.min.js +56 -56
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,44 @@
|
|
|
1
1
|
# Changelog — @adia-ai/web-components
|
|
2
2
|
|
|
3
|
+
## [0.6.50] — 2026-05-30
|
|
4
|
+
|
|
5
|
+
### Changed — `slot="caret"` disclosure-indicator convention + FEEDBACK-89 (ADR-0036)
|
|
6
|
+
|
|
7
|
+
- **`components/tree/` (`tree-item-ui`) + `components/pane/` (`pane-ui`)** — renamed the expand/collapse indicator slot `slot="chevron"` → **`slot="caret"`** (and tokens `--*-chevron-*` → `--*-caret-*`, e.g. `--tree-caret-size`, `--pane-caret-fg`), standardizing on the slot name already used by `accordion-item-ui` / `nav-group-ui` / `select-ui` and matching the Phosphor `caret-*` icon vocabulary. **No backward-compat alias** (hard rename; the caret is auto-stamped, so blast radius is small). See [ADR-0036](../../.brain/adrs/0036-caret-slot-disclosure-convention.md).
|
|
8
|
+
- **`tree-item-ui` adopt-or-stamp (FEEDBACK-89)** — `#stamp()` now adopts a consumer's declarative `slot="caret"` and `slot="actions"` children into the auto-stamped row instead of leaving them as siblings below it. A `<button-ui slot="actions">` (the natural way to add a per-row action — add / rename / overflow) now lands inline in the styled, hover-revealed actions area. The host `#onClick` already excludes `[slot="actions"] *` from row selection, so adoption is click-safe. +3 unit tests.
|
|
9
|
+
- **`dist/` CDN bundles** (`web-components.min.css` + `web-components.min.js`) regenerated to carry the renamed `--*-caret-*` tokens + caret stamp logic.
|
|
10
|
+
|
|
11
|
+
## [0.6.49] — 2026-05-30
|
|
12
|
+
|
|
13
|
+
### Changed — card-ui header slot sweep + `nav-group-ui` fixes (demos)
|
|
14
|
+
|
|
15
|
+
- **`patterns/*` + catalog demo HTML** — card-ui header anatomy swept to the canonical `<header>` slot grammar across admin-dashboard + 5 catalog patterns; `nav-group-ui` `label=` → `text=` + icon fixes. Demo/corpus-facing; absorbed into the chunk corpus regen.
|
|
16
|
+
- **31 module demos** — `<!-- design-plan -->` blocks retrofitted across the composite-demo-protocol campaign (Phase 1+3 artifacts embedded).
|
|
17
|
+
|
|
18
|
+
### Maintenance
|
|
19
|
+
|
|
20
|
+
- **`dist/web-components.min.js` + `dist/icons-manifest.js`** — bundle rebuild reflecting the calendar-grid `month` prop + date-range-picker class changes.
|
|
21
|
+
|
|
22
|
+
### Fixed — `<date-range-picker>` showed the same month in both panes
|
|
23
|
+
|
|
24
|
+
- **`components/calendar-grid/calendar-grid.{class.js,yaml}`** — new `month`
|
|
25
|
+
attribute (`YYYY-MM`): sets the displayed month when no `value` is selected, so
|
|
26
|
+
a consumer can show a specific month independent of selection. A selected value
|
|
27
|
+
always takes precedence.
|
|
28
|
+
- **`components/date-range-picker/date-range-picker.class.js`** — the end pane now
|
|
29
|
+
defaults to **one month after** the start pane (via the new `month` hint), so a
|
|
30
|
+
fresh range picker shows two consecutive months (e.g. May + June) instead of the
|
|
31
|
+
current month twice. A selected `to` date still drives its own pane. Propagates
|
|
32
|
+
to `<date-range-selector>` (embeds the picker).
|
|
33
|
+
|
|
34
|
+
### Fixed — `drop-target` demo card-structure violation
|
|
35
|
+
|
|
36
|
+
- **`traits/drop-target/drop-target.examples.html`** — the 4 `<card-ui>` drop
|
|
37
|
+
surfaces held `<text-ui>` / `<col-ui>` directly (bypassing the card body slot),
|
|
38
|
+
failing `check:card-structure` (a `check`-chain gate). Wrapped each in
|
|
39
|
+
`<section bleed style="display:grid;place-items:center">` — clears the gate while
|
|
40
|
+
keeping the centered drop-zone appearance. `audit:card-structure` now clean.
|
|
41
|
+
|
|
3
42
|
## [0.6.48] — 2026-05-29
|
|
4
43
|
|
|
5
44
|
### Demos — library-wide consolidation sweep (HTML-first)
|
|
@@ -379,7 +379,7 @@ export class UIAgentReasoning extends UIElement {
|
|
|
379
379
|
<span data-reasoning-meta>
|
|
380
380
|
${total ? `<span data-reasoning-counter>${done}/${total}</span>` : ''}
|
|
381
381
|
<span data-reasoning-time>${elapsed}s</span>
|
|
382
|
-
<icon-ui name="${this.collapsed ? 'caret-right' : 'caret-up'}" color="muted" data-reasoning-
|
|
382
|
+
<icon-ui name="${this.collapsed ? 'caret-right' : 'caret-up'}" color="muted" data-reasoning-caret></icon-ui>
|
|
383
383
|
</span>
|
|
384
384
|
`;
|
|
385
385
|
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
--agent-trace-detail-label-col-default: 9rem;
|
|
28
28
|
|
|
29
29
|
/* ── Motion ── */
|
|
30
|
-
--agent-trace-
|
|
30
|
+
--agent-trace-caret-dur-default: var(--a-duration-fast);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
:scope {
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
border-radius: var(--a-radius-sm);
|
|
51
51
|
padding: var(--a-space-0-5) var(--a-space-1);
|
|
52
52
|
margin: calc(var(--a-space-0-5) * -1) calc(var(--a-space-1) * -1);
|
|
53
|
-
transition: background var(--agent-trace-
|
|
53
|
+
transition: background var(--agent-trace-caret-dur, var(--agent-trace-caret-dur-default));
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
[data-trace-root] > summary:hover {
|
|
@@ -94,12 +94,12 @@
|
|
|
94
94
|
margin-inline: 2px;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
[data-trace-
|
|
97
|
+
[data-trace-caret] {
|
|
98
98
|
flex-shrink: 0;
|
|
99
|
-
transition: transform var(--agent-trace-
|
|
99
|
+
transition: transform var(--agent-trace-caret-dur, var(--agent-trace-caret-dur-default));
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
[data-trace-root][open] [data-trace-
|
|
102
|
+
[data-trace-root][open] [data-trace-caret] {
|
|
103
103
|
transform: rotate(90deg);
|
|
104
104
|
}
|
|
105
105
|
|
|
@@ -225,20 +225,20 @@
|
|
|
225
225
|
text-align: end;
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
-
/*
|
|
229
|
-
[data-trace-row-
|
|
228
|
+
/* Caret in the trailing column */
|
|
229
|
+
[data-trace-row-caret] {
|
|
230
230
|
--a-icon-size: 0.875em;
|
|
231
231
|
justify-self: end;
|
|
232
232
|
align-self: center;
|
|
233
|
-
transition: transform var(--a-duration-fast) var(--agent-trace-
|
|
233
|
+
transition: transform var(--a-duration-fast) var(--agent-trace-caret-dur, var(--a-duration-fast));
|
|
234
234
|
color: var(--agent-trace-fg-muted, var(--agent-trace-fg-muted-default));
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
-
details[data-trace-row][open] > summary > [data-trace-row-
|
|
237
|
+
details[data-trace-row][open] > summary > [data-trace-row-caret] {
|
|
238
238
|
transform: rotate(90deg);
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
-
[data-trace-row-
|
|
241
|
+
[data-trace-row-caret-spacer] {
|
|
242
242
|
width: 0.875em;
|
|
243
243
|
justify-self: end;
|
|
244
244
|
}
|
|
@@ -128,7 +128,7 @@ class UIAgentTrace extends UIElement {
|
|
|
128
128
|
this.#rootEl.innerHTML = `
|
|
129
129
|
<summary>
|
|
130
130
|
<span data-trace-status>${pillsHTML}</span>
|
|
131
|
-
<icon-ui name="caret-right" color="muted" size="sm" data-trace-
|
|
131
|
+
<icon-ui name="caret-right" color="muted" size="sm" data-trace-caret></icon-ui>
|
|
132
132
|
</summary>
|
|
133
133
|
<div data-trace-body>
|
|
134
134
|
${this.#metrics.length ? `<div data-trace-rows${hasAnyDetails ? ' data-has-details' : ''}>${headerRow}${rowsHTML}</div>` : ''}
|
|
@@ -156,8 +156,8 @@ class UIAgentTrace extends UIElement {
|
|
|
156
156
|
<span data-trace-aux>${aux}</span>
|
|
157
157
|
${hasAnyDetails
|
|
158
158
|
? (hasDetails
|
|
159
|
-
? '<icon-ui name="caret-right" color="muted" data-trace-row-
|
|
160
|
-
: '<span data-trace-row-
|
|
159
|
+
? '<icon-ui name="caret-right" color="muted" data-trace-row-caret></icon-ui>'
|
|
160
|
+
: '<span data-trace-row-caret-spacer aria-hidden="true"></span>')
|
|
161
161
|
: ''}
|
|
162
162
|
`;
|
|
163
163
|
|
|
@@ -31,6 +31,11 @@
|
|
|
31
31
|
"type": "string",
|
|
32
32
|
"default": ""
|
|
33
33
|
},
|
|
34
|
+
"month": {
|
|
35
|
+
"description": "Displayed month (YYYY-MM) used when no value is selected — shows a specific month independent of selection. A selected value always takes precedence. Used by date-range-picker to show its end pane one month ahead of the start.",
|
|
36
|
+
"type": "string",
|
|
37
|
+
"default": ""
|
|
38
|
+
},
|
|
34
39
|
"rangeEnd": {
|
|
35
40
|
"description": "End of a date range (ISO YYYY-MM-DD). See `rangeStart` for the full contract.",
|
|
36
41
|
"type": "string",
|
|
@@ -61,6 +61,10 @@ export class UICalendarGrid extends UIElement {
|
|
|
61
61
|
|
|
62
62
|
static properties = {
|
|
63
63
|
value: { type: String, default: '', reflect: true },
|
|
64
|
+
// Displayed month (`YYYY-MM`) when no `value` is selected — lets a consumer
|
|
65
|
+
// show a specific month independent of selection (e.g. a date-range picker
|
|
66
|
+
// showing its end pane one month ahead of the start pane). Selection wins.
|
|
67
|
+
month: { type: String, default: '', reflect: true },
|
|
64
68
|
min: { type: String, default: '', reflect: true },
|
|
65
69
|
max: { type: String, default: '', reflect: true },
|
|
66
70
|
disabled: { type: Boolean, default: false, reflect: true },
|
|
@@ -90,11 +94,14 @@ export class UICalendarGrid extends UIElement {
|
|
|
90
94
|
this.setAttribute('role', 'group');
|
|
91
95
|
if (!this.hasAttribute('aria-label')) this.setAttribute('aria-label', 'Calendar');
|
|
92
96
|
|
|
93
|
-
// Initialize view to selected date
|
|
97
|
+
// Initialize view to selected date, else the [month] hint, else today.
|
|
94
98
|
const sel = parseISO(this.value);
|
|
95
99
|
if (sel) {
|
|
96
100
|
this.#viewYear = sel.getFullYear();
|
|
97
101
|
this.#viewMonth = sel.getMonth();
|
|
102
|
+
} else {
|
|
103
|
+
const vm = /^(\d{4})-(\d{2})$/.exec(String(this.month || '').trim());
|
|
104
|
+
if (vm) { this.#viewYear = +vm[1]; this.#viewMonth = +vm[2] - 1; }
|
|
98
105
|
}
|
|
99
106
|
|
|
100
107
|
if (!this.#bound) {
|
|
@@ -22,6 +22,8 @@ export class UICalendarGrid extends UIElement {
|
|
|
22
22
|
max: string;
|
|
23
23
|
/** Minimum selectable date in ISO format (YYYY-MM-DD). */
|
|
24
24
|
min: string;
|
|
25
|
+
/** Displayed month (YYYY-MM) used when no value is selected — shows a specific month independent of selection. A selected value always takes precedence. Used by date-range-picker to show its end pane one month ahead of the start. */
|
|
26
|
+
month: string;
|
|
25
27
|
/** End of a date range (ISO YYYY-MM-DD). See `rangeStart` for the full contract. */
|
|
26
28
|
rangeEnd: string;
|
|
27
29
|
/** Start of a date range (ISO YYYY-MM-DD). When both rangeStart and rangeEnd are set + ordered, day cells strictly between the endpoints get `[data-in-range]` stamped for visual continuity. Used by `<date-range-picker-ui>` which pushes the same from/to onto both calendar panes. Endpoints themselves render via the `value` prop's `[data-selected]` state. */
|
|
@@ -22,6 +22,14 @@ props:
|
|
|
22
22
|
type: string
|
|
23
23
|
default: ''
|
|
24
24
|
reflect: true
|
|
25
|
+
month:
|
|
26
|
+
description: >-
|
|
27
|
+
Displayed month (YYYY-MM) used when no value is selected — shows a specific
|
|
28
|
+
month independent of selection. A selected value always takes precedence.
|
|
29
|
+
Used by date-range-picker to show its end pane one month ahead of the start.
|
|
30
|
+
type: string
|
|
31
|
+
default: ''
|
|
32
|
+
reflect: true
|
|
25
33
|
min:
|
|
26
34
|
description: Minimum selectable date in ISO format (YYYY-MM-DD).
|
|
27
35
|
type: string
|
|
@@ -411,6 +411,15 @@ export class UIDateRangePicker extends UIFormElement {
|
|
|
411
411
|
}
|
|
412
412
|
if (!calArea.querySelector(':scope > [data-cal-to]')) {
|
|
413
413
|
const calTo = this.constructor._pp.calTo.cloneNode(true);
|
|
414
|
+
// Default the end pane to one month after the start pane so a fresh range
|
|
415
|
+
// picker shows two consecutive months (not the same month twice). A `to`
|
|
416
|
+
// value, once selected, overrides this hint inside the grid.
|
|
417
|
+
const fromISO = parseRange(this.value)?.from;
|
|
418
|
+
let y, mo; // mo: 0-based month
|
|
419
|
+
if (fromISO) { const [yy, mm] = fromISO.split('-').map(Number); y = yy; mo = mm - 1; }
|
|
420
|
+
else { const now = new Date(); y = now.getFullYear(); mo = now.getMonth(); }
|
|
421
|
+
mo += 1; if (mo > 11) { mo = 0; y += 1; }
|
|
422
|
+
calTo.setAttribute('month', `${y}-${pad(mo + 1)}`);
|
|
414
423
|
calArea.appendChild(calTo);
|
|
415
424
|
}
|
|
416
425
|
this.#calFromRef = calArea.querySelector(':scope > [data-cal-from]');
|
|
@@ -227,9 +227,14 @@ describe('field-ui', () => {
|
|
|
227
227
|
expect(scopeBlock[0]).not.toMatch(/row-gap\s*:/);
|
|
228
228
|
});
|
|
229
229
|
|
|
230
|
-
it('CSS source contract: `:scope:has(
|
|
230
|
+
it('CSS source contract: `:scope:has([slot="hint"], [slot="error"])` declares row-gap', () => {
|
|
231
|
+
// The `>` direct-child combinator is OPTIONAL — field.css uses the
|
|
232
|
+
// descendant form `:has([slot="hint"], [slot="error"])` (v0.6.x); either
|
|
233
|
+
// satisfies the FB-54 row-gap-presence-guard contract.
|
|
231
234
|
expect(FIELD_CSS).toMatch(
|
|
232
|
-
|
|
235
|
+
// `var(--field-gap[,)]` accepts both the bare token and the override-aware
|
|
236
|
+
// `var(--field-gap, var(--field-gap-default))` chain (OD-5 token-shadowing).
|
|
237
|
+
/:scope:has\((?:>\s*)?\[slot="hint"\]\s*,\s*(?:>\s*)?\[slot="error"\]\)\s*\{[^}]*row-gap:\s*var\(--field-gap[,)]/
|
|
233
238
|
);
|
|
234
239
|
});
|
|
235
240
|
});
|
|
@@ -82,6 +82,9 @@
|
|
|
82
82
|
"text-area"
|
|
83
83
|
],
|
|
84
84
|
"slots": {
|
|
85
|
+
"caret": {
|
|
86
|
+
"description": "Collapse caret inside the header. Auto-stamped as `<icon-ui slot=\"caret\" name=\"caret-right\">` (rotates on `[collapsed]`). Supply your own `slot=\"caret\"` child in the header to customize — adopt-or-stamp honors a declarative caret instead of stamping."
|
|
87
|
+
},
|
|
85
88
|
"header": {
|
|
86
89
|
"description": "Auto-created header element with label text and toggle arrow"
|
|
87
90
|
}
|
|
@@ -100,12 +100,13 @@ export class UIPane extends UIElement {
|
|
|
100
100
|
header.setAttribute('tabindex', '0');
|
|
101
101
|
header.setAttribute('aria-expanded', String(!this.collapsed));
|
|
102
102
|
|
|
103
|
-
// Stamp
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
103
|
+
// Stamp the caret icon if not present (adopt-or-stamp: a declarative
|
|
104
|
+
// [slot="caret"] child is honored, else we stamp the default).
|
|
105
|
+
if (!header.querySelector('[slot="caret"]')) {
|
|
106
|
+
const caret = document.createElement('icon-ui');
|
|
107
|
+
caret.setAttribute('slot', 'caret');
|
|
108
|
+
caret.setAttribute('name', 'caret-right');
|
|
109
|
+
header.append(caret);
|
|
109
110
|
}
|
|
110
111
|
}
|
|
111
112
|
}
|
package/components/pane/pane.css
CHANGED
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
|
|
30
30
|
/* ── Header interaction ── */
|
|
31
31
|
--pane-header-bg-hover-default: var(--a-bg-subtle);
|
|
32
|
-
--pane-
|
|
32
|
+
--pane-caret-fg-default: var(--a-fg-muted);
|
|
33
33
|
|
|
34
34
|
/* ── Section header ── */
|
|
35
35
|
--pane-section-header-weight-default: var(--a-weight-medium);
|
|
@@ -107,17 +107,17 @@
|
|
|
107
107
|
box-shadow: var(--a-focus-ring) inset;
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
/* Collapse indicator — stamped by JS as icon-ui */
|
|
111
|
-
& > header > [slot="
|
|
110
|
+
/* Collapse indicator (caret) — stamped by JS as icon-ui */
|
|
111
|
+
& > header > [slot="caret"] {
|
|
112
112
|
--a-icon-size: var(--a-caret-size);
|
|
113
113
|
flex-shrink: 0;
|
|
114
114
|
margin-inline-start: auto;
|
|
115
|
-
color: var(--pane-
|
|
115
|
+
color: var(--pane-caret-fg, var(--pane-caret-fg-default));
|
|
116
116
|
transition: transform var(--pane-duration, var(--pane-duration-default)) var(--pane-easing, var(--pane-easing-default));
|
|
117
117
|
transform: rotate(90deg);
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
:scope[collapsed] > header > [slot="
|
|
120
|
+
:scope[collapsed] > header > [slot="caret"] {
|
|
121
121
|
transform: rotate(0deg);
|
|
122
122
|
}
|
|
123
123
|
|
|
@@ -62,6 +62,12 @@ events:
|
|
|
62
62
|
slots:
|
|
63
63
|
header:
|
|
64
64
|
description: Auto-created header element with label text and toggle arrow
|
|
65
|
+
caret:
|
|
66
|
+
description: >-
|
|
67
|
+
Collapse caret inside the header. Auto-stamped as
|
|
68
|
+
`<icon-ui slot="caret" name="caret-right">` (rotates on `[collapsed]`).
|
|
69
|
+
Supply your own `slot="caret"` child in the header to customize —
|
|
70
|
+
adopt-or-stamp honors a declarative caret instead of stamping.
|
|
65
71
|
states:
|
|
66
72
|
- name: idle
|
|
67
73
|
description: Default, ready for interaction.
|
|
@@ -65,6 +65,12 @@
|
|
|
65
65
|
"Accordion"
|
|
66
66
|
],
|
|
67
67
|
"slots": {
|
|
68
|
+
"actions": {
|
|
69
|
+
"description": "Per-row action buttons (rename / add / overflow menu), right-aligned and hover-revealed. A declarative `slot=\"actions\"` child is adopted into the auto-stamped row (FEEDBACK-89), so it sits inline in the row rather than wrapping below it."
|
|
70
|
+
},
|
|
71
|
+
"caret": {
|
|
72
|
+
"description": "Override slot for the expand/collapse caret. Auto-stamped as `<icon-ui slot=\"caret\" name=\"caret-right\">` (rotates on `[open]`, hidden for leaf nodes). Supply your own `slot=\"caret\"` child to customize — adopt-or-stamp moves a declarative caret into the row."
|
|
73
|
+
},
|
|
68
74
|
"icon": {
|
|
69
75
|
"description": "Override the leading [icon] glyph with a custom slotted element (custom icon-ui, folder open/closed glyph, file-type marker). Mutually exclusive with the [icon] attribute — slot child wins."
|
|
70
76
|
}
|
|
@@ -47,6 +47,18 @@ slots:
|
|
|
47
47
|
Override the leading [icon] glyph with a custom slotted element (custom
|
|
48
48
|
icon-ui, folder open/closed glyph, file-type marker). Mutually exclusive
|
|
49
49
|
with the [icon] attribute — slot child wins.
|
|
50
|
+
caret:
|
|
51
|
+
description: >-
|
|
52
|
+
Override slot for the expand/collapse caret. Auto-stamped as
|
|
53
|
+
`<icon-ui slot="caret" name="caret-right">` (rotates on `[open]`,
|
|
54
|
+
hidden for leaf nodes). Supply your own `slot="caret"` child to
|
|
55
|
+
customize — adopt-or-stamp moves a declarative caret into the row.
|
|
56
|
+
actions:
|
|
57
|
+
description: >-
|
|
58
|
+
Per-row action buttons (rename / add / overflow menu), right-aligned
|
|
59
|
+
and hover-revealed. A declarative `slot="actions"` child is adopted
|
|
60
|
+
into the auto-stamped row (FEEDBACK-89), so it sits inline in the row
|
|
61
|
+
rather than wrapping below it.
|
|
50
62
|
|
|
51
63
|
a2ui:
|
|
52
64
|
rules:
|
|
@@ -72,7 +84,7 @@ a2ui:
|
|
|
72
84
|
- >-
|
|
73
85
|
Nest further <tree-item-ui> in the default slot only — no
|
|
74
86
|
<list-item-ui>, <nav-item-ui>, or arbitrary content inside a
|
|
75
|
-
tree row. The
|
|
87
|
+
tree row. The caret is auto-stamped when the row has nested
|
|
76
88
|
tree-item-ui children.
|
|
77
89
|
|
|
78
90
|
keywords:
|
|
@@ -126,8 +126,8 @@
|
|
|
126
126
|
"--tree-bg-selected": {
|
|
127
127
|
"description": "Background color when selected"
|
|
128
128
|
},
|
|
129
|
-
"--tree-
|
|
130
|
-
"description": "Size of the collapse
|
|
129
|
+
"--tree-caret-size": {
|
|
130
|
+
"description": "Size of the collapse caret icon"
|
|
131
131
|
},
|
|
132
132
|
"--tree-duration": {
|
|
133
133
|
"description": "Transition duration"
|
|
@@ -139,7 +139,7 @@
|
|
|
139
139
|
"description": "Primary text color"
|
|
140
140
|
},
|
|
141
141
|
"--tree-fg-muted": {
|
|
142
|
-
"description": "Muted text color (icons,
|
|
142
|
+
"description": "Muted text color (icons, carets)"
|
|
143
143
|
},
|
|
144
144
|
"--tree-focus-ring": {
|
|
145
145
|
"description": "Focus ring box-shadow"
|
|
@@ -293,11 +293,17 @@ export class UITreeItem extends UIElement {
|
|
|
293
293
|
row.setAttribute('slot', 'row');
|
|
294
294
|
row.setAttribute('tabindex', '0');
|
|
295
295
|
|
|
296
|
-
//
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
296
|
+
// Caret — adopt a declarative [slot="caret"] child if the consumer supplied
|
|
297
|
+
// one, else stamp the default. (Adopt-or-stamp; same pattern as actions.)
|
|
298
|
+
const declaredCaret = this.querySelector(':scope > [slot="caret"]');
|
|
299
|
+
if (declaredCaret) {
|
|
300
|
+
row.appendChild(declaredCaret);
|
|
301
|
+
} else {
|
|
302
|
+
const caret = document.createElement('icon-ui');
|
|
303
|
+
caret.setAttribute('slot', 'caret');
|
|
304
|
+
caret.setAttribute('name', 'caret-right');
|
|
305
|
+
row.appendChild(caret);
|
|
306
|
+
}
|
|
301
307
|
|
|
302
308
|
// Icon
|
|
303
309
|
if (this.icon) {
|
|
@@ -320,10 +326,19 @@ export class UITreeItem extends UIElement {
|
|
|
320
326
|
if (this.badge) badgeEl.textContent = this.badge;
|
|
321
327
|
row.appendChild(badgeEl);
|
|
322
328
|
|
|
323
|
-
// Actions slot
|
|
324
|
-
|
|
325
|
-
actions.
|
|
326
|
-
row
|
|
329
|
+
// Actions — adopt pre-existing declarative [slot="actions"] children into
|
|
330
|
+
// the row (FEEDBACK-89) so per-row action buttons land in the styled,
|
|
331
|
+
// hover-revealed actions area; else stamp an empty placeholder. The host
|
|
332
|
+
// #onClick already excludes [slot="actions"] * from row selection, so
|
|
333
|
+
// adoption is click-safe.
|
|
334
|
+
const declaredActions = this.querySelectorAll(':scope > [slot="actions"]');
|
|
335
|
+
if (declaredActions.length) {
|
|
336
|
+
for (const a of declaredActions) row.appendChild(a);
|
|
337
|
+
} else {
|
|
338
|
+
const actions = document.createElement('span');
|
|
339
|
+
actions.setAttribute('slot', 'actions');
|
|
340
|
+
row.appendChild(actions);
|
|
341
|
+
}
|
|
327
342
|
|
|
328
343
|
this.prepend(row);
|
|
329
344
|
}
|
package/components/tree/tree.css
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
--tree-row-gap-default: var(--a-space-1);
|
|
8
8
|
--tree-actions-gap-default: var(--a-space-0-5);
|
|
9
9
|
--tree-indent-default: var(--a-space-4);
|
|
10
|
-
--tree-
|
|
10
|
+
--tree-caret-size-default: var(--a-space-2);
|
|
11
11
|
--tree-icon-size-default: var(--a-space-3);
|
|
12
12
|
|
|
13
13
|
/* ── Typography ── */
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
--tree-item-row-gap: var(--tree-row-gap, var(--a-space-1));
|
|
48
48
|
--tree-item-actions-gap: var(--tree-actions-gap, var(--a-space-0-5));
|
|
49
49
|
--tree-item-indent: var(--tree-indent, var(--a-space-4));
|
|
50
|
-
--tree-item-
|
|
50
|
+
--tree-item-caret-size: var(--tree-caret-size, var(--a-space-2));
|
|
51
51
|
--tree-item-icon-size: var(--tree-icon-size, var(--a-space-3));
|
|
52
52
|
|
|
53
53
|
--tree-item-fg: var(--tree-fg, var(--a-fg));
|
|
@@ -92,21 +92,21 @@
|
|
|
92
92
|
|
|
93
93
|
/* :scope[selected] rules moved outside @scope — see tree-ui Safari note at end of file. */
|
|
94
94
|
|
|
95
|
-
/* ──
|
|
96
|
-
[slot="
|
|
97
|
-
--a-icon-size: var(--tree-
|
|
95
|
+
/* ── Caret ── */
|
|
96
|
+
[slot="caret"] {
|
|
97
|
+
--a-icon-size: var(--tree-caret-size, var(--tree-caret-size-default));
|
|
98
98
|
flex-shrink: 0;
|
|
99
99
|
color: var(--tree-fg-muted, var(--tree-fg-muted-default));
|
|
100
100
|
transition: transform var(--tree-duration, var(--tree-duration-default)) var(--tree-easing, var(--tree-easing-default));
|
|
101
101
|
transform: rotate(90deg);
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
:scope:not([open]) > [slot="row"] > [slot="
|
|
104
|
+
:scope:not([open]) > [slot="row"] > [slot="caret"] {
|
|
105
105
|
transform: rotate(0deg);
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
/* Hide
|
|
109
|
-
:scope:not(:has(> tree-item-ui)) > [slot="row"] > [slot="
|
|
108
|
+
/* Hide caret for leaf nodes */
|
|
109
|
+
:scope:not(:has(> tree-item-ui)) > [slot="row"] > [slot="caret"] {
|
|
110
110
|
visibility: hidden;
|
|
111
111
|
}
|
|
112
112
|
|
|
@@ -101,3 +101,55 @@ describe('<tree-ui> tree-select forwards modifier keys (FB-46)', () => {
|
|
|
101
101
|
expect(received.shiftKey).toBe(false);
|
|
102
102
|
});
|
|
103
103
|
});
|
|
104
|
+
|
|
105
|
+
describe('<tree-item-ui> caret slot + actions adoption (caret convention + FB-89)', () => {
|
|
106
|
+
let host;
|
|
107
|
+
const settle = () => new Promise((r) => setTimeout(r, 30));
|
|
108
|
+
|
|
109
|
+
beforeEach(() => {
|
|
110
|
+
host = document.createElement('div');
|
|
111
|
+
document.body.appendChild(host);
|
|
112
|
+
});
|
|
113
|
+
afterEach(() => host.remove());
|
|
114
|
+
|
|
115
|
+
it('auto-stamps a slot="caret" icon (renamed from slot="chevron")', async () => {
|
|
116
|
+
const item = document.createElement('tree-item-ui');
|
|
117
|
+
item.setAttribute('text', 'Colors');
|
|
118
|
+
host.appendChild(item);
|
|
119
|
+
await settle();
|
|
120
|
+
const row = item.querySelector(':scope > [slot="row"]');
|
|
121
|
+
expect(row.querySelector('[slot="caret"]')).toBeTruthy();
|
|
122
|
+
expect(row.querySelector('[slot="chevron"]')).toBeNull();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('adopts a declarative slot="actions" child into the stamped row (FB-89)', async () => {
|
|
126
|
+
const item = document.createElement('tree-item-ui');
|
|
127
|
+
item.setAttribute('text', 'Colors');
|
|
128
|
+
const btn = document.createElement('button-ui');
|
|
129
|
+
btn.setAttribute('slot', 'actions');
|
|
130
|
+
btn.setAttribute('icon', 'plus');
|
|
131
|
+
item.appendChild(btn);
|
|
132
|
+
host.appendChild(item);
|
|
133
|
+
await settle();
|
|
134
|
+
const row = item.querySelector(':scope > [slot="row"]');
|
|
135
|
+
// The action button is now INSIDE the row, not a sibling left below it.
|
|
136
|
+
expect(btn.parentElement).toBe(row);
|
|
137
|
+
expect(item.querySelector(':scope > [slot="actions"]')).toBeNull();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('adopts a declarative slot="caret" child instead of stamping a default', async () => {
|
|
141
|
+
const item = document.createElement('tree-item-ui');
|
|
142
|
+
item.setAttribute('text', 'Colors');
|
|
143
|
+
const customCaret = document.createElement('icon-ui');
|
|
144
|
+
customCaret.setAttribute('slot', 'caret');
|
|
145
|
+
customCaret.setAttribute('name', 'folder');
|
|
146
|
+
item.appendChild(customCaret);
|
|
147
|
+
host.appendChild(item);
|
|
148
|
+
await settle();
|
|
149
|
+
const row = item.querySelector(':scope > [slot="row"]');
|
|
150
|
+
const carets = row.querySelectorAll('[slot="caret"]');
|
|
151
|
+
expect(carets.length).toBe(1); // no double-stamp
|
|
152
|
+
expect(carets[0]).toBe(customCaret); // consumer's caret used
|
|
153
|
+
expect(carets[0].getAttribute('name')).toBe('folder');
|
|
154
|
+
});
|
|
155
|
+
});
|
|
@@ -59,8 +59,8 @@ tokens:
|
|
|
59
59
|
description: Background color on hover
|
|
60
60
|
--tree-bg-selected:
|
|
61
61
|
description: Background color when selected
|
|
62
|
-
--tree-
|
|
63
|
-
description: Size of the collapse
|
|
62
|
+
--tree-caret-size:
|
|
63
|
+
description: Size of the collapse caret icon
|
|
64
64
|
--tree-duration:
|
|
65
65
|
description: Transition duration
|
|
66
66
|
--tree-easing:
|
|
@@ -68,7 +68,7 @@ tokens:
|
|
|
68
68
|
--tree-fg:
|
|
69
69
|
description: Primary text color
|
|
70
70
|
--tree-fg-muted:
|
|
71
|
-
description: Muted text color (icons,
|
|
71
|
+
description: Muted text color (icons, carets)
|
|
72
72
|
--tree-focus-ring:
|
|
73
73
|
description: Focus ring box-shadow
|
|
74
74
|
--tree-font-size:
|
|
@@ -121,7 +121,7 @@ a2ui:
|
|
|
121
121
|
rows — selection is managed by the parent and bubbles once.
|
|
122
122
|
Detail = {item, text, value, ctrlKey, metaKey, shiftKey}.
|
|
123
123
|
- >-
|
|
124
|
-
Per ADR-0027, <tree-ui> composes <icon-ui> (for
|
|
124
|
+
Per ADR-0027, <tree-ui> composes <icon-ui> (for carets) but
|
|
125
125
|
does NOT auto-import its children. Consumer pages must
|
|
126
126
|
explicitly import both <tree-ui> and <tree-item-ui>.
|
|
127
127
|
anti_patterns: []
|