@adia-ai/web-components 0.0.26 → 0.0.27
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/components/agent-artifact/agent-artifact.a2ui.json +1 -1
- package/components/agent-artifact/agent-artifact.css +11 -0
- package/components/agent-artifact/agent-artifact.js +23 -2
- package/components/agent-artifact/agent-artifact.yaml +1 -1
- package/components/agent-reasoning/agent-reasoning.css +11 -0
- package/components/agent-reasoning/agent-reasoning.js +16 -0
- package/components/agent-trace/agent-trace.css +19 -0
- package/components/alert/alert.a2ui.json +10 -4
- package/components/alert/alert.css +13 -0
- package/components/alert/alert.js +1 -1
- package/components/alert/alert.yaml +21 -4
- package/components/badge/badge.a2ui.json +0 -2
- package/components/badge/badge.css +20 -0
- package/components/badge/badge.js +1 -1
- package/components/badge/badge.yaml +0 -2
- package/components/calendar-picker/calendar-picker.css +17 -0
- package/components/code/code.css +41 -0
- package/components/code/code.js +44 -3
- package/components/empty-state/empty-state.js +32 -21
- package/components/list/list.js +20 -16
- package/components/menu/menu.css +18 -0
- package/components/menu/menu.js +24 -10
- package/components/pane/pane.css +5 -0
- package/components/pipeline-status/pipeline-status.css +15 -1
- package/components/popover/popover.css +17 -0
- package/components/select/select.css +18 -0
- package/components/swiper/swiper.css +9 -0
- package/components/table/table.css +5 -0
- package/components/table/table.js +45 -1
- package/components/table-toolbar/table-toolbar.css +13 -0
- package/components/tag/tag.css +10 -0
- package/components/timeline/timeline.css +10 -3
- package/components/toast/toast.css +93 -48
- package/components/toast/toast.js +101 -22
- package/components/toolbar/toolbar.css +13 -0
- package/components/tooltip/tooltip.css +8 -0
- package/package.json +1 -1
- package/styles/colors/semantics.css +1 -1
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
--agent-artifact-fg-muted: var(--a-fg-muted);
|
|
14
14
|
--agent-artifact-border: var(--a-border-subtle);
|
|
15
15
|
--agent-artifact-header-bg: var(--a-bg);
|
|
16
|
+
--agent-artifact-header-bg-hover: var(--a-bg-hover);
|
|
16
17
|
|
|
17
18
|
/* ── Typography ── */
|
|
18
19
|
--agent-artifact-title-size: var(--a-ui-md);
|
|
@@ -46,6 +47,16 @@
|
|
|
46
47
|
cursor: pointer;
|
|
47
48
|
user-select: none;
|
|
48
49
|
min-width: 0;
|
|
50
|
+
transition: background var(--agent-artifact-duration);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
[data-artifact-header]:hover {
|
|
54
|
+
background: var(--agent-artifact-header-bg-hover);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
[data-artifact-header]:focus-visible {
|
|
58
|
+
outline: none;
|
|
59
|
+
box-shadow: var(--a-focus-ring) inset;
|
|
49
60
|
}
|
|
50
61
|
|
|
51
62
|
[data-artifact-icon] {
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
*
|
|
18
18
|
* Attributes:
|
|
19
19
|
* title — header title (string)
|
|
20
|
-
* kind — small
|
|
20
|
+
* kind — small badge, e.g. "A2UI", "JSON", "TICKET" (value is
|
|
21
|
+
* normalized to uppercase before rendering)
|
|
21
22
|
* icon — icon-ui name for the header
|
|
22
23
|
* collapsed — start collapsed (body hidden)
|
|
23
24
|
* tone — neutral (default) | accent | warning | danger
|
|
@@ -59,6 +60,7 @@ class AdiaAgentArtifact extends AdiaElement {
|
|
|
59
60
|
|
|
60
61
|
disconnected() {
|
|
61
62
|
this.#headerEl?.removeEventListener('click', this.#onHeaderClick);
|
|
63
|
+
this.#headerEl?.removeEventListener('keydown', this.#onHeaderKey);
|
|
62
64
|
this.#headerEl = this.#iconEl = this.#titleEl = null;
|
|
63
65
|
this.#kindEl = this.#chevronEl = this.#bodyEl = null;
|
|
64
66
|
}
|
|
@@ -73,6 +75,17 @@ class AdiaAgentArtifact extends AdiaElement {
|
|
|
73
75
|
this.render();
|
|
74
76
|
};
|
|
75
77
|
|
|
78
|
+
// Keyboard activation — Space/Enter toggle (matches role="button" semantics).
|
|
79
|
+
#onHeaderKey = (e) => {
|
|
80
|
+
if (e.key === ' ' || e.key === 'Enter') {
|
|
81
|
+
// Don't fire when the focus is on a slotted action button — let those
|
|
82
|
+
// handle their own activation.
|
|
83
|
+
if (e.target.closest('[slot="primary"], [slot="secondary"]')) return;
|
|
84
|
+
e.preventDefault();
|
|
85
|
+
this.#onHeaderClick(e);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
76
89
|
render() {
|
|
77
90
|
if (!this.#headerEl) return;
|
|
78
91
|
|
|
@@ -95,6 +108,9 @@ class AdiaAgentArtifact extends AdiaElement {
|
|
|
95
108
|
if (this.#bodyEl) {
|
|
96
109
|
this.#bodyEl.hidden = this.collapsed;
|
|
97
110
|
}
|
|
111
|
+
if (this.#headerEl) {
|
|
112
|
+
this.#headerEl.setAttribute('aria-expanded', String(!this.collapsed));
|
|
113
|
+
}
|
|
98
114
|
}
|
|
99
115
|
|
|
100
116
|
#build() {
|
|
@@ -120,10 +136,15 @@ class AdiaAgentArtifact extends AdiaElement {
|
|
|
120
136
|
|
|
121
137
|
this.innerHTML = '';
|
|
122
138
|
|
|
123
|
-
// Header
|
|
139
|
+
// Header — keyboard-focusable button-style row that toggles collapsed.
|
|
140
|
+
// role/tabindex make Space/Enter activate it the same as click.
|
|
124
141
|
this.#headerEl = document.createElement('div');
|
|
125
142
|
this.#headerEl.setAttribute('data-artifact-header', '');
|
|
143
|
+
this.#headerEl.setAttribute('role', 'button');
|
|
144
|
+
this.#headerEl.setAttribute('tabindex', '0');
|
|
145
|
+
this.#headerEl.setAttribute('aria-expanded', String(!this.collapsed));
|
|
126
146
|
this.#headerEl.addEventListener('click', this.#onHeaderClick);
|
|
147
|
+
this.#headerEl.addEventListener('keydown', this.#onHeaderKey);
|
|
127
148
|
|
|
128
149
|
this.#iconEl = document.createElement('icon-ui');
|
|
129
150
|
this.#iconEl.setAttribute('data-artifact-icon', '');
|
|
@@ -9,7 +9,7 @@ version: 1
|
|
|
9
9
|
description: Inline container for structured agent artifacts (A2UI, JSON, tickets).
|
|
10
10
|
props:
|
|
11
11
|
kind:
|
|
12
|
-
description:
|
|
12
|
+
description: Badge label; value is normalized to uppercase before rendering.
|
|
13
13
|
type: string
|
|
14
14
|
default: ""
|
|
15
15
|
collapsed:
|
|
@@ -55,6 +55,17 @@
|
|
|
55
55
|
user-select: none;
|
|
56
56
|
padding-bottom: var(--a-space-1);
|
|
57
57
|
color: var(--agent-reasoning-fg-muted);
|
|
58
|
+
border-radius: var(--a-radius-sm);
|
|
59
|
+
transition: color var(--agent-reasoning-duration) var(--agent-reasoning-easing);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
[data-reasoning-summary]:hover {
|
|
63
|
+
color: var(--agent-reasoning-fg);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
[data-reasoning-summary]:focus-visible {
|
|
67
|
+
outline: none;
|
|
68
|
+
box-shadow: var(--a-focus-ring);
|
|
58
69
|
}
|
|
59
70
|
|
|
60
71
|
[data-reasoning-summary] > [data-reasoning-check],
|
|
@@ -109,6 +109,7 @@ class AdiaAgentReasoning extends AdiaElement {
|
|
|
109
109
|
this.#finishTimer = null;
|
|
110
110
|
}
|
|
111
111
|
this.#summaryEl?.removeEventListener('click', this.#onSummaryClick);
|
|
112
|
+
this.#summaryEl?.removeEventListener('keydown', this.#onSummaryKey);
|
|
112
113
|
this.#bodyEl?.removeEventListener('timeline-toggle', this.#onStepToggle);
|
|
113
114
|
this.#summaryEl = null;
|
|
114
115
|
this.#bodyEl = null;
|
|
@@ -265,12 +266,26 @@ class AdiaAgentReasoning extends AdiaElement {
|
|
|
265
266
|
this.#render();
|
|
266
267
|
};
|
|
267
268
|
|
|
269
|
+
// Keyboard activation — Space/Enter toggle (matches role="button" semantics).
|
|
270
|
+
#onSummaryKey = (e) => {
|
|
271
|
+
if (e.key === ' ' || e.key === 'Enter') {
|
|
272
|
+
e.preventDefault();
|
|
273
|
+
this.#onSummaryClick();
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
|
|
268
277
|
#buildShell() {
|
|
269
278
|
this.innerHTML = '';
|
|
270
279
|
|
|
280
|
+
// Summary is a click-to-toggle row; mark up + key-activate so it
|
|
281
|
+
// behaves like a button under keyboard nav too.
|
|
271
282
|
this.#summaryEl = document.createElement('div');
|
|
272
283
|
this.#summaryEl.setAttribute('data-reasoning-summary', '');
|
|
284
|
+
this.#summaryEl.setAttribute('role', 'button');
|
|
285
|
+
this.#summaryEl.setAttribute('tabindex', '0');
|
|
286
|
+
this.#summaryEl.setAttribute('aria-expanded', String(!this.collapsed));
|
|
273
287
|
this.#summaryEl.addEventListener('click', this.#onSummaryClick);
|
|
288
|
+
this.#summaryEl.addEventListener('keydown', this.#onSummaryKey);
|
|
274
289
|
this.appendChild(this.#summaryEl);
|
|
275
290
|
|
|
276
291
|
this.#bodyEl = document.createElement('div');
|
|
@@ -341,6 +356,7 @@ class AdiaAgentReasoning extends AdiaElement {
|
|
|
341
356
|
`;
|
|
342
357
|
|
|
343
358
|
this.#bodyEl.hidden = this.collapsed;
|
|
359
|
+
this.#summaryEl.setAttribute('aria-expanded', String(!this.collapsed));
|
|
344
360
|
if (this.collapsed) return;
|
|
345
361
|
|
|
346
362
|
// Body rendering: we group contiguous steps into timeline-ui blocks and
|
|
@@ -43,9 +43,23 @@
|
|
|
43
43
|
gap: var(--agent-trace-inline-gap);
|
|
44
44
|
cursor: pointer;
|
|
45
45
|
white-space: nowrap;
|
|
46
|
+
border-radius: var(--a-radius-sm);
|
|
47
|
+
padding: var(--a-space-0-5) var(--a-space-1);
|
|
48
|
+
margin: calc(var(--a-space-0-5) * -1) calc(var(--a-space-1) * -1);
|
|
49
|
+
transition: background var(--agent-trace-chevron-dur);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
[data-trace-root] > summary:hover {
|
|
53
|
+
background: var(--a-bg-subtle);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
[data-trace-root] > summary:focus-visible {
|
|
57
|
+
outline: none;
|
|
58
|
+
box-shadow: var(--a-focus-ring);
|
|
46
59
|
}
|
|
47
60
|
|
|
48
61
|
[data-trace-root] > summary::-webkit-details-marker { display: none; }
|
|
62
|
+
[data-trace-root] > summary::marker { content: ''; }
|
|
49
63
|
|
|
50
64
|
[data-trace-status] {
|
|
51
65
|
flex: 1;
|
|
@@ -163,6 +177,11 @@
|
|
|
163
177
|
background: var(--a-bg-subtle);
|
|
164
178
|
}
|
|
165
179
|
|
|
180
|
+
details[data-trace-row] > summary:focus-visible {
|
|
181
|
+
outline: none;
|
|
182
|
+
box-shadow: var(--a-focus-ring);
|
|
183
|
+
}
|
|
184
|
+
|
|
166
185
|
[data-trace-label] {
|
|
167
186
|
color: var(--agent-trace-fg-subtle);
|
|
168
187
|
}
|
|
@@ -93,11 +93,17 @@
|
|
|
93
93
|
"button"
|
|
94
94
|
],
|
|
95
95
|
"slots": {
|
|
96
|
-
"
|
|
97
|
-
"description": "
|
|
96
|
+
"action": {
|
|
97
|
+
"description": "Optional trailing action button (e.g. \"Refresh\", \"Status page\"). Right-aligned in the flex layout. See system-banners pattern for examples."
|
|
98
98
|
},
|
|
99
|
-
"
|
|
100
|
-
"description": "
|
|
99
|
+
"close": {
|
|
100
|
+
"description": "Close button. Stamped automatically when `closable` is set; the stamped element is `<button-ui slot=\"close\" icon=\"x\" variant=\"ghost\" size=\"sm\">`. Override by passing a custom `slot=\"close\"` button."
|
|
101
|
+
},
|
|
102
|
+
"content": {
|
|
103
|
+
"description": "Alert body. For single-line text use the `text=` attribute (alert.js stamps a `<span slot=\"content\">` automatically). For rich content with title + description, wrap in `<col-ui slot=\"content\">` — the alert root is `display: flex` row, so multiple bare children would lay out side-by-side instead of stacking."
|
|
104
|
+
},
|
|
105
|
+
"leading": {
|
|
106
|
+
"description": "Leading icon. Stamped automatically from the `icon=` attribute; consumers can override by passing a custom `<icon-ui slot=\"leading\">`."
|
|
101
107
|
}
|
|
102
108
|
},
|
|
103
109
|
"states": [
|
|
@@ -65,6 +65,19 @@
|
|
|
65
65
|
--alert-icon-fg: var(--a-danger-strong);
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
/* `muted` and `neutral` are semantic aliases of the base — same tokens
|
|
69
|
+
as the default variant, declared explicitly so the yaml enum and the
|
|
70
|
+
CSS contract agree. Use cases: passive callouts (beta banners,
|
|
71
|
+
tertiary notes) where the alert should read as quiet chrome rather
|
|
72
|
+
than carrying tonal weight. */
|
|
73
|
+
:scope[variant="muted"],
|
|
74
|
+
:scope[variant="neutral"] {
|
|
75
|
+
--alert-bg: var(--a-bg-muted);
|
|
76
|
+
--alert-fg: var(--a-fg);
|
|
77
|
+
--alert-border: var(--a-border-subtle);
|
|
78
|
+
--alert-icon-fg: var(--a-fg-muted);
|
|
79
|
+
}
|
|
80
|
+
|
|
68
81
|
/* ── Slots ── */
|
|
69
82
|
:scope [slot="leading"] {
|
|
70
83
|
flex-shrink: 0;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Inline alert banner with optional icon and close button.
|
|
6
6
|
*
|
|
7
|
-
* Variants: default, info, success, warning, danger
|
|
7
|
+
* Variants: default, info, success, warning, danger, muted, neutral
|
|
8
8
|
* Slots: leading (icon), content (text), close (dismiss button)
|
|
9
9
|
*
|
|
10
10
|
* Events:
|
|
@@ -36,10 +36,27 @@ events:
|
|
|
36
36
|
close:
|
|
37
37
|
description: Fired when the close button is clicked
|
|
38
38
|
slots:
|
|
39
|
-
|
|
40
|
-
description:
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
content:
|
|
40
|
+
description: >-
|
|
41
|
+
Alert body. For single-line text use the `text=` attribute (alert.js
|
|
42
|
+
stamps a `<span slot="content">` automatically). For rich content
|
|
43
|
+
with title + description, wrap in `<col-ui slot="content">` — the
|
|
44
|
+
alert root is `display: flex` row, so multiple bare children would
|
|
45
|
+
lay out side-by-side instead of stacking.
|
|
46
|
+
leading:
|
|
47
|
+
description: >-
|
|
48
|
+
Leading icon. Stamped automatically from the `icon=` attribute;
|
|
49
|
+
consumers can override by passing a custom `<icon-ui slot="leading">`.
|
|
50
|
+
close:
|
|
51
|
+
description: >-
|
|
52
|
+
Close button. Stamped automatically when `closable` is set; the
|
|
53
|
+
stamped element is `<button-ui slot="close" icon="x" variant="ghost"
|
|
54
|
+
size="sm">`. Override by passing a custom `slot="close"` button.
|
|
55
|
+
action:
|
|
56
|
+
description: >-
|
|
57
|
+
Optional trailing action button (e.g. "Refresh", "Status page").
|
|
58
|
+
Right-aligned in the flex layout. See system-banners pattern for
|
|
59
|
+
examples.
|
|
43
60
|
states:
|
|
44
61
|
- name: idle
|
|
45
62
|
description: Default, ready for interaction.
|
|
@@ -74,5 +74,25 @@
|
|
|
74
74
|
--badge-fg: var(--a-danger-text);
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
/* `primary` is the accent-filled variant — solid bg + on-accent text,
|
|
78
|
+
used for "PRO"-style emphasis chips that should read as a brand
|
|
79
|
+
stamp rather than tinted-on-canvas. Uses the L3 primary-* surface
|
|
80
|
+
matrix so it tracks the same accent-strong source as button-ui's
|
|
81
|
+
primary variant. */
|
|
82
|
+
:scope[variant="primary"] {
|
|
83
|
+
--badge-bg: var(--a-primary-bg);
|
|
84
|
+
--badge-fg: var(--a-primary-fg);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* `muted` and `neutral` are semantic aliases of the base — same tokens
|
|
88
|
+
as the default variant, declared explicitly so the yaml enum and the
|
|
89
|
+
CSS contract agree. Use cases: passive metadata chips (counts, IDs,
|
|
90
|
+
"Not set" states) where the badge should read as quiet chrome. */
|
|
91
|
+
:scope[variant="muted"],
|
|
92
|
+
:scope[variant="neutral"] {
|
|
93
|
+
--badge-bg: var(--a-bg-muted);
|
|
94
|
+
--badge-fg: var(--a-fg);
|
|
95
|
+
}
|
|
96
|
+
|
|
77
97
|
/* Size handled by universal [size] attribute system. */
|
|
78
98
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Inline badge / tag. Pill-shaped, text rendered via CSS attr().
|
|
8
8
|
*
|
|
9
|
-
* Variants: default, accent, info, success, warning, danger
|
|
9
|
+
* Variants: default, accent, info, success, warning, danger, primary, muted, neutral
|
|
10
10
|
* Sizes: sm, md (default)
|
|
11
11
|
* Icon: optional leading icon (any registered icon name; "dot" for legend
|
|
12
12
|
* markers). Inherits the variant's foreground color so legend chips
|
|
@@ -180,6 +180,23 @@ calendar-picker-ui [slot="popover"] {
|
|
|
180
180
|
font-family: inherit;
|
|
181
181
|
font-size: var(--calendar-picker-font-size);
|
|
182
182
|
color: var(--calendar-picker-popover-fg);
|
|
183
|
+
/* Fade + lift in on first paint via @starting-style. Same pattern
|
|
184
|
+
as menu/select/popover surfaces — see journal 2026-04-29 §18. */
|
|
185
|
+
opacity: 1;
|
|
186
|
+
translate: 0 0;
|
|
187
|
+
transition: opacity var(--a-duration-fast) var(--a-easing-out),
|
|
188
|
+
translate var(--a-duration-fast) var(--a-easing-out);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
calendar-picker-ui [slot="popover"]:popover-open {
|
|
192
|
+
@starting-style {
|
|
193
|
+
opacity: 0;
|
|
194
|
+
translate: 0 -4px;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
@media (prefers-reduced-motion: reduce) {
|
|
199
|
+
calendar-picker-ui [slot="popover"] { transition: none; }
|
|
183
200
|
}
|
|
184
201
|
|
|
185
202
|
/* Header row */
|
package/components/code/code.css
CHANGED
|
@@ -156,6 +156,47 @@
|
|
|
156
156
|
color: inherit;
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
/* ── Diff line states (static path) ──
|
|
160
|
+
Active when the block uses `language="diff"` (auto-parse +/- prefix)
|
|
161
|
+
or carries `data-line-states="..."` (explicit per-line). Each line is
|
|
162
|
+
a `[data-line-state]` row whose bg picks up the state token. The
|
|
163
|
+
CodeMirror path doesn't use these markers; line decorations there go
|
|
164
|
+
through CM extensions instead. */
|
|
165
|
+
> pre > code[data-line-state-mode] {
|
|
166
|
+
display: block;
|
|
167
|
+
}
|
|
168
|
+
> pre > code[data-line-state-mode] > [data-line-state] {
|
|
169
|
+
display: grid;
|
|
170
|
+
grid-template-columns: 1fr;
|
|
171
|
+
/* Bleed the row tint to the pre's padding edges so it reads as a
|
|
172
|
+
full-width line, not a gap-margined pill. */
|
|
173
|
+
margin-inline: calc(-1 * var(--code-px));
|
|
174
|
+
padding-inline: var(--code-px);
|
|
175
|
+
}
|
|
176
|
+
> pre > code[data-line-numbers] > [data-line-state] {
|
|
177
|
+
grid-template-columns: auto 1fr;
|
|
178
|
+
column-gap: var(--a-space-3);
|
|
179
|
+
}
|
|
180
|
+
> pre > code[data-line-state-mode] [data-line-num] {
|
|
181
|
+
color: var(--a-fg-subtle);
|
|
182
|
+
text-align: end;
|
|
183
|
+
user-select: none;
|
|
184
|
+
min-width: 1.5ch;
|
|
185
|
+
}
|
|
186
|
+
> pre > code[data-line-state-mode] [data-line-body] {
|
|
187
|
+
white-space: pre;
|
|
188
|
+
}
|
|
189
|
+
/* Empty lines still need height so the diff column counts line up. */
|
|
190
|
+
> pre > code[data-line-state-mode] [data-line-body]:empty::before {
|
|
191
|
+
content: " ";
|
|
192
|
+
}
|
|
193
|
+
> pre > code [data-line-state="added"] {
|
|
194
|
+
background: var(--a-success-muted);
|
|
195
|
+
}
|
|
196
|
+
> pre > code [data-line-state="removed"] {
|
|
197
|
+
background: var(--a-danger-muted);
|
|
198
|
+
}
|
|
199
|
+
|
|
159
200
|
/* Footer — optional chrome band below the code block
|
|
160
201
|
(line counts, byte size, language family, etc.) */
|
|
161
202
|
> footer {
|
package/components/code/code.js
CHANGED
|
@@ -90,9 +90,12 @@ class AdiaCode extends AdiaElement {
|
|
|
90
90
|
// Mount CodeMirror when the language is supported OR the element is
|
|
91
91
|
// editable (editable plain-text is still useful). Inline instances
|
|
92
92
|
// stay on the static <code> path. Mount failures leave the static
|
|
93
|
-
// fallback in place — it's already visible.
|
|
93
|
+
// fallback in place — it's already visible. Diff line-state mode
|
|
94
|
+
// (auto via `language="diff"` or explicit `data-line-states`) also
|
|
95
|
+
// stays on the static path — bg tinting lives on the wrapper spans.
|
|
94
96
|
const lang = canonicalLanguage(this.language);
|
|
95
|
-
const
|
|
97
|
+
const useLineState = this.hasAttribute('data-line-states') || lang === 'diff';
|
|
98
|
+
const shouldMount = !this.inline && !useLineState && (SUPPORTED_LANGUAGES.has(lang) || this.editable);
|
|
96
99
|
if (shouldMount) {
|
|
97
100
|
this.#mountEditor();
|
|
98
101
|
}
|
|
@@ -145,7 +148,45 @@ class AdiaCode extends AdiaElement {
|
|
|
145
148
|
// Pre > Code — direct semantic elements, no slot attr
|
|
146
149
|
const pre = document.createElement('pre');
|
|
147
150
|
const code = document.createElement('code');
|
|
148
|
-
|
|
151
|
+
|
|
152
|
+
// Diff line-state mode — wrap each line so it can carry a state-driven
|
|
153
|
+
// bg tint and (when [line-numbers]) a leading line-number gutter.
|
|
154
|
+
// Triggered by `language="diff"` (auto-parses +/- prefix) or by an
|
|
155
|
+
// explicit `data-line-states` CSV that maps positionally onto lines.
|
|
156
|
+
const lang = canonicalLanguage(this.language);
|
|
157
|
+
const explicit = this.getAttribute('data-line-states');
|
|
158
|
+
const useLineState = explicit != null || lang === 'diff';
|
|
159
|
+
if (useLineState) {
|
|
160
|
+
code.setAttribute('data-line-state-mode', '');
|
|
161
|
+
if (this.lineNumbers) code.setAttribute('data-line-numbers', '');
|
|
162
|
+
const states = explicit?.split(',').map((s) => s.trim()) ?? null;
|
|
163
|
+
const lines = raw.split('\n');
|
|
164
|
+
lines.forEach((line, i) => {
|
|
165
|
+
let state = 'unchanged';
|
|
166
|
+
if (states && states[i]) {
|
|
167
|
+
state = states[i];
|
|
168
|
+
} else if (lang === 'diff') {
|
|
169
|
+
const head = line.charAt(0);
|
|
170
|
+
if (head === '+') state = 'added';
|
|
171
|
+
else if (head === '-') state = 'removed';
|
|
172
|
+
}
|
|
173
|
+
const row = document.createElement('span');
|
|
174
|
+
row.setAttribute('data-line-state', state);
|
|
175
|
+
if (this.lineNumbers) {
|
|
176
|
+
const num = document.createElement('span');
|
|
177
|
+
num.setAttribute('data-line-num', '');
|
|
178
|
+
num.textContent = String(i + 1);
|
|
179
|
+
row.appendChild(num);
|
|
180
|
+
}
|
|
181
|
+
const body = document.createElement('span');
|
|
182
|
+
body.setAttribute('data-line-body', '');
|
|
183
|
+
body.textContent = line;
|
|
184
|
+
row.appendChild(body);
|
|
185
|
+
code.appendChild(row);
|
|
186
|
+
});
|
|
187
|
+
} else {
|
|
188
|
+
code.textContent = raw;
|
|
189
|
+
}
|
|
149
190
|
pre.appendChild(code);
|
|
150
191
|
|
|
151
192
|
this.appendChild(pre);
|
|
@@ -24,10 +24,15 @@ class AdiaEmptyState extends AdiaElement {
|
|
|
24
24
|
#headingEl = null;
|
|
25
25
|
#descEl = null;
|
|
26
26
|
|
|
27
|
+
// Mark slot elements we create so render() never overrides consumer-provided ones.
|
|
28
|
+
// See ADR-0010 (slot content is source of truth).
|
|
29
|
+
#stampMark(el) { el.dataset.emptyStateStamped = '1'; return el; }
|
|
30
|
+
#wasStamped(el) { return el?.dataset?.emptyStateStamped === '1'; }
|
|
31
|
+
|
|
27
32
|
connected() {
|
|
28
33
|
this.#iconEl = this.querySelector(':scope > [slot="icon"]');
|
|
29
34
|
if (!this.#iconEl) {
|
|
30
|
-
this.#iconEl = document.createElement('icon-ui');
|
|
35
|
+
this.#iconEl = this.#stampMark(document.createElement('icon-ui'));
|
|
31
36
|
this.#iconEl.setAttribute('slot', 'icon');
|
|
32
37
|
this.#iconEl.setAttribute('size', 'lg');
|
|
33
38
|
this.insertBefore(this.#iconEl, this.firstChild);
|
|
@@ -35,14 +40,14 @@ class AdiaEmptyState extends AdiaElement {
|
|
|
35
40
|
|
|
36
41
|
this.#headingEl = this.querySelector(':scope > [slot="heading"]');
|
|
37
42
|
if (!this.#headingEl) {
|
|
38
|
-
this.#headingEl = document.createElement('span');
|
|
43
|
+
this.#headingEl = this.#stampMark(document.createElement('span'));
|
|
39
44
|
this.#headingEl.setAttribute('slot', 'heading');
|
|
40
45
|
this.insertBefore(this.#headingEl, this.querySelector('[slot="action"]'));
|
|
41
46
|
}
|
|
42
47
|
|
|
43
48
|
this.#descEl = this.querySelector(':scope > [slot="description"]');
|
|
44
49
|
if (!this.#descEl) {
|
|
45
|
-
this.#descEl = document.createElement('span');
|
|
50
|
+
this.#descEl = this.#stampMark(document.createElement('span'));
|
|
46
51
|
this.#descEl.setAttribute('slot', 'description');
|
|
47
52
|
this.insertBefore(this.#descEl, this.querySelector('[slot="action"]'));
|
|
48
53
|
}
|
|
@@ -51,28 +56,34 @@ class AdiaEmptyState extends AdiaElement {
|
|
|
51
56
|
render() {
|
|
52
57
|
if (!this.#iconEl) return;
|
|
53
58
|
|
|
54
|
-
// Icon
|
|
55
|
-
if (this
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
// Icon — only mutate stamped elements; visibility follows the source of truth.
|
|
60
|
+
if (this.#wasStamped(this.#iconEl)) {
|
|
61
|
+
if (this.icon) {
|
|
62
|
+
this.#iconEl.setAttribute('name', this.icon);
|
|
63
|
+
this.#iconEl.hidden = false;
|
|
64
|
+
} else {
|
|
65
|
+
this.#iconEl.hidden = true;
|
|
66
|
+
}
|
|
60
67
|
}
|
|
61
68
|
|
|
62
|
-
// Heading
|
|
63
|
-
if (this
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
69
|
+
// Heading — same policy.
|
|
70
|
+
if (this.#wasStamped(this.#headingEl)) {
|
|
71
|
+
if (this.heading) {
|
|
72
|
+
this.#headingEl.textContent = this.heading;
|
|
73
|
+
this.#headingEl.hidden = false;
|
|
74
|
+
} else {
|
|
75
|
+
this.#headingEl.hidden = true;
|
|
76
|
+
}
|
|
68
77
|
}
|
|
69
78
|
|
|
70
|
-
// Description
|
|
71
|
-
if (this
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
79
|
+
// Description — same policy.
|
|
80
|
+
if (this.#wasStamped(this.#descEl)) {
|
|
81
|
+
if (this.description) {
|
|
82
|
+
this.#descEl.textContent = this.description;
|
|
83
|
+
this.#descEl.hidden = false;
|
|
84
|
+
} else {
|
|
85
|
+
this.#descEl.hidden = true;
|
|
86
|
+
}
|
|
76
87
|
}
|
|
77
88
|
}
|
|
78
89
|
|
package/components/list/list.js
CHANGED
|
@@ -161,15 +161,19 @@ class AdiaListItem extends AdiaElement {
|
|
|
161
161
|
return null;
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
+
// Mark slot elements we create so render() never deletes consumer-provided ones.
|
|
165
|
+
#stampMark(el) { el.dataset.listStamped = '1'; return el; }
|
|
166
|
+
#wasStamped(el) { return el?.dataset?.listStamped === '1'; }
|
|
167
|
+
|
|
164
168
|
#stamp() {
|
|
165
169
|
if (this.#ownChild('[slot="content"]')) return;
|
|
166
170
|
|
|
167
171
|
if (this.icon) {
|
|
168
172
|
let iconEl = this.#ownChild('[slot="icon"]') || this.#ownChild('icon-ui');
|
|
169
173
|
if (!iconEl) {
|
|
170
|
-
iconEl = document.createElement('icon-ui');
|
|
174
|
+
iconEl = this.#stampMark(document.createElement('icon-ui'));
|
|
171
175
|
iconEl.setAttribute('slot', 'icon');
|
|
172
|
-
this.
|
|
176
|
+
this.prepend(iconEl);
|
|
173
177
|
}
|
|
174
178
|
iconEl.setAttribute('name', this.icon);
|
|
175
179
|
}
|
|
@@ -177,56 +181,56 @@ class AdiaListItem extends AdiaElement {
|
|
|
177
181
|
if (this.text) {
|
|
178
182
|
let span = this.#ownChild('[slot="text"]');
|
|
179
183
|
if (!span) {
|
|
180
|
-
span = document.createElement('span');
|
|
184
|
+
span = this.#stampMark(document.createElement('span'));
|
|
181
185
|
span.setAttribute('slot', 'text');
|
|
182
186
|
this.appendChild(span);
|
|
183
187
|
}
|
|
184
|
-
span.textContent = this.text;
|
|
188
|
+
if (this.#wasStamped(span)) span.textContent = this.text;
|
|
185
189
|
}
|
|
186
190
|
|
|
187
191
|
if (this.description) {
|
|
188
192
|
let desc = this.#ownChild('[slot="description"]');
|
|
189
193
|
if (!desc) {
|
|
190
|
-
desc = document.createElement('span');
|
|
194
|
+
desc = this.#stampMark(document.createElement('span'));
|
|
191
195
|
desc.setAttribute('slot', 'description');
|
|
192
196
|
this.appendChild(desc);
|
|
193
197
|
}
|
|
194
|
-
desc.textContent = this.description;
|
|
198
|
+
if (this.#wasStamped(desc)) desc.textContent = this.description;
|
|
195
199
|
}
|
|
196
200
|
}
|
|
197
201
|
|
|
198
202
|
render() {
|
|
199
|
-
// Sync icon
|
|
203
|
+
// Sync icon — only touch elements we stamped.
|
|
200
204
|
const iconEl = this.#ownChild('[slot="icon"]');
|
|
201
205
|
if (this.icon) {
|
|
202
206
|
if (iconEl) {
|
|
203
|
-
iconEl.setAttribute('name', this.icon);
|
|
207
|
+
if (this.#wasStamped(iconEl)) iconEl.setAttribute('name', this.icon);
|
|
204
208
|
} else {
|
|
205
|
-
const el = document.createElement('icon-ui');
|
|
209
|
+
const el = this.#stampMark(document.createElement('icon-ui'));
|
|
206
210
|
el.setAttribute('slot', 'icon');
|
|
207
211
|
el.setAttribute('name', this.icon);
|
|
208
212
|
this.prepend(el);
|
|
209
213
|
}
|
|
210
|
-
} else if (iconEl) {
|
|
214
|
+
} else if (this.#wasStamped(iconEl)) {
|
|
211
215
|
iconEl.remove();
|
|
212
216
|
}
|
|
213
217
|
|
|
214
|
-
// Sync text
|
|
218
|
+
// Sync text — only touch elements we stamped.
|
|
215
219
|
const textEl = this.#ownChild('[slot="text"]');
|
|
216
|
-
if (textEl) textEl.textContent = this.text;
|
|
220
|
+
if (this.#wasStamped(textEl)) textEl.textContent = this.text;
|
|
217
221
|
|
|
218
|
-
// Sync description
|
|
222
|
+
// Sync description — only touch elements we stamped.
|
|
219
223
|
const descEl = this.#ownChild('[slot="description"]');
|
|
220
224
|
if (this.description) {
|
|
221
225
|
if (descEl) {
|
|
222
|
-
descEl.textContent = this.description;
|
|
226
|
+
if (this.#wasStamped(descEl)) descEl.textContent = this.description;
|
|
223
227
|
} else {
|
|
224
|
-
const el = document.createElement('span');
|
|
228
|
+
const el = this.#stampMark(document.createElement('span'));
|
|
225
229
|
el.setAttribute('slot', 'description');
|
|
226
230
|
el.textContent = this.description;
|
|
227
231
|
this.appendChild(el);
|
|
228
232
|
}
|
|
229
|
-
} else if (descEl) {
|
|
233
|
+
} else if (this.#wasStamped(descEl)) {
|
|
230
234
|
descEl.remove();
|
|
231
235
|
}
|
|
232
236
|
}
|