stream-chat-react 14.0.0-beta.1 → 14.0.0-beta.3

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 (110) hide show
  1. package/dist/{WithAudioPlayback-TERIQpZ6.js → cjs/WithAudioPlayback.4a84360f.js} +100 -12
  2. package/dist/cjs/WithAudioPlayback.4a84360f.js.map +1 -0
  3. package/dist/{audioProcessing-BbOs2wMd.js → cjs/audioProcessing.56e5db9d.js} +1 -1
  4. package/dist/cjs/audioProcessing.56e5db9d.js.map +1 -0
  5. package/dist/cjs/emojis.js +1 -1
  6. package/dist/cjs/index.js +1366 -1037
  7. package/dist/cjs/index.js.map +1 -1
  8. package/dist/cjs/mp3-encoder.js +1 -1
  9. package/dist/css/index.css +232 -62
  10. package/dist/css/index.css.map +1 -1
  11. package/dist/{WithAudioPlayback-BcKZ5Lbh.mjs → es/WithAudioPlayback.a3d5a2fc.mjs} +348 -260
  12. package/dist/es/WithAudioPlayback.a3d5a2fc.mjs.map +1 -0
  13. package/dist/{audioProcessing-ByEVSjGG.mjs → es/audioProcessing.21cb49e1.mjs} +1 -1
  14. package/dist/es/audioProcessing.21cb49e1.mjs.map +1 -0
  15. package/dist/es/emojis.mjs +1 -1
  16. package/dist/es/index.mjs +1500 -1171
  17. package/dist/es/index.mjs.map +1 -1
  18. package/dist/es/mp3-encoder.mjs +1 -1
  19. package/dist/types/components/Attachment/Giphy.d.ts.map +1 -1
  20. package/dist/types/components/Attachment/VoiceRecording.d.ts.map +1 -1
  21. package/dist/types/components/AudioPlayback/AudioPlayer.d.ts.map +1 -1
  22. package/dist/types/components/Badge/Badge.d.ts +1 -0
  23. package/dist/types/components/Badge/Badge.d.ts.map +1 -1
  24. package/dist/types/components/BaseImage/toBaseImageDescriptors.d.ts +1 -1
  25. package/dist/types/components/BaseImage/toBaseImageDescriptors.d.ts.map +1 -1
  26. package/dist/types/components/Channel/Channel.d.ts.map +1 -1
  27. package/dist/types/components/Channel/utils.d.ts +7 -1
  28. package/dist/types/components/Channel/utils.d.ts.map +1 -1
  29. package/dist/types/components/ChannelList/hooks/useMobileNavigation.d.ts.map +1 -1
  30. package/dist/types/components/ChannelListItem/ChannelListItemActionButtons.d.ts +3 -1
  31. package/dist/types/components/ChannelListItem/ChannelListItemActionButtons.d.ts.map +1 -1
  32. package/dist/types/components/ChannelListItem/ChannelListItemActionButtons.defaults.d.ts +6 -3
  33. package/dist/types/components/ChannelListItem/ChannelListItemActionButtons.defaults.d.ts.map +1 -1
  34. package/dist/types/components/Chat/hooks/useSplitActionSet.d.ts +5 -0
  35. package/dist/types/components/Chat/hooks/useSplitActionSet.d.ts.map +1 -1
  36. package/dist/types/components/ChatView/ChatView.d.ts.map +1 -1
  37. package/dist/types/components/Dialog/components/ContextMenu.d.ts +119 -3
  38. package/dist/types/components/Dialog/components/ContextMenu.d.ts.map +1 -1
  39. package/dist/types/components/Dialog/hooks/useDialog.d.ts +1 -1
  40. package/dist/types/components/Dialog/hooks/useDialog.d.ts.map +1 -1
  41. package/dist/types/components/Dialog/service/DialogAnchor.d.ts +14 -1
  42. package/dist/types/components/Dialog/service/DialogAnchor.d.ts.map +1 -1
  43. package/dist/types/components/Dialog/service/DialogManager.d.ts +14 -3
  44. package/dist/types/components/Dialog/service/DialogManager.d.ts.map +1 -1
  45. package/dist/types/components/Dialog/service/DialogPortal.d.ts.map +1 -1
  46. package/dist/types/components/Gallery/Gallery.d.ts.map +1 -1
  47. package/dist/types/components/Gallery/GalleryContext.d.ts +1 -1
  48. package/dist/types/components/Gallery/GalleryContext.d.ts.map +1 -1
  49. package/dist/types/components/Icons/icons.d.ts +4 -0
  50. package/dist/types/components/Icons/icons.d.ts.map +1 -1
  51. package/dist/types/components/MediaRecorder/AudioRecorder/AudioRecorderRecordingControls.d.ts.map +1 -1
  52. package/dist/types/components/Message/MessageErrorText.d.ts +0 -5
  53. package/dist/types/components/Message/MessageErrorText.d.ts.map +1 -1
  54. package/dist/types/components/Message/MessageText.d.ts.map +1 -1
  55. package/dist/types/components/Message/MessageUI.d.ts.map +1 -1
  56. package/dist/types/components/Message/hooks/useDeleteHandler.d.ts.map +1 -1
  57. package/dist/types/components/Message/index.d.ts +0 -1
  58. package/dist/types/components/Message/index.d.ts.map +1 -1
  59. package/dist/types/components/Message/utils.d.ts +1 -0
  60. package/dist/types/components/Message/utils.d.ts.map +1 -1
  61. package/dist/types/components/MessageActions/MessageActions.d.ts +14 -3
  62. package/dist/types/components/MessageActions/MessageActions.d.ts.map +1 -1
  63. package/dist/types/components/MessageActions/MessageActions.defaults.d.ts +1 -1
  64. package/dist/types/components/MessageActions/MessageActions.defaults.d.ts.map +1 -1
  65. package/dist/types/components/MessageActions/QuickMessageActionButton.d.ts.map +1 -1
  66. package/dist/types/components/MessageActions/hooks/useBaseMessageActionSetFilter.d.ts.map +1 -1
  67. package/dist/types/components/MessageComposer/AttachmentPreviewList/AttachmentPreviewList.d.ts.map +1 -1
  68. package/dist/types/components/MessageComposer/AttachmentPreviewList/ImageAttachmentPreview.d.ts +3 -1
  69. package/dist/types/components/MessageComposer/AttachmentPreviewList/ImageAttachmentPreview.d.ts.map +1 -1
  70. package/dist/types/components/MessageComposer/AttachmentPreviewList/MediaAttachmentPreview.d.ts +4 -2
  71. package/dist/types/components/MessageComposer/AttachmentPreviewList/MediaAttachmentPreview.d.ts.map +1 -1
  72. package/dist/types/components/MessageComposer/AttachmentPreviewList/utils/AttachmentPreviewRoot.d.ts +6 -1
  73. package/dist/types/components/MessageComposer/AttachmentPreviewList/utils/AttachmentPreviewRoot.d.ts.map +1 -1
  74. package/dist/types/components/MessageComposer/AttachmentSelector/AttachmentSelector.d.ts +9 -2
  75. package/dist/types/components/MessageComposer/AttachmentSelector/AttachmentSelector.d.ts.map +1 -1
  76. package/dist/types/components/MessageComposer/CommandChip.d.ts +5 -1
  77. package/dist/types/components/MessageComposer/CommandChip.d.ts.map +1 -1
  78. package/dist/types/components/MessageComposer/MessageComposerUI.d.ts.map +1 -1
  79. package/dist/types/components/MessageList/hooks/MessageList/useScrollLocationLogic.d.ts +18 -0
  80. package/dist/types/components/MessageList/hooks/MessageList/useScrollLocationLogic.d.ts.map +1 -1
  81. package/dist/types/components/Notifications/Notification.d.ts.map +1 -1
  82. package/dist/types/components/Notifications/hooks/useNotificationTarget.d.ts +1 -1
  83. package/dist/types/components/Notifications/hooks/useNotificationTarget.d.ts.map +1 -1
  84. package/dist/types/components/Notifications/notificationTarget.d.ts +1 -1
  85. package/dist/types/components/Notifications/notificationTarget.d.ts.map +1 -1
  86. package/dist/types/components/Poll/PollActions/PollResults/PollOptionWithVotes.d.ts.map +1 -1
  87. package/dist/types/components/Poll/PollOptionSelector.d.ts.map +1 -1
  88. package/dist/types/components/Reactions/MessageReactions.d.ts.map +1 -1
  89. package/dist/types/components/Reactions/MessageReactionsDetail.d.ts +1 -0
  90. package/dist/types/components/Reactions/MessageReactionsDetail.d.ts.map +1 -1
  91. package/dist/types/components/Reactions/ReactionSelector.d.ts +1 -1
  92. package/dist/types/components/Reactions/ReactionSelector.d.ts.map +1 -1
  93. package/dist/types/components/TextareaComposer/SuggestionList/SuggestionList.d.ts.map +1 -1
  94. package/dist/types/components/TextareaComposer/TextareaComposer.d.ts +1 -2
  95. package/dist/types/components/TextareaComposer/TextareaComposer.d.ts.map +1 -1
  96. package/dist/types/context/ChannelListContext.d.ts +1 -1
  97. package/dist/types/context/ChannelListContext.d.ts.map +1 -1
  98. package/dist/types/context/ComponentContext.d.ts +10 -3
  99. package/dist/types/context/ComponentContext.d.ts.map +1 -1
  100. package/dist/types/context/DialogManagerContext.d.ts +11 -7
  101. package/dist/types/context/DialogManagerContext.d.ts.map +1 -1
  102. package/dist/types/i18n/Streami18n.d.ts +11 -6
  103. package/dist/types/i18n/Streami18n.d.ts.map +1 -1
  104. package/package.json +13 -41
  105. package/dist/WithAudioPlayback-BcKZ5Lbh.mjs.map +0 -1
  106. package/dist/WithAudioPlayback-TERIQpZ6.js.map +0 -1
  107. package/dist/audioProcessing-BbOs2wMd.js.map +0 -1
  108. package/dist/audioProcessing-ByEVSjGG.mjs.map +0 -1
  109. package/dist/types/components/Message/icons.d.ts +0 -7
  110. package/dist/types/components/Message/icons.d.ts.map +0 -1
package/dist/cjs/index.js CHANGED
@@ -4,8 +4,8 @@ const jsxRuntime = require("react/jsx-runtime");
4
4
  const clsx = require("clsx");
5
5
  const nanoid = require("nanoid");
6
6
  const React = require("react");
7
- const audioProcessing = require("../audioProcessing-BbOs2wMd.js");
8
- const WithAudioPlayback = require("../WithAudioPlayback-TERIQpZ6.js");
7
+ const audioProcessing = require("./audioProcessing.56e5db9d.js");
8
+ const WithAudioPlayback = require("./WithAudioPlayback.4a84360f.js");
9
9
  const streamChat = require("stream-chat");
10
10
  const throttle = require("lodash.throttle");
11
11
  const linkify = require("linkifyjs");
@@ -19,7 +19,6 @@ const unistUtilVisit = require("unist-util-visit");
19
19
  const i18n = require("i18next");
20
20
  const Dayjs = require("dayjs");
21
21
  const uniqBy = require("lodash.uniqby");
22
- const tsPattern = require("ts-pattern");
23
22
  const shim = require("use-sync-external-store/shim");
24
23
  const debounce = require("lodash.debounce");
25
24
  const fixWebmDuration = require("fix-webm-duration");
@@ -88,28 +87,12 @@ const useAIState = (channel) => {
88
87
  }, [channel]);
89
88
  return { aiState };
90
89
  };
