@adia-ai/web-modules 0.3.3 → 0.3.5
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 +60 -0
- package/chat/chat-composer/chat-composer.a2ui.json +94 -0
- package/chat/chat-composer/chat-composer.examples.html +28 -0
- package/chat/chat-composer/chat-composer.html +43 -0
- package/chat/chat-composer/chat-composer.js +107 -0
- package/chat/chat-composer/chat-composer.test.js +112 -0
- package/chat/chat-composer/chat-composer.yaml +91 -0
- package/chat/chat-empty/chat-empty.a2ui.json +68 -0
- package/chat/chat-empty/chat-empty.examples.html +34 -0
- package/chat/chat-empty/chat-empty.html +42 -0
- package/chat/chat-empty/chat-empty.yaml +58 -0
- package/chat/chat-header/chat-header.a2ui.json +77 -0
- package/chat/chat-header/chat-header.examples.html +30 -0
- package/chat/chat-header/chat-header.html +42 -0
- package/chat/chat-header/chat-header.yaml +68 -0
- package/chat/chat-shell/chat-shell.css +1 -0
- package/chat/chat-shell/chat-shell.examples.html +126 -0
- package/chat/chat-shell/chat-shell.html +42 -0
- package/chat/chat-shell/chat-shell.js +35 -7
- package/chat/chat-shell/css/chat-shell.bespoke.css +196 -0
- package/chat/chat-sidebar/chat-sidebar.a2ui.json +136 -0
- package/chat/chat-sidebar/chat-sidebar.examples.html +36 -0
- package/chat/chat-sidebar/chat-sidebar.html +43 -0
- package/chat/chat-sidebar/chat-sidebar.js +227 -0
- package/chat/chat-sidebar/chat-sidebar.test.js +110 -0
- package/chat/chat-sidebar/chat-sidebar.yaml +140 -0
- package/chat/chat-status/chat-status.a2ui.json +63 -0
- package/chat/chat-status/chat-status.examples.html +29 -0
- package/chat/chat-status/chat-status.html +42 -0
- package/chat/chat-status/chat-status.yaml +52 -0
- package/chat/chat-thread/chat-thread.a2ui.json +91 -0
- package/chat/chat-thread/chat-thread.examples.html +36 -0
- package/chat/chat-thread/chat-thread.html +43 -0
- package/chat/chat-thread/chat-thread.js +106 -0
- package/chat/chat-thread/chat-thread.test.js +82 -0
- package/chat/chat-thread/chat-thread.yaml +89 -0
- package/chat/index.js +3 -0
- package/editor/editor-shell/editor-shell.examples.html +71 -0
- package/editor/editor-shell/editor-shell.html +42 -0
- package/package.json +1 -1
- package/shell/admin-command/admin-command.a2ui.json +102 -0
- package/shell/admin-command/admin-command.examples.html +83 -0
- package/shell/admin-command/admin-command.html +42 -0
- package/shell/admin-command/admin-command.js +161 -0
- package/shell/admin-command/admin-command.test.js +115 -0
- package/shell/admin-command/admin-command.yaml +102 -0
- package/shell/admin-content/admin-content.a2ui.json +73 -0
- package/shell/admin-content/admin-content.examples.html +33 -0
- package/shell/admin-content/admin-content.html +42 -0
- package/shell/admin-content/admin-content.yaml +63 -0
- package/shell/admin-page/admin-page.a2ui.json +74 -0
- package/shell/admin-page/admin-page.examples.html +37 -0
- package/shell/admin-page/admin-page.html +42 -0
- package/shell/admin-page/admin-page.yaml +61 -0
- package/shell/admin-page-body/admin-page-body.a2ui.json +62 -0
- package/shell/admin-page-body/admin-page-body.examples.html +34 -0
- package/shell/admin-page-body/admin-page-body.html +42 -0
- package/shell/admin-page-body/admin-page-body.yaml +49 -0
- package/shell/admin-page-header/admin-page-header.a2ui.json +62 -0
- package/shell/admin-page-header/admin-page-header.examples.html +34 -0
- package/shell/admin-page-header/admin-page-header.html +42 -0
- package/shell/admin-page-header/admin-page-header.yaml +47 -0
- package/shell/admin-scroll/admin-scroll.a2ui.json +62 -0
- package/shell/admin-scroll/admin-scroll.examples.html +31 -0
- package/shell/admin-scroll/admin-scroll.html +42 -0
- package/shell/admin-scroll/admin-scroll.yaml +51 -0
- package/shell/admin-shell/admin-shell.a2ui.json +0 -10
- package/shell/admin-shell/admin-shell.css +1 -0
- package/shell/admin-shell/admin-shell.examples.html +61 -5
- package/shell/admin-shell/admin-shell.js +165 -121
- package/shell/admin-shell/admin-shell.yaml +6 -6
- package/shell/admin-shell/css/admin-shell.bespoke.css +198 -0
- package/shell/admin-shell/css/admin-shell.tokens.css +10 -0
- package/shell/admin-sidebar/admin-sidebar.a2ui.json +138 -0
- package/shell/admin-sidebar/admin-sidebar.examples.html +76 -0
- package/shell/admin-sidebar/admin-sidebar.html +47 -0
- package/shell/admin-sidebar/admin-sidebar.js +227 -0
- package/shell/admin-sidebar/admin-sidebar.test.js +123 -0
- package/shell/admin-sidebar/admin-sidebar.yaml +140 -0
- package/shell/admin-statusbar/admin-statusbar.a2ui.json +81 -0
- package/shell/admin-statusbar/admin-statusbar.examples.html +29 -0
- package/shell/admin-statusbar/admin-statusbar.html +42 -0
- package/shell/admin-statusbar/admin-statusbar.yaml +68 -0
- package/shell/admin-topbar/admin-topbar.a2ui.json +83 -0
- package/shell/admin-topbar/admin-topbar.examples.html +31 -0
- package/shell/admin-topbar/admin-topbar.html +42 -0
- package/shell/admin-topbar/admin-topbar.yaml +75 -0
- package/shell/index.js +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,66 @@ Built from `@adia-ai/web-components` primitives.
|
|
|
11
11
|
|
|
12
12
|
_No pending changes._
|
|
13
13
|
|
|
14
|
+
## [0.3.5] - 2026-05-07
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- **Chat cluster bespoke family** — second cluster decomposed per ADR-0023, proving the family pattern is replicable.
|
|
19
|
+
- **`<chat-thread streaming?>`** — JS-bearing (106 LOC + 8 unit tests). Replaces legacy `<section data-chat-messages>`. Owns scroll-to-bottom on new message (with user-scroll-up suspension), `[streaming]` and `[empty]` reflected attributes. Public API: `.scrollToBottom()` / `.scrollToBottomInstant()`.
|
|
20
|
+
- **`<chat-composer disabled?>`** — JS-bearing (100 LOC + 9 unit tests). Wraps `<chat-input-ui>` (or any input-like child). Forwards inner submit as `composer-submit` event (bespoke name to prevent double-fire). Propagates `[disabled]` to inner input.
|
|
21
|
+
- **`<chat-sidebar slot="leading|trailing" resizable collapsible>`** — JS-bearing (227 LOC + 10 unit tests). Mirrors `<admin-sidebar>`. Cluster-namespaced localStorage prefix (`adia-chat-sidebar-{name}`).
|
|
22
|
+
- **3 CSS-only structural children** — `<chat-header>`, `<chat-status>`, `<chat-empty>` (visibility driven by parent `<chat-thread>[empty]`).
|
|
23
|
+
|
|
24
|
+
- **CSS bridge** `chat/chat-shell/css/chat-shell.bespoke.css` (196 LOC) — maps the 6 bespoke chat tags to existing CSS without touching legacy layer files. Imported last in `chat-shell.css`.
|
|
25
|
+
|
|
26
|
+
- **`chat/index.js` exports** all 4 JS-bearing chat children (ChatShell + ChatThread + ChatComposer + ChatSidebar). Subpath import unchanged: `import '@adia-ai/web-modules/chat'`.
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- **`<chat-shell>` host** — now coordinates bespoke children rather than centralizing their behavior. `connected()` reads BOTH legacy + bespoke shapes via priority chain. Listens for `composer-submit` on bespoke composer, `submit` on legacy input (mutually exclusive). `startStreaming()` / `stopStreaming()` propagate `[streaming]` to `<chat-thread>`.
|
|
31
|
+
|
|
32
|
+
- **`chat-shell.examples.html` reorganized** — split "Basic shape" into "Basic shape (legacy)" + new "Basic shape (bespoke — recommended)" + "State as attribute" section with CSS `:has()` examples.
|
|
33
|
+
|
|
34
|
+
### Backwards compatibility
|
|
35
|
+
|
|
36
|
+
Legacy authoring shapes (`<section data-chat-messages>`, `<chat-input-ui data-chat-input>`, `[data-chat-empty]`, `[data-chat-status]`) all still work identically. Mixed markup renders the same. Migration is opt-in.
|
|
37
|
+
|
|
38
|
+
## [0.3.4] - 2026-05-07
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- **`<admin-sidebar slot="leading|trailing" resizable collapsible>`** — first JS-bearing bespoke shell-tier child per ADR-0023. 227 LOC + 10 unit tests. Owns: resize drag with snap thresholds (96 floor / 160 min-usable), `[collapsed]` reflected attribute, localStorage persistence (`adia-sidebar-{name}`), ResizeObserver for narrow-mode children. Public API: `.toggle()` / `.collapse()` / `.expand()`. Events: `sidebar-toggle`, `sidebar-resize` (both bubble).
|
|
43
|
+
|
|
44
|
+
- **`<admin-command shortcut="cmd+k|ctrl+k|both">`** — second JS-bearing bespoke child. 156 LOC + 9 unit tests. Owns: keyboard shortcut listener (default both Cmd+K + Ctrl+K), dialog focus management, dismiss handlers, dialog wrapping. Reflects `[open]`. Public API: `.show()` / `.hide()` / `.toggle()`. Forwards `command-select` events.
|
|
45
|
+
|
|
46
|
+
- **7 CSS-only structural children** for the admin shell cluster: `<admin-content>`, `<admin-topbar>`, `<admin-statusbar>`, `<admin-scroll>`, `<admin-page>`, `<admin-page-header>`, `<admin-page-body>`. Pure CSS-only stubs (no `.js`, no own `.css`) — same pattern as `web-components/components/aside/`. Styled by parent `<admin-shell>` via tag-presence selectors.
|
|
47
|
+
|
|
48
|
+
- **CSS bridge** `shell/admin-shell/css/admin-shell.bespoke.css` — maps the 7 bespoke tags to existing CSS layer rules without touching legacy CSS files.
|
|
49
|
+
|
|
50
|
+
- **Sibling shell demo pages** (closes A1 from admin-shell deep review):
|
|
51
|
+
- `chat/chat-shell/chat-shell.html` + `.examples.html`
|
|
52
|
+
- `editor/editor-shell/editor-shell.html` + `.examples.html`
|
|
53
|
+
|
|
54
|
+
- **`shell/index.js` exports** all 3 JS-bearing children (AdminShell + AdminSidebar + AdminCommand). Subpath import unchanged: `import '@adia-ai/web-modules/shell'`.
|
|
55
|
+
|
|
56
|
+
### Changed
|
|
57
|
+
|
|
58
|
+
- **`<admin-shell>` host** — now coordinates bespoke children rather than centralizing their behavior. Source LOC actually grew (261 → 305) because the legacy compatibility layer is preserved inline (per ADR-0023 Phase 1 backwards-compat); once legacy shapes deprecate in Phase 3, the host drops to ~80 LOC.
|
|
59
|
+
|
|
60
|
+
- **`admin-shell.examples.html` reorganized** — split "Basic shape" into "Basic shape (legacy)" + "Basic shape (bespoke — recommended)" with full 9-element bespoke composition + new "State as attribute" section with CSS `:has()` examples + ADR-0023 cross-reference.
|
|
61
|
+
|
|
62
|
+
- **`--nav-*` tokens in `admin-shell.tokens.css` documented as shell-context overrides** (closes G4 from deep review). They're not canonical; canonical lives in `nav.css`, `nav-group.css`, `nav-item.css` `:where()` roots.
|
|
63
|
+
|
|
64
|
+
### Fixed
|
|
65
|
+
|
|
66
|
+
- **G1**: `data-sidebar-{name}-collapsed` states removed from `admin-shell.yaml` — they were declared but never set in JS. New semantic: `[collapsed]` reflected on `<admin-sidebar>` directly.
|
|
67
|
+
|
|
68
|
+
- **G2 + G3**: Phantom attrs (`leading-min/max-width`, `[cmd-k]`) removed from `admin-shell.examples.html` behavior-wiring table.
|
|
69
|
+
|
|
70
|
+
### Backwards compatibility
|
|
71
|
+
|
|
72
|
+
Legacy authoring shapes (`<aside data-sidebar="leading">`, `<dialog data-command>`, `<div data-resize>`, `[data-sidebar-toggle]`, `<aside-ui slot="leading">`) all still work identically. Mixed markup (some bespoke, some legacy) renders the same. Migration is opt-in.
|
|
73
|
+
|
|
14
74
|
## [0.3.3] - 2026-05-07
|
|
15
75
|
|
|
16
76
|
**Lockstep cut.** All 9 published `@adia-ai/*` packages now share version `0.3.3`, governed by [`docs/specs/package-architecture.md` § 15](../../../docs/specs/package-architecture.md#15-versioning-policy).
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://adiaui.dev/a2ui/v0_9/components/ChatComposer.json",
|
|
4
|
+
"title": "ChatComposer",
|
|
5
|
+
"description": "Module-tier chat composer wrapper — replaces legacy <chat-input-ui\ndata-chat-input> direct child of <chat-shell> per ADR-0023.\nForwards submit events as 'composer-submit', propagates [disabled]\nto the inner input, provides slot vocabulary for future composer\nsurfaces (file attach, autocomplete, model picker).\n\nSits inside <chat-shell> as the input region (typically inside or\nalongside the footer). Authors place an inner <chat-input-ui>\n(or input-ui / textarea-ui) as the primary input.\n\nBackwards compat — <chat-shell> still recognizes the legacy\n<chat-input-ui data-chat-input> shape via :is() selector. New\ncode should prefer <chat-composer>.\n",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"allOf": [
|
|
8
|
+
{
|
|
9
|
+
"$ref": "common_types.json#/$defs/ComponentCommon"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"$ref": "common_types.json#/$defs/CatalogComponentCommon"
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"properties": {
|
|
16
|
+
"component": {
|
|
17
|
+
"const": "ChatComposer"
|
|
18
|
+
},
|
|
19
|
+
"disabled": {
|
|
20
|
+
"description": "Reflected — set by the host while streaming, or by application\ncode. Propagated to the inner input element automatically.\n",
|
|
21
|
+
"type": "boolean",
|
|
22
|
+
"default": false
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"required": [
|
|
26
|
+
"component"
|
|
27
|
+
],
|
|
28
|
+
"unevaluatedProperties": false,
|
|
29
|
+
"x-adiaui": {
|
|
30
|
+
"anti_patterns": [],
|
|
31
|
+
"category": "input",
|
|
32
|
+
"events": {
|
|
33
|
+
"composer-submit": {
|
|
34
|
+
"description": "Bubbles when the inner input fires its 'submit' event. Detail mirrors the inner event's detail (typically text + model).",
|
|
35
|
+
"detail": {
|
|
36
|
+
"model": "string",
|
|
37
|
+
"text": "string"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"examples": [],
|
|
42
|
+
"keywords": [
|
|
43
|
+
"chat-composer",
|
|
44
|
+
"chat-input",
|
|
45
|
+
"composer",
|
|
46
|
+
"message-input",
|
|
47
|
+
"conversation-input"
|
|
48
|
+
],
|
|
49
|
+
"name": "ChatComposer",
|
|
50
|
+
"related": [
|
|
51
|
+
"ChatShell",
|
|
52
|
+
"ChatThread",
|
|
53
|
+
"ChatInput",
|
|
54
|
+
"Input",
|
|
55
|
+
"Textarea"
|
|
56
|
+
],
|
|
57
|
+
"slots": {
|
|
58
|
+
"default": {
|
|
59
|
+
"description": "Default — primary input child (typically <chat-input-ui> or <input-ui submit-on-enter>)."
|
|
60
|
+
},
|
|
61
|
+
"attach": {
|
|
62
|
+
"description": "Future — attachment trigger button (paperclip icon to upload)."
|
|
63
|
+
},
|
|
64
|
+
"leading": {
|
|
65
|
+
"description": "Leading controls — autocomplete trigger, voice input, etc."
|
|
66
|
+
},
|
|
67
|
+
"trailing": {
|
|
68
|
+
"description": "Trailing controls — model picker, send button override, etc."
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
"states": [
|
|
72
|
+
{
|
|
73
|
+
"description": "Default, accepting input.",
|
|
74
|
+
"name": "idle"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"description": "Input is disabled (typically during LLM streaming).",
|
|
78
|
+
"attribute": "disabled",
|
|
79
|
+
"name": "disabled"
|
|
80
|
+
}
|
|
81
|
+
],
|
|
82
|
+
"synonyms": {
|
|
83
|
+
"composer": [
|
|
84
|
+
"message-input",
|
|
85
|
+
"chat-input",
|
|
86
|
+
"conversation-input"
|
|
87
|
+
]
|
|
88
|
+
},
|
|
89
|
+
"tag": "chat-composer",
|
|
90
|
+
"tokens": {},
|
|
91
|
+
"traits": [],
|
|
92
|
+
"version": 1
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<header>
|
|
2
|
+
<div>
|
|
3
|
+
<h1>Chat Composer</h1>
|
|
4
|
+
<div data-actions>
|
|
5
|
+
<tag-ui size="sm">chat-composer</tag-ui>
|
|
6
|
+
<tag-ui size="sm" variant="ghost">JS-bearing</tag-ui>
|
|
7
|
+
</div>
|
|
8
|
+
</div>
|
|
9
|
+
<p>Module-tier chat composer wrapper — forwards submit events, propagates [disabled], slot vocabulary for future composer surfaces.</p>
|
|
10
|
+
</header>
|
|
11
|
+
|
|
12
|
+
<section data-section>
|
|
13
|
+
<h2 variant="section">Role</h2>
|
|
14
|
+
<p>Forwards inner submit events as <code>composer-submit</code>, propagates <code>[disabled]</code> to the inner input, slot vocabulary for future composer surfaces (file attach, autocomplete trigger, model picker). JS-bearing.</p>
|
|
15
|
+
</section>
|
|
16
|
+
|
|
17
|
+
<section data-section>
|
|
18
|
+
<h2 variant="section">Composition</h2>
|
|
19
|
+
<p>Typical placement inside <code><chat-shell></code>:</p>
|
|
20
|
+
<code-ui language="html"><chat-composer>
|
|
21
|
+
<chat-input-ui placeholder="Message…" submit-on-enter></chat-input-ui>
|
|
22
|
+
</chat-composer></code-ui>
|
|
23
|
+
</section>
|
|
24
|
+
|
|
25
|
+
<section data-section>
|
|
26
|
+
<h2 variant="section">Family</h2>
|
|
27
|
+
<p>Per <a href="../../../../.brain/adrs/0023-bespoke-shell-tier-children.md">ADR-0023</a>, the chat cluster's bespoke family — <code><chat-shell></code> (host), <code><chat-thread></code>, <code><chat-composer></code>, <code><chat-sidebar></code> (JS-bearing) + <code><chat-header></code>, <code><chat-status></code>, <code><chat-empty></code> (CSS-only). Mirrors the admin cluster's pattern.</p>
|
|
28
|
+
</section>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en" data-theme="auto">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>Chat Composer — AdiaUI</title>
|
|
7
|
+
|
|
8
|
+
<link rel="stylesheet" href="../../../web-components/styles/resets.css">
|
|
9
|
+
<link rel="stylesheet" href="../../../web-components/styles/tokens.css">
|
|
10
|
+
<link rel="stylesheet" href="../chat-shell/chat-shell.css">
|
|
11
|
+
<link rel="stylesheet" href="../../../web-components/components/code/code.css">
|
|
12
|
+
<link rel="stylesheet" href="../../../web-components/components/tag/tag.css">
|
|
13
|
+
|
|
14
|
+
<script type="module" src="../chat-shell/chat-shell.js"></script>
|
|
15
|
+
<script type="module" src="./chat-composer.js"></script>
|
|
16
|
+
<script type="module" src="../../../web-components/components/code/code.js"></script>
|
|
17
|
+
<script type="module" src="../../../web-components/components/tag/tag.js"></script>
|
|
18
|
+
|
|
19
|
+
<style>
|
|
20
|
+
:where(html, body) { margin: 0; min-height: 100vh; background: var(--a-bg); color: var(--a-fg); font-family: var(--a-font); }
|
|
21
|
+
main { max-width: 960px; margin-inline: auto; padding: var(--a-space-6) var(--a-space-5); }
|
|
22
|
+
</style>
|
|
23
|
+
</head>
|
|
24
|
+
<body>
|
|
25
|
+
|
|
26
|
+
<main id="demo-root">
|
|
27
|
+
<p>Loading examples…</p>
|
|
28
|
+
</main>
|
|
29
|
+
|
|
30
|
+
<script type="module">
|
|
31
|
+
const root = document.getElementById('demo-root');
|
|
32
|
+
try {
|
|
33
|
+
const res = await fetch('./chat-composer.examples.html');
|
|
34
|
+
if (!res.ok) throw new Error(`fetch failed (${res.status})`);
|
|
35
|
+
root.innerHTML = await res.text();
|
|
36
|
+
} catch (err) {
|
|
37
|
+
root.innerHTML = `<p style="color:var(--a-danger-strong);">Failed to load chat-composer.examples.html — ${err.message}</p>`;
|
|
38
|
+
console.error('[chat-composer.html]', err);
|
|
39
|
+
}
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
</body>
|
|
43
|
+
</html>
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <chat-composer disabled?>
|
|
3
|
+
* <chat-input-ui placeholder="..." submit-on-enter></chat-input-ui>
|
|
4
|
+
* <!-- future: <button-ui slot="attach" icon="paperclip"> -->
|
|
5
|
+
* </chat-composer>
|
|
6
|
+
*
|
|
7
|
+
* Module-tier chat composer wrapper — replaces legacy
|
|
8
|
+
* <chat-input-ui data-chat-input> direct child of <chat-shell>
|
|
9
|
+
* per ADR-0023. Owns:
|
|
10
|
+
*
|
|
11
|
+
* - Forward submit events from inner <chat-input-ui> as
|
|
12
|
+
* 'composer-submit' (so the host doesn't reach inside)
|
|
13
|
+
* - [disabled] reflected attribute, propagated to inner input
|
|
14
|
+
* - Slot vocabulary for future composer surfaces (file attach,
|
|
15
|
+
* autocomplete trigger, model picker, etc.)
|
|
16
|
+
* - Focus delegation — .focus() / .clear() proxy to inner input
|
|
17
|
+
*
|
|
18
|
+
* Reflected attributes:
|
|
19
|
+
* [disabled] — set by host while streaming; propagated to
|
|
20
|
+
* inner <chat-input-ui>
|
|
21
|
+
*
|
|
22
|
+
* Public methods:
|
|
23
|
+
* .focus() — focus inner input
|
|
24
|
+
* .clear() — clear inner input value
|
|
25
|
+
* .input() — getter: returns inner <chat-input-ui> reference
|
|
26
|
+
*
|
|
27
|
+
* Events forwarded from inner input:
|
|
28
|
+
* composer-submit — bubbles. detail: { text, model }
|
|
29
|
+
*
|
|
30
|
+
* The host (<chat-shell>) reads either <chat-composer> or
|
|
31
|
+
* [data-chat-input] / <chat-input-ui> via :is() selector for
|
|
32
|
+
* backwards compat.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import { UIElement } from '../../../web-components/core/element.js';
|
|
36
|
+
|
|
37
|
+
class ChatComposer extends UIElement {
|
|
38
|
+
static properties = {
|
|
39
|
+
disabled: { type: Boolean, default: false, reflect: true },
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
static template = () => null;
|
|
43
|
+
|
|
44
|
+
#inputEl = null;
|
|
45
|
+
#onSubmit = null;
|
|
46
|
+
|
|
47
|
+
connected() {
|
|
48
|
+
this.#inputEl = this.querySelector('chat-input-ui') || this.querySelector('input-ui') || this.querySelector('textarea-ui');
|
|
49
|
+
|
|
50
|
+
this.#onSubmit = (e) => {
|
|
51
|
+
// Re-emit as 'composer-submit' so host listens to one event
|
|
52
|
+
// regardless of which inner input it is
|
|
53
|
+
this.dispatchEvent(new CustomEvent('composer-submit', {
|
|
54
|
+
bubbles: true,
|
|
55
|
+
detail: e.detail || {},
|
|
56
|
+
}));
|
|
57
|
+
};
|
|
58
|
+
this.#inputEl?.addEventListener('submit', this.#onSubmit);
|
|
59
|
+
|
|
60
|
+
// Sync initial disabled state to inner input
|
|
61
|
+
this.#syncDisabled();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
disconnected() {
|
|
65
|
+
if (this.#inputEl && this.#onSubmit) {
|
|
66
|
+
this.#inputEl.removeEventListener('submit', this.#onSubmit);
|
|
67
|
+
}
|
|
68
|
+
this.#inputEl = null;
|
|
69
|
+
this.#onSubmit = null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Reflected attribute change — propagate to inner input
|
|
73
|
+
attributeChangedCallback(name, oldVal, newVal) {
|
|
74
|
+
super.attributeChangedCallback?.(name, oldVal, newVal);
|
|
75
|
+
if (name === 'disabled') {
|
|
76
|
+
this.#syncDisabled();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ── Public API ──
|
|
81
|
+
|
|
82
|
+
focus() {
|
|
83
|
+
this.#inputEl?.focus?.();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
clear() {
|
|
87
|
+
this.#inputEl?.clear?.();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
get input() {
|
|
91
|
+
return this.#inputEl;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ── Internal ──
|
|
95
|
+
|
|
96
|
+
#syncDisabled() {
|
|
97
|
+
if (!this.#inputEl) return;
|
|
98
|
+
if (this.disabled) {
|
|
99
|
+
this.#inputEl.setAttribute('disabled', '');
|
|
100
|
+
} else {
|
|
101
|
+
this.#inputEl.removeAttribute('disabled');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
customElements.define('chat-composer', ChatComposer);
|
|
107
|
+
export { ChatComposer };
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
+
import '../../../web-components/core/element.js';
|
|
3
|
+
import './chat-composer.js';
|
|
4
|
+
|
|
5
|
+
const tick = () => new Promise((r) => queueMicrotask(r));
|
|
6
|
+
|
|
7
|
+
function mount(html) {
|
|
8
|
+
const wrap = document.createElement('div');
|
|
9
|
+
wrap.innerHTML = html;
|
|
10
|
+
document.body.appendChild(wrap);
|
|
11
|
+
return wrap.firstElementChild;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
document.body.innerHTML = '';
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe('chat-composer', () => {
|
|
19
|
+
it('registers chat-composer as a custom element', () => {
|
|
20
|
+
expect(customElements.get('chat-composer')).toBeDefined();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('defaults to disabled=false', () => {
|
|
24
|
+
const c = mount('<chat-composer></chat-composer>');
|
|
25
|
+
expect(c.disabled).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('reflects [disabled] via property assignment', async () => {
|
|
29
|
+
const c = mount('<chat-composer></chat-composer>');
|
|
30
|
+
c.disabled = true;
|
|
31
|
+
await tick();
|
|
32
|
+
expect(c.hasAttribute('disabled')).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('exposes .focus() / .clear() / .input getter', () => {
|
|
36
|
+
const c = mount('<chat-composer></chat-composer>');
|
|
37
|
+
expect(typeof c.focus).toBe('function');
|
|
38
|
+
expect(typeof c.clear).toBe('function');
|
|
39
|
+
expect('input' in c).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('returns null .input when no inner input element', () => {
|
|
43
|
+
const c = mount('<chat-composer></chat-composer>');
|
|
44
|
+
expect(c.input).toBeNull();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('forwards inner submit as composer-submit event', async () => {
|
|
48
|
+
// Plain input-like element with submit dispatch
|
|
49
|
+
const wrap = document.createElement('div');
|
|
50
|
+
wrap.innerHTML = `<chat-composer>
|
|
51
|
+
<chat-input-ui></chat-input-ui>
|
|
52
|
+
</chat-composer>`;
|
|
53
|
+
document.body.appendChild(wrap);
|
|
54
|
+
const composer = wrap.firstElementChild;
|
|
55
|
+
const innerInput = composer.querySelector('chat-input-ui');
|
|
56
|
+
|
|
57
|
+
const onComposerSubmit = vi.fn();
|
|
58
|
+
composer.addEventListener('composer-submit', onComposerSubmit);
|
|
59
|
+
|
|
60
|
+
innerInput.dispatchEvent(new CustomEvent('submit', { detail: { text: 'hello' } }));
|
|
61
|
+
await tick();
|
|
62
|
+
|
|
63
|
+
expect(onComposerSubmit).toHaveBeenCalledTimes(1);
|
|
64
|
+
expect(onComposerSubmit.mock.calls[0][0].detail).toEqual({ text: 'hello' });
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('propagates [disabled] to inner input via attribute', async () => {
|
|
68
|
+
const wrap = document.createElement('div');
|
|
69
|
+
wrap.innerHTML = `<chat-composer>
|
|
70
|
+
<chat-input-ui></chat-input-ui>
|
|
71
|
+
</chat-composer>`;
|
|
72
|
+
document.body.appendChild(wrap);
|
|
73
|
+
const composer = wrap.firstElementChild;
|
|
74
|
+
const innerInput = composer.querySelector('chat-input-ui');
|
|
75
|
+
|
|
76
|
+
composer.disabled = true;
|
|
77
|
+
await tick();
|
|
78
|
+
|
|
79
|
+
expect(innerInput.hasAttribute('disabled')).toBe(true);
|
|
80
|
+
|
|
81
|
+
composer.disabled = false;
|
|
82
|
+
await tick();
|
|
83
|
+
|
|
84
|
+
expect(innerInput.hasAttribute('disabled')).toBe(false);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('cleanup on disconnect — removes inner submit listener', () => {
|
|
88
|
+
const wrap = document.createElement('div');
|
|
89
|
+
wrap.innerHTML = `<chat-composer>
|
|
90
|
+
<chat-input-ui></chat-input-ui>
|
|
91
|
+
</chat-composer>`;
|
|
92
|
+
document.body.appendChild(wrap);
|
|
93
|
+
const composer = wrap.firstElementChild;
|
|
94
|
+
const innerInput = composer.querySelector('chat-input-ui');
|
|
95
|
+
const removeSpy = vi.spyOn(innerInput, 'removeEventListener');
|
|
96
|
+
|
|
97
|
+
composer.remove();
|
|
98
|
+
|
|
99
|
+
const removedTypes = removeSpy.mock.calls.map((args) => args[0]);
|
|
100
|
+
expect(removedTypes).toContain('submit');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('handles input-ui as inner input fallback', () => {
|
|
104
|
+
const wrap = document.createElement('div');
|
|
105
|
+
wrap.innerHTML = `<chat-composer>
|
|
106
|
+
<input-ui></input-ui>
|
|
107
|
+
</chat-composer>`;
|
|
108
|
+
document.body.appendChild(wrap);
|
|
109
|
+
const composer = wrap.firstElementChild;
|
|
110
|
+
expect(composer.input?.tagName?.toLowerCase()).toBe('input-ui');
|
|
111
|
+
});
|
|
112
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Edit this file; run `npm run build:components` to regenerate a2ui.json.
|
|
2
|
+
$schema: ../../../../scripts/schemas/component.yaml.schema.json
|
|
3
|
+
name: ChatComposer
|
|
4
|
+
tag: chat-composer
|
|
5
|
+
component: ChatComposer
|
|
6
|
+
category: input
|
|
7
|
+
version: 1
|
|
8
|
+
description: |
|
|
9
|
+
Module-tier chat composer wrapper — replaces legacy <chat-input-ui
|
|
10
|
+
data-chat-input> direct child of <chat-shell> per ADR-0023.
|
|
11
|
+
Forwards submit events as 'composer-submit', propagates [disabled]
|
|
12
|
+
to the inner input, provides slot vocabulary for future composer
|
|
13
|
+
surfaces (file attach, autocomplete, model picker).
|
|
14
|
+
|
|
15
|
+
Sits inside <chat-shell> as the input region (typically inside or
|
|
16
|
+
alongside the footer). Authors place an inner <chat-input-ui>
|
|
17
|
+
(or input-ui / textarea-ui) as the primary input.
|
|
18
|
+
|
|
19
|
+
Backwards compat — <chat-shell> still recognizes the legacy
|
|
20
|
+
<chat-input-ui data-chat-input> shape via :is() selector. New
|
|
21
|
+
code should prefer <chat-composer>.
|
|
22
|
+
|
|
23
|
+
props:
|
|
24
|
+
disabled:
|
|
25
|
+
description: |
|
|
26
|
+
Reflected — set by the host while streaming, or by application
|
|
27
|
+
code. Propagated to the inner input element automatically.
|
|
28
|
+
type: boolean
|
|
29
|
+
default: false
|
|
30
|
+
reflect: true
|
|
31
|
+
|
|
32
|
+
events:
|
|
33
|
+
composer-submit:
|
|
34
|
+
description: >-
|
|
35
|
+
Bubbles when the inner input fires its 'submit' event.
|
|
36
|
+
Detail mirrors the inner event's detail (typically text + model).
|
|
37
|
+
detail:
|
|
38
|
+
text: string
|
|
39
|
+
model: string
|
|
40
|
+
|
|
41
|
+
slots:
|
|
42
|
+
default:
|
|
43
|
+
description: >-
|
|
44
|
+
Default — primary input child (typically <chat-input-ui> or
|
|
45
|
+
<input-ui submit-on-enter>).
|
|
46
|
+
attach:
|
|
47
|
+
description: >-
|
|
48
|
+
Future — attachment trigger button (paperclip icon to upload).
|
|
49
|
+
trailing:
|
|
50
|
+
description: >-
|
|
51
|
+
Trailing controls — model picker, send button override, etc.
|
|
52
|
+
leading:
|
|
53
|
+
description: >-
|
|
54
|
+
Leading controls — autocomplete trigger, voice input, etc.
|
|
55
|
+
|
|
56
|
+
states:
|
|
57
|
+
- name: idle
|
|
58
|
+
description: Default, accepting input.
|
|
59
|
+
- name: disabled
|
|
60
|
+
attribute: disabled
|
|
61
|
+
description: Input is disabled (typically during LLM streaming).
|
|
62
|
+
|
|
63
|
+
traits: []
|
|
64
|
+
|
|
65
|
+
a2ui:
|
|
66
|
+
rules:
|
|
67
|
+
- >-
|
|
68
|
+
chat-composer is the bespoke replacement for legacy
|
|
69
|
+
<chat-input-ui data-chat-input> inside <chat-shell>. Place an
|
|
70
|
+
inner <chat-input-ui submit-on-enter> as the primary input.
|
|
71
|
+
- >-
|
|
72
|
+
The host listens for 'composer-submit' on the composer (not on
|
|
73
|
+
the inner input). The event detail mirrors the inner submit
|
|
74
|
+
event so existing handlers Just Work.
|
|
75
|
+
|
|
76
|
+
keywords:
|
|
77
|
+
- chat-composer
|
|
78
|
+
- chat-input
|
|
79
|
+
- composer
|
|
80
|
+
- message-input
|
|
81
|
+
- conversation-input
|
|
82
|
+
|
|
83
|
+
synonyms:
|
|
84
|
+
composer: [message-input, chat-input, conversation-input]
|
|
85
|
+
|
|
86
|
+
related:
|
|
87
|
+
- ChatShell
|
|
88
|
+
- ChatThread
|
|
89
|
+
- ChatInput
|
|
90
|
+
- Input
|
|
91
|
+
- Textarea
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://adiaui.dev/a2ui/v0_9/components/ChatEmpty.json",
|
|
4
|
+
"title": "ChatEmpty",
|
|
5
|
+
"description": "Module-tier chat empty state. CSS-only — no behavior, no JS. Sits\nas the first child of <chat-thread> as the empty state placeholder.\nVisibility is driven by <chat-thread>'s [empty] reflected attribute\nvia CSS (no JS toggling needed) — when messages arrive, the parent\nthread loses [empty] and CSS hides this stub.\n\nReplaces the legacy <empty-state-ui data-chat-empty> child of the\nmessages container per ADR-0023. Both shapes still work via\nbackwards compat; new code should prefer <chat-empty>.\n",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"allOf": [
|
|
8
|
+
{
|
|
9
|
+
"$ref": "common_types.json#/$defs/ComponentCommon"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"$ref": "common_types.json#/$defs/CatalogComponentCommon"
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"properties": {
|
|
16
|
+
"component": {
|
|
17
|
+
"const": "ChatEmpty"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"required": [
|
|
21
|
+
"component"
|
|
22
|
+
],
|
|
23
|
+
"unevaluatedProperties": false,
|
|
24
|
+
"x-adiaui": {
|
|
25
|
+
"anti_patterns": [],
|
|
26
|
+
"category": "feedback",
|
|
27
|
+
"events": {},
|
|
28
|
+
"examples": [],
|
|
29
|
+
"keywords": [
|
|
30
|
+
"chat-empty",
|
|
31
|
+
"empty-state",
|
|
32
|
+
"placeholder",
|
|
33
|
+
"no-messages"
|
|
34
|
+
],
|
|
35
|
+
"name": "ChatEmpty",
|
|
36
|
+
"related": [
|
|
37
|
+
"ChatShell",
|
|
38
|
+
"ChatThread",
|
|
39
|
+
"EmptyState"
|
|
40
|
+
],
|
|
41
|
+
"slots": {
|
|
42
|
+
"default": {
|
|
43
|
+
"description": "Empty state content — typically an icon + heading + description. Authors compose with <empty-state-ui> as a child, or supply inline content directly."
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"states": [
|
|
47
|
+
{
|
|
48
|
+
"description": "Default, visible.",
|
|
49
|
+
"name": "idle"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"description": "Hidden via parent <chat-thread>:not([empty]) selector.",
|
|
53
|
+
"name": "hidden"
|
|
54
|
+
}
|
|
55
|
+
],
|
|
56
|
+
"synonyms": {
|
|
57
|
+
"chat-empty": [
|
|
58
|
+
"empty-state",
|
|
59
|
+
"placeholder",
|
|
60
|
+
"no-conversations"
|
|
61
|
+
]
|
|
62
|
+
},
|
|
63
|
+
"tag": "chat-empty",
|
|
64
|
+
"tokens": {},
|
|
65
|
+
"traits": [],
|
|
66
|
+
"version": 1
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<header>
|
|
2
|
+
<div>
|
|
3
|
+
<h1>Chat Empty</h1>
|
|
4
|
+
<div data-actions>
|
|
5
|
+
<tag-ui size="sm">chat-empty</tag-ui>
|
|
6
|
+
<tag-ui size="sm" variant="ghost">CSS-only</tag-ui>
|
|
7
|
+
</div>
|
|
8
|
+
</div>
|
|
9
|
+
<p>Module-tier chat empty state. CSS-only. Visibility driven by parent <chat-thread>'s [empty] reflected attribute.</p>
|
|
10
|
+
</header>
|
|
11
|
+
|
|
12
|
+
<section data-section>
|
|
13
|
+
<h2 variant="section">Role</h2>
|
|
14
|
+
<p>Empty state placeholder. CSS-only. Visibility driven automatically by parent <code><chat-thread></code>'s <code>[empty]</code> reflected attribute — no JS toggling needed.</p>
|
|
15
|
+
</section>
|
|
16
|
+
|
|
17
|
+
<section data-section>
|
|
18
|
+
<h2 variant="section">Composition</h2>
|
|
19
|
+
<p>Typical placement inside <code><chat-shell></code>:</p>
|
|
20
|
+
<code-ui language="html"><chat-thread>
|
|
21
|
+
<chat-empty>
|
|
22
|
+
<empty-state-ui
|
|
23
|
+
icon="chat-circle"
|
|
24
|
+
heading="Hello!"
|
|
25
|
+
description="Ask me anything."></empty-state-ui>
|
|
26
|
+
</chat-empty>
|
|
27
|
+
<!-- Messages get appended here; chat-empty auto-hides -->
|
|
28
|
+
</chat-thread></code-ui>
|
|
29
|
+
</section>
|
|
30
|
+
|
|
31
|
+
<section data-section>
|
|
32
|
+
<h2 variant="section">Family</h2>
|
|
33
|
+
<p>Per <a href="../../../../.brain/adrs/0023-bespoke-shell-tier-children.md">ADR-0023</a>, the chat cluster's bespoke family — <code><chat-shell></code> (host), <code><chat-thread></code>, <code><chat-composer></code>, <code><chat-sidebar></code> (JS-bearing) + <code><chat-header></code>, <code><chat-status></code>, <code><chat-empty></code> (CSS-only). Mirrors the admin cluster's pattern.</p>
|
|
34
|
+
</section>
|