@mieweb/ui 0.9.0 → 0.10.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 (121) hide show
  1. package/MIGRATION.md +112 -0
  2. package/dist/{Animated-Cs-naa2g.d.cts → Animated-XmH6k8pZ.d.cts} +16 -3
  3. package/dist/{Animated-Cs-naa2g.d.ts → Animated-XmH6k8pZ.d.ts} +16 -3
  4. package/dist/{chunk-XUVEF2RQ.js → chunk-24SEAAOH.js} +2 -2
  5. package/dist/{chunk-XUVEF2RQ.js.map → chunk-24SEAAOH.js.map} +1 -1
  6. package/dist/{chunk-FUFBRKOG.cjs → chunk-2WBPNXO2.cjs} +21 -21
  7. package/dist/{chunk-FUFBRKOG.cjs.map → chunk-2WBPNXO2.cjs.map} +1 -1
  8. package/dist/{chunk-IUDY6S7I.cjs → chunk-37UZHCLM.cjs} +35 -14
  9. package/dist/chunk-37UZHCLM.cjs.map +1 -0
  10. package/dist/{chunk-L52N6BEN.cjs → chunk-AUOTJQGO.cjs} +7 -7
  11. package/dist/{chunk-L52N6BEN.cjs.map → chunk-AUOTJQGO.cjs.map} +1 -1
  12. package/dist/{chunk-6XDPJWSC.cjs → chunk-E7G22IQM.cjs} +32 -28
  13. package/dist/chunk-E7G22IQM.cjs.map +1 -0
  14. package/dist/chunk-IIFQ6BSJ.js +1888 -0
  15. package/dist/chunk-IIFQ6BSJ.js.map +1 -0
  16. package/dist/{chunk-ONNY7UO4.js → chunk-JDJQTKTX.js} +3 -3
  17. package/dist/{chunk-ONNY7UO4.js.map → chunk-JDJQTKTX.js.map} +1 -1
  18. package/dist/{chunk-BJKAVWED.js → chunk-MBWF36DJ.js} +35 -14
  19. package/dist/chunk-MBWF36DJ.js.map +1 -0
  20. package/dist/chunk-NV2CWBI2.cjs +290 -0
  21. package/dist/chunk-NV2CWBI2.cjs.map +1 -0
  22. package/dist/{chunk-FQBTGS3E.cjs → chunk-OFUKTVBN.cjs} +363 -285
  23. package/dist/chunk-OFUKTVBN.cjs.map +1 -0
  24. package/dist/{chunk-7KN6J7XV.cjs → chunk-OMAACCFW.cjs} +7 -7
  25. package/dist/{chunk-7KN6J7XV.cjs.map → chunk-OMAACCFW.cjs.map} +1 -1
  26. package/dist/chunk-OYQ7GYAA.cjs +1921 -0
  27. package/dist/chunk-OYQ7GYAA.cjs.map +1 -0
  28. package/dist/{chunk-YJP6DUFO.cjs → chunk-PKD3JLS2.cjs} +6 -6
  29. package/dist/{chunk-YJP6DUFO.cjs.map → chunk-PKD3JLS2.cjs.map} +1 -1
  30. package/dist/{chunk-CXDMZT45.cjs → chunk-PY7XAG4D.cjs} +27 -4
  31. package/dist/chunk-PY7XAG4D.cjs.map +1 -0
  32. package/dist/{chunk-5H7SAEQO.js → chunk-QKFJDQEK.js} +3 -3
  33. package/dist/{chunk-5H7SAEQO.js.map → chunk-QKFJDQEK.js.map} +1 -1
  34. package/dist/{chunk-XJTFZLWH.cjs → chunk-QO6PRHDX.cjs} +2 -2
  35. package/dist/{chunk-XJTFZLWH.cjs.map → chunk-QO6PRHDX.cjs.map} +1 -1
  36. package/dist/{chunk-OHDIJOGG.js → chunk-RD3JDFYJ.js} +3 -3
  37. package/dist/{chunk-OHDIJOGG.js.map → chunk-RD3JDFYJ.js.map} +1 -1
  38. package/dist/{chunk-4SM3I7WE.cjs → chunk-RXPEZXAB.cjs} +67 -38
  39. package/dist/chunk-RXPEZXAB.cjs.map +1 -0
  40. package/dist/{chunk-VTR2KGLY.cjs → chunk-S3BCHPXT.cjs} +9 -9
  41. package/dist/{chunk-VTR2KGLY.cjs.map → chunk-S3BCHPXT.cjs.map} +1 -1
  42. package/dist/{chunk-KU3XRHWA.js → chunk-TZX76ZNZ.js} +67 -38
  43. package/dist/chunk-TZX76ZNZ.js.map +1 -0
  44. package/dist/{chunk-MXQ7J4BB.js → chunk-VSVGMMNO.js} +361 -284
  45. package/dist/chunk-VSVGMMNO.js.map +1 -0
  46. package/dist/{chunk-Q7G56ZIO.js → chunk-WUBMDOTG.js} +3 -3
  47. package/dist/{chunk-Q7G56ZIO.js.map → chunk-WUBMDOTG.js.map} +1 -1
  48. package/dist/{chunk-DGRXAIWK.js → chunk-X5YM3KXY.js} +27 -4
  49. package/dist/chunk-X5YM3KXY.js.map +1 -0
  50. package/dist/chunk-XCP454Q6.js +263 -0
  51. package/dist/chunk-XCP454Q6.js.map +1 -0
  52. package/dist/{chunk-I3DQXUSC.js → chunk-Y6DFMDMH.js} +3 -3
  53. package/dist/{chunk-I3DQXUSC.js.map → chunk-Y6DFMDMH.js.map} +1 -1
  54. package/dist/{chunk-ODYQYLO2.js → chunk-YSEMAI4I.js} +8 -4
  55. package/dist/chunk-YSEMAI4I.js.map +1 -0
  56. package/dist/components/AlertDialog/index.cjs +4 -4
  57. package/dist/components/AlertDialog/index.js +3 -3
  58. package/dist/components/Collapsible/index.cjs +6 -4
  59. package/dist/components/Collapsible/index.js +3 -1
  60. package/dist/components/Dropdown/index.cjs +9 -8
  61. package/dist/components/Dropdown/index.js +2 -1
  62. package/dist/components/KeyboardShortcutsOverlay/index.cjs +5 -5
  63. package/dist/components/KeyboardShortcutsOverlay/index.js +3 -3
  64. package/dist/components/MediaEditor/index.cjs +8 -8
  65. package/dist/components/MediaEditor/index.js +3 -3
  66. package/dist/components/Modal/index.cjs +10 -10
  67. package/dist/components/Modal/index.js +2 -2
  68. package/dist/components/RichTextEditor/index.cjs +6 -5
  69. package/dist/components/RichTextEditor/index.js +3 -2
  70. package/dist/components/SuperChat/index.cjs +40 -17
  71. package/dist/components/SuperChat/index.cjs.map +1 -1
  72. package/dist/components/SuperChat/index.d.cts +20 -7
  73. package/dist/components/SuperChat/index.d.ts +20 -7
  74. package/dist/components/SuperChat/index.js +35 -12
  75. package/dist/components/SuperChat/index.js.map +1 -1
  76. package/dist/components/SuperChat/plugins/index.cjs +10 -3
  77. package/dist/components/SuperChat/plugins/index.cjs.map +1 -1
  78. package/dist/components/SuperChat/plugins/index.d.cts +3 -3
  79. package/dist/components/SuperChat/plugins/index.d.ts +3 -3
  80. package/dist/components/SuperChat/plugins/index.js +9 -2
  81. package/dist/components/SuperChat/plugins/index.js.map +1 -1
  82. package/dist/esheet.cjs +6 -6
  83. package/dist/esheet.js +3 -3
  84. package/dist/hooks/index.cjs +13 -13
  85. package/dist/hooks/index.js +1 -1
  86. package/dist/index.cjs +720 -1671
  87. package/dist/index.cjs.map +1 -1
  88. package/dist/index.d.cts +347 -347
  89. package/dist/index.d.ts +347 -347
  90. package/dist/index.js +459 -1405
  91. package/dist/index.js.map +1 -1
  92. package/dist/motion.cjs +7 -7
  93. package/dist/motion.d.cts +1 -1
  94. package/dist/motion.d.ts +1 -1
  95. package/dist/motion.js +2 -2
  96. package/dist/styles.css +1 -1
  97. package/dist/tailwind-preset.cjs +4 -4
  98. package/dist/tailwind-preset.js +1 -1
  99. package/dist/{types-DZDroUW8.d.cts → types-B97JNRNQ.d.cts} +1 -1
  100. package/dist/{types-B2RLmdVd.d.ts → types-CnPEwzlp.d.cts} +6 -2
  101. package/dist/{types-B2RLmdVd.d.cts → types-CnPEwzlp.d.ts} +6 -2
  102. package/dist/{types-9vK_YdEb.d.ts → types-gZXrfLj6.d.ts} +1 -1
  103. package/package.json +2 -1
  104. package/dist/chunk-4SM3I7WE.cjs.map +0 -1
  105. package/dist/chunk-5BVFVXHN.cjs +0 -1244
  106. package/dist/chunk-5BVFVXHN.cjs.map +0 -1
  107. package/dist/chunk-6XDPJWSC.cjs.map +0 -1
  108. package/dist/chunk-BJKAVWED.js.map +0 -1
  109. package/dist/chunk-BN75TLUN.cjs +0 -163
  110. package/dist/chunk-BN75TLUN.cjs.map +0 -1
  111. package/dist/chunk-CXDMZT45.cjs.map +0 -1
  112. package/dist/chunk-DGRXAIWK.js.map +0 -1
  113. package/dist/chunk-FQBTGS3E.cjs.map +0 -1
  114. package/dist/chunk-IUDY6S7I.cjs.map +0 -1
  115. package/dist/chunk-KU3XRHWA.js.map +0 -1
  116. package/dist/chunk-MXQ7J4BB.js.map +0 -1
  117. package/dist/chunk-O6GJ4IYS.js +0 -1210
  118. package/dist/chunk-O6GJ4IYS.js.map +0 -1
  119. package/dist/chunk-ODYQYLO2.js.map +0 -1
  120. package/dist/chunk-WYMO3IIK.js +0 -136
  121. package/dist/chunk-WYMO3IIK.js.map +0 -1
