@nonoun/native-chat 0.5.3 → 0.5.8

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 ADDED
@@ -0,0 +1,332 @@
1
+ # @nonoun/native-chat
2
+
3
+ Chat UI components for `@nonoun/native-ui` — message feed, composer input, streaming transport, and a top-level panel with imperative host-integration APIs.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @nonoun/native-chat @nonoun/native-ui
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```html
14
+ <link rel="stylesheet" href="node_modules/@nonoun/native-ui/dist/native-ui.css" />
15
+ <link rel="stylesheet" href="node_modules/@nonoun/native-chat/dist/native-chat.css" />
16
+
17
+ <script type="module">
18
+ import '@nonoun/native-ui/register';
19
+ import '@nonoun/native-chat/register';
20
+ </script>
21
+
22
+ <native-chat-panel auto-focus-policy="open-request">
23
+ <!-- panel stamps its own children: header, feed, composer -->
24
+ </native-chat-panel>
25
+ ```
26
+
27
+ ## Components
28
+
29
+ | Element | Description |
30
+ |---------|-------------|
31
+ | `native-chat-panel` | Top-level panel — owns lifecycle, focus policy, header actions |
32
+ | `n-chat-feed` | Scrollable message feed with optional virtualization |
33
+ | `n-chat-messages` | Message list wrapper (inside feed) |
34
+ | `n-chat-message` | Single message container with role, status, actions |
35
+ | `n-chat-message-text` | Markdown-rendered text bubble |
36
+ | `n-chat-message-activity` | Typing / system activity indicator |
37
+ | `n-chat-message-seed` | Seed prompt card |
38
+ | `n-chat-message-genui` | Generative UI node renderer |
39
+ | `n-chat-avatar` | Message avatar |
40
+ | `n-chat-input` | Composer with submit, formatting, slash commands |
41
+ | `n-chat-input-structured` | Multi-option structured input picker |
42
+
43
+ ---
44
+
45
+ ## Embedding Guide
46
+
47
+ ### Panel Host API
48
+
49
+ `native-chat-panel` exposes three imperative methods for host orchestration:
50
+
51
+ #### `open(options?)`
52
+
53
+ ```ts
54
+ panel.open();
55
+ panel.open({ focusComposer: true, reason: 'deeplink' });
56
+ ```
57
+
58
+ | Option | Type | Default | Description |
59
+ |--------|------|---------|-------------|
60
+ | `focusComposer` | `boolean` | `false` | Request composer focus after opening |
61
+ | `reason` | `string` | — | Why the panel opened (telemetry / debugging) |
62
+
63
+ Sets the `[open]` attribute. Idempotent — calling `open()` when already open does not re-emit events.
64
+
65
+ #### `close(reason?)`
66
+
67
+ ```ts
68
+ panel.close();
69
+ panel.close('user-dismiss');
70
+ ```
71
+
72
+ Removes the `[open]` attribute. Idempotent.
73
+
74
+ #### `focusComposer(options?, by?)`
75
+
76
+ ```ts
77
+ panel.focusComposer();
78
+ panel.focusComposer({ cursor: 'end' }, 'api');
79
+ ```
80
+
81
+ | Option | Type | Default | Description |
82
+ |--------|------|---------|-------------|
83
+ | `cursor` | `'start' \| 'end' \| 'preserve'` | `'end'` | Caret placement after focus |
84
+ | `by` | `'api' \| 'user' \| 'policy'` | `'api'` | Focus source (included in events) |
85
+
86
+ Retries up to 3 times via microtask if the composer is not yet available or disabled. On success dispatches `native:composer-focused`; on failure dispatches `native:composer-focus-failed`.
87
+
88
+ ---
89
+
90
+ ### Lifecycle Events
91
+
92
+ All events bubble and are composed (cross shadow DOM). Listen on the panel or any ancestor:
93
+
94
+ ```ts
95
+ const panel = document.querySelector('native-chat-panel');
96
+
97
+ panel.addEventListener('native:chat-opened', (e) => {
98
+ console.log('opened', e.detail.source, e.detail.focusComposer);
99
+ });
100
+
101
+ panel.addEventListener('native:chat-closed', (e) => {
102
+ console.log('closed', e.detail.reason);
103
+ });
104
+
105
+ panel.addEventListener('native:send', (e) => {
106
+ console.log('user sent:', e.detail.value);
107
+ // Call e.preventDefault() to block auto-clear
108
+ });
109
+ ```
110
+
111
+ | Event | Detail | Notes |
112
+ |-------|--------|-------|
113
+ | `native:chat-opened` | `{ source?: string, focusComposer: boolean }` | After panel opens |
114
+ | `native:chat-closed` | `{ reason?: string }` | After panel closes |
115
+ | `native:composer-focused` | `{ by: 'api' \| 'user' \| 'policy' }` | Composer received focus |
116
+ | `native:composer-focus-failed` | `{ reason: string, attempts: number }` | All focus retries exhausted |
117
+ | `native:send` | `{ value: string }` | User submitted message (**cancelable**) |
118
+ | `native:chat-stop` | — | User clicked stop button |
119
+ | `native:chat-restart` | — | User clicked restart button |
120
+ | `native:message-action` | `{ action: string, messageId: string }` | Message action triggered |
121
+
122
+ ---
123
+
124
+ ### Focus Policy
125
+
126
+ The `auto-focus-policy` attribute controls when the composer auto-focuses:
127
+
128
+ | Value | Behavior |
129
+ |-------|----------|
130
+ | `open-request` (default) | Focus composer only when `open({ focusComposer: true })` is called |
131
+ | `ready` | Auto-focus once at panel initialization |
132
+ | `never` | Never auto-focus — host must call `focusComposer()` explicitly |
133
+
134
+ **Recommended default:** `open-request` — gives the host full control over focus timing.
135
+
136
+ #### Focus Failure Handling
137
+
138
+ When `focusComposer()` cannot reach the textarea (not rendered, disabled, or blocked), the panel emits `native:composer-focus-failed` after 3 retry attempts:
139
+
140
+ ```ts
141
+ panel.addEventListener('native:composer-focus-failed', (e) => {
142
+ const { reason, attempts } = e.detail;
143
+ // reason: 'composer-unavailable' | 'disabled' | 'blocked'
144
+ // Show a fallback hint or retry button
145
+ });
146
+ ```
147
+
148
+ **Recommended UX:** Keep the panel open, show a subtle status hint, and let the user retry manually.
149
+
150
+ ---
151
+
152
+ ### Host Orchestration Patterns
153
+
154
+ #### Deeplink
155
+
156
+ ```ts
157
+ // Parse URL: ?openChat=1
158
+ if (new URLSearchParams(location.search).has('openChat')) {
159
+ panel.open({ focusComposer: true, reason: 'deeplink' });
160
+ }
161
+ ```
162
+
163
+ #### Notification or External Action
164
+
165
+ ```ts
166
+ notificationButton.addEventListener('click', () => {
167
+ panel.open({ focusComposer: false, reason: 'notification' });
168
+ });
169
+ ```
170
+
171
+ #### Intercepting Send
172
+
173
+ ```ts
174
+ panel.addEventListener('native:send', (e) => {
175
+ const { value } = e.detail;
176
+ if (!value.trim()) {
177
+ e.preventDefault(); // block auto-clear, ignore empty
178
+ return;
179
+ }
180
+ myTransport.send(value);
181
+ });
182
+ ```
183
+
184
+ ---
185
+
186
+ ### Transport Error & Retry
187
+
188
+ `createChatTransport()` provides a streaming transport layer with built-in error classification and retry logic.
189
+
190
+ #### Transport States
191
+
192
+ ```
193
+ idle → sending → streaming → ready
194
+ ↘ retrying → sending (retry loop)
195
+ ↘ rate-limited (429)
196
+ ↘ auth-required (401/403)
197
+ ↘ server-error (5xx / network)
198
+ ↘ offline (DNS / TypeError)
199
+ ```
200
+
201
+ #### Usage
202
+
203
+ ```ts
204
+ import { createChatTransport } from '@nonoun/native-chat';
205
+
206
+ const transport = createChatTransport({
207
+ baseUrl: '/api/chat',
208
+ format: 'sse',
209
+ retry: { maxAttempts: 3, baseDelayMs: 1000, maxDelayMs: 30000 },
210
+ onStateChange(status) {
211
+ // status: { state, statusCode?, retryInMs?, attempt?, maxAttempts?, error? }
212
+ switch (status.state) {
213
+ case 'sending': showSpinner(); break;
214
+ case 'streaming': hideSpinner(); break;
215
+ case 'retrying': showRetryBanner(status.retryInMs); break;
216
+ case 'rate-limited': showRateLimitNotice(status.retryInMs); break;
217
+ case 'auth-required': redirectToLogin(); break;
218
+ case 'server-error': showErrorBanner(status.error); break;
219
+ case 'offline': showOfflineBanner(); break;
220
+ case 'ready': clearAllBanners(); break;
221
+ }
222
+ },
223
+ });
224
+ ```
225
+
226
+ #### Defaults & Behavior
227
+
228
+ | Behavior | Default |
229
+ |----------|---------|
230
+ | Retry | Disabled (`maxAttempts: 1`) |
231
+ | Retryable errors | 429 (rate-limited), 5xx (server error) |
232
+ | Non-retryable | 401/403 (auth-required) — never retried |
233
+ | 429 delay | Respects `Retry-After` header (seconds or HTTP-date) |
234
+ | Backoff | Exponential: `min(maxDelay, baseDelay * 2^attempt + jitter)` |
235
+ | Stream formats | `'sse'`, `'ndjson'`, `'json'` (auto-detected via `detectFormat()`) |
236
+
237
+ #### Stream End Classification
238
+
239
+ ```ts
240
+ import { classifyStreamEnd } from '@nonoun/native-chat';
241
+
242
+ // Returns: 'complete' | 'partial' | 'error' | 'stopped'
243
+ ```
244
+
245
+ | Reason | Meaning |
246
+ |--------|---------|
247
+ | `complete` | Server sent explicit done signal |
248
+ | `partial` | Stream ended without completion (truncated) |
249
+ | `error` | Transport or parse error |
250
+ | `stopped` | Consumer aborted (user clicked stop) |
251
+
252
+ Use `partial` status on messages to offer a "Continue" action.
253
+
254
+ ---
255
+
256
+ ### Message Actions
257
+
258
+ Messages show contextual action buttons (copy, regenerate, edit, feedback):
259
+
260
+ ```html
261
+ <n-chat-message role="assistant" actions="copy,regenerate,thumbs-up,thumbs-down">
262
+ <n-chat-message-text>Hello!</n-chat-message-text>
263
+ </n-chat-message>
264
+ ```
265
+
266
+ | Attribute | Values | Default |
267
+ |-----------|--------|---------|
268
+ | `actions` | Comma-separated action IDs, or `"none"` | Role-based defaults |
269
+ | `actions-style` | `'icon' \| 'label' \| 'icon-label'` | `'icon'` |
270
+ | `actions-position` | `'below' \| 'inside'` | `'below'` |
271
+
272
+ Listen for actions on any ancestor:
273
+
274
+ ```ts
275
+ panel.addEventListener('native:message-action', (e) => {
276
+ const { action, messageId } = e.detail;
277
+ if (action === 'copy') navigator.clipboard.writeText(getMessageText(messageId));
278
+ });
279
+ ```
280
+
281
+ ---
282
+
283
+ ### Feed Virtualization
284
+
285
+ For long transcripts, enable virtual scrolling on the feed:
286
+
287
+ ```html
288
+ <n-chat-feed virtual virtual-item-height="80" virtual-overscan="5">
289
+ <n-chat-messages>...</n-chat-messages>
290
+ </n-chat-feed>
291
+ ```
292
+
293
+ Only visible items (plus overscan buffer) are rendered. The feed emits `native:range-change` with `{ start, end, total }` as the viewport scrolls.
294
+
295
+ ---
296
+
297
+ ## Exports
298
+
299
+ ```ts
300
+ // Components
301
+ import {
302
+ NChatPanel, NChatInput, NChatFeed, NChatMessage,
303
+ NChatMessages, NChatMessageText, NChatMessageActivity,
304
+ NChatAvatar, NChatMessageSeed, NChatMessageGenUI,
305
+ NChatInputStructured,
306
+ } from '@nonoun/native-chat';
307
+
308
+ // Stream / Transport
309
+ import {
310
+ createChatTransport, createChatStream,
311
+ parseSSE, parseNDJSON, parseJSON, detectFormat,
312
+ classifyHttpError, classifyStreamEnd, backoffDelay,
313
+ } from '@nonoun/native-chat';
314
+
315
+ // Types
316
+ import type {
317
+ AutoFocusPolicy, ChatPanelOpenOptions, FocusComposerOptions,
318
+ ChatTransportOptions, TransportState, TransportStatus, RetryOptions,
319
+ ChatStreamEvent, ChatStreamChunk, StreamFormat, StreamEndReason,
320
+ } from '@nonoun/native-chat';
321
+
322
+ // Utilities
323
+ import { renderMarkdown, renderInline, sanitizeHtml } from '@nonoun/native-chat';
324
+ ```
325
+
326
+ ## Peer Dependency
327
+
328
+ Requires `@nonoun/native-ui >= 0.6.0`.
329
+
330
+ ## License
331
+
332
+ MIT
@@ -1,4 +1,4 @@
1
- import { NativeElement as e, VirtualScrollController as t, createDisabledEffect as n, signal as r } from "@nonoun/native-ui";
1
+ import { NativeElement as e, VirtualScrollController as t, createDisabledEffect as n, signal as r, uid as i } from "@nonoun/native-ui";
2
2
  /**
3
3
  * Chat message input with textarea, submit button, and Enter-to-send behavior.
4
4
  * @attr {boolean} disabled - Disables interaction
@@ -10,7 +10,7 @@ import { NativeElement as e, VirtualScrollController as t, createDisabledEffect
10
10
  * @fires native:composer-focus - Fired when the composer textarea gains focus
11
11
  * @fires native:composer-blur - Fired when the composer textarea loses focus
12
12
  */
