@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.
Files changed (175) hide show
  1. package/README.md +472 -0
  2. package/assets/brand-lockup-dark.svg +9 -0
  3. package/assets/brand-lockup-light.svg +9 -0
  4. package/assets/brand-mark.svg +9 -0
  5. package/colors_and_type.css +11 -0
  6. package/dist/lit/components/alert/index.css +201 -0
  7. package/dist/lit/components/alert/index.d.ts +25 -0
  8. package/dist/lit/components/alert/index.js +191 -0
  9. package/dist/lit/components/app-bar/index.css +80 -0
  10. package/dist/lit/components/app-bar/index.d.ts +19 -0
  11. package/dist/lit/components/app-bar/index.js +120 -0
  12. package/dist/lit/components/artifact/index.css +166 -0
  13. package/dist/lit/components/artifact/index.d.ts +37 -0
  14. package/dist/lit/components/artifact/index.js +294 -0
  15. package/dist/lit/components/autocomplete/index.css +171 -0
  16. package/dist/lit/components/autocomplete/index.d.ts +47 -0
  17. package/dist/lit/components/autocomplete/index.js +404 -0
  18. package/dist/lit/components/avatar/index.css +62 -0
  19. package/dist/lit/components/avatar/index.d.ts +19 -0
  20. package/dist/lit/components/avatar/index.js +112 -0
  21. package/dist/lit/components/avatar-group/index.css +60 -0
  22. package/dist/lit/components/avatar-group/index.d.ts +19 -0
  23. package/dist/lit/components/avatar-group/index.js +97 -0
  24. package/dist/lit/components/badge/index.css +72 -0
  25. package/dist/lit/components/badge/index.d.ts +18 -0
  26. package/dist/lit/components/badge/index.js +115 -0
  27. package/dist/lit/components/brand-mark/index.css +109 -0
  28. package/dist/lit/components/brand-mark/index.d.ts +24 -0
  29. package/dist/lit/components/brand-mark/index.js +116 -0
  30. package/dist/lit/components/breadcrumbs/index.css +91 -0
  31. package/dist/lit/components/breadcrumbs/index.d.ts +19 -0
  32. package/dist/lit/components/breadcrumbs/index.js +104 -0
  33. package/dist/lit/components/bubble/index.css +182 -0
  34. package/dist/lit/components/bubble/index.d.ts +72 -0
  35. package/dist/lit/components/bubble/index.js +617 -0
  36. package/dist/lit/components/button/index.css +342 -0
  37. package/dist/lit/components/button/index.d.ts +32 -0
  38. package/dist/lit/components/button/index.js +202 -0
  39. package/dist/lit/components/card/index.css +99 -0
  40. package/dist/lit/components/card/index.d.ts +20 -0
  41. package/dist/lit/components/card/index.js +133 -0
  42. package/dist/lit/components/chat/index.css +292 -0
  43. package/dist/lit/components/chat/index.d.ts +74 -0
  44. package/dist/lit/components/chat/index.js +589 -0
  45. package/dist/lit/components/checkbox/index.css +126 -0
  46. package/dist/lit/components/checkbox/index.d.ts +21 -0
  47. package/dist/lit/components/checkbox/index.js +138 -0
  48. package/dist/lit/components/chip/index.css +145 -0
  49. package/dist/lit/components/chip/index.d.ts +30 -0
  50. package/dist/lit/components/chip/index.js +230 -0
  51. package/dist/lit/components/chip-group/index.css +19 -0
  52. package/dist/lit/components/chip-group/index.d.ts +24 -0
  53. package/dist/lit/components/chip-group/index.js +171 -0
  54. package/dist/lit/components/code/index.css +42 -0
  55. package/dist/lit/components/code/index.d.ts +12 -0
  56. package/dist/lit/components/code/index.js +68 -0
  57. package/dist/lit/components/composer/index.css +548 -0
  58. package/dist/lit/components/composer/index.d.ts +67 -0
  59. package/dist/lit/components/composer/index.js +713 -0
  60. package/dist/lit/components/data-table/index.css +166 -0
  61. package/dist/lit/components/data-table/index.d.ts +55 -0
  62. package/dist/lit/components/data-table/index.js +390 -0
  63. package/dist/lit/components/dialog/index.css +124 -0
  64. package/dist/lit/components/dialog/index.d.ts +24 -0
  65. package/dist/lit/components/dialog/index.js +199 -0
  66. package/dist/lit/components/divider/index.css +27 -0
  67. package/dist/lit/components/divider/index.d.ts +13 -0
  68. package/dist/lit/components/divider/index.js +67 -0
  69. package/dist/lit/components/empty-state/index.css +69 -0
  70. package/dist/lit/components/empty-state/index.d.ts +21 -0
  71. package/dist/lit/components/empty-state/index.js +123 -0
  72. package/dist/lit/components/expansion-panel/index.css +120 -0
  73. package/dist/lit/components/expansion-panel/index.d.ts +22 -0
  74. package/dist/lit/components/expansion-panel/index.js +174 -0
  75. package/dist/lit/components/field/index.css +223 -0
  76. package/dist/lit/components/field/index.d.ts +106 -0
  77. package/dist/lit/components/field/index.js +388 -0
  78. package/dist/lit/components/file-input/index.css +257 -0
  79. package/dist/lit/components/file-input/index.d.ts +30 -0
  80. package/dist/lit/components/file-input/index.js +298 -0
  81. package/dist/lit/components/form/index.css +29 -0
  82. package/dist/lit/components/form/index.d.ts +38 -0
  83. package/dist/lit/components/form/index.js +192 -0
  84. package/dist/lit/components/grid/index.css +53 -0
  85. package/dist/lit/components/grid/index.d.ts +14 -0
  86. package/dist/lit/components/grid/index.js +82 -0
  87. package/dist/lit/components/kbd/index.css +35 -0
  88. package/dist/lit/components/kbd/index.d.ts +11 -0
  89. package/dist/lit/components/kbd/index.js +43 -0
  90. package/dist/lit/components/list/index.css +15 -0
  91. package/dist/lit/components/list/index.d.ts +28 -0
  92. package/dist/lit/components/list/index.js +188 -0
  93. package/dist/lit/components/list-item/index.css +119 -0
  94. package/dist/lit/components/list-item/index.d.ts +20 -0
  95. package/dist/lit/components/list-item/index.js +127 -0
  96. package/dist/lit/components/menu/index.css +94 -0
  97. package/dist/lit/components/menu/index.d.ts +47 -0
  98. package/dist/lit/components/menu/index.js +386 -0
  99. package/dist/lit/components/navigation-drawer/index.css +114 -0
  100. package/dist/lit/components/navigation-drawer/index.d.ts +29 -0
  101. package/dist/lit/components/navigation-drawer/index.js +218 -0
  102. package/dist/lit/components/overlay/index.css +171 -0
  103. package/dist/lit/components/overlay/index.d.ts +65 -0
  104. package/dist/lit/components/overlay/index.js +566 -0
  105. package/dist/lit/components/pagination/index.css +102 -0
  106. package/dist/lit/components/pagination/index.d.ts +22 -0
  107. package/dist/lit/components/pagination/index.js +184 -0
  108. package/dist/lit/components/primitives/index.css +504 -0
  109. package/dist/lit/components/primitives/index.d.ts +25 -0
  110. package/dist/lit/components/primitives/index.js +283 -0
  111. package/dist/lit/components/progress/index.css +143 -0
  112. package/dist/lit/components/progress/index.d.ts +23 -0
  113. package/dist/lit/components/progress/index.js +180 -0
  114. package/dist/lit/components/radio-group/index.css +178 -0
  115. package/dist/lit/components/radio-group/index.d.ts +35 -0
  116. package/dist/lit/components/radio-group/index.js +292 -0
  117. package/dist/lit/components/select/index.css +151 -0
  118. package/dist/lit/components/select/index.d.ts +50 -0
  119. package/dist/lit/components/select/index.js +390 -0
  120. package/dist/lit/components/sidebar-item/index.css +133 -0
  121. package/dist/lit/components/sidebar-item/index.d.ts +20 -0
  122. package/dist/lit/components/sidebar-item/index.js +105 -0
  123. package/dist/lit/components/skeleton/index.css +81 -0
  124. package/dist/lit/components/skeleton/index.d.ts +19 -0
  125. package/dist/lit/components/skeleton/index.js +119 -0
  126. package/dist/lit/components/slider/index.css +171 -0
  127. package/dist/lit/components/slider/index.d.ts +36 -0
  128. package/dist/lit/components/slider/index.js +302 -0
  129. package/dist/lit/components/snackbar/index.css +279 -0
  130. package/dist/lit/components/snackbar/index.d.ts +33 -0
  131. package/dist/lit/components/snackbar/index.js +195 -0
  132. package/dist/lit/components/stack/index.css +41 -0
  133. package/dist/lit/components/stack/index.d.ts +20 -0
  134. package/dist/lit/components/stack/index.js +103 -0
  135. package/dist/lit/components/switch/index.css +126 -0
  136. package/dist/lit/components/switch/index.d.ts +17 -0
  137. package/dist/lit/components/switch/index.js +116 -0
  138. package/dist/lit/components/table/index.css +85 -0
  139. package/dist/lit/components/table/index.d.ts +25 -0
  140. package/dist/lit/components/table/index.js +139 -0
  141. package/dist/lit/components/tabs/index.css +116 -0
  142. package/dist/lit/components/tabs/index.d.ts +49 -0
  143. package/dist/lit/components/tabs/index.js +320 -0
  144. package/dist/lit/components/text-field/index.css +90 -0
  145. package/dist/lit/components/text-field/index.d.ts +17 -0
  146. package/dist/lit/components/text-field/index.js +101 -0
  147. package/dist/lit/components/textarea/index.css +55 -0
  148. package/dist/lit/components/textarea/index.d.ts +26 -0
  149. package/dist/lit/components/textarea/index.js +124 -0
  150. package/dist/lit/components/tooltip/index.css +37 -0
  151. package/dist/lit/components/tooltip/index.d.ts +31 -0
  152. package/dist/lit/components/tooltip/index.js +196 -0
  153. package/dist/lit/components/validation/index.css +386 -0
  154. package/dist/lit/components/validation/index.d.ts +45 -0
  155. package/dist/lit/components/validation/index.js +318 -0
  156. package/dist/lit/index.d.ts +50 -0
  157. package/dist/lit/index.js +59 -0
  158. package/package.json +81 -0
  159. package/styles/README.md +346 -0
  160. package/styles/_elevation.css +24 -0
  161. package/styles/_fonts.css +6 -0
  162. package/styles/_layout.css +37 -0
  163. package/styles/_primitives.css +154 -0
  164. package/styles/_scroll.css +75 -0
  165. package/styles/_semantic.css +146 -0
  166. package/styles/_space.css +61 -0
  167. package/styles/_type.css +139 -0
  168. package/styles/_xmesh-extensions.css +232 -0
  169. package/styles/index.css +44 -0
  170. package/styles/md3/_color.css +102 -0
  171. package/styles/md3/_elevation.css +26 -0
  172. package/styles/md3/_motion.css +35 -0
  173. package/styles/md3/_shape.css +22 -0
  174. package/styles/md3/_state.css +22 -0
  175. 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);