@adia-ai/web-components 0.0.15 → 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/components/alert/alert.css +5 -0
- package/components/alert/alert.js +4 -2
- package/components/button/button.js +4 -1
- 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 +1 -0
- package/components/input/input.a2ui.json +1 -1
- package/components/input/input.css +19 -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 +1 -1
- package/components/textarea/textarea.js +2 -2
- package/core/data-stream.js +21 -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 +8 -3
- package/styles/components.css +1 -0
- package/styles/prose.css +3 -7
- package/styles/tokens.css +7 -4
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* <segment-ui value="tab1" text="Tab 1"></segment-ui>
|
|
2
|
+
* <segment-ui value="tab1" text="Tab 1" icon="list"></segment-ui>
|
|
3
3
|
* Child of segmented-ui. Represents one option.
|
|
4
|
+
*
|
|
5
|
+
* `icon=` stamps a leading <icon-ui name="…"> child on render.
|
|
6
|
+
* Mirrors the button-ui pattern.
|
|
4
7
|
*/
|
|
5
8
|
|
|
6
9
|
import { AdiaElement } from '../../core/element.js';
|
|
@@ -9,6 +12,7 @@ class AdiaSegment extends AdiaElement {
|
|
|
9
12
|
static properties = {
|
|
10
13
|
value: { type: String, default: '', reflect: true },
|
|
11
14
|
text: { type: String, default: '', reflect: true },
|
|
15
|
+
icon: { type: String, default: '', reflect: true },
|
|
12
16
|
disabled: { type: Boolean, default: false, reflect: true },
|
|
13
17
|
selected: { type: Boolean, default: false, reflect: true },
|
|
14
18
|
};
|
|
@@ -25,6 +29,22 @@ class AdiaSegment extends AdiaElement {
|
|
|
25
29
|
this.setAttribute('aria-checked', this.selected ? 'true' : 'false');
|
|
26
30
|
if (this.disabled) this.setAttribute('aria-disabled', 'true');
|
|
27
31
|
else this.removeAttribute('aria-disabled');
|
|
32
|
+
|
|
33
|
+
// Stamp/refresh leading icon-ui child when [icon] is set.
|
|
34
|
+
// Mirrors button-ui's pattern (button.js:29) — author code never
|
|
35
|
+
// hand-rolls an icon-ui inside a segment.
|
|
36
|
+
if (this.icon) {
|
|
37
|
+
const existing = this.querySelector(':scope > icon-ui');
|
|
38
|
+
if (!existing || existing.getAttribute('name') !== this.icon) {
|
|
39
|
+
existing?.remove();
|
|
40
|
+
const iconEl = document.createElement('icon-ui');
|
|
41
|
+
iconEl.setAttribute('name', this.icon);
|
|
42
|
+
iconEl.setAttribute('aria-hidden', 'true');
|
|
43
|
+
this.prepend(iconEl);
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
this.querySelector(':scope > icon-ui')?.remove();
|
|
47
|
+
}
|
|
28
48
|
}
|
|
29
49
|
}
|
|
30
50
|
customElements.define('segment-ui', AdiaSegment);
|
|
@@ -13,6 +13,11 @@ props:
|
|
|
13
13
|
type: boolean
|
|
14
14
|
default: false
|
|
15
15
|
reflect: true
|
|
16
|
+
icon:
|
|
17
|
+
description: Phosphor icon name. Rendered as a leading <icon-ui> child stamped on render.
|
|
18
|
+
type: string
|
|
19
|
+
default: ""
|
|
20
|
+
reflect: true
|
|
16
21
|
selected:
|
|
17
22
|
description: Whether this segment is currently selected. Managed by the parent Segmented container; don't set multiple siblings selected.
|
|
18
23
|
type: boolean
|
|
@@ -29,7 +29,7 @@ class AdiaTextarea extends AdiaFormElement {
|
|
|
29
29
|
${this.label ? `<label slot="label" label="${this.label}"></label>` : ''}
|
|
30
30
|
<div slot="text" contenteditable="plaintext-only" tabindex="0"
|
|
31
31
|
${this.value ? '' : 'data-empty=""'}
|
|
32
|
-
|
|
32
|
+
data-placeholder="${this.placeholder}"></div>
|
|
33
33
|
`;
|
|
34
34
|
}
|
|
35
35
|
|
|
@@ -47,7 +47,7 @@ class AdiaTextarea extends AdiaFormElement {
|
|
|
47
47
|
render() {
|
|
48
48
|
if (!this.#textEl) return;
|
|
49
49
|
|
|
50
|
-
this.#textEl.setAttribute('
|
|
50
|
+
this.#textEl.setAttribute('data-placeholder', this.placeholder);
|
|
51
51
|
|
|
52
52
|
if (this.disabled || this.readonly) {
|
|
53
53
|
this.#textEl.contentEditable = 'false';
|
package/core/data-stream.js
CHANGED
|
@@ -58,6 +58,7 @@ const ATTRS = {
|
|
|
58
58
|
|
|
59
59
|
const STREAMS = new Map(); /* streamId → { signal, refs, transport, opts } */
|
|
60
60
|
const ELEMENT_STREAMS = new WeakMap(); /* el → { streamId, dispose, loaded } */
|
|
61
|
+
const PENDING = new Map(); /* streamId → array of whenStream resolvers */
|
|
61
62
|
const WARNED = new WeakSet();
|
|
62
63
|
|
|
63
64
|
/* Read-only public view of the streams registry. App code can do:
|
|
@@ -70,6 +71,21 @@ export const streams = {
|
|
|
70
71
|
size() { return STREAMS.size; },
|
|
71
72
|
};
|
|
72
73
|
|
|
74
|
+
/* Resolves with the stream entry once it's registered. If the stream
|
|
75
|
+
already exists, resolves synchronously on the next microtask; if not,
|
|
76
|
+
resolves the moment the first DOM consumer (or any caller of
|
|
77
|
+
acquireStream) creates it. Useful for cross-package adapters
|
|
78
|
+
(streams-bridge, A2UI surfaces) that may run before the DOM source
|
|
79
|
+
has connected. The promise never rejects — pair with AbortSignal at
|
|
80
|
+
the caller if you need cancellation. */
|
|
81
|
+
export function whenStream(id) {
|
|
82
|
+
if (STREAMS.has(id)) return Promise.resolve(STREAMS.get(id));
|
|
83
|
+
return new Promise((resolve) => {
|
|
84
|
+
if (!PENDING.has(id)) PENDING.set(id, []);
|
|
85
|
+
PENDING.get(id).push(resolve);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
73
89
|
function attr(el, key) { return el.getAttribute(ATTRS[key]) ?? ''; }
|
|
74
90
|
function dispatch(el, name, detail) {
|
|
75
91
|
el.dispatchEvent(new CustomEvent(name, { bubbles: true, detail }));
|
|
@@ -309,6 +325,11 @@ function acquireStream(id, opts) {
|
|
|
309
325
|
};
|
|
310
326
|
STREAMS.set(id, stream);
|
|
311
327
|
stream.transport = startTransport(stream);
|
|
328
|
+
if (PENDING.has(id)) {
|
|
329
|
+
const resolvers = PENDING.get(id);
|
|
330
|
+
PENDING.delete(id);
|
|
331
|
+
for (const r of resolvers) r(stream);
|
|
332
|
+
}
|
|
312
333
|
return stream;
|
|
313
334
|
}
|
|
314
335
|
|
package/core/form.js
CHANGED
|
@@ -188,10 +188,15 @@ export class AdiaFormElement extends AdiaElement {
|
|
|
188
188
|
* slots; the per-control `label` attr renders via an inert shadow
|
|
189
189
|
* slot without `[for]`, so click-to-focus doesn't work.
|
|
190
190
|
*
|
|
191
|
+
* Subclasses that have promoted `label` to a first-class API with
|
|
192
|
+
* proper a11y wiring (e.g. input-ui's inline-leading caption with
|
|
193
|
+
* aria-labelledby) opt out by setting `static labelDeprecated = false`.
|
|
194
|
+
*
|
|
191
195
|
* One-shot per class so the console isn't spammed on docs pages.
|
|
192
196
|
*/
|
|
193
197
|
#warnDeprecatedLabel() {
|
|
194
198
|
const ctor = this.constructor;
|
|
199
|
+
if (ctor.labelDeprecated === false) return;
|
|
195
200
|
if (!ctor.properties?.label) return;
|
|
196
201
|
if (!this.hasAttribute('label')) return;
|
|
197
202
|
if (AdiaFormElement.#labelWarned.has(ctor)) return;
|
package/core/index.js
CHANGED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* streams-bridge — proxy data-stream signals into A2UI surface data
|
|
3
|
+
* models (OD-CHART-16, option b).
|
|
4
|
+
*
|
|
5
|
+
* effect()
|
|
6
|
+
* data-stream signal ──────────────────────▶ renderer.process({
|
|
7
|
+
* (any consumer: type: 'updateDataModel',
|
|
8
|
+
* chart-ui, table-ui, surfaceId, path, value
|
|
9
|
+
* headless app code) })
|
|
10
|
+
*
|
|
11
|
+
* Lives in `web-components/core/` rather than `a2ui-utils/` because
|
|
12
|
+
* the dependency direction is web-components → a2ui-utils, not the
|
|
13
|
+
* reverse. The bridge duck-types the renderer (it needs `.process()`,
|
|
14
|
+
* nothing else), so it works with any A2UI-protocol consumer — the
|
|
15
|
+
* actual `A2UIRenderer` from `@adia-ai/a2ui-utils`, a custom renderer,
|
|
16
|
+
* or a test stub.
|
|
17
|
+
*
|
|
18
|
+
* Contract:
|
|
19
|
+
* - One-way only: stream → data model. A2UI writes back to its model
|
|
20
|
+
* (e.g. via `update-data-model` wiring actions) do NOT propagate
|
|
21
|
+
* into the stream signal. If you need bidirectional state, the
|
|
22
|
+
* stream is the wrong substrate — use a wiring action.
|
|
23
|
+
* - Tear down with the returned dispose function. Dispose detaches
|
|
24
|
+
* the effect; it does NOT release the stream's refcount, because
|
|
25
|
+
* the bridge isn't the source of truth for the stream's lifecycle
|
|
26
|
+
* (the DOM consumer or a separate `acquireStream` caller is).
|
|
27
|
+
* - `select` uses dot-separated path syntax (matches
|
|
28
|
+
* `data-stream-path` on the trait). `path` uses slash-separated
|
|
29
|
+
* syntax (matches A2UI bindings).
|
|
30
|
+
*
|
|
31
|
+
* Usage:
|
|
32
|
+
* const dispose = bridgeStream(renderer, {
|
|
33
|
+
* surfaceId: 'main',
|
|
34
|
+
* streamId: 'id:rev', // explicit data-stream-id
|
|
35
|
+
* path: 'metrics/revenue', // A2UI data-model path
|
|
36
|
+
* select: 'data', // optional: sub-path within stream
|
|
37
|
+
* });
|
|
38
|
+
* // ... later
|
|
39
|
+
* dispose();
|
|
40
|
+
*
|
|
41
|
+
* // For DOM-first sources where the stream may register after the
|
|
42
|
+
* // bridge call returns:
|
|
43
|
+
* const dispose = await bridgeStreamAsync(renderer, opts);
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
import { effect } from './signals.js';
|
|
47
|
+
import { streams, whenStream } from './data-stream.js';
|
|
48
|
+
|
|
49
|
+
function resolveSelect(obj, dottedPath) {
|
|
50
|
+
if (!dottedPath) return obj;
|
|
51
|
+
let cursor = obj;
|
|
52
|
+
for (const seg of dottedPath.split('.')) {
|
|
53
|
+
if (cursor == null) return null;
|
|
54
|
+
cursor = cursor[seg];
|
|
55
|
+
}
|
|
56
|
+
return cursor;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function attach(renderer, stream, { surfaceId, path, select }) {
|
|
60
|
+
return effect(() => {
|
|
61
|
+
const value = stream.signal.value;
|
|
62
|
+
if (value == null) return;
|
|
63
|
+
const out = select ? resolveSelect(value, select) : value;
|
|
64
|
+
renderer.process({ type: 'updateDataModel', surfaceId, path, value: out });
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Bridge a registered stream into a renderer's surface data model.
|
|
70
|
+
* If the stream is not yet registered, logs a warning and returns a
|
|
71
|
+
* no-op dispose. Use {@link bridgeStreamAsync} when the stream might
|
|
72
|
+
* register after this call (e.g. DOM source connecting on the same
|
|
73
|
+
* tick as the bridge wiring).
|
|
74
|
+
*/
|
|
75
|
+
export function bridgeStream(renderer, opts) {
|
|
76
|
+
const { streamId } = opts;
|
|
77
|
+
const stream = streams.get(streamId);
|
|
78
|
+
if (!stream) {
|
|
79
|
+
console.warn(
|
|
80
|
+
`[streams-bridge] stream "${streamId}" not registered — ` +
|
|
81
|
+
`call bridgeStreamAsync() to wait, or ensure the DOM source is ` +
|
|
82
|
+
`connected before bridging.`,
|
|
83
|
+
);
|
|
84
|
+
return () => {};
|
|
85
|
+
}
|
|
86
|
+
return attach(renderer, stream, opts);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Async variant — awaits {@link whenStream} before attaching the
|
|
91
|
+
* effect. Resolves once the bridge is wired (returns the dispose fn).
|
|
92
|
+
*/
|
|
93
|
+
export async function bridgeStreamAsync(renderer, opts) {
|
|
94
|
+
const stream = await whenStream(opts.streamId);
|
|
95
|
+
return attach(renderer, stream, opts);
|
|
96
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adia-ai/web-components",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.16",
|
|
4
4
|
"description": "AdiaUI web components — vanilla custom elements. A2UI runtime (renderer, registry, streams, wiring) lives in @adia-ai/a2ui-utils.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -149,9 +149,14 @@
|
|
|
149
149
|
schemes. Anchor color in `typography.css` reads --a-link rather
|
|
150
150
|
than --a-accent-strong; emphasis on hover comes from a tighter
|
|
151
151
|
chroma + darker step rather than full saturation jump. */
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
152
|
+
/* Polarity matches `--a-accent-N` family wrappers: at fine steps >50,
|
|
153
|
+
`tint` is the lower-L (deeper) primitive and `shade` is the higher-L
|
|
154
|
+
primitive — the naming inverts past the convergence point. Light
|
|
155
|
+
mode wants dark-on-light, so we pick `tint` (deep accent); dark
|
|
156
|
+
mode wants light-on-dark, so we pick `shade` (lifted accent). */
|
|
157
|
+
--a-link: light-dark(var(--a-accent-65-tint), var(--a-accent-65-shade));
|
|
158
|
+
--a-link-hover: light-dark(var(--a-accent-70-tint), var(--a-accent-70-shade));
|
|
159
|
+
--a-link-visited: light-dark(var(--a-accent-75-tint), var(--a-accent-75-shade));
|
|
155
160
|
|
|
156
161
|
/* Accent text on accent solid bg — needs light text on dark-ish accent */
|
|
157
162
|
--a-accent-text-strong: light-dark(var(--a-accent-05-shade), var(--a-accent-05-tint));
|
package/styles/components.css
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
@import "../components/textarea/textarea.css";
|
|
18
18
|
@import "../components/check/check.css";
|
|
19
19
|
@import "../components/radio/radio.css";
|
|
20
|
+
@import "../components/option-card/option-card.css";
|
|
20
21
|
@import "../components/switch/switch.css";
|
|
21
22
|
@import "../components/slider/slider.css";
|
|
22
23
|
@import "../components/select/select.css";
|
package/styles/prose.css
CHANGED
|
@@ -12,10 +12,9 @@
|
|
|
12
12
|
|
|
13
13
|
[prose] {
|
|
14
14
|
/* ── Radius — larger bases than UI ── */
|
|
15
|
-
--a-radius-max
|
|
16
|
-
--a-radius-
|
|
17
|
-
--a-radius-
|
|
18
|
-
--a-radius-lg: clamp(var(--a-radius-min), calc(var(--a-radius-k) * var(--a-radius-max) * 0.75), var(--a-radius-max));
|
|
15
|
+
--a-radius-sm: clamp(var(--a-radius-min), calc(var(--a-radius-k) * var(--a-radius-max) * var(--a-radius-sm-k)), var(--a-radius-max));
|
|
16
|
+
--a-radius-md: clamp(var(--a-radius-min), calc(var(--a-radius-k) * var(--a-radius-max) * var(--a-radius-md-k)), var(--a-radius-max));
|
|
17
|
+
--a-radius-lg: clamp(var(--a-radius-min), calc(var(--a-radius-k) * var(--a-radius-max) * var(--a-radius-lg-k)), var(--a-radius-max));
|
|
19
18
|
--a-radius: var(--a-radius-md);
|
|
20
19
|
|
|
21
20
|
/* ── Inset — shifted up the space scale ── */
|
|
@@ -165,7 +164,6 @@
|
|
|
165
164
|
}
|
|
166
165
|
|
|
167
166
|
[prose] table {
|
|
168
|
-
margin-block: var(--a-space-4);
|
|
169
167
|
border: 1px solid var(--a-border-subtle);
|
|
170
168
|
border-radius: var(--a-radius-md);
|
|
171
169
|
overflow: hidden;
|
|
@@ -177,10 +175,8 @@
|
|
|
177
175
|
|
|
178
176
|
[prose] pre {
|
|
179
177
|
padding: var(--a-space-5);
|
|
180
|
-
margin-block: var(--a-space-4);
|
|
181
178
|
}
|
|
182
179
|
|
|
183
180
|
[prose] img {
|
|
184
181
|
border-radius: var(--a-radius-lg);
|
|
185
|
-
margin-block: var(--a-space-3);
|
|
186
182
|
}
|
package/styles/tokens.css
CHANGED
|
@@ -82,10 +82,13 @@
|
|
|
82
82
|
At k=1, values match the original fixed scale.
|
|
83
83
|
k=0 → sharp (clamped to min). k=2 → extra round (clamped to max). */
|
|
84
84
|
--a-radius-min: 0.25rem;
|
|
85
|
-
--a-radius-max: 1.
|
|
86
|
-
--a-radius-sm
|
|
87
|
-
--a-radius-md
|
|
88
|
-
--a-radius-lg
|
|
85
|
+
--a-radius-max: 1.25rem;
|
|
86
|
+
--a-radius-sm-k: 0.333;
|
|
87
|
+
--a-radius-md-k: 0.666;
|
|
88
|
+
--a-radius-lg-k: 1.000;
|
|
89
|
+
--a-radius-sm: clamp(var(--a-radius-min), calc(var(--a-radius-k) * var(--a-radius-max) * var(--a-radius-sm-k)), var(--a-radius-max));
|
|
90
|
+
--a-radius-md: clamp(var(--a-radius-min), calc(var(--a-radius-k) * var(--a-radius-max) * var(--a-radius-md-k)), var(--a-radius-max));
|
|
91
|
+
--a-radius-lg: clamp(var(--a-radius-min), calc(var(--a-radius-k) * var(--a-radius-max) * var(--a-radius-lg-k)), var(--a-radius-max));
|
|
89
92
|
--a-radius-full: 100rem;
|
|
90
93
|
--a-radius: var(--a-radius-md);
|
|
91
94
|
|