13
- var i = class extends e {
13
+ var a = class extends e {
14
14
  static observedAttributes = ["disabled", "busy"];
15
15
  #e;
16
16
  #t = r(!1);
@@ -131,7 +131,7 @@ var i = class extends e {
131
131
  })) && !this.hasAttribute("no-auto-clear") && (this.value = "", this.#i?.setAttribute("disabled", ""));
132
132
  }
133
133
  }
134
- }, a = class extends e {
134
+ }, o = class extends e {
135
135
  static observedAttributes = [
136
136
  "show-stop",
137
137
  "show-restart",
@@ -224,7 +224,7 @@ var i = class extends e {
224
224
  let d = document.createElement("n-button");
225
225
  d.setAttribute("variant", "ghost"), d.setAttribute("inline", ""), d.innerHTML = "<n-icon name=\"microphone\"></n-icon>", l.appendChild(d);
226
226
  let f = document.createElement("n-button");
227
- f.setAttribute("variant", "primary"), f.setAttribute("intent", "accent"), f.setAttribute("radius", "round"), f.setAttribute("inline", ""), f.setAttribute("disabled", ""), f.classList.add("submit-btn"), f.innerHTML = "<n-icon name=\"arrow-up\"></n-icon>", l.appendChild(f), s.appendChild(l), o.appendChild(s), this.append(e, i, o), this.addEffect(() => {
227
+ f.setAttribute("variant", "primary"), f.setAttribute("intent", "accent"), f.setAttribute("radius", "round"), f.setAttribute("inline", ""), f.setAttribute("disabled", ""), f.dataset.role = "submit", f.innerHTML = "<n-icon name=\"arrow-up\"></n-icon>", l.appendChild(f), s.appendChild(l), o.appendChild(s), this.append(e, i, o), this.addEffect(() => {
228
228
  let e = this.#e.value;
229
229
  if (e && !this.#a) {
230
230
  let e = document.createElement("n-button");
@@ -342,7 +342,7 @@ var i = class extends e {
342
342
  composed: !0
343
343
  }));
344
344
  };
345
- }, o = 40, s = class extends e {
345
+ }, s = 40, c = class extends e {
346
346
  static observedAttributes = [
347
347
  "auto-scroll",
348
348
  "scrollable",
@@ -451,7 +451,7 @@ var i = class extends e {
451
451
  }
452
452
  }
453
453
  #f = () => {
454
- let e = this.#n.value, t = this.scrollTop + this.clientHeight >= this.scrollHeight - o;
454
+ let e = this.#n.value, t = this.scrollTop + this.clientHeight >= this.scrollHeight - s;
455
455
  this.#n.value = t, t !== e && this.dispatchEvent(new CustomEvent("native:feed-scroll", {
456
456
  bubbles: !0,
457
457
  composed: !0,
@@ -470,7 +470,7 @@ var i = class extends e {
470
470
  });
471
471
  };
472
472
  #m = (e) => {};
473
- }, c = class extends e {
473
+ }, l = class extends e {
474
474
  static observedAttributes = [
475
475
  "src",
476
476
  "name",
@@ -510,7 +510,7 @@ var i = class extends e {
510
510
  }
511
511
  if (n) {
512
512
  let e = document.createElement("span");
513
- e.className = "n-chat-avatar-initials", e.textContent = l(n), e.setAttribute("aria-hidden", "true"), this.appendChild(e);
513
+ e.className = "n-chat-avatar-initials", e.textContent = u(n), e.setAttribute("aria-hidden", "true"), this.appendChild(e);
514
514
  return;
515
515
  }
516
516
  let r = document.createElement("n-icon");
@@ -521,11 +521,11 @@ var i = class extends e {
521
521
  this.textContent = "", super.teardown();
522
522
  }
523
523
  };
524
- function l(e) {
524
+ function u(e) {
525
525
  let t = e.trim().split(/\s+/);
526
526
  return t.length === 0 ? "" : t.length === 1 ? t[0][0].toUpperCase() : (t[0][0] + t[t.length - 1][0]).toUpperCase();
527
527
  }
528
- const u = {
528
+ const d = {
529
529
  copy: {
530
530
  label: "Copy",
531
531
  icon: "copy"
@@ -550,7 +550,7 @@ const u = {
550
550
  label: "Continue",
551
551
  icon: "arrow-right"
552
552
  }
553
- }, d = {
553
+ }, f = {
554
554
  assistant: [
555
555
  "copy",
556
556
  "retry",
@@ -570,12 +570,12 @@ const u = {
570
570
  * @attr {string} timestamp - Epoch milliseconds
571
571
  * @attr {string} status - `sending` | `sent` | `error` | `streaming` | `partial`
572
572
  * @attr {string} actions - Comma-separated action list, or `"none"` to suppress
573
- * @attr {string} actions-style - `"label"` (default) | `"icon"` | `"icon-label"`
574
- * @attr {string} actions-position - `"inside"` (default) | `"below"` — toolbar placement
573
+ * @attr {string} actions-style - `"icon"` (default) | `"label"` | `"icon-label"`
574
+ * @attr {string} actions-position - `"below"` (default) | `"inside"` — toolbar placement
575
575
  * @fires native:message-action - Fired when an action button is clicked
576
576
  * @fires native:continue-request - Fired when continue is requested for a partial message
577
577
  */
578
- var f = class extends e {
578
+ var p = class extends e {
579
579
  static observedAttributes = [
580
580
  "role",
581
581
  "message-id",
@@ -589,9 +589,12 @@ var f = class extends e {
589
589
  #t = r("assistant");
590
590
  #n = r("sent");
591
591
  #r = r(null);
592
- #i = r("label");
593
- #a = r("inside");
592
+ #i = r("icon");
593
+ #a = r("below");
594
594
  #o = null;
595
+ #s = !1;
596
+ #c = 0;
597
+ #l = 0;
595
598
  constructor() {
596
599
  super(), this.#e = this.attachInternals();
597
600
  }
@@ -644,40 +647,73 @@ var f = class extends e {
644
647
  this.#r.value = n;
645
648
  break;
646
649
  case "actions-style":
647
- this.#i.value = n ?? "label";
650
+ this.#i.value = n ?? "icon";
648
651
  break;
649
652
  case "actions-position":
650
- this.#a.value = n ?? "inside";
653
+ this.#a.value = n ?? "below";
651
654
  break;
652
655
  }
653
656
  super.attributeChangedCallback(e, t, n);
654
657
  }
655
658
  }
656
659
  setup() {
657
- super.setup(), this.addEffect(() => {
660
+ super.setup(), this.addEventListener("pointerenter", this.#u), this.addEventListener("pointerleave", this.#d), this.addEffect(() => {
658
661
  let e = this.#t.value, t = this.#r.value, n = this.#i.value, r = this.#a.value, i = this.#n.value;
659
- this.#s(e, t, n, r, i);
660
- }), this.#e.role = "article", this.addEventListener("native:press", this.#c);
662
+ this.#p(e, t, n, r, i);
663
+ }), this.#e.role = "article";
661
664
  }
662
665
  teardown() {
663
- this.removeEventListener("native:press", this.#c), this.#o = null, super.teardown();
664
- }
665
- #s(e, t, n, r, i) {
666
- if (this.#o &&= (this.#o.remove(), null), t === "none" || this.querySelector("[slot=\"actions\"]")) return;
667
- let a;
668
- if (a = t ? t.split(",").map((e) => e.trim()).filter(Boolean) : d[e] ?? [], i === "partial" && !a.includes("continue") && (a = [...a, "continue"]), a.length === 0) return;
669
- let o = document.createElement("n-toolbar");
670
- o.className = "n-chat-message-actions", o.setAttribute("padding", "none"), o.setAttribute("aria-label", "Message actions"), n !== "label" && o.setAttribute("data-style", n);
671
- for (let e of a) {
672
- let t = u[e];
673
- t && o.appendChild(p(e, t, n));
666
+ this.removeEventListener("pointerenter", this.#u), this.removeEventListener("pointerleave", this.#d), clearTimeout(this.#c), clearTimeout(this.#l), this.#o &&= (this.#f(), null), super.teardown();
667
+ }
668
+ #u = () => {
669
+ clearTimeout(this.#l), !(!this.#o || !this.#s || this.#n.value === "partial") && (clearTimeout(this.#c), this.#c = window.setTimeout(() => {
670
+ try {
671
+ this.#o?.showPopover();
672
+ } catch {}
673
+ }, 300));
674
+ };
675
+ #d = () => {
676
+ !this.#s || this.#n.value === "partial" || (clearTimeout(this.#c), clearTimeout(this.#l), this.#l = window.setTimeout(() => {
677
+ try {
678
+ this.#o?.hidePopover();
679
+ } catch {}
680
+ }, 150));
681
+ };
682
+ #f() {
683
+ if (this.#o) {
684
+ if (this.#s) {
685
+ this.#o.removeEventListener("pointerenter", this.#u), this.#o.removeEventListener("pointerleave", this.#d), this.#o.removeEventListener("focusin", this.#u), this.#o.removeEventListener("focusout", this.#d);
686
+ try {
687
+ this.#o.hidePopover();
688
+ } catch {}
689
+ this.style.removeProperty("anchor-name"), this.#s = !1;
690
+ }
691
+ this.#o.removeEventListener("native:press", this.#m), this.#o.remove();
674
692
  }
675
- o.children.length !== 0 && (r === "below" ? this.after(o) : this.appendChild(o), this.#o = o);
676
693
  }
677
- #c = (e) => {
678
- let t = e.target, n = t?.getAttribute("data-action");
679
- if (n && this.#o?.contains(t)) {
680
- if (e.stopPropagation(), n === "continue") {
694
+ #p(e, t, n, r, a) {
695
+ if (this.#o &&= (this.#f(), null), clearTimeout(this.#l), t === "none" || this.querySelector("[slot=\"actions\"]")) return;
696
+ let o;
697
+ if (o = t ? t.split(",").map((e) => e.trim()).filter(Boolean) : f[e] ?? [], a === "partial" && !o.includes("continue") && (o = [...o, "continue"]), o.length === 0) return;
698
+ let s = document.createElement("n-toolbar");
699
+ s.dataset.role = "actions", s.setAttribute("padding", "tight"), s.setAttribute("aria-label", "Message actions"), n !== "label" && s.setAttribute("data-style", n);
700
+ for (let e of o) {
701
+ let t = d[e];
702
+ t && s.appendChild(m(e, t, n));
703
+ }
704
+ if (s.children.length !== 0) {
705
+ if (s.addEventListener("native:press", this.#m), r === "below") {
706
+ s.setAttribute("popover", "manual");
707
+ let e = i("msg");
708
+ this.style.setProperty("anchor-name", `--${e}`), s.style.setProperty("position-anchor", `--${e}`), this.appendChild(s), this.#s = !0, s.addEventListener("pointerenter", this.#u), s.addEventListener("pointerleave", this.#d), s.addEventListener("focusin", this.#u), s.addEventListener("focusout", this.#d), a === "partial" && s.showPopover();
709
+ } else this.appendChild(s);
710
+ this.#o = s;
711
+ }
712
+ }
713
+ #m = (e) => {
714
+ let t = e.target?.getAttribute("data-action");
715
+ if (t) {
716
+ if (e.stopPropagation(), t === "continue") {
681
717
  this.dispatchEvent(new CustomEvent("native:continue-request", {
682
718
  bubbles: !0,
683
719
  composed: !0,
@@ -689,14 +725,14 @@ var f = class extends e {
689
725
  bubbles: !0,
690
726
  composed: !0,
691
727
  detail: {
692
- action: n,
728
+ action: t,
693
729
  messageId: this.messageId
694
730
  }
695
731
  }));
696
732
  }
697
733
  };
698
734
  };
699
- function p(e, t, n) {
735
+ function m(e, t, n) {
700
736
  let r = document.createElement("n-button");
701
737
  if (r.setAttribute("variant", "ghost"), r.setAttribute("size", "sm"), r.setAttribute("inline", ""), r.setAttribute("data-action", e), r.setAttribute("aria-label", t.label), n === "icon" || n === "icon-label") {
702
738
  let e = document.createElement("n-icon");
@@ -707,13 +743,23 @@ function p(e, t, n) {
707
743
  /**
708
744
  * Message group — cluster of messages from the same sender.
709
745
  *
710
- * Provides alignment (user = right, assistant/system = left) and
711
- * groups an avatar + message column.
746
+ * Provides a 2×2 grid layout:
747
+ * - Col 1, row 1–2: avatar (bottom-aligned)
748
+ * - Col 2, row 1: context area (metadata, reasoning, timestamps)
749
+ * - Col 2, row 2: messages area (chat bubbles, flex column)
750
+ *
751
+ * On setup, non-avatar children are sorted into two wrapper divs:
752
+ * - `.n-chat-context` — non-message children (row 1)
753
+ * - `.n-chat-bubbles` — `n-chat-message` children (row 2)
754
+ *
755
+ * A MutationObserver routes dynamically added children into the
756
+ * correct wrapper automatically.
712
757
  *
713
758
  * ```html
714
- * <n-chat-messages role="user" sender="Kim">
715
- * <n-chat-avatar name="Kim"></n-chat-avatar>
716
- * <n-chat-message role="user" message-id="1">
759
+ * <n-chat-messages role="assistant" sender="AI">
760
+ * <n-chat-avatar>AI</n-chat-avatar>
761
+ * <span class="reasoning">Thinking...</span>
762
+ * <n-chat-message role="assistant">
717
763
  * <n-chat-message-text>Hello!</n-chat-message-text>
718
764
  * </n-chat-message>
719
765
  * </n-chat-messages>
@@ -721,15 +767,18 @@ function p(e, t, n) {
721
767
  *
722
768
  * @attr {string} role - `user` | `assistant` | `system`
723
769
  * @attr {string} sender - Display name of the sender
724
- * @attr {string} avatar-align - `"top"` (default) | `"center"` | `"bottom"` — avatar vertical alignment
770
+ * @attr {string} avatar-align - `"top"` | `"center"` | `"bottom"` (default) — avatar vertical alignment
725
771
  */
726
- var m = class extends e {
772
+ var h = class extends e {
727
773
  static observedAttributes = [
728
774
  "role",
729
775
  "sender",
730
776
  "avatar-align"
731
777
  ];
732
778
  #e;
779
+ #t = null;
780
+ #n = null;
781
+ #r = null;
733
782
  constructor() {
734
783
  super(), this.#e = this.attachInternals();
735
784
  }
@@ -739,12 +788,31 @@ var m = class extends e {
739
788
  setup() {
740
789
  super.setup(), this.#e.role = "group";
741
790
  let e = this.getAttribute("sender");
742
- e && (this.#e.ariaLabel = `Messages from ${e}`);
791
+ e && (this.#e.ariaLabel = `Messages from ${e}`), this.#i(), this.#r = new MutationObserver((e) => {
792
+ for (let t of e) for (let e of t.addedNodes) e instanceof Element && e.localName !== "n-chat-avatar" && (e === this.#t || e === this.#n || (e.localName === "n-chat-message" ? this.#n?.appendChild(e) : this.#t?.appendChild(e)));
793
+ }), this.#r.observe(this, { childList: !0 });
743
794
  }
744
795
  teardown() {
796
+ if (this.#r?.disconnect(), this.#r = null, this.#n) {
797
+ for (; this.#n.firstChild;) this.appendChild(this.#n.firstChild);
798
+ this.#n.remove(), this.#n = null;
799
+ }
800
+ if (this.#t) {
801
+ for (; this.#t.firstChild;) this.appendChild(this.#t.firstChild);
802
+ this.#t.remove(), this.#t = null;
803
+ }
745
804
  super.teardown();
746
805
  }
747
- }, h = class extends e {
806
+ #i() {
807
+ let e = document.createElement("div");
808
+ e.className = "n-chat-context";
809
+ let t = document.createElement("div");
810
+ t.className = "n-chat-bubbles";
811
+ let n = Array.from(this.childNodes);
812
+ for (let r of n) r instanceof Element && r.localName === "n-chat-avatar" || (r instanceof Element && r.localName === "n-chat-message" ? t.appendChild(r) : e.appendChild(r));
813
+ this.appendChild(e), this.appendChild(t), this.#t = e, this.#n = t;
814
+ }
815
+ }, g = class extends e {
748
816
  static observedAttributes = ["format"];
749
817
  #e = r("markdown");
750
818
  #t = r("");
@@ -768,14 +836,14 @@ var m = class extends e {
768
836
  super.setup(), this.deferChildren(() => {
769
837
  !this.#t.value && this.textContent?.trim() && (this.#t.value = this.textContent.trim()), this.#n = document.createElement("div"), this.#n.className = "n-chat-prose", this.textContent = "", this.appendChild(this.#n), this.addEffect(() => {
770
838
  let e = this.#t.value, t = this.#e.value;
771
- this.#n && (t === "plain" ? this.#n.textContent = e : this.#n.innerHTML = y(e));
839
+ this.#n && (t === "plain" ? this.#n.textContent = e : this.#n.innerHTML = b(e));
772
840
  });
773
841
  });
774
842
  }
775
843
  teardown() {
776
844
  this.#n = null, super.teardown();
777
845
  }
778
- }, g = new Set([
846
+ }, _ = new Set([
779
847
  "p",
780
848
  "br",
781
849
  "strong",
@@ -795,14 +863,14 @@ var m = class extends e {
795
863
  "blockquote",
796
864
  "hr"
797
865
  ]);
798
- function _(e) {
866
+ function v(e) {
799
867
  return e.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;").replaceAll("'", "&#39;");
800
868
  }
801
- function v(e) {
802
- let t = _(e);
869
+ function y(e) {
870
+ let t = v(e);
803
871
  return t = t.replace(/`([^`]+)`/g, "<code>$1</code>"), t = t.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>"), t = t.replace(/__(.+?)__/g, "<strong>$1</strong>"), t = t.replace(/\*(.+?)\*/g, "<em>$1</em>"), t = t.replace(/(?<!\w)_(.+?)_(?!\w)/g, "<em>$1</em>"), t = t.replace(/\[([^\]]+)\]\((https?:\/\/[^)]+)\)/g, "<a href=\"$2\" rel=\"noopener noreferrer\" target=\"_blank\">$1</a>"), t;
804
872
  }
805
- function y(e) {
873
+ function b(e) {
806
874
  if (!e) return "";
807
875
  let t = e.split("\n"), n = [], r = 0;
808
876
  for (; r < t.length;) {
@@ -811,14 +879,14 @@ function y(e) {
811
879
  let i = e.slice(3).trim(), a = [];
812
880
  for (r += 1; r < t.length && !t[r].startsWith("```");) a.push(t[r]), r += 1;
813
881
  r += 1;
814
- let o = i ? ` data-lang="${_(i)}"` : "";
815
- n.push(`<pre${o}><code>${_(a.join("\n"))}</code></pre>`);
882
+ let o = i ? ` data-lang="${v(i)}"` : "";
883
+ n.push(`<pre${o}><code>${v(a.join("\n"))}</code></pre>`);
816
884
  continue;
817
885
  }
818
886
  let i = e.match(/^(#{1,6})\s+(.+)/);
819
887
  if (i) {
820
888
  let e = i[1].length;
821
- n.push(`<h${e}>${v(i[2])}</h${e}>`), r += 1;
889
+ n.push(`<h${e}>${y(i[2])}</h${e}>`), r += 1;
822
890
  continue;
823
891
  }
824
892
  if (/^---+$/.test(e.trim()) || /^\*\*\*+$/.test(e.trim())) {
@@ -828,19 +896,19 @@ function y(e) {
828
896
  if (e.startsWith("> ")) {
829
897
  let e = [];
830
898
  for (; r < t.length && t[r].startsWith("> ");) e.push(t[r].slice(2)), r += 1;
831
- n.push(`<blockquote>${y(e.join("\n"))}</blockquote>`);
899
+ n.push(`<blockquote>${b(e.join("\n"))}</blockquote>`);
832
900
  continue;
833
901
  }
834
902
  if (/^[-*+]\s/.test(e)) {
835
903
  let e = [];
836
904
  for (; r < t.length && /^[-*+]\s/.test(t[r]);) e.push(t[r].replace(/^[-*+]\s/, "")), r += 1;
837
- n.push("<ul>" + e.map((e) => `<li>${v(e)}</li>`).join("") + "</ul>");
905
+ n.push("<ul>" + e.map((e) => `<li>${y(e)}</li>`).join("") + "</ul>");
838
906
  continue;
839
907
  }
840
908
  if (/^\d+\.\s/.test(e)) {
841
909
  let e = [];
842
910
  for (; r < t.length && /^\d+\.\s/.test(t[r]);) e.push(t[r].replace(/^\d+\.\s/, "")), r += 1;
843
- n.push("<ol>" + e.map((e) => `<li>${v(e)}</li>`).join("") + "</ol>");
911
+ n.push("<ol>" + e.map((e) => `<li>${y(e)}</li>`).join("") + "</ol>");
844
912
  continue;
845
913
  }
846
914
  if (!e.trim()) {
@@ -849,15 +917,15 @@ function y(e) {
849
917
  }
850
918
  let a = [];
851
919
  for (; r < t.length && t[r].trim() && !t[r].startsWith("#") && !t[r].startsWith("```") && !t[r].startsWith("> ") && !/^[-*+]\s/.test(t[r]) && !/^\d+\.\s/.test(t[r]) && !/^---+$/.test(t[r].trim());) a.push(t[r]), r += 1;
852
- a.length > 0 && n.push(`<p>${v(a.join("\n"))}</p>`);
920
+ a.length > 0 && n.push(`<p>${y(a.join("\n"))}</p>`);
853
921
  }
854
922
  return n.join("");
855
923
  }
856
924
  /** Sanitize rendered HTML — strip any tags not in allowlist. */
857
- function b(e) {
925
+ function x(e) {
858
926
  return e.replace(/<\/?([a-zA-Z][a-zA-Z0-9]*)[^>]*>/g, (e, t) => {
859
927
  let n = t.toLowerCase();
860
- return g.has(n) ? e : "";
928
+ return _.has(n) ? e : "";
861
929
  });
862
930
  }
863
931
  /**
@@ -878,7 +946,7 @@ function b(e) {
878
946
  * @attr {boolean} expandable - Allow click to expand trace content
879
947
  * @fires native:activity-toggle - Fired when trace is expanded/collapsed
880
948
  */
881
- var x = class extends e {
949
+ var S = class extends e {
882
950
  static observedAttributes = [
883
951
  "type",
884
952
  "label",
@@ -936,7 +1004,7 @@ var x = class extends e {
936
1004
  this.#r.value ? (this.#p(), this.#e.states.add("active")) : (this.#m(), this.#e.states.delete("active"));
937
1005
  }), this.addEffect(() => {
938
1006
  if (!this.#c) return;
939
- let e = this.#n.value || S(this.#t.value);
1007
+ let e = this.#n.value || C(this.#t.value);
940
1008
  this.#c.textContent = e;
941
1009
  }), this.addEffect(() => {
942
1010
  this.#u && (this.#u.hidden = !this.#r.value);
@@ -966,7 +1034,7 @@ var x = class extends e {
966
1034
  #h = () => {
967
1035
  if (!this.#r.value || !this.#l) return;
968
1036
  let e = performance.now() - this.#o;
969
- this.#l.textContent = C(e), this.#s = requestAnimationFrame(this.#h);
1037
+ this.#l.textContent = w(e), this.#s = requestAnimationFrame(this.#h);
970
1038
  };
971
1039
  #g = () => {
972
1040
  this.#i.value && (this.#a.value = !this.#a.value, this.toggleAttribute("expanded", this.#a.value), this.dispatchEvent(new CustomEvent("native:activity-toggle", {
@@ -976,14 +1044,14 @@ var x = class extends e {
976
1044
  })));
977
1045
  };
978
1046
  };
979
- function S(e) {
1047
+ function C(e) {
980
1048
  switch (e) {
981
1049
  case "thinking": return "Thinking…";
982
1050
  case "tool-use": return "Using tools…";
983
1051
  default: return "Host is typing…";
984
1052
  }
985
1053
  }
986
- function C(e) {
1054
+ function w(e) {
987
1055
  let t = Math.floor(e / 1e3);
988
1056
  if (t < 60) return `${t}s`;
989
1057
  let n = Math.floor(t / 60), r = t % 60;
@@ -1003,7 +1071,7 @@ function C(e) {
1003
1071
  * @attr {boolean} disabled - Disables all chips
1004
1072
  * @fires native:seed-select - Fired when a chip is clicked
1005
1073
  */
1006
- var w = class extends e {
1074
+ var T = class extends e {
1007
1075
  static observedAttributes = ["options", "disabled"];
1008
1076
  #e;
1009
1077
  #t = r([]);
@@ -1072,7 +1140,7 @@ var w = class extends e {
1072
1140
  }
1073
1141
  }));
1074
1142
  };
1075
- }, T = new Set([
1143
+ }, E = new Set([
1076
1144
  "script",
1077
1145
  "style",
1078
1146
  "link",
@@ -1089,7 +1157,7 @@ var w = class extends e {
1089
1157
  "frame",
1090
1158
  "frameset",
1091
1159
  "noscript"
1092
- ]), E = class extends e {
1160
+ ]), D = class extends e {
1093
1161
  static observedAttributes = ["schema-type", "mode"];
1094
1162
  #e;
1095
1163
  #t = r("a2ui");
@@ -1134,7 +1202,7 @@ var w = class extends e {
1134
1202
  super.setup(), this.#i = document.createElement("div"), this.#i.className = "n-chat-genui-container", this.appendChild(this.#i), this.addEffect(() => {
1135
1203
  let e = this.#r.value, t = this.#n.value;
1136
1204
  if (!this.#i || (this.#i.textContent = "", !e)) return;
1137
- let n = D(e);
1205
+ let n = O(e);
1138
1206
  if (n.length > 0) {
1139
1207
  this.#a(n), this.dispatchEvent(new CustomEvent("native:genui-error", {
1140
1208
  bubbles: !0,
@@ -1145,7 +1213,7 @@ var w = class extends e {
1145
1213
  }
1146
1214
  if (t === "lightbox") this.#o(e);
1147
1215
  else {
1148
- let t = O(e);
1216
+ let t = k(e);
1149
1217
  t && this.#i.appendChild(t);
1150
1218
  }
1151
1219
  this.#e.states.add("rendered");
@@ -1162,14 +1230,14 @@ var w = class extends e {
1162
1230
  #o(e) {
1163
1231
  if (!this.#i) return;
1164
1232
  let t = document.createElement("n-card");
1165
- t.className = "n-chat-genui-preview";
1233
+ t.dataset.role = "preview";
1166
1234
  let n = document.createElement("span");
1167
1235
  n.textContent = `Interactive UI (${e.tag})`, t.appendChild(n);
1168
1236
  let r = document.createElement("n-button");
1169
1237
  r.setAttribute("variant", "outline"), r.setAttribute("size", "sm"), r.setAttribute("inline", ""), r.textContent = "Open", r.addEventListener("click", () => this.#s(e)), t.appendChild(r), this.#i.appendChild(t);
1170
1238
  }
1171
1239
  #s(e) {
1172
- let t = document.createElement("n-dialog"), n = O(e);
1240
+ let t = document.createElement("n-dialog"), n = k(e);
1173
1241
  n && t.appendChild(n), this.appendChild(t), requestAnimationFrame(() => {
1174
1242
  let e = t.querySelector("dialog");
1175
1243
  e && e.showModal();
@@ -1189,20 +1257,20 @@ var w = class extends e {
1189
1257
  }));
1190
1258
  };
1191
1259
  };
1192
- function D(e, t = 0) {
1260
+ function O(e, t = 0) {
1193
1261
  let n = [];
1194
1262
  if (t > 20) return n.push("Maximum nesting depth (20) exceeded"), n;
1195
1263
  if (!e.tag || typeof e.tag != "string") return n.push("Node missing required \"tag\" property"), n;
1196
- if (T.has(e.tag.toLowerCase()) && n.push(`Forbidden tag: <${e.tag}>`), e.children) for (let r of e.children) n.push(...D(r, t + 1));
1264
+ if (E.has(e.tag.toLowerCase()) && n.push(`Forbidden tag: <${e.tag}>`), e.children) for (let r of e.children) n.push(...O(r, t + 1));
1197
1265
  return n;
1198
1266
  }
1199
- function O(e) {
1200
- if (T.has(e.tag.toLowerCase())) return null;
1267
+ function k(e) {
1268
+ if (E.has(e.tag.toLowerCase())) return null;
1201
1269
  let t = document.createElement(e.tag);
1202
1270
  if (e.id && (t.id = e.id), e.slot && (t.slot = e.slot), e.attributes) for (let [n, r] of Object.entries(e.attributes)) t.setAttribute(n, r);
1203
1271
  if (e.properties) for (let [n, r] of Object.entries(e.properties)) t[n] = r;
1204
1272
  if (e.text && (t.textContent = e.text), e.children) for (let n of e.children) {
1205
- let e = O(n);
1273
+ let e = k(n);
1206
1274
  e && t.appendChild(e);
1207
1275
  }
1208
1276
  return t;
@@ -1227,7 +1295,7 @@ function O(e) {
1227
1295
  * @fires native:structured-submit - Fired on submit with selections
1228
1296
  * @fires native:structured-cancel - Fired when dismissed without selecting
1229
1297
  */
1230
- var k = class extends e {
1298
+ var A = class extends e {
1231
1299
  static observedAttributes = [
1232
1300
  "question",
1233
1301
  "type",
@@ -1353,4 +1421,4 @@ var k = class extends e {
1353
1421
  }));
1354
1422
  };
1355
1423
  };
1356
- export { h as a, b as c, f as d, d as f, i as g, a as h, x as i, m as l, s as m, E as n, v as o, c as p, w as r, y as s, k as t, u };
1424
+ export { g as a, x as c, p as d, f, a as g, o as h, S as i, h as l, c as m, D as n, y as o, l as p, T as r, b as s, A as t, d as u };
@@ -16,8 +16,8 @@ export declare const ROLE_DEFAULTS: Record<string, string[]>;
16
16
  * @attr {string} timestamp - Epoch milliseconds
17
17
  * @attr {string} status - `sending` | `sent` | `error` | `streaming` | `partial`
18
18
  * @attr {string} actions - Comma-separated action list, or `"none"` to suppress
19
- * @attr {string} actions-style - `"label"` (default) | `"icon"` | `"icon-label"`
20
- * @attr {string} actions-position - `"inside"` (default) | `"below"` — toolbar placement
19
+ * @attr {string} actions-style - `"icon"` (default) | `"label"` | `"icon-label"`
20
+ * @attr {string} actions-position - `"below"` (default) | `"inside"` — toolbar placement
21
21
  * @fires native:message-action - Fired when an action button is clicked
22
22
  * @fires native:continue-request - Fired when continue is requested for a partial message
23
23
  */
@@ -1 +1 @@
1
- {"version":3,"file":"chat-message-element.d.ts","sourceRoot":"","sources":["../../src/message/chat-message-element.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAU,MAAM,mBAAmB,CAAC;AAI1D,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAOhE,CAAC;AAEF,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAGlD,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,YAAa,SAAQ,aAAa;;IAC7C,MAAM,CAAC,kBAAkB,WAAiG;;IAiB1H,IAAI,IAAI,IAAI,MAAM,CAA6B;IAC/C,IAAI,IAAI,CAAC,GAAG,EAAE,MAAM,EAGnB;IAED,IAAI,SAAS,IAAI,MAAM,CAAkD;IAEzE,IAAI,SAAS,IAAI,MAAM,CAAwD;IAE/E,IAAI,MAAM,IAAI,MAAM,CAA+B;IACnD,IAAI,MAAM,CAAC,GAAG,EAAE,MAAM,EAGrB;IAED,IAAI,OAAO,IAAI,MAAM,GAAG,IAAI,CAAgC;IAC5D,IAAI,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,EAO7B;IAED,IAAI,YAAY,IAAI,MAAM,CAAqC;IAC/D,IAAI,YAAY,CAAC,GAAG,EAAE,MAAM,EAG3B;IAED,IAAI,eAAe,IAAI,MAAM,CAAwC;IACrE,IAAI,eAAe,CAAC,GAAG,EAAE,MAAM,EAG9B;IAID,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAcpF,KAAK,IAAI,IAAI;IAmBb,QAAQ,IAAI,IAAI;CA0FjB"}
1
+ {"version":3,"file":"chat-message-element.d.ts","sourceRoot":"","sources":["../../src/message/chat-message-element.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAe,MAAM,mBAAmB,CAAC;AAI/D,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAOhE,CAAC;AAEF,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAGlD,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,YAAa,SAAQ,aAAa;;IAC7C,MAAM,CAAC,kBAAkB,WAAiG;;IAoB1H,IAAI,IAAI,IAAI,MAAM,CAA6B;IAC/C,IAAI,IAAI,CAAC,GAAG,EAAE,MAAM,EAGnB;IAED,IAAI,SAAS,IAAI,MAAM,CAAkD;IAEzE,IAAI,SAAS,IAAI,MAAM,CAAwD;IAE/E,IAAI,MAAM,IAAI,MAAM,CAA+B;IACnD,IAAI,MAAM,CAAC,GAAG,EAAE,MAAM,EAGrB;IAED,IAAI,OAAO,IAAI,MAAM,GAAG,IAAI,CAAgC;IAC5D,IAAI,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,EAO7B;IAED,IAAI,YAAY,IAAI,MAAM,CAAqC;IAC/D,IAAI,YAAY,CAAC,GAAG,EAAE,MAAM,EAG3B;IAED,IAAI,eAAe,IAAI,MAAM,CAAwC;IACrE,IAAI,eAAe,CAAC,GAAG,EAAE,MAAM,EAG9B;IAID,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAcpF,KAAK,IAAI,IAAI;IAqBb,QAAQ,IAAI,IAAI;CAoJjB"}
@@ -2,13 +2,23 @@ import { NativeElement } from '@nonoun/native-ui';
2
2
  /**
3
3
  * Message group — cluster of messages from the same sender.
4
4
  *
5
- * Provides alignment (user = right, assistant/system = left) and
6
- * groups an avatar + message column.
5
+ * Provides a 2×2 grid layout:
6
+ * - Col 1, row 1–2: avatar (bottom-aligned)
7
+ * - Col 2, row 1: context area (metadata, reasoning, timestamps)
8
+ * - Col 2, row 2: messages area (chat bubbles, flex column)
9
+ *
10
+ * On setup, non-avatar children are sorted into two wrapper divs:
11
+ * - `.n-chat-context` — non-message children (row 1)
12
+ * - `.n-chat-bubbles` — `n-chat-message` children (row 2)
13
+ *
14
+ * A MutationObserver routes dynamically added children into the
15
+ * correct wrapper automatically.
7
16
  *
8
17
  * ```html
9
- * <n-chat-messages role="user" sender="Kim">
10
- * <n-chat-avatar name="Kim"></n-chat-avatar>
11
- * <n-chat-message role="user" message-id="1">
18
+ * <n-chat-messages role="assistant" sender="AI">
19
+ * <n-chat-avatar>AI</n-chat-avatar>
20
+ * <span class="reasoning">Thinking...</span>
21
+ * <n-chat-message role="assistant">
12
22
  * <n-chat-message-text>Hello!</n-chat-message-text>
13
23
  * </n-chat-message>
14
24
  * </n-chat-messages>
@@ -16,7 +26,7 @@ import { NativeElement } from '@nonoun/native-ui';
16
26
  *
17
27
  * @attr {string} role - `user` | `assistant` | `system`
18
28
  * @attr {string} sender - Display name of the sender
19
- * @attr {string} avatar-align - `"top"` (default) | `"center"` | `"bottom"` — avatar vertical alignment
29
+ * @attr {string} avatar-align - `"top"` | `"center"` | `"bottom"` (default) — avatar vertical alignment
20
30
  */
21
31
  export declare class NChatMessages extends NativeElement {
22
32
  #private;
@@ -1 +1 @@
1
- {"version":3,"file":"chat-messages-element.d.ts","sourceRoot":"","sources":["../../src/message/chat-messages-element.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAElD;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,aAAc,SAAQ,aAAa;;IAC9C,MAAM,CAAC,kBAAkB,WAAsC;;IAS/D,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAKpF,KAAK,IAAI,IAAI;IASb,QAAQ,IAAI,IAAI;CAGjB"}
1
+ {"version":3,"file":"chat-messages-element.d.ts","sourceRoot":"","sources":["../../src/message/chat-messages-element.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,qBAAa,aAAc,SAAQ,aAAa;;IAC9C,MAAM,CAAC,kBAAkB,WAAsC;;IAY/D,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAKpF,KAAK,IAAI,IAAI;IA8Bb,QAAQ,IAAI,IAAI;CA4CjB"}
@@ -191,37 +191,85 @@
191
191
  n-chat-messages — message group (bubble cluster)
192
192
  ══════════════════════════════════════════════════ */
193
193
 
194
+ /* 2×2 grid: avatar | context + bubbles.
195
+ Col 1: avatar (spans both rows, bottom-aligned)
196
+ Col 2 row 1: .n-chat-context — metadata, reasoning, timestamps
197
+ Col 2 row 2: .n-chat-bubbles — message bubbles (flex column) */
194
198
  :where(n-chat-messages) {
195
- display: flex;
196
- flex-direction: row;
197
- align-items: flex-start;
198
- gap: var(--n-chat-message-gap);
199
+ display: grid;
200
+ grid-template-columns: auto 1fr;
201
+ grid-template-rows: auto 1fr;
202
+ column-gap: var(--n-chat-message-gap);
199
203
  align-self: flex-start;
204
+ max-width: var(--n-chat-bubble-max-width);
200
205
  min-width: 0;
201
206
  }
202
207
 
203
- /* User messages: right-aligned, avatar on right */
204
- [role="user"]:where(n-chat-messages) {
205
- align-self: flex-end;
206
- flex-direction: row-reverse;
208
+ /* Avatar: col 1, spans both rows, bottom-align */
209
+ :where(n-chat-messages) > :where(n-chat-avatar) {
210
+ grid-column: 1;
211
+ grid-row: 1 / -1;
212
+ align-self: end;
207
213
  }
208
214
 
209
- /* Message column (everything except avatar) */
210
- :where(n-chat-messages) > :where(:not(n-chat-avatar)) {
215
+ /* Context area: col 2, row 1 — metadata above messages */
216
+ :where(n-chat-messages) > :where(.n-chat-context) {
217
+ grid-column: 2;
218
+ grid-row: 1;
211
219
  display: flex;
212
220
  flex-direction: column;
213
- gap: calc(var(--n-space));
214
- max-width: var(--n-chat-bubble-max-width);
221
+ gap: var(--n-space);
215
222
  min-width: 0;
216
223
  }
217
224
 
218
- /* Avatar alignment */
219
- [avatar-align="center"]:where(n-chat-messages) {
220
- align-items: center;
225
+ /* Empty context collapses */
226
+ :where(n-chat-messages) > :where(.n-chat-context:empty) {
227
+ display: none;
228
+ }
229
+
230
+ /* Bubbles area: col 2, row 2 — flex column of messages */
231
+ :where(n-chat-messages) > :where(.n-chat-bubbles) {
232
+ grid-column: 2;
233
+ grid-row: 2;
234
+ display: flex;
235
+ flex-direction: column;
236
+ gap: var(--n-space);
237
+ min-width: 0;
238
+ }
239
+
240
+ /* Messages shrink-wrap to content width */
241
+ :where(.n-chat-bubbles) > :where(n-chat-message) {
242
+ width: fit-content;
243
+ max-width: 100%;
244
+ }
245
+
246
+ /* Avatar alignment overrides */
247
+ [avatar-align="top"]:where(n-chat-messages) > :where(n-chat-avatar) {
248
+ align-self: start;
249
+ }
250
+
251
+ [avatar-align="center"]:where(n-chat-messages) > :where(n-chat-avatar) {
252
+ align-self: center;
221
253
  }
222
254
 
223
- [avatar-align="bottom"]:where(n-chat-messages) {
224
- align-items: flex-end;
255
+ /* User messages: right-aligned, avatar on right */
256
+ [role="user"]:where(n-chat-messages) {
257
+ grid-template-columns: 1fr auto;
258
+ align-self: flex-end;
259
+ }
260
+
261
+ [role="user"]:where(n-chat-messages) > :where(n-chat-avatar) {
262
+ grid-column: 2;
263
+ }
264
+
265
+ [role="user"]:where(n-chat-messages) > :where(.n-chat-context),
266
+ [role="user"]:where(n-chat-messages) > :where(.n-chat-bubbles) {
267
+ grid-column: 1;
268
+ }
269
+
270
+ /* User bubbles align to the right edge */
271
+ [role="user"]:where(n-chat-messages) :where(.n-chat-bubbles) > :where(n-chat-message) {
272
+ align-self: flex-end;
225
273
  }
226
274
 
227
275
  :where(n-chat-messages[hidden]) { display: none; }
@@ -270,9 +318,7 @@
270
318
  display: flex;
271
319
  flex-direction: column;
272
320
  gap: var(--n-space);
273
-
274
- padding-block: var(--n-chat-bubble-padding-block);
275
- padding-inline: var(--n-chat-bubble-padding-inline);
321
+ overflow: hidden;
276
322
 
277
323
  /* Asymmetric radius: assistant = avatar on left */
278
324
  border-radius:
@@ -317,40 +363,64 @@
317
363
 
318
364
  /* ── Message actions toolbar ── */
319
365
 
320
- :where(n-chat-message) > :where(.n-chat-message-actions) {
366
+ :where(n-chat-message) > :where(n-toolbar[data-role="actions"]) {
321
367
  opacity: 0;
322
368
  transition: opacity var(--n-duration) var(--n-easing);
369
+ padding-inline: var(--n-chat-bubble-padding-inline);
323
370
  }
324
371
 
325
- :where(n-chat-message):hover > :where(.n-chat-message-actions),
326
- :where(n-chat-message):focus-within > :where(.n-chat-message-actions),
327
- :where(n-chat-message[force-hover]) > :where(.n-chat-message-actions) {
372
+ :where(n-chat-message):hover > :where(n-toolbar[data-role="actions"]),
373
+ :where(n-chat-message):focus-within > :where(n-toolbar[data-role="actions"]),
374
+ :where(n-chat-message[force-hover]) > :where(n-toolbar[data-role="actions"]) {
328
375
  opacity: 1;
329
376
  }
330
377
 
331
378
  /* Partial status — always show actions (contains continue button) */
332
- [status="partial"]:where(n-chat-message) > :where(.n-chat-message-actions) {
379
+ [status="partial"]:where(n-chat-message) > :where(n-toolbar[data-role="actions"]) {
333
380
  opacity: 1;
334
381
  }
335
382
 
336
- /* ── Below-bubble actions position ── */
383
+ /* ── Below-bubble popover toolbar ── */
337
384
 
338
- /* When actions-position="below", the toolbar is a sibling of n-chat-message
339
- in the message column. Show on message hover via adjacent sibling. */
340
- [actions-position="below"]:where(n-chat-message) + :where(.n-chat-message-actions) {
385
+ /* Popover toolbar: anchored below the message via CSS anchor positioning.
386
+ Toolbar is a child of n-chat-message with popover="manual", rendered in the
387
+ top layer. JS shows/hides via pointerenter/pointerleave + showPopover().
388
+ Entry/exit transition mirrors n-listbox[popover] (perspective + scale). */
389
+ :where(n-chat-message) > :where(n-toolbar[data-role="actions"][popover]) {
390
+ position: fixed;
391
+ inset: auto;
392
+ top: anchor(bottom);
393
+ left: anchor(center);
394
+ translate: -50% 0;
395
+ margin: 2px 0 0;
396
+ width: max-content;
397
+
398
+ transform-origin: top center;
341
399
  opacity: 0;
342
- transition: opacity var(--n-duration) var(--n-easing);
400
+ transform: perspective(800px) scale(0.96) rotateX(-20deg);
401
+
402
+ transition:
403
+ opacity var(--n-duration) var(--n-easing),
404
+ transform var(--n-duration) var(--n-easing),
405
+ display var(--n-duration) var(--n-easing) allow-discrete,
406
+ overlay var(--n-duration) var(--n-easing) allow-discrete;
343
407
  }
344
408
 
345
- [actions-position="below"]:where(n-chat-message):hover + :where(.n-chat-message-actions),
346
- [actions-position="below"]:where(n-chat-message[force-hover]) + :where(.n-chat-message-actions),
347
- [actions-position="below"]:where(n-chat-message) + :where(.n-chat-message-actions):focus-within {
409
+ :where(n-chat-message) > :where(n-toolbar[data-role="actions"][popover]):popover-open {
348
410
  opacity: 1;
411
+ transform: none;
349
412
  }
350
413
 
351
- /* Partial status — always show below-bubble actions */
352
- [status="partial"][actions-position="below"]:where(n-chat-message) + :where(.n-chat-message-actions) {
353
- opacity: 1;
414
+ @starting-style {
415
+ :where(n-chat-message) > :where(n-toolbar[data-role="actions"][popover]):popover-open {
416
+ opacity: 0;
417
+ transform: perspective(800px) scale(0.96) rotateX(-20deg);
418
+ }
419
+ }
420
+
421
+ /* Override n-toolbar display to respect popover hidden state */
422
+ :where(n-toolbar[data-role="actions"][popover]):not(:popover-open) {
423
+ display: none;
354
424
  }
355
425
 
356
426
  :where(n-chat-message[hidden]) { display: none; }
@@ -361,6 +431,8 @@
361
431
 
362
432
  :where(n-chat-message-text) {
363
433
  display: block;
434
+ padding-block: var(--n-chat-bubble-padding-block);
435
+ padding-inline: var(--n-chat-bubble-padding-inline);
364
436
  }
365
437
 
366
438
  /* ── Prose reset — typography inside rendered markdown ── */
@@ -453,6 +525,7 @@
453
525
  color: var(--n-chat-activity-color);
454
526
  font-size: var(--n-font-size-sm);
455
527
  padding-block: var(--n-space);
528
+ padding-inline: var(--n-chat-bubble-padding-inline);
456
529
  align-self: flex-start;
457
530
  }
458
531
 
@@ -545,6 +618,7 @@
545
618
  flex-wrap: wrap;
546
619
  gap: var(--n-space);
547
620
  padding-block: var(--n-space);
621
+ padding-inline: var(--n-chat-bubble-padding-inline);
548
622
  align-self: flex-start;
549
623
  }
550
624
 
@@ -575,7 +649,7 @@
575
649
  font-size: var(--n-font-size-sm);
576
650
  }
577
651
 
578
- :where(n-chat-message-genui) :where(.n-chat-genui-preview) {
652
+ :where(n-chat-message-genui) :where(n-card[data-role="preview"]) {
579
653
  display: flex;
580
654
  align-items: center;
581
655
  justify-content: space-between;
@@ -1,4 +1,4 @@
1
- import { a as e, c as t, d as n, f as r, g as i, h as a, i as o, l as s, m as c, n as l, o as u, p as d, r as f, s as p, t as m, u as h } from "./chat-input-structured-element-uXGIe-HI.js";
1
+ import { a as e, c as t, d as n, f as r, g as i, h as a, i as o, l as s, m as c, n as l, o as u, p as d, r as f, s as p, t as m, u as h } from "./chat-input-structured-element-DPKHlnRm.js";
2
2
  /**
3
3
  * Parse a Server-Sent Events (SSE) response stream into ChatStreamChunk values.
4
4
  *
@@ -1 +1 @@
1
- {"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../src/register.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wCAAwC,CAAC;AAC1E,OAAO,EAAE,oBAAoB,EAAE,MAAM,4CAA4C,CAAC;AAClF,OAAO,EAAE,gBAAgB,EAAE,MAAM,wCAAwC,CAAC;AAC1E,OAAO,EAAE,iBAAiB,EAAE,MAAM,yCAAyC,CAAC;AAC5E,OAAO,EAAE,oBAAoB,EAAE,MAAM,4CAA4C,CAAC;AA6BlF,OAAO,EACL,UAAU,EACV,UAAU,EACV,SAAS,EACT,WAAW,EACX,YAAY,EACZ,aAAa,EACb,gBAAgB,EAChB,oBAAoB,EACpB,gBAAgB,EAChB,iBAAiB,EACjB,oBAAoB,GACrB,CAAC"}
1
+ {"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../src/register.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wCAAwC,CAAC;AAC1E,OAAO,EAAE,oBAAoB,EAAE,MAAM,4CAA4C,CAAC;AAClF,OAAO,EAAE,gBAAgB,EAAE,MAAM,wCAAwC,CAAC;AAC1E,OAAO,EAAE,iBAAiB,EAAE,MAAM,yCAAyC,CAAC;AAC5E,OAAO,EAAE,oBAAoB,EAAE,MAAM,4CAA4C,CAAC;AAqClF,OAAO,EACL,UAAU,EACV,UAAU,EACV,SAAS,EACT,WAAW,EACX,YAAY,EACZ,aAAa,EACb,gBAAgB,EAChB,oBAAoB,EACpB,gBAAgB,EAChB,iBAAiB,EACjB,oBAAoB,GACrB,CAAC"}
package/dist/register.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as e, d as t, g as n, h as r, i, l as a, m as o, n as s, p as c, r as l, t as u } from "./chat-input-structured-element-uXGIe-HI.js";
1
+ import { a as e, d as t, g as n, h as r, i, l as a, m as o, n as s, p as c, r as l, t as u } from "./chat-input-structured-element-DPKHlnRm.js";
2
2
  import { NButton as d, NCard as f, NDialog as p, NIcon as m, NTextarea as h, NToolbar as g, define as _, registerIcon as v } from "@nonoun/native-ui";
3
- _("n-chat-input", n), _("native-chat-panel", r), _("n-chat-feed", o), _("n-chat-avatar", c), _("n-chat-message", t), _("n-chat-messages", a), _("n-chat-message-text", e), _("n-chat-message-activity", i), _("n-chat-message-seed", l), _("n-chat-message-genui", s), _("n-chat-input-structured", u), _("n-textarea", h), _("n-button", d), _("n-icon", m), _("n-toolbar", g), _("n-dialog", p), _("n-card", f), v("chat-dots", "<svg viewBox=\"0 0 256 256\" fill=\"currentColor\"><path d=\"M116,128a12,12,0,1,1,12,12A12,12,0,0,1,116,128ZM84,140a12,12,0,1,0-12-12A12,12,0,0,0,84,140Zm88,0a12,12,0,1,0-12-12A12,12,0,0,0,172,140Zm60-76V192a16,16,0,0,1-16,16H83l-32.6,28.16-.09.07A15.89,15.89,0,0,1,40,240a16.13,16.13,0,0,1-6.8-1.52A15.85,15.85,0,0,1,24,224V64A16,16,0,0,1,40,48H216A16,16,0,0,1,232,64ZM40,224h0ZM216,64H40V224l34.77-30A8,8,0,0,1,80,192H216Z\"/></svg>"), v("user", "<svg viewBox=\"0 0 256 256\" fill=\"currentColor\"><path d=\"M230.92,212c-15.23-26.33-38.7-45.21-66.09-54.16a72,72,0,1,0-73.66,0C63.78,166.78,40.31,185.66,25.08,212a8,8,0,1,0,13.85,8C55.71,194.74,89.05,176,128,176s72.29,18.74,89.07,44a8,8,0,0,0,13.85-8ZM72,96a56,56,0,1,1,56,56A56.06,56.06,0,0,1,72,96Z\"/></svg>"), v("stop", "<svg viewBox=\"0 0 256 256\" fill=\"currentColor\"><path d=\"M200,40H56A16,16,0,0,0,40,56V200a16,16,0,0,0,16,16H200a16,16,0,0,0,16-16V56A16,16,0,0,0,200,40Zm0,160H56V56H200V200Z\"/></svg>"), v("arrow-counter-clockwise", "<svg viewBox=\"0 0 256 256\" fill=\"currentColor\"><path d=\"M224,128a96,96,0,0,1-94.71,96H128A95.38,95.38,0,0,1,62.1,197.8a8,8,0,0,1,11-11.63A80,80,0,1,0,71.43,71.39a3.07,3.07,0,0,1-.26.25L44.59,96H72a8,8,0,0,1,0,16H24a8,8,0,0,1-8-8V56a8,8,0,0,1,16,0V85.8L60.25,60A96,96,0,0,1,224,128Z\"/></svg>");
3
+ _("n-chat-input", n), _("native-chat-panel", r), _("n-chat-feed", o), _("n-chat-avatar", c), _("n-chat-message", t), _("n-chat-messages", a), _("n-chat-message-text", e), _("n-chat-message-activity", i), _("n-chat-message-seed", l), _("n-chat-message-genui", s), _("n-chat-input-structured", u), _("n-textarea", h), _("n-button", d), _("n-icon", m), _("n-toolbar", g), _("n-dialog", p), _("n-card", f), v("chat-dots", "<svg viewBox=\"0 0 256 256\" fill=\"currentColor\"><path d=\"M116,128a12,12,0,1,1,12,12A12,12,0,0,1,116,128ZM84,140a12,12,0,1,0-12-12A12,12,0,0,0,84,140Zm88,0a12,12,0,1,0-12-12A12,12,0,0,0,172,140Zm60-76V192a16,16,0,0,1-16,16H83l-32.6,28.16-.09.07A15.89,15.89,0,0,1,40,240a16.13,16.13,0,0,1-6.8-1.52A15.85,15.85,0,0,1,24,224V64A16,16,0,0,1,40,48H216A16,16,0,0,1,232,64ZM40,224h0ZM216,64H40V224l34.77-30A8,8,0,0,1,80,192H216Z\"/></svg>"), v("user", "<svg viewBox=\"0 0 256 256\" fill=\"currentColor\"><path d=\"M230.92,212c-15.23-26.33-38.7-45.21-66.09-54.16a72,72,0,1,0-73.66,0C63.78,166.78,40.31,185.66,25.08,212a8,8,0,1,0,13.85,8C55.71,194.74,89.05,176,128,176s72.29,18.74,89.07,44a8,8,0,0,0,13.85-8ZM72,96a56,56,0,1,1,56,56A56.06,56.06,0,0,1,72,96Z\"/></svg>"), v("stop", "<svg viewBox=\"0 0 256 256\" fill=\"currentColor\"><path d=\"M200,40H56A16,16,0,0,0,40,56V200a16,16,0,0,0,16,16H200a16,16,0,0,0,16-16V56A16,16,0,0,0,200,40Zm0,160H56V56H200V200Z\"/></svg>"), v("arrow-counter-clockwise", "<svg viewBox=\"0 0 256 256\" fill=\"currentColor\"><path d=\"M224,128a96,96,0,0,1-94.71,96H128A95.38,95.38,0,0,1,62.1,197.8a8,8,0,0,1,11-11.63A80,80,0,1,0,71.43,71.39a3.07,3.07,0,0,1-.26.25L44.59,96H72a8,8,0,0,1,0,16H24a8,8,0,0,1-8-8V56a8,8,0,0,1,16,0V85.8L60.25,60A96,96,0,0,1,224,128Z\"/></svg>"), v("copy", "<svg viewBox=\"0 0 256 256\" fill=\"currentColor\"><path d=\"M216,32H88a8,8,0,0,0-8,8V80H40a8,8,0,0,0-8,8V216a8,8,0,0,0,8,8H168a8,8,0,0,0,8-8V176h40a8,8,0,0,0,8-8V40A8,8,0,0,0,216,32ZM160,208H48V96H160Zm48-48H176V88a8,8,0,0,0-8-8H96V48H208Z\"/></svg>"), v("arrow-clockwise", "<svg viewBox=\"0 0 256 256\" fill=\"currentColor\"><path d=\"M240,56v48a8,8,0,0,1-8,8H184a8,8,0,0,1,0-16H211.4L184.81,71.64l-.25-.24a80,80,0,1,0-1.67,114.78,8,8,0,0,1,11,11.63A95.44,95.44,0,0,1,128,224h-1.32A96,96,0,1,1,195.75,60L224,85.8V56a8,8,0,1,1,16,0Z\"/></svg>"), v("pencil-simple", "<svg viewBox=\"0 0 256 256\" fill=\"currentColor\"><path d=\"M227.31,73.37,182.63,28.68a16,16,0,0,0-22.63,0L36.69,152A15.86,15.86,0,0,0,32,163.31V208a16,16,0,0,0,16,16H92.69A15.86,15.86,0,0,0,104,219.31L227.31,96a16,16,0,0,0,0-22.63ZM92.69,208H48V163.31l88-88L180.69,120ZM192,108.68,147.31,64l24-24L216,84.68Z\"/></svg>"), v("thumbs-up", "<svg viewBox=\"0 0 256 256\" fill=\"currentColor\"><path d=\"M234,80.12A24,24,0,0,0,216,72H160V56a40,40,0,0,0-40-40,8,8,0,0,0-7.16,4.42L75.06,96H32a16,16,0,0,0-16,16v88a16,16,0,0,0,16,16H204a24,24,0,0,0,23.82-21l12-96A24,24,0,0,0,234,80.12ZM32,112H72v88H32ZM223.94,97l-12,96a8,8,0,0,1-7.94,7H88V105.89l36.71-73.43A24,24,0,0,1,144,56V80a8,8,0,0,0,8,8h64a8,8,0,0,1,7.94,9Z\"/></svg>"), v("thumbs-down", "<svg viewBox=\"0 0 256 256\" fill=\"currentColor\"><path d=\"M239.82,157l-12-96A24,24,0,0,0,204,40H32A16,16,0,0,0,16,56v88a16,16,0,0,0,16,16H75.06l37.78,75.58A8,8,0,0,0,120,240a40,40,0,0,0,40-40V184h56a24,24,0,0,0,23.82-27ZM72,144H32V56H72Zm150,21.29a7.88,7.88,0,0,1-6,2.71H152a8,8,0,0,0-8,8v24a24,24,0,0,1-19.29,23.54L88,150.11V56H204a8,8,0,0,1,7.94,7l12,96A7.87,7.87,0,0,1,222,165.29Z\"/></svg>"), v("arrow-right", "<svg viewBox=\"0 0 256 256\" fill=\"currentColor\"><path d=\"M221.66,133.66l-72,72a8,8,0,0,1-11.32-11.32L196.69,136H40a8,8,0,0,1,0-16H196.69L138.34,61.66a8,8,0,0,1,11.32-11.32l72,72A8,8,0,0,1,221.66,133.66Z\"/></svg>");
4
4
  export { c as NChatAvatar, o as NChatFeed, n as NChatInput, u as NChatInputStructured, t as NChatMessage, i as NChatMessageActivity, s as NChatMessageGenUI, l as NChatMessageSeed, e as NChatMessageText, a as NChatMessages, r as NChatPanel };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nonoun/native-chat",
3
- "version": "0.5.3",
3
+ "version": "0.5.8",
4
4
  "description": "Chat input and message layout components for @nonoun/native-ui",
5
5
  "license": "MIT",
6
6
  "type": "module",