@mieweb/ui 0.9.0-dev.5 → 0.9.0-dev.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 (37) hide show
  1. package/MIGRATION.md +112 -0
  2. package/dist/{chunk-QWJGEDHV.js → chunk-22CYDCXA.js} +13 -1183
  3. package/dist/chunk-22CYDCXA.js.map +1 -0
  4. package/dist/chunk-IIFQ6BSJ.js +1888 -0
  5. package/dist/chunk-IIFQ6BSJ.js.map +1 -0
  6. package/dist/{chunk-ZXRVVNT5.cjs → chunk-OFUKTVBN.cjs} +367 -798
  7. package/dist/chunk-OFUKTVBN.cjs.map +1 -0
  8. package/dist/chunk-OYQ7GYAA.cjs +1921 -0
  9. package/dist/chunk-OYQ7GYAA.cjs.map +1 -0
  10. package/dist/{chunk-PL6TFCYR.js → chunk-VSVGMMNO.js} +365 -793
  11. package/dist/chunk-VSVGMMNO.js.map +1 -0
  12. package/dist/{chunk-XJUIZA4G.cjs → chunk-WIAQQNFU.cjs} +12 -1184
  13. package/dist/chunk-WIAQQNFU.cjs.map +1 -0
  14. package/dist/components/SuperChat/index.cjs +11 -8
  15. package/dist/components/SuperChat/index.cjs.map +1 -1
  16. package/dist/components/SuperChat/index.js +5 -2
  17. package/dist/components/SuperChat/index.js.map +1 -1
  18. package/dist/components/SuperChat/plugins/index.cjs +10 -3
  19. package/dist/components/SuperChat/plugins/index.cjs.map +1 -1
  20. package/dist/components/SuperChat/plugins/index.js +9 -2
  21. package/dist/components/SuperChat/plugins/index.js.map +1 -1
  22. package/dist/index.cjs +91 -132
  23. package/dist/index.cjs.map +1 -1
  24. package/dist/index.d.cts +38 -98
  25. package/dist/index.d.ts +38 -98
  26. package/dist/index.js +20 -48
  27. package/dist/index.js.map +1 -1
  28. package/dist/styles.css +1 -1
  29. package/package.json +2 -1
  30. package/dist/chunk-H3GRO43Z.cjs +0 -735
  31. package/dist/chunk-H3GRO43Z.cjs.map +0 -1
  32. package/dist/chunk-MD3ZY4UO.js +0 -705
  33. package/dist/chunk-MD3ZY4UO.js.map +0 -1
  34. package/dist/chunk-PL6TFCYR.js.map +0 -1
  35. package/dist/chunk-QWJGEDHV.js.map +0 -1
  36. package/dist/chunk-XJUIZA4G.cjs.map +0 -1
  37. package/dist/chunk-ZXRVVNT5.cjs.map +0 -1
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
- var chunkH3GRO43Z_cjs = require('./chunk-H3GRO43Z.cjs');
3
+ var chunkOYQ7GYAA_cjs = require('./chunk-OYQ7GYAA.cjs');
4
4
  var chunkS5RVSXDV_cjs = require('./chunk-S5RVSXDV.cjs');
5
- var React6 = require('react');
5
+ var React = require('react');
6
6
  var classVarianceAuthority = require('class-variance-authority');
7
7
  var jsxRuntime = require('react/jsx-runtime');
8
8
 
@@ -24,8 +24,287 @@ function _interopNamespace(e) {
24
24
  return Object.freeze(n);
25
25
  }
26
26
 
27
- var React6__namespace = /*#__PURE__*/_interopNamespace(React6);
27
+ var React__namespace = /*#__PURE__*/_interopNamespace(React);
28
28
 
