@xmesh/system-design 0.0.1
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 +472 -0
- package/assets/brand-lockup-dark.svg +9 -0
- package/assets/brand-lockup-light.svg +9 -0
- package/assets/brand-mark.svg +9 -0
- package/colors_and_type.css +11 -0
- package/dist/lit/components/alert/index.css +201 -0
- package/dist/lit/components/alert/index.d.ts +25 -0
- package/dist/lit/components/alert/index.js +191 -0
- package/dist/lit/components/app-bar/index.css +80 -0
- package/dist/lit/components/app-bar/index.d.ts +19 -0
- package/dist/lit/components/app-bar/index.js +120 -0
- package/dist/lit/components/artifact/index.css +166 -0
- package/dist/lit/components/artifact/index.d.ts +37 -0
- package/dist/lit/components/artifact/index.js +294 -0
- package/dist/lit/components/autocomplete/index.css +171 -0
- package/dist/lit/components/autocomplete/index.d.ts +47 -0
- package/dist/lit/components/autocomplete/index.js +404 -0
- package/dist/lit/components/avatar/index.css +62 -0
- package/dist/lit/components/avatar/index.d.ts +19 -0
- package/dist/lit/components/avatar/index.js +112 -0
- package/dist/lit/components/avatar-group/index.css +60 -0
- package/dist/lit/components/avatar-group/index.d.ts +19 -0
- package/dist/lit/components/avatar-group/index.js +97 -0
- package/dist/lit/components/badge/index.css +72 -0
- package/dist/lit/components/badge/index.d.ts +18 -0
- package/dist/lit/components/badge/index.js +115 -0
- package/dist/lit/components/brand-mark/index.css +109 -0
- package/dist/lit/components/brand-mark/index.d.ts +24 -0
- package/dist/lit/components/brand-mark/index.js +116 -0
- package/dist/lit/components/breadcrumbs/index.css +91 -0
- package/dist/lit/components/breadcrumbs/index.d.ts +19 -0
- package/dist/lit/components/breadcrumbs/index.js +104 -0
- package/dist/lit/components/bubble/index.css +182 -0
- package/dist/lit/components/bubble/index.d.ts +72 -0
- package/dist/lit/components/bubble/index.js +617 -0
- package/dist/lit/components/button/index.css +342 -0
- package/dist/lit/components/button/index.d.ts +32 -0
- package/dist/lit/components/button/index.js +202 -0
- package/dist/lit/components/card/index.css +99 -0
- package/dist/lit/components/card/index.d.ts +20 -0
- package/dist/lit/components/card/index.js +133 -0
- package/dist/lit/components/chat/index.css +292 -0
- package/dist/lit/components/chat/index.d.ts +74 -0
- package/dist/lit/components/chat/index.js +589 -0
- package/dist/lit/components/checkbox/index.css +126 -0
- package/dist/lit/components/checkbox/index.d.ts +21 -0
- package/dist/lit/components/checkbox/index.js +138 -0
- package/dist/lit/components/chip/index.css +145 -0
- package/dist/lit/components/chip/index.d.ts +30 -0
- package/dist/lit/components/chip/index.js +230 -0
- package/dist/lit/components/chip-group/index.css +19 -0
- package/dist/lit/components/chip-group/index.d.ts +24 -0
- package/dist/lit/components/chip-group/index.js +171 -0
- package/dist/lit/components/code/index.css +42 -0
- package/dist/lit/components/code/index.d.ts +12 -0
- package/dist/lit/components/code/index.js +68 -0
- package/dist/lit/components/composer/index.css +548 -0
- package/dist/lit/components/composer/index.d.ts +67 -0
- package/dist/lit/components/composer/index.js +713 -0
- package/dist/lit/components/data-table/index.css +166 -0
- package/dist/lit/components/data-table/index.d.ts +55 -0
- package/dist/lit/components/data-table/index.js +390 -0
- package/dist/lit/components/dialog/index.css +124 -0
- package/dist/lit/components/dialog/index.d.ts +24 -0
- package/dist/lit/components/dialog/index.js +199 -0
- package/dist/lit/components/divider/index.css +27 -0
- package/dist/lit/components/divider/index.d.ts +13 -0
- package/dist/lit/components/divider/index.js +67 -0
- package/dist/lit/components/empty-state/index.css +69 -0
- package/dist/lit/components/empty-state/index.d.ts +21 -0
- package/dist/lit/components/empty-state/index.js +123 -0
- package/dist/lit/components/expansion-panel/index.css +120 -0
- package/dist/lit/components/expansion-panel/index.d.ts +22 -0
- package/dist/lit/components/expansion-panel/index.js +174 -0
- package/dist/lit/components/field/index.css +223 -0
- package/dist/lit/components/field/index.d.ts +106 -0
- package/dist/lit/components/field/index.js +388 -0
- package/dist/lit/components/file-input/index.css +257 -0
- package/dist/lit/components/file-input/index.d.ts +30 -0
- package/dist/lit/components/file-input/index.js +298 -0
- package/dist/lit/components/form/index.css +29 -0
- package/dist/lit/components/form/index.d.ts +38 -0
- package/dist/lit/components/form/index.js +192 -0
- package/dist/lit/components/grid/index.css +53 -0
- package/dist/lit/components/grid/index.d.ts +14 -0
- package/dist/lit/components/grid/index.js +82 -0
- package/dist/lit/components/kbd/index.css +35 -0
- package/dist/lit/components/kbd/index.d.ts +11 -0
- package/dist/lit/components/kbd/index.js +43 -0
- package/dist/lit/components/list/index.css +15 -0
- package/dist/lit/components/list/index.d.ts +28 -0
- package/dist/lit/components/list/index.js +188 -0
- package/dist/lit/components/list-item/index.css +119 -0
- package/dist/lit/components/list-item/index.d.ts +20 -0
- package/dist/lit/components/list-item/index.js +127 -0
- package/dist/lit/components/menu/index.css +94 -0
- package/dist/lit/components/menu/index.d.ts +47 -0
- package/dist/lit/components/menu/index.js +386 -0
- package/dist/lit/components/navigation-drawer/index.css +114 -0
- package/dist/lit/components/navigation-drawer/index.d.ts +29 -0
- package/dist/lit/components/navigation-drawer/index.js +218 -0
- package/dist/lit/components/overlay/index.css +171 -0
- package/dist/lit/components/overlay/index.d.ts +65 -0
- package/dist/lit/components/overlay/index.js +566 -0
- package/dist/lit/components/pagination/index.css +102 -0
- package/dist/lit/components/pagination/index.d.ts +22 -0
- package/dist/lit/components/pagination/index.js +184 -0
- package/dist/lit/components/primitives/index.css +504 -0
- package/dist/lit/components/primitives/index.d.ts +25 -0
- package/dist/lit/components/primitives/index.js +283 -0
- package/dist/lit/components/progress/index.css +143 -0
- package/dist/lit/components/progress/index.d.ts +23 -0
- package/dist/lit/components/progress/index.js +180 -0
- package/dist/lit/components/radio-group/index.css +178 -0
- package/dist/lit/components/radio-group/index.d.ts +35 -0
- package/dist/lit/components/radio-group/index.js +292 -0
- package/dist/lit/components/select/index.css +151 -0
- package/dist/lit/components/select/index.d.ts +50 -0
- package/dist/lit/components/select/index.js +390 -0
- package/dist/lit/components/sidebar-item/index.css +133 -0
- package/dist/lit/components/sidebar-item/index.d.ts +20 -0
- package/dist/lit/components/sidebar-item/index.js +105 -0
- package/dist/lit/components/skeleton/index.css +81 -0
- package/dist/lit/components/skeleton/index.d.ts +19 -0
- package/dist/lit/components/skeleton/index.js +119 -0
- package/dist/lit/components/slider/index.css +171 -0
- package/dist/lit/components/slider/index.d.ts +36 -0
- package/dist/lit/components/slider/index.js +302 -0
- package/dist/lit/components/snackbar/index.css +279 -0
- package/dist/lit/components/snackbar/index.d.ts +33 -0
- package/dist/lit/components/snackbar/index.js +195 -0
- package/dist/lit/components/stack/index.css +41 -0
- package/dist/lit/components/stack/index.d.ts +20 -0
- package/dist/lit/components/stack/index.js +103 -0
- package/dist/lit/components/switch/index.css +126 -0
- package/dist/lit/components/switch/index.d.ts +17 -0
- package/dist/lit/components/switch/index.js +116 -0
- package/dist/lit/components/table/index.css +85 -0
- package/dist/lit/components/table/index.d.ts +25 -0
- package/dist/lit/components/table/index.js +139 -0
- package/dist/lit/components/tabs/index.css +116 -0
- package/dist/lit/components/tabs/index.d.ts +49 -0
- package/dist/lit/components/tabs/index.js +320 -0
- package/dist/lit/components/text-field/index.css +90 -0
- package/dist/lit/components/text-field/index.d.ts +17 -0
- package/dist/lit/components/text-field/index.js +101 -0
- package/dist/lit/components/textarea/index.css +55 -0
- package/dist/lit/components/textarea/index.d.ts +26 -0
- package/dist/lit/components/textarea/index.js +124 -0
- package/dist/lit/components/tooltip/index.css +37 -0
- package/dist/lit/components/tooltip/index.d.ts +31 -0
- package/dist/lit/components/tooltip/index.js +196 -0
- package/dist/lit/components/validation/index.css +386 -0
- package/dist/lit/components/validation/index.d.ts +45 -0
- package/dist/lit/components/validation/index.js +318 -0
- package/dist/lit/index.d.ts +50 -0
- package/dist/lit/index.js +59 -0
- package/package.json +81 -0
- package/styles/README.md +346 -0
- package/styles/_elevation.css +24 -0
- package/styles/_fonts.css +6 -0
- package/styles/_layout.css +37 -0
- package/styles/_primitives.css +154 -0
- package/styles/_scroll.css +75 -0
- package/styles/_semantic.css +146 -0
- package/styles/_space.css +61 -0
- package/styles/_type.css +139 -0
- package/styles/_xmesh-extensions.css +232 -0
- package/styles/index.css +44 -0
- package/styles/md3/_color.css +102 -0
- package/styles/md3/_elevation.css +26 -0
- package/styles/md3/_motion.css +35 -0
- package/styles/md3/_shape.css +22 -0
- package/styles/md3/_state.css +22 -0
- package/styles/md3/_type.css +111 -0
|
@@ -0,0 +1,617 @@
|
|
|
1
|
+
/*
|
|
2
|
+
bubble/index.ts — Lit port of components/bubble/index.jsx.
|
|
3
|
+
|
|
4
|
+
Three custom elements ship together because they always compose:
|
|
5
|
+
|
|
6
|
+
<xm-bubble-group variant="user|bot"> column wrapper, anchors edge,
|
|
7
|
+
reveals actions on hover
|
|
8
|
+
<xm-bubble variant="user|bot"> the message body itself
|
|
9
|
+
<xm-bubble-actions ts="00:03"> hover-reveal toolbar; <button>
|
|
10
|
+
children get .bubble-actions__btn
|
|
11
|
+
applied via DOM walk
|
|
12
|
+
|
|
13
|
+
Plus <xm-artifact-chip> for the small file-reference card that shows
|
|
14
|
+
below a bot bubble.
|
|
15
|
+
|
|
16
|
+
Light DOM is used throughout. The original components/bubble/index.css
|
|
17
|
+
targets BEM classes (.bubble, .bubble__paragraph, .bubble-actions__btn)
|
|
18
|
+
and reuses .att-card* from composer/index.css for the artifact chip;
|
|
19
|
+
rendering in light DOM lets those stylesheets keep working unchanged.
|
|
20
|
+
|
|
21
|
+
Markdown class walker (xm-bubble):
|
|
22
|
+
The React BubbleBody walks React.Children, adding a BEM class to
|
|
23
|
+
each known tag (<p>, <ol>, <ul>, <li>, <strong>, <code>). We do
|
|
24
|
+
the same here by traversing this.childNodes once on connect and
|
|
25
|
+
mutating each authored element's classList. Authors write semantic
|
|
26
|
+
HTML; the bubble adds the BEM hooks the CSS needs.
|
|
27
|
+
*/
|
|
28
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
29
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
30
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
31
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
32
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
33
|
+
};
|
|
34
|
+
import { LitElement, html, nothing } from "lit";
|
|
35
|
+
import { unsafeHTML } from "lit/directives/unsafe-html.js";
|
|
36
|
+
import { customElement, property } from "lit/decorators.js";
|
|
37
|
+
// Register <xm-button> for the artifact "Download" action below.
|
|
38
|
+
import "../button/index.js";
|
|
39
|
+
class LightElement extends LitElement {
|
|
40
|
+
createRenderRoot() {
|
|
41
|
+
return this;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// Inline check-icon SVG for the static-preview "Copied" feedback. We can't
|
|
45
|
+
// rely on <xm-check-icon> here because the static authoring shape uses the
|
|
46
|
+
// custom element directly, and we'd need to mount and upgrade it on the fly;
|
|
47
|
+
// inlining the SVG avoids that and matches the hairline 1.8 stroke used by
|
|
48
|
+
// the icon primitives.
|
|
49
|
+
const CHECK_ICON_SVG = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="20 6 9 17 4 12"></polyline></svg>`;
|
|
50
|
+
// Best-effort clipboard write. Mirrors the same helper in chat/index.ts —
|
|
51
|
+
// duplicated here so xm-bubble-actions can copy on its own when no parent
|
|
52
|
+
// component wires the click.
|
|
53
|
+
const writeClipboard = async (text) => {
|
|
54
|
+
try {
|
|
55
|
+
if (navigator.clipboard?.writeText) {
|
|
56
|
+
await navigator.clipboard.writeText(text);
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (_) { /* fall through to legacy path */ }
|
|
61
|
+
try {
|
|
62
|
+
const ta = document.createElement("textarea");
|
|
63
|
+
ta.value = text;
|
|
64
|
+
ta.setAttribute("readonly", "");
|
|
65
|
+
ta.style.position = "fixed";
|
|
66
|
+
ta.style.opacity = "0";
|
|
67
|
+
document.body.appendChild(ta);
|
|
68
|
+
ta.select();
|
|
69
|
+
const ok = document.execCommand("copy");
|
|
70
|
+
document.body.removeChild(ta);
|
|
71
|
+
return ok;
|
|
72
|
+
}
|
|
73
|
+
catch (_) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
const BUBBLE_TAG_CLASS = {
|
|
78
|
+
P: ["bubble__paragraph"],
|
|
79
|
+
OL: ["bubble__list", "bubble__list--ordered"],
|
|
80
|
+
UL: ["bubble__list", "bubble__list--unordered"],
|
|
81
|
+
LI: ["bubble__list-item"],
|
|
82
|
+
STRONG: ["bubble__strong"],
|
|
83
|
+
CODE: ["bubble__code"],
|
|
84
|
+
};
|
|
85
|
+
function applyBubbleClasses(root) {
|
|
86
|
+
// Mirrors the React applyBubbleClass walker exactly:
|
|
87
|
+
// • shallow walk of top-level children (one level)
|
|
88
|
+
// • <ol>/<ul> additionally walk their direct children one level,
|
|
89
|
+
// so <li> elements pick up .bubble__list-item — but children of
|
|
90
|
+
// <li> are NOT walked (a <strong> inside <li> is left alone).
|
|
91
|
+
const apply = (node) => {
|
|
92
|
+
if (node.nodeType !== Node.ELEMENT_NODE)
|
|
93
|
+
return;
|
|
94
|
+
const el = node;
|
|
95
|
+
const cls = BUBBLE_TAG_CLASS[el.tagName];
|
|
96
|
+
if (cls)
|
|
97
|
+
cls.forEach((c) => el.classList.add(c));
|
|
98
|
+
if (el.tagName === "OL" || el.tagName === "UL") {
|
|
99
|
+
for (const child of Array.from(el.children))
|
|
100
|
+
apply(child);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
for (const child of Array.from(root))
|
|
104
|
+
apply(child);
|
|
105
|
+
}
|
|
106
|
+
/* ─────────────────────────────────────────────────────────────
|
|
107
|
+
<xm-bubble-group> — variant-aware column wrapper.
|
|
108
|
+
Authors put bubble + actions inside it; the group anchors the
|
|
109
|
+
layout edge and reveals .bubble-actions on hover via CSS only.
|
|
110
|
+
─────────────────────────────────────────────────────────────*/
|
|
111
|
+
let XmBubbleGroup = class XmBubbleGroup extends LightElement {
|
|
112
|
+
constructor() {
|
|
113
|
+
super(...arguments);
|
|
114
|
+
this.variant = "bot";
|
|
115
|
+
this._authoredChildren = [];
|
|
116
|
+
}
|
|
117
|
+
connectedCallback() {
|
|
118
|
+
super.connectedCallback();
|
|
119
|
+
// Snapshot children so render() can mount them into a slot host
|
|
120
|
+
// we own. Lit's render replaces innerHTML; this preserves them.
|
|
121
|
+
this._authoredChildren = Array.from(this.childNodes).filter((n) => n.nodeType === Node.ELEMENT_NODE ||
|
|
122
|
+
(n.nodeType === Node.TEXT_NODE && !!n.textContent?.trim()));
|
|
123
|
+
for (const n of this._authoredChildren)
|
|
124
|
+
n.remove();
|
|
125
|
+
}
|
|
126
|
+
render() {
|
|
127
|
+
const cls = `bubble-group bubble-group--${this.variant}`;
|
|
128
|
+
return html `<div class="${cls}" data-mount="children"></div>`;
|
|
129
|
+
}
|
|
130
|
+
updated() {
|
|
131
|
+
const host = this.querySelector('[data-mount="children"]');
|
|
132
|
+
if (!host)
|
|
133
|
+
return;
|
|
134
|
+
if (host.childNodes.length === 0 && this._authoredChildren.length) {
|
|
135
|
+
for (const n of this._authoredChildren)
|
|
136
|
+
host.appendChild(n);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
__decorate([
|
|
141
|
+
property({ type: String })
|
|
142
|
+
], XmBubbleGroup.prototype, "variant", void 0);
|
|
143
|
+
XmBubbleGroup = __decorate([
|
|
144
|
+
customElement("xm-bubble-group")
|
|
145
|
+
], XmBubbleGroup);
|
|
146
|
+
/* ─────────────────────────────────────────────────────────────
|
|
147
|
+
<xm-bubble> — the message body.
|
|
148
|
+
|
|
149
|
+
Properties:
|
|
150
|
+
variant "user" | "bot" (default "bot")
|
|
151
|
+
attachments Array of { id, name, kind, dataUrl? }
|
|
152
|
+
artifact Object { title, sub:[], provider? } | null
|
|
153
|
+
download Boolean — show Download button on the artifact chip
|
|
154
|
+
|
|
155
|
+
Authoring children are the markdown content. They are walked once
|
|
156
|
+
on connect to apply .bubble__* BEM classes.
|
|
157
|
+
─────────────────────────────────────────────────────────────*/
|
|
158
|
+
let XmBubble = class XmBubble extends LightElement {
|
|
159
|
+
constructor() {
|
|
160
|
+
super(...arguments);
|
|
161
|
+
this.variant = "bot";
|
|
162
|
+
this.attachments = [];
|
|
163
|
+
this.artifact = null;
|
|
164
|
+
this.download = false;
|
|
165
|
+
this.text = "";
|
|
166
|
+
this.bodyHtml = "";
|
|
167
|
+
this._authored = [];
|
|
168
|
+
this._onArtifactOpen = () => {
|
|
169
|
+
if (!this.artifact)
|
|
170
|
+
return;
|
|
171
|
+
this.dispatchEvent(new CustomEvent("artifact-open", {
|
|
172
|
+
detail: { artifact: this.artifact },
|
|
173
|
+
bubbles: true,
|
|
174
|
+
composed: true,
|
|
175
|
+
}));
|
|
176
|
+
};
|
|
177
|
+
this._onArtifactDownload = (e) => {
|
|
178
|
+
e.stopPropagation();
|
|
179
|
+
if (!this.artifact)
|
|
180
|
+
return;
|
|
181
|
+
this.dispatchEvent(new CustomEvent("artifact-download", {
|
|
182
|
+
detail: { artifact: this.artifact },
|
|
183
|
+
bubbles: true,
|
|
184
|
+
composed: true,
|
|
185
|
+
}));
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
connectedCallback() {
|
|
189
|
+
super.connectedCallback();
|
|
190
|
+
// Convenience: a data-artifact='{"title":...}' attribute hydrates
|
|
191
|
+
// the artifact property at upgrade time, so static HTML previews
|
|
192
|
+
// can author a chip without an inline <script>.
|
|
193
|
+
const dataArtifact = this.getAttribute("data-artifact");
|
|
194
|
+
if (dataArtifact && !this.artifact) {
|
|
195
|
+
try {
|
|
196
|
+
this.artifact = JSON.parse(dataArtifact);
|
|
197
|
+
}
|
|
198
|
+
catch (_) { /* malformed JSON — leave artifact null */ }
|
|
199
|
+
}
|
|
200
|
+
// Same convenience for attachments.
|
|
201
|
+
const dataAtts = this.getAttribute("data-attachments");
|
|
202
|
+
if (dataAtts && (!this.attachments || this.attachments.length === 0)) {
|
|
203
|
+
try {
|
|
204
|
+
this.attachments = JSON.parse(dataAtts);
|
|
205
|
+
}
|
|
206
|
+
catch (_) { /* malformed JSON — leave attachments empty */ }
|
|
207
|
+
}
|
|
208
|
+
// Snapshot authored markdown children. This works for static HTML
|
|
209
|
+
// (preview/bubbles.html) where children exist at upgrade time.
|
|
210
|
+
// Parent lit templates should use the `text` / `body-html`
|
|
211
|
+
// properties instead — those skip this snapshot+remount path
|
|
212
|
+
// entirely.
|
|
213
|
+
applyBubbleClasses(this.childNodes);
|
|
214
|
+
this._authored = Array.from(this.childNodes).filter((n) => n.nodeType === Node.ELEMENT_NODE ||
|
|
215
|
+
(n.nodeType === Node.TEXT_NODE && !!n.textContent?.trim()));
|
|
216
|
+
for (const n of this._authored)
|
|
217
|
+
n.remove();
|
|
218
|
+
}
|
|
219
|
+
_attachmentTemplate(a) {
|
|
220
|
+
if (a.kind === "image" && a.dataUrl) {
|
|
221
|
+
return html `
|
|
222
|
+
<span class="bubble__att-mini">
|
|
223
|
+
<span
|
|
224
|
+
class="bubble__att-thumb"
|
|
225
|
+
style="background-image:url(${a.dataUrl})"
|
|
226
|
+
></span>
|
|
227
|
+
${a.name}
|
|
228
|
+
</span>
|
|
229
|
+
`;
|
|
230
|
+
}
|
|
231
|
+
return html `
|
|
232
|
+
<span class="bubble__att-mini">
|
|
233
|
+
<svg width="11" height="11" viewBox="0 0 24 24"
|
|
234
|
+
fill="none" stroke="currentColor" stroke-width="1.8"
|
|
235
|
+
stroke-linecap="round" stroke-linejoin="round">
|
|
236
|
+
<path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z" />
|
|
237
|
+
<path d="M14 3v5h5" />
|
|
238
|
+
</svg>
|
|
239
|
+
${a.name}
|
|
240
|
+
</span>
|
|
241
|
+
`;
|
|
242
|
+
}
|
|
243
|
+
_artifactTemplate() {
|
|
244
|
+
const a = this.artifact;
|
|
245
|
+
if (!a)
|
|
246
|
+
return nothing;
|
|
247
|
+
return html `
|
|
248
|
+
<div
|
|
249
|
+
role="button"
|
|
250
|
+
tabindex="0"
|
|
251
|
+
class="att-card att-card--text att-card--text-row"
|
|
252
|
+
title="Open ${a.title}"
|
|
253
|
+
@click=${this._onArtifactOpen}
|
|
254
|
+
@keydown=${(e) => {
|
|
255
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
256
|
+
e.preventDefault();
|
|
257
|
+
this._onArtifactOpen();
|
|
258
|
+
}
|
|
259
|
+
}}
|
|
260
|
+
>
|
|
261
|
+
<span class="att-card__icon" aria-hidden="true">
|
|
262
|
+
<svg width="18" height="18" viewBox="0 0 24 24"
|
|
263
|
+
fill="none" stroke="currentColor" stroke-width="1.6"
|
|
264
|
+
stroke-linecap="round" stroke-linejoin="round">
|
|
265
|
+
<path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z" />
|
|
266
|
+
<path d="M14 3v5h5" />
|
|
267
|
+
</svg>
|
|
268
|
+
</span>
|
|
269
|
+
<span class="att-card__body">
|
|
270
|
+
<span class="att-card__name">${a.title}</span>
|
|
271
|
+
${a.sub?.length
|
|
272
|
+
? html `<span class="att-card__sub">
|
|
273
|
+
${a.sub.map((s, i) => html `
|
|
274
|
+
${i > 0 ? html `<span class="ds-sep">·</span>` : nothing}
|
|
275
|
+
<span>${s}</span>
|
|
276
|
+
`)}
|
|
277
|
+
</span>`
|
|
278
|
+
: nothing}
|
|
279
|
+
</span>
|
|
280
|
+
${this.download
|
|
281
|
+
? html `<span class="att-card__action" @click=${(e) => e.stopPropagation()}>
|
|
282
|
+
<xm-button variant="outline-canvas" size="md"
|
|
283
|
+
@click=${this._onArtifactDownload}>Download</xm-button>
|
|
284
|
+
</span>`
|
|
285
|
+
: nothing}
|
|
286
|
+
</div>
|
|
287
|
+
`;
|
|
288
|
+
}
|
|
289
|
+
render() {
|
|
290
|
+
const cls = `bubble bubble--${this.variant}`;
|
|
291
|
+
const atts = this.attachments || [];
|
|
292
|
+
// Two paths to author body content:
|
|
293
|
+
// 1. `text` / `body-html` properties — preferred when xm-bubble is
|
|
294
|
+
// rendered by another lit template (e.g. xm-chat-shell). The
|
|
295
|
+
// body is part of the bubble's own template, so lit's marker
|
|
296
|
+
// tracking stays consistent across re-renders.
|
|
297
|
+
// 2. authored children — preferred for static HTML previews
|
|
298
|
+
// (e.g. preview/bubbles.html). Snapshotted in connectedCallback,
|
|
299
|
+
// remounted into [data-mount="body"] in updated().
|
|
300
|
+
const hasDirectBody = !!(this.text || this.bodyHtml);
|
|
301
|
+
return html `
|
|
302
|
+
<div class="${cls}">
|
|
303
|
+
${atts.length
|
|
304
|
+
? html `<div class="bubble__atts">
|
|
305
|
+
${atts.map((a) => this._attachmentTemplate(a))}
|
|
306
|
+
</div>`
|
|
307
|
+
: nothing}
|
|
308
|
+
${hasDirectBody
|
|
309
|
+
? html `<div class="bubble__body">${this.bodyHtml ? unsafeHTML(this.bodyHtml) : this.text}</div>`
|
|
310
|
+
: html `<div data-mount="body" style="display:contents"></div>`}
|
|
311
|
+
${this._artifactTemplate()}
|
|
312
|
+
</div>
|
|
313
|
+
`;
|
|
314
|
+
}
|
|
315
|
+
updated() {
|
|
316
|
+
const host = this.querySelector('[data-mount="body"]');
|
|
317
|
+
if (host && host.childNodes.length === 0 && this._authored.length) {
|
|
318
|
+
for (const n of this._authored)
|
|
319
|
+
host.appendChild(n);
|
|
320
|
+
}
|
|
321
|
+
// When the body comes from `body-html`, run the markdown-walker
|
|
322
|
+
// over the rendered DOM so <p>/<ol>/<ul>/etc. pick up their BEM
|
|
323
|
+
// classes (same effect the React BubbleBody walker has).
|
|
324
|
+
if (this.bodyHtml) {
|
|
325
|
+
const body = this.querySelector(".bubble__body");
|
|
326
|
+
if (body)
|
|
327
|
+
applyBubbleClasses(body.children);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
__decorate([
|
|
332
|
+
property({ type: String })
|
|
333
|
+
], XmBubble.prototype, "variant", void 0);
|
|
334
|
+
__decorate([
|
|
335
|
+
property({ attribute: false })
|
|
336
|
+
], XmBubble.prototype, "attachments", void 0);
|
|
337
|
+
__decorate([
|
|
338
|
+
property({ attribute: false })
|
|
339
|
+
], XmBubble.prototype, "artifact", void 0);
|
|
340
|
+
__decorate([
|
|
341
|
+
property({ type: Boolean })
|
|
342
|
+
], XmBubble.prototype, "download", void 0);
|
|
343
|
+
__decorate([
|
|
344
|
+
property({ type: String })
|
|
345
|
+
], XmBubble.prototype, "text", void 0);
|
|
346
|
+
__decorate([
|
|
347
|
+
property({ type: String, attribute: "body-html" })
|
|
348
|
+
], XmBubble.prototype, "bodyHtml", void 0);
|
|
349
|
+
XmBubble = __decorate([
|
|
350
|
+
customElement("xm-bubble")
|
|
351
|
+
], XmBubble);
|
|
352
|
+
/* ─────────────────────────────────────────────────────────────
|
|
353
|
+
<xm-bubble-actions> — hover-reveal toolbar.
|
|
354
|
+
|
|
355
|
+
Authoring:
|
|
356
|
+
<xm-bubble-actions ts="00:03" aria-label="Message actions">
|
|
357
|
+
<button aria-label="Regenerate"><xm-regenerate-icon></xm-regenerate-icon></button>
|
|
358
|
+
<button aria-label="Copy"><xm-copy-icon></xm-copy-icon></button>
|
|
359
|
+
</xm-bubble-actions>
|
|
360
|
+
|
|
361
|
+
Walks children on connect; <button> children get .bubble-actions__btn
|
|
362
|
+
appended (mirrors the React BubbleActions walker).
|
|
363
|
+
─────────────────────────────────────────────────────────────*/
|
|
364
|
+
let XmBubbleActions = class XmBubbleActions extends LightElement {
|
|
365
|
+
constructor() {
|
|
366
|
+
super(...arguments);
|
|
367
|
+
this.ts = "";
|
|
368
|
+
this.ariaLabel = "Message actions";
|
|
369
|
+
this._authored = [];
|
|
370
|
+
this._copyResetTimer = null;
|
|
371
|
+
this._stragglerObserver = null;
|
|
372
|
+
this._onDelegatedClick = async (e) => {
|
|
373
|
+
const target = e.target;
|
|
374
|
+
if (!target)
|
|
375
|
+
return;
|
|
376
|
+
const btn = target.closest('xm-button[aria-label="Copy message"]');
|
|
377
|
+
if (!btn || !this.contains(btn))
|
|
378
|
+
return;
|
|
379
|
+
// Find the sibling xm-bubble inside the parent xm-bubble-group.
|
|
380
|
+
const group = this.parentElement;
|
|
381
|
+
const bubble = group?.querySelector("xm-bubble");
|
|
382
|
+
const text = (bubble?.textContent || "").trim();
|
|
383
|
+
if (!text)
|
|
384
|
+
return;
|
|
385
|
+
const ok = await writeClipboard(text);
|
|
386
|
+
if (!ok)
|
|
387
|
+
return;
|
|
388
|
+
this._showCopiedFeedback(btn);
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
connectedCallback() {
|
|
392
|
+
super.connectedCallback();
|
|
393
|
+
this._harvestAuthored(Array.from(this.childNodes));
|
|
394
|
+
// Lit-template parents may insert authored children AFTER connectedCallback
|
|
395
|
+
// (the host element is appended before its child parts are filled). Watch
|
|
396
|
+
// for direct-child additions that aren't part of our rendered shell and
|
|
397
|
+
// harvest them too, so chat-shell-rendered actions reach data-mount="actions".
|
|
398
|
+
this._stragglerObserver = new MutationObserver((records) => {
|
|
399
|
+
const stragglers = [];
|
|
400
|
+
const seen = new Set();
|
|
401
|
+
for (const r of records) {
|
|
402
|
+
for (const node of Array.from(r.addedNodes)) {
|
|
403
|
+
if (this._isRenderedShell(node))
|
|
404
|
+
continue;
|
|
405
|
+
if (seen.has(node))
|
|
406
|
+
continue;
|
|
407
|
+
seen.add(node);
|
|
408
|
+
stragglers.push(node);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
if (stragglers.length)
|
|
412
|
+
this._harvestAuthored(stragglers);
|
|
413
|
+
});
|
|
414
|
+
this._stragglerObserver.observe(this, { childList: true });
|
|
415
|
+
// Delegated copy handler for static-preview authoring (bubbles.html etc.)
|
|
416
|
+
// where no parent component wires @click on the copy button. The chat
|
|
417
|
+
// shell stops propagation in its own handler, so this never double-fires
|
|
418
|
+
// for shell-rendered rows.
|
|
419
|
+
this.addEventListener("click", this._onDelegatedClick);
|
|
420
|
+
}
|
|
421
|
+
disconnectedCallback() {
|
|
422
|
+
super.disconnectedCallback();
|
|
423
|
+
this.removeEventListener("click", this._onDelegatedClick);
|
|
424
|
+
if (this._stragglerObserver) {
|
|
425
|
+
this._stragglerObserver.disconnect();
|
|
426
|
+
this._stragglerObserver = null;
|
|
427
|
+
}
|
|
428
|
+
if (this._copyResetTimer != null) {
|
|
429
|
+
window.clearTimeout(this._copyResetTimer);
|
|
430
|
+
this._copyResetTimer = null;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
_isRenderedShell(node) {
|
|
434
|
+
return (node.nodeType === Node.ELEMENT_NODE &&
|
|
435
|
+
node.classList?.contains("bubble-actions"));
|
|
436
|
+
}
|
|
437
|
+
_harvestAuthored(nodes) {
|
|
438
|
+
const harvested = [];
|
|
439
|
+
for (const n of nodes) {
|
|
440
|
+
if (this._isRenderedShell(n))
|
|
441
|
+
continue;
|
|
442
|
+
if (n.nodeType === Node.ELEMENT_NODE) {
|
|
443
|
+
harvested.push(n);
|
|
444
|
+
}
|
|
445
|
+
else if (n.nodeType === Node.TEXT_NODE && n.textContent?.trim()) {
|
|
446
|
+
harvested.push(n);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
if (!harvested.length)
|
|
450
|
+
return;
|
|
451
|
+
this._authored.push(...harvested);
|
|
452
|
+
const host = this.querySelector('[data-mount="actions"]');
|
|
453
|
+
if (host) {
|
|
454
|
+
for (const n of harvested)
|
|
455
|
+
host.appendChild(n);
|
|
456
|
+
}
|
|
457
|
+
else {
|
|
458
|
+
for (const n of harvested) {
|
|
459
|
+
if (n.parentNode === this)
|
|
460
|
+
this.removeChild(n);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
_showCopiedFeedback(btn) {
|
|
465
|
+
// Don't stringify/re-parse the button's children — the original icon is
|
|
466
|
+
// a live custom element (e.g. <xm-copy-icon>) whose children were filled
|
|
467
|
+
// by Lit on upgrade. Setting innerHTML back would create a fresh empty
|
|
468
|
+
// instance that Lit re-renders into, leaving two stacked icons. Instead,
|
|
469
|
+
// hide the original icon nodes and append a temporary check span next to
|
|
470
|
+
// them; on reset, drop the span and unhide the originals.
|
|
471
|
+
const originalLabel = btn.getAttribute("aria-label") || "Copy message";
|
|
472
|
+
const originalTitle = btn.getAttribute("title");
|
|
473
|
+
const hidden = [];
|
|
474
|
+
for (const child of Array.from(btn.children)) {
|
|
475
|
+
const el = child;
|
|
476
|
+
hidden.push({ el, prev: el.style.display });
|
|
477
|
+
el.style.display = "none";
|
|
478
|
+
}
|
|
479
|
+
const checkSpan = document.createElement("span");
|
|
480
|
+
checkSpan.setAttribute("data-copy-check", "");
|
|
481
|
+
checkSpan.style.display = "inline-flex";
|
|
482
|
+
checkSpan.innerHTML = CHECK_ICON_SVG;
|
|
483
|
+
btn.appendChild(checkSpan);
|
|
484
|
+
btn.setAttribute("aria-label", "Copied");
|
|
485
|
+
btn.setAttribute("title", "Copied");
|
|
486
|
+
if (this._copyResetTimer != null)
|
|
487
|
+
window.clearTimeout(this._copyResetTimer);
|
|
488
|
+
this._copyResetTimer = window.setTimeout(() => {
|
|
489
|
+
checkSpan.remove();
|
|
490
|
+
for (const { el, prev } of hidden)
|
|
491
|
+
el.style.display = prev;
|
|
492
|
+
btn.setAttribute("aria-label", originalLabel);
|
|
493
|
+
if (originalTitle == null)
|
|
494
|
+
btn.removeAttribute("title");
|
|
495
|
+
else
|
|
496
|
+
btn.setAttribute("title", originalTitle);
|
|
497
|
+
this._copyResetTimer = null;
|
|
498
|
+
}, 1200);
|
|
499
|
+
}
|
|
500
|
+
render() {
|
|
501
|
+
return html `
|
|
502
|
+
<div
|
|
503
|
+
class="bubble-actions"
|
|
504
|
+
role="toolbar"
|
|
505
|
+
aria-label="${this.ariaLabel}"
|
|
506
|
+
>
|
|
507
|
+
${this.ts
|
|
508
|
+
? html `<span class="bubble-actions__ts">${this.ts}</span>`
|
|
509
|
+
: nothing}
|
|
510
|
+
<span data-mount="actions" style="display:contents"></span>
|
|
511
|
+
</div>
|
|
512
|
+
`;
|
|
513
|
+
}
|
|
514
|
+
updated() {
|
|
515
|
+
const host = this.querySelector('[data-mount="actions"]');
|
|
516
|
+
if (!host)
|
|
517
|
+
return;
|
|
518
|
+
if (host.childNodes.length === 0 && this._authored.length) {
|
|
519
|
+
for (const n of this._authored)
|
|
520
|
+
host.appendChild(n);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
__decorate([
|
|
525
|
+
property({ type: String })
|
|
526
|
+
], XmBubbleActions.prototype, "ts", void 0);
|
|
527
|
+
__decorate([
|
|
528
|
+
property({ type: String, attribute: "aria-label" })
|
|
529
|
+
], XmBubbleActions.prototype, "ariaLabel", void 0);
|
|
530
|
+
XmBubbleActions = __decorate([
|
|
531
|
+
customElement("xm-bubble-actions")
|
|
532
|
+
], XmBubbleActions);
|
|
533
|
+
/* ─────────────────────────────────────────────────────────────
|
|
534
|
+
<xm-artifact-chip> — standalone chip for surfaces other than
|
|
535
|
+
xm-bubble (e.g. snackbar, drawer). xm-bubble renders the same
|
|
536
|
+
markup inline, but exposing this as its own element lets pages
|
|
537
|
+
compose it without an enclosing bubble.
|
|
538
|
+
─────────────────────────────────────────────────────────────*/
|
|
539
|
+
let XmArtifactChip = class XmArtifactChip extends LightElement {
|
|
540
|
+
constructor() {
|
|
541
|
+
super(...arguments);
|
|
542
|
+
this.artifact = null;
|
|
543
|
+
this.download = false;
|
|
544
|
+
this._open = () => {
|
|
545
|
+
if (!this.artifact)
|
|
546
|
+
return;
|
|
547
|
+
this.dispatchEvent(new CustomEvent("artifact-open", {
|
|
548
|
+
detail: { artifact: this.artifact },
|
|
549
|
+
bubbles: true,
|
|
550
|
+
composed: true,
|
|
551
|
+
}));
|
|
552
|
+
};
|
|
553
|
+
this._download = (e) => {
|
|
554
|
+
e.stopPropagation();
|
|
555
|
+
this.dispatchEvent(new CustomEvent("artifact-download", {
|
|
556
|
+
detail: { artifact: this.artifact },
|
|
557
|
+
bubbles: true,
|
|
558
|
+
composed: true,
|
|
559
|
+
}));
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
render() {
|
|
563
|
+
const a = this.artifact;
|
|
564
|
+
if (!a)
|
|
565
|
+
return nothing;
|
|
566
|
+
return html `
|
|
567
|
+
<div
|
|
568
|
+
role="button"
|
|
569
|
+
tabindex="0"
|
|
570
|
+
class="att-card att-card--text att-card--text-row"
|
|
571
|
+
title="Open ${a.title}"
|
|
572
|
+
@click=${this._open}
|
|
573
|
+
@keydown=${(e) => {
|
|
574
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
575
|
+
e.preventDefault();
|
|
576
|
+
this._open();
|
|
577
|
+
}
|
|
578
|
+
}}
|
|
579
|
+
>
|
|
580
|
+
<span class="att-card__icon" aria-hidden="true">
|
|
581
|
+
<svg width="18" height="18" viewBox="0 0 24 24"
|
|
582
|
+
fill="none" stroke="currentColor" stroke-width="1.6"
|
|
583
|
+
stroke-linecap="round" stroke-linejoin="round">
|
|
584
|
+
<path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z" />
|
|
585
|
+
<path d="M14 3v5h5" />
|
|
586
|
+
</svg>
|
|
587
|
+
</span>
|
|
588
|
+
<span class="att-card__body">
|
|
589
|
+
<span class="att-card__name">${a.title}</span>
|
|
590
|
+
${a.sub?.length
|
|
591
|
+
? html `<span class="att-card__sub">
|
|
592
|
+
${a.sub.map((s, i) => html `
|
|
593
|
+
${i > 0 ? html `<span class="ds-sep">·</span>` : nothing}
|
|
594
|
+
<span>${s}</span>
|
|
595
|
+
`)}
|
|
596
|
+
</span>`
|
|
597
|
+
: nothing}
|
|
598
|
+
</span>
|
|
599
|
+
${this.download
|
|
600
|
+
? html `<span class="att-card__action" @click=${(e) => e.stopPropagation()}>
|
|
601
|
+
<xm-button variant="outline-canvas" size="md"
|
|
602
|
+
@click=${this._download}>Download</xm-button>
|
|
603
|
+
</span>`
|
|
604
|
+
: nothing}
|
|
605
|
+
</div>
|
|
606
|
+
`;
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
__decorate([
|
|
610
|
+
property({ attribute: false })
|
|
611
|
+
], XmArtifactChip.prototype, "artifact", void 0);
|
|
612
|
+
__decorate([
|
|
613
|
+
property({ type: Boolean })
|
|
614
|
+
], XmArtifactChip.prototype, "download", void 0);
|
|
615
|
+
XmArtifactChip = __decorate([
|
|
616
|
+
customElement("xm-artifact-chip")
|
|
617
|
+
], XmArtifactChip);
|