@mieweb/ui 0.9.0-dev.0 → 0.9.0-dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/{chunk-WIAQQNFU.cjs → chunk-2A2FLQJC.cjs} +1138 -12
  2. package/dist/chunk-2A2FLQJC.cjs.map +1 -0
  3. package/dist/{chunk-5BVFVXHN.cjs → chunk-5VXRKOPR.cjs} +16 -525
  4. package/dist/chunk-5VXRKOPR.cjs.map +1 -0
  5. package/dist/{chunk-O6GJ4IYS.js → chunk-IF3X2WZP.js} +16 -521
  6. package/dist/chunk-IF3X2WZP.js.map +1 -0
  7. package/dist/{chunk-22CYDCXA.js → chunk-KATEJG2F.js} +1137 -13
  8. package/dist/chunk-KATEJG2F.js.map +1 -0
  9. package/dist/{chunk-FQBTGS3E.cjs → chunk-O425FKDI.cjs} +563 -55
  10. package/dist/chunk-O425FKDI.cjs.map +1 -0
  11. package/dist/{chunk-MXQ7J4BB.js → chunk-RPG2ZP2T.js} +557 -53
  12. package/dist/chunk-RPG2ZP2T.js.map +1 -0
  13. package/dist/components/SuperChat/index.cjs +36 -17
  14. package/dist/components/SuperChat/index.cjs.map +1 -1
  15. package/dist/components/SuperChat/index.d.cts +16 -3
  16. package/dist/components/SuperChat/index.d.ts +16 -3
  17. package/dist/components/SuperChat/index.js +31 -12
  18. package/dist/components/SuperChat/index.js.map +1 -1
  19. package/dist/components/SuperChat/plugins/index.cjs +3 -3
  20. package/dist/components/SuperChat/plugins/index.js +2 -2
  21. package/dist/index.cjs +820 -1930
  22. package/dist/index.cjs.map +1 -1
  23. package/dist/index.js +663 -1780
  24. package/dist/index.js.map +1 -1
  25. package/package.json +1 -1
  26. package/dist/chunk-22CYDCXA.js.map +0 -1
  27. package/dist/chunk-5BVFVXHN.cjs.map +0 -1
  28. package/dist/chunk-FQBTGS3E.cjs.map +0 -1
  29. package/dist/chunk-MXQ7J4BB.js.map +0 -1
  30. package/dist/chunk-O6GJ4IYS.js.map +0 -1
  31. package/dist/chunk-WIAQQNFU.cjs.map +0 -1
@@ -1,9 +1,513 @@
1
- import { DragDropZone, MessageComposer } from './chunk-O6GJ4IYS.js';
1
+ import { useMentionAutocomplete, DragDropZone, AttachmentPreviewItem, AttachmentPicker, CameraButton, MentionMenu, getFileType, generateAttachmentId } from './chunk-IF3X2WZP.js';
2
2
  import { cn } from './chunk-TQONQSJO.js';
3
- import * as React5 from 'react';
3
+ import * as React6 from 'react';
4
4
  import { cva } from 'class-variance-authority';
5
5
  import { jsx, jsxs } from 'react/jsx-runtime';
6
6
 