29
+ function useMessages(options) {
30
+ const {
31
+ initialMessages = [],
32
+ currentUser,
33
+ onSend,
34
+ onRetry,
35
+ onLoadMore
36
+ } = options;
37
+ const [messages, setMessages] = React__namespace.useState(initialMessages);
38
+ const [isSending, setIsSending] = React__namespace.useState(false);
39
+ const [isLoadingMore, setIsLoadingMore] = React__namespace.useState(false);
40
+ React__namespace.useEffect(() => {
41
+ setMessages(initialMessages);
42
+ }, [initialMessages]);
43
+ const addMessage = React__namespace.useCallback((message) => {
44
+ setMessages((prev) => {
45
+ if (prev.some((m) => m.id === message.id)) {
46
+ return prev;
47
+ }
48
+ return [...prev, message];
49
+ });
50
+ }, []);
51
+ const updateMessage = React__namespace.useCallback(
52
+ (messageId, updates) => {
53
+ setMessages(
54
+ (prev) => prev.map((m) => m.id === messageId ? { ...m, ...updates } : m)
55
+ );
56
+ },
57
+ []
58
+ );
59
+ const removeMessage = React__namespace.useCallback((messageId) => {
60
+ setMessages((prev) => prev.filter((m) => m.id !== messageId));
61
+ }, []);
62
+ const updateStatus = React__namespace.useCallback(
63
+ (messageId, status) => {
64
+ updateMessage(messageId, { status });
65
+ },
66
+ [updateMessage]
67
+ );
68
+ const markAsRead = React__namespace.useCallback(
69
+ (messageId) => {
70
+ updateStatus(messageId, "read");
71
+ },
72
+ [updateStatus]
73
+ );
74
+ const sendMessage = React__namespace.useCallback(
75
+ async (newMessage) => {
76
+ const optimisticId = `optimistic-${Date.now()}`;
77
+ const optimisticMessage = {
78
+ id: optimisticId,
79
+ type: "text",
80
+ content: newMessage.content,
81
+ sender: currentUser,
82
+ timestamp: /* @__PURE__ */ new Date(),
83
+ status: "sending",
84
+ attachments: []
85
+ // Would handle attachment uploads here
86
+ };
87
+ addMessage(optimisticMessage);
88
+ setIsSending(true);
89
+ try {
90
+ if (onSend) {
91
+ const sentMessage = await onSend(newMessage);
92
+ setMessages(
93
+ (prev) => prev.map((m) => m.id === optimisticId ? sentMessage : m)
94
+ );
95
+ } else {
96
+ updateStatus(optimisticId, "sent");
97
+ }
98
+ } catch {
99
+ updateStatus(optimisticId, "failed");
100
+ } finally {
101
+ setIsSending(false);
102
+ }
103
+ },
104
+ [currentUser, onSend, addMessage, updateStatus]
105
+ );
106
+ const retryMessage = React__namespace.useCallback(
107
+ async (messageId) => {
108
+ updateStatus(messageId, "sending");
109
+ try {
110
+ if (onRetry) {
111
+ await onRetry(messageId);
112
+ updateStatus(messageId, "sent");
113
+ }
114
+ } catch {
115
+ updateStatus(messageId, "failed");
116
+ }
117
+ },
118
+ [onRetry, updateStatus]
119
+ );
120
+ const loadMore = React__namespace.useCallback(async () => {
121
+ if (isLoadingMore || !onLoadMore) return;
122
+ setIsLoadingMore(true);
123
+ try {
124
+ const olderMessages = await onLoadMore();
125
+ setMessages((prev) => [...olderMessages, ...prev]);
126
+ } finally {
127
+ setIsLoadingMore(false);
128
+ }
129
+ }, [isLoadingMore, onLoadMore]);
130
+ return {
131
+ messages,
132
+ addMessage,
133
+ updateMessage,
134
+ removeMessage,
135
+ sendMessage,
136
+ retryMessage,
137
+ loadMore,
138
+ isSending,
139
+ isLoadingMore,
140
+ markAsRead,
141
+ updateStatus
142
+ };
143
+ }
144
+ function useTypingIndicator(options = {}) {
145
+ const {
146
+ typingParticipants: initialParticipants = [],
147
+ debounceTime = 2e3,
148
+ onTypingStart,
149
+ onTypingStop
150
+ } = options;
151
+ const [participants, setParticipants] = React__namespace.useState(initialParticipants);
152
+ const [isLocalTyping, setIsLocalTyping] = React__namespace.useState(false);
153
+ const typingTimeoutRef = React__namespace.useRef(null);
154
+ React__namespace.useEffect(() => {
155
+ setParticipants(initialParticipants);
156
+ }, [initialParticipants]);
157
+ const startTyping = React__namespace.useCallback(() => {
158
+ if (!isLocalTyping) {
159
+ setIsLocalTyping(true);
160
+ onTypingStart?.();
161
+ }
162
+ if (typingTimeoutRef.current) {
163
+ clearTimeout(typingTimeoutRef.current);
164
+ }
165
+ typingTimeoutRef.current = setTimeout(() => {
166
+ setIsLocalTyping(false);
167
+ onTypingStop?.();
168
+ }, debounceTime);
169
+ }, [isLocalTyping, debounceTime, onTypingStart, onTypingStop]);
170
+ const stopTyping = React__namespace.useCallback(() => {
171
+ if (typingTimeoutRef.current) {
172
+ clearTimeout(typingTimeoutRef.current);
173
+ }
174
+ setIsLocalTyping(false);
175
+ onTypingStop?.();
176
+ }, [onTypingStop]);
177
+ React__namespace.useEffect(() => {
178
+ return () => {
179
+ if (typingTimeoutRef.current) {
180
+ clearTimeout(typingTimeoutRef.current);
181
+ }
182
+ };
183
+ }, []);
184
+ const typingState = React__namespace.useMemo(
185
+ () => ({
186
+ participants,
187
+ lastUpdated: /* @__PURE__ */ new Date()
188
+ }),
189
+ [participants]
190
+ );
191
+ return {
192
+ typingState,
193
+ startTyping,
194
+ stopTyping,
195
+ setTypingParticipants: setParticipants
196
+ };
197
+ }
198
+ function useMessageScroll(options) {
199
+ const { messages, currentUserId, threshold = 100 } = options;
200
+ const scrollContainerRef = React__namespace.useRef(null);
201
+ const bottomRef = React__namespace.useRef(null);
202
+ const [isScrolledUp, setIsScrolledUp] = React__namespace.useState(false);
203
+ const prevMessageCountRef = React__namespace.useRef(messages.length);
204
+ React__namespace.useEffect(() => {
205
+ const container = scrollContainerRef.current;
206
+ if (!container) return;
207
+ const handleScroll = () => {
208
+ const { scrollTop, scrollHeight, clientHeight } = container;
209
+ const isAtBottom = scrollHeight - scrollTop - clientHeight < threshold;
210
+ setIsScrolledUp(!isAtBottom);
211
+ };
212
+ container.addEventListener("scroll", handleScroll);
213
+ return () => container.removeEventListener("scroll", handleScroll);
214
+ }, [threshold]);
215
+ const scrollToBottom = React__namespace.useCallback((smooth = true) => {
216
+ bottomRef.current?.scrollIntoView({
217
+ behavior: smooth ? "smooth" : "auto"
218
+ });
219
+ }, []);
220
+ React__namespace.useEffect(() => {
221
+ const messageCountChanged = messages.length !== prevMessageCountRef.current;
222
+ prevMessageCountRef.current = messages.length;
223
+ if (!messageCountChanged) return;
224
+ const lastMessage = messages[messages.length - 1];
225
+ const isOutgoing = lastMessage?.sender.id === currentUserId;
226
+ if (isOutgoing || !isScrolledUp) {
227
+ scrollToBottom(true);
228
+ }
229
+ }, [messages, currentUserId, isScrolledUp, scrollToBottom]);
230
+ return {
231
+ scrollContainerRef,
232
+ bottomRef,
233
+ isScrolledUp,
234
+ scrollToBottom
235
+ };
236
+ }
237
+ function useReadReceipts(options) {
238
+ const { currentUserId, onMarkRead, threshold = 0.5 } = options;
239
+ const observerRef = React__namespace.useRef(null);
240
+ const observedMessagesRef = React__namespace.useRef(/* @__PURE__ */ new Set());
241
+ React__namespace.useEffect(() => {
242
+ observerRef.current = new IntersectionObserver(
243
+ (entries) => {
244
+ entries.forEach((entry) => {
245
+ if (entry.isIntersecting) {
246
+ const messageId = entry.target.getAttribute("data-message-id");
247
+ if (messageId && !observedMessagesRef.current.has(messageId)) {
248
+ observedMessagesRef.current.add(messageId);
249
+ onMarkRead?.(messageId);
250
+ }
251
+ }
252
+ });
253
+ },
254
+ { threshold }
255
+ );
256
+ return () => {
257
+ observerRef.current?.disconnect();
258
+ };
259
+ }, [onMarkRead, threshold]);
260
+ const observeMessage = React__namespace.useCallback(
261
+ (element, message) => {
262
+ if (!element || !observerRef.current) return;
263
+ if (message.sender.id !== currentUserId && message.status !== "read" && !observedMessagesRef.current.has(message.id)) {
264
+ element.setAttribute("data-message-id", message.id);
265
+ observerRef.current.observe(element);
266
+ }
267
+ },
268
+ [currentUserId]
269
+ );
270
+ return { observeMessage };
271
+ }
272
+ function useTypingEmulation({
273
+ value,
274
+ onTypingStart,
275
+ onTypingStop
276
+ }) {
277
+ const [isTyping, setIsTyping] = React__namespace.useState(false);
278
+ const typingTimeoutRef = React__namespace.useRef(
279
+ null
280
+ );
281
+ React__namespace.useEffect(() => {
282
+ if (!onTypingStart && !onTypingStop) return;
283
+ if (value.length > 0 && !isTyping) {
284
+ setIsTyping(true);
285
+ onTypingStart?.();
286
+ }
287
+ if (typingTimeoutRef.current) {
288
+ clearTimeout(typingTimeoutRef.current);
289
+ }
290
+ typingTimeoutRef.current = setTimeout(() => {
291
+ if (isTyping) {
292
+ setIsTyping(false);
293
+ onTypingStop?.();
294
+ }
295
+ }, 2e3);
296
+ return () => {
297
+ if (typingTimeoutRef.current) {
298
+ clearTimeout(typingTimeoutRef.current);
299
+ }
300
+ };
301
+ }, [value, isTyping, onTypingStart, onTypingStop]);
302
+ const stopTyping = React__namespace.useCallback(() => {
303
+ setIsTyping(false);
304
+ onTypingStop?.();
305
+ }, [onTypingStop]);
306
+ return { stopTyping };
307
+ }
29
308
  var statusIconVariants = classVarianceAuthority.cva(
30
309
  "inline-flex items-center gap-0.5 text-current",
31
310
  {
@@ -406,7 +685,7 @@ function defaultFormatTimestamp(timestamp) {
406
685
  minute: "2-digit"
407
686
  });
408
687
  }
