@scalemule/chat 0.0.5 → 0.0.7

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 (85) hide show
  1. package/dist/{ChatClient-COmdEJ11.d.ts → ChatClient-DQPHdUHX.d.cts} +21 -2
  2. package/dist/{ChatClient-BoZaTtyM.d.cts → ChatClient-DtUKF-4c.d.ts} +21 -2
  3. package/dist/chat.embed.global.js +1 -1
  4. package/dist/chat.umd.global.js +288 -12
  5. package/dist/{chunk-ZLMMNFZL.js → chunk-5O5YLRJL.js} +386 -16
  6. package/dist/chunk-GTMAK3IA.js +285 -0
  7. package/dist/chunk-TRCELAZQ.cjs +287 -0
  8. package/dist/{chunk-YDLRISR7.cjs → chunk-W2PWFS3E.cjs} +386 -15
  9. package/dist/constants.d.ts +9 -0
  10. package/dist/constants.d.ts.map +1 -0
  11. package/dist/core/ChatClient.d.ts +96 -0
  12. package/dist/core/ChatClient.d.ts.map +1 -0
  13. package/dist/core/EventEmitter.d.ts +11 -0
  14. package/dist/core/EventEmitter.d.ts.map +1 -0
  15. package/dist/core/MessageCache.d.ts +18 -0
  16. package/dist/core/MessageCache.d.ts.map +1 -0
  17. package/dist/core/OfflineQueue.d.ts +19 -0
  18. package/dist/core/OfflineQueue.d.ts.map +1 -0
  19. package/dist/element.cjs +542 -51
  20. package/dist/element.d.ts +2 -2
  21. package/dist/element.d.ts.map +1 -0
  22. package/dist/element.js +541 -50
  23. package/dist/embed/index.d.ts +2 -0
  24. package/dist/embed/index.d.ts.map +1 -0
  25. package/dist/factory.d.ts +8 -0
  26. package/dist/factory.d.ts.map +1 -0
  27. package/dist/iframe.d.cts +1 -1
  28. package/dist/iframe.d.ts +3 -5
  29. package/dist/iframe.d.ts.map +1 -0
  30. package/dist/index.cjs +34 -5
  31. package/dist/index.d.cts +93 -4
  32. package/dist/index.d.ts +8 -77
  33. package/dist/index.d.ts.map +1 -0
  34. package/dist/index.js +29 -4
  35. package/dist/react-components/ChatInput.d.ts +16 -0
  36. package/dist/react-components/ChatInput.d.ts.map +1 -0
  37. package/dist/react-components/ChatMessageItem.d.ts +13 -0
  38. package/dist/react-components/ChatMessageItem.d.ts.map +1 -0
  39. package/dist/react-components/ChatMessageList.d.ts +15 -0
  40. package/dist/react-components/ChatMessageList.d.ts.map +1 -0
  41. package/dist/react-components/ChatThread.d.ts +12 -0
  42. package/dist/react-components/ChatThread.d.ts.map +1 -0
  43. package/dist/react-components/ConversationList.d.ts +13 -0
  44. package/dist/react-components/ConversationList.d.ts.map +1 -0
  45. package/dist/react-components/EmojiPicker.d.ts +8 -0
  46. package/dist/react-components/EmojiPicker.d.ts.map +1 -0
  47. package/dist/react-components/index.d.ts +8 -0
  48. package/dist/react-components/index.d.ts.map +1 -0
  49. package/dist/react-components/theme.d.ts +19 -0
  50. package/dist/react-components/theme.d.ts.map +1 -0
  51. package/dist/react-components/utils.d.ts +4 -0
  52. package/dist/react-components/utils.d.ts.map +1 -0
  53. package/dist/react.cjs +1212 -50
  54. package/dist/react.d.cts +100 -4
  55. package/dist/react.d.ts +38 -15
  56. package/dist/react.d.ts.map +1 -0
  57. package/dist/react.js +1164 -13
  58. package/dist/shared/ChatController.d.ts +65 -0
  59. package/dist/shared/ChatController.d.ts.map +1 -0
  60. package/dist/shared/upload.d.ts +3 -0
  61. package/dist/shared/upload.d.ts.map +1 -0
  62. package/dist/support-widget.global.js +485 -157
  63. package/dist/support.d.ts +99 -0
  64. package/dist/support.d.ts.map +1 -0
  65. package/dist/transport/HttpTransport.d.ts +20 -0
  66. package/dist/transport/HttpTransport.d.ts.map +1 -0
  67. package/dist/transport/WebSocketTransport.d.ts +78 -0
  68. package/dist/transport/WebSocketTransport.d.ts.map +1 -0
  69. package/dist/{types-BmD7f1gV.d.cts → types-COPVrm3K.d.cts} +25 -1
  70. package/dist/{types-BmD7f1gV.d.ts → types-COPVrm3K.d.ts} +25 -1
  71. package/dist/types.d.ts +271 -0
  72. package/dist/types.d.ts.map +1 -0
  73. package/dist/umd.d.ts +6 -0
  74. package/dist/umd.d.ts.map +1 -0
  75. package/dist/version.d.ts +2 -0
  76. package/dist/version.d.ts.map +1 -0
  77. package/dist/widget/icons.d.ts +8 -0
  78. package/dist/widget/icons.d.ts.map +1 -0
  79. package/dist/widget/index.d.ts +2 -0
  80. package/dist/widget/index.d.ts.map +1 -0
  81. package/dist/widget/storage.d.ts +5 -0
  82. package/dist/widget/storage.d.ts.map +1 -0
  83. package/dist/widget/styles.d.ts +3 -0
  84. package/dist/widget/styles.d.ts.map +1 -0
  85. package/package.json +5 -2
package/dist/react.cjs CHANGED
@@ -1,50 +1,1069 @@
1
1
  'use strict';
2
2
 
3
- var chunkYDLRISR7_cjs = require('./chunk-YDLRISR7.cjs');
4
- var react = require('react');
3
+ var chunkW2PWFS3E_cjs = require('./chunk-W2PWFS3E.cjs');
4
+ var React3 = require('react');
5
5
  var jsxRuntime = require('react/jsx-runtime');
6
6
 