91
- const ChannelListContext = React.createContext(
92
- void 0
93
- );
94
- const ChannelListContextProvider = ({
95
- children,
96
- value
97
- }) => /* @__PURE__ */ jsxRuntime.jsx(ChannelListContext.Provider, { value, children });
98
- const useChannelListContext = (componentName) => {
99
- const contextValue = React.useContext(ChannelListContext);
100
- if (!contextValue) {
101
- console.warn(
102
- `The useChannelListContext hook was called outside of the ChannelListContext provider. Make sure this hook is called within the ChannelList component. The errored call is located in the ${componentName} component.`
103
- );
104
- return {};
105
- }
106
- return contextValue;
107
- };
108
90
  class DialogManager {
109
- constructor({ id } = {}) {
91
+ constructor({ closeOnClickOutside = true, id } = {}) {
110
92
  this.state = new streamChat.StateStore({
111
93
  dialogsById: {}
112
94
  });
95
+ this.closeOnClickOutside = closeOnClickOutside;
113
96
  this.id = id ?? nanoid.nanoid();
114
97
  }
115
98
  get openDialogCount() {
@@ -124,13 +107,14 @@ class DialogManager {
124
107
  get(id) {
125
108
  return this.state.getLatestValue().dialogsById[id];
126
109
  }
127
- getOrCreate({ id }) {
110
+ getOrCreate({ closeOnClickOutside, id }) {
128
111
  let dialog = this.state.getLatestValue().dialogsById[id];
129
112
  if (!dialog) {
130
113
  dialog = {
131
114
  close: () => {
132
115
  this.close(id);
133
116
  },
117
+ closeOnClickOutside,
134
118
  id,
135
119
  isOpen: false,
136
120
  open: () => {
@@ -146,21 +130,22 @@ class DialogManager {
146
130
  };
147
131
  this.state.next((current) => ({
148
132
  ...current,
149
- ...{ dialogsById: { ...current.dialogsById, [id]: dialog } }
133
+ dialogsById: { ...current.dialogsById, [id]: dialog }
150
134
  }));
151
135
  }
152
- if (dialog.removalTimeout) {
153
- clearTimeout(dialog.removalTimeout);
136
+ const shouldUpdateDialogSettings = dialog.closeOnClickOutside !== closeOnClickOutside || !!dialog.removalTimeout;
137
+ if (shouldUpdateDialogSettings) {
138
+ if (dialog.removalTimeout) clearTimeout(dialog.removalTimeout);
139
+ dialog = {
140
+ ...dialog,
141
+ closeOnClickOutside,
142
+ removalTimeout: void 0
143
+ };
154
144
  this.state.next((current) => ({
155
145
  ...current,
156
- ...{
157
- dialogsById: {
158
- ...current.dialogsById,
159
- [id]: {
160
- ...dialog,
161
- removalTimeout: void 0
162
- }
163
- }
146
+ dialogsById: {
147
+ ...current.dialogsById,
148
+ [id]: dialog
164
149
  }
165
150
  }));
166
151
  }
@@ -237,7 +222,11 @@ class DialogManager {
237
222
  }));
238
223
  }
239
224
  }
240
- const useDialog = ({ dialogManagerId, id }) => {
225
+ const useDialog = ({
226
+ closeOnClickOutside,
227
+ dialogManagerId,
228
+ id
229
+ }) => {
241
230
  const { dialogManager } = useDialogManager({ dialogManagerId });
242
231
  React.useEffect(
243
232
  () => () => {
@@ -245,7 +234,7 @@ const useDialog = ({ dialogManagerId, id }) => {
245
234
  },
246
235
  [dialogManager, id]
247
236
  );
248
- return dialogManager.getOrCreate({ id });
237
+ return dialogManager.getOrCreate({ closeOnClickOutside, id });
249
238
  };
250
239
  const useDialogOnNearestManager = ({ id }) => {
251
240
  const { dialogManager } = useNearestDialogManagerContext() ?? {};
@@ -292,9 +281,37 @@ const Portal = ({
292
281
  if (!portalDestination) return null;
293
282
  return reactDom.createPortal(children, portalDestination);
294
283
  };
284
+ const shouldCloseOnOutsideClick = ({
285
+ dialog,
286
+ managerCloseOnClickOutside
287
+ }) => dialog.closeOnClickOutside ?? managerCloseOnClickOutside;
295
288
  const DialogPortalDestination = () => {
296
289
  const { dialogManager } = useNearestDialogManagerContext() ?? {};
297
290
  const openedDialogCount = useOpenedDialogCount({ dialogManagerId: dialogManager?.id });
291
+ const [destinationRoot, setDestinationRoot] = React.useState(null);
292
+ React.useEffect(() => {
293
+ if (!destinationRoot || !dialogManager) return;
294
+ const handleDocumentClick = (event) => {
295
+ if (destinationRoot.contains(event.target)) return;
296
+ setTimeout(() => {
297
+ Object.values(dialogManager.state.getLatestValue().dialogsById).forEach(
298
+ (dialog) => {
299
+ if (!dialog.isOpen) return;
300
+ if (!shouldCloseOnOutsideClick({
301
+ dialog,
302
+ managerCloseOnClickOutside: dialogManager.closeOnClickOutside
303
+ }))
304
+ return;
305
+ dialogManager.close(dialog.id);
306
+ }
307
+ );
308
+ }, 0);
309
+ };
310
+ document.addEventListener("click", handleDocumentClick, { capture: true });
311
+ return () => {
312
+ document.removeEventListener("click", handleDocumentClick, { capture: true });
313
+ };
314
+ }, [destinationRoot, dialogManager]);
298
315
  if (!openedDialogCount) return null;
299
316
  return /* @__PURE__ */ jsxRuntime.jsx(
300
317
  "div",
@@ -302,7 +319,22 @@ const DialogPortalDestination = () => {
302
319
  className: "str-chat__dialog-overlay",
303
320
  "data-str-chat__portal-id": dialogManager?.id,
304
321
  "data-testid": "str-chat__dialog-overlay",
305
- onClick: () => dialogManager?.closeAll(),
322
+ onClick: (event) => {
323
+ if (!dialogManager) return;
324
+ if (event.target !== event.currentTarget) return;
325
+ Object.values(dialogManager.state.getLatestValue().dialogsById).forEach(
326
+ (dialog) => {
327
+ if (!dialog.isOpen) return;
328
+ if (!shouldCloseOnOutsideClick({
329
+ dialog,
330
+ managerCloseOnClickOutside: dialogManager.closeOnClickOutside
331
+ }))
332
+ return;
333
+ dialogManager.close(dialog.id);
334
+ }
335
+ );
336
+ },
337
+ ref: setDestinationRoot,
306
338
  style: {
307
339
  "--str-chat__dialog-overlay-height": openedDialogCount > 0 ? "100%" : "0"
308
340
  }
@@ -324,11 +356,16 @@ const DialogPortalEntry = ({
324
356
  };
325
357
  const dialogManagersRegistry = new streamChat.StateStore({});
326
358
  const getDialogManager = (id) => dialogManagersRegistry.getLatestValue()[id];
327
- const getOrCreateDialogManager = (id) => {
359
+ const getOrCreateDialogManager = ({
360
+ closeOnClickOutside,
361
+ id
362
+ }) => {
328
363
  let manager = getDialogManager(id);
329
364
  if (!manager) {
330
- manager = new DialogManager({ id });
365
+ manager = new DialogManager({ closeOnClickOutside, id });
331
366
  dialogManagersRegistry.partialNext({ [id]: manager });
367
+ } else if (typeof closeOnClickOutside === "boolean") {
368
+ manager.closeOnClickOutside = closeOnClickOutside;
332
369
  }
333
370
  return manager;
334
371
  };
@@ -339,20 +376,21 @@ const removeDialogManager = (id) => {
339
376
  const DialogManagerProviderContext = React.createContext(void 0);
340
377
  const DialogManagerProvider = ({
341
378
  children,
379
+ closeOnClickOutside,
342
380
  id
343
381
  }) => {
344
382
  const [dialogManager, setDialogManager] = React.useState(() => {
345
383
  if (id) return getDialogManager(id) ?? null;
346
- return new DialogManager();
384
+ return new DialogManager({ closeOnClickOutside });
347
385
  });
348
386
  React.useEffect(() => {
349
387
  if (!id) return;
350
- setDialogManager(getOrCreateDialogManager(id));
388
+ setDialogManager(getOrCreateDialogManager({ closeOnClickOutside, id }));
351
389
  return () => {
352
390
  removeDialogManager(id);
353
391
  setDialogManager(null);
354
392
  };
355
- }, [id]);
393
+ }, [closeOnClickOutside, id]);
356
394
  if (!dialogManager) return null;
357
395
  return /* @__PURE__ */ jsxRuntime.jsxs(DialogManagerProviderContext.Provider, { value: { dialogManager }, children: [
358
396
  children,
@@ -967,7 +1005,6 @@ const deTranslations = {
967
1005
  "End vote": "Abstimmung beenden",
968
1006
  "Enforce unique vote is enabled": "Eindeutige Abstimmung ist aktiviert",
969
1007
  "Error": "Fehler",
970
- "Error · Unsent": "Fehler · Nicht gesendet",
971
1008
  "Error adding flag": "Fehler beim Hinzufügen des Flags",
972
1009
  "Error connecting to chat, refresh the page to try again.": "Verbindungsfehler zum Chat, aktualisieren Sie die Seite, um es erneut zu versuchen.",
973
1010
  "Error deleting message": "Fehler beim Löschen der Nachricht",
@@ -1089,8 +1126,6 @@ const deTranslations = {
1089
1126
  "Maximum number of votes (from 2 to 10)": "Maximale Anzahl der Stimmen (von 2 bis 10)",
1090
1127
  Menu: Menu$b,
1091
1128
  "Message deleted": "Nachricht gelöscht",
1092
- "Message Failed · Click to try again": "Nachricht fehlgeschlagen · Klicken, um es erneut zu versuchen",
1093
- "Message Failed · Unauthorized": "Nachricht fehlgeschlagen · Nicht autorisiert",
1094
1129
  "Message failed to send": "Nachricht konnte nicht gesendet werden",
1095
1130
  "Message has been successfully flagged": "Nachricht wurde erfolgreich gemeldet",
1096
1131
  "Message pinned": "Nachricht angeheftet",
@@ -1260,6 +1295,7 @@ const deTranslations = {
1260
1295
  "View translation": "Übersetzung anzeigen",
1261
1296
  "Voice message": "Sprachnachricht",
1262
1297
  "Voice message {{ duration }}": "Sprachnachricht {{ duration }}",
1298
+ "Voice message deleted": "Sprachnachricht gelöscht",
1263
1299
  voiceMessageCount_one: voiceMessageCount_one$9,
1264
1300
  voiceMessageCount_other: voiceMessageCount_other$b,
1265
1301
  "Vote ended": "Abstimmung beendet",
@@ -1490,7 +1526,6 @@ const enTranslations = {
1490
1526
  "End vote": "End Vote",
1491
1527
  "Enforce unique vote is enabled": "Enforce unique vote is enabled",
1492
1528
  "Error": "Error",
1493
- "Error · Unsent": "Error · Unsent",
1494
1529
  "Error adding flag": "Error adding flag",
1495
1530
  "Error connecting to chat, refresh the page to try again.": "Error connecting to chat, refresh the page to try again.",
1496
1531
  "Error deleting message": "Error deleting message",
@@ -1612,8 +1647,6 @@ const enTranslations = {
1612
1647
  "Maximum number of votes (from 2 to 10)": "Maximum number of votes (from 2 to 10)",
1613
1648
  Menu: Menu$a,
1614
1649
  "Message deleted": "Message deleted",
1615
- "Message Failed · Click to try again": "Message Failed · Click to try again",
1616
- "Message Failed · Unauthorized": "Message Failed · Unauthorized",
1617
1650
  "Message failed to send": "Message failed to send",
1618
1651
  "Message has been successfully flagged": "Message has been successfully flagged",
1619
1652
  "Message pinned": "Message pinned",
@@ -1783,6 +1816,7 @@ const enTranslations = {
1783
1816
  "View translation": "View translation",
1784
1817
  "Voice message": "Voice message",
1785
1818
  "Voice message {{ duration }}": "Voice message {{ duration }}",
1819
+ "Voice message deleted": "Voice message deleted",
1786
1820
  voiceMessageCount_one: voiceMessageCount_one$8,
1787
1821
  voiceMessageCount_other: voiceMessageCount_other$a,
1788
1822
  "Vote ended": "Vote ended",
@@ -2029,7 +2063,6 @@ const esTranslations = {
2029
2063
  "End vote": "Finalizar votación",
2030
2064
  "Enforce unique vote is enabled": "El voto único está habilitado",
2031
2065
  "Error": "Error",
2032
- "Error · Unsent": "Error · No enviado",
2033
2066
  "Error adding flag": "Error al agregar la bandera",
2034
2067
  "Error connecting to chat, refresh the page to try again.": "Error al conectarse al chat, actualice la página para volver a intentarlo.",
2035
2068
  "Error deleting message": "Error al eliminar el mensaje",
@@ -2154,8 +2187,6 @@ const esTranslations = {
2154
2187
  "Maximum number of votes (from 2 to 10)": "Número máximo de votos (de 2 a 10)",
2155
2188
  Menu: Menu$9,
2156
2189
  "Message deleted": "Mensaje eliminado",
2157
- "Message Failed · Click to try again": "Mensaje fallido · Haga clic para volver a intentarlo",
2158
- "Message Failed · Unauthorized": "Mensaje fallido · No autorizado",
2159
2190
  "Message failed to send": "No se pudo enviar el mensaje",
2160
2191
  "Message has been successfully flagged": "El mensaje se marcó correctamente",
2161
2192
  "Message pinned": "Mensaje fijado",
@@ -2332,6 +2363,7 @@ const esTranslations = {
2332
2363
  "View translation": "Ver traducción",
2333
2364
  "Voice message": "Mensaje de voz",
2334
2365
  "Voice message {{ duration }}": "Mensaje de voz {{ duration }}",
2366
+ "Voice message deleted": "Mensaje de voz eliminado",
2335
2367
  voiceMessageCount_one: voiceMessageCount_one$7,
2336
2368
  voiceMessageCount_many: voiceMessageCount_many$4,
2337
2369
  voiceMessageCount_other: voiceMessageCount_other$9,
@@ -2579,7 +2611,6 @@ const frTranslations = {
2579
2611
  "End vote": "Fin du vote",
2580
2612
  "Enforce unique vote is enabled": "Le vote unique est activé",
2581
2613
  "Error": "Erreur",
2582
- "Error · Unsent": "Erreur - Non envoyé",
2583
2614
  "Error adding flag": "Erreur lors de l'ajout du signalement",
2584
2615
  "Error connecting to chat, refresh the page to try again.": "Erreur de connexion au chat, rafraîchissez la page pour réessayer.",
2585
2616
  "Error deleting message": "Erreur lors de la suppression du message",
@@ -2704,8 +2735,6 @@ const frTranslations = {
2704
2735
  "Maximum number of votes (from 2 to 10)": "Nombre maximum de votes (de 2 à 10)",
2705
2736
  Menu: Menu$8,
2706
2737
  "Message deleted": "Message supprimé",
2707
- "Message Failed · Click to try again": "Échec de l'envoi du message - Cliquez pour réessayer",
2708
- "Message Failed · Unauthorized": "Échec de l'envoi du message - Non autorisé",
2709
2738
  "Message failed to send": "Échec de l'envoi du message",
2710
2739
  "Message has been successfully flagged": "Le message a été signalé avec succès",
2711
2740
  "Message pinned": "Message épinglé",
@@ -2882,6 +2911,7 @@ const frTranslations = {
2882
2911
  "View translation": "Voir la traduction",
2883
2912
  "Voice message": "Message vocal",
2884
2913
  "Voice message {{ duration }}": "Message vocal {{ duration }}",
2914
+ "Voice message deleted": "Message vocal supprimé",
2885
2915
  voiceMessageCount_one: voiceMessageCount_one$6,
2886
2916
  voiceMessageCount_many: voiceMessageCount_many$3,
2887
2917
  voiceMessageCount_other: voiceMessageCount_other$8,
@@ -3113,7 +3143,6 @@ const hiTranslations = {
3113
3143
  "End vote": "मत समाप्त करें",
3114
3144
  "Enforce unique vote is enabled": "अनोखा वोट सक्षम है",
3115
3145
  "Error": "त्रुटि",
3116
- "Error · Unsent": "फेल",
3117
3146
  "Error adding flag": "ध्वज जोड़ने में त्रुटि",
3118
3147
  "Error connecting to chat, refresh the page to try again.": "चैट से कनेक्ट करने में त्रुटि, पेज को रिफ्रेश करें",
3119
3148
  "Error deleting message": "संदेश हटाने में त्रुटि",
@@ -3236,8 +3265,6 @@ const hiTranslations = {
3236
3265
  "Maximum number of votes (from 2 to 10)": "अधिकतम वोटों की संख्या (2 से 10)",
3237
3266
  Menu: Menu$7,
3238
3267
  "Message deleted": "मैसेज हटा दिया गया",
3239
- "Message Failed · Click to try again": "मैसेज फ़ैल - पुनः कोशिश करें",
3240
- "Message Failed · Unauthorized": "मैसेज फ़ैल - अनधिकृत",
3241
3268
  "Message failed to send": "संदेश भेजने में विफल",
3242
3269
  "Message has been successfully flagged": "मैसेज को फ्लैग कर दिया गया है",
3243
3270
  "Message pinned": "संदेश पिन किया गया",
@@ -3407,6 +3434,7 @@ const hiTranslations = {
3407
3434
  "View translation": "अनुवाद देखें",
3408
3435
  "Voice message": "आवाज संदेश",
3409
3436
  "Voice message {{ duration }}": "वॉइस संदेश {{ duration }}",
3437
+ "Voice message deleted": "वॉइस संदेश हटा दिया गया",
3410
3438
  voiceMessageCount_one: voiceMessageCount_one$5,
3411
3439
  voiceMessageCount_other: voiceMessageCount_other$7,
3412
3440
  "Vote ended": "मतदान समाप्त",
@@ -3653,7 +3681,6 @@ const itTranslations = {
3653
3681
  "End vote": "Termina il voto",
3654
3682
  "Enforce unique vote is enabled": "Il voto unico è abilitato",
3655
3683
  "Error": "Errore",
3656
- "Error · Unsent": "Errore · Non inviato",
3657
3684
  "Error adding flag": "Errore durante l'aggiunta del flag",
3658
3685
  "Error connecting to chat, refresh the page to try again.": "Errore di connessione alla chat, aggiorna la pagina per riprovare.",
3659
3686
  "Error deleting message": "Errore durante l'eliminazione del messaggio",
@@ -3778,8 +3805,6 @@ const itTranslations = {
3778
3805
  "Maximum number of votes (from 2 to 10)": "Numero massimo di voti (da 2 a 10)",
3779
3806
  Menu: Menu$6,
3780
3807
  "Message deleted": "Messaggio cancellato",
3781
- "Message Failed · Click to try again": "Invio messaggio fallito · Clicca per riprovare",
3782
- "Message Failed · Unauthorized": "Invio messaggio fallito · Non autorizzato",
3783
3808
  "Message failed to send": "Invio del messaggio non riuscito",
3784
3809
  "Message has been successfully flagged": "Il messaggio è stato segnalato con successo",
3785
3810
  "Message pinned": "Messaggio bloccato",
@@ -3956,6 +3981,7 @@ const itTranslations = {
3956
3981
  "View translation": "Visualizza traduzione",
3957
3982
  "Voice message": "Messaggio vocale",
3958
3983
  "Voice message {{ duration }}": "Messaggio vocale {{ duration }}",
3984
+ "Voice message deleted": "Messaggio vocale eliminato",
3959
3985
  voiceMessageCount_one: voiceMessageCount_one$4,
3960
3986
  voiceMessageCount_many: voiceMessageCount_many$2,
3961
3987
  voiceMessageCount_other: voiceMessageCount_other$6,
@@ -4180,7 +4206,6 @@ const jaTranslations = {
4180
4206
  "End vote": "投票を終了",
4181
4207
  "Enforce unique vote is enabled": "一意の投票が有効になっています",
4182
4208
  "Error": "エラー",
4183
- "Error · Unsent": "エラー・未送信",
4184
4209
  "Error adding flag": "フラグを追加のエラーが発生しました",
4185
4210
  "Error connecting to chat, refresh the page to try again.": "チャットへの接続ができませんでした。ページを更新してください。",
4186
4211
  "Error deleting message": "メッセージを削除するエラーが発生しました",
@@ -4299,8 +4324,6 @@ const jaTranslations = {
4299
4324
  "Maximum number of votes (from 2 to 10)": "最大投票数(2から10まで)",
4300
4325
  Menu: Menu$5,
4301
4326
  "Message deleted": "メッセージが削除されました",
4302
- "Message Failed · Click to try again": "メッセージが失敗しました · クリックして再試行してください",
4303
- "Message Failed · Unauthorized": "メッセージが失敗しました · 許可されていません",
4304
4327
  "Message failed to send": "メッセージの送信に失敗しました",
4305
4328
  "Message has been successfully flagged": "メッセージに正常にフラグが付けられました",
4306
4329
  "Message pinned": "メッセージにピンが付けられました",
@@ -4467,6 +4490,7 @@ const jaTranslations = {
4467
4490
  "View translation": "翻訳を表示",
4468
4491
  "Voice message": "ボイスメッセージ",
4469
4492
  "Voice message {{ duration }}": "ボイスメッセージ {{ duration }}",
4493
+ "Voice message deleted": "ボイスメッセージが削除されました",
4470
4494
  voiceMessageCount_other: voiceMessageCount_other$5,
4471
4495
  "Vote ended": "投票が終了しました",
4472
4496
  Votes: Votes$5,
@@ -4689,7 +4713,6 @@ const koTranslations = {
4689
4713
  "End vote": "투표 종료",
4690
4714
  "Enforce unique vote is enabled": "고유 투표가 활성화되었습니다",
4691
4715
  "Error": "오류",
4692
- "Error · Unsent": "오류 · 전송되지 않음",
4693
4716
  "Error adding flag": "플래그를 추가하는 동안 오류가 발생했습니다.",
4694
4717
  "Error connecting to chat, refresh the page to try again.": "채팅에 연결하는 동안 오류가 발생했습니다. 페이지를 새로고침하여 다시 시도하세요.",
4695
4718
  "Error deleting message": "메시지를 삭제하는 중에 오류가 발생했습니다.",
@@ -4808,8 +4831,6 @@ const koTranslations = {
4808
4831
  "Maximum number of votes (from 2 to 10)": "최대 투표 수 (2에서 10까지)",
4809
4832
  Menu: Menu$4,
4810
4833
  "Message deleted": "메시지가 삭제되었습니다.",
4811
- "Message Failed · Click to try again": "메시지 실패 · 다시 시도하려면 클릭하세요.",
4812
- "Message Failed · Unauthorized": "메시지 실패 · 승인되지 않음",
4813
4834
  "Message failed to send": "메시지 전송 실패",
4814
4835
  "Message has been successfully flagged": "메시지에 플래그가 지정되었습니다.",
4815
4836
  "Message pinned": "메시지 핀했습니다",
@@ -4976,6 +4997,7 @@ const koTranslations = {
4976
4997
  "View translation": "번역 보기",
4977
4998
  "Voice message": "음성 메시지",
4978
4999
  "Voice message {{ duration }}": "음성 메시지 {{ duration }}",
5000
+ "Voice message deleted": "음성 메시지가 삭제됨",
4979
5001
  voiceMessageCount_other: voiceMessageCount_other$4,
4980
5002
  "Vote ended": "투표 종료",
4981
5003
  Votes: Votes$4,
@@ -5205,7 +5227,6 @@ const nlTranslations = {
5205
5227
  "End vote": "Einde stem",
5206
5228
  "Enforce unique vote is enabled": "Unieke stem is ingeschakeld",
5207
5229
  "Error": "Fout",
5208
- "Error · Unsent": "Fout · niet verzonden",
5209
5230
  "Error adding flag": "Fout bij toevoegen van vlag",
5210
5231
  "Error connecting to chat, refresh the page to try again.": "Fout bij het verbinden, ververs de pagina om nogmaals te proberen",
5211
5232
  "Error deleting message": "Fout bij verwijderen van bericht",
@@ -5327,8 +5348,6 @@ const nlTranslations = {
5327
5348
  "Maximum number of votes (from 2 to 10)": "Maximaal aantal stemmen (van 2 tot 10)",
5328
5349
  Menu: Menu$3,
5329
5350
  "Message deleted": "Bericht verwijderd",
5330
- "Message Failed · Click to try again": "Bericht mislukt, klik om het nogmaals te proberen",
5331
- "Message Failed · Unauthorized": "Bericht mislukt, ongeautoriseerd",
5332
5351
  "Message failed to send": "Bericht kon niet worden verzonden",
5333
5352
  "Message has been successfully flagged": "Bericht is succesvol gemarkeerd",
5334
5353
  "Message pinned": "Bericht vastgezet",
@@ -5500,6 +5519,7 @@ const nlTranslations = {
5500
5519
  "View translation": "Vertaling bekijken",
5501
5520
  "Voice message": "Spraakbericht",
5502
5521
  "Voice message {{ duration }}": "Spraakbericht {{ duration }}",
5522
+ "Voice message deleted": "Spraakbericht verwijderd",
5503
5523
  voiceMessageCount_one: voiceMessageCount_one$3,
5504
5524
  voiceMessageCount_other: voiceMessageCount_other$3,
5505
5525
  "Vote ended": "Stemmen beëindigd",
@@ -5746,7 +5766,6 @@ const ptTranslations = {
5746
5766
  "End vote": "Encerrar votação",
5747
5767
  "Enforce unique vote is enabled": "Voto único está habilitado",
5748
5768
  "Error": "Erro",
5749
- "Error · Unsent": "Erro · Não enviado",
5750
5769
  "Error adding flag": "Erro ao reportar",
5751
5770
  "Error connecting to chat, refresh the page to try again.": "Erro ao conectar ao bate-papo, atualize a página para tentar novamente.",
5752
5771
  "Error deleting message": "Erro ao deletar mensagem",
@@ -5871,8 +5890,6 @@ const ptTranslations = {
5871
5890
  "Maximum number of votes (from 2 to 10)": "Número máximo de votos (de 2 a 10)",
5872
5891
  Menu: Menu$2,
5873
5892
  "Message deleted": "Mensagem apagada",
5874
- "Message Failed · Click to try again": "A mensagem falhou · Clique para tentar novamente",
5875
- "Message Failed · Unauthorized": "A mensagem falhou · não autorizado",
5876
5893
  "Message failed to send": "Falha ao enviar a mensagem",
5877
5894
  "Message has been successfully flagged": "A mensagem foi reportada com sucesso",
5878
5895
  "Message pinned": "Mensagem fixada",
@@ -6049,6 +6066,7 @@ const ptTranslations = {
6049
6066
  "View translation": "Ver tradução",
6050
6067
  "Voice message": "Mensagem de voz",
6051
6068
  "Voice message {{ duration }}": "Mensagem de voz {{ duration }}",
6069
+ "Voice message deleted": "Mensagem de voz excluída",
6052
6070
  voiceMessageCount_one: voiceMessageCount_one$2,
6053
6071
  voiceMessageCount_many: voiceMessageCount_many$1,
6054
6072
  voiceMessageCount_other: voiceMessageCount_other$2,
@@ -6316,7 +6334,6 @@ const ruTranslations = {
6316
6334
  "End vote": "Закончить голосование",
6317
6335
  "Enforce unique vote is enabled": "Уникальное голосование включено",
6318
6336
  "Error": "Ошибка",
6319
- "Error · Unsent": "Ошибка · Не отправлено",
6320
6337
  "Error adding flag": "Ошибка добавления флага",
6321
6338
  "Error connecting to chat, refresh the page to try again.": "Ошибка подключения к чату, обновите страницу чтобы попробовать снова.",
6322
6339
  "Error deleting message": "Ошибка при удалении сообщения",
@@ -6447,8 +6464,6 @@ const ruTranslations = {
6447
6464
  "Maximum number of votes (from 2 to 10)": "Максимальное количество голосов (от 2 до 10)",
6448
6465
  Menu: Menu$1,
6449
6466
  "Message deleted": "Сообщение удалено",
6450
- "Message Failed · Click to try again": "Ошибка отправки сообщения · Нажмите чтобы повторить",
6451
- "Message Failed · Unauthorized": "Ошибка отправки сообщения · Неавторизованный",
6452
6467
  "Message failed to send": "Не удалось отправить сообщение",
6453
6468
  "Message has been successfully flagged": "Жалоба на сообщение была принята",
6454
6469
  "Message pinned": "Сообщение закреплено",
@@ -6632,6 +6647,7 @@ const ruTranslations = {
6632
6647
  "View translation": "Показать перевод",
6633
6648
  "Voice message": "Голосовое сообщение",
6634
6649
  "Voice message {{ duration }}": "Голосовое сообщение {{ duration }}",
6650
+ "Voice message deleted": "Голосовое сообщение удалено",
6635
6651
  voiceMessageCount_one: voiceMessageCount_one$1,
6636
6652
  voiceMessageCount_few,
6637
6653
  voiceMessageCount_many,
@@ -6864,7 +6880,6 @@ const trTranslations = {
6864
6880
  "End vote": "Oyu bitir",
6865
6881
  "Enforce unique vote is enabled": "Benzersiz oy etkinleştirildi",
6866
6882
  "Error": "Hata",
6867
- "Error · Unsent": "Hata · Gönderilemedi",
6868
6883
  "Error adding flag": "Bayrak eklenirken hata oluştu",
6869
6884
  "Error connecting to chat, refresh the page to try again.": "Bağlantı hatası, sayfayı yenileyip tekrar deneyin.",
6870
6885
  "Error deleting message": "Mesaj silinirken hata oluştu",
@@ -6986,8 +7001,6 @@ const trTranslations = {
6986
7001
  "Maximum number of votes (from 2 to 10)": "Maksimum oy sayısı (2 ile 10 arası)",
6987
7002
  Menu,
6988
7003
  "Message deleted": "Mesaj silindi",
6989
- "Message Failed · Click to try again": "Mesaj Başarısız · Tekrar denemek için tıklayın",
6990
- "Message Failed · Unauthorized": "Mesaj Başarısız · Yetkisiz",
6991
7004
  "Message failed to send": "Mesaj gönderilemedi",
6992
7005
  "Message has been successfully flagged": "Mesaj başarıyla bayraklandı",
6993
7006
  "Message pinned": "Mesaj sabitlendi",
@@ -7157,6 +7170,7 @@ const trTranslations = {
7157
7170
  "View translation": "Çeviriyi görüntüle",
7158
7171
  "Voice message": "Sesli mesaj",
7159
7172
  "Voice message {{ duration }}": "Sesli mesaj {{ duration }}",
7173
+ "Voice message deleted": "Sesli mesaj silindi",
7160
7174
  voiceMessageCount_one,
7161
7175
  voiceMessageCount_other,
7162
7176
  "Vote ended": "Oylama sona erdi",
@@ -8586,11 +8600,18 @@ function useActionHandler(message) {
8586
8600
  }
8587
8601
  const useDeleteHandler = (message, notifications = {}) => {
8588
8602
  const { getErrorNotification, notify } = notifications;
8589
- const { deleteMessage, updateMessage } = WithAudioPlayback.useChannelActionContext("useDeleteHandler");
8603
+ const { deleteMessage, removeMessage, updateMessage } = WithAudioPlayback.useChannelActionContext("useDeleteHandler");
8590
8604
  const { client } = WithAudioPlayback.useChatContext("useDeleteHandler");
8591
8605
  const { t } = WithAudioPlayback.useTranslationContext("useDeleteHandler");
8592
8606
  return async (options) => {
8593
- if (!message?.id || !client || !updateMessage) {
8607
+ if (!message) {
8608
+ return;
8609
+ }
8610
+ if (message.type === "error" || WithAudioPlayback.isNetworkSendFailure(message)) {
8611
+ removeMessage?.(message);
8612
+ return;
8613
+ }
8614
+ if (!message.id || !client || !updateMessage) {
8594
8615
  return;
8595
8616
  }
8596
8617
  try {
@@ -8599,6 +8620,7 @@ const useDeleteHandler = (message, notifications = {}) => {
8599
8620
  } catch (e) {
8600
8621
  const errorMessage = getErrorNotification && WithAudioPlayback.validateAndGetMessage(getErrorNotification, [message]);
8601
8622
  if (notify) notify(errorMessage || t("Error deleting message"), "error");
8623
+ throw e;
8602
8624
  }
8603
8625
  };
8604
8626
  };
@@ -8910,7 +8932,7 @@ const useUserRole = (message, onlySenderCanEdit, disableQuotedMessages) => {
8910
8932
  const canFlag = !isMyMessage && channelCapabilities["flag-message"];
8911
8933
  const canMute = !isMyMessage && channelCapabilities["mute-channel"];
8912
8934
  const canBlockUser = !isMyMessage;
8913
- const canMarkUnread = channelCapabilities["read-events"];
8935
+ const canMarkUnread = !isMyMessage && channelCapabilities["read-events"];
8914
8936
  const canQuote = !disableQuotedMessages && channelCapabilities["quote-message"];
8915
8937
  const canReact = channelCapabilities["send-reaction"];
8916
8938
  const canReply = channelCapabilities["send-reply"];
@@ -9007,73 +9029,6 @@ const useMessageReminder = (messageId) => {
9007
9029
  const { reminder } = WithAudioPlayback.useStateStore(client.reminders.state, reminderSelector);
9008
9030
  return reminder;
9009
9031
  };
9010
- const ActionsIcon = ({ className = "" }) => /* @__PURE__ */ jsxRuntime.jsx(
9011
- "svg",
9012
- {
9013
- className,
9014
- height: "4",
9015
- viewBox: "0 0 11 4",
9016
- width: "11",
9017
- xmlns: "http://www.w3.org/2000/svg",
9018
- children: /* @__PURE__ */ jsxRuntime.jsx(
9019
- "path",
9020
- {
9021
- d: "M1.5 3a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm4 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm4 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3z",
9022
- fillRule: "nonzero"
9023
- }
9024
- )
9025
- }
9026
- );
9027
- const ReactionIcon = ({ className = "" }) => /* @__PURE__ */ jsxRuntime.jsx(
9028
- "svg",
9029
- {
9030
- className,
9031
- height: "12",
9032
- viewBox: "0 0 12 12",
9033
- width: "12",
9034
- xmlns: "http://www.w3.org/2000/svg",
9035
- children: /* @__PURE__ */ jsxRuntime.jsxs("g", { clipRule: "evenodd", fillRule: "evenodd", children: [
9036
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6 1.2C3.3 1.2 1.2 3.3 1.2 6c0 2.7 2.1 4.8 4.8 4.8 2.7 0 4.8-2.1 4.8-4.8 0-2.7-2.1-4.8-4.8-4.8zM0 6c0-3.3 2.7-6 6-6s6 2.7 6 6-2.7 6-6 6-6-2.7-6-6z" }),
9037
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M5.4 4.5c0 .5-.4.9-.9.9s-.9-.4-.9-.9.4-.9.9-.9.9.4.9.9zM8.4 4.5c0 .5-.4.9-.9.9s-.9-.4-.9-.9.4-.9.9-.9.9.4.9.9zM3.3 6.7c.3-.2.6-.1.8.1.3.4.8.9 1.5 1 .6.2 1.4.1 2.4-1 .2-.2.6-.3.8 0 .2.2.3.6 0 .8-1.1 1.3-2.4 1.7-3.5 1.5-1-.2-1.8-.9-2.2-1.5-.2-.3-.1-.7.2-.9z" })
9038
- ] })
9039
- }
9040
- );
9041
- const ThreadIcon = ({ className = "" }) => /* @__PURE__ */ jsxRuntime.jsx("svg", { className, height: "10", width: "14", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ jsxRuntime.jsx(
9042
- "path",
9043
- {
9044
- d: "M8.516 3c4.78 0 4.972 6.5 4.972 6.5-1.6-2.906-2.847-3.184-4.972-3.184v2.872L3.772 4.994 8.516.5V3zM.484 5l4.5-4.237v1.78L2.416 5l2.568 2.125v1.828L.484 5z",
9045
- fillRule: "evenodd"
9046
- }
9047
- ) });
9048
- const PinIcon = () => /* @__PURE__ */ jsxRuntime.jsx("svg", { height: "13", viewBox: "0 0 14 13", width: "14", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ jsxRuntime.jsx(
9049
- "path",
9050
- {
9051
- d: "M13.3518 6.686L6.75251 0.0866699L5.80984 1.02867L6.75318 1.972V1.97334L3.45318 5.272L3.45251 5.27334L2.50984 4.32934L1.56718 5.27267L4.39584 8.10067L0.624512 11.8713L1.56718 12.814L5.33851 9.04334L8.16718 11.8713L9.10984 10.9293L8.16718 9.986L11.4672 6.686L12.4098 7.62867L13.3518 6.686ZM7.22451 9.04267L7.22385 9.04334L4.39584 6.21467L7.69518 2.91467L10.5232 5.74267L7.22451 9.04267Z",
9052
- fillRule: "evenodd"
9053
- }
9054
- ) });
9055
- const MessageErrorIcon = () => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__message-error-icon", children: /* @__PURE__ */ jsxRuntime.jsxs(
9056
- "svg",
9057
- {
9058
- "data-testid": "error",
9059
- fill: "none",
9060
- height: "24",
9061
- viewBox: "0 0 24 24",
9062
- width: "24",
9063
- xmlns: "http://www.w3.org/2000/svg",
9064
- children: [
9065
- /* @__PURE__ */ jsxRuntime.jsx(
9066
- "path",
9067
- {
9068
- d: "M12 2C6.48 2 2 6.48 2 12C2 17.52 6.48 22 12 22C17.52 22 22 17.52 22 12C22 6.48 17.52 2 12 2Z",
9069
- fill: "black",
9070
- id: "background"
9071
- }
9072
- ),
9073
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M13 17H11V15H13V17ZM13 13H11V7H13V13Z", fill: "white" })
9074
- ]
9075
- }
9076
- ) });
9077
9032
  const Root = React.forwardRef(function AlertRoot({ children, className, ...props }, ref) {
9078
9033
  return /* @__PURE__ */ jsxRuntime.jsx("div", { ...props, className: clsx("str-chat__alert-root", className), ref, children });
9079
9034
  });
@@ -9154,6 +9109,7 @@ function useDialogAnchor({
9154
9109
  setPopperElement(null);
9155
9110
  }
9156
9111
  return {
9112
+ placement: stabilisedChosenPlacement ?? chosenPlacement,
9157
9113
  setPopperElement,
9158
9114
  styles: {
9159
9115
  left: x ?? 0,
@@ -9166,6 +9122,8 @@ const DialogAnchor = ({
9166
9122
  allowFlip = true,
9167
9123
  children,
9168
9124
  className,
9125
+ closeOnClickOutside,
9126
+ closeTransitionMs = 0,
9169
9127
  dialogManagerId,
9170
9128
  focus: focus$1 = true,
9171
9129
  id,
@@ -9178,12 +9136,44 @@ const DialogAnchor = ({
9178
9136
  updatePositionOnContentResize,
9179
9137
  ...restDivProps
9180
9138
  }) => {
9181
- const dialog = useDialog({ dialogManagerId, id });
9139
+ const dialog = useDialog({ closeOnClickOutside, dialogManagerId, id });
9182
9140
  const open = useDialogIsOpen(id, dialogManagerId);
9183
- const { setPopperElement, styles } = useDialogAnchor({
9141
+ const [shouldRender, setShouldRender] = React.useState(open);
9142
+ const closeTimeoutRef = React.useRef(null);
9143
+ const isClosing = !open && shouldRender;
9144
+ React.useEffect(() => {
9145
+ if (open) {
9146
+ setShouldRender(true);
9147
+ if (closeTimeoutRef.current) {
9148
+ clearTimeout(closeTimeoutRef.current);
9149
+ closeTimeoutRef.current = null;
9150
+ }
9151
+ return;
9152
+ }
9153
+ if (!shouldRender) return;
9154
+ if (!closeTransitionMs) {
9155
+ setShouldRender(false);
9156
+ return;
9157
+ }
9158
+ closeTimeoutRef.current = setTimeout(() => {
9159
+ setShouldRender(false);
9160
+ closeTimeoutRef.current = null;
9161
+ }, closeTransitionMs);
9162
+ }, [closeTransitionMs, open, shouldRender]);
9163
+ React.useEffect(
9164
+ () => () => {
9165
+ if (closeTimeoutRef.current) clearTimeout(closeTimeoutRef.current);
9166
+ },
9167
+ []
9168
+ );
9169
+ const {
9170
+ placement: chosenPlacement,
9171
+ setPopperElement,
9172
+ styles
9173
+ } = useDialogAnchor({
9184
9174
  allowFlip,
9185
9175
  offset,
9186
- open,
9176
+ open: shouldRender,
9187
9177
  placement,
9188
9178
  referenceElement,
9189
9179
  updateKey,
@@ -9200,7 +9190,7 @@ const DialogAnchor = ({
9200
9190
  document.removeEventListener("keyup", hideOnEscape);
9201
9191
  };
9202
9192
  }, [dialog, open]);
9203
- if (!open) {
9193
+ if (!shouldRender) {
9204
9194
  return null;
9205
9195
  }
9206
9196
  return /* @__PURE__ */ jsxRuntime.jsx(DialogPortalEntry, { dialogId: id, dialogManagerId, children: /* @__PURE__ */ jsxRuntime.jsx(focus.FocusScope, { autoFocus: focus$1, contain: trapFocus, restoreFocus: true, children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -9208,6 +9198,8 @@ const DialogAnchor = ({
9208
9198
  {
9209
9199
  ...restDivProps,
9210
9200
  className: clsx("str-chat__dialog-contents", className),
9201
+ "data-str-chat-dialog-state": isClosing ? "closing" : "open",
9202
+ "data-str-chat-placement": chosenPlacement,
9211
9203
  "data-testid": "str-chat__dialog-contents",
9212
9204
  ref: setPopperElement,
9213
9205
  style: styles,
@@ -9334,6 +9326,11 @@ const Badge = ({
9334
9326
  children
9335
9327
  }
9336
9328
  );
9329
+ const ErrorBadge = ({
9330
+ className,
9331
+ size = "sm",
9332
+ ...rest
9333
+ }) => /* @__PURE__ */ jsxRuntime.jsx(Badge, { ...rest, className, size, variant: "error", children: /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconExclamation, {}) });
9337
9334
  function AvatarStack({
9338
9335
  badgeSize,
9339
9336
  component: Component = "div",
@@ -9545,16 +9542,152 @@ const EmojiContextMenuButton = ({
9545
9542
  ]
9546
9543
  }
9547
9544
  );
9548
- const ContextMenuButton = ({
9549
- onBlur,
9550
- onFocus,
9551
- ...props
9545
+ const ContextMenuButtonWithSubmenu = ({
9546
+ children,
9547
+ className,
9548
+ Submenu,
9549
+ submenuContainerProps,
9550
+ submenuPlacement = "right-start",
9551
+ submenuRollAxis = "x",
9552
+ ...buttonProps
9552
9553
  }) => {
9554
+ const { className: submenuClassName, ...submenuContainerRestProps } = submenuContainerProps ?? {};
9555
+ const buttonRef = React.useRef(null);
9556
+ const [dialogContainer, setDialogContainer] = React.useState(null);
9557
+ const keepSubmenuOpenFlag = React.useRef(false);
9558
+ const dialogCloseTimeout = React.useRef(null);
9559
+ const dialogId2 = React.useMemo(() => `submenu-${Math.random().toString(36).slice(2)}`, []);
9560
+ const { dialog, dialogManager } = useDialogOnNearestManager({ id: dialogId2 });
9561
+ const dialogIsOpen = useDialogIsOpen(dialogId2, dialogManager?.id);
9562
+ const {
9563
+ placement: chosenPlacement,
9564
+ setPopperElement,
9565
+ styles
9566
+ } = useDialogAnchor({
9567
+ open: dialogIsOpen,
9568
+ placement: submenuPlacement,
9569
+ referenceElement: buttonRef.current
9570
+ });
9571
+ const closeDialogLazily = React.useCallback(() => {
9572
+ if (dialogCloseTimeout.current) clearTimeout(dialogCloseTimeout.current);
9573
+ dialogCloseTimeout.current = setTimeout(() => {
9574
+ if (keepSubmenuOpenFlag.current) return;
9575
+ dialog.close();
9576
+ }, 100);
9577
+ }, [dialog]);
9578
+ const keepSubmenuOpen = React.useCallback(() => {
9579
+ keepSubmenuOpenFlag.current = true;
9580
+ }, []);
9581
+ const allowToCloseSubmenu = React.useCallback(() => {
9582
+ keepSubmenuOpenFlag.current = false;
9583
+ }, []);
9584
+ const closeSubmenu = React.useCallback(() => {
9585
+ allowToCloseSubmenu();
9586
+ closeDialogLazily();
9587
+ }, [allowToCloseSubmenu, closeDialogLazily]);
9588
+ const handleClose = React.useCallback(
9589
+ (event) => {
9590
+ const parentButton = buttonRef.current;
9591
+ if (!dialogIsOpen || !parentButton) return;
9592
+ event.stopPropagation();
9593
+ closeDialogLazily();
9594
+ parentButton.focus();
9595
+ },
9596
+ [closeDialogLazily, dialogIsOpen, buttonRef]
9597
+ );
9598
+ const handleFocusParentButton = () => {
9599
+ if (dialogIsOpen) return;
9600
+ dialog.open();
9601
+ keepSubmenuOpen();
9602
+ };
9603
+ React.useEffect(() => {
9604
+ const parentButton = buttonRef.current;
9605
+ if (!dialogIsOpen || !parentButton) return;
9606
+ const hideOnEscape = (event) => {
9607
+ if (event.key !== "Escape") return;
9608
+ handleClose(event);
9609
+ closeSubmenu();
9610
+ };
9611
+ document.addEventListener("keyup", hideOnEscape, { capture: true });
9612
+ return () => {
9613
+ document.removeEventListener("keyup", hideOnEscape, { capture: true });
9614
+ };
9615
+ }, [dialogIsOpen, handleClose, closeSubmenu]);
9616
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
9617
+ /* @__PURE__ */ jsxRuntime.jsx(
9618
+ BaseContextMenuButton,
9619
+ {
9620
+ "aria-selected": "false",
9621
+ className: clsx(className, "str_chat__button-with-submenu", {
9622
+ "str_chat__button-with-submenu--submenu-open": dialogIsOpen
9623
+ }),
9624
+ hasSubMenu: true,
9625
+ onBlur: closeSubmenu,
9626
+ onClick: (event) => {
9627
+ event.stopPropagation();
9628
+ dialog.toggle();
9629
+ },
9630
+ onFocus: handleFocusParentButton,
9631
+ onMouseEnter: handleFocusParentButton,
9632
+ onMouseLeave: closeSubmenu,
9633
+ role: "option",
9634
+ ...buttonProps,
9635
+ ref: buttonRef,
9636
+ children
9637
+ }
9638
+ ),
9639
+ dialogIsOpen && /* @__PURE__ */ jsxRuntime.jsx(
9640
+ "div",
9641
+ {
9642
+ className: clsx("str-chat__context-menu__submenu-container", submenuClassName),
9643
+ "data-str-chat-placement": chosenPlacement,
9644
+ "data-str-chat-roll-axis": submenuRollAxis,
9645
+ onBlur: (event) => {
9646
+ const isBlurredDescendant = event.relatedTarget instanceof Node && dialogContainer?.contains(event.relatedTarget);
9647
+ if (isBlurredDescendant) return;
9648
+ closeSubmenu();
9649
+ },
9650
+ onFocus: keepSubmenuOpen,
9651
+ onMouseEnter: keepSubmenuOpen,
9652
+ onMouseLeave: closeSubmenu,
9653
+ ref: (element) => {
9654
+ setPopperElement(element);
9655
+ setDialogContainer(element);
9656
+ },
9657
+ style: styles,
9658
+ tabIndex: -1,
9659
+ ...submenuContainerRestProps,
9660
+ children: /* @__PURE__ */ jsxRuntime.jsx(Submenu, {})
9661
+ }
9662
+ )
9663
+ ] });
9664
+ };
9665
+ const ContextMenuButton = (props) => {
9666
+ const {
9667
+ Submenu,
9668
+ submenuContainerProps,
9669
+ submenuPlacement,
9670
+ submenuRollAxis,
9671
+ ...buttonProps
9672
+ } = props;
9553
9673
  const [isFocused, setIsFocused] = React.useState(false);
9674
+ if (Submenu) {
9675
+ return /* @__PURE__ */ jsxRuntime.jsx(
9676
+ ContextMenuButtonWithSubmenu,
9677
+ {
9678
+ ...buttonProps,
9679
+ Submenu,
9680
+ submenuContainerProps,
9681
+ submenuPlacement,
9682
+ submenuRollAxis
9683
+ }
9684
+ );
9685
+ }
9686
+ const { onBlur, onFocus, ...baseButtonProps } = buttonProps;
9554
9687
  return /* @__PURE__ */ jsxRuntime.jsx(
9555
9688
  BaseContextMenuButton,
9556
9689
  {
9557
- ...props,
9690
+ ...baseButtonProps,
9558
9691
  "aria-selected": isFocused ? "true" : "false",
9559
9692
  onBlur: (e) => {
9560
9693
  setIsFocused(false);
@@ -9603,12 +9736,14 @@ function ContextMenuContent({
9603
9736
  backLabel = "Back",
9604
9737
  children,
9605
9738
  className,
9739
+ enableAnimations = true,
9606
9740
  Header: Header2,
9607
9741
  items,
9608
9742
  ItemsWrapper,
9609
9743
  menuClassName,
9610
9744
  onClose,
9611
9745
  onMenuLevelChange,
9746
+ transitionDirection,
9612
9747
  ...props
9613
9748
  }) {
9614
9749
  const rootLevel = React.useMemo(
@@ -9621,6 +9756,7 @@ function ContextMenuContent({
9621
9756
  [Header2, items, ItemsWrapper, menuClassName]
9622
9757
  );
9623
9758
  const [menuStack, setMenuStack] = React.useState(() => [rootLevel]);
9759
+ const [menuBodyAnimationKey, setMenuBodyAnimationKey] = React.useState(0);
9624
9760
  const activeMenu = menuStack[menuStack.length - 1];
9625
9761
  const ActiveMenuItemsWrapper = activeMenu.ItemsWrapper ?? React.Fragment;
9626
9762
  const closeMenu = React.useCallback(() => {
@@ -9644,10 +9780,9 @@ function ContextMenuContent({
9644
9780
  [ItemsWrapper]
9645
9781
  );
9646
9782
  const returnToParentMenu = React.useCallback(() => {
9647
- setMenuStack(
9648
- (current) => current.length > 1 ? current.slice(0, current.length - 1) : current
9649
- );
9650
- }, []);
9783
+ if (menuStack.length <= 1) return;
9784
+ setMenuStack((current) => current.slice(0, current.length - 1));
9785
+ }, [menuStack.length]);
9651
9786
  React.useEffect(() => {
9652
9787
  setMenuStack((current) => {
9653
9788
  if (current.length === 1 && current[0] === rootLevel) return current;
@@ -9657,42 +9792,116 @@ function ContextMenuContent({
9657
9792
  React.useEffect(() => {
9658
9793
  onMenuLevelChange?.(menuStack.length);
9659
9794
  }, [menuStack.length, onMenuLevelChange]);
9660
- return /* @__PURE__ */ jsxRuntime.jsx(ContextMenuContext.Provider, { value: { closeMenu, openSubmenu, returnToParentMenu }, children: /* @__PURE__ */ jsxRuntime.jsxs(ContextMenuRoot, { className: clsx(className, activeMenu.menuClassName), ...props, children: [
9661
- activeMenu.Header ? /* @__PURE__ */ jsxRuntime.jsx(activeMenu.Header, {}) : menuStack.length > 1 ? /* @__PURE__ */ jsxRuntime.jsx(ContextMenuHeader, { children: /* @__PURE__ */ jsxRuntime.jsxs(ContextMenuBackButton, { onClick: returnToParentMenu, children: [
9662
- /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconChevronLeft, {}),
9663
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: backLabel })
9664
- ] }) }) : null,
9665
- /* @__PURE__ */ jsxRuntime.jsx(ContextMenuBody, { children: activeMenu.Submenu ? /* @__PURE__ */ jsxRuntime.jsx(activeMenu.Submenu, {}) : /* @__PURE__ */ jsxRuntime.jsx(ActiveMenuItemsWrapper, { children: typeof children !== "undefined" ? children : activeMenu.items?.map((Item2, index) => /* @__PURE__ */ jsxRuntime.jsx(Item2, {}, `context-menu-item-${index}`)) }) })
9666
- ] }) });
9795
+ React.useEffect(() => {
9796
+ if (!transitionDirection) return;
9797
+ setMenuBodyAnimationKey((value) => value + 1);
9798
+ }, [transitionDirection, menuStack.length]);
9799
+ return /* @__PURE__ */ jsxRuntime.jsx(ContextMenuContext.Provider, { value: { closeMenu, openSubmenu, returnToParentMenu }, children: /* @__PURE__ */ jsxRuntime.jsxs(
9800
+ ContextMenuRoot,
9801
+ {
9802
+ className: clsx(className, activeMenu.menuClassName),
9803
+ "data-str-chat-enable-animations": enableAnimations,
9804
+ ...props,
9805
+ children: [
9806
+ activeMenu.Header ? /* @__PURE__ */ jsxRuntime.jsx(activeMenu.Header, {}) : menuStack.length > 1 ? /* @__PURE__ */ jsxRuntime.jsx(ContextMenuHeader, { children: /* @__PURE__ */ jsxRuntime.jsxs(ContextMenuBackButton, { onClick: returnToParentMenu, children: [
9807
+ /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconChevronLeft, {}),
9808
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: backLabel })
9809
+ ] }) }) : null,
9810
+ /* @__PURE__ */ jsxRuntime.jsx(
9811
+ ContextMenuBody,
9812
+ {
9813
+ className: clsx({
9814
+ "str-chat__context-menu__body--submenu-backward": transitionDirection === "backward",
9815
+ "str-chat__context-menu__body--submenu-forward": transitionDirection === "forward"
9816
+ }),
9817
+ children: activeMenu.Submenu ? /* @__PURE__ */ jsxRuntime.jsx(activeMenu.Submenu, {}) : /* @__PURE__ */ jsxRuntime.jsx(ActiveMenuItemsWrapper, { children: typeof children !== "undefined" ? children : activeMenu.items?.map((Item2, index) => /* @__PURE__ */ jsxRuntime.jsx(Item2, {}, `context-menu-item-${index}`)) })
9818
+ },
9819
+ `context-menu-body-${menuStack.length}-${menuBodyAnimationKey}`
9820
+ )
9821
+ ]
9822
+ }
9823
+ ) });
9667
9824
  }
9668
9825
  const ContextMenu = (props) => {
9826
+ const { ContextMenuContent: ContextMenuContentComponent = ContextMenuContent } = WithAudioPlayback.useComponentContext();
9669
9827
  const {
9670
9828
  allowFlip,
9829
+ closeOnClickOutside,
9830
+ closeTransitionMs = 130,
9671
9831
  dialogManagerId,
9672
9832
  focus: focus2,
9673
9833
  id,
9674
9834
  placement,
9675
9835
  referenceElement,
9836
+ submenuTransitionDurationMs,
9676
9837
  tabIndex,
9677
9838
  trapFocus,
9678
9839
  ...menuProps
9679
9840
  } = props;
9841
+ const resolvedSubmenuTransitionDurationMs = submenuTransitionDurationMs ?? 460;
9680
9842
  const isAnchored = id != null;
9681
9843
  const [menuLevel, setMenuLevel] = React.useState(1);
9844
+ const [transitionDirection, setTransitionDirection] = React.useState(void 0);
9845
+ const [contentResetToken, setContentResetToken] = React.useState(0);
9846
+ const transitionTimeoutRef = React.useRef(null);
9847
+ const previousMenuLevelRef = React.useRef(1);
9682
9848
  const open = useDialogIsOpen(id ?? "", dialogManagerId);
9849
+ const previousOpenRef = React.useRef(open);
9683
9850
  React.useEffect(() => {
9684
- if (isAnchored && !open) setMenuLevel(1);
9851
+ if (!isAnchored) return;
9852
+ if (previousOpenRef.current && !open) {
9853
+ setMenuLevel(1);
9854
+ setTransitionDirection(void 0);
9855
+ setContentResetToken((value) => value + 1);
9856
+ previousMenuLevelRef.current = 1;
9857
+ if (transitionTimeoutRef.current) {
9858
+ clearTimeout(transitionTimeoutRef.current);
9859
+ transitionTimeoutRef.current = null;
9860
+ }
9861
+ }
9862
+ previousOpenRef.current = open;
9685
9863
  }, [isAnchored, open]);
9686
- const content = /* @__PURE__ */ jsxRuntime.jsx(
9687
- ContextMenuContent,
9864
+ React.useEffect(
9865
+ () => () => {
9866
+ if (transitionTimeoutRef.current) {
9867
+ clearTimeout(transitionTimeoutRef.current);
9868
+ }
9869
+ },
9870
+ []
9871
+ );
9872
+ const handleMenuLevelChange = React.useCallback(
9873
+ (level) => {
9874
+ if (isAnchored) {
9875
+ const previousLevel = previousMenuLevelRef.current;
9876
+ if (level !== previousLevel) {
9877
+ setTransitionDirection(level > previousLevel ? "forward" : "backward");
9878
+ if (transitionTimeoutRef.current) clearTimeout(transitionTimeoutRef.current);
9879
+ transitionTimeoutRef.current = setTimeout(() => {
9880
+ setTransitionDirection(void 0);
9881
+ transitionTimeoutRef.current = null;
9882
+ }, resolvedSubmenuTransitionDurationMs);
9883
+ }
9884
+ previousMenuLevelRef.current = level;
9885
+ setMenuLevel(level);
9886
+ return;
9887
+ }
9888
+ menuProps.onMenuLevelChange?.(level);
9889
+ },
9890
+ [isAnchored, menuProps, resolvedSubmenuTransitionDurationMs]
9891
+ );
9892
+ const content = /* @__PURE__ */ React.createElement(
9893
+ ContextMenuContentComponent,
9688
9894
  {
9689
9895
  ...menuProps,
9690
- onMenuLevelChange: isAnchored ? setMenuLevel : menuProps.onMenuLevelChange
9896
+ key: `context-menu-content-${contentResetToken}`,
9897
+ onMenuLevelChange: handleMenuLevelChange,
9898
+ transitionDirection
9691
9899
  }
9692
9900
  );
9693
9901
  if (isAnchored) {
9694
9902
  const {
9695
9903
  backLabel: _b,
9904
+ enableAnimations: _ea,
9696
9905
  Header: _h,
9697
9906
  items: _i,
9698
9907
  ItemsWrapper: _w,
@@ -9705,6 +9914,8 @@ const ContextMenu = (props) => {
9705
9914
  DialogAnchor,
9706
9915
  {
9707
9916
  allowFlip,
9917
+ closeOnClickOutside,
9918
+ closeTransitionMs,
9708
9919
  dialogManagerId,
9709
9920
  focus: focus2,
9710
9921
  id,
@@ -9858,9 +10069,9 @@ const GlobalModal = ({
9858
10069
  (source, event) => {
9859
10070
  const allow = onCloseAttempt?.(source, event);
9860
10071
  if (allow !== false) {
9861
- onClose?.(event);
9862
10072
  dialog.close();
9863
10073
  closingRef.current = true;
10074
+ onClose?.(event);
9864
10075
  }
9865
10076
  },
9866
10077
  [dialog, onClose, onCloseAttempt]
@@ -10036,16 +10247,19 @@ const useBaseMessageActionSetFilter = (messageActionSet, disable = false) => {
10036
10247
  const isMessageThreadReply = typeof message.parent_id === "string";
10037
10248
  const isBounced = WithAudioPlayback.isMessageBounced(message);
10038
10249
  const allowRetry = WithAudioPlayback.isMessageErrorRetryable(message);
10250
+ const hasNetworkSendFailure = WithAudioPlayback.isNetworkSendFailure(message);
10039
10251
  return React.useMemo(() => {
10040
10252
  if (disable) return messageActionSet;
10041
10253
  if (isBounced || isInitialMessage || // not sure whether this thing even works anymore
10042
10254
  !message.type || message.type === "system" || message.type === "ephemeral" || message.status === "sending")
10043
10255
  return [];
10044
- return messageActionSet.filter(({ type }) => {
10256
+ return messageActionSet.filter((action) => {
10257
+ if (action.placement === "quick-dropdown-toggle") return true;
10258
+ const type = action.type;
10045
10259
  if (WithAudioPlayback.ACTIONS_NOT_WORKING_IN_THREAD.includes(type) && isMessageThreadReply)
10046
10260
  return false;
10047
10261
  if (message.error) {
10048
- return type === "resendMessage" && canSendMessage && (allowRetry || isBounced) || type === "edit" && canEdit && isBounced || type === "delete" && canDelete && isBounced;
10262
+ return type === "resendMessage" && canSendMessage && (allowRetry || isBounced) || type === "edit" && (isBounced && canEdit || hasNetworkSendFailure) || type === "delete" && (isBounced && canDelete || hasNetworkSendFailure);
10049
10263
  }
10050
10264
  if (type === "resendMessage" || type === "blockUser" && !canBlockUser || type === "copyMessageText" && !message.text || type === "delete" && !canDelete || type === "edit" && !canEdit || type === "flag" && !canFlag || type === "markUnread" && !canMarkUnread || type === "mute" && !canMute || type === "quote" && !canQuote || type === "react" && !canReact || type === "reply" && !canReply || type === "remindMe" && !channelConfig?.["user_message_reminders"] || type === "saveForLater" && !channelConfig?.["user_message_reminders"])
10051
10265
  return false;
@@ -10072,6 +10286,7 @@ const useBaseMessageActionSetFilter = (messageActionSet, disable = false) => {
10072
10286
  message.text,
10073
10287
  message.type,
10074
10288
  disable,
10289
+ hasNetworkSendFailure,
10075
10290
  messageActionSet
10076
10291
  ]);
10077
10292
  };
@@ -10696,17 +10911,6 @@ const renderText = (text2, mentionedUsers, {
10696
10911
  }
10697
10912
  ) });
10698
10913
  };
10699
- const ROOT_CLASS_NAME = "str-chat__message--error-message";
10700
- function MessageErrorText({ message }) {
10701
- const { t } = WithAudioPlayback.useTranslationContext("MessageText");
10702
- if (message.type === "error" && !WithAudioPlayback.isMessageBounced(message)) {
10703
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: ROOT_CLASS_NAME, children: t("Error · Unsent") });
10704
- }
10705
- if (message.status === "failed") {
10706
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: ROOT_CLASS_NAME, children: message.error?.status !== 403 ? t("Message Failed · Click to try again") : t("Message Failed · Unauthorized") });
10707
- }
10708
- return null;
10709
- }
10710
10914
  const UnMemoizedMessageTextComponent = (props) => {
10711
10915
  const {
10712
10916
  customInnerClass,
@@ -10734,7 +10938,7 @@ const UnMemoizedMessageTextComponent = (props) => {
10734
10938
  const wrapperClass = customWrapperClass || "str-chat__message-text";
10735
10939
  const innerClass = customInnerClass;
10736
10940
  if (!messageTextToRender) return null;
10737
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: wrapperClass, tabIndex: 0, children: /* @__PURE__ */ jsxRuntime.jsxs(
10941
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: wrapperClass, tabIndex: 0, children: /* @__PURE__ */ jsxRuntime.jsx(
10738
10942
  "div",
10739
10943
  {
10740
10944
  className: clsx(innerClass, {
@@ -10744,10 +10948,7 @@ const UnMemoizedMessageTextComponent = (props) => {
10744
10948
  "data-testid": "message-text-inner-wrapper",
10745
10949
  onClick: onMentionsClickMessage,
10746
10950
  onMouseOver: onMentionsHoverMessage,
10747
- children: [
10748
- /* @__PURE__ */ jsxRuntime.jsx(MessageErrorText, { message }),
10749
- unsafeHTML && message.html ? /* @__PURE__ */ jsxRuntime.jsx("div", { dangerouslySetInnerHTML: { __html: message.html } }) : /* @__PURE__ */ jsxRuntime.jsx("div", { children: messageText })
10750
- ]
10951
+ children: unsafeHTML && message.html ? /* @__PURE__ */ jsxRuntime.jsx("div", { dangerouslySetInnerHTML: { __html: message.html } }) : /* @__PURE__ */ jsxRuntime.jsx("div", { children: messageText })
10751
10952
  }
10752
10953
  ) });
10753
10954
  };
@@ -11962,6 +12163,7 @@ const toBaseImageDescriptors = (attachment, options = {}) => {
11962
12163
  title: attachment.title
11963
12164
  };
11964
12165
  }
12166
+ return void 0;
11965
12167
  };
11966
12168
  const BASE_FILE_ICON_CLASSNAME = "str-chat__file-icon";
11967
12169
  const FILE_ICON_GRAPHIC_CLASSNAME = "str-chat__file-icon__graphic";
@@ -14799,7 +15001,8 @@ const PollOptionWithVotes = ({
14799
15001
  "div",
14800
15002
  {
14801
15003
  className: clsx("str-chat__poll-option", {
14802
- "str-chat__poll-option--has-more-votes": isVotesPreview && voteCount > countVotesPreview
15004
+ "str-chat__poll-option--has-more-votes": isVotesPreview && voteCount > countVotesPreview,
15005
+ "str-chat__poll-option--has-votes": voteCount
14803
15006
  }),
14804
15007
  children: [
14805
15008
  /* @__PURE__ */ jsxRuntime.jsx(PollOptionWithVotesHeader, { option, optionOrderNumber: orderNumber }),
@@ -15093,10 +15296,12 @@ const PollOptionSelector = ({
15093
15296
  canCastVote && /* @__PURE__ */ jsxRuntime.jsx(Checkmark, { checked: !!ownVotesByOptionId[option.id] }),
15094
15297
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__poll-option-data", children: [
15095
15298
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "str-chat__poll-option-text", children: option.text }),
15096
- displayAvatarCount && voting_visibility === "public" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__poll-option-voters", children: /* @__PURE__ */ jsxRuntime.jsx(AvatarStack$1, { displayInfo: avatarDisplayInfo, size: "xs" }) }),
15097
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__poll-option-vote-count", children: voteCountVerbose ? t("{{count}} votes", {
15098
- count: vote_counts_by_option[option.id] ?? 0
15099
- }) : vote_counts_by_option[option.id] ?? 0 })
15299
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__poll-option-votes", children: [
15300
+ displayAvatarCount && voting_visibility === "public" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__poll-option-voters", children: /* @__PURE__ */ jsxRuntime.jsx(AvatarStack$1, { displayInfo: avatarDisplayInfo, size: "xs" }) }),
15301
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__poll-option-vote-count", children: voteCountVerbose ? t("{{count}} votes", {
15302
+ count: vote_counts_by_option[option.id] ?? 0
15303
+ }) : vote_counts_by_option[option.id] ?? 0 })
15304
+ ] })
15100
15305
  ] }),
15101
15306
  /* @__PURE__ */ jsxRuntime.jsx(
15102
15307
  AmountBar,
@@ -15611,31 +15816,34 @@ const CommandContextMenuItem = ({
15611
15816
  }
15612
15817
  );
15613
15818
  };
15614
- const AttachmentSelectorMenuInitButtonIcon = () => {
15819
+ const AttachmentSelectorMenuInitButtonIcon = ({ className }) => {
15615
15820
  const { AttachmentSelectorInitiationButtonContents } = WithAudioPlayback.useComponentContext();
15616
15821
  if (AttachmentSelectorInitiationButtonContents) {
15617
- return /* @__PURE__ */ jsxRuntime.jsx(AttachmentSelectorInitiationButtonContents, {});
15822
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className, children: /* @__PURE__ */ jsxRuntime.jsx(AttachmentSelectorInitiationButtonContents, {}) });
15618
15823
  }
15619
- return /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconPlusLarge, { className: "str-chat__attachment-selector__menu-button__icon" });
15824
+ return /* @__PURE__ */ jsxRuntime.jsx(
15825
+ WithAudioPlayback.IconPlusLarge,
15826
+ {
15827
+ className: clsx("str-chat__attachment-selector__menu-button__icon", className)
15828
+ }
15829
+ );
15620
15830
  };
15621
- const AttachmentSelectorButton = React.forwardRef(
15622
- function AttachmentSelectorButton2({ className, ...props }, ref) {
15623
- return /* @__PURE__ */ jsxRuntime.jsx(
15624
- WithAudioPlayback.Button,
15625
- {
15626
- appearance: "outline",
15627
- circular: true,
15628
- className: clsx("str-chat__attachment-selector__menu-button", className),
15629
- "data-testid": "invoke-attachment-selector-button",
15630
- size: "lg",
15631
- variant: "secondary",
15632
- ...props,
15633
- ref,
15634
- children: /* @__PURE__ */ jsxRuntime.jsx(AttachmentSelectorMenuInitButtonIcon, {})
15635
- }
15636
- );
15637
- }
15638
- );
15831
+ const AttachmentSelectorButton = React.forwardRef(function AttachmentSelectorButton2({ className, iconClassName, ...props }, ref) {
15832
+ return /* @__PURE__ */ jsxRuntime.jsx(
15833
+ WithAudioPlayback.Button,
15834
+ {
15835
+ appearance: "outline",
15836
+ circular: true,
15837
+ className: clsx("str-chat__attachment-selector__menu-button", className),
15838
+ "data-testid": "invoke-attachment-selector-button",
15839
+ size: "lg",
15840
+ variant: "secondary",
15841
+ ...props,
15842
+ ref,
15843
+ children: /* @__PURE__ */ jsxRuntime.jsx(AttachmentSelectorMenuInitButtonIcon, { className: iconClassName })
15844
+ }
15845
+ );
15846
+ });
15639
15847
  const SimpleAttachmentSelector = () => {
15640
15848
  const { channelCapabilities } = WithAudioPlayback.useChannelStateContext();
15641
15849
  const inputRef = React.useRef(null);
@@ -15807,7 +16015,7 @@ const AttachmentSelector = ({
15807
16015
  getModalPortalDestination
15808
16016
  }) => {
15809
16017
  const { t } = WithAudioPlayback.useTranslationContext();
15810
- const { Modal = GlobalModal } = WithAudioPlayback.useComponentContext();
16018
+ const { ContextMenu: ContextMenuComponent = ContextMenu, Modal = GlobalModal } = WithAudioPlayback.useComponentContext();
15811
16019
  const { channelCapabilities } = WithAudioPlayback.useChannelStateContext();
15812
16020
  const messageComposer = WithAudioPlayback.useMessageComposerController();
15813
16021
  const isCooldownActive = WithAudioPlayback.useIsCooldownActive();
@@ -15858,16 +16066,16 @@ const AttachmentSelector = ({
15858
16066
  "aria-expanded": menuDialogIsOpen,
15859
16067
  "aria-haspopup": "true",
15860
16068
  "aria-label": t("aria/Open Attachment Selector"),
15861
- className: clsx("str-chat__prepare-rotate45", {
16069
+ disabled: isCooldownActive,
16070
+ iconClassName: clsx("str-chat__prepare-rotate45", {
15862
16071
  "str-chat__rotate45": menuDialogIsOpen
15863
16072
  }),
15864
- disabled: isCooldownActive,
15865
16073
  onClick: () => menuDialog?.toggle(),
15866
16074
  ref: menuButtonRef
15867
16075
  }
15868
16076
  ),
15869
16077
  /* @__PURE__ */ jsxRuntime.jsx(
15870
- ContextMenu,
16078
+ ContextMenuComponent,
15871
16079
  {
15872
16080
  allowFlip: true,
15873
16081
  backLabel: t("Back"),
@@ -15986,562 +16194,175 @@ const PlayIcon = () => /* @__PURE__ */ jsxRuntime.jsx(
15986
16194
  }
15987
16195
  );
15988
16196
  const CheckSignIcon = () => /* @__PURE__ */ jsxRuntime.jsx("svg", { fill: "currentColor", viewBox: "0 0 18 14", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M5.79457 10.875L2.32457 7.40502C1.93457 7.01502 1.30457 7.01502 0.91457 7.40502C0.52457 7.79502 0.52457 8.42502 0.91457 8.81502L5.09457 12.995C5.48457 13.385 6.11457 13.385 6.50457 12.995L17.0846 2.41502C17.4746 2.02502 17.4746 1.39502 17.0846 1.00502C16.6946 0.615024 16.0646 0.615024 15.6746 1.00502L5.79457 10.875Z" }) });
15989
- const toGalleryItemDescriptors = (...args) => toBaseImageDescriptors(...args);
15990
- const GalleryContext = React.createContext(void 0);
15991
- const useGalleryContext = () => {
15992
- const contextValue = React.useContext(GalleryContext);
15993
- if (!contextValue) {
15994
- console.warn(
15995
- `The useGalleryContext hook was called outside of the GalleryContext provider. Make sure this hook is called within a child of the Gallery component.`
15996
- );
15997
- return {};
16197
+ const INTERACTIVE_SELECTOR = 'button, a, input, textarea, select, [role="button"], [role="link"], [data-interactive="true"]';
16198
+ function hasInteractiveAncestorBeforeRoot(target, root) {
16199
+ if (!(target instanceof Element) || !root) return false;
16200
+ let el = target;
16201
+ while (el && el !== root) {
16202
+ if (el.matches(INTERACTIVE_SELECTOR)) return true;
16203
+ el = el.parentElement;
15998
16204
  }
15999
- return contextValue;
16000
- };
16001
- const Gallery = ({
16002
- closeOnBackgroundClick = true,
16003
- GalleryUI: GalleryUI2,
16004
- initialIndex = 0,
16005
- items,
16006
- onIndexChange,
16007
- onRequestClose
16205
+ return false;
16206
+ }
16207
+ const AttachmentPreviewRoot = ({
16208
+ attachment,
16209
+ onPressed,
16210
+ openPreview,
16211
+ tabIndex = 0,
16212
+ ...props
16008
16213
  }) => {
16009
- const [currentIndex, setCurrentIndex] = React.useState(initialIndex);
16010
- const itemCount = items.length;
16011
- const goToIndex = React.useCallback(
16012
- (index) => {
16013
- if (index >= 0 && index < itemCount) {
16014
- setCurrentIndex(index);
16015
- }
16016
- },
16017
- [itemCount]
16018
- );
16019
- const goToNext = React.useCallback(() => {
16020
- setCurrentIndex((prev) => prev < itemCount - 1 ? prev + 1 : prev);
16021
- }, [itemCount]);
16022
- const goToPrevious = React.useCallback(() => {
16023
- setCurrentIndex((prev) => prev > 0 ? prev - 1 : prev);
16024
- }, []);
16025
- React.useEffect(() => {
16026
- onIndexChange?.(currentIndex);
16027
- }, [currentIndex, onIndexChange]);
16028
- const hasNext = currentIndex < itemCount - 1;
16029
- const hasPrevious = currentIndex > 0;
16030
- const currentItem = items[currentIndex];
16031
- const contextValue = React.useMemo(
16032
- () => ({
16033
- closeOnBackgroundClick,
16034
- currentIndex,
16035
- currentItem,
16036
- goToIndex,
16037
- goToNext,
16038
- goToPrevious,
16039
- hasNext,
16040
- hasPrevious,
16041
- itemCount,
16042
- items,
16043
- onRequestClose
16044
- }),
16045
- [
16046
- closeOnBackgroundClick,
16047
- currentIndex,
16048
- currentItem,
16049
- goToIndex,
16050
- goToNext,
16051
- goToPrevious,
16052
- hasNext,
16053
- hasPrevious,
16054
- itemCount,
16055
- items,
16056
- onRequestClose
16057
- ]
16058
- );
16059
- return /* @__PURE__ */ jsxRuntime.jsx(GalleryContext.Provider, { value: contextValue, children: GalleryUI2 ? /* @__PURE__ */ jsxRuntime.jsx(GalleryUI2, {}) : null });
16060
- };
16061
- const GalleryHeader = ({ currentItem }) => {
16062
16214
  const { t } = WithAudioPlayback.useTranslationContext();
16063
- const { MessageTimestamp: MessageTimestamp$1 = MessageTimestamp } = WithAudioPlayback.useComponentContext();
16064
- const { isMyMessage, message } = useMessageContext();
16065
- const modalContext = React.useContext(ModalContext);
16066
- const headerTitle = isMyMessage?.() && t("You") || message?.user?.name || message?.user?.id || currentItem.title || t("User uploaded content");
16067
- const downloadUrl = React.useMemo(() => {
16068
- const rawDownloadUrl = currentItem.videoUrl ?? currentItem.imageUrl;
16069
- if (!rawDownloadUrl) return void 0;
16070
- const sanitizedUrl = sanitizeUrl.sanitizeUrl(rawDownloadUrl);
16071
- return sanitizedUrl === "about:blank" ? void 0 : sanitizedUrl;
16072
- }, [currentItem.imageUrl, currentItem.videoUrl]);
16073
- const downloadLabel = t("aria/Download attachment");
16074
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__gallery__header", children: [
16075
- /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-hidden": "true", className: "str-chat__gallery__header-spacer" }),
16076
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__gallery__header-meta", children: [
16077
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__gallery__title", children: headerTitle }),
16078
- message?.created_at ? /* @__PURE__ */ jsxRuntime.jsx(MessageTimestamp$1, { customClass: "str-chat__gallery__timestamp" }) : null
16079
- ] }),
16080
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__gallery__header-actions", children: [
16081
- downloadUrl ? /* @__PURE__ */ jsxRuntime.jsx(
16082
- "a",
16083
- {
16084
- "aria-label": downloadLabel,
16085
- className: "str-chat__gallery__action-button str-chat__gallery__action-button--download",
16086
- download: true,
16087
- href: downloadUrl,
16088
- rel: "noreferrer",
16089
- target: "_blank",
16090
- title: downloadLabel,
16091
- children: /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconArrowDownCircle, {})
16092
- }
16093
- ) : null,
16094
- modalContext?.close ? /* @__PURE__ */ jsxRuntime.jsx(
16095
- WithAudioPlayback.Button,
16096
- {
16097
- "aria-label": t("Close"),
16098
- className: "str-chat__gallery__action-button str-chat__gallery__action-button--close",
16099
- onClick: modalContext.close,
16100
- title: t("Close"),
16101
- children: /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconCrossMedium, {})
16102
- }
16103
- ) : null
16104
- ] })
16105
- ] });
16106
- };
16107
- const VideoPlayer = ({ isPlaying, thumbnailUrl, videoUrl }) => {
16108
- const { VideoPlayer: VideoPlayerContext } = WithAudioPlayback.useComponentContext();
16109
- return VideoPlayerContext ? /* @__PURE__ */ jsxRuntime.jsx(VideoPlayerContext, { thumbnailUrl, videoUrl }) : /* @__PURE__ */ jsxRuntime.jsx(
16110
- ReactPlayer,
16215
+ const [root, setRoot] = React.useState(null);
16216
+ const url = attachment.asset_url || attachment.image_url || attachment.localMetadata.previewUri;
16217
+ const canDownloadAttachment = false;
16218
+ const canPreviewAttachment = !!openPreview && (!!url && streamChat.isImageAttachment(attachment) || streamChat.isVideoAttachment(attachment));
16219
+ const handlePressed = (e) => {
16220
+ if (e.defaultPrevented) return;
16221
+ if (hasInteractiveAncestorBeforeRoot(e.target, root)) return;
16222
+ if (onPressed) {
16223
+ const shouldContinue = onPressed(e);
16224
+ if (!shouldContinue) return;
16225
+ }
16226
+ if (canPreviewAttachment) {
16227
+ openPreview();
16228
+ return;
16229
+ }
16230
+ };
16231
+ const isInteractive = canPreviewAttachment || canDownloadAttachment;
16232
+ return /* @__PURE__ */ jsxRuntime.jsx(
16233
+ "div",
16111
16234
  {
16112
- className: "react-player",
16113
- config: { file: { attributes: { poster: thumbnailUrl } } },
16114
- controls: true,
16115
- height: "100%",
16116
- playing: isPlaying,
16117
- url: videoUrl,
16118
- width: "100%"
16235
+ "aria-label": isInteractive ? t(canPreviewAttachment ? "aria/Show preview" : "aria/Download attachment") : void 0,
16236
+ ...props,
16237
+ onClick: handlePressed,
16238
+ onKeyDown: isInteractive ? (e) => {
16239
+ if (e.key !== "Enter" && e.key !== " ") return;
16240
+ e.preventDefault();
16241
+ handlePressed(e);
16242
+ } : void 0,
16243
+ ref: setRoot,
16244
+ tabIndex: isInteractive ? tabIndex : -1,
16245
+ children: props.children
16119
16246
  }
16120
16247
  );
16121
16248
  };
16122
- const VideoThumbnail = ({
16123
- className,
16124
- onPlay,
16125
- ...imageProps
16249
+ const FileAttachmentPreview = ({
16250
+ attachment,
16251
+ handleRetry,
16252
+ removeAttachments
16126
16253
  }) => {
16127
- const { t } = WithAudioPlayback.useTranslationContext();
16128
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__message-attachment__video-thumbnail", children: [
16129
- /* @__PURE__ */ jsxRuntime.jsx(
16130
- BaseImage,
16131
- {
16132
- className: clsx("str-chat__message-attachment__video-thumbnail-image", className),
16133
- ...imageProps
16134
- }
16135
- ),
16136
- onPlay ? /* @__PURE__ */ jsxRuntime.jsx(
16137
- WithAudioPlayback.Button,
16138
- {
16139
- appearance: "solid",
16140
- "aria-label": t("Play video"),
16141
- circular: true,
16142
- className: clsx(
16143
- "str-chat__message-attachment__video-thumbnail__play-indicator"
16144
- ),
16145
- onClick: onPlay,
16146
- size: "lg",
16147
- variant: "secondary",
16148
- children: /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconPlaySolid, {})
16149
- }
16150
- ) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__message-attachment__video-thumbnail__play-indicator", children: /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconPlaySolid, {}) })
16151
- ] });
16152
- };
16153
- const SWIPE_THRESHOLD = 50;
16154
- const TRANSITION_DURATION = 300;
16155
- const GalleryUI = () => {
16156
- const { t } = WithAudioPlayback.useTranslationContext();
16157
- const {
16158
- closeOnBackgroundClick,
16159
- currentIndex,
16160
- currentItem,
16161
- goToNext,
16162
- goToPrevious,
16163
- hasNext,
16164
- hasPrevious,
16165
- itemCount,
16166
- onRequestClose
16167
- } = useGalleryContext();
16168
- const modalContext = React.useContext(ModalContext);
16169
- const [showVideo, setShowVideo] = React.useState(false);
16170
- const isTransitioningRef = React.useRef(false);
16171
- const [slideOffset, setSlideOffset] = React.useState(0);
16172
- const [isDragging, setIsDragging] = React.useState(false);
16173
- const [slideDirection, setSlideDirection] = React.useState(
16174
- null
16254
+ const { t } = WithAudioPlayback.useTranslationContext("FilePreview");
16255
+ const { id, uploadPermissionCheck, uploadState } = attachment.localMetadata ?? {};
16256
+ const hasSizeLimitError = uploadPermissionCheck?.reason === "size_limit";
16257
+ const hasFatalError = uploadState === "blocked" || hasSizeLimitError;
16258
+ const hasRetriableError = uploadState === "failed" && !!handleRetry;
16259
+ const hasError = hasRetriableError || hasFatalError;
16260
+ return /* @__PURE__ */ jsxRuntime.jsxs(
16261
+ AttachmentPreviewRoot,
16262
+ {
16263
+ attachment,
16264
+ className: "str-chat__attachment-preview-file",
16265
+ "data-testid": "attachment-preview-file",
16266
+ children: [
16267
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__attachment-preview-file__icon", children: /* @__PURE__ */ jsxRuntime.jsx(FileIcon, { fileName: attachment.title, mimeType: attachment.mime_type }) }),
16268
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__attachment-preview-file__info", children: [
16269
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__attachment-preview-file-name", title: attachment.title, children: attachment.title }),
16270
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__attachment-preview-file__data", children: [
16271
+ uploadState === "uploading" && /* @__PURE__ */ jsxRuntime.jsx(LoadingIndicatorIcon, {}),
16272
+ !hasError && /* @__PURE__ */ jsxRuntime.jsx(FileSizeIndicator, { fileSize: attachment.file_size }),
16273
+ hasFatalError && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__attachment-preview-file__fatal-error", children: [
16274
+ /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconExclamationCircle, {}),
16275
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: hasSizeLimitError ? t("File too large") : uploadState === "blocked" ? t("Upload blocked") : t("Upload failed") })
16276
+ ] }),
16277
+ hasRetriableError && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__attachment-preview-file__retriable-error", children: [
16278
+ /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconExclamationTriangle, {}),
16279
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: t("Upload error") }),
16280
+ /* @__PURE__ */ jsxRuntime.jsx(
16281
+ "button",
16282
+ {
16283
+ "aria-label": t("aria/Retry upload"),
16284
+ className: "str-chat__attachment-preview-file__retry-upload-button",
16285
+ "data-testid": "file-preview-item-retry-button",
16286
+ onClick: () => {
16287
+ handleRetry(attachment);
16288
+ },
16289
+ type: "button",
16290
+ children: t("Retry upload")
16291
+ }
16292
+ )
16293
+ ] })
16294
+ ] })
16295
+ ] }),
16296
+ /* @__PURE__ */ jsxRuntime.jsx(
16297
+ RemoveAttachmentPreviewButton,
16298
+ {
16299
+ "data-testid": "file-preview-item-delete-button",
16300
+ onClick: () => {
16301
+ if (id) removeAttachments([id]);
16302
+ },
16303
+ uploadState
16304
+ }
16305
+ )
16306
+ ]
16307
+ }
16175
16308
  );
16176
- const ignoreNextClickRef = React.useRef(false);
16177
- const touchStartRef = React.useRef(null);
16178
- const isVerticalSwipeRef = React.useRef(false);
16179
- const containerRef = React.useRef(null);
16180
- React.useEffect(() => {
16181
- setShowVideo(false);
16182
- }, [currentIndex]);
16183
- const prevIndexRef = React.useRef(currentIndex);
16184
- React.useEffect(() => {
16185
- if (prevIndexRef.current === currentIndex) return;
16186
- const direction = currentIndex > prevIndexRef.current ? "forward" : "backward";
16187
- setSlideDirection(direction);
16188
- setSlideOffset(0);
16189
- setIsDragging(false);
16190
- isTransitioningRef.current = true;
16191
- const timer = setTimeout(() => {
16192
- setSlideDirection(null);
16193
- isTransitioningRef.current = false;
16194
- }, TRANSITION_DURATION);
16195
- prevIndexRef.current = currentIndex;
16196
- return () => clearTimeout(timer);
16197
- }, [currentIndex]);
16198
- const handleGoToNext = React.useCallback(() => {
16199
- if (isTransitioningRef.current) return;
16200
- goToNext();
16201
- }, [goToNext]);
16202
- const handleGoToPrevious = React.useCallback(() => {
16203
- if (isTransitioningRef.current) return;
16204
- goToPrevious();
16205
- }, [goToPrevious]);
16206
- const handleKeyDown = React.useCallback(
16207
- (event) => {
16208
- if (event.key === "ArrowLeft") {
16209
- event.preventDefault();
16210
- handleGoToPrevious();
16211
- } else if (event.key === "ArrowRight") {
16212
- event.preventDefault();
16213
- handleGoToNext();
16214
- }
16215
- },
16216
- [handleGoToNext, handleGoToPrevious]
16217
- );
16218
- React.useEffect(() => {
16219
- document.addEventListener("keydown", handleKeyDown);
16220
- return () => document.removeEventListener("keydown", handleKeyDown);
16221
- }, [handleKeyDown]);
16222
- const handleTouchStart = React.useCallback((event) => {
16223
- if (isTransitioningRef.current) return;
16224
- const touch = event.touches[0];
16225
- ignoreNextClickRef.current = false;
16226
- touchStartRef.current = { x: touch.clientX, y: touch.clientY };
16227
- isVerticalSwipeRef.current = false;
16228
- }, []);
16229
- const handleTouchMove = React.useCallback(
16230
- (event) => {
16231
- if (!touchStartRef.current || isTransitioningRef.current) return;
16232
- const touch = event.touches[0];
16233
- const deltaX = touch.clientX - touchStartRef.current.x;
16234
- const deltaY = touch.clientY - touchStartRef.current.y;
16235
- if (!isDragging && !isVerticalSwipeRef.current) {
16236
- if (Math.abs(deltaY) > Math.abs(deltaX) && Math.abs(deltaY) > 10) {
16237
- ignoreNextClickRef.current = true;
16238
- isVerticalSwipeRef.current = true;
16239
- return;
16240
- }
16241
- if (Math.abs(deltaX) > 10) {
16242
- ignoreNextClickRef.current = true;
16243
- setIsDragging(true);
16244
- }
16245
- }
16246
- if (isVerticalSwipeRef.current) return;
16247
- if (!hasNext && deltaX < 0 || !hasPrevious && deltaX > 0) {
16248
- setSlideOffset(deltaX * 0.3);
16249
- } else {
16250
- setSlideOffset(deltaX);
16251
- }
16252
- },
16253
- [isDragging, hasNext, hasPrevious]
16254
- );
16255
- const handleTouchEnd = React.useCallback(() => {
16256
- if (!touchStartRef.current || isVerticalSwipeRef.current) {
16257
- if (isVerticalSwipeRef.current) ignoreNextClickRef.current = true;
16258
- touchStartRef.current = null;
16259
- return;
16260
- }
16261
- const offset = slideOffset;
16262
- if (isDragging || Math.abs(offset) > 10) {
16263
- ignoreNextClickRef.current = true;
16264
- }
16265
- touchStartRef.current = null;
16266
- if (Math.abs(offset) >= SWIPE_THRESHOLD) {
16267
- if (offset < 0 && hasNext) {
16268
- goToNext();
16269
- } else if (offset > 0 && hasPrevious) {
16270
- goToPrevious();
16271
- } else {
16272
- setSlideOffset(0);
16273
- }
16274
- } else {
16275
- setSlideOffset(0);
16276
- }
16277
- setIsDragging(false);
16278
- }, [slideOffset, hasNext, hasPrevious, goToNext, goToPrevious, isDragging]);
16279
- const requestClose = modalContext?.close ?? onRequestClose;
16280
- const handleBackgroundClick = React.useCallback(
16281
- (event) => {
16282
- if (event.target !== event.currentTarget) return;
16283
- if (ignoreNextClickRef.current) {
16284
- ignoreNextClickRef.current = false;
16285
- return;
16286
- }
16287
- if (!closeOnBackgroundClick) return;
16288
- requestClose?.();
16289
- },
16290
- [closeOnBackgroundClick, requestClose]
16291
- );
16292
- const mediaStyle = isDragging || slideOffset !== 0 && slideDirection === null ? { transform: `translateX(${slideOffset}px)` } : {};
16293
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__gallery", children: [
16294
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__gallery__main", children: [
16295
- /* @__PURE__ */ jsxRuntime.jsx(GalleryHeader, { currentItem }),
16296
- /* @__PURE__ */ jsxRuntime.jsx(
16297
- NavButton,
16298
- {
16299
- "aria-label": t("Previous image"),
16300
- className: clsx(
16301
- "str-chat__gallery__nav-button--prev",
16302
- !hasPrevious && "str-chat__gallery__nav-button--hidden"
16303
- ),
16304
- disabled: !hasPrevious,
16305
- onClick: handleGoToPrevious,
16306
- children: /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconChevronLeft, {})
16307
- }
16308
- ),
16309
- /* @__PURE__ */ jsxRuntime.jsx(
16310
- "div",
16311
- {
16312
- className: "str-chat__gallery__slide-container",
16313
- onClick: handleBackgroundClick,
16314
- onTouchEnd: handleTouchEnd,
16315
- onTouchMove: handleTouchMove,
16316
- onTouchStart: handleTouchStart,
16317
- ref: containerRef,
16318
- children: /* @__PURE__ */ jsxRuntime.jsx(
16319
- "div",
16320
- {
16321
- className: clsx({
16322
- "str-chat__gallery__media--dragging": isDragging,
16323
- "str-chat__gallery__media--slide-backward": !isDragging && slideDirection === "backward",
16324
- "str-chat__gallery__media--slide-forward": !isDragging && slideDirection === "forward"
16325
- }),
16326
- style: mediaStyle,
16327
- children: currentItem.videoUrl && currentItem.videoThumbnailUrl ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__gallery__media str-chat__gallery__media--video", children: showVideo ? /* @__PURE__ */ jsxRuntime.jsx(VideoPlayer, { isPlaying: true, videoUrl: currentItem.videoUrl }) : /* @__PURE__ */ jsxRuntime.jsx(
16328
- VideoThumbnail,
16329
- {
16330
- alt: currentItem.title ?? "",
16331
- onPlay: () => setShowVideo(true),
16332
- src: currentItem.videoThumbnailUrl
16333
- }
16334
- ) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__gallery__media str-chat__gallery__media--image", children: /* @__PURE__ */ jsxRuntime.jsx(BaseImage, { alt: currentItem.alt, src: currentItem.imageUrl }) })
16335
- }
16336
- )
16337
- }
16338
- ),
16339
- /* @__PURE__ */ jsxRuntime.jsx(
16340
- NavButton,
16341
- {
16342
- "aria-label": t("Next image"),
16343
- className: clsx(
16344
- "str-chat__gallery__nav-button--next",
16345
- !hasNext && "str-chat__gallery__nav-button--hidden"
16346
- ),
16347
- disabled: !hasNext,
16348
- onClick: handleGoToNext,
16349
- children: /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconChevronRight, {})
16350
- }
16351
- )
16352
- ] }),
16353
- itemCount > 1 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__gallery__position-indicator", children: [
16354
- currentIndex + 1,
16355
- " of ",
16356
- itemCount
16357
- ] })
16358
- ] });
16359
16309
  };
16360
- const NavButton = ({ className, ...props }) => /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.Button, { ...props, className: clsx("str-chat__gallery__nav-button", className) });
16361
- const INTERACTIVE_SELECTOR = 'button, a, input, textarea, select, [role="button"], [role="link"], [data-interactive="true"]';
16362
- function hasInteractiveAncestorBeforeRoot(target, root) {
16363
- if (!(target instanceof Element) || !root) return false;
16364
- let el = target;
16365
- while (el && el !== root) {
16366
- if (el.matches(INTERACTIVE_SELECTOR)) return true;
16367
- el = el.parentElement;
16310
+ const UnsupportedAttachmentPreview = ({
16311
+ attachment,
16312
+ handleRetry,
16313
+ removeAttachments
16314
+ }) => /* @__PURE__ */ jsxRuntime.jsx(
16315
+ FileAttachmentPreview,
16316
+ {
16317
+ attachment,
16318
+ handleRetry,
16319
+ removeAttachments
16368
16320
  }
16369
- return false;
16321
+ );
16322
+ function formatTime(totalSeconds, rounding = "ceil") {
16323
+ if (totalSeconds == null || Number.isNaN(totalSeconds) || totalSeconds < 0) {
16324
+ return null;
16325
+ }
16326
+ const roundedSeconds = rounding === "floor" ? Math.floor(totalSeconds) : Math.ceil(totalSeconds);
16327
+ const hours = Math.floor(roundedSeconds / 3600);
16328
+ const minutes = Math.floor(roundedSeconds % 3600 / 60);
16329
+ const seconds = roundedSeconds % 60;
16330
+ const minSec = `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(
16331
+ 2,
16332
+ "0"
16333
+ )}`;
16334
+ return hours ? `${String(hours).padStart(2, "0")}:${minSec}` : minSec;
16370
16335
  }
16371
- const AttachmentPreviewRoot = ({
16372
- attachment,
16373
- onPressed,
16374
- tabIndex = 0,
16375
- ...props
16376
- }) => {
16377
- const { t } = WithAudioPlayback.useTranslationContext("FilePreview");
16378
- const { Modal = GlobalModal } = WithAudioPlayback.useComponentContext();
16379
- const [showPreview, setShowPreview] = React.useState(false);
16380
- const [root, setRoot] = React.useState(null);
16381
- const url = attachment.asset_url || attachment.image_url || attachment.localMetadata.previewUri;
16382
- const canDownloadAttachment = false;
16383
- const canPreviewAttachment = !!url && streamChat.isImageAttachment(attachment) || streamChat.isVideoAttachment(attachment);
16384
- const handlePressed = (e) => {
16385
- if (e.defaultPrevented) return;
16386
- if (hasInteractiveAncestorBeforeRoot(e.target, root)) return;
16387
- if (onPressed) {
16388
- const shouldContinue = onPressed(e);
16389
- if (!shouldContinue) return;
16390
- }
16391
- if (canPreviewAttachment) {
16392
- setShowPreview(true);
16393
- return;
16394
- }
16395
- };
16336
+ function DurationDisplay({
16337
+ className,
16338
+ duration: duration2,
16339
+ isPlaying,
16340
+ secondsElapsed,
16341
+ showRemaining = false
16342
+ }) {
16343
+ const remainingSeconds = duration2 != null && secondsElapsed != null ? Math.max(0, duration2 - secondsElapsed) : void 0;
16344
+ const formattedDuration = formatTime(duration2);
16345
+ const formattedSecondsElapsed = formatTime(secondsElapsed);
16346
+ const formattedRemaining = formatTime(remainingSeconds);
16347
+ const shouldShowElapsed = !!secondsElapsed && secondsElapsed > 0 && secondsElapsed < (duration2 || 0);
16348
+ const canShowRemaining = showRemaining && duration2 != null && secondsElapsed != null;
16349
+ const primaryValue = showRemaining ? formattedRemaining : formattedSecondsElapsed;
16350
+ const showPrimary = (canShowRemaining || shouldShowElapsed) && !!primaryValue;
16351
+ const showDuration = !showPrimary && !!formattedDuration;
16396
16352
  return /* @__PURE__ */ jsxRuntime.jsxs(
16397
16353
  "div",
16398
16354
  {
16399
- "aria-label": t(showPreview ? "aria/Show preview" : "aria/Download attachment"),
16400
- ...props,
16401
- onClick: handlePressed,
16402
- onKeyDown: (e) => {
16403
- if (e.key !== "Enter" && e.key !== " ") return;
16404
- e.preventDefault();
16405
- handlePressed(e);
16406
- },
16407
- ref: setRoot,
16408
- role: showPreview ? "button" : props.role,
16409
- tabIndex: showPreview || canDownloadAttachment ? tabIndex : -1,
16355
+ className: clsx(
16356
+ "str-chat__duration-display",
16357
+ {
16358
+ "str-chat__duration-display--hasProgress": !!secondsElapsed,
16359
+ "str-chat__duration-display--isPlaying": isPlaying
16360
+ },
16361
+ className
16362
+ ),
16410
16363
  children: [
16411
- props.children,
16412
- /* @__PURE__ */ jsxRuntime.jsx(
16413
- Modal,
16414
- {
16415
- className: "str-chat__gallery-modal",
16416
- onClose: (e) => {
16417
- e.stopPropagation();
16418
- setShowPreview(false);
16419
- },
16420
- open: showPreview && canPreviewAttachment,
16421
- children: streamChat.isImageAttachment(attachment) || streamChat.isVideoAttachment(attachment) ? /* @__PURE__ */ jsxRuntime.jsx(Gallery, { items: [attachment] }) : null
16422
- }
16423
- )
16424
- ]
16425
- }
16426
- );
16427
- };
16428
- const FileAttachmentPreview = ({
16429
- attachment,
16430
- handleRetry,
16431
- removeAttachments
16432
- }) => {
16433
- const { t } = WithAudioPlayback.useTranslationContext("FilePreview");
16434
- const { id, uploadPermissionCheck, uploadState } = attachment.localMetadata ?? {};
16435
- const hasSizeLimitError = uploadPermissionCheck?.reason === "size_limit";
16436
- const hasFatalError = uploadState === "blocked" || hasSizeLimitError;
16437
- const hasRetriableError = uploadState === "failed" && !!handleRetry;
16438
- const hasError = hasRetriableError || hasFatalError;
16439
- return /* @__PURE__ */ jsxRuntime.jsxs(
16440
- AttachmentPreviewRoot,
16441
- {
16442
- attachment,
16443
- className: "str-chat__attachment-preview-file",
16444
- "data-testid": "attachment-preview-file",
16445
- children: [
16446
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__attachment-preview-file__icon", children: /* @__PURE__ */ jsxRuntime.jsx(FileIcon, { fileName: attachment.title, mimeType: attachment.mime_type }) }),
16447
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__attachment-preview-file__info", children: [
16448
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__attachment-preview-file-name", title: attachment.title, children: attachment.title }),
16449
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__attachment-preview-file__data", children: [
16450
- uploadState === "uploading" && /* @__PURE__ */ jsxRuntime.jsx(LoadingIndicatorIcon, {}),
16451
- !hasError && /* @__PURE__ */ jsxRuntime.jsx(FileSizeIndicator, { fileSize: attachment.file_size }),
16452
- hasFatalError && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__attachment-preview-file__fatal-error", children: [
16453
- /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconExclamationCircle, {}),
16454
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: hasSizeLimitError ? t("File too large") : uploadState === "blocked" ? t("Upload blocked") : t("Upload failed") })
16455
- ] }),
16456
- hasRetriableError && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__attachment-preview-file__retriable-error", children: [
16457
- /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconExclamationTriangle, {}),
16458
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: t("Upload error") }),
16459
- /* @__PURE__ */ jsxRuntime.jsx(
16460
- "button",
16461
- {
16462
- "aria-label": t("aria/Retry upload"),
16463
- className: "str-chat__attachment-preview-file__retry-upload-button",
16464
- "data-testid": "file-preview-item-retry-button",
16465
- onClick: () => {
16466
- handleRetry(attachment);
16467
- },
16468
- type: "button",
16469
- children: t("Retry upload")
16470
- }
16471
- )
16472
- ] })
16473
- ] })
16474
- ] }),
16475
- /* @__PURE__ */ jsxRuntime.jsx(
16476
- RemoveAttachmentPreviewButton,
16477
- {
16478
- "data-testid": "file-preview-item-delete-button",
16479
- onClick: () => {
16480
- if (id) removeAttachments([id]);
16481
- },
16482
- uploadState
16483
- }
16484
- )
16485
- ]
16486
- }
16487
- );
16488
- };
16489
- const UnsupportedAttachmentPreview = ({
16490
- attachment,
16491
- handleRetry,
16492
- removeAttachments
16493
- }) => /* @__PURE__ */ jsxRuntime.jsx(
16494
- FileAttachmentPreview,
16495
- {
16496
- attachment,
16497
- handleRetry,
16498
- removeAttachments
16499
- }
16500
- );
16501
- function formatTime(totalSeconds, rounding = "ceil") {
16502
- if (totalSeconds == null || Number.isNaN(totalSeconds) || totalSeconds < 0) {
16503
- return null;
16504
- }
16505
- const roundedSeconds = rounding === "floor" ? Math.floor(totalSeconds) : Math.ceil(totalSeconds);
16506
- const hours = Math.floor(roundedSeconds / 3600);
16507
- const minutes = Math.floor(roundedSeconds % 3600 / 60);
16508
- const seconds = roundedSeconds % 60;
16509
- const minSec = `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(
16510
- 2,
16511
- "0"
16512
- )}`;
16513
- return hours ? `${String(hours).padStart(2, "0")}:${minSec}` : minSec;
16514
- }
16515
- function DurationDisplay({
16516
- className,
16517
- duration: duration2,
16518
- isPlaying,
16519
- secondsElapsed,
16520
- showRemaining = false
16521
- }) {
16522
- const remainingSeconds = duration2 != null && secondsElapsed != null ? Math.max(0, duration2 - secondsElapsed) : void 0;
16523
- const formattedDuration = formatTime(duration2);
16524
- const formattedSecondsElapsed = formatTime(secondsElapsed);
16525
- const formattedRemaining = formatTime(remainingSeconds);
16526
- const shouldShowElapsed = !!secondsElapsed && secondsElapsed > 0 && secondsElapsed < (duration2 || 0);
16527
- const canShowRemaining = showRemaining && duration2 != null && secondsElapsed != null;
16528
- const primaryValue = showRemaining ? formattedRemaining : formattedSecondsElapsed;
16529
- const showPrimary = (canShowRemaining || shouldShowElapsed) && !!primaryValue;
16530
- const showDuration = !showPrimary && !!formattedDuration;
16531
- return /* @__PURE__ */ jsxRuntime.jsxs(
16532
- "div",
16533
- {
16534
- className: clsx(
16535
- "str-chat__duration-display",
16536
- {
16537
- "str-chat__duration-display--hasProgress": !!secondsElapsed,
16538
- "str-chat__duration-display--isPlaying": isPlaying
16539
- },
16540
- className
16541
- ),
16542
- children: [
16543
- showPrimary && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "str-chat__duration-display__time-elapsed", children: primaryValue }),
16544
- showDuration && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "str-chat__duration-display__duration", children: formattedDuration })
16364
+ showPrimary && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "str-chat__duration-display__time-elapsed", children: primaryValue }),
16365
+ showDuration && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "str-chat__duration-display__duration", children: formattedDuration })
16545
16366
  ]
16546
16367
  }
16547
16368
  );
@@ -17048,6 +16869,7 @@ const AudioAttachmentPreview = ({
17048
16869
  const MediaAttachmentPreview = ({
17049
16870
  attachment,
17050
16871
  handleRetry,
16872
+ openPreview,
17051
16873
  removeAttachments
17052
16874
  }) => {
17053
16875
  const { t } = WithAudioPlayback.useTranslationContext();
@@ -17088,6 +16910,7 @@ const MediaAttachmentPreview = ({
17088
16910
  }),
17089
16911
  "data-testid": "attachment-preview-media",
17090
16912
  onPressed: hasRetriableError ? retry : void 0,
16913
+ openPreview: !isUploading && !hasUploadError ? openPreview : void 0,
17091
16914
  children: [
17092
16915
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__attachment-preview-media__thumbnail-wrapper", children: [
17093
16916
  thumbnail.url && /* @__PURE__ */ jsxRuntime.jsx(
@@ -17137,6 +16960,380 @@ const MediaAttachmentPreview = ({
17137
16960
  }
17138
16961
  );
17139
16962
  };
16963
+ const toGalleryItemDescriptors = (...args) => toBaseImageDescriptors(...args);
16964
+ const GalleryContext = React.createContext(void 0);
16965
+ const useGalleryContext = () => {
16966
+ const contextValue = React.useContext(GalleryContext);
16967
+ if (!contextValue) {
16968
+ console.warn(
16969
+ `The useGalleryContext hook was called outside of the GalleryContext provider. Make sure this hook is called within a child of the Gallery component.`
16970
+ );
16971
+ return {};
16972
+ }
16973
+ return contextValue;
16974
+ };
16975
+ const GalleryHeader = ({ currentItem }) => {
16976
+ const { t } = WithAudioPlayback.useTranslationContext();
16977
+ const { MessageTimestamp: MessageTimestamp$1 = MessageTimestamp } = WithAudioPlayback.useComponentContext();
16978
+ const { isMyMessage, message } = useMessageContext();
16979
+ const modalContext = React.useContext(ModalContext);
16980
+ const headerTitle = isMyMessage?.() && t("You") || message?.user?.name || message?.user?.id || currentItem.title || t("User uploaded content");
16981
+ const downloadUrl = React.useMemo(() => {
16982
+ const rawDownloadUrl = currentItem.videoUrl ?? currentItem.imageUrl;
16983
+ if (!rawDownloadUrl) return void 0;
16984
+ const sanitizedUrl = sanitizeUrl.sanitizeUrl(rawDownloadUrl);
16985
+ return sanitizedUrl === "about:blank" ? void 0 : sanitizedUrl;
16986
+ }, [currentItem.imageUrl, currentItem.videoUrl]);
16987
+ const downloadLabel = t("aria/Download attachment");
16988
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__gallery__header", children: [
16989
+ /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-hidden": "true", className: "str-chat__gallery__header-spacer" }),
16990
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__gallery__header-meta", children: [
16991
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__gallery__title", children: headerTitle }),
16992
+ message?.created_at ? /* @__PURE__ */ jsxRuntime.jsx(MessageTimestamp$1, { customClass: "str-chat__gallery__timestamp" }) : null
16993
+ ] }),
16994
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__gallery__header-actions", children: [
16995
+ downloadUrl ? /* @__PURE__ */ jsxRuntime.jsx(
16996
+ "a",
16997
+ {
16998
+ "aria-label": downloadLabel,
16999
+ className: "str-chat__gallery__action-button str-chat__gallery__action-button--download",
17000
+ download: true,
17001
+ href: downloadUrl,
17002
+ rel: "noreferrer",
17003
+ target: "_blank",
17004
+ title: downloadLabel,
17005
+ children: /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconArrowDownCircle, {})
17006
+ }
17007
+ ) : null,
17008
+ modalContext?.close ? /* @__PURE__ */ jsxRuntime.jsx(
17009
+ WithAudioPlayback.Button,
17010
+ {
17011
+ "aria-label": t("Close"),
17012
+ className: "str-chat__gallery__action-button str-chat__gallery__action-button--close",
17013
+ onClick: modalContext.close,
17014
+ title: t("Close"),
17015
+ children: /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconCrossMedium, {})
17016
+ }
17017
+ ) : null
17018
+ ] })
17019
+ ] });
17020
+ };
17021
+ const VideoPlayer = ({ isPlaying, thumbnailUrl, videoUrl }) => {
17022
+ const { VideoPlayer: VideoPlayerContext } = WithAudioPlayback.useComponentContext();
17023
+ return VideoPlayerContext ? /* @__PURE__ */ jsxRuntime.jsx(VideoPlayerContext, { thumbnailUrl, videoUrl }) : /* @__PURE__ */ jsxRuntime.jsx(
17024
+ ReactPlayer,
17025
+ {
17026
+ className: "react-player",
17027
+ config: { file: { attributes: { poster: thumbnailUrl } } },
17028
+ controls: true,
17029
+ height: "100%",
17030
+ playing: isPlaying,
17031
+ url: videoUrl,
17032
+ width: "100%"
17033
+ }
17034
+ );
17035
+ };
17036
+ const VideoThumbnail = ({
17037
+ className,
17038
+ onPlay,
17039
+ ...imageProps
17040
+ }) => {
17041
+ const { t } = WithAudioPlayback.useTranslationContext();
17042
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__message-attachment__video-thumbnail", children: [
17043
+ /* @__PURE__ */ jsxRuntime.jsx(
17044
+ BaseImage,
17045
+ {
17046
+ className: clsx("str-chat__message-attachment__video-thumbnail-image", className),
17047
+ ...imageProps
17048
+ }
17049
+ ),
17050
+ onPlay ? /* @__PURE__ */ jsxRuntime.jsx(
17051
+ WithAudioPlayback.Button,
17052
+ {
17053
+ appearance: "solid",
17054
+ "aria-label": t("Play video"),
17055
+ circular: true,
17056
+ className: clsx(
17057
+ "str-chat__message-attachment__video-thumbnail__play-indicator"
17058
+ ),
17059
+ onClick: onPlay,
17060
+ size: "lg",
17061
+ variant: "secondary",
17062
+ children: /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconPlaySolid, {})
17063
+ }
17064
+ ) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__message-attachment__video-thumbnail__play-indicator", children: /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconPlaySolid, {}) })
17065
+ ] });
17066
+ };
17067
+ const SWIPE_THRESHOLD = 50;
17068
+ const TRANSITION_DURATION = 300;
17069
+ const GalleryUI = () => {
17070
+ const { t } = WithAudioPlayback.useTranslationContext();
17071
+ const {
17072
+ closeOnBackgroundClick,
17073
+ currentIndex,
17074
+ currentItem,
17075
+ goToNext,
17076
+ goToPrevious,
17077
+ hasNext,
17078
+ hasPrevious,
17079
+ itemCount,
17080
+ onRequestClose
17081
+ } = useGalleryContext();
17082
+ const modalContext = React.useContext(ModalContext);
17083
+ const [showVideo, setShowVideo] = React.useState(false);
17084
+ const isTransitioningRef = React.useRef(false);
17085
+ const [slideOffset, setSlideOffset] = React.useState(0);
17086
+ const [isDragging, setIsDragging] = React.useState(false);
17087
+ const [slideDirection, setSlideDirection] = React.useState(
17088
+ null
17089
+ );
17090
+ const ignoreNextClickRef = React.useRef(false);
17091
+ const touchStartRef = React.useRef(null);
17092
+ const isVerticalSwipeRef = React.useRef(false);
17093
+ const containerRef = React.useRef(null);
17094
+ React.useEffect(() => {
17095
+ setShowVideo(false);
17096
+ }, [currentIndex]);
17097
+ const prevIndexRef = React.useRef(currentIndex);
17098
+ React.useEffect(() => {
17099
+ if (prevIndexRef.current === currentIndex) return;
17100
+ const direction = currentIndex > prevIndexRef.current ? "forward" : "backward";
17101
+ setSlideDirection(direction);
17102
+ setSlideOffset(0);
17103
+ setIsDragging(false);
17104
+ isTransitioningRef.current = true;
17105
+ const timer = setTimeout(() => {
17106
+ setSlideDirection(null);
17107
+ isTransitioningRef.current = false;
17108
+ }, TRANSITION_DURATION);
17109
+ prevIndexRef.current = currentIndex;
17110
+ return () => clearTimeout(timer);
17111
+ }, [currentIndex]);
17112
+ const handleGoToNext = React.useCallback(() => {
17113
+ if (isTransitioningRef.current) return;
17114
+ goToNext();
17115
+ }, [goToNext]);
17116
+ const handleGoToPrevious = React.useCallback(() => {
17117
+ if (isTransitioningRef.current) return;
17118
+ goToPrevious();
17119
+ }, [goToPrevious]);
17120
+ const handleKeyDown = React.useCallback(
17121
+ (event) => {
17122
+ if (event.key === "ArrowLeft") {
17123
+ event.preventDefault();
17124
+ handleGoToPrevious();
17125
+ } else if (event.key === "ArrowRight") {
17126
+ event.preventDefault();
17127
+ handleGoToNext();
17128
+ }
17129
+ },
17130
+ [handleGoToNext, handleGoToPrevious]
17131
+ );
17132
+ React.useEffect(() => {
17133
+ document.addEventListener("keydown", handleKeyDown);
17134
+ return () => document.removeEventListener("keydown", handleKeyDown);
17135
+ }, [handleKeyDown]);
17136
+ const handleTouchStart = React.useCallback((event) => {
17137
+ if (isTransitioningRef.current) return;
17138
+ const touch = event.touches[0];
17139
+ ignoreNextClickRef.current = false;
17140
+ touchStartRef.current = { x: touch.clientX, y: touch.clientY };
17141
+ isVerticalSwipeRef.current = false;
17142
+ }, []);
17143
+ const handleTouchMove = React.useCallback(
17144
+ (event) => {
17145
+ if (!touchStartRef.current || isTransitioningRef.current) return;
17146
+ const touch = event.touches[0];
17147
+ const deltaX = touch.clientX - touchStartRef.current.x;
17148
+ const deltaY = touch.clientY - touchStartRef.current.y;
17149
+ if (!isDragging && !isVerticalSwipeRef.current) {
17150
+ if (Math.abs(deltaY) > Math.abs(deltaX) && Math.abs(deltaY) > 10) {
17151
+ ignoreNextClickRef.current = true;
17152
+ isVerticalSwipeRef.current = true;
17153
+ return;
17154
+ }
17155
+ if (Math.abs(deltaX) > 10) {
17156
+ ignoreNextClickRef.current = true;
17157
+ setIsDragging(true);
17158
+ }
17159
+ }
17160
+ if (isVerticalSwipeRef.current) return;
17161
+ if (!hasNext && deltaX < 0 || !hasPrevious && deltaX > 0) {
17162
+ setSlideOffset(deltaX * 0.3);
17163
+ } else {
17164
+ setSlideOffset(deltaX);
17165
+ }
17166
+ },
17167
+ [isDragging, hasNext, hasPrevious]
17168
+ );
17169
+ const handleTouchEnd = React.useCallback(() => {
17170
+ if (!touchStartRef.current || isVerticalSwipeRef.current) {
17171
+ if (isVerticalSwipeRef.current) ignoreNextClickRef.current = true;
17172
+ touchStartRef.current = null;
17173
+ return;
17174
+ }
17175
+ const offset = slideOffset;
17176
+ if (isDragging || Math.abs(offset) > 10) {
17177
+ ignoreNextClickRef.current = true;
17178
+ }
17179
+ touchStartRef.current = null;
17180
+ if (Math.abs(offset) >= SWIPE_THRESHOLD) {
17181
+ if (offset < 0 && hasNext) {
17182
+ goToNext();
17183
+ } else if (offset > 0 && hasPrevious) {
17184
+ goToPrevious();
17185
+ } else {
17186
+ setSlideOffset(0);
17187
+ }
17188
+ } else {
17189
+ setSlideOffset(0);
17190
+ }
17191
+ setIsDragging(false);
17192
+ }, [slideOffset, hasNext, hasPrevious, goToNext, goToPrevious, isDragging]);
17193
+ const requestClose = modalContext?.close ?? onRequestClose;
17194
+ const handleBackgroundClick = React.useCallback(
17195
+ (event) => {
17196
+ if (event.target !== event.currentTarget) return;
17197
+ if (ignoreNextClickRef.current) {
17198
+ ignoreNextClickRef.current = false;
17199
+ return;
17200
+ }
17201
+ if (!closeOnBackgroundClick) return;
17202
+ requestClose?.();
17203
+ },
17204
+ [closeOnBackgroundClick, requestClose]
17205
+ );
17206
+ const mediaStyle = isDragging || slideOffset !== 0 && slideDirection === null ? { transform: `translateX(${slideOffset}px)` } : {};
17207
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__gallery", children: [
17208
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__gallery__main", children: [
17209
+ /* @__PURE__ */ jsxRuntime.jsx(GalleryHeader, { currentItem }),
17210
+ /* @__PURE__ */ jsxRuntime.jsx(
17211
+ NavButton,
17212
+ {
17213
+ "aria-label": t("Previous image"),
17214
+ className: clsx(
17215
+ "str-chat__gallery__nav-button--prev",
17216
+ !hasPrevious && "str-chat__gallery__nav-button--hidden"
17217
+ ),
17218
+ disabled: !hasPrevious,
17219
+ onClick: handleGoToPrevious,
17220
+ children: /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconChevronLeft, {})
17221
+ }
17222
+ ),
17223
+ /* @__PURE__ */ jsxRuntime.jsx(
17224
+ "div",
17225
+ {
17226
+ className: "str-chat__gallery__slide-container",
17227
+ onClick: handleBackgroundClick,
17228
+ onTouchEnd: handleTouchEnd,
17229
+ onTouchMove: handleTouchMove,
17230
+ onTouchStart: handleTouchStart,
17231
+ ref: containerRef,
17232
+ children: /* @__PURE__ */ jsxRuntime.jsx(
17233
+ "div",
17234
+ {
17235
+ className: clsx({
17236
+ "str-chat__gallery__media--dragging": isDragging,
17237
+ "str-chat__gallery__media--slide-backward": !isDragging && slideDirection === "backward",
17238
+ "str-chat__gallery__media--slide-forward": !isDragging && slideDirection === "forward"
17239
+ }),
17240
+ style: mediaStyle,
17241
+ children: currentItem.videoUrl && currentItem.videoThumbnailUrl ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__gallery__media str-chat__gallery__media--video", children: showVideo ? /* @__PURE__ */ jsxRuntime.jsx(VideoPlayer, { isPlaying: true, videoUrl: currentItem.videoUrl }) : /* @__PURE__ */ jsxRuntime.jsx(
17242
+ VideoThumbnail,
17243
+ {
17244
+ alt: currentItem.title ?? "",
17245
+ onPlay: () => setShowVideo(true),
17246
+ src: currentItem.videoThumbnailUrl
17247
+ }
17248
+ ) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__gallery__media str-chat__gallery__media--image", children: /* @__PURE__ */ jsxRuntime.jsx(BaseImage, { alt: currentItem.alt, src: currentItem.imageUrl }) })
17249
+ }
17250
+ )
17251
+ }
17252
+ ),
17253
+ /* @__PURE__ */ jsxRuntime.jsx(
17254
+ NavButton,
17255
+ {
17256
+ "aria-label": t("Next image"),
17257
+ className: clsx(
17258
+ "str-chat__gallery__nav-button--next",
17259
+ !hasNext && "str-chat__gallery__nav-button--hidden"
17260
+ ),
17261
+ disabled: !hasNext,
17262
+ onClick: handleGoToNext,
17263
+ children: /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconChevronRight, {})
17264
+ }
17265
+ )
17266
+ ] }),
17267
+ itemCount > 1 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__gallery__position-indicator", children: [
17268
+ currentIndex + 1,
17269
+ " of ",
17270
+ itemCount
17271
+ ] })
17272
+ ] });
17273
+ };
17274
+ const NavButton = ({ className, ...props }) => /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.Button, { ...props, className: clsx("str-chat__gallery__nav-button", className) });
17275
+ const Gallery = ({
17276
+ closeOnBackgroundClick = true,
17277
+ GalleryUI: GalleryUI$1,
17278
+ initialIndex = 0,
17279
+ items,
17280
+ onIndexChange,
17281
+ onRequestClose
17282
+ }) => {
17283
+ const { GalleryUI: ContextGalleryUI } = WithAudioPlayback.useComponentContext();
17284
+ const ResolvedGalleryUI = GalleryUI$1 ?? ContextGalleryUI ?? GalleryUI;
17285
+ const [currentIndex, setCurrentIndex] = React.useState(initialIndex);
17286
+ const itemCount = items.length;
17287
+ const goToIndex = React.useCallback(
17288
+ (index) => {
17289
+ if (index >= 0 && index < itemCount) {
17290
+ setCurrentIndex(index);
17291
+ }
17292
+ },
17293
+ [itemCount]
17294
+ );
17295
+ const goToNext = React.useCallback(() => {
17296
+ setCurrentIndex((prev) => prev < itemCount - 1 ? prev + 1 : prev);
17297
+ }, [itemCount]);
17298
+ const goToPrevious = React.useCallback(() => {
17299
+ setCurrentIndex((prev) => prev > 0 ? prev - 1 : prev);
17300
+ }, []);
17301
+ React.useEffect(() => {
17302
+ onIndexChange?.(currentIndex);
17303
+ }, [currentIndex, onIndexChange]);
17304
+ const hasNext = currentIndex < itemCount - 1;
17305
+ const hasPrevious = currentIndex > 0;
17306
+ const currentItem = items[currentIndex];
17307
+ const contextValue = React.useMemo(
17308
+ () => ({
17309
+ closeOnBackgroundClick,
17310
+ currentIndex,
17311
+ currentItem,
17312
+ goToIndex,
17313
+ goToNext,
17314
+ goToPrevious,
17315
+ hasNext,
17316
+ hasPrevious,
17317
+ itemCount,
17318
+ items,
17319
+ onRequestClose
17320
+ }),
17321
+ [
17322
+ closeOnBackgroundClick,
17323
+ currentIndex,
17324
+ currentItem,
17325
+ goToIndex,
17326
+ goToNext,
17327
+ goToPrevious,
17328
+ hasNext,
17329
+ hasPrevious,
17330
+ itemCount,
17331
+ items,
17332
+ onRequestClose
17333
+ ]
17334
+ );
17335
+ return /* @__PURE__ */ jsxRuntime.jsx(GalleryContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxRuntime.jsx(ResolvedGalleryUI, {}) });
17336
+ };
17140
17337
  const AttachmentPreviewList = ({
17141
17338
  AudioAttachmentPreview: AudioAttachmentPreview2 = FileAttachmentPreview,
17142
17339
  FileAttachmentPreview: FileAttachmentPreview$1 = FileAttachmentPreview,
@@ -17145,73 +17342,107 @@ const AttachmentPreviewList = ({
17145
17342
  VideoAttachmentPreview = MediaAttachmentPreview
17146
17343
  }) => {
17147
17344
  const messageComposer = WithAudioPlayback.useMessageComposerController();
17345
+ const { Modal = GlobalModal } = WithAudioPlayback.useComponentContext();
17346
+ const [showPreview, setShowPreview] = React.useState(false);
17347
+ const initialIndexRef = React.useRef(0);
17148
17348
  const { attachments } = useAttachmentsForPreview();
17149
17349
  const filteredAttachments = React.useMemo(
17150
17350
  () => attachments.filter((a) => !streamChat.isVoiceRecordingAttachment(a)),
17151
17351
  [attachments]
17152
17352
  );
17353
+ const { galleryItems, previewIndexById } = React.useMemo(() => {
17354
+ const items = [];
17355
+ const indexById = {};
17356
+ for (const a of attachments) {
17357
+ if (streamChat.isLocalImageAttachment(a) || streamChat.isLocalVideoAttachment(a)) {
17358
+ const descriptor = toBaseImageDescriptors(a);
17359
+ if (descriptor) {
17360
+ indexById[a.localMetadata.id] = items.length;
17361
+ items.push(descriptor);
17362
+ }
17363
+ }
17364
+ }
17365
+ return { galleryItems: items, previewIndexById: indexById };
17366
+ }, [attachments]);
17367
+ const openPreviewAtIndex = React.useCallback((index) => {
17368
+ initialIndexRef.current = index;
17369
+ setShowPreview(true);
17370
+ }, []);
17153
17371
  if (!filteredAttachments.length) return null;
17154
- return /* @__PURE__ */ jsxRuntime.jsx(
17372
+ return /* @__PURE__ */ jsxRuntime.jsxs(
17155
17373
  "div",
17156
17374
  {
17157
17375
  className: "str-chat__attachment-preview-list",
17158
17376
  "data-testid": "attachment-preview-list",
17159
- children: attachments.map((attachment) => {
17160
- if (streamChat.isScrapedContent(attachment)) return null;
17161
- if (streamChat.isLocalVoiceRecordingAttachment(attachment)) return null;
17162
- if (streamChat.isLocalAudioAttachment(attachment)) {
17163
- return /* @__PURE__ */ jsxRuntime.jsx(
17164
- AudioAttachmentPreview2,
17165
- {
17166
- attachment,
17167
- handleRetry: messageComposer.attachmentManager.uploadAttachment,
17168
- removeAttachments: messageComposer.attachmentManager.removeAttachments
17169
- },
17170
- attachment.localMetadata.id || attachment.asset_url
17171
- );
17172
- } else if (streamChat.isLocalVideoAttachment(attachment)) {
17173
- return /* @__PURE__ */ jsxRuntime.jsx(
17174
- VideoAttachmentPreview,
17175
- {
17176
- attachment,
17177
- handleRetry: messageComposer.attachmentManager.uploadAttachment,
17178
- removeAttachments: messageComposer.attachmentManager.removeAttachments
17179
- },
17180
- attachment.localMetadata.id || attachment.asset_url
17181
- );
17182
- } else if (streamChat.isLocalImageAttachment(attachment)) {
17183
- return /* @__PURE__ */ jsxRuntime.jsx(
17184
- ImageAttachmentPreview,
17185
- {
17186
- attachment,
17187
- handleRetry: messageComposer.attachmentManager.uploadAttachment,
17188
- removeAttachments: messageComposer.attachmentManager.removeAttachments
17189
- },
17190
- attachment.localMetadata.id || attachment.image_url
17191
- );
17192
- } else if (streamChat.isLocalFileAttachment(attachment)) {
17193
- return /* @__PURE__ */ jsxRuntime.jsx(
17194
- FileAttachmentPreview$1,
17195
- {
17196
- attachment,
17197
- handleRetry: messageComposer.attachmentManager.uploadAttachment,
17198
- removeAttachments: messageComposer.attachmentManager.removeAttachments
17199
- },
17200
- attachment.localMetadata.id || attachment.asset_url
17201
- );
17202
- } else if (streamChat.isLocalAttachment(attachment)) {
17203
- return /* @__PURE__ */ jsxRuntime.jsx(
17204
- UnsupportedAttachmentPreview$1,
17205
- {
17206
- attachment,
17207
- handleRetry: messageComposer.attachmentManager.uploadAttachment,
17208
- removeAttachments: messageComposer.attachmentManager.removeAttachments
17209
- },
17210
- attachment.localMetadata.id
17211
- );
17212
- }
17213
- return null;
17214
- })
17377
+ children: [
17378
+ attachments.map((attachment) => {
17379
+ if (streamChat.isScrapedContent(attachment)) return null;
17380
+ if (streamChat.isLocalVoiceRecordingAttachment(attachment)) return null;
17381
+ if (streamChat.isLocalAudioAttachment(attachment)) {
17382
+ return /* @__PURE__ */ jsxRuntime.jsx(
17383
+ AudioAttachmentPreview2,
17384
+ {
17385
+ attachment,
17386
+ handleRetry: messageComposer.attachmentManager.uploadAttachment,
17387
+ removeAttachments: messageComposer.attachmentManager.removeAttachments
17388
+ },
17389
+ attachment.localMetadata.id || attachment.asset_url
17390
+ );
17391
+ } else if (streamChat.isLocalVideoAttachment(attachment)) {
17392
+ return /* @__PURE__ */ jsxRuntime.jsx(
17393
+ VideoAttachmentPreview,
17394
+ {
17395
+ attachment,
17396
+ handleRetry: messageComposer.attachmentManager.uploadAttachment,
17397
+ openPreview: () => openPreviewAtIndex(previewIndexById[attachment.localMetadata.id] ?? 0),
17398
+ removeAttachments: messageComposer.attachmentManager.removeAttachments
17399
+ },
17400
+ attachment.localMetadata.id || attachment.asset_url
17401
+ );
17402
+ } else if (streamChat.isLocalImageAttachment(attachment)) {
17403
+ return /* @__PURE__ */ jsxRuntime.jsx(
17404
+ ImageAttachmentPreview,
17405
+ {
17406
+ attachment,
17407
+ handleRetry: messageComposer.attachmentManager.uploadAttachment,
17408
+ openPreview: () => openPreviewAtIndex(previewIndexById[attachment.localMetadata.id] ?? 0),
17409
+ removeAttachments: messageComposer.attachmentManager.removeAttachments
17410
+ },
17411
+ attachment.localMetadata.id || attachment.image_url
17412
+ );
17413
+ } else if (streamChat.isLocalFileAttachment(attachment)) {
17414
+ return /* @__PURE__ */ jsxRuntime.jsx(
17415
+ FileAttachmentPreview$1,
17416
+ {
17417
+ attachment,
17418
+ handleRetry: messageComposer.attachmentManager.uploadAttachment,
17419
+ removeAttachments: messageComposer.attachmentManager.removeAttachments
17420
+ },
17421
+ attachment.localMetadata.id || attachment.asset_url
17422
+ );
17423
+ } else if (streamChat.isLocalAttachment(attachment)) {
17424
+ return /* @__PURE__ */ jsxRuntime.jsx(
17425
+ UnsupportedAttachmentPreview$1,
17426
+ {
17427
+ attachment,
17428
+ handleRetry: messageComposer.attachmentManager.uploadAttachment,
17429
+ removeAttachments: messageComposer.attachmentManager.removeAttachments
17430
+ },
17431
+ attachment.localMetadata.id
17432
+ );
17433
+ }
17434
+ return null;
17435
+ }),
17436
+ galleryItems.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
17437
+ Modal,
17438
+ {
17439
+ className: "str-chat__gallery-modal",
17440
+ onClose: () => setShowPreview(false),
17441
+ open: showPreview,
17442
+ children: /* @__PURE__ */ jsxRuntime.jsx(Gallery, { initialIndex: initialIndexRef.current, items: galleryItems })
17443
+ }
17444
+ )
17445
+ ]
17215
17446
  }
17216
17447
  );
17217
17448
  };
@@ -17239,11 +17470,9 @@ const VoiceRecordingPreviewSlot = ({
17239
17470
  }
17240
17471
  );
17241
17472
  };
17242
- const textComposerStateSelector$4 = ({ command }) => ({ command });
17243
- const CommandChip = () => {
17473
+ const CommandChip = ({ command }) => {
17244
17474
  const { textComposer } = WithAudioPlayback.useMessageComposerController();
17245
17475
  const { textareaRef } = WithAudioPlayback.useMessageComposerContext();
17246
- const { command } = WithAudioPlayback.useStateStore(textComposer.state, textComposerStateSelector$4);
17247
17476
  if (!command) return null;
17248
17477
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__command-chip", children: [
17249
17478
  /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconThunder, {}),
@@ -17542,9 +17771,12 @@ const ToggleRecordingButton = () => {
17542
17771
  );
17543
17772
  };
17544
17773
  const AudioRecorderRecordingControls = () => {
17774
+ const { client } = WithAudioPlayback.useChatContext();
17775
+ const { t } = WithAudioPlayback.useTranslationContext();
17545
17776
  const {
17546
17777
  recordingController: { completeRecording, recorder, recording, recordingState }
17547
17778
  } = WithAudioPlayback.useMessageComposerContext();
17779
+ const panel = WithAudioPlayback.useNotificationTarget();
17548
17780
  const isUploadingFile = recording?.localMetadata?.uploadState === "uploading";
17549
17781
  if (!recorder) return null;
17550
17782
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__audio_recorder__recording-controls", children: [
@@ -17556,7 +17788,17 @@ const AudioRecorderRecordingControls = () => {
17556
17788
  className: "str-chat__audio_recorder__cancel-button",
17557
17789
  "data-testid": "cancel-recording-audio-button",
17558
17790
  disabled: isUploadingFile,
17559
- onClick: recorder.cancel,
17791
+ onClick: () => {
17792
+ recorder.cancel();
17793
+ client.notifications.addInfo({
17794
+ message: t("Voice message deleted"),
17795
+ options: {
17796
+ tags: WithAudioPlayback.addNotificationTargetTag(panel),
17797
+ type: "audioRecording:cancel:success"
17798
+ },
17799
+ origin: { emitter: "AudioRecorder" }
17800
+ });
17801
+ },
17560
17802
  size: "sm",
17561
17803
  variant: "secondary",
17562
17804
  children: /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconTrashBin, {})
@@ -17863,7 +18105,10 @@ const SuggestionList = ({
17863
18105
  setFocusedItemIndex,
17864
18106
  suggestionItemComponents = defaultComponents
17865
18107
  }) => {
17866
- const { AutocompleteSuggestionItem = SuggestionListItem } = WithAudioPlayback.useComponentContext();
18108
+ const {
18109
+ AutocompleteSuggestionItem = SuggestionListItem,
18110
+ ContextMenu: ContextMenuComponent = ContextMenu
18111
+ } = WithAudioPlayback.useComponentContext();
17867
18112
  const { textareaRef } = WithAudioPlayback.useMessageComposerContext();
17868
18113
  const messageComposer = WithAudioPlayback.useMessageComposerController();
17869
18114
  const { textComposer } = messageComposer;
@@ -17981,7 +18226,7 @@ const SuggestionList = ({
17981
18226
  zIndex: 1e3
17982
18227
  },
17983
18228
  children: /* @__PURE__ */ jsxRuntime.jsx(
17984
- ContextMenu,
18229
+ ContextMenuComponent,
17985
18230
  {
17986
18231
  className: clsx("str-chat__suggestion-list", className),
17987
18232
  Header: suggestions.searchSource.type === "commands" ? CommandsMenuHeader : void 0,
@@ -18080,6 +18325,12 @@ const TextareaComposer = ({
18080
18325
  textComposer.state,
18081
18326
  textComposerStateSelector$1
18082
18327
  );
18328
+ const autosizeRows = !text2 && minRows == null ? 1 : void 0;
18329
+ const textareaStyle = text2 ? void 0 : {
18330
+ overflow: "hidden",
18331
+ textOverflow: "ellipsis",
18332
+ whiteSpace: "nowrap"
18333
+ };
18083
18334
  const { enabled } = WithAudioPlayback.useStateStore(messageComposer.configState, configStateSelector);
18084
18335
  const { quotedMessage } = WithAudioPlayback.useStateStore(
18085
18336
  messageComposer.state,
@@ -18233,8 +18484,8 @@ const TextareaComposer = ({
18233
18484
  ),
18234
18485
  "data-testid": "message-input",
18235
18486
  disabled: !enabled || !!cooldownRemaining,
18236
- maxRows,
18237
- minRows,
18487
+ maxRows: autosizeRows ?? maxRows,
18488
+ minRows: autosizeRows ?? minRows,
18238
18489
  onBlur,
18239
18490
  onChange: changeHandler,
18240
18491
  onCompositionEnd,
@@ -18246,7 +18497,8 @@ const TextareaComposer = ({
18246
18497
  placeholder,
18247
18498
  ref: (ref) => {
18248
18499
  textareaRef.current = ref;
18249
- }
18500
+ },
18501
+ style: textareaStyle
18250
18502
  }
18251
18503
  ),
18252
18504
  !isComposing && /* @__PURE__ */ jsxRuntime.jsx(
@@ -18578,7 +18830,7 @@ const MessageComposerUI = () => {
18578
18830
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__message-composer-controls", children: [
18579
18831
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__message-composer-controls__text-composition-controls", children: [
18580
18832
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__message-composer-controls__text-composition-controls__text", children: [
18581
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__message-composer-controls__text-composition-controls__command-chip-container", children: /* @__PURE__ */ jsxRuntime.jsx(CommandChip$1, {}) }),
18833
+ command && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__message-composer-controls__text-composition-controls__command-chip-container", children: /* @__PURE__ */ jsxRuntime.jsx(CommandChip$1, { command }) }),
18582
18834
  /* @__PURE__ */ jsxRuntime.jsx(TextareaComposer$1, {})
18583
18835
  ] }),
18584
18836
  /* @__PURE__ */ jsxRuntime.jsx(SendToChannelCheckbox$1, {})
@@ -18653,7 +18905,7 @@ const MessageComposerProvider = (props) => {
18653
18905
  const messageComposer = WithAudioPlayback.useMessageComposerController();
18654
18906
  React.useEffect(
18655
18907
  () => () => {
18656
- messageComposer.createDraft();
18908
+ messageComposer.createDraft().finally(() => messageComposer.clear());
18657
18909
  },
18658
18910
  [messageComposer]
18659
18911
  );
@@ -18971,7 +19223,7 @@ const useChat = ({
18971
19223
  };
18972
19224
  React.useEffect(() => {
18973
19225
  if (!client) return;
18974
- const version = "14.0.0-beta.1";
19226
+ const version = "14.0.0-beta.3";
18975
19227
  const userAgent = client.getUserAgent();
18976
19228
  if (!userAgent.includes("stream-chat-react")) {
18977
19229
  client.setUserAgent(`stream-chat-react-${version}-${userAgent}`);
@@ -19472,13 +19724,13 @@ const ReactionSelector = (props) => {
19472
19724
  ) })
19473
19725
  ] });
19474
19726
  };
19475
- ReactionSelector.getDialogId = (({ messageId, threadList }) => {
19727
+ ReactionSelector.getDialogId = ({ messageId, threadList }) => {
19476
19728
  const dialogIdNamespace = threadList ? "-thread" : "";
19477
- return `reaction-selector${dialogIdNamespace}--${messageId}`;
19478
- });
19729
+ return `reaction-selector${dialogIdNamespace}-${messageId}`;
19730
+ };
19479
19731
  ReactionSelector.displayName = "ReactionSelector";
19480
19732
  const ReactionSelectorWithButton = ({
19481
- ReactionIcon: ReactionIcon2
19733
+ ReactionIcon
19482
19734
  }) => {
19483
19735
  const { t } = WithAudioPlayback.useTranslationContext("ReactionSelectorWithButton");
19484
19736
  const { isMyMessage, message, threadList } = useMessageContext();
@@ -19512,7 +19764,7 @@ const ReactionSelectorWithButton = ({
19512
19764
  "data-testid": "message-reaction-action",
19513
19765
  onClick: () => dialog?.toggle(),
19514
19766
  ref: buttonRef,
19515
- children: /* @__PURE__ */ jsxRuntime.jsx(ReactionIcon2, { className: "str-chat__message-action-icon" })
19767
+ children: /* @__PURE__ */ jsxRuntime.jsx(ReactionIcon, { className: "str-chat__message-action-icon" })
19516
19768
  }
19517
19769
  )
19518
19770
  ] });
@@ -19560,6 +19812,7 @@ const QuickMessageActionsButton = ({ className, ...props }) => /* @__PURE__ */ j
19560
19812
  appearance: "ghost",
19561
19813
  circular: true,
19562
19814
  className: clsx("str-chat__message-actions-box-button", className),
19815
+ size: "sm",
19563
19816
  variant: "secondary",
19564
19817
  ...props
19565
19818
  }
@@ -19849,8 +20102,7 @@ const DefaultMessageActionComponents = {
19849
20102
  const { closeMenu } = useContextMenuContext();
19850
20103
  const { client } = WithAudioPlayback.useChatContext();
19851
20104
  const { Modal = GlobalModal } = WithAudioPlayback.useComponentContext();
19852
- const { removeMessage } = WithAudioPlayback.useChannelActionContext();
19853
- const { handleDelete, message } = useMessageContext();
20105
+ const { handleDelete } = useMessageContext();
19854
20106
  const panel = WithAudioPlayback.useNotificationTarget();
19855
20107
  const { t } = WithAudioPlayback.useTranslationContext();
19856
20108
  const [openModal, setOpenModal] = React.useState(false);
@@ -19876,29 +20128,20 @@ const DefaultMessageActionComponents = {
19876
20128
  closeMenu();
19877
20129
  },
19878
20130
  onDelete: async () => {
19879
- if (message.type === "error") removeMessage(message);
19880
- else {
19881
- try {
19882
- await handleDelete();
19883
- client.notifications.addSuccess({
19884
- message: t("Message deleted"),
19885
- options: {
19886
- tags: WithAudioPlayback.addNotificationTargetTag(panel)
19887
- },
19888
- origin: { emitter: "MessageActions" }
19889
- });
19890
- } catch (error) {
19891
- client.notifications.addError({
19892
- message: t("Failed to delete the message"),
19893
- options: {
19894
- tags: WithAudioPlayback.addNotificationTargetTag(panel)
19895
- },
19896
- origin: { emitter: "MessageActions" }
19897
- });
19898
- }
20131
+ try {
20132
+ await handleDelete();
20133
+ client.notifications.addSuccess({
20134
+ message: t("Message deleted"),
20135
+ options: {
20136
+ tags: WithAudioPlayback.addNotificationTargetTag(panel)
20137
+ },
20138
+ origin: { emitter: "MessageActions" }
20139
+ });
20140
+ } catch {
20141
+ } finally {
20142
+ setOpenModal(false);
20143
+ closeMenu();
19899
20144
  }
19900
- setOpenModal(false);
19901
- closeMenu();
19902
20145
  }
19903
20146
  }
19904
20147
  ) })
@@ -19930,8 +20173,34 @@ const DefaultMessageActionComponents = {
19930
20173
  }
19931
20174
  },
