@particle-academy/react-fancy 5.70.0 → 5.71.0

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 (74) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +1 -0
  3. package/dist/autocomplete.cjs +3 -3
  4. package/dist/autocomplete.js +2 -2
  5. package/dist/chat-thread.cjs +27 -0
  6. package/dist/chat-thread.cjs.map +1 -0
  7. package/dist/chat-thread.d.cts +260 -0
  8. package/dist/chat-thread.d.ts +260 -0
  9. package/dist/chat-thread.js +14 -0
  10. package/dist/chat-thread.js.map +1 -0
  11. package/dist/chunk-5IE65DIH.cjs +623 -0
  12. package/dist/chunk-5IE65DIH.cjs.map +1 -0
  13. package/dist/{chunk-6QAF35AJ.cjs → chunk-7JLKOVHX.cjs} +3 -3
  14. package/dist/{chunk-6QAF35AJ.cjs.map → chunk-7JLKOVHX.cjs.map} +1 -1
  15. package/dist/{chunk-TCMOD3WW.js → chunk-A6NYZ3F5.js} +3 -3
  16. package/dist/{chunk-TCMOD3WW.js.map → chunk-A6NYZ3F5.js.map} +1 -1
  17. package/dist/{chunk-6EA3DSPT.cjs → chunk-FGOFG2UZ.cjs} +5 -5
  18. package/dist/{chunk-6EA3DSPT.cjs.map → chunk-FGOFG2UZ.cjs.map} +1 -1
  19. package/dist/{chunk-J7S42MJP.js → chunk-FYUEDPST.js} +39 -20
  20. package/dist/chunk-FYUEDPST.js.map +1 -0
  21. package/dist/{chunk-HN5J7TK6.js → chunk-GDH43QH6.js} +21 -8
  22. package/dist/chunk-GDH43QH6.js.map +1 -0
  23. package/dist/{chunk-AC3W3TIV.js → chunk-J6IQHJET.js} +14 -5
  24. package/dist/chunk-J6IQHJET.js.map +1 -0
  25. package/dist/{chunk-XNHAJQRF.cjs → chunk-JKD6CFIP.cjs} +14 -5
  26. package/dist/chunk-JKD6CFIP.cjs.map +1 -0
  27. package/dist/{chunk-FHRVSYJU.cjs → chunk-MP55W37V.cjs} +3 -3
  28. package/dist/{chunk-FHRVSYJU.cjs.map → chunk-MP55W37V.cjs.map} +1 -1
  29. package/dist/{chunk-LBSAWRFT.cjs → chunk-NPSKZ7ZB.cjs} +22 -9
  30. package/dist/chunk-NPSKZ7ZB.cjs.map +1 -0
  31. package/dist/{chunk-EZL5DILH.js → chunk-OPDHSOUN.js} +3 -3
  32. package/dist/{chunk-EZL5DILH.js.map → chunk-OPDHSOUN.js.map} +1 -1
  33. package/dist/{chunk-XAKZ6VBP.cjs → chunk-PS7C6GKI.cjs} +40 -21
  34. package/dist/chunk-PS7C6GKI.cjs.map +1 -0
  35. package/dist/chunk-UWYSJARZ.js +620 -0
  36. package/dist/chunk-UWYSJARZ.js.map +1 -0
  37. package/dist/{chunk-M6UAVA2I.js → chunk-XIA2UAVO.js} +3 -3
  38. package/dist/{chunk-M6UAVA2I.js.map → chunk-XIA2UAVO.js.map} +1 -1
  39. package/dist/composer.cjs +3 -3
  40. package/dist/composer.d.cts +3 -2
  41. package/dist/composer.d.ts +3 -2
  42. package/dist/composer.js +2 -2
  43. package/dist/editor.cjs +3 -3
  44. package/dist/editor.js +3 -3
  45. package/dist/{index-BAtF94lw.d.ts → index-B8OkuYSt.d.ts} +3 -2
  46. package/dist/{index-xwL2MACB.d.cts → index-BMVQnKTH.d.cts} +3 -2
  47. package/dist/index.cjs +170 -161
  48. package/dist/index.cjs.map +1 -1
  49. package/dist/index.d.cts +2 -1
  50. package/dist/index.d.ts +2 -1
  51. package/dist/index.js +26 -25
  52. package/dist/index.js.map +1 -1
  53. package/dist/input.cjs +1 -1
  54. package/dist/input.js +1 -1
  55. package/dist/inputs.cjs +7 -7
  56. package/dist/inputs.js +3 -3
  57. package/dist/json-editor.cjs +16 -16
  58. package/dist/json-editor.js +5 -5
  59. package/dist/prompt-input.cjs +6 -6
  60. package/dist/prompt-input.d.cts +1 -1
  61. package/dist/prompt-input.d.ts +1 -1
  62. package/dist/prompt-input.js +3 -3
  63. package/dist/select.cjs +3 -3
  64. package/dist/select.js +2 -2
  65. package/docs/ChatThread.md +318 -0
  66. package/docs/Composer.md +5 -2
  67. package/docs/PromptInput.md +8 -2
  68. package/package.json +11 -1
  69. package/dist/chunk-AC3W3TIV.js.map +0 -1
  70. package/dist/chunk-HN5J7TK6.js.map +0 -1
  71. package/dist/chunk-J7S42MJP.js.map +0 -1
  72. package/dist/chunk-LBSAWRFT.cjs.map +0 -1
  73. package/dist/chunk-XAKZ6VBP.cjs.map +0 -1
  74. package/dist/chunk-XNHAJQRF.cjs.map +0 -1