409
- var MessageBubble = React6__namespace.forwardRef(
688
+ var MessageBubble = React__namespace.forwardRef(
410
689
  ({
411
690
  className,
412
691
  message,
@@ -881,7 +1160,7 @@ function LoadMoreButton({
881
1160
  );
882
1161
  }
883
1162
  LoadMoreButton.displayName = "LoadMoreButton";
884
- var MessageList = React6__namespace.forwardRef(
1163
+ var MessageList = React__namespace.forwardRef(
885
1164
  ({
886
1165
  messages,
887
1166
  currentUser,
@@ -900,19 +1179,19 @@ var MessageList = React6__namespace.forwardRef(
900
1179
  className,
901
1180
  autoScroll = "onNewMessage"
902
1181
  }, ref) => {
903
- const scrollContainerRef = React6__namespace.useRef(null);
904
- const bottomRef = React6__namespace.useRef(null);
905
- const [isUserScrolled, setIsUserScrolled] = React6__namespace.useState(false);
906
- const prevMessageCountRef = React6__namespace.useRef(messages.length);
907
- React6__namespace.useImperativeHandle(ref, () => scrollContainerRef.current);
908
- const handleScroll = React6__namespace.useCallback(() => {
1182
+ const scrollContainerRef = React__namespace.useRef(null);
1183
+ const bottomRef = React__namespace.useRef(null);
1184
+ const [isUserScrolled, setIsUserScrolled] = React__namespace.useState(false);
1185
+ const prevMessageCountRef = React__namespace.useRef(messages.length);
1186
+ React__namespace.useImperativeHandle(ref, () => scrollContainerRef.current);
1187
+ const handleScroll = React__namespace.useCallback(() => {
909
1188
  const container = scrollContainerRef.current;
910
1189
  if (!container) return;
911
1190
  const { scrollTop, scrollHeight, clientHeight } = container;
912
1191
  const isAtBottom = scrollHeight - scrollTop - clientHeight < 100;
913
1192
  setIsUserScrolled(!isAtBottom);
914
1193
  }, []);
915
- React6__namespace.useEffect(() => {
1194
+ React__namespace.useEffect(() => {
916
1195
  const container = scrollContainerRef.current;
917
1196
  const bottom = bottomRef.current;
918
1197
  if (!container || !bottom) return;
@@ -928,7 +1207,7 @@ var MessageList = React6__namespace.forwardRef(
928
1207
  }
929
1208
  }
930
1209
  }, [messages, currentUser.id, autoScroll, isUserScrolled]);
931
- React6__namespace.useEffect(() => {
1210
+ React__namespace.useEffect(() => {
932
1211
  const bottom = bottomRef.current;
933
1212
  if (bottom && !isLoading) {
934
1213
  bottom.scrollIntoView();
@@ -981,7 +1260,7 @@ var MessageList = React6__namespace.forwardRef(
981
1260
  {
982
1261
  "data-slot": "message-list-content",
983
1262
  className: "flex flex-col gap-1 p-4",
984
- children: messageGroups.map((group) => /* @__PURE__ */ jsxRuntime.jsxs(React6__namespace.Fragment, { children: [
1263
+ children: messageGroups.map((group) => /* @__PURE__ */ jsxRuntime.jsxs(React__namespace.Fragment, { children: [
985
1264
  groupByDate && group.label && /* @__PURE__ */ jsxRuntime.jsx(DateSeparator, { label: group.label }),
986
1265
  group.messages.map((message, index) => {
987
1266
  const prevMessage = group.messages[index - 1];
@@ -1061,516 +1340,11 @@ var MessageList = React6__namespace.forwardRef(
1061
1340
  }
1062
1341
  );
1063
1342
  MessageList.displayName = "MessageList";
1064
- function isFileAccepted(file, accepted) {
1065
- if (!accepted || accepted.length === 0) return true;
1066
- const mime = file.type;
1067
- const name = file.name.toLowerCase();
1068
- return accepted.some((token) => {
1069
- if (token.startsWith(".")) return name.endsWith(token.toLowerCase());
1070
- if (token.endsWith("/*")) return mime.startsWith(token.slice(0, -1));
1071
- if (token.includes("/")) return mime === token;
1072
- return false;
1073
- });
1074
- }
1075
- function CharacterCounter({
1076
- current,
1077
- max,
1078
- showWarningAt = 0.9,
1079
- className
1080
- }) {
1081
- const percentage = current / max;
1082
- const isWarning = percentage >= showWarningAt && percentage < 1;
1083
- const isOver = current > max;
1084
- return /* @__PURE__ */ jsxRuntime.jsxs(
1085
- "span",
1086
- {
1087
- className: chunkS5RVSXDV_cjs.cn(
1088
- "text-xs tabular-nums",
1089
- 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",
1090
- className
1091
- ),
1092
- "aria-live": "polite",
1093
- "aria-label": `${current} of ${max} characters`,
1094
- children: [
1095
- current,
1096
- "/",
1097
- max
1098
- ]
1099
- }
1100
- );
1101
- }
1102
- CharacterCounter.displayName = "CharacterCounter";
1103
- var sendButtonVariants = classVarianceAuthority.cva(
1343
+ var headerVariants = classVarianceAuthority.cva(
1104
1344
  [
1105
- "inline-flex items-center justify-center",
1106
- "rounded-full p-3 self-start",
1107
- "transition-all duration-200",
1108
- "focus:outline-none focus:ring-2 focus:ring-primary-500",
1109
- "disabled:opacity-50 disabled:cursor-not-allowed"
1110
- ],
1111
- {
1112
- variants: {
1113
- variant: {
1114
- primary: [
1115
- "bg-primary-800 text-white",
1116
- "hover:bg-primary-900",
1117
- "active:scale-95"
1118
- ],
1119
- subtle: [
1120
- "bg-transparent text-primary-800",
1121
- "hover:bg-primary-50 dark:hover:bg-primary-900/20"
1122
- ]
1123
- },
1124
- canSend: {
1125
- true: "",
1126
- false: "opacity-50 cursor-not-allowed"
1127
- }
1128
- },
1129
- defaultVariants: {
1130
- variant: "primary",
1131
- canSend: false
1132
- }
1133
- }
1134
- );
1135
- var SendButton = React6__namespace.forwardRef(
1136
- ({ className, variant, canSend, isLoading, disabled, ...props }, ref) => {
1137
- return /* @__PURE__ */ jsxRuntime.jsx(
1138
- "button",
1139
- {
1140
- ref,
1141
- type: "submit",
1142
- disabled: disabled || !canSend || isLoading,
1143
- "data-slot": "composer-send-button",
1144
- className: chunkS5RVSXDV_cjs.cn(sendButtonVariants({ variant, canSend }), className),
1145
- "aria-label": isLoading ? "Sending message" : "Send message",
1146
- ...props,
1147
- children: isLoading ? /* @__PURE__ */ jsxRuntime.jsxs(
1148
- "svg",
1149
- {
1150
- "aria-hidden": "true",
1151
- className: "h-5 w-5 animate-spin",
1152
- fill: "none",
1153
- viewBox: "0 0 24 24",
1154
- children: [
1155
- /* @__PURE__ */ jsxRuntime.jsx(
1156
- "circle",
1157
- {
1158
- className: "opacity-25",
1159
- cx: "12",
1160
- cy: "12",
1161
- r: "10",
1162
- stroke: "currentColor",
1163
- strokeWidth: "4"
1164
- }
1165
- ),
1166
- /* @__PURE__ */ jsxRuntime.jsx(
1167
- "path",
1168
- {
1169
- className: "opacity-75",
1170
- fill: "currentColor",
1171
- d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
1172
- }
1173
- )
1174
- ]
1175
- }
1176
- ) : /* @__PURE__ */ jsxRuntime.jsx(
1177
- "svg",
1178
- {
1179
- "aria-hidden": "true",
1180
- className: "h-5 w-5",
1181
- fill: "none",
1182
- viewBox: "0 0 24 24",
1183
- stroke: "currentColor",
1184
- children: /* @__PURE__ */ jsxRuntime.jsx(
1185
- "path",
1186
- {
1187
- strokeLinecap: "round",
1188
- strokeLinejoin: "round",
1189
- strokeWidth: 2,
1190
- d: "M12 19l9 2-9-18-9 18 9-2zm0 0v-8"
1191
- }
1192
- )
1193
- }
1194
- )
1195
- }
1196
- );
1197
- }
1198
- );
1199
- SendButton.displayName = "SendButton";
1200
- var MessageComposer = React6__namespace.forwardRef(
1201
- ({
1202
- onSend,
1203
- onTypingStart,
1204
- onTypingStop,
1205
- value: controlledValue,
1206
- onValueChange,
1207
- placeholder = "Type a message...",
1208
- maxLength = 1600,
1209
- showCharacterCount = false,
1210
- disabled = false,
1211
- isSending = false,
1212
- showAttachmentPicker = true,
1213
- showCameraButton = false,
1214
- acceptedFileTypes = ["image/*", "video/*", ".pdf", ".doc", ".docx"],
1215
- maxFileSize = 25 * 1024 * 1024,
1216
- maxAttachments = 10,
1217
- onError,
1218
- autoFocus = false,
1219
- replyTo = null,
1220
- onCancelReply,
1221
- variant = "default",
1222
- inputTrailing,
1223
- mentionOptions,
1224
- className
1225
- }, ref) => {
1226
- const textareaRef = React6__namespace.useRef(null);
1227
- const [internalContent, setInternalContent] = React6__namespace.useState("");
1228
- const isControlled = controlledValue !== void 0;
1229
- const content = isControlled ? controlledValue : internalContent;
1230
- const setContent = React6__namespace.useCallback(
1231
- (val) => {
1232
- if (isControlled) {
1233
- const newVal = typeof val === "function" ? val(controlledValue) : val;
1234
- onValueChange?.(newVal);
1235
- } else {
1236
- setInternalContent(val);
1237
- }
1238
- },
1239
- [isControlled, controlledValue, onValueChange]
1240
- );
1241
- const [attachments, setAttachments] = React6__namespace.useState(
1242
- []
1243
- );
1244
- const [isTyping, setIsTyping] = React6__namespace.useState(false);
1245
- const typingTimeoutRef = React6__namespace.useRef(null);
1246
- React6__namespace.useImperativeHandle(ref, () => textareaRef.current);
1247
- React6__namespace.useEffect(() => {
1248
- const textarea = textareaRef.current;
1249
- if (textarea) {
1250
- textarea.style.height = "auto";
1251
- textarea.style.height = `${Math.min(textarea.scrollHeight, 150)}px`;
1252
- }
1253
- }, [content]);
1254
- React6__namespace.useEffect(() => {
1255
- if (content.length > 0 && !isTyping) {
1256
- setIsTyping(true);
1257
- onTypingStart?.();
1258
- }
1259
- if (typingTimeoutRef.current) {
1260
- clearTimeout(typingTimeoutRef.current);
1261
- }
1262
- typingTimeoutRef.current = setTimeout(() => {
1263
- if (isTyping) {
1264
- setIsTyping(false);
1265
- onTypingStop?.();
1266
- }
1267
- }, 2e3);
1268
- return () => {
1269
- if (typingTimeoutRef.current) {
1270
- clearTimeout(typingTimeoutRef.current);
1271
- }
1272
- };
1273
- }, [content, isTyping, onTypingStart, onTypingStop]);
1274
- React6__namespace.useEffect(() => {
1275
- if (autoFocus) {
1276
- textareaRef.current?.focus();
1277
- }
1278
- }, [autoFocus]);
1279
- const replyToId = replyTo?.id;
1280
- React6__namespace.useEffect(() => {
1281
- if (replyToId !== void 0) {
1282
- textareaRef.current?.focus();
1283
- }
1284
- }, [replyToId]);
1285
- const mention = chunkH3GRO43Z_cjs.useMentionAutocomplete({
1286
- options: mentionOptions,
1287
- value: content,
1288
- setValue: setContent,
1289
- textareaRef
1290
- });
1291
- const canSend = (content.trim().length > 0 || attachments.length > 0) && content.length <= maxLength && !disabled && !isSending;
1292
- const handleSubmit = async (event) => {
1293
- event.preventDefault();
1294
- if (!canSend) return;
1295
- const message = {
1296
- content: content.trim(),
1297
- attachments: attachments.map((a) => a.file),
1298
- replyToId: replyTo?.id
1299
- };
1300
- setContent("");
1301
- setAttachments([]);
1302
- setIsTyping(false);
1303
- onTypingStop?.();
1304
- try {
1305
- await onSend(message);
1306
- } catch {
1307
- setContent(message.content);
1308
- onError?.("Failed to send message");
1309
- }
1310
- };
1311
- const handleKeyDown = (event) => {
1312
- if (mention.handleKeyDown(event)) return;
1313
- if (event.key === "Enter" && !event.shiftKey) {
1314
- event.preventDefault();
1315
- if (canSend) {
1316
- handleSubmit(event);
1317
- }
1318
- }
1319
- };
1320
- const handleFilesSelected = (files) => {
1321
- const remainingSlots = maxAttachments - attachments.length;
1322
- const filesToAdd = files.slice(0, remainingSlots);
1323
- if (files.length > remainingSlots) {
1324
- onError?.(`Maximum ${maxAttachments} attachments allowed`);
1325
- }
1326
- const newAttachments = filesToAdd.map((file) => {
1327
- const type = chunkH3GRO43Z_cjs.getFileType(file.type);
1328
- let previewUrl;
1329
- if (type === "image" || type === "video") {
1330
- previewUrl = URL.createObjectURL(file);
1331
- }
1332
- return {
1333
- id: chunkH3GRO43Z_cjs.generateAttachmentId(),
1334
- file,
1335
- previewUrl,
1336
- type,
1337
- state: "pending"
1338
- };
1339
- });
1340
- setAttachments((prev) => [...prev, ...newAttachments]);
1341
- };
1342
- const handlePaste = (event) => {
1343
- if (!showAttachmentPicker || disabled) return;
1344
- 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));
1345
- if (pasted.length === 0) return;
1346
- event.preventDefault();
1347
- handleFilesSelected(pasted);
1348
- };
1349
- const handleRemoveAttachment = (attachmentId) => {
1350
- setAttachments((prev) => {
1351
- const attachment = prev.find((a) => a.id === attachmentId);
1352
- if (attachment?.previewUrl) {
1353
- URL.revokeObjectURL(attachment.previewUrl);
1354
- }
1355
- return prev.filter((a) => a.id !== attachmentId);
1356
- });
1357
- };
1358
- React6__namespace.useEffect(() => {
1359
- const currentAttachments = attachments;
1360
- return () => {
1361
- currentAttachments.forEach((a) => {
1362
- if (a.previewUrl) {
1363
- URL.revokeObjectURL(a.previewUrl);
1364
- }
1365
- });
1366
- };
1367
- }, [attachments]);
1368
- return /* @__PURE__ */ jsxRuntime.jsx(
1369
- chunkH3GRO43Z_cjs.DragDropZone,
1370
- {
1371
- onFilesDropped: handleFilesSelected,
1372
- acceptedTypes: acceptedFileTypes,
1373
- maxFileSize,
1374
- maxFiles: maxAttachments - attachments.length,
1375
- disabled: disabled || attachments.length >= maxAttachments,
1376
- onError,
1377
- className: chunkS5RVSXDV_cjs.cn("w-full", className),
1378
- children: /* @__PURE__ */ jsxRuntime.jsxs(
1379
- "form",
1380
- {
1381
- onSubmit: handleSubmit,
1382
- "data-slot": "message-composer",
1383
- className: "w-full",
1384
- children: [
1385
- replyTo && /* @__PURE__ */ jsxRuntime.jsxs(
1386
- "div",
1387
- {
1388
- "data-slot": "composer-reply-preview",
1389
- className: chunkS5RVSXDV_cjs.cn(
1390
- "flex items-center gap-2 px-4 py-2",
1391
- "bg-neutral-50 dark:bg-neutral-800/50",
1392
- "border-primary-500 border-s-4"
1393
- ),
1394
- children: [
1395
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
1396
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-primary-800 dark:text-primary-400 text-xs font-medium", children: [
1397
- "Replying to ",
1398
- replyTo.senderName
1399
- ] }),
1400
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-sm text-neutral-600 dark:text-neutral-300", children: replyTo.content })
1401
- ] }),
1402
- /* @__PURE__ */ jsxRuntime.jsx(
1403
- "button",
1404
- {
1405
- type: "button",
1406
- onClick: onCancelReply,
1407
- className: chunkS5RVSXDV_cjs.cn(
1408
- "shrink-0 rounded p-1",
1409
- "text-neutral-500 hover:text-neutral-700",
1410
- "dark:text-neutral-400 dark:hover:text-neutral-200",
1411
- "focus:ring-primary-500 focus:ring-2 focus:outline-none"
1412
- ),
1413
- "aria-label": "Cancel reply",
1414
- children: /* @__PURE__ */ jsxRuntime.jsx(
1415
- "svg",
1416
- {
1417
- "aria-hidden": "true",
1418
- className: "h-4 w-4",
1419
- fill: "none",
1420
- viewBox: "0 0 24 24",
1421
- stroke: "currentColor",
1422
- children: /* @__PURE__ */ jsxRuntime.jsx(
1423
- "path",
1424
- {
1425
- strokeLinecap: "round",
1426
- strokeLinejoin: "round",
1427
- strokeWidth: 2,
1428
- d: "M6 18L18 6M6 6l12 12"
1429
- }
1430
- )
1431
- }
1432
- )
1433
- }
1434
- )
1435
- ]
1436
- }
1437
- ),
1438
- attachments.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
1439
- "div",
1440
- {
1441
- "data-slot": "composer-attachments",
1442
- className: chunkS5RVSXDV_cjs.cn(
1443
- "flex flex-wrap gap-2 p-3",
1444
- "border-t border-neutral-200 dark:border-neutral-700"
1445
- ),
1446
- children: attachments.map((attachment) => /* @__PURE__ */ jsxRuntime.jsx(
1447
- chunkH3GRO43Z_cjs.AttachmentPreviewItem,
1448
- {
1449
- attachment,
1450
- onRemove: () => handleRemoveAttachment(attachment.id)
1451
- },
1452
- attachment.id
1453
- ))
1454
- }
1455
- ),
1456
- /* @__PURE__ */ jsxRuntime.jsxs(
1457
- "div",
1458
- {
1459
- "data-slot": "composer-input-area",
1460
- className: chunkS5RVSXDV_cjs.cn(
1461
- "flex items-center gap-2 p-3",
1462
- "bg-white dark:bg-neutral-900",
1463
- variant === "default" && "border-t border-neutral-200 dark:border-neutral-700"
1464
- ),
1465
- children: [
1466
- showAttachmentPicker && /* @__PURE__ */ jsxRuntime.jsx(
1467
- chunkH3GRO43Z_cjs.AttachmentPicker,
1468
- {
1469
- onFilesSelected: handleFilesSelected,
1470
- acceptedTypes: acceptedFileTypes,
1471
- maxFileSize,
1472
- maxFiles: maxAttachments - attachments.length,
1473
- disabled: disabled || attachments.length >= maxAttachments,
1474
- onError
1475
- }
1476
- ),
1477
- showCameraButton && /* @__PURE__ */ jsxRuntime.jsx(
1478
- chunkH3GRO43Z_cjs.CameraButton,
1479
- {
1480
- onCapture: (file) => handleFilesSelected([file]),
1481
- disabled: disabled || attachments.length >= maxAttachments
1482
- }
1483
- ),
1484
- /* @__PURE__ */ jsxRuntime.jsxs(
1485
- "div",
1486
- {
1487
- "data-slot": "composer-input-wrapper",
1488
- className: "relative flex-1",
1489
- ref: mention.anchorRef,
1490
- children: [
1491
- /* @__PURE__ */ jsxRuntime.jsx(chunkH3GRO43Z_cjs.MentionMenu, { mention }),
1492
- /* @__PURE__ */ jsxRuntime.jsx(
1493
- "textarea",
1494
- {
1495
- ref: textareaRef,
1496
- "data-slot": "composer-input",
1497
- value: content,
1498
- onChange: (e) => {
1499
- setContent(e.target.value);
1500
- mention.sync(
1501
- e.target.value,
1502
- e.target.selectionStart ?? e.target.value.length
1503
- );
1504
- },
1505
- onClick: (e) => {
1506
- const el = e.currentTarget;
1507
- mention.sync(el.value, el.selectionStart ?? el.value.length);
1508
- },
1509
- onKeyDown: handleKeyDown,
1510
- onPaste: handlePaste,
1511
- placeholder,
1512
- disabled: disabled || isSending,
1513
- rows: 1,
1514
- className: chunkS5RVSXDV_cjs.cn(
1515
- "w-full resize-none rounded-2xl py-2.5",
1516
- inputTrailing ? "ps-4 pe-10" : "px-4",
1517
- "bg-neutral-100 dark:bg-neutral-800",
1518
- "text-neutral-900 dark:text-neutral-100",
1519
- "placeholder:text-neutral-400 dark:placeholder:text-neutral-500",
1520
- "focus:ring-primary-500 focus:ring-2 focus:outline-none",
1521
- "disabled:cursor-not-allowed disabled:opacity-50",
1522
- "transition-colors",
1523
- "max-h-[150px]"
1524
- ),
1525
- "aria-label": "Message",
1526
- "aria-describedby": showCharacterCount ? "char-count" : void 0,
1527
- ...mention.inputProps
1528
- }
1529
- ),
1530
- inputTrailing && /* @__PURE__ */ jsxRuntime.jsx(
1531
- "div",
1532
- {
1533
- "data-slot": "composer-input-trailing",
1534
- className: "pointer-events-none absolute end-1 top-0 flex h-[44px] items-center [&>*]:pointer-events-auto",
1535
- children: inputTrailing
1536
- }
1537
- ),
1538
- showCharacterCount && /* @__PURE__ */ jsxRuntime.jsx(
1539
- "div",
1540
- {
1541
- "data-slot": "composer-char-count",
1542
- id: "char-count",
1543
- className: "absolute end-3 bottom-1.5",
1544
- children: /* @__PURE__ */ jsxRuntime.jsx(CharacterCounter, { current: content.length, max: maxLength })
1545
- }
1546
- )
1547
- ]
1548
- }
1549
- ),
1550
- /* @__PURE__ */ jsxRuntime.jsx(
1551
- SendButton,
1552
- {
1553
- canSend,
1554
- isLoading: isSending,
1555
- disabled
1556
- }
1557
- )
1558
- ]
1559
- }
1560
- )
1561
- ]
1562
- }
1563
- )
1564
- }
1565
- );
1566
- }
1567
- );
1568
- MessageComposer.displayName = "MessageComposer";
1569
- var headerVariants = classVarianceAuthority.cva(
1570
- [
1571
- "flex items-center gap-3 px-4 py-3",
1572
- "bg-white dark:bg-neutral-900",
1573
- "border-b border-neutral-200 dark:border-neutral-700"
1345
+ "flex items-center gap-3 px-4 py-3",
1346
+ "bg-white dark:bg-neutral-900",
1347
+ "border-b border-neutral-200 dark:border-neutral-700"
1574
1348
  ],
1575
1349
  {
1576
1350
  variants: {
@@ -1625,7 +1399,7 @@ function formatLastSeen(date) {
1625
1399
  if (diffDays < 7) return `${diffDays}d ago`;
1626
1400
  return date.toLocaleDateString();
1627
1401
  }
1628
- var ConversationHeader = React6__namespace.forwardRef(
1402
+ var ConversationHeader = React__namespace.forwardRef(
1629
1403
  ({
1630
1404
  className,
1631
1405
  size,
@@ -1747,7 +1521,7 @@ var ConversationHeader = React6__namespace.forwardRef(
1747
1521
  }
1748
1522
  );
1749
1523
  ConversationHeader.displayName = "ConversationHeader";
1750
- var ConversationListItem = React6__namespace.forwardRef(({ className, conversation, isSelected, onSelect, ...props }, ref) => {
1524
+ var ConversationListItem = React__namespace.forwardRef(({ className, conversation, isSelected, onSelect, ...props }, ref) => {
1751
1525
  const participant = conversation.participants.find((p) => !p.isCurrentUser);
1752
1526
  const title = getConversationTitle(conversation, participant);
1753
1527
  const avatarUrl = conversation.avatarUrl || participant?.avatarUrl;
@@ -1955,7 +1729,7 @@ function ConversationListSkeleton({
1955
1729
  }
1956
1730
  ConversationListSkeleton.displayName = "ConversationListSkeleton";
1957
1731
  function LightboxModal({ attachment, onClose }) {
1958
- React6__namespace.useEffect(() => {
1732
+ React__namespace.useEffect(() => {
1959
1733
  const handleKeyDown = (event) => {
1960
1734
  if (event.key === "Escape") {
1961
1735
  onClose();
@@ -2052,7 +1826,7 @@ function LightboxModal({ attachment, onClose }) {
2052
1826
  );
2053
1827
  }
2054
1828
  LightboxModal.displayName = "LightboxModal";
2055
- var MessageThread = React6__namespace.forwardRef(
1829
+ var MessageThread = React__namespace.forwardRef(
2056
1830
  ({
2057
1831
  conversation,
2058
1832
  messages,
@@ -2083,8 +1857,23 @@ var MessageThread = React6__namespace.forwardRef(
2083
1857
  onError,
2084
1858
  className
2085
1859
  }, ref) => {
2086
- const [lightboxAttachment, setLightboxAttachment] = React6__namespace.useState(null);
2087
- const [replyTo, setReplyTo] = React6__namespace.useState(null);
1860
+ const [lightboxAttachment, setLightboxAttachment] = React__namespace.useState(null);
1861
+ const [replyTo, setReplyTo] = React__namespace.useState(null);
1862
+ const composerRef = React__namespace.useRef(null);
1863
+ React__namespace.useEffect(() => {
1864
+ chunkOYQ7GYAA_cjs.notifyComposerMigrationOnce("MessageThread");
1865
+ }, []);
1866
+ const [draft, setDraft] = React__namespace.useState("");
1867
+ const draftEpochRef = React__namespace.useRef(0);
1868
+ const handleDraftChange = React__namespace.useCallback((value) => {
1869
+ draftEpochRef.current += 1;
1870
+ setDraft(value);
1871
+ }, []);
1872
+ const { stopTyping } = useTypingEmulation({
1873
+ value: draft,
1874
+ onTypingStart: eventHandlers.onTypingStart,
1875
+ onTypingStop: eventHandlers.onTypingStop
1876
+ });
2088
1877
  const participant = conversation?.type === "direct" ? conversation.participants.find((p) => p.id !== currentUser.id) : void 0;
2089
1878
  const handleAttachmentClick = (attachment, message) => {
2090
1879
  if (attachment.type === "image" || attachment.type === "video") {
@@ -2093,12 +1882,21 @@ var MessageThread = React6__namespace.forwardRef(
2093
1882
  eventHandlers.onAttachmentClick?.(attachment, message);
2094
1883
  };
2095
1884
  const handleSendMessage = async (newMessage) => {
1885
+ stopTyping();
2096
1886
  const messageWithReply = {
2097
1887
  ...newMessage,
2098
1888
  replyToId: replyTo?.id || newMessage.replyToId
2099
1889
  };
2100
1890
  setReplyTo(null);
2101
- await eventHandlers.onSendMessage?.(messageWithReply);
1891
+ const epoch = draftEpochRef.current;
1892
+ try {
1893
+ await Promise.resolve(eventHandlers.onSendMessage?.(messageWithReply));
1894
+ } catch (error) {
1895
+ if (draftEpochRef.current === epoch) {
1896
+ setDraft(newMessage.content);
1897
+ }
1898
+ throw error;
1899
+ }
2102
1900
  };
2103
1901
  return /* @__PURE__ */ jsxRuntime.jsxs(
2104
1902
  "div",
@@ -2122,11 +1920,13 @@ var MessageThread = React6__namespace.forwardRef(
2122
1920
  }
2123
1921
  ),
2124
1922
  /* @__PURE__ */ jsxRuntime.jsx(
2125
- chunkH3GRO43Z_cjs.DragDropZone,
1923
+ chunkOYQ7GYAA_cjs.DragDropZone,
2126
1924
  {
2127
- onFilesDropped: () => {
2128
- onError?.("Drop files on the composer to attach them");
1925
+ onFilesDropped: (files) => {
1926
+ composerRef.current?.addFiles(files);
2129
1927
  },
1928
+ maxFiles: maxAttachments,
1929
+ onError,
2130
1930
  disabled: !showAttachmentPicker,
2131
1931
  className: "flex-1 overflow-hidden",
2132
1932
  children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -2152,23 +1952,38 @@ var MessageThread = React6__namespace.forwardRef(
2152
1952
  }
2153
1953
  ),
2154
1954
  /* @__PURE__ */ jsxRuntime.jsx(
2155
- MessageComposer,
1955
+ "div",
2156
1956
  {
2157
- onSend: handleSendMessage,
2158
- onTypingStart: eventHandlers.onTypingStart,
2159
- onTypingStop: eventHandlers.onTypingStop,
2160
- placeholder,
2161
- maxLength: maxMessageLength,
2162
- showCharacterCount,
2163
- isSending,
2164
- showAttachmentPicker,
2165
- showCameraButton,
2166
- acceptedFileTypes,
2167
- maxFileSize,
2168
- maxAttachments,
2169
- onError,
2170
- replyTo,
2171
- onCancelReply: () => setReplyTo(null)
1957
+ "data-slot": "message-thread-composer",
1958
+ className: "shrink-0 border-t border-neutral-200 p-3 dark:border-neutral-700",
1959
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1960
+ chunkOYQ7GYAA_cjs.ChatComposer,
1961
+ {
1962
+ ref: composerRef,
1963
+ value: draft,
1964
+ onValueChange: handleDraftChange,
1965
+ onSend: handleSendMessage,
1966
+ placeholder,
1967
+ maxLength: maxMessageLength,
1968
+ showCharacterCount,
1969
+ isSending,
1970
+ allowAttachments: showAttachmentPicker,
1971
+ acceptedFileTypes: acceptedFileTypes ?? chunkOYQ7GYAA_cjs.DEFAULT_ACCEPTED_FILE_TYPES,
1972
+ maxFileSize: maxFileSize ?? chunkOYQ7GYAA_cjs.DEFAULT_MAX_FILE_SIZE,
1973
+ maxAttachments,
1974
+ onError,
1975
+ replyTo,
1976
+ onCancelReply: () => setReplyTo(null),
1977
+ inputLabel: "Message",
1978
+ micSlot: showCameraButton ? /* @__PURE__ */ jsxRuntime.jsx(
1979
+ chunkOYQ7GYAA_cjs.CameraButton,
1980
+ {
1981
+ onCapture: (file) => composerRef.current?.addFiles([file]),
1982
+ disabled: isSending
1983
+ }
1984
+ ) : void 0
1985
+ }
1986
+ )
2172
1987
  }
2173
1988
  ),
2174
1989
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -2248,252 +2063,8 @@ function MessagingSplitView({
2248
2063
  );
2249
2064
  }
2250
2065
  MessagingSplitView.displayName = "MessagingSplitView";
2251
- function useMessages(options) {
2252
- const {
2253
- initialMessages = [],
2254
- currentUser,
2255
- onSend,
2256
- onRetry,
2257
- onLoadMore
2258
- } = options;
2259
- const [messages, setMessages] = React6__namespace.useState(initialMessages);
2260
- const [isSending, setIsSending] = React6__namespace.useState(false);
2261
- const [isLoadingMore, setIsLoadingMore] = React6__namespace.useState(false);
2262
- React6__namespace.useEffect(() => {
2263
- setMessages(initialMessages);
2264
- }, [initialMessages]);
2265
- const addMessage = React6__namespace.useCallback((message) => {
2266
- setMessages((prev) => {
2267
- if (prev.some((m) => m.id === message.id)) {
2268
- return prev;
2269
- }
2270
- return [...prev, message];
2271
- });
2272
- }, []);
2273
- const updateMessage = React6__namespace.useCallback(
2274
- (messageId, updates) => {
2275
- setMessages(
2276
- (prev) => prev.map((m) => m.id === messageId ? { ...m, ...updates } : m)
2277
- );
2278
- },
2279
- []
2280
- );
2281
- const removeMessage = React6__namespace.useCallback((messageId) => {
2282
- setMessages((prev) => prev.filter((m) => m.id !== messageId));
2283
- }, []);
2284
- const updateStatus = React6__namespace.useCallback(
2285
- (messageId, status) => {
2286
- updateMessage(messageId, { status });
2287
- },
2288
- [updateMessage]
2289
- );
2290
- const markAsRead = React6__namespace.useCallback(
2291
- (messageId) => {
2292
- updateStatus(messageId, "read");
2293
- },
2294
- [updateStatus]
2295
- );
2296
- const sendMessage = React6__namespace.useCallback(
2297
- async (newMessage) => {
2298
- const optimisticId = `optimistic-${Date.now()}`;
2299
- const optimisticMessage = {
2300
- id: optimisticId,
2301
- type: "text",
2302
- content: newMessage.content,
2303
- sender: currentUser,
2304
- timestamp: /* @__PURE__ */ new Date(),
2305
- status: "sending",
2306
- attachments: []
2307
- // Would handle attachment uploads here
2308
- };
2309
- addMessage(optimisticMessage);
2310
- setIsSending(true);
2311
- try {
2312
- if (onSend) {
2313
- const sentMessage = await onSend(newMessage);
2314
- setMessages(
2315
- (prev) => prev.map((m) => m.id === optimisticId ? sentMessage : m)
2316
- );
2317
- } else {
2318
- updateStatus(optimisticId, "sent");
2319
- }
2320
- } catch {
2321
- updateStatus(optimisticId, "failed");
2322
- } finally {
2323
- setIsSending(false);
2324
- }
2325
- },
2326
- [currentUser, onSend, addMessage, updateStatus]
2327
- );
2328
- const retryMessage = React6__namespace.useCallback(
2329
- async (messageId) => {
2330
- updateStatus(messageId, "sending");
2331
- try {
2332
- if (onRetry) {
2333
- await onRetry(messageId);
2334
- updateStatus(messageId, "sent");
2335
- }
2336
- } catch {
2337
- updateStatus(messageId, "failed");
2338
- }
2339
- },
2340
- [onRetry, updateStatus]
2341
- );
2342
- const loadMore = React6__namespace.useCallback(async () => {
2343
- if (isLoadingMore || !onLoadMore) return;
2344
- setIsLoadingMore(true);
2345
- try {
2346
- const olderMessages = await onLoadMore();
2347
- setMessages((prev) => [...olderMessages, ...prev]);
2348
- } finally {
2349
- setIsLoadingMore(false);
2350
- }
2351
- }, [isLoadingMore, onLoadMore]);
2352
- return {
2353
- messages,
2354
- addMessage,
2355
- updateMessage,
2356
- removeMessage,
2357
- sendMessage,
2358
- retryMessage,
2359
- loadMore,
2360
- isSending,
2361
- isLoadingMore,
2362
- markAsRead,
2363
- updateStatus
2364
- };
2365
- }
2366
- function useTypingIndicator(options = {}) {
2367
- const {
2368
- typingParticipants: initialParticipants = [],
2369
- debounceTime = 2e3,
2370
- onTypingStart,
2371
- onTypingStop
2372
- } = options;
2373
- const [participants, setParticipants] = React6__namespace.useState(initialParticipants);
2374
- const [isLocalTyping, setIsLocalTyping] = React6__namespace.useState(false);
2375
- const typingTimeoutRef = React6__namespace.useRef(null);
2376
- React6__namespace.useEffect(() => {
2377
- setParticipants(initialParticipants);
2378
- }, [initialParticipants]);
2379
- const startTyping = React6__namespace.useCallback(() => {
2380
- if (!isLocalTyping) {
2381
- setIsLocalTyping(true);
2382
- onTypingStart?.();
2383
- }
2384
- if (typingTimeoutRef.current) {
2385
- clearTimeout(typingTimeoutRef.current);
2386
- }
2387
- typingTimeoutRef.current = setTimeout(() => {
2388
- setIsLocalTyping(false);
2389
- onTypingStop?.();
2390
- }, debounceTime);
2391
- }, [isLocalTyping, debounceTime, onTypingStart, onTypingStop]);
2392
- const stopTyping = React6__namespace.useCallback(() => {
2393
- if (typingTimeoutRef.current) {
2394
- clearTimeout(typingTimeoutRef.current);
2395
- }
2396
- setIsLocalTyping(false);
2397
- onTypingStop?.();
2398
- }, [onTypingStop]);
2399
- React6__namespace.useEffect(() => {
2400
- return () => {
2401
- if (typingTimeoutRef.current) {
2402
- clearTimeout(typingTimeoutRef.current);
2403
- }
2404
- };
2405
- }, []);
2406
- const typingState = React6__namespace.useMemo(
2407
- () => ({
2408
- participants,
2409
- lastUpdated: /* @__PURE__ */ new Date()
2410
- }),
2411
- [participants]
2412
- );
2413
- return {
2414
- typingState,
2415
- startTyping,
2416
- stopTyping,
2417
- setTypingParticipants: setParticipants
2418
- };
2419
- }
2420
- function useMessageScroll(options) {
2421
- const { messages, currentUserId, threshold = 100 } = options;
2422
- const scrollContainerRef = React6__namespace.useRef(null);
2423
- const bottomRef = React6__namespace.useRef(null);
2424
- const [isScrolledUp, setIsScrolledUp] = React6__namespace.useState(false);
2425
- const prevMessageCountRef = React6__namespace.useRef(messages.length);
2426
- React6__namespace.useEffect(() => {
2427
- const container = scrollContainerRef.current;
2428
- if (!container) return;
2429
- const handleScroll = () => {
2430
- const { scrollTop, scrollHeight, clientHeight } = container;
2431
- const isAtBottom = scrollHeight - scrollTop - clientHeight < threshold;
2432
- setIsScrolledUp(!isAtBottom);
2433
- };
2434
- container.addEventListener("scroll", handleScroll);
2435
- return () => container.removeEventListener("scroll", handleScroll);
2436
- }, [threshold]);
2437
- const scrollToBottom = React6__namespace.useCallback((smooth = true) => {
2438
- bottomRef.current?.scrollIntoView({
2439
- behavior: smooth ? "smooth" : "auto"
2440
- });
2441
- }, []);
2442
- React6__namespace.useEffect(() => {
2443
- const messageCountChanged = messages.length !== prevMessageCountRef.current;
2444
- prevMessageCountRef.current = messages.length;
2445
- if (!messageCountChanged) return;
2446
- const lastMessage = messages[messages.length - 1];
2447
- const isOutgoing = lastMessage?.sender.id === currentUserId;
2448
- if (isOutgoing || !isScrolledUp) {
2449
- scrollToBottom(true);
2450
- }
2451
- }, [messages, currentUserId, isScrolledUp, scrollToBottom]);
2452
- return {
2453
- scrollContainerRef,
2454
- bottomRef,
2455
- isScrolledUp,
2456
- scrollToBottom
2457
- };
2458
- }
2459
- function useReadReceipts(options) {
2460
- const { currentUserId, onMarkRead, threshold = 0.5 } = options;
2461
- const observerRef = React6__namespace.useRef(null);
2462
- const observedMessagesRef = React6__namespace.useRef(/* @__PURE__ */ new Set());
2463
- React6__namespace.useEffect(() => {
2464
- observerRef.current = new IntersectionObserver(
2465
- (entries) => {
2466
- entries.forEach((entry) => {
2467
- if (entry.isIntersecting) {
2468
- const messageId = entry.target.getAttribute("data-message-id");
2469
- if (messageId && !observedMessagesRef.current.has(messageId)) {
2470
- observedMessagesRef.current.add(messageId);
2471
- onMarkRead?.(messageId);
2472
- }
2473
- }
2474
- });
2475
- },
2476
- { threshold }
2477
- );
2478
- return () => {
2479
- observerRef.current?.disconnect();
2480
- };
2481
- }, [onMarkRead, threshold]);
2482
- const observeMessage = React6__namespace.useCallback(
2483
- (element, message) => {
2484
- if (!element || !observerRef.current) return;
2485
- if (message.sender.id !== currentUserId && message.status !== "read" && !observedMessagesRef.current.has(message.id)) {
2486
- element.setAttribute("data-message-id", message.id);
2487
- observerRef.current.observe(element);
2488
- }
2489
- },
2490
- [currentUserId]
2491
- );
2492
- return { observeMessage };
2493
- }
2494
2066
 
2495
2067
  exports.AttachmentPreview = AttachmentPreview;
2496
- exports.CharacterCounter = CharacterCounter;
2497
2068
  exports.ConversationHeader = ConversationHeader;
2498
2069
  exports.ConversationListItem = ConversationListItem;
2499
2070
  exports.ConversationListSkeleton = ConversationListSkeleton;
@@ -2502,13 +2073,11 @@ exports.EmptyState = EmptyState;
2502
2073
  exports.LightboxModal = LightboxModal;
2503
2074
  exports.LoadMoreButton = LoadMoreButton;
2504
2075
  exports.MessageBubble = MessageBubble;
2505
- exports.MessageComposer = MessageComposer;
2506
2076
  exports.MessageList = MessageList;
2507
2077
  exports.MessageStatusIcon = MessageStatusIcon;
2508
2078
  exports.MessageThread = MessageThread;
2509
2079
  exports.MessagingSplitView = MessagingSplitView;
2510
2080
  exports.ReadReceiptIndicator = ReadReceiptIndicator;
2511
- exports.SendButton = SendButton;
2512
2081
  exports.SkeletonMessage = SkeletonMessage;
2513
2082
  exports.TypingIndicator = TypingIndicator;
2514
2083
  exports.bubbleVariants = bubbleVariants;
@@ -2520,10 +2089,10 @@ exports.getConversationTitle = getConversationTitle;
2520
2089
  exports.groupMessagesByDate = groupMessagesByDate;
2521
2090
  exports.headerVariants = headerVariants;
2522
2091
  exports.isSameSenderGroup = isSameSenderGroup;
2523
- exports.sendButtonVariants = sendButtonVariants;
2524
2092
  exports.useMessageScroll = useMessageScroll;
2525
2093
  exports.useMessages = useMessages;
2526
2094
  exports.useReadReceipts = useReadReceipts;
2095
+ exports.useTypingEmulation = useTypingEmulation;
2527
2096
  exports.useTypingIndicator = useTypingIndicator;
2528
- //# sourceMappingURL=chunk-ZXRVVNT5.cjs.map
2529
- //# sourceMappingURL=chunk-ZXRVVNT5.cjs.map
2097
+ //# sourceMappingURL=chunk-OFUKTVBN.cjs.map
2098
+ //# sourceMappingURL=chunk-OFUKTVBN.cjs.map