19932
20175
  quick: {
20176
+ // eslint-disable-next-line react/display-name
20177
+ DropdownToggle: React.forwardRef((_, ref) => {
20178
+ const { t } = WithAudioPlayback.useTranslationContext();
20179
+ const { message } = useMessageContext();
20180
+ const dropdownDialogIsOpen = useDialogIsOpen(
20181
+ MessageActions.getDialogId({ messageId: message.id })
20182
+ );
20183
+ const { dialog } = useDialogOnNearestManager({
20184
+ id: MessageActions.getDialogId({ messageId: message.id })
20185
+ });
20186
+ return /* @__PURE__ */ jsxRuntime.jsx(
20187
+ QuickMessageActionsButton,
20188
+ {
20189
+ "aria-expanded": dropdownDialogIsOpen,
20190
+ "aria-haspopup": "true",
20191
+ "aria-label": t("aria/Open Message Actions Menu"),
20192
+ className: "str-chat__message-actions-box-button",
20193
+ "data-testid": "message-actions-toggle-button",
20194
+ onClick: () => {
20195
+ dialog?.toggle();
20196
+ },
20197
+ ref,
20198
+ children: /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconDotGrid1x3Horizontal, { className: "str-chat__message-action-icon" })
20199
+ }
20200
+ );
20201
+ }),
19933
20202
  React() {
19934
- return /* @__PURE__ */ jsxRuntime.jsx(ReactionSelectorWithButton, { ReactionIcon });
20203
+ return /* @__PURE__ */ jsxRuntime.jsx(ReactionSelectorWithButton, { ReactionIcon: WithAudioPlayback.IconEmojiSmile });
19935
20204
  },