package/CHANGELOG.md CHANGED
@@ -11,6 +11,55 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
11
11
 
12
12
  ## [Unreleased]
13
13
 
14
+ ## [5.71.0] - 2026-10-27
15
+
16
+ ### Added
17
+
18
+ - **`ChatThread` and `ChatMessage`**: a controlled message list for agent and
19
+ support conversations, built for charlotte-ui's side panel and
20
+ fancy-support's admin chat and ticket thread. Messages are JSON
21
+ (`ChatMessageData`: `id`, `role`, `author`, markdown `content`, `createdAt`,
22
+ `status`, `visibility`, `attachments`, `proposal`, `meta`). Content renders
23
+ through `ContentRenderer`, always sanitised (there is no `unsafe` prop), and a
24
+ `streaming` message uses its `streaming` / `"progressive"` mode and is
25
+ `aria-busy`. The log follows new messages only while the reader is at the
26
+ bottom; a reader who scrolled up stays put and gets an "N new messages" jump
27
+ control, and prepended history keeps their place. Only the log's own
28
+ `scrollTop` is set and focus uses `preventScroll`, so nothing outside the log
29
+ scrolls. `visibility: "internal"` is labelled and styled, never filtered: the
30
+ host passes only what the viewer may see. A message with a `proposal` is an
31
+ approval card (`data-chat-proposal`) with accept / reject callbacks; it never
32
+ acts on its own, and a returned Promise disables both decisions until it
33
+ settles. Also: `onLoadEarlier`, day separators, consecutive grouping, a
34
+ per-message actions slot, a composer slot (for `Composer` / `PromptInput`;
35
+ no new input), `onActivity` for every interaction, `role="log"` with polite
36
+ updates, and a `ref` handle with `scrollToLatest` / `scrollToMessage`. New
37
+ subpath `@particle-academy/react-fancy/chat-thread`. Nothing to do on upgrade.
38
+
39
+ ## [5.70.1] - 2026-10-27
40
+
41
+ ### Fixed
42
+
43
+ - **An acknowledged send no longer clears a newer draft that happens to read
44
+ the same** (`Composer`, `PromptInput`, from 5.70.0). When an async `onSubmit`
45
+ resolved, the draft was cleared if its text and the ids of its pills and
46
+ attachments still matched what was sent. A newer draft that compared equal was
47
+ therefore thrown away: an edit typed and then deleted during the send, a host
48
+ `setDraft` that restored the same draft, or a pill's text or an attachment
49
+ changed under the same id. The clear is now tied to the draft revision that
50
+ was sent. Anything that changes the draft after the send (the person, the
51
+ host's `setDraft`, a controlled `draft` / `value` / `heldPastes` /
52
+ `heldImages` that now says something different) makes it newer, and a newer
53
+ draft is kept whole. A `ref` arriving from `persistAttachment` mid-send is not
54
+ a change, so it does not block the clear. A controlled host that echoes a
55
+ copy of each draft is still cleared, because an echo that says the same thing
56
+ is not a new revision. Nothing to do on upgrade.
57
+ - **Only the newest send can acknowledge.** Both components refuse a second
58
+ send while one is pending, and nothing turns that off. If two ever overlapped,
59
+ the older one settling would have cleared the draft and ended the newer one's
60
+ `submitting` state. It is now ignored, except that its failure still reaches
61
+ `onSubmitError`.
62
+
14
63
  ## [5.70.0] - 2026-10-27
15
64
 
16
65
  ### Added
package/README.md CHANGED
@@ -161,6 +161,7 @@ npx vite build # Build demo app (verifies imports work)
161
161
  | Component | Description | Docs |
162
162
  |-----------|-------------|------|
163
163
  | Composer | Chat-style message input composing textarea + actions | [docs](docs/Composer.md) |
164
+ | ChatThread | Controlled message list for agent and support chat: sanitised markdown, streaming, internal notes, approval cards, stick-to-bottom without yanking | [docs](docs/ChatThread.md) |
164
165
  | Chart | SVG-based Bar, Line, Area, Pie, Donut, and Sparkline charts | [docs](docs/Chart.md) |
165
166
  | Editor | Toolbar chrome wrapper for contentEditable, with a Source toggle to edit raw HTML/Markdown | [docs](docs/Editor.md) |
166
167
  | CodeView | Lightweight syntax-highlighted source view (HTML highlighted; fills height) | [docs](docs/CodeView.md) |
@@ -1,9 +1,9 @@
1
1
  'use strict';
2
2
 
3
- var chunkFHRVSYJU_cjs = require('./chunk-FHRVSYJU.cjs');
3
+ var chunkMP55W37V_cjs = require('./chunk-MP55W37V.cjs');
4
+ require('./chunk-J2DI6RMR.cjs');
4
5
  require('./chunk-KJ22KE56.cjs');
5
6
  require('./chunk-L3V3BYVS.cjs');
6
- require('./chunk-J2DI6RMR.cjs');
7
7
  require('./chunk-6V44RRSB.cjs');