7
- var ChatContext = react.createContext(null);
7
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
8
+
9
+ var React3__default = /*#__PURE__*/_interopDefault(React3);
10
+
11
+ function ChatInput({
12
+ onSend,
13
+ onTypingChange,
14
+ onUploadAttachment,
15
+ placeholder = "Type a message..."
16
+ }) {
17
+ const [content, setContent] = React3.useState("");
18
+ const [attachments, setAttachments] = React3.useState([]);
19
+ const [isSending, setIsSending] = React3.useState(false);
20
+ const [isDragging, setIsDragging] = React3.useState(false);
21
+ const fileInputRef = React3.useRef(null);
22
+ const typingTimeoutRef = React3.useRef(null);
23
+ const readyAttachments = React3.useMemo(
24
+ () => attachments.filter((attachment) => attachment.attachment).map((attachment) => attachment.attachment),
25
+ [attachments]
26
+ );
27
+ const uploadingCount = attachments.filter((attachment) => !attachment.attachment && !attachment.error).length;
28
+ const emitTyping = () => {
29
+ onTypingChange?.(true);
30
+ if (typingTimeoutRef.current) {
31
+ clearTimeout(typingTimeoutRef.current);
32
+ }
33
+ typingTimeoutRef.current = setTimeout(() => {
34
+ onTypingChange?.(false);
35
+ }, 2500);
36
+ };
37
+ const handleFiles = async (fileList) => {
38
+ if (!onUploadAttachment) return;
39
+ const files = Array.from(fileList);
40
+ for (const file of files) {
41
+ const id = `${file.name}:${file.size}:${Date.now()}:${Math.random().toString(36).slice(2)}`;
42
+ setAttachments((current) => [
43
+ ...current,
44
+ {
45
+ id,
46
+ fileName: file.name,
47
+ progress: 0
48
+ }
49
+ ]);
50
+ const result = await onUploadAttachment(file, (progress) => {
51
+ setAttachments(
52
+ (current) => current.map(
53
+ (attachment) => attachment.id === id ? { ...attachment, progress } : attachment
54
+ )
55
+ );
56
+ });
57
+ setAttachments(
58
+ (current) => current.map((attachment) => {
59
+ if (attachment.id !== id) return attachment;
60
+ if (result?.data) {
61
+ return {
62
+ ...attachment,
63
+ progress: 100,
64
+ attachment: result.data
65
+ };
66
+ }
67
+ return {
68
+ ...attachment,
69
+ error: result?.error?.message ?? "Upload failed"
70
+ };
71
+ })
72
+ );
73
+ }
74
+ };
75
+ const handleSubmit = async () => {
76
+ const trimmed = content.trim();
77
+ if (!trimmed && !readyAttachments.length || isSending || uploadingCount > 0) return;
78
+ setIsSending(true);
79
+ try {
80
+ await onSend(trimmed, readyAttachments);
81
+ setContent("");
82
+ setAttachments([]);
83
+ onTypingChange?.(false);
84
+ } finally {
85
+ setIsSending(false);
86
+ }
87
+ };
88
+ return /* @__PURE__ */ jsxRuntime.jsxs(
89
+ "div",
90
+ {
91
+ onDragOver: (event) => {
92
+ event.preventDefault();
93
+ if (onUploadAttachment) {
94
+ setIsDragging(true);
95
+ }
96
+ },
97
+ onDragLeave: () => setIsDragging(false),
98
+ onDrop: (event) => {
99
+ event.preventDefault();
100
+ setIsDragging(false);
101
+ void handleFiles(event.dataTransfer.files);
102
+ },
103
+ style: {
104
+ borderTop: "1px solid var(--sm-border-color, #e5e7eb)",
105
+ background: isDragging ? "rgba(37, 99, 235, 0.06)" : "var(--sm-surface, #fff)",
106
+ padding: 12,
107
+ display: "flex",
108
+ flexDirection: "column",
109
+ gap: 10
110
+ },
111
+ children: [
112
+ attachments.length ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", gap: 8, flexWrap: "wrap" }, children: attachments.map((attachment) => /* @__PURE__ */ jsxRuntime.jsxs(
113
+ "div",
114
+ {
115
+ style: {
116
+ display: "inline-flex",
117
+ alignItems: "center",
118
+ gap: 8,
119
+ padding: "6px 10px",
120
+ borderRadius: 999,
121
+ background: "var(--sm-surface-muted, #f8fafc)",
122
+ border: "1px solid var(--sm-border-color, #e5e7eb)",
123
+ fontSize: 12
124
+ },
125
+ children: [
126
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: attachment.fileName }),
127
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: attachment.error ? "#dc2626" : "var(--sm-muted-text, #6b7280)" }, children: attachment.error ?? `${attachment.progress}%` }),
128
+ /* @__PURE__ */ jsxRuntime.jsx(
129
+ "button",
130
+ {
131
+ type: "button",
132
+ onClick: () => {
133
+ setAttachments((current) => current.filter((item) => item.id !== attachment.id));
134
+ },
135
+ style: {
136
+ border: "none",
137
+ background: "transparent",
138
+ cursor: "pointer",
139
+ color: "var(--sm-muted-text, #6b7280)"
140
+ },
141
+ children: "x"
142
+ }
143
+ )
144
+ ]
145
+ },
146
+ attachment.id
147
+ )) }) : null,
148
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 10, alignItems: "flex-end" }, children: [
149
+ /* @__PURE__ */ jsxRuntime.jsx(
150
+ "textarea",
151
+ {
152
+ value: content,
153
+ onChange: (event) => {
154
+ setContent(event.target.value);
155
+ emitTyping();
156
+ },
157
+ onKeyDown: (event) => {
158
+ if (event.key === "Enter" && !event.shiftKey) {
159
+ event.preventDefault();
160
+ void handleSubmit();
161
+ }
162
+ },
163
+ rows: 1,
164
+ placeholder,
165
+ style: {
166
+ flex: 1,
167
+ minHeight: 44,
168
+ maxHeight: 120,
169
+ resize: "vertical",
170
+ borderRadius: 14,
171
+ border: "1px solid var(--sm-border-color, #e5e7eb)",
172
+ padding: "12px 14px",
173
+ font: "inherit",
174
+ color: "var(--sm-text-color, #111827)"
175
+ }
176
+ }
177
+ ),
178
+ onUploadAttachment ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
179
+ /* @__PURE__ */ jsxRuntime.jsx(
180
+ "input",
181
+ {
182
+ ref: fileInputRef,
183
+ type: "file",
184
+ hidden: true,
185
+ multiple: true,
186
+ accept: "image/*,video/*,audio/*",
187
+ onChange: (event) => {
188
+ if (event.target.files) {
189
+ void handleFiles(event.target.files);
190
+ event.target.value = "";
191
+ }
192
+ }
193
+ }
194
+ ),
195
+ /* @__PURE__ */ jsxRuntime.jsx(
196
+ "button",
197
+ {
198
+ type: "button",
199
+ onClick: () => fileInputRef.current?.click(),
200
+ "aria-label": "Attach files",
201
+ style: {
202
+ width: 44,
203
+ height: 44,
204
+ borderRadius: 14,
205
+ border: "1px solid var(--sm-border-color, #e5e7eb)",
206
+ background: "var(--sm-surface, #fff)",
207
+ cursor: "pointer",
208
+ color: "var(--sm-text-color, #111827)"
209
+ },
210
+ children: "+"
211
+ }
212
+ )
213
+ ] }) : null,
214
+ /* @__PURE__ */ jsxRuntime.jsx(
215
+ "button",
216
+ {
217
+ type: "button",
218
+ onClick: () => void handleSubmit(),
219
+ disabled: isSending || uploadingCount > 0 || !content.trim() && !readyAttachments.length,
220
+ style: {
221
+ height: 44,
222
+ padding: "0 16px",
223
+ borderRadius: 14,
224
+ border: "none",
225
+ background: "var(--sm-primary, #2563eb)",
226
+ color: "#fff",
227
+ cursor: isSending ? "wait" : "pointer",
228
+ opacity: isSending || uploadingCount > 0 ? 0.75 : 1
229
+ },
230
+ children: "Send"
231
+ }
232
+ )
233
+ ] })
234
+ ]
235
+ }
236
+ );
237
+ }
238
+ var DEFAULT_EMOJIS = ["\u{1F44D}", "\u2764\uFE0F", "\u{1F602}", "\u{1F389}", "\u{1F62E}", "\u{1F440}"];
239
+ function EmojiPicker({
240
+ onSelect,
241
+ emojis = DEFAULT_EMOJIS
242
+ }) {
243
+ return /* @__PURE__ */ jsxRuntime.jsx(
244
+ "div",
245
+ {
246
+ style: {
247
+ display: "inline-flex",
248
+ gap: 6,
249
+ padding: 6,
250
+ borderRadius: 999,
251
+ background: "var(--sm-surface, #fff)",
252
+ border: "1px solid var(--sm-border-color, #e5e7eb)",
253
+ boxShadow: "0 10px 25px rgba(15, 23, 42, 0.12)"
254
+ },
255
+ children: emojis.map((emoji) => /* @__PURE__ */ jsxRuntime.jsx(
256
+ "button",
257
+ {
258
+ type: "button",
259
+ onClick: () => onSelect(emoji),
260
+ "aria-label": `React with ${emoji}`,
261
+ style: {
262
+ width: 32,
263
+ height: 32,
264
+ border: "none",
265
+ background: "transparent",
266
+ borderRadius: 999,
267
+ cursor: "pointer",
268
+ fontSize: 18
269
+ },
270
+ children: emoji
271
+ },
272
+ emoji
273
+ ))
274
+ }
275
+ );
276
+ }
277
+
278
+ // src/react-components/utils.ts
279
+ var timeFormatter = new Intl.DateTimeFormat(void 0, {
280
+ hour: "numeric",
281
+ minute: "2-digit"
282
+ });
283
+ var dateFormatter = new Intl.DateTimeFormat(void 0, {
284
+ month: "short",
285
+ day: "numeric",
286
+ year: "numeric"
287
+ });
288
+ function formatMessageTime(value) {
289
+ if (!value) return "";
290
+ return timeFormatter.format(new Date(value));
291
+ }
292
+ function formatDayLabel(value) {
293
+ if (!value) return "";
294
+ return dateFormatter.format(new Date(value));
295
+ }
296
+ function isSameDay(left, right) {
297
+ if (!left || !right) return false;
298
+ const leftDate = new Date(left);
299
+ const rightDate = new Date(right);
300
+ return leftDate.getFullYear() === rightDate.getFullYear() && leftDate.getMonth() === rightDate.getMonth() && leftDate.getDate() === rightDate.getDate();
301
+ }
302
+ function renderAttachment(messageId, attachment) {
303
+ const key = `${messageId}:${attachment.file_id}`;
304
+ const url = attachment.presigned_url;
305
+ if (!url) {
306
+ return /* @__PURE__ */ jsxRuntime.jsx(
307
+ "div",
308
+ {
309
+ style: {
310
+ padding: "10px 12px",
311
+ borderRadius: 12,
312
+ background: "rgba(255,255,255,0.16)",
313
+ fontSize: 13
314
+ },
315
+ children: attachment.file_name
316
+ },
317
+ key
318
+ );
319
+ }
320
+ if (attachment.mime_type.startsWith("image/")) {
321
+ return /* @__PURE__ */ jsxRuntime.jsx(
322
+ "img",
323
+ {
324
+ src: url,
325
+ alt: attachment.file_name,
326
+ loading: "lazy",
327
+ style: {
328
+ display: "block",
329
+ maxWidth: "100%",
330
+ borderRadius: 12,
331
+ marginTop: 8
332
+ }
333
+ },
334
+ key
335
+ );
336
+ }
337
+ if (attachment.mime_type.startsWith("video/")) {
338
+ return /* @__PURE__ */ jsxRuntime.jsx(
339
+ "video",
340
+ {
341
+ controls: true,
342
+ src: url,
343
+ style: {
344
+ display: "block",
345
+ width: "100%",
346
+ maxWidth: 320,
347
+ borderRadius: 12,
348
+ marginTop: 8
349
+ }
350
+ },
351
+ key
352
+ );
353
+ }
354
+ if (attachment.mime_type.startsWith("audio/")) {
355
+ return /* @__PURE__ */ jsxRuntime.jsx(
356
+ "audio",
357
+ {
358
+ controls: true,
359
+ src: url,
360
+ style: {
361
+ display: "block",
362
+ width: "100%",
363
+ marginTop: 8
364
+ }
365
+ },
366
+ key
367
+ );
368
+ }
369
+ return /* @__PURE__ */ jsxRuntime.jsx(
370
+ "a",
371
+ {
372
+ href: url,
373
+ target: "_blank",
374
+ rel: "noreferrer",
375
+ style: {
376
+ display: "inline-block",
377
+ marginTop: 8,
378
+ color: "inherit",
379
+ fontSize: 13
380
+ },
381
+ children: attachment.file_name
382
+ },
383
+ key
384
+ );
385
+ }
386
+ function ChatMessageItem({
387
+ message,
388
+ currentUserId,
389
+ onAddReaction,
390
+ onRemoveReaction,
391
+ onReport,
392
+ highlight = false
393
+ }) {
394
+ const [showPicker, setShowPicker] = React3.useState(false);
395
+ const isOwn = Boolean(currentUserId && message.sender_id === currentUserId);
396
+ const canReact = Boolean(onAddReaction || onRemoveReaction);
397
+ const reactionEntries = React3.useMemo(() => message.reactions ?? [], [message.reactions]);
398
+ return /* @__PURE__ */ jsxRuntime.jsxs(
399
+ "div",
400
+ {
401
+ style: {
402
+ display: "flex",
403
+ flexDirection: "column",
404
+ alignItems: isOwn ? "flex-end" : "flex-start",
405
+ gap: 6
406
+ },
407
+ children: [
408
+ /* @__PURE__ */ jsxRuntime.jsxs(
409
+ "div",
410
+ {
411
+ style: {
412
+ maxWidth: "min(82%, 560px)",
413
+ padding: message.attachments?.length ? 10 : "10px 12px",
414
+ borderRadius: "var(--sm-border-radius, 16px)",
415
+ background: isOwn ? "var(--sm-own-bubble, #2563eb)" : "var(--sm-other-bubble, #f3f4f6)",
416
+ color: isOwn ? "var(--sm-own-text, #fff)" : "var(--sm-other-text, #111827)",
417
+ boxShadow: highlight ? "0 0 0 2px rgba(37, 99, 235, 0.22)" : "none",
418
+ transition: "box-shadow 0.2s ease"
419
+ },
420
+ children: [
421
+ message.content ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { whiteSpace: "pre-wrap", wordBreak: "break-word", fontSize: 14 }, children: message.content }) : null,
422
+ message.attachments?.map((attachment) => renderAttachment(message.id, attachment))
423
+ ]
424
+ }
425
+ ),
426
+ /* @__PURE__ */ jsxRuntime.jsxs(
427
+ "div",
428
+ {
429
+ style: {
430
+ display: "flex",
431
+ alignItems: "center",
432
+ gap: 8,
433
+ color: "var(--sm-muted-text, #6b7280)",
434
+ fontSize: 12
435
+ },
436
+ children: [
437
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatMessageTime(message.created_at) }),
438
+ message.is_edited ? /* @__PURE__ */ jsxRuntime.jsx("span", { children: "edited" }) : null,
439
+ onReport && !isOwn ? /* @__PURE__ */ jsxRuntime.jsx(
440
+ "button",
441
+ {
442
+ type: "button",
443
+ onClick: () => void onReport(message.id),
444
+ style: {
445
+ border: "none",
446
+ background: "transparent",
447
+ color: "inherit",
448
+ cursor: "pointer",
449
+ fontSize: 12,
450
+ padding: 0
451
+ },
452
+ children: "Report"
453
+ }
454
+ ) : null,
455
+ canReact ? /* @__PURE__ */ jsxRuntime.jsx(
456
+ "button",
457
+ {
458
+ type: "button",
459
+ onClick: () => setShowPicker((value) => !value),
460
+ style: {
461
+ border: "none",
462
+ background: "transparent",
463
+ color: "inherit",
464
+ cursor: "pointer",
465
+ fontSize: 12,
466
+ padding: 0
467
+ },
468
+ children: "React"
469
+ }
470
+ ) : null
471
+ ]
472
+ }
473
+ ),
474
+ showPicker && canReact ? /* @__PURE__ */ jsxRuntime.jsx(
475
+ EmojiPicker,
476
+ {
477
+ onSelect: (emoji) => {
478
+ setShowPicker(false);
479
+ void onAddReaction?.(message.id, emoji);
480
+ }
481
+ }
482
+ ) : null,
483
+ reactionEntries.length ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", gap: 6, flexWrap: "wrap" }, children: reactionEntries.map((reaction) => {
484
+ const reacted = Boolean(currentUserId && reaction.user_ids.includes(currentUserId));
485
+ return /* @__PURE__ */ jsxRuntime.jsxs(
486
+ "button",
487
+ {
488
+ type: "button",
489
+ onClick: () => {
490
+ if (reacted) {
491
+ void onRemoveReaction?.(message.id, reaction.emoji);
492
+ return;
493
+ }
494
+ void onAddReaction?.(message.id, reaction.emoji);
495
+ },
496
+ style: {
497
+ border: reacted ? "1px solid rgba(37, 99, 235, 0.4)" : "1px solid var(--sm-border-color, #e5e7eb)",
498
+ background: reacted ? "rgba(37, 99, 235, 0.08)" : "var(--sm-surface, #fff)",
499
+ color: "var(--sm-text-color, #111827)",
500
+ borderRadius: 999,
501
+ padding: "4px 8px",
502
+ cursor: "pointer",
503
+ fontSize: 12
504
+ },
505
+ children: [
506
+ reaction.emoji,
507
+ " ",
508
+ reaction.count
509
+ ]
510
+ },
511
+ `${message.id}:${reaction.emoji}`
512
+ );
513
+ }) }) : null
514
+ ]
515
+ }
516
+ );
517
+ }
518
+ function getUnreadIndex(messages, unreadSince) {
519
+ if (!unreadSince) return -1;
520
+ return messages.findIndex((message) => new Date(message.created_at).getTime() > new Date(unreadSince).getTime());
521
+ }
522
+ function ChatMessageList({
523
+ messages,
524
+ currentUserId,
525
+ unreadSince,
526
+ scrollToUnreadOnMount = true,
527
+ onAddReaction,
528
+ onRemoveReaction,
529
+ onReport,
530
+ emptyState
531
+ }) {
532
+ const containerRef = React3.useRef(null);
533
+ const unreadMarkerRef = React3.useRef(null);
534
+ const lastMessageCountRef = React3.useRef(messages.length);
535
+ const didScrollToUnreadRef = React3.useRef(false);
536
+ const [showJumpToLatest, setShowJumpToLatest] = React3.useState(false);
537
+ const unreadIndex = React3.useMemo(() => getUnreadIndex(messages, unreadSince), [messages, unreadSince]);
538
+ React3.useEffect(() => {
539
+ const container = containerRef.current;
540
+ if (!container) return;
541
+ const distanceFromBottom = container.scrollHeight - container.scrollTop - container.clientHeight;
542
+ const shouldStickToBottom = distanceFromBottom < 80;
543
+ if (messages.length > lastMessageCountRef.current && shouldStickToBottom) {
544
+ container.scrollTo({ top: container.scrollHeight, behavior: "smooth" });
545
+ setShowJumpToLatest(false);
546
+ } else if (messages.length > lastMessageCountRef.current) {
547
+ setShowJumpToLatest(true);
548
+ }
549
+ lastMessageCountRef.current = messages.length;
550
+ }, [messages]);
551
+ React3.useEffect(() => {
552
+ if (!scrollToUnreadOnMount || unreadIndex < 0 || didScrollToUnreadRef.current) {
553
+ return;
554
+ }
555
+ unreadMarkerRef.current?.scrollIntoView({ block: "center" });
556
+ didScrollToUnreadRef.current = true;
557
+ }, [scrollToUnreadOnMount, unreadIndex, messages.length]);
558
+ if (!messages.length) {
559
+ return /* @__PURE__ */ jsxRuntime.jsx(
560
+ "div",
561
+ {
562
+ style: {
563
+ flex: 1,
564
+ display: "flex",
565
+ alignItems: "center",
566
+ justifyContent: "center",
567
+ color: "var(--sm-muted-text, #6b7280)",
568
+ fontSize: 14,
569
+ padding: 24
570
+ },
571
+ children: emptyState ?? "No messages yet"
572
+ }
573
+ );
574
+ }
575
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative", flex: 1, minHeight: 0 }, children: [
576
+ /* @__PURE__ */ jsxRuntime.jsx(
577
+ "div",
578
+ {
579
+ ref: containerRef,
580
+ onScroll: (event) => {
581
+ const element = event.currentTarget;
582
+ const distanceFromBottom = element.scrollHeight - element.scrollTop - element.clientHeight;
583
+ setShowJumpToLatest(distanceFromBottom > 120);
584
+ },
585
+ style: {
586
+ height: "100%",
587
+ overflowY: "auto",
588
+ padding: 16,
589
+ display: "flex",
590
+ flexDirection: "column",
591
+ gap: 12,
592
+ background: "var(--sm-surface-muted, #f8fafc)"
593
+ },
594
+ children: messages.map((message, index) => {
595
+ const previousMessage = messages[index - 1];
596
+ const showDateDivider = !previousMessage || !isSameDay(previousMessage.created_at, message.created_at);
597
+ const showUnreadDivider = unreadIndex === index;
598
+ return /* @__PURE__ */ jsxRuntime.jsxs(React3__default.default.Fragment, { children: [
599
+ showDateDivider ? /* @__PURE__ */ jsxRuntime.jsx(
600
+ "div",
601
+ {
602
+ style: {
603
+ alignSelf: "center",
604
+ fontSize: 12,
605
+ color: "var(--sm-muted-text, #6b7280)",
606
+ padding: "4px 10px",
607
+ borderRadius: 999,
608
+ background: "rgba(148, 163, 184, 0.12)"
609
+ },
610
+ children: formatDayLabel(message.created_at)
611
+ }
612
+ ) : null,
613
+ showUnreadDivider ? /* @__PURE__ */ jsxRuntime.jsxs(
614
+ "div",
615
+ {
616
+ ref: unreadMarkerRef,
617
+ style: {
618
+ display: "flex",
619
+ alignItems: "center",
620
+ gap: 10,
621
+ color: "var(--sm-primary, #2563eb)",
622
+ fontSize: 12,
623
+ fontWeight: 600
624
+ },
625
+ children: [
626
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { flex: 1, height: 1, background: "rgba(37, 99, 235, 0.28)" } }),
627
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "New messages" }),
628
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { flex: 1, height: 1, background: "rgba(37, 99, 235, 0.28)" } })
629
+ ]
630
+ }
631
+ ) : null,
632
+ /* @__PURE__ */ jsxRuntime.jsx(
633
+ ChatMessageItem,
634
+ {
635
+ message,
636
+ currentUserId,
637
+ onAddReaction,
638
+ onRemoveReaction,
639
+ onReport,
640
+ highlight: showUnreadDivider
641
+ }
642
+ )
643
+ ] }, message.id);
644
+ })
645
+ }
646
+ ),
647
+ showJumpToLatest ? /* @__PURE__ */ jsxRuntime.jsx(
648
+ "button",
649
+ {
650
+ type: "button",
651
+ onClick: () => {
652
+ containerRef.current?.scrollTo({
653
+ top: containerRef.current.scrollHeight,
654
+ behavior: "smooth"
655
+ });
656
+ setShowJumpToLatest(false);
657
+ },
658
+ style: {
659
+ position: "absolute",
660
+ right: 16,
661
+ bottom: 16,
662
+ border: "none",
663
+ borderRadius: 999,
664
+ background: "var(--sm-primary, #2563eb)",
665
+ color: "#fff",
666
+ padding: "10px 14px",
667
+ cursor: "pointer",
668
+ boxShadow: "0 12px 28px rgba(37, 99, 235, 0.28)"
669
+ },
670
+ children: "New messages"
671
+ }
672
+ ) : null
673
+ ] });
674
+ }
675
+
676
+ // src/react-components/theme.ts
677
+ function themeToStyle(theme) {
678
+ return {
679
+ "--sm-primary": theme?.primary ?? "#2563eb",
680
+ "--sm-own-bubble": theme?.ownBubble ?? theme?.primary ?? "#2563eb",
681
+ "--sm-own-text": theme?.ownText ?? "#ffffff",
682
+ "--sm-other-bubble": theme?.otherBubble ?? "#f3f4f6",
683
+ "--sm-other-text": theme?.otherText ?? "#111827",
684
+ "--sm-surface": theme?.surface ?? "#ffffff",
685
+ "--sm-surface-muted": theme?.surfaceMuted ?? "#f8fafc",
686
+ "--sm-border-color": theme?.borderColor ?? "#e5e7eb",
687
+ "--sm-text-color": theme?.textColor ?? "#111827",
688
+ "--sm-muted-text": theme?.mutedText ?? "#6b7280",
689
+ "--sm-border-radius": typeof theme?.borderRadius === "number" ? `${theme.borderRadius}px` : theme?.borderRadius ?? "16px",
690
+ "--sm-font-family": theme?.fontFamily ?? 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'
691
+ };
692
+ }
693
+ function inferMessageType(content, attachments) {
694
+ if (!attachments.length) return "text";
695
+ if (!content && attachments.every((attachment) => attachment.mime_type.startsWith("image/"))) {
696
+ return "image";
697
+ }
698
+ return "file";
699
+ }
700
+ function ChatThread({
701
+ conversationId,
702
+ theme,
703
+ currentUserId,
704
+ title = "Chat",
705
+ subtitle
706
+ }) {
707
+ const client = useChatClient();
708
+ const resolvedUserId = currentUserId ?? client.userId;
709
+ const {
710
+ messages,
711
+ readStatuses,
712
+ isLoading,
713
+ error,
714
+ sendMessage,
715
+ markRead,
716
+ addReaction,
717
+ removeReaction,
718
+ reportMessage,
719
+ uploadAttachment
720
+ } = useChat(conversationId);
721
+ const { typingUsers, sendTyping } = useTyping(conversationId);
722
+ const { members } = usePresence(conversationId);
723
+ const ownReadStatus = React3.useMemo(
724
+ () => resolvedUserId ? readStatuses.find((status) => status.user_id === resolvedUserId)?.last_read_at : void 0,
725
+ [readStatuses, resolvedUserId]
726
+ );
727
+ const otherTypingUsers = React3.useMemo(
728
+ () => typingUsers.filter((userId) => userId !== resolvedUserId),
729
+ [typingUsers, resolvedUserId]
730
+ );
731
+ const activeMembers = React3.useMemo(
732
+ () => members.filter((member) => member.userId !== resolvedUserId),
733
+ [members, resolvedUserId]
734
+ );
735
+ React3.useEffect(() => {
736
+ if (!messages.length) return;
737
+ void markRead();
738
+ }, [markRead, messages.length, messages[messages.length - 1]?.id]);
739
+ return /* @__PURE__ */ jsxRuntime.jsxs(
740
+ "div",
741
+ {
742
+ "data-scalemule-chat": "",
743
+ style: {
744
+ ...themeToStyle(theme),
745
+ display: "flex",
746
+ flexDirection: "column",
747
+ height: "100%",
748
+ minHeight: 320,
749
+ borderRadius: "var(--sm-border-radius, 16px)",
750
+ border: "1px solid var(--sm-border-color, #e5e7eb)",
751
+ background: "var(--sm-surface, #fff)",
752
+ color: "var(--sm-text-color, #111827)",
753
+ fontFamily: "var(--sm-font-family)",
754
+ overflow: "hidden"
755
+ },
756
+ children: [
757
+ /* @__PURE__ */ jsxRuntime.jsxs(
758
+ "div",
759
+ {
760
+ style: {
761
+ padding: "16px 18px",
762
+ borderBottom: "1px solid var(--sm-border-color, #e5e7eb)",
763
+ display: "flex",
764
+ justifyContent: "space-between",
765
+ alignItems: "center",
766
+ gap: 16
767
+ },
768
+ children: [
769
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
770
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 16, fontWeight: 700 }, children: title }),
771
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 13, color: "var(--sm-muted-text, #6b7280)" }, children: otherTypingUsers.length ? "Typing..." : subtitle ?? (activeMembers.length ? `${activeMembers.length} online` : "No one online") })
772
+ ] }),
773
+ /* @__PURE__ */ jsxRuntime.jsxs(
774
+ "div",
775
+ {
776
+ style: {
777
+ display: "inline-flex",
778
+ alignItems: "center",
779
+ gap: 8,
780
+ fontSize: 12,
781
+ color: "var(--sm-muted-text, #6b7280)"
782
+ },
783
+ children: [
784
+ /* @__PURE__ */ jsxRuntime.jsx(
785
+ "span",
786
+ {
787
+ style: {
788
+ width: 10,
789
+ height: 10,
790
+ borderRadius: 999,
791
+ background: activeMembers.length ? "#22c55e" : "#94a3b8"
792
+ }
793
+ }
794
+ ),
795
+ activeMembers.length ? "Online" : "Away"
796
+ ]
797
+ }
798
+ )
799
+ ]
800
+ }
801
+ ),
802
+ error ? /* @__PURE__ */ jsxRuntime.jsx(
803
+ "div",
804
+ {
805
+ style: {
806
+ padding: "10px 16px",
807
+ fontSize: 13,
808
+ color: "#b91c1c",
809
+ background: "#fef2f2",
810
+ borderBottom: "1px solid #fecaca"
811
+ },
812
+ children: error
813
+ }
814
+ ) : null,
815
+ /* @__PURE__ */ jsxRuntime.jsx(
816
+ ChatMessageList,
817
+ {
818
+ messages,
819
+ currentUserId: resolvedUserId,
820
+ unreadSince: ownReadStatus,
821
+ onAddReaction: (messageId, emoji) => void addReaction(messageId, emoji),
822
+ onRemoveReaction: (messageId, emoji) => void removeReaction(messageId, emoji),
823
+ onReport: (messageId) => void reportMessage(messageId, "other"),
824
+ emptyState: isLoading ? "Loading messages..." : "Start the conversation"
825
+ }
826
+ ),
827
+ /* @__PURE__ */ jsxRuntime.jsx(
828
+ "div",
829
+ {
830
+ style: {
831
+ minHeight: otherTypingUsers.length ? 28 : 0,
832
+ padding: otherTypingUsers.length ? "0 16px 8px" : 0,
833
+ fontSize: 12,
834
+ color: "var(--sm-muted-text, #6b7280)"
835
+ },
836
+ children: otherTypingUsers.length ? "Someone is typing..." : null
837
+ }
838
+ ),
839
+ /* @__PURE__ */ jsxRuntime.jsx(
840
+ ChatInput,
841
+ {
842
+ onSend: async (content, attachments) => {
843
+ await sendMessage(content, {
844
+ attachments,
845
+ message_type: inferMessageType(content, attachments)
846
+ });
847
+ },
848
+ onTypingChange: (isTyping) => {
849
+ sendTyping(isTyping);
850
+ },
851
+ onUploadAttachment: uploadAttachment
852
+ }
853
+ )
854
+ ]
855
+ }
856
+ );
857
+ }
858
+ function formatPreview(conversation) {
859
+ if (conversation.last_message_preview) {
860
+ return conversation.last_message_preview;
861
+ }
862
+ return conversation.name ?? conversation.id;
863
+ }
864
+ function ConversationList({
865
+ conversationType,
866
+ selectedConversationId,
867
+ onSelect,
868
+ theme,
869
+ title = "Conversations"
870
+ }) {
871
+ const { conversations, isLoading } = useConversations({
872
+ conversationType
873
+ });
874
+ const [search, setSearch] = React3.useState("");
875
+ const filtered = React3.useMemo(() => {
876
+ const query = search.trim().toLowerCase();
877
+ if (!query) return conversations;
878
+ return conversations.filter((conversation) => {
879
+ const haystack = [
880
+ conversation.name,
881
+ conversation.last_message_preview,
882
+ conversation.counterparty_user_id
883
+ ].filter(Boolean).join(" ").toLowerCase();
884
+ return haystack.includes(query);
885
+ });
886
+ }, [conversations, search]);
887
+ return /* @__PURE__ */ jsxRuntime.jsxs(
888
+ "div",
889
+ {
890
+ "data-scalemule-chat": "",
891
+ style: {
892
+ ...themeToStyle(theme),
893
+ display: "flex",
894
+ flexDirection: "column",
895
+ height: "100%",
896
+ minHeight: 280,
897
+ borderRadius: "var(--sm-border-radius, 16px)",
898
+ border: "1px solid var(--sm-border-color, #e5e7eb)",
899
+ background: "var(--sm-surface, #fff)",
900
+ color: "var(--sm-text-color, #111827)",
901
+ fontFamily: "var(--sm-font-family)",
902
+ overflow: "hidden"
903
+ },
904
+ children: [
905
+ /* @__PURE__ */ jsxRuntime.jsxs(
906
+ "div",
907
+ {
908
+ style: {
909
+ padding: 16,
910
+ borderBottom: "1px solid var(--sm-border-color, #e5e7eb)",
911
+ display: "flex",
912
+ flexDirection: "column",
913
+ gap: 10
914
+ },
915
+ children: [
916
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 16, fontWeight: 700 }, children: title }),
917
+ /* @__PURE__ */ jsxRuntime.jsx(
918
+ "input",
919
+ {
920
+ value: search,
921
+ onChange: (event) => setSearch(event.target.value),
922
+ placeholder: "Search conversations",
923
+ style: {
924
+ width: "100%",
925
+ borderRadius: 12,
926
+ border: "1px solid var(--sm-border-color, #e5e7eb)",
927
+ padding: "10px 12px",
928
+ font: "inherit"
929
+ }
930
+ }
931
+ )
932
+ ]
933
+ }
934
+ ),
935
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { flex: 1, overflowY: "auto" }, children: isLoading ? /* @__PURE__ */ jsxRuntime.jsx(
936
+ "div",
937
+ {
938
+ style: {
939
+ padding: 24,
940
+ fontSize: 14,
941
+ color: "var(--sm-muted-text, #6b7280)"
942
+ },
943
+ children: "Loading conversations..."
944
+ }
945
+ ) : !filtered.length ? /* @__PURE__ */ jsxRuntime.jsx(
946
+ "div",
947
+ {
948
+ style: {
949
+ padding: 24,
950
+ fontSize: 14,
951
+ color: "var(--sm-muted-text, #6b7280)"
952
+ },
953
+ children: "No conversations found"
954
+ }
955
+ ) : filtered.map((conversation) => {
956
+ const selected = conversation.id === selectedConversationId;
957
+ return /* @__PURE__ */ jsxRuntime.jsxs(
958
+ "button",
959
+ {
960
+ type: "button",
961
+ onClick: () => onSelect?.(conversation),
962
+ style: {
963
+ width: "100%",
964
+ border: "none",
965
+ borderBottom: "1px solid var(--sm-border-color, #e5e7eb)",
966
+ padding: 16,
967
+ textAlign: "left",
968
+ background: selected ? "rgba(37, 99, 235, 0.08)" : "transparent",
969
+ cursor: "pointer",
970
+ display: "flex",
971
+ flexDirection: "column",
972
+ gap: 6
973
+ },
974
+ children: [
975
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12 }, children: [
976
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 14, fontWeight: 600 }, children: conversation.name ?? conversation.counterparty_user_id ?? "Conversation" }),
977
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
978
+ conversation.is_muted ? /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 11, color: "var(--sm-muted-text, #6b7280)" }, children: "Muted" }) : null,
979
+ conversation.unread_count ? /* @__PURE__ */ jsxRuntime.jsx(
980
+ "span",
981
+ {
982
+ style: {
983
+ minWidth: 22,
984
+ height: 22,
985
+ borderRadius: 999,
986
+ background: "var(--sm-primary, #2563eb)",
987
+ color: "#fff",
988
+ display: "inline-flex",
989
+ alignItems: "center",
990
+ justifyContent: "center",
991
+ fontSize: 12,
992
+ fontWeight: 700,
993
+ padding: "0 6px"
994
+ },
995
+ children: conversation.unread_count
996
+ }
997
+ ) : null
998
+ ] })
999
+ ] }),
1000
+ /* @__PURE__ */ jsxRuntime.jsx(
1001
+ "div",
1002
+ {
1003
+ style: {
1004
+ fontSize: 13,
1005
+ color: "var(--sm-muted-text, #6b7280)",
1006
+ overflow: "hidden",
1007
+ textOverflow: "ellipsis",
1008
+ whiteSpace: "nowrap"
1009
+ },
1010
+ children: formatPreview(conversation)
1011
+ }
1012
+ )
1013
+ ]
1014
+ },
1015
+ conversation.id
1016
+ );
1017
+ }) })
1018
+ ]
1019
+ }
1020
+ );
1021
+ }
1022
+ var ChatContext = React3.createContext(null);
8
1023
  function useChatContext() {
9
- const ctx = react.useContext(ChatContext);
1024
+ const ctx = React3.useContext(ChatContext);
10
1025
  if (!ctx) throw new Error("useChatContext must be used within a ChatProvider");
11
1026
  return ctx;
12
1027
  }