19936
20205
  Reply() {
19937
20206
  const { handleOpenThread } = useMessageContext();
@@ -19943,14 +20212,17 @@ const DefaultMessageActionComponents = {
19943
20212
  className: "str-chat__message-reply-in-thread-button",
19944
20213
  "data-testid": "thread-action",
19945
20214
  onClick: handleOpenThread,
19946
- children: /* @__PURE__ */ jsxRuntime.jsx(ThreadIcon, { className: "str-chat__message-action-icon" })
20215
+ children: /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconArrowShareLeft, { className: "str-chat__message-action-icon" })
19947
20216
  }
19948
20217
  );
19949
20218
  }
19950
20219
  }
19951
20220
  };
19952
20221
  const defaultMessageActionSet = [
19953
- // { placement: 'dropdown', type: 'block' },
20222
+ {
20223
+ Component: DefaultMessageActionComponents.quick.DropdownToggle,
20224
+ placement: "quick-dropdown-toggle"
20225
+ },
19954
20226
  {
19955
20227
  Component: DefaultMessageActionComponents.quick.Reply,
19956
20228
  placement: "quick",
@@ -20040,14 +20312,14 @@ function useFetchReactions(options) {
20040
20312
  const handleFetchReactions = propHandleFetchReactions ?? contextHandleFetchReactions;
20041
20313
  const [refetchNonce, setRefetchNonce] = React.useState(null);
20042
20314
  React.useEffect(() => {
20043
- if (!shouldFetch || !reactionType) {
20315
+ if (!shouldFetch) {
20044
20316
  return;
20045
20317
  }
20046
20318
  let cancel = false;
20047
20319
  (async () => {
20048
20320
  try {
20049
20321
  setIsLoading(true);
20050
- const reactions2 = await handleFetchReactions(reactionType, sort);
20322
+ const reactions2 = await handleFetchReactions(reactionType ?? void 0, sort);
20051
20323
  if (!cancel) {
20052
20324
  setReactions(reactions2);
20053
20325
  }
@@ -20071,6 +20343,16 @@ function useFetchReactions(options) {
20071
20343
  return { isLoading, reactions, refetch };
20072
20344
  }
20073
20345
  const defaultReactionDetailsSort = { created_at: -1 };
20346
+ const MessageReactionsDetailLoadingIndicator = () => {
20347
+ const elements = React.useMemo(
20348
+ () => Array.from({ length: 3 }, (_, index) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__message-reactions-detail__skeleton-item", children: [
20349
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__message-reactions-detail__skeleton-avatar" }),
20350
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__message-reactions-detail__skeleton-line" })
20351
+ ] }, index)),
20352
+ []
20353
+ );
20354
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: elements });
20355
+ };
20074
20356
  function MessageReactionsDetail({
20075
20357
  handleFetchReactions,
20076
20358
  onSelectedReactionTypeChange,
@@ -20081,7 +20363,11 @@ function MessageReactionsDetail({
20081
20363
  totalReactionCount
20082
20364
  }) {
20083
20365
  const { client } = WithAudioPlayback.useChatContext();
20084
- const { Avatar: Avatar$1 = Avatar } = WithAudioPlayback.useComponentContext(MessageReactionsDetail.name);
20366
+ const {
20367
+ Avatar: Avatar$1 = Avatar,
20368
+ LoadingIndicator: LoadingIndicator2 = MessageReactionsDetailLoadingIndicator,
20369
+ reactionOptions = defaultReactionOptions
20370
+ } = WithAudioPlayback.useComponentContext(MessageReactionsDetail.name);
20085
20371
  const { t } = WithAudioPlayback.useTranslationContext();
20086
20372
  const {
20087
20373
  handleReaction: contextHandleReaction,
@@ -20108,7 +20394,7 @@ function MessageReactionsDetail({
20108
20394
  "div",
20109
20395
  {
20110
20396
  className: "str-chat__message-reactions-detail",
20111
- "data-testid": "reactions-list-modal",
20397
+ "data-testid": "message-reactions-detail",
20112
20398
  children: [
20113
20399
  typeof totalReactionCount === "number" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__message-reactions-detail__total-count", children: t("{{ count }} reactions", { count: totalReactionCount }) }),
20114
20400
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__message-reactions-detail__reaction-type-list-container", children: /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "str-chat__message-reactions-detail__reaction-type-list", children: reactions.map(
@@ -20121,7 +20407,9 @@ function MessageReactionsDetail({
20121
20407
  {
20122
20408
  "aria-pressed": reactionType === selectedReactionType,
20123
20409
  className: "str-chat__message-reactions-detail__reaction-type-list-item-button",
20124
- onClick: () => onSelectedReactionTypeChange?.(reactionType),
20410
+ onClick: () => onSelectedReactionTypeChange?.(
20411
+ selectedReactionType === reactionType ? null : reactionType
20412
+ ),
20125
20413
  children: [
20126
20414
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "str-chat__message-reactions-detail__reaction-type-list-item-icon", children: /* @__PURE__ */ jsxRuntime.jsx(EmojiComponent, {}) }),
20127
20415
  reactionCount > 1 && /* @__PURE__ */ jsxRuntime.jsx(
@@ -20145,22 +20433,10 @@ function MessageReactionsDetail({
20145
20433
  className: "str-chat__message-reactions-detail__user-list",
20146
20434
  "data-testid": "all-reacting-users",
20147
20435
  children: [
20148
- areReactionsLoading && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
20149
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__message-reactions-detail__skeleton-item", children: [
20150
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__message-reactions-detail__skeleton-avatar" }),
20151
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__message-reactions-detail__skeleton-line" })
20152
- ] }),
20153
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__message-reactions-detail__skeleton-item", children: [
20154
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__message-reactions-detail__skeleton-avatar" }),
20155
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__message-reactions-detail__skeleton-line" })
20156
- ] }),
20157
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__message-reactions-detail__skeleton-item", children: [
20158
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__message-reactions-detail__skeleton-avatar" }),
20159
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__message-reactions-detail__skeleton-line" })
20160
- ] })
20161
- ] }),
20162
- !areReactionsLoading && /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: reactionDetailsWithLegacyFallback.map(({ user }) => {
20436
+ areReactionsLoading && /* @__PURE__ */ jsxRuntime.jsx(LoadingIndicator2, {}),
20437
+ !areReactionsLoading && /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: reactionDetailsWithLegacyFallback.map(({ type, user }) => {
20163
20438
  const belongsToCurrentUser = client.user?.id === user?.id;
20439
+ const EmojiComponent = Array.isArray(reactionOptions) ? void 0 : reactionOptions.quick[type]?.Component ?? reactionOptions.extended?.[type]?.Component;
20164
20440
  return /* @__PURE__ */ jsxRuntime.jsxs(
20165
20441
  "div",
20166
20442
  {
@@ -20185,23 +20461,23 @@ function MessageReactionsDetail({
20185
20461
  children: belongsToCurrentUser ? t("You") : user?.name || user?.id
20186
20462
  }
20187
20463
  ),
20188
- belongsToCurrentUser && selectedReactionType && /* @__PURE__ */ jsxRuntime.jsx(
20464
+ belongsToCurrentUser && /* @__PURE__ */ jsxRuntime.jsx(
20189
20465
  "button",
20190
20466
  {
20191
20467
  className: "str-chat__message-reactions-detail__user-list-item-button",
20192
20468
  "data-testid": "remove-reaction-button",
20193
- onClick: (e) => {
20194
- contextHandleReaction(selectedReactionType, e).then(() => {
20195
- refetch();
20196
- });
20469
+ onClick: async (e) => {
20470
+ await contextHandleReaction(type, e);
20471
+ refetch();
20197
20472
  },
20198
20473
  children: t("Tap to remove")
20199
20474
  }
20200
20475
  )
20201
- ] })
20476
+ ] }),
20477
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "str-chat__message-reactions-detail__user-list-item-icon", children: EmojiComponent && !selectedReactionType && /* @__PURE__ */ jsxRuntime.jsx(EmojiComponent, {}) })
20202
20478
  ]
20203
20479
  },
20204
- user?.id
20480
+ `${user?.id}-${type}`
20205
20481
  );
20206
20482
  }) })