8
8
  require('./chunk-MKHYENKR.cjs');
9
9
  require('./chunk-TI7D4QDM.cjs');
@@ -16,7 +16,7 @@ require('./chunk-ZU5Q77OT.cjs');
16
16
 
17
17
  Object.defineProperty(exports, "Autocomplete", {
18
18
  enumerable: true,
19
- get: function () { return chunkFHRVSYJU_cjs.Autocomplete; }
19
+ get: function () { return chunkMP55W37V_cjs.Autocomplete; }
20
20
  });
21
21
  //# sourceMappingURL=autocomplete.cjs.map
22
22
  //# sourceMappingURL=autocomplete.cjs.map
@@ -1,7 +1,7 @@
1
- export { Autocomplete } from './chunk-TCMOD3WW.js';
1
+ export { Autocomplete } from './chunk-A6NYZ3F5.js';
2
+ import './chunk-MQ6VGG62.js';
2
3
  import './chunk-OL53XYHF.js';
3
4
  import './chunk-NRXK42LO.js';
4
- import './chunk-MQ6VGG62.js';
5
5
  import './chunk-S42OGG43.js';
6
6
  import './chunk-5VMILVWD.js';
7
7
  import './chunk-RBCMKPBX.js';
@@ -0,0 +1,27 @@
1
+ 'use strict';
2
+
3
+ var chunk5IE65DIH_cjs = require('./chunk-5IE65DIH.cjs');
4
+ require('./chunk-S3TRSECD.cjs');
5
+ require('./chunk-R3EJNGJV.cjs');
6
+ require('./chunk-2C64OVOZ.cjs');
7
+ require('./chunk-BJL3VDRK.cjs');
8
+ require('./chunk-VBWRH4UW.cjs');
9
+ require('./chunk-MOU6O324.cjs');
10
+ require('./chunk-M5MUGSON.cjs');
11
+ require('./chunk-3U7PUZLI.cjs');
12
+ require('./chunk-XCZZVRBY.cjs');
13
+ require('./chunk-MXWR3T3N.cjs');
14
+ require('./chunk-ZU5Q77OT.cjs');
15
+
16
+
17
+
18
+ Object.defineProperty(exports, "ChatMessage", {
19
+ enumerable: true,
20
+ get: function () { return chunk5IE65DIH_cjs.ChatMessage; }
21
+ });
22
+ Object.defineProperty(exports, "ChatThread", {
23
+ enumerable: true,
24
+ get: function () { return chunk5IE65DIH_cjs.ChatThread; }
25
+ });
26
+ //# sourceMappingURL=chat-thread.cjs.map
27
+ //# sourceMappingURL=chat-thread.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"chat-thread.cjs"}
@@ -0,0 +1,260 @@
1
+ import * as react from 'react';
2
+ import { ReactNode, HTMLAttributes, Ref } from 'react';
3
+ import { d as ContentRendererProps } from './ContentRenderer.types-RO_l7_6N.cjs';
4
+
5
+ /**
6
+ * Who a message is from. The named roles get default labels and styling; any
7
+ * other string is carried verbatim (`data-chat-role`) and rendered neutrally.
8
+ */
9
+ type ChatRole = "user" | "assistant" | "system" | "agent" | "staff" | "customer" | (string & {});
10
+ type ChatMessageStatus = "pending" | "streaming" | "sent" | "failed";
11
+ /**
12
+ * `internal` is a staff-only note. The component LABELS it; it never decides
13
+ * who sees it. Pass only the messages the viewer is allowed to see.
14
+ */
15
+ type ChatMessageVisibility = "public" | "internal";
16
+ interface ChatAuthor {
17
+ name: string;
18
+ /** Image URL for the avatar. Without it, initials are shown. */
19
+ avatar?: string;
20
+ }
21
+ /** A file on a message. Rendered as a link, never auto-loaded inline. */
22
+ interface ChatAttachment {
23
+ id: string;
24
+ name: string;
25
+ /** Unsafe schemes (`javascript:`, `data:`, ...) render the name without a link. */
26
+ url?: string;
27
+ mimeType?: string;
28
+ /** Bytes. */
29
+ size?: number;
30
+ }
31
+ /**
32
+ * An agent's suggested action awaiting a human: an approval card. The message
33
+ * `content` is the draft (a reply, a field change, a page edit). The state is
34
+ * the HOST's: the component shows decision buttons only while `pending`, and
35
+ * never changes the state itself.
36
+ */
37
+ interface ChatProposal {
38
+ /** Stable id, rendered as `data-chat-proposal`. Usually the card id. */
39
+ id: string;
40
+ title?: string;
41
+ /** @default "pending" */
42
+ state?: "pending" | "accepted" | "rejected" | "withdrawn";
43
+ /** Overrides the thread's accept / reject labels for this card. */
44
+ acceptLabel?: string;
45
+ rejectLabel?: string;
46
+ }
47
+ /** One message. Plain JSON: an agent can emit it, a server can send it. */
48
+ interface ChatMessageData {
49
+ /** Stable id, rendered as `data-chat-message`. */
50
+ id: string;
51
+ role: ChatRole;
52
+ author?: ChatAuthor;
53
+ /** Markdown by default; see `format`. Always sanitised. */
54
+ content: string;
55
+ /** `"text"` renders the content literally (no markdown, no HTML). @default "markdown" */
56
+ format?: "markdown" | "text";
57
+ /** ISO 8601 string or epoch milliseconds. */
58
+ createdAt: string | number;
59
+ /** @default "sent" */
60
+ status?: ChatMessageStatus;
61
+ /** @default "public" */
62
+ visibility?: ChatMessageVisibility;
63
+ attachments?: ChatAttachment[];
64
+ /** Makes this message an approval card. */
65
+ proposal?: ChatProposal;
66
+ /**
67
+ * Align as the viewer's own message. Defaults to `ownRoles` containing
68
+ * `role`.
69
+ */
70
+ own?: boolean;
71
+ /** Anything the host wants carried along (card version, ticket ref, ...). Not rendered. */
72
+ meta?: Record<string, unknown>;
73
+ }
74
+ /**
75
+ * Every human interaction the thread hands to a callback, as one event stream
76
+ * for presence, undo and coaching layers. Map it onto fancy-auto-common's
77
+ * `AutoActivityEvent` (or anything else) in the host.
78
+ */
79
+ type ChatThreadActivity = {
80
+ type: "load-earlier";
81
+ } | {
82
+ type: "proposal-accept";
83
+ messageId: string;
84
+ proposalId: string;
85
+ } | {
86
+ type: "proposal-reject";
87
+ messageId: string;
88
+ proposalId: string;
89
+ } | {
90
+ type: "jump-to-latest";
91
+ unseen: number;
92
+ };
93
+ /** What `onActionError` is told about: the action whose Promise rejected. */
94
+ type ChatThreadActionContext = Exclude<ChatThreadActivity, {
95
+ type: "jump-to-latest";
96
+ }>;
97
+ /** A callback that may be acknowledged: return a Promise to disable the control until it settles. */
98
+ type ChatAction<A extends unknown[] = []> = (...args: A) => void | Promise<unknown>;
99
+ /** Every visible string, for translation. */
100
+ interface ChatThreadLabels {
101
+ /** The log's `aria-label` when there is no `heading`. */
102
+ log: string;
103
+ loadEarlier: string;
104
+ loadingEarlier: string;
105
+ /** The jump control's text. */
106
+ newMessages: (count: number) => string;
107
+ internal: string;
108
+ pending: string;
109
+ failed: string;
110
+ proposal: string;
111
+ accept: string;
112
+ reject: string;
113
+ accepted: string;
114
+ rejected: string;
115
+ withdrawn: string;
116
+ attachments: string;
117
+ /** Role names shown when a message has no `author`. */
118
+ roles: Record<string, string>;
119
+ }
120
+ /** Content options passed to every message's `ContentRenderer`. `unsafe` is deliberately absent. */
121
+ type ChatContentOptions = Pick<ContentRendererProps, "extensions" | "copyButtons" | "onCopy" | "clipboardWriter" | "lineSpacing">;
122
+ interface ChatMessageClassNames {
123
+ message?: string;
124
+ bubble?: string;
125
+ header?: string;
126
+ actions?: string;
127
+ proposal?: string;
128
+ }
129
+ /** Display options shared by `ChatThread` and a standalone `ChatMessage`. */
130
+ interface ChatMessageDisplayProps {
131
+ /** Roles aligned as the viewer's own. @default ["user"] */
132
+ ownRoles?: string[];
133
+ /**
134
+ * How a message with `status: "streaming"` renders its unfinished tail
135
+ * (`ContentRenderer`'s `streaming`). `true` holds back an open fence or tag;
136
+ * `"progressive"` shows it as it fills. @default true
137
+ */
138
+ streaming?: true | "progressive";
139
+ contentOptions?: ChatContentOptions;
140
+ labels?: Partial<ChatThreadLabels>;
141
+ /** Formats a message time. Default: locale time, hours and minutes. */
142
+ formatTime?: (date: Date) => string;
143
+ /** Replace how one attachment renders (it is still wrapped in `[data-chat-attachment]`). */
144
+ renderAttachment?: (attachment: ChatAttachment, message: ChatMessageData) => ReactNode;
145
+ onProposalAccept?: ChatAction<[message: ChatMessageData]>;
146
+ onProposalReject?: ChatAction<[message: ChatMessageData]>;
147
+ onActivity?: (event: ChatThreadActivity) => void;
148
+ /**
149
+ * Called when a Promise returned by `onLoadEarlier`, `onProposalAccept` or
150
+ * `onProposalReject` rejects. Without it the rejection is left unhandled,
151
+ * so it still reaches the console rather than vanishing.
152
+ */
153
+ onActionError?: (error: unknown, action: ChatThreadActionContext) => void;
154
+ classNames?: ChatMessageClassNames;
155
+ }
156
+ interface ChatMessageProps extends ChatMessageDisplayProps {
157
+ message: ChatMessageData;
158
+ /** The per-message actions slot (copy, edit, retry, ...). */
159
+ actions?: ReactNode;
160
+ /** A continuation of the previous message's group: the header is omitted. */
161
+ grouped?: boolean;
162
+ className?: string;
163
+ }
164
+ interface ChatThreadClassNames extends ChatMessageClassNames {
165
+ root?: string;
166
+ heading?: string;
167
+ log?: string;
168
+ composer?: string;
169
+ separator?: string;
170
+ jump?: string;
171
+ }
172
+ /** Imperative handle, for a host or an agent bridge pointing at a message. */
173
+ interface ChatThreadHandle {
174
+ /** Scroll the log (only the log) to the newest message. */
175
+ scrollToLatest(): void;
176
+ /** Scroll the log (only the log) to a message. `false` if no such message is rendered. */
177
+ scrollToMessage(id: string): boolean;
178
+ /** The log element. */
179
+ readonly element: HTMLDivElement | null;
180
+ }
181
+ interface ChatThreadProps extends ChatMessageDisplayProps, Omit<HTMLAttributes<HTMLDivElement>, "children" | "onChange"> {
182
+ ref?: Ref<ChatThreadHandle>;
183
+ /** The messages, oldest first. Controlled: the thread renders exactly these. */
184
+ messages: ChatMessageData[];
185
+ /** Visible title; the log is labelled by it. Without it the log gets `labels.log`. */
186
+ heading?: ReactNode;
187
+ /** @default 2 */
188
+ headingLevel?: 2 | 3 | 4 | 5 | 6;
189
+ /** Shows a "Load earlier messages" control. Return a Promise to disable it until it settles. */
190
+ onLoadEarlier?: ChatAction;
191
+ /** Whether there is anything earlier to load. @default true when `onLoadEarlier` is set */
192
+ hasEarlier?: boolean;
193
+ /** Host-controlled loading state for the load-earlier control. */
194
+ loadingEarlier?: boolean;
195
+ /** Follow new messages while the reader is at the bottom. @default true */
196
+ stickToBottom?: boolean;
197
+ /** How close to the bottom (px) still counts as "at the bottom". @default 48 */
198
+ bottomThreshold?: number;
199
+ /** Called when the reader arrives at or leaves the bottom. */
200
+ onAtBottomChange?: (atBottom: boolean) => void;
201
+ /** `"day"` inserts a separator at each calendar-day change (local time). @default "none" */
202
+ separators?: "none" | "day";
203
+ /** Formats a day separator. Default: locale date, medium style. */
204
+ formatDay?: (date: Date) => string;
205
+ /**
206
+ * Merge consecutive messages from the same author: `true` within 5 minutes,
207
+ * or a window in milliseconds. Internal and public messages never merge.
208
+ * @default false
209
+ */
210
+ groupConsecutive?: boolean | number;
211
+ /** The per-message actions slot. Return `null` for none. */
212
+ renderActions?: (message: ChatMessageData) => ReactNode;
213
+ /** Rendered below the log: put a `Composer` or `PromptInput` here. */
214
+ composer?: ReactNode;
215
+ /** Shown in the log when there are no messages. */
216
+ empty?: ReactNode;
217
+ classNames?: ChatThreadClassNames;
218
+ }
219
+
220
+ /**
221
+ * ChatThread — a controlled message list for agent and support conversations.
222
+ *
223
+ * - **Controlled.** It renders exactly `messages`, oldest first. Loading
224
+ * earlier history and deciding a proposal go through callbacks; nothing is
225
+ * held internally that an agent would need to read or write.
226
+ * - **Stick to the bottom without yanking.** New messages are followed only
227
+ * while the reader is at the bottom. A reader who scrolled up stays put and
228
+ * gets an "N new messages" jump control. Prepended history keeps the
229
+ * reader's place. Only the log's own `scrollTop` is ever set, and focus moves
230
+ * with `preventScroll`, so no ancestor (or the page) scrolls (the 5.69 rule).
231
+ * - **Visibility is the host's.** `visibility: "internal"` is labelled and
232
+ * styled, never filtered: pass only what the viewer may see.
233
+ * - **Handles:** `data-react-fancy-chat-thread`, `data-chat-message="<id>"`,
234
+ * `data-chat-role`, `data-chat-status`, `data-chat-visibility`,
235
+ * `data-chat-proposal="<id>"`, `data-chat-proposal-action`.
236
+ * - **Observable:** `onActivity` reports every interaction as one stream.
237
+ *
238
+ * Pair it with `Composer` or `PromptInput` in the `composer` slot (both have
239
+ * acknowledged send). It does not ship an input of its own.
240
+ */
241
+ declare function ChatThread({ ref, messages, heading, headingLevel, onLoadEarlier, hasEarlier, loadingEarlier, stickToBottom, bottomThreshold, onAtBottomChange, separators, formatDay, groupConsecutive, renderActions, composer, empty, classNames, className, ownRoles, streaming, contentOptions, labels: labelsProp, formatTime, renderAttachment, onProposalAccept, onProposalReject, onActivity, onActionError, onScroll: onScrollProp, ...rest }: ChatThreadProps): react.JSX.Element;
242
+ declare namespace ChatThread {
243
+ var displayName: string;
244
+ }
245
+
246
+ /**
247
+ * ChatMessage — one message of a `ChatThread`, usable on its own.
248
+ *
249
+ * Content is markdown through `ContentRenderer`, always sanitised (there is no
250
+ * `unsafe` here on purpose: message bodies are text the host did not author).
251
+ * `format: "text"` renders it literally. A message with a `proposal` is an
252
+ * approval card: the host owns its state and decides it through
253
+ * `onProposalAccept` / `onProposalReject`; the card never acts on its own.
254
+ */
255
+ declare function ChatMessage({ message, actions, grouped, className, ownRoles, streaming, contentOptions, labels: labelsProp, formatTime, renderAttachment, onProposalAccept, onProposalReject, onActivity, onActionError, classNames, }: ChatMessageProps): react.JSX.Element;
256
+ declare namespace ChatMessage {
257
+ var displayName: string;
258
+ }
259
+
260
+ export { type ChatAction, type ChatAttachment, type ChatAuthor, type ChatContentOptions, ChatMessage, type ChatMessageClassNames, type ChatMessageData, type ChatMessageDisplayProps, type ChatMessageProps, type ChatMessageStatus, type ChatMessageVisibility, type ChatProposal, type ChatRole, ChatThread, type ChatThreadActionContext, type ChatThreadActivity, type ChatThreadClassNames, type ChatThreadHandle, type ChatThreadLabels, type ChatThreadProps };
@@ -0,0 +1,260 @@
1
+ import * as react from 'react';
2
+ import { ReactNode, HTMLAttributes, Ref } from 'react';
3
+ import { d as ContentRendererProps } from './ContentRenderer.types-RO_l7_6N.js';
4
+
5
+ /**
6
+ * Who a message is from. The named roles get default labels and styling; any
7
+ * other string is carried verbatim (`data-chat-role`) and rendered neutrally.
8
+ */
9
+ type ChatRole = "user" | "assistant" | "system" | "agent" | "staff" | "customer" | (string & {});
10
+ type ChatMessageStatus = "pending" | "streaming" | "sent" | "failed";
11
+ /**
12
+ * `internal` is a staff-only note. The component LABELS it; it never decides
13
+ * who sees it. Pass only the messages the viewer is allowed to see.
14
+ */
15
+ type ChatMessageVisibility = "public" | "internal";
16
+ interface ChatAuthor {
17
+ name: string;
18
+ /** Image URL for the avatar. Without it, initials are shown. */
19
+ avatar?: string;
20
+ }
21
+ /** A file on a message. Rendered as a link, never auto-loaded inline. */
22
+ interface ChatAttachment {
23
+ id: string;
24
+ name: string;
25
+ /** Unsafe schemes (`javascript:`, `data:`, ...) render the name without a link. */
26
+ url?: string;
27
+ mimeType?: string;
28
+ /** Bytes. */
29
+ size?: number;
30
+ }
31
+ /**
32
+ * An agent's suggested action awaiting a human: an approval card. The message
33
+ * `content` is the draft (a reply, a field change, a page edit). The state is
34
+ * the HOST's: the component shows decision buttons only while `pending`, and
35
+ * never changes the state itself.
36
+ */
37
+ interface ChatProposal {
38
+ /** Stable id, rendered as `data-chat-proposal`. Usually the card id. */
39
+ id: string;
40
+ title?: string;
41
+ /** @default "pending" */
42
+ state?: "pending" | "accepted" | "rejected" | "withdrawn";
43
+ /** Overrides the thread's accept / reject labels for this card. */
44
+ acceptLabel?: string;
45
+ rejectLabel?: string;
46
+ }
47
+ /** One message. Plain JSON: an agent can emit it, a server can send it. */
48
+ interface ChatMessageData {
49
+ /** Stable id, rendered as `data-chat-message`. */
50
+ id: string;
51
+ role: ChatRole;
52
+ author?: ChatAuthor;
53
+ /** Markdown by default; see `format`. Always sanitised. */
54
+ content: string;
55
+ /** `"text"` renders the content literally (no markdown, no HTML). @default "markdown" */
56
+ format?: "markdown" | "text";
57
+ /** ISO 8601 string or epoch milliseconds. */
58
+ createdAt: string | number;
59
+ /** @default "sent" */
60
+ status?: ChatMessageStatus;
61
+ /** @default "public" */
62
+ visibility?: ChatMessageVisibility;
63
+ attachments?: ChatAttachment[];
64
+ /** Makes this message an approval card. */
65
+ proposal?: ChatProposal;
66
+ /**
67
+ * Align as the viewer's own message. Defaults to `ownRoles` containing
68
+ * `role`.
69
+ */
70
+ own?: boolean;
71
+ /** Anything the host wants carried along (card version, ticket ref, ...). Not rendered. */
72
+ meta?: Record<string, unknown>;
73
+ }
74
+ /**
75
+ * Every human interaction the thread hands to a callback, as one event stream
76
+ * for presence, undo and coaching layers. Map it onto fancy-auto-common's
77
+ * `AutoActivityEvent` (or anything else) in the host.
78
+ */
79
+ type ChatThreadActivity = {
80
+ type: "load-earlier";
81
+ } | {
82
+ type: "proposal-accept";
83
+ messageId: string;
84
+ proposalId: string;
85
+ } | {
86
+ type: "proposal-reject";
87
+ messageId: string;
88
+ proposalId: string;
89
+ } | {
90
+ type: "jump-to-latest";
91
+ unseen: number;
92
+ };
93
+ /** What `onActionError` is told about: the action whose Promise rejected. */
94
+ type ChatThreadActionContext = Exclude<ChatThreadActivity, {
95
+ type: "jump-to-latest";
96
+ }>;
97
+ /** A callback that may be acknowledged: return a Promise to disable the control until it settles. */
98
+ type ChatAction<A extends unknown[] = []> = (...args: A) => void | Promise<unknown>;
99
+ /** Every visible string, for translation. */
100
+ interface ChatThreadLabels {
101
+ /** The log's `aria-label` when there is no `heading`. */
102
+ log: string;
103
+ loadEarlier: string;
104
+ loadingEarlier: string;
105
+ /** The jump control's text. */
106
+ newMessages: (count: number) => string;
107
+ internal: string;
108
+ pending: string;
109
+ failed: string;
110
+ proposal: string;
111
+ accept: string;
112
+ reject: string;
113
+ accepted: string;
114
+ rejected: string;
115
+ withdrawn: string;
116
+ attachments: string;
117
+ /** Role names shown when a message has no `author`. */
118
+ roles: Record<string, string>;
119
+ }
120
+ /** Content options passed to every message's `ContentRenderer`. `unsafe` is deliberately absent. */
121
+ type ChatContentOptions = Pick<ContentRendererProps, "extensions" | "copyButtons" | "onCopy" | "clipboardWriter" | "lineSpacing">;
122
+ interface ChatMessageClassNames {
123
+ message?: string;
124
+ bubble?: string;
125
+ header?: string;
126
+ actions?: string;
127
+ proposal?: string;
128
+ }
129
+ /** Display options shared by `ChatThread` and a standalone `ChatMessage`. */
130
+ interface ChatMessageDisplayProps {
131
+ /** Roles aligned as the viewer's own. @default ["user"] */
132
+ ownRoles?: string[];
133
+ /**
134
+ * How a message with `status: "streaming"` renders its unfinished tail
135
+ * (`ContentRenderer`'s `streaming`). `true` holds back an open fence or tag;
136
+ * `"progressive"` shows it as it fills. @default true
137
+ */
138
+ streaming?: true | "progressive";
139
+ contentOptions?: ChatContentOptions;
140
+ labels?: Partial<ChatThreadLabels>;
141
+ /** Formats a message time. Default: locale time, hours and minutes. */
142
+ formatTime?: (date: Date) => string;
143
+ /** Replace how one attachment renders (it is still wrapped in `[data-chat-attachment]`). */
144
+ renderAttachment?: (attachment: ChatAttachment, message: ChatMessageData) => ReactNode;
145
+ onProposalAccept?: ChatAction<[message: ChatMessageData]>;
146
+ onProposalReject?: ChatAction<[message: ChatMessageData]>;
147
+ onActivity?: (event: ChatThreadActivity) => void;
148
+ /**
149
+ * Called when a Promise returned by `onLoadEarlier`, `onProposalAccept` or
150
+ * `onProposalReject` rejects. Without it the rejection is left unhandled,
151
+ * so it still reaches the console rather than vanishing.
152
+ */
153
+ onActionError?: (error: unknown, action: ChatThreadActionContext) => void;
154
+ classNames?: ChatMessageClassNames;
155
+ }
156
+ interface ChatMessageProps extends ChatMessageDisplayProps {
157
+ message: ChatMessageData;
158
+ /** The per-message actions slot (copy, edit, retry, ...). */
159
+ actions?: ReactNode;
160
+ /** A continuation of the previous message's group: the header is omitted. */
161
+ grouped?: boolean;
162
+ className?: string;
163
+ }
164
+ interface ChatThreadClassNames extends ChatMessageClassNames {
165
+ root?: string;
166
+ heading?: string;
167
+ log?: string;
168
+ composer?: string;
169
+ separator?: string;
170
+ jump?: string;
171
+ }
172
+ /** Imperative handle, for a host or an agent bridge pointing at a message. */
173
+ interface ChatThreadHandle {
174
+ /** Scroll the log (only the log) to the newest message. */
175
+ scrollToLatest(): void;
176
+ /** Scroll the log (only the log) to a message. `false` if no such message is rendered. */
177
+ scrollToMessage(id: string): boolean;
178
+ /** The log element. */
179
+ readonly element: HTMLDivElement | null;
180
+ }
181
+ interface ChatThreadProps extends ChatMessageDisplayProps, Omit<HTMLAttributes<HTMLDivElement>, "children" | "onChange"> {
182
+ ref?: Ref<ChatThreadHandle>;
183
+ /** The messages, oldest first. Controlled: the thread renders exactly these. */
184
+ messages: ChatMessageData[];
185
+ /** Visible title; the log is labelled by it. Without it the log gets `labels.log`. */
186
+ heading?: ReactNode;
187
+ /** @default 2 */
188
+ headingLevel?: 2 | 3 | 4 | 5 | 6;
189
+ /** Shows a "Load earlier messages" control. Return a Promise to disable it until it settles. */
190
+ onLoadEarlier?: ChatAction;
191
+ /** Whether there is anything earlier to load. @default true when `onLoadEarlier` is set */
192
+ hasEarlier?: boolean;
193
+ /** Host-controlled loading state for the load-earlier control. */
194
+ loadingEarlier?: boolean;
195
+ /** Follow new messages while the reader is at the bottom. @default true */
196
+ stickToBottom?: boolean;
197
+ /** How close to the bottom (px) still counts as "at the bottom". @default 48 */
198
+ bottomThreshold?: number;
199
+ /** Called when the reader arrives at or leaves the bottom. */
200
+ onAtBottomChange?: (atBottom: boolean) => void;
201
+ /** `"day"` inserts a separator at each calendar-day change (local time). @default "none" */
202
+ separators?: "none" | "day";
203
+ /** Formats a day separator. Default: locale date, medium style. */
204
+ formatDay?: (date: Date) => string;
205
+ /**
206
+ * Merge consecutive messages from the same author: `true` within 5 minutes,
207
+ * or a window in milliseconds. Internal and public messages never merge.
208
+ * @default false
209
+ */
210
+ groupConsecutive?: boolean | number;
211
+ /** The per-message actions slot. Return `null` for none. */
212
+ renderActions?: (message: ChatMessageData) => ReactNode;
213
+ /** Rendered below the log: put a `Composer` or `PromptInput` here. */
214
+ composer?: ReactNode;
215
+ /** Shown in the log when there are no messages. */
216
+ empty?: ReactNode;
217
+ classNames?: ChatThreadClassNames;
218
+ }
219
+
220
+ /**
221
+ * ChatThread — a controlled message list for agent and support conversations.
222
+ *
223
+ * - **Controlled.** It renders exactly `messages`, oldest first. Loading
224
+ * earlier history and deciding a proposal go through callbacks; nothing is
225
+ * held internally that an agent would need to read or write.
226
+ * - **Stick to the bottom without yanking.** New messages are followed only
227
+ * while the reader is at the bottom. A reader who scrolled up stays put and
228
+ * gets an "N new messages" jump control. Prepended history keeps the
229
+ * reader's place. Only the log's own `scrollTop` is ever set, and focus moves
230
+ * with `preventScroll`, so no ancestor (or the page) scrolls (the 5.69 rule).
231
+ * - **Visibility is the host's.** `visibility: "internal"` is labelled and
232
+ * styled, never filtered: pass only what the viewer may see.
233
+ * - **Handles:** `data-react-fancy-chat-thread`, `data-chat-message="<id>"`,
234
+ * `data-chat-role`, `data-chat-status`, `data-chat-visibility`,
235
+ * `data-chat-proposal="<id>"`, `data-chat-proposal-action`.
236
+ * - **Observable:** `onActivity` reports every interaction as one stream.
237
+ *
238
+ * Pair it with `Composer` or `PromptInput` in the `composer` slot (both have
239
+ * acknowledged send). It does not ship an input of its own.
240
+ */
241
+ declare function ChatThread({ ref, messages, heading, headingLevel, onLoadEarlier, hasEarlier, loadingEarlier, stickToBottom, bottomThreshold, onAtBottomChange, separators, formatDay, groupConsecutive, renderActions, composer, empty, classNames, className, ownRoles, streaming, contentOptions, labels: labelsProp, formatTime, renderAttachment, onProposalAccept, onProposalReject, onActivity, onActionError, onScroll: onScrollProp, ...rest }: ChatThreadProps): react.JSX.Element;
242
+ declare namespace ChatThread {
243
+ var displayName: string;
244
+ }
245
+
246
+ /**
247
+ * ChatMessage — one message of a `ChatThread`, usable on its own.
248
+ *
249
+ * Content is markdown through `ContentRenderer`, always sanitised (there is no
250
+ * `unsafe` here on purpose: message bodies are text the host did not author).
251
+ * `format: "text"` renders it literally. A message with a `proposal` is an
252
+ * approval card: the host owns its state and decides it through
253
+ * `onProposalAccept` / `onProposalReject`; the card never acts on its own.
254
+ */
255
+ declare function ChatMessage({ message, actions, grouped, className, ownRoles, streaming, contentOptions, labels: labelsProp, formatTime, renderAttachment, onProposalAccept, onProposalReject, onActivity, onActionError, classNames, }: ChatMessageProps): react.JSX.Element;
256
+ declare namespace ChatMessage {
257
+ var displayName: string;
258
+ }
259
+
260
+ export { type ChatAction, type ChatAttachment, type ChatAuthor, type ChatContentOptions, ChatMessage, type ChatMessageClassNames, type ChatMessageData, type ChatMessageDisplayProps, type ChatMessageProps, type ChatMessageStatus, type ChatMessageVisibility, type ChatProposal, type ChatRole, ChatThread, type ChatThreadActionContext, type ChatThreadActivity, type ChatThreadClassNames, type ChatThreadHandle, type ChatThreadLabels, type ChatThreadProps };
@@ -0,0 +1,14 @@
1
+ export { ChatMessage, ChatThread } from './chunk-UWYSJARZ.js';
2
+ import './chunk-AUTW5O4W.js';
3
+ import './chunk-NAYXNVNU.js';
4
+ import './chunk-I3LAFKJU.js';
5
+ import './chunk-UZCR7DK5.js';
6
+ import './chunk-IQ4KQJAO.js';
7
+ import './chunk-6FCQD2KQ.js';
8
+ import './chunk-LQ4ICCN2.js';
9
+ import './chunk-OXERDFFF.js';
10
+ import './chunk-45BBNJQD.js';
11
+ import './chunk-J3A4Z2P7.js';
12
+ import './chunk-MMW2JGKZ.js';
13
+ //# sourceMappingURL=chat-thread.js.map
14
+ //# sourceMappingURL=chat-thread.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"chat-thread.js"}