@adia-ai/web-modules 0.3.4 → 0.3.6
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 +50 -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 +43 -2
- 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-canvas/editor-canvas.a2ui.json +87 -0
- package/editor/editor-canvas/editor-canvas.js +103 -0
- package/editor/editor-canvas/editor-canvas.test.js +100 -0
- package/editor/editor-canvas/editor-canvas.yaml +88 -0
- package/editor/editor-canvas-empty/editor-canvas-empty.a2ui.json +69 -0
- package/editor/editor-canvas-empty/editor-canvas-empty.yaml +56 -0
- package/editor/editor-shell/css/editor-shell.bespoke.css +172 -0
- package/editor/editor-shell/editor-shell.css +1 -0
- package/editor/editor-shell/editor-shell.js +85 -30
- package/editor/editor-sidebar/editor-sidebar.a2ui.json +88 -0
- package/editor/editor-sidebar/editor-sidebar.js +173 -0
- package/editor/editor-sidebar/editor-sidebar.test.js +126 -0
- package/editor/editor-sidebar/editor-sidebar.yaml +83 -0
- package/editor/editor-statusbar/editor-statusbar.a2ui.json +76 -0
- package/editor/editor-statusbar/editor-statusbar.yaml +57 -0
- package/editor/editor-toolbar/editor-toolbar.a2ui.json +96 -0
- package/editor/editor-toolbar/editor-toolbar.js +58 -0
- package/editor/editor-toolbar/editor-toolbar.test.js +99 -0
- package/editor/editor-toolbar/editor-toolbar.yaml +81 -0
- package/editor/index.js +3 -0
- package/package.json +1 -1
|
@@ -0,0 +1,42 @@
|
|
|
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 Empty — 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="../../../web-components/components/code/code.js"></script>
|
|
16
|
+
<script type="module" src="../../../web-components/components/tag/tag.js"></script>
|
|
17
|
+
|
|
18
|
+
<style>
|
|
19
|
+
:where(html, body) { margin: 0; min-height: 100vh; background: var(--a-bg); color: var(--a-fg); font-family: var(--a-font); }
|
|
20
|
+
main { max-width: 960px; margin-inline: auto; padding: var(--a-space-6) var(--a-space-5); }
|
|
21
|
+
</style>
|
|
22
|
+
</head>
|
|
23
|
+
<body>
|
|
24
|
+
|
|
25
|
+
<main id="demo-root">
|
|
26
|
+
<p>Loading examples…</p>
|
|
27
|
+
</main>
|
|
28
|
+
|
|
29
|
+
<script type="module">
|
|
30
|
+
const root = document.getElementById('demo-root');
|
|
31
|
+
try {
|
|
32
|
+
const res = await fetch('./chat-empty.examples.html');
|
|
33
|
+
if (!res.ok) throw new Error(`fetch failed (${res.status})`);
|
|
34
|
+
root.innerHTML = await res.text();
|
|
35
|
+
} catch (err) {
|
|
36
|
+
root.innerHTML = `<p style="color:var(--a-danger-strong);">Failed to load chat-empty.examples.html — ${err.message}</p>`;
|
|
37
|
+
console.error('[chat-empty.html]', err);
|
|
38
|
+
}
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
</body>
|
|
42
|
+
</html>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Edit this file; run `npm run build:components` to regenerate a2ui.json.
|
|
2
|
+
$schema: ../../../../scripts/schemas/component.yaml.schema.json
|
|
3
|
+
name: ChatEmpty
|
|
4
|
+
tag: chat-empty
|
|
5
|
+
component: ChatEmpty
|
|
6
|
+
category: feedback
|
|
7
|
+
version: 1
|
|
8
|
+
description: |
|
|
9
|
+
Module-tier chat empty state. CSS-only — no behavior, no JS. Sits
|
|
10
|
+
as the first child of <chat-thread> as the empty state placeholder.
|
|
11
|
+
Visibility is driven by <chat-thread>'s [empty] reflected attribute
|
|
12
|
+
via CSS (no JS toggling needed) — when messages arrive, the parent
|
|
13
|
+
thread loses [empty] and CSS hides this stub.
|
|
14
|
+
|
|
15
|
+
Replaces the legacy <empty-state-ui data-chat-empty> child of the
|
|
16
|
+
messages container per ADR-0023. Both shapes still work via
|
|
17
|
+
backwards compat; new code should prefer <chat-empty>.
|
|
18
|
+
|
|
19
|
+
props: {}
|
|
20
|
+
|
|
21
|
+
events: {}
|
|
22
|
+
|
|
23
|
+
slots:
|
|
24
|
+
default:
|
|
25
|
+
description: >-
|
|
26
|
+
Empty state content — typically an icon + heading + description.
|
|
27
|
+
Authors compose with <empty-state-ui> as a child, or supply
|
|
28
|
+
inline content directly.
|
|
29
|
+
|
|
30
|
+
states:
|
|
31
|
+
- name: idle
|
|
32
|
+
description: Default, visible.
|
|
33
|
+
- name: hidden
|
|
34
|
+
description: Hidden via parent <chat-thread>:not([empty]) selector.
|
|
35
|
+
|
|
36
|
+
traits: []
|
|
37
|
+
|
|
38
|
+
a2ui:
|
|
39
|
+
rules:
|
|
40
|
+
- >-
|
|
41
|
+
chat-empty is the bespoke replacement for legacy
|
|
42
|
+
<empty-state-ui data-chat-empty>. Place as the first child of
|
|
43
|
+
<chat-thread>; visibility is automatic via the [empty]
|
|
44
|
+
reflected attribute.
|
|
45
|
+
|
|
46
|
+
keywords:
|
|
47
|
+
- chat-empty
|
|
48
|
+
- empty-state
|
|
49
|
+
- placeholder
|
|
50
|
+
- no-messages
|
|
51
|
+
|
|
52
|
+
synonyms:
|
|
53
|
+
chat-empty: [empty-state, placeholder, no-conversations]
|
|
54
|
+
|
|
55
|
+
related:
|
|
56
|
+
- ChatShell
|
|
57
|
+
- ChatThread
|
|
58
|
+
- EmptyState
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://adiaui.dev/a2ui/v0_9/components/ChatHeader.json",
|
|
4
|
+
"title": "ChatHeader",
|
|
5
|
+
"description": "Module-tier chat header chrome bar. CSS-only — no behavior, no JS.\nSits at the top of <chat-shell> or inside a <chat-sidebar>. Holds\nthe chat name, status indicator, and action clusters via slot\nvocabulary.\n\nReplaces the legacy <header> with [data-chat-name] / [data-chat-status]\ndata-attribute children. Both shapes still work per ADR-0023 backwards\ncompat; new code should prefer <chat-header> with <chat-status> as\na slotted child.\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": "ChatHeader"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"required": [
|
|
21
|
+
"component"
|
|
22
|
+
],
|
|
23
|
+
"unevaluatedProperties": false,
|
|
24
|
+
"x-adiaui": {
|
|
25
|
+
"anti_patterns": [],
|
|
26
|
+
"category": "layout",
|
|
27
|
+
"events": {},
|
|
28
|
+
"examples": [],
|
|
29
|
+
"keywords": [
|
|
30
|
+
"chat-header",
|
|
31
|
+
"chat-titlebar",
|
|
32
|
+
"chrome-bar",
|
|
33
|
+
"app-header"
|
|
34
|
+
],
|
|
35
|
+
"name": "ChatHeader",
|
|
36
|
+
"related": [
|
|
37
|
+
"ChatShell",
|
|
38
|
+
"ChatStatus",
|
|
39
|
+
"ChatThread",
|
|
40
|
+
"ChatComposer"
|
|
41
|
+
],
|
|
42
|
+
"slots": {
|
|
43
|
+
"default": {
|
|
44
|
+
"description": "Default — typically [slot=\"name\"] + [slot=\"status\"] children, or ad-hoc inline content."
|
|
45
|
+
},
|
|
46
|
+
"action": {
|
|
47
|
+
"description": "Trailing control cluster (settings, share, clear conversation)."
|
|
48
|
+
},
|
|
49
|
+
"action-leading": {
|
|
50
|
+
"description": "Leading control cluster (back button, conversation switcher)."
|
|
51
|
+
},
|
|
52
|
+
"name": {
|
|
53
|
+
"description": "Primary chat label (model name, conversation title, etc.)."
|
|
54
|
+
},
|
|
55
|
+
"status": {
|
|
56
|
+
"description": "Status indicator — typically <chat-status> showing connection / streaming state."
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"states": [
|
|
60
|
+
{
|
|
61
|
+
"description": "Default, the only state.",
|
|
62
|
+
"name": "idle"
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
"synonyms": {
|
|
66
|
+
"chat-header": [
|
|
67
|
+
"titlebar",
|
|
68
|
+
"app-header",
|
|
69
|
+
"navbar"
|
|
70
|
+
]
|
|
71
|
+
},
|
|
72
|
+
"tag": "chat-header",
|
|
73
|
+
"tokens": {},
|
|
74
|
+
"traits": [],
|
|
75
|
+
"version": 1
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<header>
|
|
2
|
+
<div>
|
|
3
|
+
<h1>Chat Header</h1>
|
|
4
|
+
<div data-actions>
|
|
5
|
+
<tag-ui size="sm">chat-header</tag-ui>
|
|
6
|
+
<tag-ui size="sm" variant="ghost">CSS-only</tag-ui>
|
|
7
|
+
</div>
|
|
8
|
+
</div>
|
|
9
|
+
<p>Module-tier chat top chrome bar. CSS-only. Holds chat name, status, and action clusters.</p>
|
|
10
|
+
</header>
|
|
11
|
+
|
|
12
|
+
<section data-section>
|
|
13
|
+
<h2 variant="section">Role</h2>
|
|
14
|
+
<p>Top chrome bar with slot vocabulary — name, status, action, action-leading clusters. Pure CSS-only stub styled by parent shell via tag-presence.</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-header>
|
|
21
|
+
<span slot="name">Claude</span>
|
|
22
|
+
<chat-status slot="status">Connected</chat-status>
|
|
23
|
+
<button-ui slot="action" icon="gear" variant="ghost"></button-ui>
|
|
24
|
+
</chat-header>
|
|
25
|
+
</section>
|
|
26
|
+
|
|
27
|
+
<section data-section>
|
|
28
|
+
<h2 variant="section">Family</h2>
|
|
29
|
+
<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>
|
|
30
|
+
</section>
|
|
@@ -0,0 +1,42 @@
|
|
|
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 Header — 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="../../../web-components/components/code/code.js"></script>
|
|
16
|
+
<script type="module" src="../../../web-components/components/tag/tag.js"></script>
|
|
17
|
+
|
|
18
|
+
<style>
|
|
19
|
+
:where(html, body) { margin: 0; min-height: 100vh; background: var(--a-bg); color: var(--a-fg); font-family: var(--a-font); }
|
|
20
|
+
main { max-width: 960px; margin-inline: auto; padding: var(--a-space-6) var(--a-space-5); }
|
|
21
|
+
</style>
|
|
22
|
+
</head>
|
|
23
|
+
<body>
|
|
24
|
+
|
|
25
|
+
<main id="demo-root">
|
|
26
|
+
<p>Loading examples…</p>
|
|
27
|
+
</main>
|
|
28
|
+
|
|
29
|
+
<script type="module">
|
|
30
|
+
const root = document.getElementById('demo-root');
|
|
31
|
+
try {
|
|
32
|
+
const res = await fetch('./chat-header.examples.html');
|
|
33
|
+
if (!res.ok) throw new Error(`fetch failed (${res.status})`);
|
|
34
|
+
root.innerHTML = await res.text();
|
|
35
|
+
} catch (err) {
|
|
36
|
+
root.innerHTML = `<p style="color:var(--a-danger-strong);">Failed to load chat-header.examples.html — ${err.message}</p>`;
|
|
37
|
+
console.error('[chat-header.html]', err);
|
|
38
|
+
}
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
</body>
|
|
42
|
+
</html>
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Edit this file; run `npm run build:components` to regenerate a2ui.json.
|
|
2
|
+
$schema: ../../../../scripts/schemas/component.yaml.schema.json
|
|
3
|
+
name: ChatHeader
|
|
4
|
+
tag: chat-header
|
|
5
|
+
component: ChatHeader
|
|
6
|
+
category: layout
|
|
7
|
+
version: 1
|
|
8
|
+
description: |
|
|
9
|
+
Module-tier chat header chrome bar. CSS-only — no behavior, no JS.
|
|
10
|
+
Sits at the top of <chat-shell> or inside a <chat-sidebar>. Holds
|
|
11
|
+
the chat name, status indicator, and action clusters via slot
|
|
12
|
+
vocabulary.
|
|
13
|
+
|
|
14
|
+
Replaces the legacy <header> with [data-chat-name] / [data-chat-status]
|
|
15
|
+
data-attribute children. Both shapes still work per ADR-0023 backwards
|
|
16
|
+
compat; new code should prefer <chat-header> with <chat-status> as
|
|
17
|
+
a slotted child.
|
|
18
|
+
|
|
19
|
+
props: {}
|
|
20
|
+
|
|
21
|
+
events: {}
|
|
22
|
+
|
|
23
|
+
slots:
|
|
24
|
+
default:
|
|
25
|
+
description: >-
|
|
26
|
+
Default — typically [slot="name"] + [slot="status"] children, or
|
|
27
|
+
ad-hoc inline content.
|
|
28
|
+
name:
|
|
29
|
+
description: >-
|
|
30
|
+
Primary chat label (model name, conversation title, etc.).
|
|
31
|
+
status:
|
|
32
|
+
description: >-
|
|
33
|
+
Status indicator — typically <chat-status> showing connection /
|
|
34
|
+
streaming state.
|
|
35
|
+
action:
|
|
36
|
+
description: >-
|
|
37
|
+
Trailing control cluster (settings, share, clear conversation).
|
|
38
|
+
action-leading:
|
|
39
|
+
description: >-
|
|
40
|
+
Leading control cluster (back button, conversation switcher).
|
|
41
|
+
|
|
42
|
+
states:
|
|
43
|
+
- name: idle
|
|
44
|
+
description: Default, the only state.
|
|
45
|
+
|
|
46
|
+
traits: []
|
|
47
|
+
|
|
48
|
+
a2ui:
|
|
49
|
+
rules:
|
|
50
|
+
- >-
|
|
51
|
+
chat-header replaces the legacy <header> chrome bar inside
|
|
52
|
+
<chat-shell>. Use named slots for canonical clusters; ad-hoc
|
|
53
|
+
content goes in the default slot.
|
|
54
|
+
|
|
55
|
+
keywords:
|
|
56
|
+
- chat-header
|
|
57
|
+
- chat-titlebar
|
|
58
|
+
- chrome-bar
|
|
59
|
+
- app-header
|
|
60
|
+
|
|
61
|
+
synonyms:
|
|
62
|
+
chat-header: [titlebar, app-header, navbar]
|
|
63
|
+
|
|
64
|
+
related:
|
|
65
|
+
- ChatShell
|
|
66
|
+
- ChatStatus
|
|
67
|
+
- ChatThread
|
|
68
|
+
- ChatComposer
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
</header>
|
|
11
11
|
|
|
12
12
|
<section data-section>
|
|
13
|
-
<h2 variant="section">Basic shape</h2>
|
|
14
|
-
<p data-note>Author provides the structural DOM; the shell binds the LLM streaming behavior and renders chunks into the messages container.</p>
|
|
13
|
+
<h2 variant="section">Basic shape (legacy)</h2>
|
|
14
|
+
<p data-note>Author provides the structural DOM; the shell binds the LLM streaming behavior and renders chunks into the messages container. This is the original raw-HTML authoring shape — still fully supported.</p>
|
|
15
15
|
<code-ui language="html"><chat-shell provider="anthropic" model="claude-sonnet-4-7" proxy-url="/api/llm">
|
|
16
16
|
<header>
|
|
17
17
|
<span slot="heading">Chat</span>
|
|
@@ -31,6 +31,47 @@
|
|
|
31
31
|
</chat-shell></code-ui>
|
|
32
32
|
</section>
|
|
33
33
|
|
|
34
|
+
<section data-section>
|
|
35
|
+
<h2 variant="section">Basic shape (bespoke — recommended)</h2>
|
|
36
|
+
<p data-note>The bespoke shape uses module-namespaced custom elements per <a href="../../../../.brain/adrs/0023-bespoke-shell-tier-children.md">ADR-0023</a>. Each child owns its own behavior + state attributes; queryable from outside via <code>:has(chat-thread[streaming])</code>.</p>
|
|
37
|
+
<code-ui language="html"><chat-shell provider="anthropic" model="claude-sonnet-4-7" proxy-url="/api/llm">
|
|
38
|
+
<chat-header>
|
|
39
|
+
<span slot="name">Claude</span>
|
|
40
|
+
<chat-status slot="status">Connected</chat-status>
|
|
41
|
+
</chat-header>
|
|
42
|
+
|
|
43
|
+
<chat-thread>
|
|
44
|
+
<chat-empty>
|
|
45
|
+
<empty-state-ui icon="chat-circle" heading="Hello!" description="Ask me anything."></empty-state-ui>
|
|
46
|
+
</chat-empty>
|
|
47
|
+
</chat-thread>
|
|
48
|
+
|
|
49
|
+
<chat-composer>
|
|
50
|
+
<chat-input-ui placeholder="Message…" submit-on-enter></chat-input-ui>
|
|
51
|
+
</chat-composer>
|
|
52
|
+
</chat-shell></code-ui>
|
|
53
|
+
</section>
|
|
54
|
+
|
|
55
|
+
<section data-section>
|
|
56
|
+
<h2 variant="section">State as attribute</h2>
|
|
57
|
+
<p>Every queryable state is reflected on the relevant child element. Style cross-cuts via <code>:has()</code>:</p>
|
|
58
|
+
<code-ui language="css">/* Subtle indicator while LLM is streaming */
|
|
59
|
+
chat-shell:has(chat-thread[streaming]) chat-header {
|
|
60
|
+
/* … */
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* Disable composer style during streaming */
|
|
64
|
+
chat-shell:has(chat-composer[disabled]) {
|
|
65
|
+
/* … */
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* Empty-state visibility — driven automatically */
|
|
69
|
+
chat-thread:not([empty]) > chat-empty {
|
|
70
|
+
display: none;
|
|
71
|
+
}</code-ui>
|
|
72
|
+
<p data-note>JS reads the same attributes — <code>shell.querySelector('chat-thread').streaming</code>. The host (<code><chat-shell></code>) propagates <code>[streaming]</code> to the bespoke <code><chat-thread></code> child automatically when an LLM response is in flight.</p>
|
|
73
|
+
</section>
|
|
74
|
+
|
|
34
75
|
<section data-section>
|
|
35
76
|
<h2 variant="section">Properties</h2>
|
|
36
77
|
<table>
|
|
@@ -79,16 +79,36 @@ class ChatShell extends UIElement {
|
|
|
79
79
|
// ── Lifecycle ──
|
|
80
80
|
|
|
81
81
|
connected() {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
this.#
|
|
86
|
-
|
|
87
|
-
|
|
82
|
+
// Per ADR-0023 — read BOTH legacy (data-* / raw HTML) AND bespoke
|
|
83
|
+
// (chat-thread / chat-composer / chat-empty / chat-status) shapes.
|
|
84
|
+
// The :is() selectors let consumers mix shapes without breakage.
|
|
85
|
+
this.#messagesEl = this.querySelector('chat-thread')
|
|
86
|
+
|| this.querySelector('[data-chat-messages]')
|
|
87
|
+
|| this.querySelector('section');
|
|
88
|
+
this.#inputEl = this.querySelector('chat-composer')
|
|
89
|
+
|| this.querySelector('[data-chat-input]')
|
|
90
|
+
|| this.querySelector('chat-input-ui');
|
|
91
|
+
this.#emptyEl = this.querySelector('chat-empty')
|
|
92
|
+
|| this.querySelector('[data-chat-empty]');
|
|
93
|
+
this.#statusEl = this.querySelector('chat-status')
|
|
94
|
+
|| this.querySelector('[data-chat-status]');
|
|
95
|
+
|
|
96
|
+
// Bespoke <chat-composer> emits 'composer-submit' instead of 'submit'
|
|
97
|
+
// (so the legacy 'submit' event from inside <chat-input-ui> doesn't
|
|
98
|
+
// double-fire). Listen for both depending on which shape is present.
|
|
99
|
+
if (this.#inputEl?.tagName?.toLowerCase() === 'chat-composer') {
|
|
100
|
+
this.#inputEl.addEventListener('composer-submit', this.#onSubmit);
|
|
101
|
+
} else {
|
|
102
|
+
this.#inputEl?.addEventListener('submit', this.#onSubmit);
|
|
103
|
+
}
|
|
88
104
|
}
|
|
89
105
|
|
|
90
106
|
disconnected() {
|
|
91
|
-
this.#inputEl?.
|
|
107
|
+
if (this.#inputEl?.tagName?.toLowerCase() === 'chat-composer') {
|
|
108
|
+
this.#inputEl.removeEventListener('composer-submit', this.#onSubmit);
|
|
109
|
+
} else {
|
|
110
|
+
this.#inputEl?.removeEventListener('submit', this.#onSubmit);
|
|
111
|
+
}
|
|
92
112
|
this.abort();
|
|
93
113
|
}
|
|
94
114
|
|
|
@@ -176,12 +196,20 @@ class ChatShell extends UIElement {
|
|
|
176
196
|
this.streaming = true;
|
|
177
197
|
if (this.#inputEl) this.#inputEl.disabled = true;
|
|
178
198
|
if (this.#statusEl) this.#statusEl.textContent = 'Typing...';
|
|
199
|
+
// Bespoke chat-thread reflects [streaming] for CSS hooks like
|
|
200
|
+
// chat-shell:has(chat-thread[streaming]) { … }
|
|
201
|
+
if (this.#messagesEl?.tagName?.toLowerCase() === 'chat-thread') {
|
|
202
|
+
this.#messagesEl.streaming = true;
|
|
203
|
+
}
|
|
179
204
|
}
|
|
180
205
|
|
|
181
206
|
stopStreaming() {
|
|
182
207
|
this.streaming = false;
|
|
183
208
|
if (this.#inputEl) this.#inputEl.disabled = false;
|
|
184
209
|
if (this.#statusEl) this.#statusEl.textContent = '';
|
|
210
|
+
if (this.#messagesEl?.tagName?.toLowerCase() === 'chat-thread') {
|
|
211
|
+
this.#messagesEl.streaming = false;
|
|
212
|
+
}
|
|
185
213
|
|
|
186
214
|
// Remove cursor
|
|
187
215
|
this.#messagesEl?.querySelector('[data-role]:last-child [data-cursor]')?.remove();
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/* ═══════════════════════════════════════════════════════════════
|
|
2
|
+
chat-shell — Bespoke shell-tier children (Phase 2 of ADR-0023)
|
|
3
|
+
|
|
4
|
+
The chat-* CSS-only structural children are styled by SHARING the
|
|
5
|
+
same CSS rules as their legacy raw-HTML counterparts. This file
|
|
6
|
+
re-applies the existing selectors (header / section / footer) to
|
|
7
|
+
the new bespoke tags via display + structural mappings.
|
|
8
|
+
|
|
9
|
+
Mirrors the admin-shell.bespoke.css pattern. Keeps the chat
|
|
10
|
+
cluster's CSS modifications isolated in one bridge file so Phase 3
|
|
11
|
+
(legacy shape removal) is a single-file delete.
|
|
12
|
+
═══════════════════════════════════════════════════════════════ */
|
|
13
|
+
|
|
14
|
+
/* ── chat-header — top chrome bar ── */
|
|
15
|
+
chat-shell > chat-header,
|
|
16
|
+
chat-sidebar > chat-header[slot="header"],
|
|
17
|
+
chat-sidebar > chat-header:first-child {
|
|
18
|
+
display: flex;
|
|
19
|
+
align-items: center;
|
|
20
|
+
gap: var(--chat-header-gap, var(--a-space-2));
|
|
21
|
+
padding: 0 var(--chat-header-px, var(--a-space-3));
|
|
22
|
+
height: var(--chat-header-height, var(--a-size-lg));
|
|
23
|
+
font-size: var(--chat-header-font, var(--a-ui-size));
|
|
24
|
+
border-bottom: var(--chat-border, 1px solid var(--a-border-subtle));
|
|
25
|
+
flex-shrink: 0;
|
|
26
|
+
background: var(--chat-bg, var(--a-bg));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* Slot vocabulary inside chat-header */
|
|
30
|
+
chat-header > [slot="name"] {
|
|
31
|
+
font-weight: var(--a-weight-medium, 500);
|
|
32
|
+
color: var(--a-fg);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
chat-header > [slot="status"] {
|
|
36
|
+
margin-inline-start: var(--a-space-2);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
chat-header > [slot="action"]:first-of-type {
|
|
40
|
+
margin-inline-start: auto;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
chat-header > [slot="action-leading"] {
|
|
44
|
+
margin-inline-end: var(--a-space-2);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* ── chat-status — inline status indicator ── */
|
|
48
|
+
chat-status {
|
|
49
|
+
display: inline-flex;
|
|
50
|
+
align-items: center;
|
|
51
|
+
gap: var(--a-space-1);
|
|
52
|
+
font-size: var(--a-ui-sm);
|
|
53
|
+
color: var(--a-fg-muted);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* ── chat-thread — message scroll surface ── */
|
|
57
|
+
chat-shell > chat-thread {
|
|
58
|
+
flex: 1;
|
|
59
|
+
min-height: 0;
|
|
60
|
+
overflow-y: auto;
|
|
61
|
+
overscroll-behavior: contain;
|
|
62
|
+
display: flex;
|
|
63
|
+
flex-direction: column;
|
|
64
|
+
gap: var(--chat-message-gap, var(--a-space-3));
|
|
65
|
+
padding: var(--chat-thread-py, var(--a-space-4)) var(--chat-thread-px, var(--a-space-4));
|
|
66
|
+
background: var(--chat-thread-bg, var(--a-bg));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
chat-thread[streaming] {
|
|
70
|
+
/* Optional: subtle indicator while streaming. Default: no visual change. */
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* ── chat-empty — empty state, hidden when messages present ── */
|
|
74
|
+
chat-thread:not([empty]) > chat-empty {
|
|
75
|
+
display: none;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
chat-thread[empty] > chat-empty {
|
|
79
|
+
display: flex;
|
|
80
|
+
flex: 1;
|
|
81
|
+
align-items: center;
|
|
82
|
+
justify-content: center;
|
|
83
|
+
padding: var(--a-space-6);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* ── chat-composer — input wrapper ── */
|
|
87
|
+
chat-shell > chat-composer {
|
|
88
|
+
display: flex;
|
|
89
|
+
align-items: stretch;
|
|
90
|
+
gap: var(--a-space-2);
|
|
91
|
+
padding: var(--chat-composer-py, var(--a-space-3)) var(--chat-composer-px, var(--a-space-3));
|
|
92
|
+
border-top: var(--chat-border, 1px solid var(--a-border-subtle));
|
|
93
|
+
background: var(--chat-bg, var(--a-bg));
|
|
94
|
+
flex-shrink: 0;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
chat-composer > [slot="leading"],
|
|
98
|
+
chat-composer > [slot="attach"] {
|
|
99
|
+
flex-shrink: 0;
|
|
100
|
+
display: inline-flex;
|
|
101
|
+
align-items: center;
|
|
102
|
+
gap: var(--a-space-1);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
chat-composer > [slot="trailing"] {
|
|
106
|
+
flex-shrink: 0;
|
|
107
|
+
display: inline-flex;
|
|
108
|
+
align-items: center;
|
|
109
|
+
gap: var(--a-space-1);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* The primary input child takes remaining space */
|
|
113
|
+
chat-composer > :is(chat-input-ui, input-ui, textarea-ui) {
|
|
114
|
+
flex: 1;
|
|
115
|
+
min-width: 0;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
chat-composer[disabled] {
|
|
119
|
+
opacity: 0.6;
|
|
120
|
+
pointer-events: none;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/* ── chat-sidebar — bridges to admin-sidebar pattern ──
|
|
124
|
+
Uses the same geometry + container-query + resize handle rules as
|
|
125
|
+
admin-sidebar. The cluster-namespaced tag exists for discoverability;
|
|
126
|
+
the styling logic is identical because the concern is identical
|
|
127
|
+
(resizable side rail). */
|
|
128
|
+
|
|
129
|
+
:is(chat-sidebar[slot="leading"], chat-sidebar[slot="trailing"]) {
|
|
130
|
+
display: flex;
|
|
131
|
+
flex-direction: column;
|
|
132
|
+
flex-shrink: 0;
|
|
133
|
+
min-width: var(--chat-sidebar-min-width, 48px);
|
|
134
|
+
max-width: var(--chat-sidebar-max-width, 480px);
|
|
135
|
+
min-height: 0;
|
|
136
|
+
font-size: var(--chat-sidebar-font, var(--a-ui-size));
|
|
137
|
+
position: relative;
|
|
138
|
+
container-type: inline-size;
|
|
139
|
+
container-name: sidebar;
|
|
140
|
+
transition: width var(--chat-duration, var(--a-duration, 200ms)) var(--chat-easing, var(--a-easing, ease));
|
|
141
|
+
background: var(--chat-bg, var(--a-bg));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
chat-sidebar[slot="leading"] {
|
|
145
|
+
width: var(--chat-sidebar-width-leading, 240px);
|
|
146
|
+
border-right: var(--chat-border, 1px solid var(--a-border-subtle));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
chat-sidebar[slot="trailing"] {
|
|
150
|
+
width: var(--chat-sidebar-width-trailing, 320px);
|
|
151
|
+
border-left: var(--chat-border, 1px solid var(--a-border-subtle));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/* Resize handle */
|
|
155
|
+
:is(chat-sidebar[slot="leading"], chat-sidebar[slot="trailing"]) > [data-resize] {
|
|
156
|
+
position: absolute;
|
|
157
|
+
top: 0;
|
|
158
|
+
bottom: 0;
|
|
159
|
+
width: 6px;
|
|
160
|
+
cursor: col-resize;
|
|
161
|
+
z-index: 1;
|
|
162
|
+
user-select: none;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
chat-sidebar[slot="leading"] > [data-resize] { right: -3px; }
|
|
166
|
+
chat-sidebar[slot="trailing"] > [data-resize] { left: -3px; }
|
|
167
|
+
|
|
168
|
+
:is(chat-sidebar[slot="leading"], chat-sidebar[slot="trailing"]) > [data-resize]:hover {
|
|
169
|
+
background: var(--a-accent);
|
|
170
|
+
opacity: 0.5;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
chat-sidebar[resizing] {
|
|
174
|
+
transition: none;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
chat-sidebar[resizing] > [data-resize] {
|
|
178
|
+
background: var(--a-accent);
|
|
179
|
+
opacity: 0.8;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/* ── chat-shell layout — when bespoke children are used ──
|
|
183
|
+
Adjusts flex direction so chat-sidebar can sit alongside the main
|
|
184
|
+
chat surface. Without this, chat-shell's existing layout.css
|
|
185
|
+
assumes a vertical stack (header / messages / composer). */
|
|
186
|
+
|
|
187
|
+
chat-shell:has(> chat-sidebar) {
|
|
188
|
+
display: flex;
|
|
189
|
+
flex-direction: row;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/* When sidebar is present, the main chat content (everything that
|
|
193
|
+
isn't a sidebar) needs to be a flex column itself */
|
|
194
|
+
chat-shell:has(> chat-sidebar) > :not(chat-sidebar) {
|
|
195
|
+
display: contents;
|
|
196
|
+
}
|