13
1028
  function ChatProvider({ config, children }) {
14
- const [client] = react.useState(() => new chunkYDLRISR7_cjs.ChatClient(config));
15
- react.useEffect(() => {
1029
+ const [client] = React3.useState(() => new chunkW2PWFS3E_cjs.ChatClient(config));
1030
+ React3.useEffect(() => {
16
1031
  return () => {
17
1032
  client.destroy();
18
1033
  };
19
1034
  }, [client]);
20
- return /* @__PURE__ */ jsxRuntime.jsx(ChatContext.Provider, { value: { client }, children });
1035
+ return /* @__PURE__ */ jsxRuntime.jsx(ChatContext.Provider, { value: { client, config }, children });
21
1036
  }
22
1037
  function useChatClient() {
23
1038
  return useChatContext().client;
24
1039
  }
1040
+ function useChatConfig() {
1041
+ return useChatContext().config;
1042
+ }
25
1043
  function useConnection() {
26
1044
  const { client } = useChatContext();
27
- const [status, setStatus] = react.useState(client.status);
28
- react.useEffect(() => {
1045
+ const [status, setStatus] = React3.useState(client.status);
1046
+ React3.useEffect(() => {
29
1047
  return client.on("connected", () => setStatus("connected"));
30
1048
  }, [client]);
31
- react.useEffect(() => {
1049
+ React3.useEffect(() => {
32
1050
  return client.on("disconnected", () => setStatus("disconnected"));
33
1051
  }, [client]);
34
- react.useEffect(() => {
1052
+ React3.useEffect(() => {
35
1053
  return client.on("reconnecting", () => setStatus("reconnecting"));
36
1054
  }, [client]);
37
- const connect = react.useCallback(() => client.connect(), [client]);
38
- const disconnect = react.useCallback(() => client.disconnect(), [client]);
1055
+ const connect = React3.useCallback(() => client.connect(), [client]);
1056
+ const disconnect = React3.useCallback(() => client.disconnect(), [client]);
39
1057
  return { status, connect, disconnect };
40
1058
  }
41
1059
  function useChat(conversationId) {
42
1060
  const { client } = useChatContext();
43
- const [messages, setMessages] = react.useState([]);
44
- const [isLoading, setIsLoading] = react.useState(false);
45
- const [error, setError] = react.useState(null);
46
- const [hasMore, setHasMore] = react.useState(false);
47
- react.useEffect(() => {
1061
+ const [messages, setMessages] = React3.useState([]);
1062
+ const [readStatuses, setReadStatuses] = React3.useState([]);
1063
+ const [isLoading, setIsLoading] = React3.useState(false);
1064
+ const [error, setError] = React3.useState(null);
1065
+ const [hasMore, setHasMore] = React3.useState(false);
1066
+ React3.useEffect(() => {
48
1067
  if (!conversationId) return;
49
1068
  setIsLoading(true);
50
1069
  setError(null);
@@ -61,6 +1080,11 @@ function useChat(conversationId) {
61
1080
  } else if (result.error) {
62
1081
  setError(result.error.message);
63
1082
  }
1083
+ const readStatusResult = await client.getReadStatus(conversationId);
1084
+ if (cancelled) return;
1085
+ if (readStatusResult.data?.statuses) {
1086
+ setReadStatuses(readStatusResult.data.statuses);
1087
+ }
64
1088
  setIsLoading(false);
65
1089
  unsub = client.subscribeToConversation(conversationId);
66
1090
  client.connect();
@@ -70,41 +1094,61 @@ function useChat(conversationId) {
70
1094
  unsub?.();
71
1095
  };
72
1096
  }, [client, conversationId]);
73
- react.useEffect(() => {
1097
+ React3.useEffect(() => {
74
1098
  if (!conversationId) return;
75
1099
  return client.on("message", ({ message, conversationId: convId }) => {
76
1100
  if (convId === conversationId) {
77
- setMessages((prev) => {
78
- if (prev.some((m) => m.id === message.id)) return prev;
79
- return [...prev, message];
80
- });
1101
+ setMessages([...client.getCachedMessages(conversationId)]);
81
1102
  }
82
1103
  });
83
1104
  }, [client, conversationId]);
84
- react.useEffect(() => {
1105
+ React3.useEffect(() => {
85
1106
  if (!conversationId) return;
86
1107
  return client.on("message:updated", ({ message, conversationId: convId }) => {
87
1108
  if (convId === conversationId) {
88
- setMessages((prev) => prev.map((m) => m.id === message.id ? message : m));
1109
+ setMessages([...client.getCachedMessages(conversationId)]);
89
1110
  }
90
1111
  });
91
1112
  }, [client, conversationId]);
92
- react.useEffect(() => {
1113
+ React3.useEffect(() => {
93
1114
  if (!conversationId) return;
94
1115
  return client.on("message:deleted", ({ messageId, conversationId: convId }) => {
95
1116
  if (convId === conversationId) {
96
- setMessages((prev) => prev.filter((m) => m.id !== messageId));
1117
+ setMessages([...client.getCachedMessages(conversationId)]);
1118
+ }
1119
+ });
1120
+ }, [client, conversationId]);
1121
+ React3.useEffect(() => {
1122
+ if (!conversationId) return;
1123
+ return client.on("reaction", ({ conversationId: convId }) => {
1124
+ if (convId === conversationId) {
1125
+ setMessages([...client.getCachedMessages(conversationId)]);
97
1126
  }
98
1127
  });
99
1128
  }, [client, conversationId]);
100
- const sendMessage = react.useCallback(
1129
+ React3.useEffect(() => {
1130
+ if (!conversationId) return;
1131
+ return client.on("read", ({ conversationId: convId, userId, lastReadAt }) => {
1132
+ if (convId !== conversationId) return;
1133
+ setReadStatuses((prev) => {
1134
+ const existingIndex = prev.findIndex((status) => status.user_id === userId);
1135
+ if (existingIndex < 0) {
1136
+ return [...prev, { user_id: userId, last_read_at: lastReadAt }];
1137
+ }
1138
+ return prev.map(
1139
+ (status) => status.user_id === userId ? { ...status, last_read_at: lastReadAt } : status
1140
+ );
1141
+ });
1142
+ });
1143
+ }, [client, conversationId]);
1144
+ const sendMessage = React3.useCallback(
101
1145
  async (content, options) => {
102
1146
  if (!conversationId) return;
103
1147
  return client.sendMessage(conversationId, { content, ...options });
104
1148
  },
105
1149
  [client, conversationId]
106
1150
  );
107
- const loadMore = react.useCallback(async () => {
1151
+ const loadMore = React3.useCallback(async () => {
108
1152
  if (!conversationId || !messages.length) return;
109
1153
  const oldestId = messages[0]?.id;
110
1154
  const result = await client.getMessages(conversationId, { before: oldestId });
@@ -113,24 +1157,135 @@ function useChat(conversationId) {
113
1157
  setHasMore(result.data.has_more ?? false);
114
1158
  }
115
1159
  }, [client, conversationId, messages]);
116
- const markRead = react.useCallback(async () => {
1160
+ const editMessage = React3.useCallback(
1161
+ async (messageId, content) => {
1162
+ const result = await client.editMessage(messageId, content);
1163
+ if (result.error) {
1164
+ setError(result.error.message);
1165
+ }
1166
+ return result;
1167
+ },
1168
+ [client]
1169
+ );
1170
+ const deleteMessage = React3.useCallback(
1171
+ async (messageId) => {
1172
+ const result = await client.deleteMessage(messageId);
1173
+ if (result.error) {
1174
+ setError(result.error.message);
1175
+ }
1176
+ return result;
1177
+ },
1178
+ [client]
1179
+ );
1180
+ const addReaction = React3.useCallback(
1181
+ async (messageId, emoji) => {
1182
+ const result = await client.addReaction(messageId, emoji);
1183
+ if (result.error) {
1184
+ setError(result.error.message);
1185
+ }
1186
+ return result;
1187
+ },
1188
+ [client]
1189
+ );
1190
+ const removeReaction = React3.useCallback(
1191
+ async (messageId, emoji) => {
1192
+ const result = await client.removeReaction(messageId, emoji);
1193
+ if (result.error) {
1194
+ setError(result.error.message);
1195
+ }
1196
+ return result;
1197
+ },
1198
+ [client]
1199
+ );
1200
+ const uploadAttachment = React3.useCallback(
1201
+ async (file, onProgress, signal) => {
1202
+ const result = await client.uploadAttachment(file, onProgress, signal);
1203
+ if (result.error) {
1204
+ setError(result.error.message);
1205
+ }
1206
+ return result;
1207
+ },
1208
+ [client]
1209
+ );
1210
+ const refreshAttachmentUrl = React3.useCallback(
1211
+ async (messageId, fileId) => {
1212
+ const result = await client.refreshAttachmentUrl(messageId, fileId);
1213
+ if (result.error) {
1214
+ setError(result.error.message);
1215
+ }
1216
+ return result;
1217
+ },
1218
+ [client]
1219
+ );
1220
+ const reportMessage = React3.useCallback(
1221
+ async (messageId, reason, description) => {
1222
+ const result = await client.reportMessage(messageId, reason, description);
1223
+ if (result.error) {
1224
+ setError(result.error.message);
1225
+ }
1226
+ return result;
1227
+ },
1228
+ [client]
1229
+ );
1230
+ const muteConversation = React3.useCallback(
1231
+ async (mutedUntil) => {
1232
+ if (!conversationId) return;
1233
+ const result = await client.muteConversation(conversationId, mutedUntil);
1234
+ if (result.error) {
1235
+ setError(result.error.message);
1236
+ }
1237
+ return result;
1238
+ },
1239
+ [client, conversationId]
1240
+ );
1241
+ const unmuteConversation = React3.useCallback(async () => {
117
1242
  if (!conversationId) return;
118
- await client.markRead(conversationId);
1243
+ const result = await client.unmuteConversation(conversationId);
1244
+ if (result.error) {
1245
+ setError(result.error.message);
1246
+ }
1247
+ return result;
119
1248
  }, [client, conversationId]);
1249
+ const getReadStatus = React3.useCallback(async () => {
1250
+ if (!conversationId) return;
1251
+ const result = await client.getReadStatus(conversationId);
1252
+ if (result.data?.statuses) {
1253
+ setReadStatuses(result.data.statuses);
1254
+ } else if (result.error) {
1255
+ setError(result.error.message);
1256
+ }
1257
+ return result;
1258
+ }, [client, conversationId]);
1259
+ const markRead = React3.useCallback(async () => {
1260
+ if (!conversationId) return;
1261
+ await client.markRead(conversationId);
1262
+ await getReadStatus();
1263
+ }, [client, conversationId, getReadStatus]);
120
1264
  return {
121
1265
  messages,
1266
+ readStatuses,
122
1267
  isLoading,
123
1268
  error,
124
1269
  hasMore,
125
1270
  sendMessage,
126
1271
  loadMore,
1272
+ editMessage,
1273
+ deleteMessage,
1274
+ addReaction,
1275
+ removeReaction,
1276
+ uploadAttachment,
1277
+ refreshAttachmentUrl,
1278
+ reportMessage,
1279
+ muteConversation,
1280
+ unmuteConversation,
1281
+ getReadStatus,
127
1282
  markRead
128
1283
  };
129
1284
  }
130
1285
  function usePresence(conversationId) {
131
1286
  const { client } = useChatContext();
132
- const [members, setMembers] = react.useState([]);
133
- react.useEffect(() => {
1287
+ const [members, setMembers] = React3.useState([]);
1288
+ React3.useEffect(() => {
134
1289
  if (!conversationId) return;
135
1290
  let cancelled = false;
136
1291
  (async () => {
@@ -187,9 +1342,9 @@ function usePresence(conversationId) {
187
1342
  }
188
1343
  function useTyping(conversationId) {
189
1344
  const { client } = useChatContext();
190
- const [typingUsers, setTypingUsers] = react.useState([]);
191
- const typingTimers = react.useRef(/* @__PURE__ */ new Map());
192
- react.useEffect(() => {
1345
+ const [typingUsers, setTypingUsers] = React3.useState([]);
1346
+ const typingTimers = React3.useRef(/* @__PURE__ */ new Map());
1347
+ React3.useEffect(() => {
193
1348
  if (!conversationId) return;
194
1349
  const unsubTyping = client.on("typing", ({ conversationId: convId, userId }) => {
195
1350
  if (convId !== conversationId) return;
@@ -222,7 +1377,7 @@ function useTyping(conversationId) {
222
1377
  typingTimers.current.clear();
223
1378
  };
224
1379
  }, [client, conversationId]);
225
- const sendTyping = react.useCallback(
1380
+ const sendTyping = React3.useCallback(
226
1381
  (isTyping = true) => {
227
1382
  if (!conversationId) return;
228
1383
  client.sendTyping(conversationId, isTyping);
@@ -233,10 +1388,10 @@ function useTyping(conversationId) {
233
1388
  }
234
1389
  function useConversations(options) {
235
1390
  const { client } = useChatContext();
236
- const [conversations, setConversations] = react.useState([]);
237
- const [isLoading, setIsLoading] = react.useState(true);
238
- const refreshTimer = react.useRef(null);
239
- const fetchConversations = react.useCallback(async () => {
1391
+ const [conversations, setConversations] = React3.useState([]);
1392
+ const [isLoading, setIsLoading] = React3.useState(true);
1393
+ const refreshTimer = React3.useRef(null);
1394
+ const fetchConversations = React3.useCallback(async () => {
240
1395
  const result = await client.listConversations({
241
1396
  conversation_type: options?.conversationType
242
1397
  });
@@ -245,10 +1400,10 @@ function useConversations(options) {
245
1400
  }
246
1401
  setIsLoading(false);
247
1402
  }, [client, options?.conversationType]);
248
- react.useEffect(() => {
1403
+ React3.useEffect(() => {
249
1404
  fetchConversations();
250
1405
  }, [fetchConversations]);
251
- react.useEffect(() => {
1406
+ React3.useEffect(() => {
252
1407
  return client.on("inbox:update", () => {
253
1408
  if (refreshTimer.current) clearTimeout(refreshTimer.current);
254
1409
  refreshTimer.current = setTimeout(() => {
@@ -256,14 +1411,14 @@ function useConversations(options) {
256
1411
  }, 500);
257
1412
  });
258
1413
  }, [client, fetchConversations]);
259
- react.useEffect(() => {
1414
+ React3.useEffect(() => {
260
1415
  return client.on("read", ({ conversationId }) => {
261
1416
  setConversations(
262
1417
  (prev) => prev.map((c) => c.id === conversationId ? { ...c, unread_count: 0 } : c)
263
1418
  );
264
1419
  });
265
1420
  }, [client]);
266
- react.useEffect(() => {
1421
+ React3.useEffect(() => {
267
1422
  return () => {
268
1423
  if (refreshTimer.current) clearTimeout(refreshTimer.current);
269
1424
  };
@@ -272,8 +1427,8 @@ function useConversations(options) {
272
1427
  }
273
1428
  function useUnreadCount() {
274
1429
  const { client } = useChatContext();
275
- const [totalUnread, setTotalUnread] = react.useState(0);
276
- react.useEffect(() => {
1430
+ const [totalUnread, setTotalUnread] = React3.useState(0);
1431
+ React3.useEffect(() => {
277
1432
  (async () => {
278
1433
  const result = await client.getUnreadTotal();
279
1434
  if (result.data) {
@@ -281,12 +1436,12 @@ function useUnreadCount() {
281
1436
  }
282
1437
  })();
283
1438
  }, [client]);
284
- react.useEffect(() => {
1439
+ React3.useEffect(() => {
285
1440
  return client.on("inbox:update", () => {
286
1441
  setTotalUnread((prev) => prev + 1);
287
1442
  });
288
1443
  }, [client]);
289
- react.useEffect(() => {
1444
+ React3.useEffect(() => {
290
1445
  return client.on("read", () => {
291
1446
  (async () => {
292
1447
  const result = await client.getUnreadTotal();
@@ -301,11 +1456,18 @@ function useUnreadCount() {
301
1456
 
302
1457
  Object.defineProperty(exports, "ChatClient", {
303
1458
  enumerable: true,
304
- get: function () { return chunkYDLRISR7_cjs.ChatClient; }
1459
+ get: function () { return chunkW2PWFS3E_cjs.ChatClient; }
305
1460
  });
1461
+ exports.ChatInput = ChatInput;
1462
+ exports.ChatMessageItem = ChatMessageItem;
1463
+ exports.ChatMessageList = ChatMessageList;
306
1464
  exports.ChatProvider = ChatProvider;
1465
+ exports.ChatThread = ChatThread;
1466
+ exports.ConversationList = ConversationList;
1467
+ exports.EmojiPicker = EmojiPicker;
307
1468
  exports.useChat = useChat;
308
1469
  exports.useChatClient = useChatClient;
1470
+ exports.useChatConfig = useChatConfig;
309
1471
  exports.useConnection = useConnection;
310
1472
  exports.useConversations = useConversations;
311
1473
  exports.usePresence = usePresence;