@nonoun/native-chat 0.5.7 → 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
@@ -594,6 +594,7 @@ var p = class extends e {
594
594
  #o = null;
595
595
  #s = !1;
596
596
  #c = 0;
597
+ #l = 0;
597
598
  constructor() {
598
599
  super(), this.#e = this.attachInternals();
599
600
  }
@@ -656,40 +657,42 @@ var p = class extends e {
656
657
  }
657
658
  }
658
659
  setup() {
659
- super.setup(), this.addEventListener("pointerenter", this.#l), this.addEventListener("pointerleave", this.#u), this.addEffect(() => {
660
+ super.setup(), this.addEventListener("pointerenter", this.#u), this.addEventListener("pointerleave", this.#d), this.addEffect(() => {
660
661
  let e = this.#t.value, t = this.#r.value, n = this.#i.value, r = this.#a.value, i = this.#n.value;
661
- this.#f(e, t, n, r, i);
662
+ this.#p(e, t, n, r, i);
662
663
  }), this.#e.role = "article";
663
664
  }
664
665
  teardown() {
665
- this.removeEventListener("pointerenter", this.#l), this.removeEventListener("pointerleave", this.#u), clearTimeout(this.#c), this.#o &&= (this.#d(), null), super.teardown();
666
+ this.removeEventListener("pointerenter", this.#u), this.removeEventListener("pointerleave", this.#d), clearTimeout(this.#c), clearTimeout(this.#l), this.#o &&= (this.#f(), null), super.teardown();
666
667
  }
667
- #l = () => {
668
- if (clearTimeout(this.#c), this.#o && this.#s && this.#n.value !== "partial") try {
669
- this.#o.showPopover();
670
- } catch {}
671
- };
672
668
  #u = () => {
673
- !this.#s || this.#n.value === "partial" || (clearTimeout(this.#c), this.#c = window.setTimeout(() => {
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(() => {
674
677
  try {
675
678
  this.#o?.hidePopover();
676
679
  } catch {}
677
680
  }, 150));
678
681
  };
679
- #d() {
682
+ #f() {
680
683
  if (this.#o) {
681
684
  if (this.#s) {
682
- this.#o.removeEventListener("pointerenter", this.#l), this.#o.removeEventListener("pointerleave", this.#u), this.#o.removeEventListener("focusin", this.#l), this.#o.removeEventListener("focusout", this.#u);
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);
683
686
  try {
684
687
  this.#o.hidePopover();
685
688
  } catch {}
686
689
  this.style.removeProperty("anchor-name"), this.#s = !1;
687
690
  }
688
- this.#o.removeEventListener("native:press", this.#p), this.#o.remove();
691
+ this.#o.removeEventListener("native:press", this.#m), this.#o.remove();
689
692
  }
690
693
  }
691
- #f(e, t, n, r, a) {
692
- if (this.#o &&= (this.#d(), null), clearTimeout(this.#c), t === "none" || this.querySelector("[slot=\"actions\"]")) return;
694
+ #p(e, t, n, r, a) {
695
+ if (this.#o &&= (this.#f(), null), clearTimeout(this.#l), t === "none" || this.querySelector("[slot=\"actions\"]")) return;
693
696
  let o;
694
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;
695
698
  let s = document.createElement("n-toolbar");
@@ -699,15 +702,15 @@ var p = class extends e {
699
702
  t && s.appendChild(m(e, t, n));
700
703
  }
701
704
  if (s.children.length !== 0) {
702
- if (s.addEventListener("native:press", this.#p), r === "below") {
705
+ if (s.addEventListener("native:press", this.#m), r === "below") {
703
706
  s.setAttribute("popover", "manual");
704
707
  let e = i("msg");
705
- this.style.setProperty("anchor-name", `--${e}`), s.style.setProperty("position-anchor", `--${e}`), this.appendChild(s), this.#s = !0, s.addEventListener("pointerenter", this.#l), s.addEventListener("pointerleave", this.#u), s.addEventListener("focusin", this.#l), s.addEventListener("focusout", this.#u), a === "partial" && s.showPopover();
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();
706
709
  } else this.appendChild(s);
707
710
  this.#o = s;
708
711
  }
709
712
  }
710
- #p = (e) => {
713
+ #m = (e) => {
711
714
  let t = e.target?.getAttribute("data-action");
712
715
  if (t) {
713
716
  if (e.stopPropagation(), t === "continue") {
@@ -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,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;;IAmB1H,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;CAgJjB"}
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"}
@@ -384,7 +384,8 @@
384
384
 
385
385
  /* Popover toolbar: anchored below the message via CSS anchor positioning.
386
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(). */
387
+ top layer. JS shows/hides via pointerenter/pointerleave + showPopover().
388
+ Entry/exit transition mirrors n-listbox[popover] (perspective + scale). */
388
389
  :where(n-chat-message) > :where(n-toolbar[data-role="actions"][popover]) {
389
390
  position: fixed;
390
391
  inset: auto;
@@ -393,6 +394,28 @@
393
394
  translate: -50% 0;
394
395
  margin: 2px 0 0;
395
396
  width: max-content;
397
+
398
+ transform-origin: top center;
399
+ opacity: 0;
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;
407
+ }
408
+
409
+ :where(n-chat-message) > :where(n-toolbar[data-role="actions"][popover]):popover-open {
410
+ opacity: 1;
411
+ transform: none;
412
+ }
413
+
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
+ }
396
419
  }
397
420
 
398
421
  /* Override n-toolbar display to respect popover hidden state */
@@ -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-Bt9nkgXp.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
  *
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-Bt9nkgXp.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
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.7",
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",