@@ -1,9 +1,288 @@
1
- import { DragDropZone, MessageComposer } from './chunk-O6GJ4IYS.js';
1
+ import { notifyComposerMigrationOnce, DragDropZone, ChatComposer, DEFAULT_MAX_FILE_SIZE, DEFAULT_ACCEPTED_FILE_TYPES, CameraButton } from './chunk-IIFQ6BSJ.js';
2
2
  import { cn } from './chunk-TQONQSJO.js';
3
- import * as React5 from 'react';
3
+ import * as React from 'react';
4
4
  import { cva } from 'class-variance-authority';
5
5
  import { jsx, jsxs } from 'react/jsx-runtime';
6
6
 
7
+ function useMessages(options) {
8
+ const {
9
+ initialMessages = [],
10
+ currentUser,
11
+ onSend,
12
+ onRetry,
13
+ onLoadMore
14
+ } = options;
15
+ const [messages, setMessages] = React.useState(initialMessages);
16
+ const [isSending, setIsSending] = React.useState(false);
17
+ const [isLoadingMore, setIsLoadingMore] = React.useState(false);
18
+ React.useEffect(() => {
19
+ setMessages(initialMessages);
20
+ }, [initialMessages]);
21
+ const addMessage = React.useCallback((message) => {
22
+ setMessages((prev) => {
23
+ if (prev.some((m) => m.id === message.id)) {
24
+ return prev;
25
+ }
26
+ return [...prev, message];
27
+ });
28
+ }, []);
29
+ const updateMessage = React.useCallback(
30
+ (messageId, updates) => {
31
+ setMessages(
32
+ (prev) => prev.map((m) => m.id === messageId ? { ...m, ...updates } : m)
33
+ );
34
+ },
35
+ []
36
+ );
37
+ const removeMessage = React.useCallback((messageId) => {
38
+ setMessages((prev) => prev.filter((m) => m.id !== messageId));
39
+ }, []);
40
+ const updateStatus = React.useCallback(
41
+ (messageId, status) => {
42
+ updateMessage(messageId, { status });
43
+ },
44
+ [updateMessage]
45
+ );
46
+ const markAsRead = React.useCallback(
47
+ (messageId) => {
48
+ updateStatus(messageId, "read");
49
+ },
50
+ [updateStatus]
51
+ );
52
+ const sendMessage = React.useCallback(
53
+ async (newMessage) => {
54
+ const optimisticId = `optimistic-${Date.now()}`;
55
+ const optimisticMessage = {
56
+ id: optimisticId,
57
+ type: "text",
58
+ content: newMessage.content,
59
+ sender: currentUser,
60
+ timestamp: /* @__PURE__ */ new Date(),
61
+ status: "sending",
62
+ attachments: []
63
+ // Would handle attachment uploads here
64
+ };
65
+ addMessage(optimisticMessage);
66
+ setIsSending(true);
67
+ try {
68
+ if (onSend) {
69
+ const sentMessage = await onSend(newMessage);
70
+ setMessages(
71
+ (prev) => prev.map((m) => m.id === optimisticId ? sentMessage : m)
72
+ );
73
+ } else {
74
+ updateStatus(optimisticId, "sent");
75
+ }
76
+ } catch {
77
+ updateStatus(optimisticId, "failed");
78
+ } finally {
79
+ setIsSending(false);
80
+ }
81
+ },
82
+ [currentUser, onSend, addMessage, updateStatus]
83
+ );
84
+ const retryMessage = React.useCallback(
85
+ async (messageId) => {
86
+ updateStatus(messageId, "sending");
87
+ try {
88
+ if (onRetry) {
89
+ await onRetry(messageId);
90
+ updateStatus(messageId, "sent");
91
+ }
92
+ } catch {
93
+ updateStatus(messageId, "failed");
94
+ }
95
+ },
96
+ [onRetry, updateStatus]
97
+ );
98
+ const loadMore = React.useCallback(async () => {
99
+ if (isLoadingMore || !onLoadMore) return;
100
+ setIsLoadingMore(true);
101
+ try {
102
+ const olderMessages = await onLoadMore();
103
+ setMessages((prev) => [...olderMessages, ...prev]);
104
+ } finally {
105
+ setIsLoadingMore(false);
106
+ }
107
+ }, [isLoadingMore, onLoadMore]);
108
+ return {
109
+ messages,
110
+ addMessage,
111
+ updateMessage,
112
+ removeMessage,
113
+ sendMessage,
114
+ retryMessage,
115
+ loadMore,
116
+ isSending,
117
+ isLoadingMore,
118
+ markAsRead,
119
+ updateStatus
120
+ };
121
+ }
122
+ function useTypingIndicator(options = {}) {
123
+ const {
124
+ typingParticipants: initialParticipants = [],
125
+ debounceTime = 2e3,
126
+ onTypingStart,
127
+ onTypingStop
128
+ } = options;
129
+ const [participants, setParticipants] = React.useState(initialParticipants);
130
+ const [isLocalTyping, setIsLocalTyping] = React.useState(false);
131
+ const typingTimeoutRef = React.useRef(null);
132
+ React.useEffect(() => {
133
+ setParticipants(initialParticipants);
134
+ }, [initialParticipants]);
135
+ const startTyping = React.useCallback(() => {
136
+ if (!isLocalTyping) {
137
+ setIsLocalTyping(true);
138
+ onTypingStart?.();
139
+ }
140
+ if (typingTimeoutRef.current) {
141
+ clearTimeout(typingTimeoutRef.current);
142
+ }
143
+ typingTimeoutRef.current = setTimeout(() => {
144
+ setIsLocalTyping(false);
145
+ onTypingStop?.();
146
+ }, debounceTime);
147
+ }, [isLocalTyping, debounceTime, onTypingStart, onTypingStop]);
148
+ const stopTyping = React.useCallback(() => {
149
+ if (typingTimeoutRef.current) {
150
+ clearTimeout(typingTimeoutRef.current);
151
+ }
152
+ setIsLocalTyping(false);
153
+ onTypingStop?.();
154
+ }, [onTypingStop]);
155
+ React.useEffect(() => {
156
+ return () => {
157
+ if (typingTimeoutRef.current) {
158
+ clearTimeout(typingTimeoutRef.current);
159
+ }
160
+ };
161
+ }, []);
162
+ const typingState = React.useMemo(
163
+ () => ({
164
+ participants,
165
+ lastUpdated: /* @__PURE__ */ new Date()
166
+ }),
167
+ [participants]
168
+ );
169
+ return {
170
+ typingState,
171
+ startTyping,
172
+ stopTyping,
173
+ setTypingParticipants: setParticipants
174
+ };
175
+ }
176
+ function useMessageScroll(options) {
177
+ const { messages, currentUserId, threshold = 100 } = options;
178
+ const scrollContainerRef = React.useRef(null);
179
+ const bottomRef = React.useRef(null);
180
+ const [isScrolledUp, setIsScrolledUp] = React.useState(false);
181
+ const prevMessageCountRef = React.useRef(messages.length);
182
+ React.useEffect(() => {
183
+ const container = scrollContainerRef.current;
184
+ if (!container) return;
185
+ const handleScroll = () => {
186
+ const { scrollTop, scrollHeight, clientHeight } = container;
187
+ const isAtBottom = scrollHeight - scrollTop - clientHeight < threshold;
188
+ setIsScrolledUp(!isAtBottom);
189
+ };
190
+ container.addEventListener("scroll", handleScroll);
191
+ return () => container.removeEventListener("scroll", handleScroll);
192
+ }, [threshold]);
193
+ const scrollToBottom = React.useCallback((smooth = true) => {
194
+ bottomRef.current?.scrollIntoView({
195
+ behavior: smooth ? "smooth" : "auto"
196
+ });
197
+ }, []);
198
+ React.useEffect(() => {
199
+ const messageCountChanged = messages.length !== prevMessageCountRef.current;
200
+ prevMessageCountRef.current = messages.length;
201
+ if (!messageCountChanged) return;
202
+ const lastMessage = messages[messages.length - 1];
203
+ const isOutgoing = lastMessage?.sender.id === currentUserId;
204
+ if (isOutgoing || !isScrolledUp) {
205
+ scrollToBottom(true);
206
+ }
207
+ }, [messages, currentUserId, isScrolledUp, scrollToBottom]);
208
+ return {
209
+ scrollContainerRef,
210
+ bottomRef,
211
+ isScrolledUp,
212
+ scrollToBottom
213
+ };
214
+ }
215
+ function useReadReceipts(options) {
216
+ const { currentUserId, onMarkRead, threshold = 0.5 } = options;
217
+ const observerRef = React.useRef(null);
218
+ const observedMessagesRef = React.useRef(/* @__PURE__ */ new Set());
219
+ React.useEffect(() => {
220
+ observerRef.current = new IntersectionObserver(
221
+ (entries) => {
222
+ entries.forEach((entry) => {
223
+ if (entry.isIntersecting) {
224
+ const messageId = entry.target.getAttribute("data-message-id");
225
+ if (messageId && !observedMessagesRef.current.has(messageId)) {
226
+ observedMessagesRef.current.add(messageId);
227
+ onMarkRead?.(messageId);
228
+ }
229
+ }
230
+ });
231
+ },
232
+ { threshold }
233
+ );
234
+ return () => {
235
+ observerRef.current?.disconnect();
236
+ };
237
+ }, [onMarkRead, threshold]);
238
+ const observeMessage = React.useCallback(
239
+ (element, message) => {
240
+ if (!element || !observerRef.current) return;
241
+ if (message.sender.id !== currentUserId && message.status !== "read" && !observedMessagesRef.current.has(message.id)) {
242
+ element.setAttribute("data-message-id", message.id);
243
+ observerRef.current.observe(element);
244
+ }
245
+ },
246
+ [currentUserId]
247
+ );
248
+ return { observeMessage };
249
+ }
250
+ function useTypingEmulation({
251
+ value,
252
+ onTypingStart,
253
+ onTypingStop
254
+ }) {
255
+ const [isTyping, setIsTyping] = React.useState(false);
256
+ const typingTimeoutRef = React.useRef(
257
+ null
258
+ );
259
+ React.useEffect(() => {
260
+ if (!onTypingStart && !onTypingStop) return;
261
+ if (value.length > 0 && !isTyping) {
262
+ setIsTyping(true);
263
+ onTypingStart?.();
264
+ }
265
+ if (typingTimeoutRef.current) {
266
+ clearTimeout(typingTimeoutRef.current);
267
+ }
268
+ typingTimeoutRef.current = setTimeout(() => {
269
+ if (isTyping) {
270
+ setIsTyping(false);
271
+ onTypingStop?.();
272
+ }
273
+ }, 2e3);
274
+ return () => {
275
+ if (typingTimeoutRef.current) {
276
+ clearTimeout(typingTimeoutRef.current);
277
+ }
278
+ };
279
+ }, [value, isTyping, onTypingStart, onTypingStop]);
280
+ const stopTyping = React.useCallback(() => {
281
+ setIsTyping(false);
282
+ onTypingStop?.();
283
+ }, [onTypingStop]);
284
+ return { stopTyping };
285
+ }
7
286
  var statusIconVariants = cva(
8
287
  "inline-flex items-center gap-0.5 text-current",
9
288
  {
@@ -384,7 +663,7 @@ function defaultFormatTimestamp(timestamp) {
384
663
  minute: "2-digit"
385
664
  });
386
665
  }