7
+ function isFileAccepted(file, accepted) {
8
+ if (!accepted || accepted.length === 0) return true;
9
+ const mime = file.type;
10
+ const name = file.name.toLowerCase();
11
+ return accepted.some((token) => {
12
+ if (token.startsWith(".")) return name.endsWith(token.toLowerCase());
13
+ if (token.endsWith("/*")) return mime.startsWith(token.slice(0, -1));
14
+ if (token.includes("/")) return mime === token;
15
+ return false;
16
+ });
17
+ }
18
+ function CharacterCounter({
19
+ current,
20
+ max,
21
+ showWarningAt = 0.9,
22
+ className
23
+ }) {
24
+ const percentage = current / max;
25
+ const isWarning = percentage >= showWarningAt && percentage < 1;
26
+ const isOver = current > max;
27
+ return /* @__PURE__ */ jsxs(
28
+ "span",
29
+ {
30
+ className: cn(
31
+ "text-xs tabular-nums",
32
+ isOver ? "font-medium text-red-700 dark:text-red-400" : isWarning ? "text-amber-700 dark:text-amber-400" : "text-neutral-600 dark:text-neutral-400",
33
+ className
34
+ ),
35
+ "aria-live": "polite",
36
+ "aria-label": `${current} of ${max} characters`,
37
+ children: [
38
+ current,
39
+ "/",
40
+ max
41
+ ]
42
+ }
43
+ );
44
+ }
45
+ CharacterCounter.displayName = "CharacterCounter";
46
+ var sendButtonVariants = cva(
47
+ [
48
+ "inline-flex items-center justify-center",
49
+ "rounded-full p-3 self-start",
50
+ "transition-all duration-200",
51
+ "focus:outline-none focus:ring-2 focus:ring-primary-500",
52
+ "disabled:opacity-50 disabled:cursor-not-allowed"
53
+ ],
54
+ {
55
+ variants: {
56
+ variant: {
57
+ primary: [
58
+ "bg-primary-800 text-white",
59
+ "hover:bg-primary-900",
60
+ "active:scale-95"
61
+ ],
62
+ subtle: [
63
+ "bg-transparent text-primary-800",
64
+ "hover:bg-primary-50 dark:hover:bg-primary-900/20"
65
+ ]
66
+ },
67
+ canSend: {
68
+ true: "",
69
+ false: "opacity-50 cursor-not-allowed"
70
+ }
71
+ },
72
+ defaultVariants: {
73
+ variant: "primary",
74
+ canSend: false
75
+ }
76
+ }
77
+ );
78
+ var SendButton = React6.forwardRef(
79
+ ({ className, variant, canSend, isLoading, disabled, ...props }, ref) => {
80
+ return /* @__PURE__ */ jsx(
81
+ "button",
82
+ {
83
+ ref,
84
+ type: "submit",
85
+ disabled: disabled || !canSend || isLoading,
86
+ "data-slot": "composer-send-button",
87
+ className: cn(sendButtonVariants({ variant, canSend }), className),
88
+ "aria-label": isLoading ? "Sending message" : "Send message",
89
+ ...props,
90
+ children: isLoading ? /* @__PURE__ */ jsxs(
91
+ "svg",
92
+ {
93
+ "aria-hidden": "true",
94
+ className: "h-5 w-5 animate-spin",
95
+ fill: "none",
96
+ viewBox: "0 0 24 24",
97
+ children: [
98
+ /* @__PURE__ */ jsx(
99
+ "circle",
100
+ {
101
+ className: "opacity-25",
102
+ cx: "12",
103
+ cy: "12",
104
+ r: "10",
105
+ stroke: "currentColor",
106
+ strokeWidth: "4"
107
+ }
108
+ ),
109
+ /* @__PURE__ */ jsx(
110
+ "path",
111
+ {
112
+ className: "opacity-75",
113
+ fill: "currentColor",
114
+ d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
115
+ }
116
+ )
117
+ ]
118
+ }
119
+ ) : /* @__PURE__ */ jsx(
120
+ "svg",
121
+ {
122
+ "aria-hidden": "true",
123
+ className: "h-5 w-5",
124
+ fill: "none",
125
+ viewBox: "0 0 24 24",
126
+ stroke: "currentColor",
127
+ children: /* @__PURE__ */ jsx(
128
+ "path",
129
+ {
130
+ strokeLinecap: "round",
131
+ strokeLinejoin: "round",
132
+ strokeWidth: 2,
133
+ d: "M12 19l9 2-9-18-9 18 9-2zm0 0v-8"
134
+ }
135
+ )
136
+ }
137
+ )
138
+ }
139
+ );
140
+ }
141
+ );
142
+ SendButton.displayName = "SendButton";
143
+ var MessageComposer = React6.forwardRef(
144
+ ({
145
+ onSend,
146
+ onTypingStart,
147
+ onTypingStop,
148
+ value: controlledValue,
149
+ onValueChange,
150
+ placeholder = "Type a message...",
151
+ maxLength = 1600,
152
+ showCharacterCount = false,
153
+ disabled = false,
154
+ isSending = false,
155
+ showAttachmentPicker = true,
156
+ showCameraButton = false,
157
+ acceptedFileTypes = ["image/*", "video/*", ".pdf", ".doc", ".docx"],
158
+ maxFileSize = 25 * 1024 * 1024,
159
+ maxAttachments = 10,
160
+ onError,
161
+ autoFocus = false,
162
+ replyTo = null,
163
+ onCancelReply,
164
+ variant = "default",
165
+ inputTrailing,
166
+ mentionOptions,
167
+ className
168
+ }, ref) => {
169
+ const textareaRef = React6.useRef(null);
170
+ const [internalContent, setInternalContent] = React6.useState("");
171
+ const isControlled = controlledValue !== void 0;
172
+ const content = isControlled ? controlledValue : internalContent;
173
+ const setContent = React6.useCallback(
174
+ (val) => {
175
+ if (isControlled) {
176
+ const newVal = typeof val === "function" ? val(controlledValue) : val;
177
+ onValueChange?.(newVal);
178
+ } else {
179
+ setInternalContent(val);
180
+ }
181
+ },
182
+ [isControlled, controlledValue, onValueChange]
183
+ );
184
+ const [attachments, setAttachments] = React6.useState(
185
+ []
186
+ );
187
+ const [isTyping, setIsTyping] = React6.useState(false);
188
+ const typingTimeoutRef = React6.useRef(null);
189
+ React6.useImperativeHandle(ref, () => textareaRef.current);
190
+ React6.useEffect(() => {
191
+ const textarea = textareaRef.current;
192
+ if (textarea) {
193
+ textarea.style.height = "auto";
194
+ textarea.style.height = `${Math.min(textarea.scrollHeight, 150)}px`;
195
+ }
196
+ }, [content]);
197
+ React6.useEffect(() => {
198
+ if (content.length > 0 && !isTyping) {
199
+ setIsTyping(true);
200
+ onTypingStart?.();
201
+ }
202
+ if (typingTimeoutRef.current) {
203
+ clearTimeout(typingTimeoutRef.current);
204
+ }
205
+ typingTimeoutRef.current = setTimeout(() => {
206
+ if (isTyping) {
207
+ setIsTyping(false);
208
+ onTypingStop?.();
209
+ }
210
+ }, 2e3);
211
+ return () => {
212
+ if (typingTimeoutRef.current) {
213
+ clearTimeout(typingTimeoutRef.current);
214
+ }
215
+ };
216
+ }, [content, isTyping, onTypingStart, onTypingStop]);
217
+ React6.useEffect(() => {
218
+ if (autoFocus) {
219
+ textareaRef.current?.focus();
220
+ }
221
+ }, [autoFocus]);
222
+ React6.useEffect(() => {
223
+ if (replyTo) {
224
+ textareaRef.current?.focus();
225
+ }
226
+ }, [replyTo]);
227
+ const mention = useMentionAutocomplete({
228
+ options: mentionOptions,
229
+ value: content,
230
+ setValue: setContent,
231
+ textareaRef
232
+ });
233
+ const canSend = (content.trim().length > 0 || attachments.length > 0) && content.length <= maxLength && !disabled && !isSending;
234
+ const handleSubmit = async (event) => {
235
+ event.preventDefault();
236
+ if (!canSend) return;
237
+ const message = {
238
+ content: content.trim(),
239
+ attachments: attachments.map((a) => a.file),
240
+ replyToId: replyTo?.id
241
+ };
242
+ setContent("");
243
+ setAttachments([]);
244
+ setIsTyping(false);
245
+ onTypingStop?.();
246
+ try {
247
+ await onSend(message);
248
+ } catch {
249
+ setContent(message.content);
250
+ onError?.("Failed to send message");
251
+ }
252
+ };
253
+ const handleKeyDown = (event) => {
254
+ if (mention.handleKeyDown(event)) return;
255
+ if (event.key === "Enter" && !event.shiftKey) {
256
+ event.preventDefault();
257
+ if (canSend) {
258
+ handleSubmit(event);
259
+ }
260
+ }
261
+ };
262
+ const handleFilesSelected = (files) => {
263
+ const remainingSlots = maxAttachments - attachments.length;
264
+ const filesToAdd = files.slice(0, remainingSlots);
265
+ if (files.length > remainingSlots) {
266
+ onError?.(`Maximum ${maxAttachments} attachments allowed`);
267
+ }
268
+ const newAttachments = filesToAdd.map((file) => {
269
+ const type = getFileType(file.type);
270
+ let previewUrl;
271
+ if (type === "image" || type === "video") {
272
+ previewUrl = URL.createObjectURL(file);
273
+ }
274
+ return {
275
+ id: generateAttachmentId(),
276
+ file,
277
+ previewUrl,
278
+ type,
279
+ state: "pending"
280
+ };
281
+ });
282
+ setAttachments((prev) => [...prev, ...newAttachments]);
283
+ };
284
+ const handlePaste = (event) => {
285
+ if (!showAttachmentPicker || disabled) return;
286
+ const pasted = Array.from(event.clipboardData.items).filter((item) => item.kind === "file").map((item) => item.getAsFile()).filter((f) => f !== null).filter((f) => isFileAccepted(f, acceptedFileTypes));
287
+ if (pasted.length === 0) return;
288
+ event.preventDefault();
289
+ handleFilesSelected(pasted);
290
+ };
291
+ const handleRemoveAttachment = (attachmentId) => {
292
+ setAttachments((prev) => {
293
+ const attachment = prev.find((a) => a.id === attachmentId);
294
+ if (attachment?.previewUrl) {
295
+ URL.revokeObjectURL(attachment.previewUrl);
296
+ }
297
+ return prev.filter((a) => a.id !== attachmentId);
298
+ });
299
+ };
300
+ React6.useEffect(() => {
301
+ const currentAttachments = attachments;
302
+ return () => {
303
+ currentAttachments.forEach((a) => {
304
+ if (a.previewUrl) {
305
+ URL.revokeObjectURL(a.previewUrl);
306
+ }
307
+ });
308
+ };
309
+ }, [attachments]);
310
+ return /* @__PURE__ */ jsx(
311
+ DragDropZone,
312
+ {
313
+ onFilesDropped: handleFilesSelected,
314
+ acceptedTypes: acceptedFileTypes,
315
+ maxFileSize,
316
+ maxFiles: maxAttachments - attachments.length,
317
+ disabled: disabled || attachments.length >= maxAttachments,
318
+ onError,
319
+ className: cn("w-full", className),
320
+ children: /* @__PURE__ */ jsxs(
321
+ "form",
322
+ {
323
+ onSubmit: handleSubmit,
324
+ "data-slot": "message-composer",
325
+ className: "w-full",
326
+ children: [
327
+ replyTo && /* @__PURE__ */ jsxs(
328
+ "div",
329
+ {
330
+ "data-slot": "composer-reply-preview",
331
+ className: cn(
332
+ "flex items-center gap-2 px-4 py-2",
333
+ "bg-neutral-50 dark:bg-neutral-800/50",
334
+ "border-primary-500 border-s-4"
335
+ ),
336
+ children: [
337
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
338
+ /* @__PURE__ */ jsxs("span", { className: "text-primary-800 dark:text-primary-400 text-xs font-medium", children: [
339
+ "Replying to ",
340
+ replyTo.senderName
341
+ ] }),
342
+ /* @__PURE__ */ jsx("p", { className: "truncate text-sm text-neutral-600 dark:text-neutral-300", children: replyTo.content })
343
+ ] }),
344
+ /* @__PURE__ */ jsx(
345
+ "button",
346
+ {
347
+ type: "button",
348
+ onClick: onCancelReply,
349
+ className: cn(
350
+ "shrink-0 rounded p-1",
351
+ "text-neutral-500 hover:text-neutral-700",
352
+ "dark:text-neutral-400 dark:hover:text-neutral-200",
353
+ "focus:ring-primary-500 focus:ring-2 focus:outline-none"
354
+ ),
355
+ "aria-label": "Cancel reply",
356
+ children: /* @__PURE__ */ jsx(
357
+ "svg",
358
+ {
359
+ "aria-hidden": "true",
360
+ className: "h-4 w-4",
361
+ fill: "none",
362
+ viewBox: "0 0 24 24",
363
+ stroke: "currentColor",
364
+ children: /* @__PURE__ */ jsx(
365
+ "path",
366
+ {
367
+ strokeLinecap: "round",
368
+ strokeLinejoin: "round",
369
+ strokeWidth: 2,
370
+ d: "M6 18L18 6M6 6l12 12"
371
+ }
372
+ )
373
+ }
374
+ )
375
+ }
376
+ )
377
+ ]
378
+ }
379
+ ),
380
+ attachments.length > 0 && /* @__PURE__ */ jsx(
381
+ "div",
382
+ {
383
+ "data-slot": "composer-attachments",
384
+ className: cn(
385
+ "flex flex-wrap gap-2 p-3",
386
+ "border-t border-neutral-200 dark:border-neutral-700"
387
+ ),
388
+ children: attachments.map((attachment) => /* @__PURE__ */ jsx(
389
+ AttachmentPreviewItem,
390
+ {
391
+ attachment,
392
+ onRemove: () => handleRemoveAttachment(attachment.id)
393
+ },
394
+ attachment.id
395
+ ))
396
+ }
397
+ ),
398
+ /* @__PURE__ */ jsxs(
399
+ "div",
400
+ {
401
+ "data-slot": "composer-input-area",
402
+ className: cn(
403
+ "flex items-center gap-2 p-3",
404
+ "bg-white dark:bg-neutral-900",
405
+ variant === "default" && "border-t border-neutral-200 dark:border-neutral-700"
406
+ ),
407
+ children: [
408
+ showAttachmentPicker && /* @__PURE__ */ jsx(
409
+ AttachmentPicker,
410
+ {
411
+ onFilesSelected: handleFilesSelected,
412
+ acceptedTypes: acceptedFileTypes,
413
+ maxFileSize,
414
+ maxFiles: maxAttachments - attachments.length,
415
+ disabled: disabled || attachments.length >= maxAttachments,
416
+ onError
417
+ }
418
+ ),
419
+ showCameraButton && /* @__PURE__ */ jsx(
420
+ CameraButton,
421
+ {
422
+ onCapture: (file) => handleFilesSelected([file]),
423
+ disabled: disabled || attachments.length >= maxAttachments
424
+ }
425
+ ),
426
+ /* @__PURE__ */ jsxs(
427
+ "div",
428
+ {
429
+ "data-slot": "composer-input-wrapper",
430
+ className: "relative flex-1",
431
+ ref: mention.anchorRef,
432
+ children: [
433
+ /* @__PURE__ */ jsx(MentionMenu, { mention }),
434
+ /* @__PURE__ */ jsx(
435
+ "textarea",
436
+ {
437
+ ref: textareaRef,
438
+ "data-slot": "composer-input",
439
+ value: content,
440
+ onChange: (e) => {
441
+ setContent(e.target.value);
442
+ mention.sync(
443
+ e.target.value,
444
+ e.target.selectionStart ?? e.target.value.length
445
+ );
446
+ },
447
+ onClick: (e) => {
448
+ const el = e.currentTarget;
449
+ mention.sync(el.value, el.selectionStart ?? el.value.length);
450
+ },
451
+ onKeyDown: handleKeyDown,
452
+ onPaste: handlePaste,
453
+ placeholder,
454
+ disabled: disabled || isSending,
455
+ rows: 1,
456
+ className: cn(
457
+ "w-full resize-none rounded-2xl py-2.5",
458
+ inputTrailing ? "ps-4 pe-10" : "px-4",
459
+ "bg-neutral-100 dark:bg-neutral-800",
460
+ "text-neutral-900 dark:text-neutral-100",
461
+ "placeholder:text-neutral-400 dark:placeholder:text-neutral-500",
462
+ "focus:ring-primary-500 focus:ring-2 focus:outline-none",
463
+ "disabled:cursor-not-allowed disabled:opacity-50",
464
+ "transition-colors",
465
+ "max-h-[150px]"
466
+ ),
467
+ "aria-label": "Message",
468
+ "aria-describedby": showCharacterCount ? "char-count" : void 0,
469
+ ...mention.inputProps
470
+ }
471
+ ),
472
+ inputTrailing && /* @__PURE__ */ jsx(
473
+ "div",
474
+ {
475
+ "data-slot": "composer-input-trailing",
476
+ className: "pointer-events-none absolute end-1 top-0 flex h-[44px] items-center [&>*]:pointer-events-auto",
477
+ children: inputTrailing
478
+ }
479
+ ),
480
+ showCharacterCount && /* @__PURE__ */ jsx(
481
+ "div",
482
+ {
483
+ "data-slot": "composer-char-count",
484
+ id: "char-count",
485
+ className: "absolute end-3 bottom-1.5",
486
+ children: /* @__PURE__ */ jsx(CharacterCounter, { current: content.length, max: maxLength })
487
+ }
488
+ )
489
+ ]
490
+ }
491
+ ),
492
+ /* @__PURE__ */ jsx(
493
+ SendButton,
494
+ {
495
+ canSend,
496
+ isLoading: isSending,
497
+ disabled
498
+ }
499
+ )
500
+ ]
501
+ }
502
+ )
503
+ ]
504
+ }
505
+ )
506
+ }
507
+ );
508
+ }
509
+ );
510
+ MessageComposer.displayName = "MessageComposer";
7
511
  var statusIconVariants = cva(
8
512
  "inline-flex items-center gap-0.5 text-current",
9
513
  {
@@ -384,7 +888,7 @@ function defaultFormatTimestamp(timestamp) {
384
888
  minute: "2-digit"
385
889
  });
386
890
  }
