@adia-ai/web-components 0.0.14 → 0.0.16
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/README.md +43 -1
- package/components/alert/alert.css +5 -0
- package/components/alert/alert.js +4 -2
- package/components/button/button.js +4 -1
- package/components/chart/chart.js +7 -4
- package/components/chat/chat-input.js +13 -2
- package/components/description-list/description-list.js +4 -3
- package/components/field/field.css +113 -63
- package/components/field/field.js +44 -142
- package/components/icon/icon.a2ui.json +1 -1
- package/components/icon/icon.css +16 -0
- package/components/icon/icon.js +18 -0
- package/components/icon/icon.yaml +6 -2
- package/components/index.js +7 -0
- package/components/input/input.a2ui.json +1 -1
- package/components/input/input.css +21 -23
- package/components/input/input.js +36 -9
- package/components/input/input.yaml +3 -1
- package/components/option-card/option-card.a2ui.json +262 -0
- package/components/option-card/option-card.css +215 -0
- package/components/option-card/option-card.js +158 -0
- package/components/option-card/option-card.yaml +234 -0
- package/components/rating/rating.a2ui.json +10 -0
- package/components/rating/rating.yaml +8 -0
- package/components/segment/segment.a2ui.json +5 -0
- package/components/segment/segment.css +2 -0
- package/components/segment/segment.js +21 -1
- package/components/segment/segment.yaml +5 -0
- package/components/textarea/textarea.css +3 -1
- package/components/textarea/textarea.js +2 -2
- package/components/tooltip/tooltip.js +10 -3
- package/core/data-stream.js +486 -0
- package/core/form.js +5 -0
- package/core/index.js +2 -0
- package/core/streams-bridge.js +96 -0
- package/package.json +1 -1
- package/styles/colors/semantics.css +21 -3
- package/styles/components.css +1 -0
- package/styles/prose.css +3 -7
- package/styles/tokens.css +7 -4
- package/styles/typography.css +6 -1
package/README.md
CHANGED
|
@@ -42,7 +42,9 @@ web-components/
|
|
|
42
42
|
│ ├── provider.js global provider registration + router-ui
|
|
43
43
|
│ ├── anchor.js popover + anchor-positioning
|
|
44
44
|
│ ├── markdown.js lightweight markdown renderer
|
|
45
|
-
│
|
|
45
|
+
│ ├── transport.js SSE / streaming helpers for LLM adapters
|
|
46
|
+
│ └── data-stream.js `data-stream-*` attribute trait (HTTP/SSE/WS,
|
|
47
|
+
│ signal-backed, refcounted shared transports)
|
|
46
48
|
│
|
|
47
49
|
├── components/ — 80 *-ui custom elements
|
|
48
50
|
│ └── <tag>/
|
|
@@ -159,6 +161,46 @@ Accepts the four A2UI message kinds: `updateComponents`,
|
|
|
159
161
|
normalizes LLM-emitted aliases (e.g. `Carousel` → `swiper-ui`) so generated
|
|
160
162
|
output is robust to name drift.
|
|
161
163
|
|
|
164
|
+
## Data streaming via `data-stream-*` attributes
|
|
165
|
+
|
|
166
|
+
Any element with a settable `.data` property — chart-ui, table-ui,
|
|
167
|
+
heatmap-ui, stat-ui, list-ui, etc. — can be fed from a backing
|
|
168
|
+
source via attributes alone. No per-component opt-in:
|
|
169
|
+
|
|
170
|
+
```html
|
|
171
|
+
<!-- HTTP one-shot fetch, JSON -->
|
|
172
|
+
<chart-ui type="area" x="month" y="revenue"
|
|
173
|
+
data-stream-src="/api/revenue?range=3m"
|
|
174
|
+
data-stream-path="data"></chart-ui>
|
|
175
|
+
|
|
176
|
+
<!-- HTTP polling every 5s -->
|
|
177
|
+
<table-ui sortable striped
|
|
178
|
+
data-stream-src="/api/orders"
|
|
179
|
+
data-stream-interval="5000"></table-ui>
|
|
180
|
+
|
|
181
|
+
<!-- Server-Sent Events, append on each message -->
|
|
182
|
+
<heatmap-ui type="matrix" rows="7" cols="52"
|
|
183
|
+
data-stream-src="/sse/activity"
|
|
184
|
+
data-stream-mode="sse"
|
|
185
|
+
data-stream-merge="append"></heatmap-ui>
|
|
186
|
+
|
|
187
|
+
<!-- Spread a multi-property response onto the element -->
|
|
188
|
+
<stat-ui data-stream-src="/api/kpi"
|
|
189
|
+
data-stream-target="*"></stat-ui>
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Modes: HTTP (one-shot or polling), `sse` (`EventSource`), `ws`
|
|
193
|
+
(`WebSocket`). Formats: `json` (default), `csv`, `tsv`, `jsonl`,
|
|
194
|
+
`text` — auto-detected from URL extension or content-type. Two
|
|
195
|
+
elements with attribute-identical streams share one transport
|
|
196
|
+
(refcounted, signal-backed); explicit `data-stream-id` lets
|
|
197
|
+
unrelated configs share intentionally. Programmatic access via
|
|
198
|
+
the `streams` registry export from `core/data-stream.js`.
|
|
199
|
+
|
|
200
|
+
Implementation: `core/data-stream.js` (~360 lines). Full
|
|
201
|
+
attribute table + live demos:
|
|
202
|
+
[`/site/components/chart#data-stream`](./site/pages/components/chart/index.html).
|
|
203
|
+
|
|
162
204
|
## Build
|
|
163
205
|
|
|
164
206
|
```bash
|
|
@@ -65,6 +65,11 @@
|
|
|
65
65
|
:scope [slot="leading"] {
|
|
66
66
|
flex-shrink: 0;
|
|
67
67
|
color: var(--alert-icon-fg);
|
|
68
|
+
/* `ensure()` appends the leading-slot icon to the host, which
|
|
69
|
+
puts it after any consumer-provided content in DOM order.
|
|
70
|
+
Force it to the visual lead via flex `order` so the icon
|
|
71
|
+
always reads first. */
|
|
72
|
+
order: -1;
|
|
68
73
|
}
|
|
69
74
|
|
|
70
75
|
:scope [slot="content"] {
|
|
@@ -66,9 +66,11 @@ class AdiaAlert extends AdiaElement {
|
|
|
66
66
|
this.drop('leading');
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
// Text
|
|
69
|
+
// Text — only write from the `text` attribute when it's set, so
|
|
70
|
+
// consumers passing rich content via `<span slot="content">…</span>`
|
|
71
|
+
// (links, <strong>, etc.) aren't clobbered with empty textContent.
|
|
70
72
|
const content = this.ensure('content');
|
|
71
|
-
if (content) content.textContent = this.text;
|
|
73
|
+
if (content && this.text) content.textContent = this.text;
|
|
72
74
|
|
|
73
75
|
// Close button
|
|
74
76
|
if (this.closable) {
|
|
@@ -22,7 +22,10 @@ class AdiaButton extends AdiaElement {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
render() {
|
|
25
|
-
|
|
25
|
+
// Don't clobber a user-provided aria-label with an empty string when
|
|
26
|
+
// text is unset (e.g. icon-only button with author-set aria-label).
|
|
27
|
+
// Only auto-set when we have meaningful text to put there.
|
|
28
|
+
if (this.text) this.setAttribute('aria-label', this.text);
|
|
26
29
|
if (this.icon) {
|
|
27
30
|
const existing = this.querySelector('icon-ui');
|
|
28
31
|
if (!existing || existing.name !== this.icon) {
|
|
@@ -885,15 +885,18 @@ class AdiaChart extends AdiaElement {
|
|
|
885
885
|
|
|
886
886
|
try { this.#tipEl.showPopover(); } catch (_) { /* popover not supported */ }
|
|
887
887
|
|
|
888
|
-
/* Follow the cursor —
|
|
888
|
+
/* Follow the cursor — centered horizontally above, clamp to viewport
|
|
889
|
+
with an 8px edge-pad, flip below when there's no room above. */
|
|
889
890
|
const gap = 12;
|
|
891
|
+
const edgePad = 8;
|
|
890
892
|
const { clientX, clientY } = event;
|
|
891
893
|
const tw = this.#tipEl.offsetWidth || 0;
|
|
892
894
|
const th = this.#tipEl.offsetHeight || 0;
|
|
893
|
-
let x = clientX
|
|
895
|
+
let x = clientX - tw / 2;
|
|
894
896
|
let y = clientY - th - gap;
|
|
895
|
-
if (x
|
|
896
|
-
if (
|
|
897
|
+
if (x < edgePad) x = edgePad;
|
|
898
|
+
if (x + tw > window.innerWidth - edgePad) x = window.innerWidth - tw - edgePad;
|
|
899
|
+
if (y < edgePad) y = clientY + gap;
|
|
897
900
|
this.#tipEl.style.left = `${x}px`;
|
|
898
901
|
this.#tipEl.style.top = `${y}px`;
|
|
899
902
|
}
|
|
@@ -85,8 +85,8 @@ class AdiaChatInput extends AdiaElement {
|
|
|
85
85
|
this.innerHTML = `
|
|
86
86
|
<textarea-ui placeholder="${this.placeholder}" rows="1"></textarea-ui>
|
|
87
87
|
<div slot="toolbar">
|
|
88
|
-
<select-ui slot="model" placeholder="Model" divider></select-ui>
|
|
89
|
-
<button-ui icon="paper-plane-right" variant="ghost" slot="send"></button-ui>
|
|
88
|
+
<select-ui slot="model" placeholder="Model" aria-label="Select model" divider></select-ui>
|
|
89
|
+
<button-ui icon="paper-plane-right" variant="ghost" slot="send" aria-label="Send message"></button-ui>
|
|
90
90
|
</div>
|
|
91
91
|
`;
|
|
92
92
|
}
|
|
@@ -95,6 +95,16 @@ class AdiaChatInput extends AdiaElement {
|
|
|
95
95
|
this.#sendEl = this.querySelector('[slot="send"]');
|
|
96
96
|
this.#modelEl = this.querySelector('[slot="model"]');
|
|
97
97
|
|
|
98
|
+
// Default aria-labels on author-provided send/model when not set —
|
|
99
|
+
// these elements are screen-reader-relevant and shouldn't fall back
|
|
100
|
+
// to the icon-name announcement.
|
|
101
|
+
if (this.#sendEl && !this.#sendEl.hasAttribute('aria-label')) {
|
|
102
|
+
this.#sendEl.setAttribute('aria-label', 'Send message');
|
|
103
|
+
}
|
|
104
|
+
if (this.#modelEl && !this.#modelEl.hasAttribute('aria-label')) {
|
|
105
|
+
this.#modelEl.setAttribute('aria-label', 'Select model');
|
|
106
|
+
}
|
|
107
|
+
|
|
98
108
|
// Apply models if set before connected (options first, then value)
|
|
99
109
|
if (this.#models.length && this.#modelEl) {
|
|
100
110
|
this.#modelEl.options = this.#models;
|
|
@@ -116,6 +126,7 @@ class AdiaChatInput extends AdiaElement {
|
|
|
116
126
|
this.#attachBtn.setAttribute('variant', 'ghost');
|
|
117
127
|
this.#attachBtn.setAttribute('slot', 'attach');
|
|
118
128
|
this.#attachBtn.setAttribute('size', 'sm');
|
|
129
|
+
this.#attachBtn.setAttribute('aria-label', 'Attach image');
|
|
119
130
|
const sendBtn = toolbar.querySelector('[slot="send"]');
|
|
120
131
|
toolbar.insertBefore(this.#attachBtn, sendBtn);
|
|
121
132
|
this.#attachBtn.addEventListener('press', this.#onAttachPress);
|
|
@@ -30,9 +30,10 @@ class AdiaDescriptionList extends AdiaElement {
|
|
|
30
30
|
static template = () => null;
|
|
31
31
|
|
|
32
32
|
connected() {
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
|
|
33
|
+
// ARIA 1.2: list role requires listitem children, but <dt>/<dd>
|
|
34
|
+
// aren't listitems. group role is the accurate fit for a
|
|
35
|
+
// labeled-pairs grouping.
|
|
36
|
+
this.setAttribute('role', 'group');
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
render() {
|
|
@@ -7,105 +7,155 @@
|
|
|
7
7
|
--field-label-weight: var(--a-weight-medium);
|
|
8
8
|
--field-required-color: var(--a-danger);
|
|
9
9
|
--field-trailing-color: var(--a-fg-subtle);
|
|
10
|
-
--field-trailing-size: var(--a-ui-
|
|
10
|
+
--field-trailing-size: var(--a-ui-sm);
|
|
11
11
|
--field-hint-color: var(--a-fg-muted);
|
|
12
|
-
--field-hint-size: var(--a-ui-
|
|
12
|
+
--field-hint-size: var(--a-ui-sm);
|
|
13
13
|
--field-error-color: var(--a-danger);
|
|
14
|
-
--field-error-size: var(--a-ui-
|
|
14
|
+
--field-error-size: var(--a-ui-sm);
|
|
15
|
+
|
|
16
|
+
/* In inline mode, the label column auto-sizes by default (each
|
|
17
|
+
field's label column is independent of its siblings). Consumers
|
|
18
|
+
that want shared label-column alignment across stacked inline
|
|
19
|
+
fields can raise this floor — e.g. 12rem in a multi-field form. */
|
|
20
|
+
--field-label-inline-min: 0;
|
|
15
21
|
}
|
|
16
22
|
|
|
17
|
-
/* ── Base —
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
23
|
+
/* ── Base — single grid; children placed by [slot] attribute via
|
|
24
|
+
named grid-areas. No row wrappers; no DOM reparenting. The
|
|
25
|
+
template adapts via `:has()` to which slots are present so
|
|
26
|
+
empty tracks don't leak column-gap. ── */
|
|
21
27
|
:scope {
|
|
22
28
|
box-sizing: border-box;
|
|
23
|
-
display:
|
|
24
|
-
|
|
25
|
-
|
|
29
|
+
display: grid;
|
|
30
|
+
grid-template-columns: minmax(0, 1fr);
|
|
31
|
+
grid-template-areas:
|
|
32
|
+
"label"
|
|
33
|
+
"control"
|
|
34
|
+
"message";
|
|
35
|
+
column-gap: var(--field-gap);
|
|
36
|
+
row-gap: var(--field-gap);
|
|
37
|
+
align-items: center;
|
|
26
38
|
}
|
|
27
39
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
40
|
+
/* Stacked + (trailing or action) → 2-col */
|
|
41
|
+
:scope:has(> :is([slot="trailing"], [slot="action"])) {
|
|
42
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
43
|
+
grid-template-areas:
|
|
44
|
+
"label trailing"
|
|
45
|
+
"control action"
|
|
46
|
+
"message message";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* Stacked + no label, no trailing → drop the empty top row. */
|
|
50
|
+
:scope:not([label]):not(:has(> [slot="trailing"])) {
|
|
51
|
+
grid-template-areas:
|
|
52
|
+
"control"
|
|
53
|
+
"message";
|
|
54
|
+
}
|
|
55
|
+
:scope:not([label]):not(:has(> [slot="trailing"])):has(> [slot="action"]) {
|
|
56
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
57
|
+
grid-template-areas:
|
|
58
|
+
"control action"
|
|
59
|
+
"message message";
|
|
33
60
|
}
|
|
34
61
|
|
|
35
|
-
/*
|
|
36
|
-
:scope > [
|
|
37
|
-
|
|
62
|
+
/* Hide the label cell when the label attr is absent. */
|
|
63
|
+
:scope:not([label]) > [slot="label"] { display: none; }
|
|
64
|
+
|
|
65
|
+
/* ── Slot styling ── */
|
|
66
|
+
:scope > [slot="label"] {
|
|
67
|
+
grid-area: label;
|
|
38
68
|
color: var(--field-label-color);
|
|
39
69
|
font-size: var(--field-label-size);
|
|
40
70
|
font-weight: var(--field-label-weight);
|
|
41
71
|
cursor: pointer;
|
|
42
72
|
min-width: 0;
|
|
43
73
|
}
|
|
44
|
-
:scope > [
|
|
74
|
+
:scope > [slot="label"] > [data-field-required] {
|
|
45
75
|
color: var(--field-required-color);
|
|
46
76
|
margin-inline-start: 0.15em;
|
|
47
77
|
font-weight: var(--a-weight-bold);
|
|
48
78
|
}
|
|
49
|
-
:scope > [
|
|
50
|
-
|
|
79
|
+
:scope > [slot="trailing"] {
|
|
80
|
+
grid-area: trailing;
|
|
81
|
+
justify-self: end;
|
|
51
82
|
color: var(--field-trailing-color);
|
|
52
83
|
font-size: var(--field-trailing-size);
|
|
53
84
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
:scope > [data-row="control"] > :not([slot="action"]) {
|
|
57
|
-
flex: 1 1 auto;
|
|
85
|
+
:scope > :not([slot]) {
|
|
86
|
+
grid-area: control;
|
|
58
87
|
min-width: 0;
|
|
59
88
|
}
|
|
60
|
-
:scope > [
|
|
61
|
-
|
|
89
|
+
:scope > [slot="action"] {
|
|
90
|
+
grid-area: action;
|
|
91
|
+
justify-self: end;
|
|
62
92
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
93
|
+
:scope > [slot="hint"],
|
|
94
|
+
:scope > [slot="error"] {
|
|
95
|
+
grid-area: message;
|
|
96
|
+
line-height: 1.3;
|
|
97
|
+
}
|
|
98
|
+
:scope > [slot="hint"] {
|
|
66
99
|
color: var(--field-hint-color);
|
|
67
100
|
font-size: var(--field-hint-size);
|
|
68
|
-
line-height: 1.3;
|
|
69
101
|
}
|
|
70
|
-
:scope > [
|
|
102
|
+
:scope > [slot="error"] {
|
|
71
103
|
color: var(--field-error-color);
|
|
72
104
|
font-size: var(--field-error-size);
|
|
73
|
-
line-height: 1.3;
|
|
74
105
|
font-weight: var(--a-weight-medium);
|
|
75
106
|
}
|
|
76
107
|
|
|
77
|
-
/*
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
108
|
+
/* ── Mode: inline — content slots on row 1, message row below
|
|
109
|
+
(aligned with the control column so hint/error sit under the
|
|
110
|
+
input, not under the label). Templates branch by which
|
|
111
|
+
optional slots are present so we don't carry zero-width
|
|
112
|
+
tracks + their gaps. ── */
|
|
113
|
+
:scope[inline] {
|
|
114
|
+
grid-template-columns: minmax(var(--field-label-inline-min), auto) minmax(0, 1fr);
|
|
115
|
+
grid-template-areas:
|
|
116
|
+
"label control"
|
|
117
|
+
". message";
|
|
81
118
|
}
|
|
82
|
-
:scope:
|
|
83
|
-
|
|
119
|
+
:scope[inline]:has(> [slot="trailing"]):not(:has(> [slot="action"])) {
|
|
120
|
+
grid-template-columns: minmax(var(--field-label-inline-min), auto) auto minmax(0, 1fr);
|
|
121
|
+
grid-template-areas:
|
|
122
|
+
"label trailing control"
|
|
123
|
+
". . message";
|
|
84
124
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
message
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
grid-template-columns: auto auto 1fr auto;
|
|
93
|
-
grid-template-
|
|
94
|
-
|
|
95
|
-
|
|
125
|
+
:scope[inline]:not(:has(> [slot="trailing"])):has(> [slot="action"]) {
|
|
126
|
+
grid-template-columns: minmax(var(--field-label-inline-min), auto) minmax(0, 1fr) auto;
|
|
127
|
+
grid-template-areas:
|
|
128
|
+
"label control action"
|
|
129
|
+
". message message";
|
|
130
|
+
}
|
|
131
|
+
:scope[inline]:has(> [slot="trailing"]):has(> [slot="action"]) {
|
|
132
|
+
grid-template-columns: minmax(var(--field-label-inline-min), auto) auto minmax(0, 1fr) auto;
|
|
133
|
+
grid-template-areas:
|
|
134
|
+
"label trailing control action"
|
|
135
|
+
". . message message";
|
|
96
136
|
}
|
|
97
|
-
:scope[inline] > [
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
:scope[inline]
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
137
|
+
:scope[inline]:not([label]):not(:has(> [slot="trailing"])):not(:has(> [slot="action"])) {
|
|
138
|
+
grid-template-columns: minmax(0, 1fr);
|
|
139
|
+
grid-template-areas:
|
|
140
|
+
"control"
|
|
141
|
+
"message";
|
|
142
|
+
}
|
|
143
|
+
:scope[inline]:not([label]):not(:has(> [slot="trailing"])):has(> [slot="action"]) {
|
|
144
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
145
|
+
grid-template-areas:
|
|
146
|
+
"control action"
|
|
147
|
+
"message message";
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/* In inline mode, push compact toggle controls (switch / check /
|
|
151
|
+
radio) to the row's end edge — settings rows then render label-
|
|
152
|
+
left, control-right regardless of label length. Wide controls
|
|
153
|
+
(input / textarea / select) keep their default stretch behavior
|
|
154
|
+
so they fill the trailing column. Already in HEAD as `356a39f`
|
|
155
|
+
against the row-wrapper model; the second selector here keeps
|
|
156
|
+
the rule alive once the v2 flat-DOM refactor (§18) lands. */
|
|
157
|
+
:scope[inline]:has(:is(switch-ui, check-ui, radio-ui)) > [data-row="control"] > :not([slot="action"]),
|
|
158
|
+
:scope[inline]:has(> :is(switch-ui, check-ui, radio-ui)) > :not([slot]) {
|
|
159
|
+
justify-self: end;
|
|
110
160
|
}
|
|
111
161
|
}
|