387
- var MessageBubble = React5.forwardRef(
666
+ var MessageBubble = React.forwardRef(
388
667
  ({
389
668
  className,
390
669
  message,
@@ -859,7 +1138,7 @@ function LoadMoreButton({
859
1138
  );
860
1139
  }
861
1140
  LoadMoreButton.displayName = "LoadMoreButton";
862
- var MessageList = React5.forwardRef(
1141
+ var MessageList = React.forwardRef(
863
1142
  ({
864
1143
  messages,
865
1144
  currentUser,
@@ -878,19 +1157,19 @@ var MessageList = React5.forwardRef(
878
1157
  className,
879
1158
  autoScroll = "onNewMessage"
880
1159
  }, 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(() => {
1160
+ const scrollContainerRef = React.useRef(null);
1161
+ const bottomRef = React.useRef(null);
1162
+ const [isUserScrolled, setIsUserScrolled] = React.useState(false);
1163
+ const prevMessageCountRef = React.useRef(messages.length);
1164
+ React.useImperativeHandle(ref, () => scrollContainerRef.current);
1165
+ const handleScroll = React.useCallback(() => {
887
1166
  const container = scrollContainerRef.current;
888
1167
  if (!container) return;
889
1168
  const { scrollTop, scrollHeight, clientHeight } = container;
890
1169
  const isAtBottom = scrollHeight - scrollTop - clientHeight < 100;
891
1170
  setIsUserScrolled(!isAtBottom);
892
1171
  }, []);
893
- React5.useEffect(() => {
1172
+ React.useEffect(() => {
894
1173
  const container = scrollContainerRef.current;
895
1174
  const bottom = bottomRef.current;
896
1175
  if (!container || !bottom) return;
@@ -906,7 +1185,7 @@ var MessageList = React5.forwardRef(
906
1185
  }
907
1186
  }
908
1187
  }, [messages, currentUser.id, autoScroll, isUserScrolled]);
909
- React5.useEffect(() => {
1188
+ React.useEffect(() => {
910
1189
  const bottom = bottomRef.current;
911
1190
  if (bottom && !isLoading) {
912
1191
  bottom.scrollIntoView();
@@ -959,7 +1238,7 @@ var MessageList = React5.forwardRef(
959
1238
  {
960
1239
  "data-slot": "message-list-content",
961
1240
  className: "flex flex-col gap-1 p-4",
962
- children: messageGroups.map((group) => /* @__PURE__ */ jsxs(React5.Fragment, { children: [
1241
+ children: messageGroups.map((group) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
963
1242
  groupByDate && group.label && /* @__PURE__ */ jsx(DateSeparator, { label: group.label }),
964
1243
  group.messages.map((message, index) => {
965
1244
  const prevMessage = group.messages[index - 1];
@@ -1098,7 +1377,7 @@ function formatLastSeen(date) {
1098
1377
  if (diffDays < 7) return `${diffDays}d ago`;
1099
1378
  return date.toLocaleDateString();
1100
1379
  }
1101
- var ConversationHeader = React5.forwardRef(
1380
+ var ConversationHeader = React.forwardRef(
1102
1381
  ({
1103
1382
  className,
1104
1383
  size,
@@ -1220,7 +1499,7 @@ var ConversationHeader = React5.forwardRef(
1220
1499
  }
1221
1500
  );
1222
1501
  ConversationHeader.displayName = "ConversationHeader";
1223
- var ConversationListItem = React5.forwardRef(({ className, conversation, isSelected, onSelect, ...props }, ref) => {
1502
+ var ConversationListItem = React.forwardRef(({ className, conversation, isSelected, onSelect, ...props }, ref) => {
1224
1503
  const participant = conversation.participants.find((p) => !p.isCurrentUser);
1225
1504
  const title = getConversationTitle(conversation, participant);
1226
1505
  const avatarUrl = conversation.avatarUrl || participant?.avatarUrl;
@@ -1428,7 +1707,7 @@ function ConversationListSkeleton({
1428
1707
  }
1429
1708
  ConversationListSkeleton.displayName = "ConversationListSkeleton";
1430
1709
  function LightboxModal({ attachment, onClose }) {
1431
- React5.useEffect(() => {
1710
+ React.useEffect(() => {
1432
1711
  const handleKeyDown = (event) => {
1433
1712
  if (event.key === "Escape") {
1434
1713
  onClose();
@@ -1525,7 +1804,7 @@ function LightboxModal({ attachment, onClose }) {
1525
1804
  );
1526
1805
  }
1527
1806
  LightboxModal.displayName = "LightboxModal";
1528
- var MessageThread = React5.forwardRef(
1807
+ var MessageThread = React.forwardRef(
1529
1808
  ({
1530
1809
  conversation,
1531
1810
  messages,
@@ -1556,8 +1835,23 @@ var MessageThread = React5.forwardRef(
1556
1835
  onError,
1557
1836
  className
1558
1837
  }, ref) => {
1559
- const [lightboxAttachment, setLightboxAttachment] = React5.useState(null);
1560
- const [replyTo, setReplyTo] = React5.useState(null);
1838
+ const [lightboxAttachment, setLightboxAttachment] = React.useState(null);
1839
+ const [replyTo, setReplyTo] = React.useState(null);
1840
+ const composerRef = React.useRef(null);
1841
+ React.useEffect(() => {
1842
+ notifyComposerMigrationOnce("MessageThread");
1843
+ }, []);
1844
+ const [draft, setDraft] = React.useState("");
1845
+ const draftEpochRef = React.useRef(0);
1846
+ const handleDraftChange = React.useCallback((value) => {
1847
+ draftEpochRef.current += 1;
1848
+ setDraft(value);
1849
+ }, []);
1850
+ const { stopTyping } = useTypingEmulation({
1851
+ value: draft,
1852
+ onTypingStart: eventHandlers.onTypingStart,
1853
+ onTypingStop: eventHandlers.onTypingStop
1854
+ });
1561
1855
  const participant = conversation?.type === "direct" ? conversation.participants.find((p) => p.id !== currentUser.id) : void 0;
1562
1856
  const handleAttachmentClick = (attachment, message) => {
1563
1857
  if (attachment.type === "image" || attachment.type === "video") {
@@ -1566,12 +1860,21 @@ var MessageThread = React5.forwardRef(
1566
1860
  eventHandlers.onAttachmentClick?.(attachment, message);
1567
1861
  };
1568
1862
  const handleSendMessage = async (newMessage) => {
1863
+ stopTyping();
1569
1864
  const messageWithReply = {
1570
1865
  ...newMessage,
1571
1866
  replyToId: replyTo?.id || newMessage.replyToId
1572
1867
  };
1573
1868
  setReplyTo(null);
1574
- await eventHandlers.onSendMessage?.(messageWithReply);
1869
+ const epoch = draftEpochRef.current;
1870
+ try {
1871
+ await Promise.resolve(eventHandlers.onSendMessage?.(messageWithReply));
1872
+ } catch (error) {
1873
+ if (draftEpochRef.current === epoch) {
1874
+ setDraft(newMessage.content);
1875
+ }
1876
+ throw error;
1877
+ }
1575
1878
  };
1576
1879
  return /* @__PURE__ */ jsxs(
1577
1880
  "div",
@@ -1597,9 +1900,11 @@ var MessageThread = React5.forwardRef(
1597
1900
  /* @__PURE__ */ jsx(
1598
1901
  DragDropZone,
1599
1902
  {
1600
- onFilesDropped: () => {
1601
- onError?.("Drop files on the composer to attach them");
1903
+ onFilesDropped: (files) => {
1904
+ composerRef.current?.addFiles(files);
1602
1905
  },
1906
+ maxFiles: maxAttachments,
1907
+ onError,
1603
1908
  disabled: !showAttachmentPicker,
1604
1909
  className: "flex-1 overflow-hidden",
1605
1910
  children: /* @__PURE__ */ jsx(
@@ -1625,23 +1930,38 @@ var MessageThread = React5.forwardRef(
1625
1930
  }
1626
1931
  ),
1627
1932
  /* @__PURE__ */ jsx(
1628
- MessageComposer,
1933
+ "div",
1629
1934
  {
1630
- onSend: handleSendMessage,
1631
- onTypingStart: eventHandlers.onTypingStart,
1632
- onTypingStop: eventHandlers.onTypingStop,
1633
- placeholder,
1634
- maxLength: maxMessageLength,
1635
- showCharacterCount,
1636
- isSending,
1637
- showAttachmentPicker,
1638
- showCameraButton,
1639
- acceptedFileTypes,
1640
- maxFileSize,
1641
- maxAttachments,
1642
- onError,
1643
- replyTo,
1644
- onCancelReply: () => setReplyTo(null)
1935
+ "data-slot": "message-thread-composer",
1936
+ className: "shrink-0 border-t border-neutral-200 p-3 dark:border-neutral-700",
1937
+ children: /* @__PURE__ */ jsx(
1938
+ ChatComposer,
1939
+ {
1940
+ ref: composerRef,
1941
+ value: draft,
1942
+ onValueChange: handleDraftChange,
1943
+ onSend: handleSendMessage,
1944
+ placeholder,
1945
+ maxLength: maxMessageLength,
1946
+ showCharacterCount,
1947
+ isSending,
1948
+ allowAttachments: showAttachmentPicker,
1949
+ acceptedFileTypes: acceptedFileTypes ?? DEFAULT_ACCEPTED_FILE_TYPES,
1950
+ maxFileSize: maxFileSize ?? DEFAULT_MAX_FILE_SIZE,
1951
+ maxAttachments,
1952
+ onError,
1953
+ replyTo,
1954
+ onCancelReply: () => setReplyTo(null),
1955
+ inputLabel: "Message",
1956
+ micSlot: showCameraButton ? /* @__PURE__ */ jsx(
1957
+ CameraButton,
1958
+ {
1959
+ onCapture: (file) => composerRef.current?.addFiles([file]),
1960
+ disabled: isSending
1961
+ }
1962
+ ) : void 0
1963
+ }
1964
+ )
1645
1965
  }
1646
1966
  ),
1647
1967
  /* @__PURE__ */ jsx(
@@ -1721,250 +2041,7 @@ function MessagingSplitView({
1721
2041
  );
1722
2042
  }
1723
2043
  MessagingSplitView.displayName = "MessagingSplitView";
1724
- function useMessages(options) {
1725
- const {
1726
- initialMessages = [],
1727
- currentUser,
1728
- onSend,
1729
- onRetry,
1730
- onLoadMore
1731
- } = 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(() => {
1736
- setMessages(initialMessages);
1737
- }, [initialMessages]);
1738
- const addMessage = React5.useCallback((message) => {
1739
- setMessages((prev) => {
1740
- if (prev.some((m) => m.id === message.id)) {
1741
- return prev;
1742
- }
1743
- return [...prev, message];
1744
- });
1745
- }, []);
1746
- const updateMessage = React5.useCallback(
1747
- (messageId, updates) => {
1748
- setMessages(
1749
- (prev) => prev.map((m) => m.id === messageId ? { ...m, ...updates } : m)
1750
- );
1751
- },
1752
- []
1753
- );
1754
- const removeMessage = React5.useCallback((messageId) => {
1755
- setMessages((prev) => prev.filter((m) => m.id !== messageId));
1756
- }, []);
1757
- const updateStatus = React5.useCallback(
1758
- (messageId, status) => {
1759
- updateMessage(messageId, { status });
1760
- },
1761
- [updateMessage]
1762
- );
1763
- const markAsRead = React5.useCallback(
1764
- (messageId) => {
1765
- updateStatus(messageId, "read");
1766
- },
1767
- [updateStatus]
1768
- );
1769
- const sendMessage = React5.useCallback(
1770
- async (newMessage) => {
1771
- const optimisticId = `optimistic-${Date.now()}`;
1772
- const optimisticMessage = {
1773
- id: optimisticId,
1774
- type: "text",
1775
- content: newMessage.content,
1776
- sender: currentUser,
1777
- timestamp: /* @__PURE__ */ new Date(),
1778
- status: "sending",
1779
- attachments: []
1780
- // Would handle attachment uploads here
1781
- };
1782
- addMessage(optimisticMessage);
1783
- setIsSending(true);
1784
- try {
1785
- if (onSend) {
1786
- const sentMessage = await onSend(newMessage);
1787
- setMessages(
1788
- (prev) => prev.map((m) => m.id === optimisticId ? sentMessage : m)
1789
- );
1790
- } else {
1791
- updateStatus(optimisticId, "sent");
1792
- }
1793
- } catch {
1794
- updateStatus(optimisticId, "failed");
1795
- } finally {
1796
- setIsSending(false);
1797
- }
1798
- },
1799
- [currentUser, onSend, addMessage, updateStatus]
1800
- );
1801
- const retryMessage = React5.useCallback(
1802
- async (messageId) => {
1803
- updateStatus(messageId, "sending");
1804
- try {
1805
- if (onRetry) {
1806
- await onRetry(messageId);
1807
- updateStatus(messageId, "sent");
1808
- }
1809
- } catch {
1810
- updateStatus(messageId, "failed");
1811
- }
1812
- },
1813
- [onRetry, updateStatus]
1814
- );
1815
- const loadMore = React5.useCallback(async () => {
1816
- if (isLoadingMore || !onLoadMore) return;
1817
- setIsLoadingMore(true);
1818
- try {
1819
- const olderMessages = await onLoadMore();
1820
- setMessages((prev) => [...olderMessages, ...prev]);
1821
- } finally {
1822
- setIsLoadingMore(false);
1823
- }
1824
- }, [isLoadingMore, onLoadMore]);
1825
- return {
1826
- messages,
1827
- addMessage,
1828
- updateMessage,
1829
- removeMessage,
1830
- sendMessage,
1831
- retryMessage,
1832
- loadMore,
1833
- isSending,
1834
- isLoadingMore,
1835
- markAsRead,
1836
- updateStatus
1837
- };
1838
- }
1839
- function useTypingIndicator(options = {}) {
1840
- const {
1841
- typingParticipants: initialParticipants = [],
1842
- debounceTime = 2e3,
1843
- onTypingStart,
1844
- onTypingStop
1845
- } = options;
1846
- const [participants, setParticipants] = React5.useState(initialParticipants);
1847
- const [isLocalTyping, setIsLocalTyping] = React5.useState(false);
1848
- const typingTimeoutRef = React5.useRef(null);
1849
- React5.useEffect(() => {
1850
- setParticipants(initialParticipants);
1851
- }, [initialParticipants]);
1852
- const startTyping = React5.useCallback(() => {
1853
- if (!isLocalTyping) {
1854
- setIsLocalTyping(true);
1855
- onTypingStart?.();
1856
- }
1857
- if (typingTimeoutRef.current) {
1858
- clearTimeout(typingTimeoutRef.current);
1859
- }
1860
- typingTimeoutRef.current = setTimeout(() => {
1861
- setIsLocalTyping(false);
1862
- onTypingStop?.();
1863
- }, debounceTime);
1864
- }, [isLocalTyping, debounceTime, onTypingStart, onTypingStop]);
1865
- const stopTyping = React5.useCallback(() => {
1866
- if (typingTimeoutRef.current) {
1867
- clearTimeout(typingTimeoutRef.current);
1868
- }
1869
- setIsLocalTyping(false);
1870
- onTypingStop?.();
1871
- }, [onTypingStop]);
1872
- React5.useEffect(() => {
1873
- return () => {
1874
- if (typingTimeoutRef.current) {
1875
- clearTimeout(typingTimeoutRef.current);
1876
- }
1877
- };
1878
- }, []);
1879
- const typingState = React5.useMemo(
1880
- () => ({
1881
- participants,
1882
- lastUpdated: /* @__PURE__ */ new Date()
1883
- }),
1884
- [participants]
1885
- );
1886
- return {
1887
- typingState,
1888
- startTyping,
1889
- stopTyping,
1890
- setTypingParticipants: setParticipants
1891
- };
1892
- }
1893
- function useMessageScroll(options) {
1894
- 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(() => {
1900
- const container = scrollContainerRef.current;
1901
- if (!container) return;
1902
- const handleScroll = () => {
1903
- const { scrollTop, scrollHeight, clientHeight } = container;
1904
- const isAtBottom = scrollHeight - scrollTop - clientHeight < threshold;
1905
- setIsScrolledUp(!isAtBottom);
1906
- };
1907
- container.addEventListener("scroll", handleScroll);
1908
- return () => container.removeEventListener("scroll", handleScroll);
1909
- }, [threshold]);
1910
- const scrollToBottom = React5.useCallback((smooth = true) => {
1911
- bottomRef.current?.scrollIntoView({
1912
- behavior: smooth ? "smooth" : "auto"
1913
- });
1914
- }, []);
1915
- React5.useEffect(() => {
1916
- const messageCountChanged = messages.length !== prevMessageCountRef.current;
1917
- prevMessageCountRef.current = messages.length;
1918
- if (!messageCountChanged) return;
1919
- const lastMessage = messages[messages.length - 1];
1920
- const isOutgoing = lastMessage?.sender.id === currentUserId;
1921
- if (isOutgoing || !isScrolledUp) {
1922
- scrollToBottom(true);
1923
- }
1924
- }, [messages, currentUserId, isScrolledUp, scrollToBottom]);
1925
- return {
1926
- scrollContainerRef,
1927
- bottomRef,
1928
- isScrolledUp,
1929
- scrollToBottom
1930
- };
1931
- }
1932
- function useReadReceipts(options) {
1933
- const { currentUserId, onMarkRead, threshold = 0.5 } = options;
1934
- const observerRef = React5.useRef(null);
1935
- const observedMessagesRef = React5.useRef(/* @__PURE__ */ new Set());
1936
- React5.useEffect(() => {
1937
- observerRef.current = new IntersectionObserver(
1938
- (entries) => {
1939
- entries.forEach((entry) => {
1940
- if (entry.isIntersecting) {
1941
- const messageId = entry.target.getAttribute("data-message-id");
1942
- if (messageId && !observedMessagesRef.current.has(messageId)) {
1943
- observedMessagesRef.current.add(messageId);
1944
- onMarkRead?.(messageId);
1945
- }
1946
- }
1947
- });
1948
- },
1949
- { threshold }
1950
- );
1951
- return () => {
1952
- observerRef.current?.disconnect();
1953
- };
1954
- }, [onMarkRead, threshold]);
1955
- const observeMessage = React5.useCallback(
1956
- (element, message) => {
1957
- if (!element || !observerRef.current) return;
1958
- if (message.sender.id !== currentUserId && message.status !== "read" && !observedMessagesRef.current.has(message.id)) {
1959
- element.setAttribute("data-message-id", message.id);
1960
- observerRef.current.observe(element);
1961
- }
1962
- },
1963
- [currentUserId]
1964
- );
1965
- return { observeMessage };
1966
- }
1967
2044
 
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
2045
+ 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, useTypingEmulation, useTypingIndicator };
2046
+ //# sourceMappingURL=chunk-VSVGMMNO.js.map
2047
+ //# sourceMappingURL=chunk-VSVGMMNO.js.map