387
- var MessageBubble = React5.forwardRef(
891
+ var MessageBubble = React6.forwardRef(
388
892
  ({
389
893
  className,
390
894
  message,
@@ -859,7 +1363,7 @@ function LoadMoreButton({
859
1363
  );
860
1364
  }
861
1365
  LoadMoreButton.displayName = "LoadMoreButton";
862
- var MessageList = React5.forwardRef(
1366
+ var MessageList = React6.forwardRef(
863
1367
  ({
864
1368
  messages,
865
1369
  currentUser,
@@ -878,19 +1382,19 @@ var MessageList = React5.forwardRef(
878
1382
  className,
879
1383
  autoScroll = "onNewMessage"
880
1384
  }, ref) => {
881
- const scrollContainerRef = React5.useRef(null);
882
- const bottomRef = React5.useRef(null);
883
- const [isUserScrolled, setIsUserScrolled] = React5.useState(false);
884
- const prevMessageCountRef = React5.useRef(messages.length);
885
- React5.useImperativeHandle(ref, () => scrollContainerRef.current);
886
- const handleScroll = React5.useCallback(() => {
1385
+ const scrollContainerRef = React6.useRef(null);
1386
+ const bottomRef = React6.useRef(null);
1387
+ const [isUserScrolled, setIsUserScrolled] = React6.useState(false);
1388
+ const prevMessageCountRef = React6.useRef(messages.length);
1389
+ React6.useImperativeHandle(ref, () => scrollContainerRef.current);
1390
+ const handleScroll = React6.useCallback(() => {
887
1391
  const container = scrollContainerRef.current;
888
1392
  if (!container) return;
889
1393
  const { scrollTop, scrollHeight, clientHeight } = container;
890
1394
  const isAtBottom = scrollHeight - scrollTop - clientHeight < 100;
891
1395
  setIsUserScrolled(!isAtBottom);
892
1396
  }, []);
893
- React5.useEffect(() => {
1397
+ React6.useEffect(() => {
894
1398
  const container = scrollContainerRef.current;
895
1399
  const bottom = bottomRef.current;
896
1400
  if (!container || !bottom) return;
@@ -906,7 +1410,7 @@ var MessageList = React5.forwardRef(
906
1410
  }
907
1411
  }
908
1412
  }, [messages, currentUser.id, autoScroll, isUserScrolled]);
909
- React5.useEffect(() => {
1413
+ React6.useEffect(() => {
910
1414
  const bottom = bottomRef.current;
911
1415
  if (bottom && !isLoading) {
912
1416
  bottom.scrollIntoView();
@@ -959,7 +1463,7 @@ var MessageList = React5.forwardRef(
959
1463
  {
960
1464
  "data-slot": "message-list-content",
961
1465
  className: "flex flex-col gap-1 p-4",
962
- children: messageGroups.map((group) => /* @__PURE__ */ jsxs(React5.Fragment, { children: [
1466
+ children: messageGroups.map((group) => /* @__PURE__ */ jsxs(React6.Fragment, { children: [
963
1467
  groupByDate && group.label && /* @__PURE__ */ jsx(DateSeparator, { label: group.label }),
964
1468
  group.messages.map((message, index) => {
965
1469
  const prevMessage = group.messages[index - 1];
@@ -1098,7 +1602,7 @@ function formatLastSeen(date) {
1098
1602
  if (diffDays < 7) return `${diffDays}d ago`;
1099
1603
  return date.toLocaleDateString();
1100
1604
  }
1101
- var ConversationHeader = React5.forwardRef(
1605
+ var ConversationHeader = React6.forwardRef(
1102
1606
  ({
1103
1607
  className,
1104
1608
  size,
@@ -1220,7 +1724,7 @@ var ConversationHeader = React5.forwardRef(
1220
1724
  }
1221
1725
  );
1222
1726
  ConversationHeader.displayName = "ConversationHeader";
1223
- var ConversationListItem = React5.forwardRef(({ className, conversation, isSelected, onSelect, ...props }, ref) => {
1727
+ var ConversationListItem = React6.forwardRef(({ className, conversation, isSelected, onSelect, ...props }, ref) => {
1224
1728
  const participant = conversation.participants.find((p) => !p.isCurrentUser);
1225
1729
  const title = getConversationTitle(conversation, participant);
1226
1730
  const avatarUrl = conversation.avatarUrl || participant?.avatarUrl;
@@ -1428,7 +1932,7 @@ function ConversationListSkeleton({
1428
1932
  }
1429
1933
  ConversationListSkeleton.displayName = "ConversationListSkeleton";
1430
1934
  function LightboxModal({ attachment, onClose }) {
1431
- React5.useEffect(() => {
1935
+ React6.useEffect(() => {
1432
1936
  const handleKeyDown = (event) => {
1433
1937
  if (event.key === "Escape") {
1434
1938
  onClose();
@@ -1525,7 +2029,7 @@ function LightboxModal({ attachment, onClose }) {
1525
2029
  );
1526
2030
  }
1527
2031
  LightboxModal.displayName = "LightboxModal";
1528
- var MessageThread = React5.forwardRef(
2032
+ var MessageThread = React6.forwardRef(
1529
2033
  ({
1530
2034
  conversation,
1531
2035
  messages,
@@ -1556,8 +2060,8 @@ var MessageThread = React5.forwardRef(
1556
2060
  onError,
1557
2061
  className
1558
2062
  }, ref) => {
1559
- const [lightboxAttachment, setLightboxAttachment] = React5.useState(null);
1560
- const [replyTo, setReplyTo] = React5.useState(null);
2063
+ const [lightboxAttachment, setLightboxAttachment] = React6.useState(null);
2064
+ const [replyTo, setReplyTo] = React6.useState(null);
1561
2065
  const participant = conversation?.type === "direct" ? conversation.participants.find((p) => p.id !== currentUser.id) : void 0;
1562
2066
  const handleAttachmentClick = (attachment, message) => {
1563
2067
  if (attachment.type === "image" || attachment.type === "video") {
@@ -1729,13 +2233,13 @@ function useMessages(options) {
1729
2233
  onRetry,
1730
2234
  onLoadMore
1731
2235
  } = options;
1732
- const [messages, setMessages] = React5.useState(initialMessages);
1733
- const [isSending, setIsSending] = React5.useState(false);
1734
- const [isLoadingMore, setIsLoadingMore] = React5.useState(false);
1735
- React5.useEffect(() => {
2236
+ const [messages, setMessages] = React6.useState(initialMessages);
2237
+ const [isSending, setIsSending] = React6.useState(false);
2238
+ const [isLoadingMore, setIsLoadingMore] = React6.useState(false);
2239
+ React6.useEffect(() => {
1736
2240
  setMessages(initialMessages);
1737
2241
  }, [initialMessages]);
1738
- const addMessage = React5.useCallback((message) => {
2242
+ const addMessage = React6.useCallback((message) => {
1739
2243
  setMessages((prev) => {
1740
2244
  if (prev.some((m) => m.id === message.id)) {
1741
2245
  return prev;
@@ -1743,7 +2247,7 @@ function useMessages(options) {
1743
2247
  return [...prev, message];
1744
2248
  });
1745
2249
  }, []);
1746
- const updateMessage = React5.useCallback(
2250
+ const updateMessage = React6.useCallback(
1747
2251
  (messageId, updates) => {
1748
2252
  setMessages(
1749
2253
  (prev) => prev.map((m) => m.id === messageId ? { ...m, ...updates } : m)
@@ -1751,22 +2255,22 @@ function useMessages(options) {
1751
2255
  },
1752
2256
  []
1753
2257
  );
1754
- const removeMessage = React5.useCallback((messageId) => {
2258
+ const removeMessage = React6.useCallback((messageId) => {
1755
2259
  setMessages((prev) => prev.filter((m) => m.id !== messageId));
1756
2260
  }, []);
1757
- const updateStatus = React5.useCallback(
2261
+ const updateStatus = React6.useCallback(
1758
2262
  (messageId, status) => {
1759
2263
  updateMessage(messageId, { status });
1760
2264
  },
1761
2265
  [updateMessage]
1762
2266
  );
1763
- const markAsRead = React5.useCallback(
2267
+ const markAsRead = React6.useCallback(
1764
2268
  (messageId) => {
1765
2269
  updateStatus(messageId, "read");
1766
2270
  },
1767
2271
  [updateStatus]
1768
2272
  );
1769
- const sendMessage = React5.useCallback(
2273
+ const sendMessage = React6.useCallback(
1770
2274
  async (newMessage) => {
1771
2275
  const optimisticId = `optimistic-${Date.now()}`;
1772
2276
  const optimisticMessage = {
@@ -1798,7 +2302,7 @@ function useMessages(options) {
1798
2302
  },
1799
2303
  [currentUser, onSend, addMessage, updateStatus]
1800
2304
  );
1801
- const retryMessage = React5.useCallback(
2305
+ const retryMessage = React6.useCallback(
1802
2306
  async (messageId) => {
1803
2307
  updateStatus(messageId, "sending");
1804
2308
  try {
@@ -1812,7 +2316,7 @@ function useMessages(options) {
1812
2316
  },
1813
2317
  [onRetry, updateStatus]
1814
2318
  );
1815
- const loadMore = React5.useCallback(async () => {
2319
+ const loadMore = React6.useCallback(async () => {
1816
2320
  if (isLoadingMore || !onLoadMore) return;
1817
2321
  setIsLoadingMore(true);
1818
2322
  try {
@@ -1843,13 +2347,13 @@ function useTypingIndicator(options = {}) {
1843
2347
  onTypingStart,
1844
2348
  onTypingStop
1845
2349
  } = options;
1846
- const [participants, setParticipants] = React5.useState(initialParticipants);
1847
- const [isLocalTyping, setIsLocalTyping] = React5.useState(false);
1848
- const typingTimeoutRef = React5.useRef(null);
1849
- React5.useEffect(() => {
2350
+ const [participants, setParticipants] = React6.useState(initialParticipants);
2351
+ const [isLocalTyping, setIsLocalTyping] = React6.useState(false);
2352
+ const typingTimeoutRef = React6.useRef(null);
2353
+ React6.useEffect(() => {
1850
2354
  setParticipants(initialParticipants);
1851
2355
  }, [initialParticipants]);
1852
- const startTyping = React5.useCallback(() => {
2356
+ const startTyping = React6.useCallback(() => {
1853
2357
  if (!isLocalTyping) {
1854
2358
  setIsLocalTyping(true);
1855
2359
  onTypingStart?.();
@@ -1862,21 +2366,21 @@ function useTypingIndicator(options = {}) {
1862
2366
  onTypingStop?.();
1863
2367
  }, debounceTime);
1864
2368
  }, [isLocalTyping, debounceTime, onTypingStart, onTypingStop]);
1865
- const stopTyping = React5.useCallback(() => {
2369
+ const stopTyping = React6.useCallback(() => {
1866
2370
  if (typingTimeoutRef.current) {
1867
2371
  clearTimeout(typingTimeoutRef.current);
1868
2372
  }
1869
2373
  setIsLocalTyping(false);
1870
2374
  onTypingStop?.();
1871
2375
  }, [onTypingStop]);
1872
- React5.useEffect(() => {
2376
+ React6.useEffect(() => {
1873
2377
  return () => {
1874
2378
  if (typingTimeoutRef.current) {
1875
2379
  clearTimeout(typingTimeoutRef.current);
1876
2380
  }
1877
2381
  };
1878
2382
  }, []);
1879
- const typingState = React5.useMemo(
2383
+ const typingState = React6.useMemo(
1880
2384
  () => ({
1881
2385
  participants,
1882
2386
  lastUpdated: /* @__PURE__ */ new Date()
@@ -1892,11 +2396,11 @@ function useTypingIndicator(options = {}) {
1892
2396
  }
1893
2397
  function useMessageScroll(options) {
1894
2398
  const { messages, currentUserId, threshold = 100 } = options;
1895
- const scrollContainerRef = React5.useRef(null);
1896
- const bottomRef = React5.useRef(null);
1897
- const [isScrolledUp, setIsScrolledUp] = React5.useState(false);
1898
- const prevMessageCountRef = React5.useRef(messages.length);
1899
- React5.useEffect(() => {
2399
+ const scrollContainerRef = React6.useRef(null);
2400
+ const bottomRef = React6.useRef(null);
2401
+ const [isScrolledUp, setIsScrolledUp] = React6.useState(false);
2402
+ const prevMessageCountRef = React6.useRef(messages.length);
2403
+ React6.useEffect(() => {
1900
2404
  const container = scrollContainerRef.current;
1901
2405
  if (!container) return;
1902
2406
  const handleScroll = () => {
@@ -1907,12 +2411,12 @@ function useMessageScroll(options) {
1907
2411
  container.addEventListener("scroll", handleScroll);
1908
2412
  return () => container.removeEventListener("scroll", handleScroll);
1909
2413
  }, [threshold]);
1910
- const scrollToBottom = React5.useCallback((smooth = true) => {
2414
+ const scrollToBottom = React6.useCallback((smooth = true) => {
1911
2415
  bottomRef.current?.scrollIntoView({
1912
2416
  behavior: smooth ? "smooth" : "auto"
1913
2417
  });
1914
2418
  }, []);
1915
- React5.useEffect(() => {
2419
+ React6.useEffect(() => {
1916
2420
  const messageCountChanged = messages.length !== prevMessageCountRef.current;
1917
2421
  prevMessageCountRef.current = messages.length;
1918
2422
  if (!messageCountChanged) return;
@@ -1931,9 +2435,9 @@ function useMessageScroll(options) {
1931
2435
  }
1932
2436
  function useReadReceipts(options) {
1933
2437
  const { currentUserId, onMarkRead, threshold = 0.5 } = options;
1934
- const observerRef = React5.useRef(null);
1935
- const observedMessagesRef = React5.useRef(/* @__PURE__ */ new Set());
1936
- React5.useEffect(() => {
2438
+ const observerRef = React6.useRef(null);
2439
+ const observedMessagesRef = React6.useRef(/* @__PURE__ */ new Set());
2440
+ React6.useEffect(() => {
1937
2441
  observerRef.current = new IntersectionObserver(
1938
2442
  (entries) => {
1939
2443
  entries.forEach((entry) => {
@@ -1952,7 +2456,7 @@ function useReadReceipts(options) {
1952
2456
  observerRef.current?.disconnect();
1953
2457
  };
1954
2458
  }, [onMarkRead, threshold]);
1955
- const observeMessage = React5.useCallback(
2459
+ const observeMessage = React6.useCallback(
1956
2460
  (element, message) => {
1957
2461
  if (!element || !observerRef.current) return;
1958
2462
  if (message.sender.id !== currentUserId && message.status !== "read" && !observedMessagesRef.current.has(message.id)) {
@@ -1965,6 +2469,6 @@ function useReadReceipts(options) {
1965
2469
  return { observeMessage };
1966
2470
  }
1967
2471
 
1968
- export { AttachmentPreview, ConversationHeader, ConversationListItem, ConversationListSkeleton, DateSeparator, EmptyState, LightboxModal, LoadMoreButton, MessageBubble, MessageList, MessageStatusIcon, MessageThread, MessagingSplitView, ReadReceiptIndicator, SkeletonMessage, TypingIndicator, bubbleVariants, formatDateLabel, formatFileSize, formatLastSeen, getConversationSubtitle, getConversationTitle, groupMessagesByDate, headerVariants, isSameSenderGroup, useMessageScroll, useMessages, useReadReceipts, useTypingIndicator };
1969
- //# sourceMappingURL=chunk-MXQ7J4BB.js.map
1970
- //# sourceMappingURL=chunk-MXQ7J4BB.js.map
2472
+ export { AttachmentPreview, CharacterCounter, ConversationHeader, ConversationListItem, ConversationListSkeleton, DateSeparator, EmptyState, LightboxModal, LoadMoreButton, MessageBubble, MessageComposer, MessageList, MessageStatusIcon, MessageThread, MessagingSplitView, ReadReceiptIndicator, SendButton, SkeletonMessage, TypingIndicator, bubbleVariants, formatDateLabel, formatFileSize, formatLastSeen, getConversationSubtitle, getConversationTitle, groupMessagesByDate, headerVariants, isSameSenderGroup, sendButtonVariants, useMessageScroll, useMessages, useReadReceipts, useTypingIndicator };
2473
+ //# sourceMappingURL=chunk-RPG2ZP2T.js.map
2474
+ //# sourceMappingURL=chunk-RPG2ZP2T.js.map