20207
20483
  ]
@@ -20350,6 +20626,7 @@ const UnMemoizedMessageReactions = (props) => {
20350
20626
  const divRef = React.useRef(null);
20351
20627
  const dialogId2 = `message-reactions-detail-${message.id}`;
20352
20628
  const { dialog, dialogManager } = useDialogOnNearestManager({ id: dialogId2 });
20629
+ const isDialogOpen = useDialogIsOpen(dialogId2, dialogManager?.id);
20353
20630
  const handleReactionButtonClick = (reactionType) => {
20354
20631
  if (totalReactionCount > MAX_MESSAGE_REACTIONS_TO_FETCH) {
20355
20632
  return;
@@ -20384,6 +20661,8 @@ const UnMemoizedMessageReactions = (props) => {
20384
20661
  children: /* @__PURE__ */ jsxRuntime.jsxs(
20385
20662
  FragmentOrButton,
20386
20663
  {
20664
+ "aria-expanded": isDialogOpen,
20665
+ "aria-pressed": isDialogOpen,
20387
20666
  buttonIf: visualStyle === "clustered",
20388
20667
  className: "str-chat__message-reactions__list-button",
20389
20668
  onClick: () => handleReactionButtonClick(existingReactions[0]?.reactionType ?? null),
@@ -20421,9 +20700,7 @@ const UnMemoizedMessageReactions = (props) => {
20421
20700
  "button",
20422
20701
  {
20423
20702
  className: "str-chat__message-reactions__list-item-button",
20424
- onClick: () => handleReactionButtonClick(
20425
- existingReactions.at(-1)?.reactionType ?? null
20426
- ),
20703
+ onClick: () => handleReactionButtonClick(null),
20427
20704
  children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "str-chat__message-reactions__overflow-count", children: [
20428
20705
  "+",
20429
20706
  totalReactionCount - cappedExistingReactions.reactionCountToDisplay
@@ -20548,91 +20825,74 @@ const StreamEmoji = ({
20548
20825
  const useSplitActionSet = (actionSet) => React.useMemo(() => {
20549
20826
  const quickActionSet = [];
20550
20827
  const dropdownActionSet = [];
20828
+ let quickDropdownToggleAction;
20551
20829
  for (const action of actionSet) {
20552
20830
  if (action.placement === "quick")
20553
20831
  quickActionSet.push(action);
20554
20832
  if (action.placement === "dropdown")
20555
20833
  dropdownActionSet.push(action);
20834
+ if (action.placement === "quick-dropdown-toggle") {
20835
+ quickDropdownToggleAction ?? (quickDropdownToggleAction = action);
20836
+ }
20556
20837
  }
20557
- return { dropdownActionSet, quickActionSet };
20838
+ return { dropdownActionSet, quickActionSet, quickDropdownToggleAction };
20558
20839
  }, [actionSet]);
20559
20840
  const MessageActions = ({
20560
20841
  disableBaseMessageActionSetFilter = false,
20561
20842
  messageActionSet = defaultMessageActionSet
20562
20843
  }) => {
20563
- const { theme } = WithAudioPlayback.useChatContext();
20564
20844
  const { isMyMessage, message, threadList } = useMessageContext();
20845
+ const { ContextMenu: ContextMenuComponent = ContextMenu } = WithAudioPlayback.useComponentContext();
20565
20846
  const { t } = WithAudioPlayback.useTranslationContext();
20566
20847
  const [actionsBoxButtonElement, setActionsBoxButtonElement] = React.useState(null);
20567
20848
  const filteredMessageActionSet = useBaseMessageActionSetFilter(
20568
20849
  messageActionSet,
20569
20850
  disableBaseMessageActionSetFilter
20570
20851
  );
20571
- const { dropdownActionSet, quickActionSet } = useSplitActionSet(
20572
- filteredMessageActionSet
20573
- );
20574
- const dropdownDialogId = `message-actions--${message.id}`;
20852
+ const { dropdownActionSet, quickActionSet, quickDropdownToggleAction } = useSplitActionSet(filteredMessageActionSet);
20853
+ const messageActionsDialogId = MessageActions.getDialogId({ messageId: message.id });
20575
20854
  const reactionSelectorDialogId = ReactionSelector.getDialogId({
20576
20855
  messageId: message.id,
20577
20856
  threadList
20578
20857
  });
20579
- const { dialog, dialogManager } = useDialogOnNearestManager({ id: dropdownDialogId });
20580
- const dropdownDialogIsOpen = useDialogIsOpen(dropdownDialogId, dialogManager?.id);
20858
+ const { dialog, dialogManager } = useDialogOnNearestManager({
20859
+ id: messageActionsDialogId
20860
+ });
20861
+ const messageActionsDialogIsOpen = useDialogIsOpen(
20862
+ messageActionsDialogId,
20863
+ dialogManager?.id
20864
+ );
20581
20865
  const reactionSelectorDialogIsOpen = useDialogIsOpen(
20582
20866
  reactionSelectorDialogId,
20583
20867
  dialogManager?.id
20584
20868
  );
20585
- const contextMenuItems = React.useMemo(
20586
- () => dropdownActionSet.map(({ Component }) => {
20587
- const ActionItem = (menuProps) => /* @__PURE__ */ jsxRuntime.jsx(Component, { ...menuProps });
20588
- return ActionItem;
20589
- }),
20590
- [dropdownActionSet]
20591
- );
20592
20869
  if (dropdownActionSet.length + quickActionSet.length === 0) {
20593
20870
  return null;
20594
20871
  }
20595
20872
  return /* @__PURE__ */ jsxRuntime.jsxs(
20596
20873
  "div",
20597
20874
  {
20598
- className: clsx(`str-chat__message-${theme}__actions str-chat__message-options`, {
20599
- "str-chat__message-options--active": dropdownDialogIsOpen || reactionSelectorDialogIsOpen
20875
+ className: clsx("str-chat__message-options", {
20876
+ "str-chat__message-options--active": messageActionsDialogIsOpen || reactionSelectorDialogIsOpen
20600
20877
  }),
20601
20878
  children: [
20602
- dropdownActionSet.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
20603
- /* @__PURE__ */ jsxRuntime.jsx(
20604
- WithAudioPlayback.Button,
20605
- {
20606
- appearance: "ghost",
20607
- "aria-expanded": dropdownDialogIsOpen,
20608
- "aria-haspopup": "true",
20609
- "aria-label": t("aria/Open Message Actions Menu"),
20610
- circular: true,
20611
- className: "str-chat__message-actions-box-button",
20612
- "data-testid": "message-actions-toggle-button",
20613
- onClick: () => {
20614
- dialog?.toggle();
20615
- },
20616
- ref: setActionsBoxButtonElement,
20617
- variant: "secondary",
20618
- children: /* @__PURE__ */ jsxRuntime.jsx(ActionsIcon, { className: "str-chat__message-action-icon" })
20619
- }
20620
- ),
20879
+ quickDropdownToggleAction && dropdownActionSet.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
20880
+ /* @__PURE__ */ jsxRuntime.jsx(quickDropdownToggleAction.Component, { ref: setActionsBoxButtonElement }),
20621
20881
  /* @__PURE__ */ jsxRuntime.jsx(
20622
- ContextMenu,
20882
+ ContextMenuComponent,
20623
20883
  {
20624
20884
  backLabel: t("Back"),
20625
20885
  className: clsx("str-chat__message-actions-box", {
20626
- "str-chat__message-actions-box--open": dropdownDialogIsOpen
20886
+ "str-chat__message-actions-box--open": messageActionsDialogIsOpen
20627
20887
  }),
20628
20888
  dialogManagerId: dialogManager?.id,
20629
- id: dropdownDialogId,
20630
- items: contextMenuItems,
20889
+ id: messageActionsDialogId,
20631
20890
  onClose: dialog?.close,
20632
20891
  placement: isMyMessage() ? "top-end" : "top-start",
20633
20892
  referenceElement: actionsBoxButtonElement,
20634
20893
  tabIndex: -1,
20635
- trapFocus: true
20894
+ trapFocus: true,
20895
+ children: dropdownActionSet.map(({ Component, type }) => /* @__PURE__ */ jsxRuntime.jsx(Component, {}, type))
20636
20896
  }
20637
20897
  )
20638
20898
  ] }),
@@ -20641,6 +20901,8 @@ const MessageActions = ({
20641
20901
  }
20642
20902
  );
20643
20903
  };
20904
+ MessageActions.getDialogId = ({ messageId }) => `message-actions-${messageId}`;
20905
+ MessageActions.displayName = "MessageActions";
20644
20906
  const MessageUIWithContext = ({
20645
20907
  endOfGroup,
20646
20908
  firstOfGroup,
@@ -20804,7 +21066,7 @@ const MessageUIWithContext = ({
20804
21066
  isAIGenerated ? /* @__PURE__ */ jsxRuntime.jsx(StreamedMessageText$1, { message, renderText: renderText2 }) : /* @__PURE__ */ jsxRuntime.jsx(MessageText, { message, renderText: renderText2 })
20805
21067
  ] }),
20806
21068
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__message-reactions-host", children: hasReactions && /* @__PURE__ */ jsxRuntime.jsx(MessageReactions$1, { reverse: true }) }),
20807
- /* @__PURE__ */ jsxRuntime.jsx(MessageErrorIcon, {})
21069
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__message-error-indicator", children: /* @__PURE__ */ jsxRuntime.jsx(ErrorBadge, {}) })
20808
21070
  ] })
20809
21071
  ]
20810
21072
  }
@@ -21467,6 +21729,7 @@ const useScrollLocationLogic = (params) => {
21467
21729
  const closeToBottom = React.useRef(false);
21468
21730
  const closeToTop = React.useRef(false);
21469
21731
  const previousScrollTopRef = React.useRef(0);
21732
+ const previousMessagesLengthRef = React.useRef(messages.length);
21470
21733
  const anchorRestoreCleanupRef = React.useRef(null);
21471
21734
  const captureAnchor = React.useCallback(() => {
21472
21735
  if (!listElement) return null;
@@ -21618,6 +21881,60 @@ const useScrollLocationLogic = (params) => {
21618
21881
  scrollToBottom();
21619
21882
  }
21620
21883
  }, [disableAutoScrollToBottom, justReachedLatestMessageSet, listElement, hasMoreNewer]);
21884
+ React.useLayoutEffect(() => {
21885
+ if (!listElement || disableAutoScrollToBottom || hasMoreNewer || suppressAutoscroll || justReachedLatestMessageSet || isRestoringOlderAnchorRef.current) {
21886
+ return;
21887
+ }
21888
+ const initialDistanceToBottom = listElement.scrollHeight - (listElement.scrollTop + listElement.clientHeight);
21889
+ const messagesHydrated = previousMessagesLengthRef.current === 0 && messages.length > 0;
21890
+ if (initialDistanceToBottom > scrolledUpThreshold && !messagesHydrated) {
21891
+ return;
21892
+ }
21893
+ let keepPinnedToBottom = true;
21894
+ const maybeScrollToBottom = () => {
21895
+ if (keepPinnedToBottom) {
21896
+ scrollToBottom();
21897
+ }
21898
+ };
21899
+ maybeScrollToBottom();
21900
+ const settleDelays = [80, messagesHydrated ? 260 : 420, 900, 1700];
21901
+ const settleTimeoutIds = settleDelays.map(
21902
+ (delay) => setTimeout(maybeScrollToBottom, delay)
21903
+ );
21904
+ const stopKeepingPinnedToBottom = () => {
21905
+ keepPinnedToBottom = false;
21906
+ };
21907
+ listElement.addEventListener("pointerdown", stopKeepingPinnedToBottom, {
21908
+ passive: true
21909
+ });
21910
+ listElement.addEventListener("touchstart", stopKeepingPinnedToBottom, {
21911
+ passive: true
21912
+ });
21913
+ listElement.addEventListener("wheel", stopKeepingPinnedToBottom, {
21914
+ passive: true
21915
+ });
21916
+ listElement.addEventListener("keydown", stopKeepingPinnedToBottom);
21917
+ const pinWindowTimeoutId = setTimeout(() => {
21918
+ stopKeepingPinnedToBottom();
21919
+ }, 2200);
21920
+ return () => {
21921
+ settleTimeoutIds.forEach(clearTimeout);
21922
+ clearTimeout(pinWindowTimeoutId);
21923
+ listElement.removeEventListener("pointerdown", stopKeepingPinnedToBottom);
21924
+ listElement.removeEventListener("touchstart", stopKeepingPinnedToBottom);
21925
+ listElement.removeEventListener("wheel", stopKeepingPinnedToBottom);
21926
+ listElement.removeEventListener("keydown", stopKeepingPinnedToBottom);
21927
+ };
21928
+ }, [
21929
+ disableAutoScrollToBottom,
21930
+ hasMoreNewer,
21931
+ justReachedLatestMessageSet,
21932
+ listElement,
21933
+ messages.length,
21934
+ scrollToBottom,
21935
+ scrolledUpThreshold,
21936
+ suppressAutoscroll
21937
+ ]);
21621
21938
  const updateScrollTop = useMessageListScrollManager({
21622
21939
  captureAnchor,
21623
21940
  disableScrollManagement: disableScrollManagement || isRestoringOlderAnchorRef.current,
@@ -21644,6 +21961,9 @@ const useScrollLocationLogic = (params) => {
21644
21961
  React.useLayoutEffect(() => {
21645
21962
  previousHasMoreNewerRef.current = hasMoreNewer;
21646
21963
  }, [hasMoreNewer]);
21964
+ React.useLayoutEffect(() => {
21965
+ previousMessagesLengthRef.current = messages.length;
21966
+ }, [messages.length]);
21647
21967
  const onScroll = React.useCallback(
21648
21968
  (event) => {
21649
21969
  const element = event.target;
@@ -21652,8 +21972,10 @@ const useScrollLocationLogic = (params) => {
21652
21972
  updateScrollTop(scrollTop, captureAnchor);
21653
21973
  const offsetHeight = element.offsetHeight;
21654
21974
  const scrollHeight = element.scrollHeight;
21975
+ const distanceToBottom = scrollHeight - (scrollTop + offsetHeight);
21976
+ const bottomEnterThreshold = Math.max(Math.floor(scrolledUpThreshold * 0.6), 24);
21655
21977
  const prevCloseToBottom = closeToBottom.current;
21656
- closeToBottom.current = scrollHeight - (scrollTop + offsetHeight) < scrolledUpThreshold;
21978
+ closeToBottom.current = prevCloseToBottom ? distanceToBottom < scrolledUpThreshold : distanceToBottom < bottomEnterThreshold;
21657
21979
  closeToTop.current = scrollTop < scrolledUpThreshold;
21658
21980
  if (closeToBottom.current) {
21659
21981
  setHasNewMessages(false);
@@ -22488,7 +22810,7 @@ function defaultRenderMessages({
22488
22810
  return renderedMessages;
22489
22811
  }
22490
22812
  const findReverse = (items, matches) => {
22491
- for (let i = items.length - 1; i > 0; i -= 1) {
22813
+ for (let i = items.length - 1; i >= 0; i -= 1) {
22492
22814
  if (matches(items[i])) {
22493
22815
  return items[i];
22494
22816
  }
@@ -23588,11 +23910,17 @@ const useConnectionRecoveredListener = (forceUpdate) => {
23588
23910
  const MOBILE_NAV_BREAKPOINT = 768;
23589
23911
  const useMobileNavigation = (channelListRef, navOpen, closeMobileNav) => {
23590
23912
  React.useEffect(() => {
23913
+ const isClickInsideChannelList = (event) => {
23914
+ const channelListElement = channelListRef.current;
23915
+ if (!channelListElement) return false;
23916
+ const eventPath = event.composedPath();
23917
+ return eventPath.includes(channelListElement);
23918
+ };
23591
23919
  const handleClickOutside = (event) => {
23592
23920
  if (typeof window !== "undefined" && window.innerWidth >= MOBILE_NAV_BREAKPOINT) {
23593
23921
  return;
23594
23922
  }
23595
- if (closeMobileNav && channelListRef.current && !channelListRef.current.contains(event.target) && navOpen) {
23923
+ if (closeMobileNav && channelListRef.current && !isClickInsideChannelList(event) && navOpen) {
23596
23924
  closeMobileNav();
23597
23925
  }
23598
23926
  };
@@ -24354,7 +24682,7 @@ const SearchBar = () => {
24354
24682
  };
24355
24683
  const ChannelSearchResultItem = ({ item }) => {
24356
24684
  const { setActiveChannel } = WithAudioPlayback.useChatContext();
24357
- const { setChannels } = useChannelListContext();
24685
+ const { setChannels } = WithAudioPlayback.useChannelListContext();
24358
24686
  const onSelect = React.useCallback(() => {
24359
24687
  setActiveChannel(item);
24360
24688
  setChannels?.((channels) => uniqBy([item, ...channels], "cid"));
@@ -24377,7 +24705,7 @@ const MessageSearchResultItem = ({
24377
24705
  searchController,
24378
24706
  setActiveChannel
24379
24707
  } = WithAudioPlayback.useChatContext();
24380
- const { setChannels } = useChannelListContext();
24708
+ const { setChannels } = WithAudioPlayback.useChannelListContext();
24381
24709
  const channel = React.useMemo(() => {
24382
24710
  const { channel: channelData } = item;
24383
24711
  const type = channelData?.type ?? "unknown";
@@ -24410,7 +24738,7 @@ const MessageSearchResultItem = ({
24410
24738
  };
24411
24739
  const UserSearchResultItem = ({ item }) => {
24412
24740
  const { client, setActiveChannel } = WithAudioPlayback.useChatContext();
24413
- const { setChannels } = useChannelListContext();
24741
+ const { setChannels } = WithAudioPlayback.useChannelListContext();
24414
24742
  const { directMessagingChannelType } = useSearchContext();
24415
24743
  const onClick = React.useCallback(() => {
24416
24744
  const newChannel = client.channel(directMessagingChannelType, {
@@ -24722,7 +25050,7 @@ const UnMemoizedLoadMoreButton = ({
24722
25050
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__load-more-button", children: /* @__PURE__ */ jsxRuntime.jsx(
24723
25051
  WithAudioPlayback.Button,
24724
25052
  {
24725
- appearance: "outline",
25053
+ appearance: "ghost",
24726
25054
  "aria-label": t("aria/Load More Channels"),
24727
25055
  "data-testid": "load-more-button",
24728
25056
  disabled: loading,
@@ -24936,7 +25264,7 @@ const UnMemoizedChannelList = (props) => {
24936
25264
  );
24937
25265
  const showChannelList = !searchIsActive;
24938
25266
  return /* @__PURE__ */ jsxRuntime.jsx(DialogManagerProvider, { id: `channel-list-dialog-manager-${stableId}`, children: /* @__PURE__ */ jsxRuntime.jsx(
24939
- ChannelListContextProvider,
25267
+ WithAudioPlayback.ChannelListContextProvider,
24940
25268
  {
24941
25269
  value: { channels, hasNextPage, loadNextPage, setChannels },
24942
25270
  children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className, ref: channelListRef, children: [
@@ -25330,6 +25658,36 @@ const useArchiveActionButtonBehavior = () => {
25330
25658
  };
25331
25659
  };
25332
25660
  const defaultChannelActionSet = [
25661
+ {
25662
+ // eslint-disable-next-line react/display-name
25663
+ Component: React.forwardRef((_, ref) => {
25664
+ const { channel } = useChannelListItemContext();
25665
+ const dialogId2 = ChannelListItemActionButtons.getDialogId({
25666
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
25667
+ channelId: channel.id
25668
+ });
25669
+ const { dialog, dialogManager } = useDialogOnNearestManager({ id: dialogId2 });
25670
+ const dialogIsOpen = useDialogIsOpen(dialogId2, dialogManager?.id);
25671
+ return /* @__PURE__ */ jsxRuntime.jsx(
25672
+ WithAudioPlayback.Button,
25673
+ {
25674
+ appearance: "ghost",
25675
+ "aria-expanded": dialogIsOpen,
25676
+ "aria-pressed": dialogIsOpen,
25677
+ circular: true,
25678
+ onClick: (e) => {
25679
+ e.stopPropagation();
25680
+ dialog.toggle();
25681
+ },
25682
+ ref,
25683
+ size: "sm",
25684
+ variant: "secondary",
25685
+ children: /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconDotGrid1x3Horizontal, {})
25686
+ }
25687
+ );
25688
+ }),
25689
+ placement: "quick-dropdown-toggle"
25690
+ },
25333
25691
  {
25334
25692
  Component() {
25335
25693
  const behaviorProps = useArchiveActionButtonBehavior();
@@ -25462,7 +25820,7 @@ const defaultChannelActionSet = [
25462
25820
  const membership = useChannelMembershipState(channel);
25463
25821
  const dialogId2 = ChannelListItemActionButtons.getDialogId(
25464
25822
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
25465
- channel.id
25823
+ { channelId: channel.id }
25466
25824
  );
25467
25825
  const { dialog } = useDialogOnNearestManager({ id: dialogId2 });
25468
25826
  const [inProgress, setInProgress] = React.useState(false);
@@ -25564,59 +25922,23 @@ const useBaseChannelActionSetFilter = (channelActionSet) => {
25564
25922
  const connectedUserIsMember = typeof membership.user !== "undefined";
25565
25923
  const ownCapabilities = channel.data?.own_capabilities;
25566
25924
  return React.useMemo(() => {
25567
- const filtered = channelActionSet.filter(
25568
- (action) => tsPattern.match({
25569
- action,
25570
- connectedUserIsMember,
25571
- isDirectMessageChannel,
25572
- memberCount,
25573
- ownCapabilities
25574
- }).returnType().with(
25575
- {
25576
- action: { connectedUserIsMember: true, placement: "quick", type: "archive" },
25577
- isDirectMessageChannel: true
25578
- },
25579
- {
25580
- action: {
25581
- connectedUserIsMember: true,
25582
- placement: "dropdown",
25583
- type: "archive"
25584
- },
25585
- isDirectMessageChannel: false
25586
- },
25587
- {
25588
- action: { placement: "dropdown", type: "mute" },
25589
- isDirectMessageChannel: true,
25590
- ownCapabilities: tsPattern.P.when(
25591
- (capabilities) => capabilities?.includes("mute-channel")
25592
- )
25593
- },
25594
- {
25595
- action: { placement: "quick", type: "mute" },
25596
- isDirectMessageChannel: false,
25597
- ownCapabilities: tsPattern.P.when(
25598
- (capabilities) => capabilities?.includes("mute-channel")
25599
- )
25600
- },
25601
- {
25602
- action: { type: "ban" },
25603
- memberCount: tsPattern.P.number.gt(0).and(tsPattern.P.number.lte(2)),
25604
- ownCapabilities: tsPattern.P.when(
25605
- (capabilities) => capabilities?.includes("ban-channel-members")
25606
- )
25607
- },
25608
- {
25609
- action: { type: "leave" },
25610
- ownCapabilities: tsPattern.P.when(
25611
- (capabilities) => capabilities?.includes("leave-channel")
25612
- )
25613
- },
25614
- {
25615
- action: { connectedUserIsMember: true, type: "pin" }
25616
- },
25617
- () => true
25618
- ).otherwise(() => false)
25619
- );
25925
+ const filtered = channelActionSet.filter((action) => {
25926
+ if (action.placement === "quick-dropdown-toggle") return true;
25927
+ switch (action.type) {
25928
+ case "archive":
25929
+ return connectedUserIsMember && (action.placement === "quick" && isDirectMessageChannel || action.placement === "dropdown" && !isDirectMessageChannel);
25930
+ case "mute":
25931
+ return ownCapabilities?.includes("mute-channel") && (action.placement === "dropdown" && isDirectMessageChannel || action.placement === "quick" && !isDirectMessageChannel);
25932
+ case "ban":
25933
+ return memberCount > 0 && memberCount <= 2 && ownCapabilities?.includes("ban-channel-members");
25934
+ case "leave":
25935
+ return ownCapabilities?.includes("leave-channel");
25936
+ case "pin":
25937
+ return connectedUserIsMember;
25938
+ default:
25939
+ return true;
25940
+ }
25941
+ });
25620
25942
  return filtered;
25621
25943
  }, [
25622
25944
  channelActionSet,
@@ -25627,17 +25949,18 @@ const useBaseChannelActionSetFilter = (channelActionSet) => {
25627
25949
  ]);
25628
25950
  };
25629
25951
  const ChannelListItemActionButtons = () => {
25952
+ const { ContextMenu: ContextMenuComponent = ContextMenu } = WithAudioPlayback.useComponentContext();
25630
25953
  const { channel } = useChannelListItemContext();
25631
25954
  const [referenceElement, setReferenceElement] = React.useState(null);
25632
- const dialogId2 = ChannelListItemActionButtons.getDialogId(
25955
+ const dialogId2 = ChannelListItemActionButtons.getDialogId({
25633
25956
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
25634
- channel.id
25635
- );
25957
+ channelId: channel.id
25958
+ });
25636
25959
  const { dialog, dialogManager } = useDialogOnNearestManager({ id: dialogId2 });
25637
25960
  const dialogIsOpen = useDialogIsOpen(dialogId2, dialogManager?.id);
25638
25961
  const filteredActionSet = useBaseChannelActionSetFilter(defaultChannelActionSet);
25639
- const splitActionSet = useSplitActionSet(filteredActionSet);
25640
- if (splitActionSet.quickActionSet.length + splitActionSet.dropdownActionSet.length === 0) {
25962
+ const { dropdownActionSet, quickActionSet, quickDropdownToggleAction } = useSplitActionSet(filteredActionSet);
25963
+ if (quickActionSet.length + dropdownActionSet.length === 0) {
25641
25964
  return null;
25642
25965
  }
25643
25966
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -25647,26 +25970,10 @@ const ChannelListItemActionButtons = () => {
25647
25970
  "str-chat__channel-list-item__action-buttons--active": dialogIsOpen
25648
25971
  }),
25649
25972
  children: [
25650
- splitActionSet.dropdownActionSet.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
25651
- WithAudioPlayback.Button,
25652
- {
25653
- appearance: "ghost",
25654
- "aria-expanded": dialogIsOpen,
25655
- "aria-pressed": dialogIsOpen,
25656
- circular: true,
25657
- onClick: (e) => {
25658
- e.stopPropagation();
25659
- dialog.toggle();
25660
- },
25661
- ref: setReferenceElement,
25662
- size: "sm",
25663
- variant: "secondary",
25664
- children: /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconDotGrid1x3Horizontal, {})
25665
- }
25666
- ),
25667
- splitActionSet.quickActionSet.map(({ Component, type }) => /* @__PURE__ */ jsxRuntime.jsx(Component, {}, type)),
25973
+ quickDropdownToggleAction && dropdownActionSet.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(quickDropdownToggleAction.Component, { ref: setReferenceElement }),
25974
+ quickActionSet.map(({ Component, type }) => /* @__PURE__ */ jsxRuntime.jsx(Component, {}, type)),
25668
25975
  /* @__PURE__ */ jsxRuntime.jsx(
25669
- ContextMenu,
25976
+ ContextMenuComponent,
25670
25977
  {
25671
25978
  className: "str-chat__channel-list-item__action-buttons-context-menu",
25672
25979
  dialogManagerId: dialogManager?.id,
@@ -25676,14 +25983,14 @@ const ChannelListItemActionButtons = () => {
25676
25983
  referenceElement,
25677
25984
  tabIndex: -1,
25678
25985
  trapFocus: true,
25679
- children: splitActionSet.dropdownActionSet.map(({ Component, type }) => /* @__PURE__ */ jsxRuntime.jsx(Component, {}, type))
25986
+ children: dropdownActionSet.map(({ Component, type }) => /* @__PURE__ */ jsxRuntime.jsx(Component, {}, type))
25680
25987
  }
25681
25988
  )
25682
25989
  ]
25683
25990
  }
25684
25991
  );
25685
25992
  };
25686
- ChannelListItemActionButtons.getDialogId = (channelId) => `channel-action-buttons-${channelId}`;
25993
+ ChannelListItemActionButtons.getDialogId = ({ channelId }) => `channel-action-buttons-${channelId}`;
25687
25994
  ChannelListItemActionButtons.displayName = "ChannelListItemActionButtons";
25688
25995
  function ChannelListItemTimestamp({ lastMessage }) {
25689
25996
  const { t, tDateTimeParser } = WithAudioPlayback.useTranslationContext("ChannelListItemTimestamp");
@@ -26622,7 +26929,8 @@ const ThreadList = ({ virtuosoProps }) => {
26622
26929
  };
26623
26930
  const IconsBySeverity = {
26624
26931
  error: WithAudioPlayback.IconExclamationCircle,
26625
- info: WithAudioPlayback.IconCircleInfoTooltip,
26932
+ info: null,
26933
+ // IconCircleInfoTooltip,
26626
26934
  loading: WithAudioPlayback.IconArrowRotateRightLeftRepeatRefresh,
26627
26935
  success: WithAudioPlayback.IconCheckmark2,
26628
26936
  warning: WithAudioPlayback.IconExclamationTriangle
@@ -26630,7 +26938,7 @@ const IconsBySeverity = {
26630
26938
  const DefaultNotificationIcon = ({ notification }) => {
26631
26939
  if (!notification.severity) return null;
26632
26940
  const Icon = IconsBySeverity[notification.severity] ?? null;
26633
- return Icon && /* @__PURE__ */ jsxRuntime.jsx(Icon, {});
26941
+ return Icon && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__notification-icon", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, {}) });
26634
26942
  };
26635
26943
  const Notification = React.forwardRef(
26636
26944
  ({
@@ -26674,7 +26982,7 @@ const Notification = React.forwardRef(
26674
26982
  ref,
26675
26983
  children: [
26676
26984
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__notification-content", children: [
26677
- Icon && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__notification-icon", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { notification }) }),
26985
+ Icon && /* @__PURE__ */ jsxRuntime.jsx(Icon, { notification }),
26678
26986
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__notification-message", children: displayMessage })
26679
26987
  ] }),
26680
26988
  notification.actions && notification.actions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__notification-actions", children: notification.actions.map((action, index) => /* @__PURE__ */ jsxRuntime.jsx(
@@ -27008,7 +27316,7 @@ const VoiceRecordingPlayerUI = ({ audioPlayer }) => {
27008
27316
  secondsElapsed
27009
27317
  } = WithAudioPlayback.useStateStore(audioPlayer?.state, audioPlayerStateSelector) ?? {};
27010
27318
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: rootClassName, "data-testid": "voice-recording-widget", children: [
27011
- /* @__PURE__ */ jsxRuntime.jsx(PlayButton, { isPlaying: !!isPlaying, onClick: audioPlayer.togglePlay }),
27319
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__message-attachment__voice-recording-widget__play-button-container", children: /* @__PURE__ */ jsxRuntime.jsx(PlayButton, { isPlaying: !!isPlaying, onClick: audioPlayer.togglePlay }) }),
27012
27320
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__message-attachment__voice-recording-widget__metadata", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__message-attachment__voice-recording-widget__audio-state", children: [
27013
27321
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__message-attachment__voice-recording-widget__timer", children: durationSeconds ? /* @__PURE__ */ jsxRuntime.jsx(
27014
27322
  DurationDisplay,
@@ -27417,23 +27725,44 @@ const FileAttachment = ({ attachment }) => {
27417
27725
  );
27418
27726
  };
27419
27727
  const Giphy = ({ attachment }) => {
27420
- const { giphyVersion: giphyVersionName } = WithAudioPlayback.useChannelStateContext();
27728
+ const { giphyVersion: giphyVersionName, imageAttachmentSizeHandler } = WithAudioPlayback.useChannelStateContext();
27421
27729
  const { BaseImage: BaseImage$1 = BaseImage } = WithAudioPlayback.useComponentContext();
27422
27730
  const { t } = WithAudioPlayback.useTranslationContext();
27423
27731
  const usesDefaultBaseImage = BaseImage$1 === BaseImage;
27732
+ const imageElement = React.useRef(null);
27733
+ const [attachmentConfiguration, setAttachmentConfiguration] = React.useState(void 0);
27424
27734
  const imageDescriptors = React.useMemo(
27425
27735
  () => toGalleryItemDescriptors(attachment, { giphyVersionName }),
27426
27736
  [attachment, giphyVersionName]
27427
27737
  );
27428
- if (!imageDescriptors?.imageUrl) return null;
27429
- const { alt, dimensions, imageUrl, title } = imageDescriptors;
27738
+ const alt = imageDescriptors && imageDescriptors.alt;
27739
+ const dimensions = imageDescriptors && imageDescriptors.dimensions;
27740
+ const imageUrl = imageDescriptors && imageDescriptors.imageUrl;
27741
+ const title = imageDescriptors && imageDescriptors.title;
27742
+ const resolvedImageUrl = attachmentConfiguration?.url || imageUrl;
27743
+ const imageStyleVariables = React.useMemo(() => {
27744
+ const originalHeight = Number(dimensions?.height);
27745
+ const originalWidth = Number(dimensions?.width);
27746
+ return {
27747
+ "--original-height": String(originalHeight > 1 ? originalHeight : 1e6),
27748
+ "--original-width": String(originalWidth > 1 ? originalWidth : 1e6)
27749
+ };
27750
+ }, [dimensions?.height, dimensions?.width]);
27751
+ React.useLayoutEffect(() => {
27752
+ if (!imageElement.current || !imageAttachmentSizeHandler) return;
27753
+ const config = imageAttachmentSizeHandler(attachment, imageElement.current);
27754
+ setAttachmentConfiguration(config);
27755
+ }, [attachment, imageAttachmentSizeHandler]);
27756
+ if (!imageUrl) return null;
27430
27757
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: clsx(`str-chat__message-attachment-giphy`), children: [
27431
27758
  /* @__PURE__ */ jsxRuntime.jsx(
27432
27759
  BaseImage$1,
27433
27760
  {
27434
27761
  alt: alt ?? title ?? t("User uploaded content"),
27435
27762
  height: dimensions?.height,
27436
- src: imageUrl,
27763
+ ref: imageElement,
27764
+ src: resolvedImageUrl,
27765
+ style: imageStyleVariables,
27437
27766
  width: dimensions?.width,
27438
27767
  ...usesDefaultBaseImage ? { showDownloadButtonOnError: false } : {}
27439
27768
  }
@@ -28034,6 +28363,8 @@ exports.Button = WithAudioPlayback.Button;
28034
28363
  exports.Channel = WithAudioPlayback.Channel;
28035
28364
  exports.ChannelActionContext = WithAudioPlayback.ChannelActionContext;
28036
28365
  exports.ChannelActionProvider = WithAudioPlayback.ChannelActionProvider;
28366
+ exports.ChannelListContext = WithAudioPlayback.ChannelListContext;
28367
+ exports.ChannelListContextProvider = WithAudioPlayback.ChannelListContextProvider;
28037
28368
  exports.ChannelStateContext = WithAudioPlayback.ChannelStateContext;
28038
28369
  exports.ChannelStateProvider = WithAudioPlayback.ChannelStateProvider;
28039
28370
  exports.ChatContext = WithAudioPlayback.ChatContext;
@@ -28119,6 +28450,7 @@ exports.IconEditBigSolid = WithAudioPlayback.IconEditBigSolid;
28119
28450
  exports.IconEmojiAddReaction = WithAudioPlayback.IconEmojiAddReaction;
28120
28451
  exports.IconEmojiSad = WithAudioPlayback.IconEmojiSad;
28121
28452
  exports.IconEmojiSmile = WithAudioPlayback.IconEmojiSmile;
28453
+ exports.IconExclamation = WithAudioPlayback.IconExclamation;
28122
28454
  exports.IconExclamationCircle = WithAudioPlayback.IconExclamationCircle;
28123
28455
  exports.IconExclamationCircle1 = WithAudioPlayback.IconExclamationCircle1;
28124
28456
  exports.IconExclamationTriangle = WithAudioPlayback.IconExclamationTriangle;
@@ -28248,6 +28580,7 @@ exports.isMessageBlocked = WithAudioPlayback.isMessageBlocked;
28248
28580
  exports.isMessageBounced = WithAudioPlayback.isMessageBounced;
28249
28581
  exports.isMessageEdited = WithAudioPlayback.isMessageEdited;
28250
28582
  exports.isMessageErrorRetryable = WithAudioPlayback.isMessageErrorRetryable;
28583
+ exports.isNetworkSendFailure = WithAudioPlayback.isNetworkSendFailure;
28251
28584
  exports.isNotificationForPanel = WithAudioPlayback.isNotificationForPanel;
28252
28585
  exports.isNotificationTargetPanel = WithAudioPlayback.isNotificationTargetPanel;
28253
28586
  exports.isNumberOrString = WithAudioPlayback.isNumberOrString;
@@ -28266,6 +28599,7 @@ exports.useActiveThread = WithAudioPlayback.useActiveThread;
28266
28599
  exports.useAudioPlayer = WithAudioPlayback.useAudioPlayer;
28267
28600
  exports.useChannelActionContext = WithAudioPlayback.useChannelActionContext;
28268
28601
  exports.useChannelEditMessageHandler = WithAudioPlayback.useEditMessageHandler;
28602
+ exports.useChannelListContext = WithAudioPlayback.useChannelListContext;
28269
28603
  exports.useChannelMentionsHandler = WithAudioPlayback.useMentionsHandlers;
28270
28604
  exports.useChannelStateContext = WithAudioPlayback.useChannelStateContext;
28271
28605
  exports.useChatContext = WithAudioPlayback.useChatContext;
@@ -28284,7 +28618,6 @@ exports.validateAndGetMessage = WithAudioPlayback.validateAndGetMessage;
28284
28618
  exports.AIStateIndicator = AIStateIndicator;
28285
28619
  exports.AIStates = AIStates;
28286
28620
  exports.ATTACHMENT_GROUPS_ORDER = ATTACHMENT_GROUPS_ORDER;
28287
- exports.ActionsIcon = ActionsIcon;
28288
28621
  exports.AddCommentPrompt = AddCommentPrompt;
28289
28622
  exports.Alert = Alert;
28290
28623
  exports.AmountBar = AmountBar;
@@ -28310,8 +28643,6 @@ exports.CardContainer = CardContainer;
28310
28643
  exports.ChannelAvatar = ChannelAvatar;
28311
28644
  exports.ChannelHeader = ChannelHeader;
28312
28645
  exports.ChannelList = ChannelList;
28313
- exports.ChannelListContext = ChannelListContext;
28314
- exports.ChannelListContextProvider = ChannelListContextProvider;
28315
28646
  exports.ChannelListItem = ChannelListItem;
28316
28647
  exports.ChannelListItemActionButtons = ChannelListItemActionButtons;
28317
28648
  exports.ChannelListItemTimestamp = ChannelListItemTimestamp;
@@ -28328,6 +28659,7 @@ exports.ContextMenu = ContextMenu;
28328
28659
  exports.ContextMenuBackButton = ContextMenuBackButton;
28329
28660
  exports.ContextMenuBody = ContextMenuBody;
28330
28661
  exports.ContextMenuButton = ContextMenuButton;
28662
+ exports.ContextMenuContent = ContextMenuContent;
28331
28663
  exports.ContextMenuHeader = ContextMenuHeader;
28332
28664
  exports.ContextMenuRoot = ContextMenuRoot;
28333
28665
  exports.CooldownTimer = CooldownTimer;
@@ -28345,6 +28677,7 @@ exports.DurationDisplay = DurationDisplay;
28345
28677
  exports.EmojiContextMenuButton = EmojiContextMenuButton;
28346
28678
  exports.EmoticonItem = EmoticonItem;
28347
28679
  exports.EndPollAlert = EndPollAlert;
28680
+ exports.ErrorBadge = ErrorBadge;
28348
28681
  exports.EventComponent = EventComponent;
28349
28682
  exports.FILE_ICON_GRAPHIC_CLASSNAME = FILE_ICON_GRAPHIC_CLASSNAME;
28350
28683
  exports.FILE_ICON_NO_LABEL_CLASSNAME = FILE_ICON_NO_LABEL_CLASSNAME;
@@ -28393,13 +28726,13 @@ exports.MessageContext = MessageContext;
28393
28726
  exports.MessageDeletedBubble = MessageDeletedBubble;
28394
28727
  exports.MessageDeliveryStatus = MessageDeliveryStatus;
28395
28728
  exports.MessageEditedIndicator = MessageEditedIndicator;
28396
- exports.MessageErrorIcon = MessageErrorIcon;
28397
28729
  exports.MessageList = MessageList;
28398
28730
  exports.MessageListContext = MessageListContext;
28399
28731
  exports.MessageListContextProvider = MessageListContextProvider;
28400
28732
  exports.MessageProvider = MessageProvider;
28401
28733
  exports.MessageReactions = MessageReactions;
28402
28734
  exports.MessageReactionsDetail = MessageReactionsDetail;
28735
+ exports.MessageReactionsDetailLoadingIndicator = MessageReactionsDetailLoadingIndicator;
28403
28736
  exports.MessageRepliesCountButton = MessageRepliesCountButton;
28404
28737
  exports.MessageSearchResultItem = MessageSearchResultItem;
28405
28738
  exports.MessageStatus = MessageStatus;
@@ -28421,7 +28754,6 @@ exports.NotificationTranslationTopic = NotificationTranslationTopic;
28421
28754
  exports.NumericInput = NumericInput;
28422
28755
  exports.OtherFilesContainer = OtherFilesContainer;
28423
28756
  exports.PauseIcon = PauseIcon;
28424
- exports.PinIcon = PinIcon;
28425
28757
  exports.PinIndicator = PinIndicator;
28426
28758
  exports.PlayButton = PlayButton;
28427
28759
  exports.PlayIcon = PlayIcon;
@@ -28448,7 +28780,6 @@ exports.QuotedMessage = QuotedMessage;
28448
28780
  exports.QuotedMessagePreview = QuotedMessagePreview;
28449
28781
  exports.QuotedMessagePreviewUI = QuotedMessagePreviewUI;
28450
28782
  exports.QuotedVoiceRecording = QuotedVoiceRecording;
28451
- exports.ReactionIcon = ReactionIcon;
28452
28783
  exports.ReactionSelector = ReactionSelector;
28453
28784
  exports.RecordingPermission = RecordingPermission;
28454
28785
  exports.RecordingPermissionDeniedNotification = RecordingPermissionDeniedNotification;
@@ -28488,7 +28819,6 @@ exports.TextInputFieldSet = TextInputFieldSet;
28488
28819
  exports.TextareaComposer = TextareaComposer;
28489
28820
  exports.Thread = Thread;
28490
28821
  exports.ThreadHeader = ThreadHeader;
28491
- exports.ThreadIcon = ThreadIcon;
28492
28822
  exports.ThreadList = ThreadList;
28493
28823
  exports.ThreadListItem = ThreadListItem;
28494
28824
  exports.ThreadListItemUI = ThreadListItemUI;
@@ -28596,7 +28926,6 @@ exports.useCanCreatePoll = useCanCreatePoll;
28596
28926
  exports.useChannelDeletedListener = useChannelDeletedListener;
28597
28927
  exports.useChannelDisplayName = useChannelDisplayName;
28598
28928
  exports.useChannelHiddenListener = useChannelHiddenListener;
28599
- exports.useChannelListContext = useChannelListContext;
28600
28929
  exports.useChannelListItemContext = useChannelListItemContext;
28601
28930
  exports.useChannelMembersState = useChannelMembersState;
28602
28931
  exports.useChannelMembershipState = useChannelMembershipState;