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/es/index.mjs CHANGED
@@ -1,10 +1,10 @@
1
1
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
2
  import clsx from "clsx";
3
3
  import { nanoid } from "nanoid";
4
- import React, { useState, useEffect, createContext, useContext, useCallback, useLayoutEffect, useMemo, useRef, forwardRef, Component, createElement, Fragment as Fragment$1 } from "react";
5
- import { u as useHandleFileChangeWrapper, d as dataTransferItemsToFiles, r as renderAudio, t as toAudioBuffer, c as createFileFromBlobs, g as getExtensionFromMimeType, a as getRecordedMediaTypeFromMimeType } from "../audioProcessing-ByEVSjGG.mjs";
6
- import { u as useStateStore, a as useMessageComposerController, i as isMessageBounced, b as useChannelActionContext, C as ComponentContext, c as useTranslationContext, d as useChannelStateContext, e as useChatContext, f as isNotificationForPanel, B as BaseIcon, g as Button, I as IconPause, h as IconPlaySolid, j as getDefaultExportFromCjs, k as defaultTranslatorFunction, p as predefinedFormatters, L as LocalizedFormat, l as calendar, m as IconLoadingCircle, v as validateAndGetMessage, n as isUserMuted, o as defaultPinPermissions, q as useThreadContext, r as usePopoverPosition, s as useComponentContext, t as IconCrossMedium, w as IconPeople, x as IconChevronRight, y as IconChevronLeft, z as IconArrowLeft, A as IconExclamationCircle, D as IconCircleBanSign, E as isMessageErrorRetryable, F as ACTIONS_NOT_WORKING_IN_THREAD, G as useNotificationTarget, H as IconArrowRightUp, J as addNotificationTargetTag, K as IconPin, M as mapToUserNameOrId, N as IconClock, O as IconCheckmark1Small, P as IconDoubleCheckmark1Small, Q as getReadByTooltipText, R as messageHasAttachments, S as messageTextHasEmojisOnly, T as isDate, U as getDateString, V as IconTranslate, W as useMessageComposerContext, X as useIsCooldownActive, Y as IconCrossSmall, Z as IconImages1Alt, _ as IconChart5, $ as IconMapPin, a0 as IconFileBend, a1 as IconChainLink, a2 as IconVideo, a3 as IconVideoSolid, a4 as IconMicrophone, a5 as IconBookmark, a6 as IconBellNotification, a7 as IconChevronDown, a8 as IconPlusSmall, a9 as IconCheckmark2, aa as DEFAULT_LOAD_PAGE_SCROLL_THRESHOLD, ab as IconTrophy, ac as IconDotGrid2x3, ad as IconCircleMinus, ae as IconPaperPlane, af as IconVolumeFull, ag as IconPeopleAdd, ah as IconMute, ai as IconFlag2, aj as IconPeopleRemove, ak as IconPaperclip, al as IconRunShortcut, am as CHANNEL_CONTAINER_ID, an as IconPlusLarge, ao as IconArrowDownCircle, ap as IconExclamationTriangle, aq as useAudioPlayer, ar as IconArrowRotateClockwise, as as IconThunder, at as IconTrashBin, au as IconFileArrowLeftIn, av as MessageComposerContextProvider, aw as useTypingContext, ax as defaultDateTimeParser, ay as isLanguageSupported, az as IconLayoutAlignLeft, aA as useChatViewContext, aB as MESSAGE_ACTIONS, aC as LegacyThreadContext, aD as IconPeopleAdded, aE as IconBookmarkRemove, aF as IconBellOff, aG as IconBubbleWideNotificationChatMessage, aH as IconEditBig, aI as IconSquareBehindSquare2_Copy, aJ as IconUnpin, aK as IconCloseQuote2, aL as IconBubbleText6ChatMessage, aM as areMessageUIPropsEqual, aN as isDateSeparatorMessage, aO as isMessageBlocked, aP as messageHasSingleAttachment, aQ as messageHasGiphyAttachment, aR as messageHasReactions, aS as isMessageEdited, aT as countEmojis, aU as areMessagePropsEqual, aV as getMessageActions, aW as processMessages, aX as insertIntro, aY as getGroupStyles, aZ as getLastReceived, a_ as IconArrowUp, a$ as isIntroMessage, b0 as isLocalMessage, b1 as getIsFirstUnreadMessage, b2 as IconArrowDown, b3 as DEFAULT_NEXT_CHANNEL_PAGE_SIZE, b4 as EmptyStateIndicator, b5 as getChannel, b6 as IconMagnifyingGlassSearch, b7 as IconCircleX, b8 as DEFAULT_JUMP_TO_PAGE_SIZE, b9 as IconArchive, ba as IconArrowBoxLeft, bb as IconDotGrid1x3Horizontal, bc as IconCamera1, bd as IconExclamationCircle1, be as ChatProvider, bf as TranslationProvider, bg as useThreadsViewContext, bh as IconBubbles, bi as IconArrowRotateRightLeftRepeatRefresh, bj as IconCircleInfoTooltip, bk as IconEyeOpen } from "../WithAudioPlayback-BcKZ5Lbh.mjs";
7
- import { bp, bs, d8, d9, da, db, dc, bu, bx, bt, bw, by, dd, bB, bC, bD, bE, bF, bG, bH, bI, bJ, bK, bL, bM, bN, bP, bO, bQ, bR, bS, bT, bU, bV, bW, bX, bY, bZ, b_, b$, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, ca, cb, cc, cd, ce, cf, cg, ch, ci, cj, ck, cl, cm, cn, co, cp, cq, cr, cs, ct, cu, cv, cw, cx, cy, cz, cA, cB, cC, cD, cE, cF, cG, cH, cI, cJ, cK, cL, cM, cN, cO, cP, cQ, cR, cS, cT, cU, cV, cW, de, cX, d6, d7, df, dg, dh, bm, bA, bz, bn, bo, cY, cZ, d3, d4, d0, d1, di, d2, dj, c$, c_, bl, bv, bq, br, d5 } from "../WithAudioPlayback-BcKZ5Lbh.mjs";
4
+ import React, { useState, useEffect, useCallback, useLayoutEffect, useMemo, useContext, createContext, useRef, forwardRef, createElement, Component, Fragment as Fragment$1 } from "react";
5
+ import { u as useHandleFileChangeWrapper, d as dataTransferItemsToFiles, r as renderAudio, t as toAudioBuffer, c as createFileFromBlobs, g as getExtensionFromMimeType, a as getRecordedMediaTypeFromMimeType } from "./audioProcessing.21cb49e1.mjs";
6
+ import { u as useStateStore, a as useMessageComposerController, i as isMessageBounced, b as useChannelActionContext, C as ComponentContext, c as useTranslationContext, d as useChannelStateContext, e as useChatContext, f as isNotificationForPanel, B as BaseIcon, g as Button, I as IconPause, h as IconPlaySolid, j as getDefaultExportFromCjs, k as defaultTranslatorFunction, p as predefinedFormatters, L as LocalizedFormat, l as calendar, m as IconLoadingCircle, n as isNetworkSendFailure, v as validateAndGetMessage, o as isUserMuted, q as defaultPinPermissions, r as useThreadContext, s as usePopoverPosition, t as useComponentContext, w as IconCrossMedium, x as IconPeople, y as IconExclamation, z as IconChevronRight, A as IconChevronLeft, D as IconArrowLeft, E as IconExclamationCircle, F as IconCircleBanSign, G as isMessageErrorRetryable, H as ACTIONS_NOT_WORKING_IN_THREAD, J as useNotificationTarget, K as IconArrowRightUp, M as addNotificationTargetTag, N as IconPin, O as mapToUserNameOrId, P as IconClock, Q as IconCheckmark1Small, R as IconDoubleCheckmark1Small, S as getReadByTooltipText, T as messageHasAttachments, U as messageTextHasEmojisOnly, V as isDate, W as getDateString, X as IconTranslate, Y as useMessageComposerContext, Z as useIsCooldownActive, _ as IconCrossSmall, $ as IconImages1Alt, a0 as IconChart5, a1 as IconMapPin, a2 as IconFileBend, a3 as IconChainLink, a4 as IconVideo, a5 as IconVideoSolid, a6 as IconMicrophone, a7 as IconBookmark, a8 as IconBellNotification, a9 as IconChevronDown, aa as IconPlusSmall, ab as IconCheckmark2, ac as DEFAULT_LOAD_PAGE_SCROLL_THRESHOLD, ad as IconTrophy, ae as IconDotGrid2x3, af as IconCircleMinus, ag as IconPaperPlane, ah as IconVolumeFull, ai as IconPeopleAdd, aj as IconMute, ak as IconFlag2, al as IconPeopleRemove, am as IconPaperclip, an as IconRunShortcut, ao as CHANNEL_CONTAINER_ID, ap as IconPlusLarge, aq as IconExclamationTriangle, ar as useAudioPlayer, as as IconArrowRotateClockwise, at as IconArrowDownCircle, au as IconThunder, av as IconTrashBin, aw as IconFileArrowLeftIn, ax as MessageComposerContextProvider, ay as useTypingContext, az as defaultDateTimeParser, aA as isLanguageSupported, aB as IconLayoutAlignLeft, aC as useChatViewContext, aD as MESSAGE_ACTIONS, aE as LegacyThreadContext, aF as IconArrowShareLeft, aG as IconEmojiSmile, aH as IconDotGrid1x3Horizontal, aI as IconPeopleAdded, aJ as IconBookmarkRemove, aK as IconBellOff, aL as IconBubbleWideNotificationChatMessage, aM as IconEditBig, aN as IconSquareBehindSquare2_Copy, aO as IconUnpin, aP as IconCloseQuote2, aQ as IconBubbleText6ChatMessage, aR as areMessageUIPropsEqual, aS as isDateSeparatorMessage, aT as isMessageBlocked, aU as messageHasSingleAttachment, aV as messageHasGiphyAttachment, aW as messageHasReactions, aX as isMessageEdited, aY as countEmojis, aZ as areMessagePropsEqual, a_ as getMessageActions, a$ as processMessages, b0 as insertIntro, b1 as getGroupStyles, b2 as getLastReceived, b3 as IconArrowUp, b4 as isIntroMessage, b5 as isLocalMessage, b6 as getIsFirstUnreadMessage, b7 as IconArrowDown, b8 as DEFAULT_NEXT_CHANNEL_PAGE_SIZE, b9 as EmptyStateIndicator, ba as getChannel, bb as IconMagnifyingGlassSearch, bc as IconCircleX, bd as useChannelListContext, be as DEFAULT_JUMP_TO_PAGE_SIZE, bf as ChannelListContextProvider, bg as IconArchive, bh as IconArrowBoxLeft, bi as IconCamera1, bj as IconExclamationCircle1, bk as ChatProvider, bl as TranslationProvider, bm as useThreadsViewContext, bn as IconBubbles, bo as IconArrowRotateRightLeftRepeatRefresh, bp as IconEyeOpen } from "./WithAudioPlayback.a3d5a2fc.mjs";
7
+ import { bu, bx, dc, dd, de, df, dg, dh, bz, bC, by, bB, bD, di, bG, bH, bI, bJ, bK, bL, bM, bN, bO, bP, bQ, bR, bT, bS, bU, bV, bW, bX, bY, bZ, b_, b$, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, ca, cb, cc, cd, ce, cf, cg, ch, ci, cj, ck, cl, cm, cn, co, cp, cq, cr, cs, ct, cu, cv, cw, cx, cy, cz, cA, cB, cC, cD, cE, cF, cG, cH, cI, cJ, cK, cL, cM, cN, cO, cP, cQ, cR, cS, cT, cU, cV, cW, cX, cY, cZ, c_, dj, c$, da, db, dk, dl, dm, br, bF, bE, bs, bt, d0, d1, d7, d8, d4, d5, dn, d6, dp, d3, d2, bq, bA, bv, bw, d9 } from "./WithAudioPlayback.a3d5a2fc.mjs";
8
8
  import { StateStore, formatMessage, MessageComposer as MessageComposer$1, isGiphyAttachment, isScrapedContent, isLocalVideoAttachment, isVideoAttachment, isLocalImageAttachment, isImageAttachment, isAudioAttachment, isVoiceRecordingAttachment, isFileAttachment, isVoteAnswer, VotingVisibility, isLocalVoiceRecordingAttachment, isLocalAudioAttachment, isLocalFileAttachment, isLocalAttachment, LinkPreviewsManager, SearchController, ChannelSearchSource, UserSearchSource, MessageSearchSource, StreamChat, isSharedLocationResponse, LiveLocationManager } from "stream-chat";
9
9
  import throttle from "lodash.throttle";
10
10
  import * as linkify from "linkifyjs";
@@ -19,7 +19,6 @@ import { visit, SKIP } from "unist-util-visit";
19
19
  import i18n from "i18next";
20
20
  import Dayjs from "dayjs";
21
21
  import uniqBy from "lodash.uniqby";
22
- import { match, P } from "ts-pattern";
23
22
  import { useSyncExternalStore } from "use-sync-external-store/shim";
24
23
  import debounce from "lodash.debounce";
25
24
  import fixWebmDuration from "fix-webm-duration";
@@ -71,28 +70,12 @@ const useAIState = (channel) => {
71
70
  }, [channel]);
72
71
  return { aiState };
73
72
  };
74
- const ChannelListContext = createContext(
75
- void 0
76
- );
77
- const ChannelListContextProvider = ({
78
- children,
79
- value
80
- }) => /* @__PURE__ */ jsx(ChannelListContext.Provider, { value, children });
81
- const useChannelListContext = (componentName) => {
82
- const contextValue = useContext(ChannelListContext);
83
- if (!contextValue) {
84
- console.warn(
85
- `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.`
86
- );
87
- return {};
88
- }
89
- return contextValue;
90
- };
91
73
  class DialogManager {
92
- constructor({ id } = {}) {
74
+ constructor({ closeOnClickOutside = true, id } = {}) {
93
75
  this.state = new StateStore({
94
76
  dialogsById: {}
95
77
  });
78
+ this.closeOnClickOutside = closeOnClickOutside;
96
79
  this.id = id ?? nanoid();
97
80
  }
98
81
  get openDialogCount() {
@@ -107,13 +90,14 @@ class DialogManager {
107
90
  get(id) {
108
91
  return this.state.getLatestValue().dialogsById[id];
109
92
  }
110
- getOrCreate({ id }) {
93
+ getOrCreate({ closeOnClickOutside, id }) {
111
94
  let dialog = this.state.getLatestValue().dialogsById[id];
112
95
  if (!dialog) {
113
96
  dialog = {
114
97
  close: () => {
115
98
  this.close(id);
116
99
  },
100
+ closeOnClickOutside,
117
101
  id,
118
102
  isOpen: false,
119
103
  open: () => {
@@ -129,21 +113,22 @@ class DialogManager {
129
113
  };
130
114
  this.state.next((current) => ({
131
115
  ...current,
132
- ...{ dialogsById: { ...current.dialogsById, [id]: dialog } }
116
+ dialogsById: { ...current.dialogsById, [id]: dialog }
133
117
  }));
134
118
  }
135
- if (dialog.removalTimeout) {
136
- clearTimeout(dialog.removalTimeout);
119
+ const shouldUpdateDialogSettings = dialog.closeOnClickOutside !== closeOnClickOutside || !!dialog.removalTimeout;
120
+ if (shouldUpdateDialogSettings) {
121
+ if (dialog.removalTimeout) clearTimeout(dialog.removalTimeout);
122
+ dialog = {
123
+ ...dialog,
124
+ closeOnClickOutside,
125
+ removalTimeout: void 0
126
+ };
137
127
  this.state.next((current) => ({
138
128
  ...current,
139
- ...{
140
- dialogsById: {
141
- ...current.dialogsById,
142
- [id]: {
143
- ...dialog,
144
- removalTimeout: void 0
145
- }
146
- }
129
+ dialogsById: {
130
+ ...current.dialogsById,
131
+ [id]: dialog
147
132
  }
148
133
  }));
149
134
  }
@@ -220,7 +205,11 @@ class DialogManager {
220
205
  }));
221
206
  }
222
207
  }
223
- const useDialog = ({ dialogManagerId, id }) => {
208
+ const useDialog = ({
209
+ closeOnClickOutside,
210
+ dialogManagerId,
211
+ id
212
+ }) => {
224
213
  const { dialogManager } = useDialogManager({ dialogManagerId });
225
214
  useEffect(
226
215
  () => () => {
@@ -228,7 +217,7 @@ const useDialog = ({ dialogManagerId, id }) => {
228
217
  },
229
218
  [dialogManager, id]
230
219
  );
231
- return dialogManager.getOrCreate({ id });
220
+ return dialogManager.getOrCreate({ closeOnClickOutside, id });
232
221
  };
233
222
  const useDialogOnNearestManager = ({ id }) => {
234
223
  const { dialogManager } = useNearestDialogManagerContext() ?? {};
@@ -275,9 +264,37 @@ const Portal = ({
275
264
  if (!portalDestination) return null;
276
265
  return createPortal(children, portalDestination);
277
266
  };
267
+ const shouldCloseOnOutsideClick = ({
268
+ dialog,
269
+ managerCloseOnClickOutside
270
+ }) => dialog.closeOnClickOutside ?? managerCloseOnClickOutside;
278
271
  const DialogPortalDestination = () => {
279
272
  const { dialogManager } = useNearestDialogManagerContext() ?? {};
280
273
  const openedDialogCount = useOpenedDialogCount({ dialogManagerId: dialogManager?.id });
274
+ const [destinationRoot, setDestinationRoot] = useState(null);
275
+ useEffect(() => {
276
+ if (!destinationRoot || !dialogManager) return;
277
+ const handleDocumentClick = (event) => {
278
+ if (destinationRoot.contains(event.target)) return;
279
+ setTimeout(() => {
280
+ Object.values(dialogManager.state.getLatestValue().dialogsById).forEach(
281
+ (dialog) => {
282
+ if (!dialog.isOpen) return;
283
+ if (!shouldCloseOnOutsideClick({
284
+ dialog,
285
+ managerCloseOnClickOutside: dialogManager.closeOnClickOutside
286
+ }))
287
+ return;
288
+ dialogManager.close(dialog.id);
289
+ }
290
+ );
291
+ }, 0);
292
+ };
293
+ document.addEventListener("click", handleDocumentClick, { capture: true });
294
+ return () => {
295
+ document.removeEventListener("click", handleDocumentClick, { capture: true });
296
+ };
297
+ }, [destinationRoot, dialogManager]);
281
298
  if (!openedDialogCount) return null;
282
299
  return /* @__PURE__ */ jsx(
283
300
  "div",
@@ -285,7 +302,22 @@ const DialogPortalDestination = () => {
285
302
  className: "str-chat__dialog-overlay",
286
303
  "data-str-chat__portal-id": dialogManager?.id,
287
304
  "data-testid": "str-chat__dialog-overlay",
288
- onClick: () => dialogManager?.closeAll(),
305
+ onClick: (event) => {
306
+ if (!dialogManager) return;
307
+ if (event.target !== event.currentTarget) return;
308
+ Object.values(dialogManager.state.getLatestValue().dialogsById).forEach(
309
+ (dialog) => {
310
+ if (!dialog.isOpen) return;
311
+ if (!shouldCloseOnOutsideClick({
312
+ dialog,
313
+ managerCloseOnClickOutside: dialogManager.closeOnClickOutside
314
+ }))
315
+ return;
316
+ dialogManager.close(dialog.id);
317
+ }
318
+ );
319
+ },
320
+ ref: setDestinationRoot,
289
321
  style: {
290
322
  "--str-chat__dialog-overlay-height": openedDialogCount > 0 ? "100%" : "0"
291
323
  }
@@ -307,11 +339,16 @@ const DialogPortalEntry = ({
307
339
  };
308
340
  const dialogManagersRegistry = new StateStore({});
309
341
  const getDialogManager = (id) => dialogManagersRegistry.getLatestValue()[id];
310
- const getOrCreateDialogManager = (id) => {
342
+ const getOrCreateDialogManager = ({
343
+ closeOnClickOutside,
344
+ id
345
+ }) => {
311
346
  let manager = getDialogManager(id);
312
347
  if (!manager) {
313
- manager = new DialogManager({ id });
348
+ manager = new DialogManager({ closeOnClickOutside, id });
314
349
  dialogManagersRegistry.partialNext({ [id]: manager });
350
+ } else if (typeof closeOnClickOutside === "boolean") {
351
+ manager.closeOnClickOutside = closeOnClickOutside;
315
352
  }
316
353
  return manager;
317
354
  };
@@ -322,20 +359,21 @@ const removeDialogManager = (id) => {
322
359
  const DialogManagerProviderContext = React.createContext(void 0);
323
360
  const DialogManagerProvider = ({
324
361
  children,
362
+ closeOnClickOutside,
325
363
  id
326
364
  }) => {
327
365
  const [dialogManager, setDialogManager] = useState(() => {
328
366
  if (id) return getDialogManager(id) ?? null;
329
- return new DialogManager();
367
+ return new DialogManager({ closeOnClickOutside });
330
368
  });
331
369
  useEffect(() => {
332
370
  if (!id) return;
333
- setDialogManager(getOrCreateDialogManager(id));
371
+ setDialogManager(getOrCreateDialogManager({ closeOnClickOutside, id }));
334
372
  return () => {
335
373
  removeDialogManager(id);
336
374
  setDialogManager(null);
337
375
  };
338
- }, [id]);
376
+ }, [closeOnClickOutside, id]);
339
377
  if (!dialogManager) return null;
340
378
  return /* @__PURE__ */ jsxs(DialogManagerProviderContext.Provider, { value: { dialogManager }, children: [
341
379
  children,
@@ -950,7 +988,6 @@ const deTranslations = {
950
988
  "End vote": "Abstimmung beenden",
951
989
  "Enforce unique vote is enabled": "Eindeutige Abstimmung ist aktiviert",
952
990
  "Error": "Fehler",
953
- "Error · Unsent": "Fehler · Nicht gesendet",
954
991
  "Error adding flag": "Fehler beim Hinzufügen des Flags",
955
992
  "Error connecting to chat, refresh the page to try again.": "Verbindungsfehler zum Chat, aktualisieren Sie die Seite, um es erneut zu versuchen.",
956
993
  "Error deleting message": "Fehler beim Löschen der Nachricht",
@@ -1072,8 +1109,6 @@ const deTranslations = {
1072
1109
  "Maximum number of votes (from 2 to 10)": "Maximale Anzahl der Stimmen (von 2 bis 10)",
1073
1110
  Menu: Menu$b,
1074
1111
  "Message deleted": "Nachricht gelöscht",
1075
- "Message Failed · Click to try again": "Nachricht fehlgeschlagen · Klicken, um es erneut zu versuchen",
1076
- "Message Failed · Unauthorized": "Nachricht fehlgeschlagen · Nicht autorisiert",
1077
1112
  "Message failed to send": "Nachricht konnte nicht gesendet werden",
1078
1113
  "Message has been successfully flagged": "Nachricht wurde erfolgreich gemeldet",
1079
1114
  "Message pinned": "Nachricht angeheftet",
@@ -1243,6 +1278,7 @@ const deTranslations = {
1243
1278
  "View translation": "Übersetzung anzeigen",
1244
1279
  "Voice message": "Sprachnachricht",
1245
1280
  "Voice message {{ duration }}": "Sprachnachricht {{ duration }}",
1281
+ "Voice message deleted": "Sprachnachricht gelöscht",
1246
1282
  voiceMessageCount_one: voiceMessageCount_one$9,
1247
1283
  voiceMessageCount_other: voiceMessageCount_other$b,
1248
1284
  "Vote ended": "Abstimmung beendet",
@@ -1473,7 +1509,6 @@ const enTranslations = {
1473
1509
  "End vote": "End Vote",
1474
1510
  "Enforce unique vote is enabled": "Enforce unique vote is enabled",
1475
1511
  "Error": "Error",
1476
- "Error · Unsent": "Error · Unsent",
1477
1512
  "Error adding flag": "Error adding flag",
1478
1513
  "Error connecting to chat, refresh the page to try again.": "Error connecting to chat, refresh the page to try again.",
1479
1514
  "Error deleting message": "Error deleting message",
@@ -1595,8 +1630,6 @@ const enTranslations = {
1595
1630
  "Maximum number of votes (from 2 to 10)": "Maximum number of votes (from 2 to 10)",
1596
1631
  Menu: Menu$a,
1597
1632
  "Message deleted": "Message deleted",
1598
- "Message Failed · Click to try again": "Message Failed · Click to try again",
1599
- "Message Failed · Unauthorized": "Message Failed · Unauthorized",
1600
1633
  "Message failed to send": "Message failed to send",
1601
1634
  "Message has been successfully flagged": "Message has been successfully flagged",
1602
1635
  "Message pinned": "Message pinned",
@@ -1766,6 +1799,7 @@ const enTranslations = {
1766
1799
  "View translation": "View translation",
1767
1800
  "Voice message": "Voice message",
1768
1801
  "Voice message {{ duration }}": "Voice message {{ duration }}",
1802
+ "Voice message deleted": "Voice message deleted",
1769
1803
  voiceMessageCount_one: voiceMessageCount_one$8,
1770
1804
  voiceMessageCount_other: voiceMessageCount_other$a,
1771
1805
  "Vote ended": "Vote ended",
@@ -2012,7 +2046,6 @@ const esTranslations = {
2012
2046
  "End vote": "Finalizar votación",
2013
2047
  "Enforce unique vote is enabled": "El voto único está habilitado",
2014
2048
  "Error": "Error",
2015
- "Error · Unsent": "Error · No enviado",
2016
2049
  "Error adding flag": "Error al agregar la bandera",
2017
2050
  "Error connecting to chat, refresh the page to try again.": "Error al conectarse al chat, actualice la página para volver a intentarlo.",
2018
2051
  "Error deleting message": "Error al eliminar el mensaje",
@@ -2137,8 +2170,6 @@ const esTranslations = {
2137
2170
  "Maximum number of votes (from 2 to 10)": "Número máximo de votos (de 2 a 10)",
2138
2171
  Menu: Menu$9,
2139
2172
  "Message deleted": "Mensaje eliminado",
2140
- "Message Failed · Click to try again": "Mensaje fallido · Haga clic para volver a intentarlo",
2141
- "Message Failed · Unauthorized": "Mensaje fallido · No autorizado",
2142
2173
  "Message failed to send": "No se pudo enviar el mensaje",
2143
2174
  "Message has been successfully flagged": "El mensaje se marcó correctamente",
2144
2175
  "Message pinned": "Mensaje fijado",
@@ -2315,6 +2346,7 @@ const esTranslations = {
2315
2346
  "View translation": "Ver traducción",
2316
2347
  "Voice message": "Mensaje de voz",
2317
2348
  "Voice message {{ duration }}": "Mensaje de voz {{ duration }}",
2349
+ "Voice message deleted": "Mensaje de voz eliminado",
2318
2350
  voiceMessageCount_one: voiceMessageCount_one$7,
2319
2351
  voiceMessageCount_many: voiceMessageCount_many$4,
2320
2352
  voiceMessageCount_other: voiceMessageCount_other$9,
@@ -2562,7 +2594,6 @@ const frTranslations = {
2562
2594
  "End vote": "Fin du vote",
2563
2595
  "Enforce unique vote is enabled": "Le vote unique est activé",
2564
2596
  "Error": "Erreur",
2565
- "Error · Unsent": "Erreur - Non envoyé",
2566
2597
  "Error adding flag": "Erreur lors de l'ajout du signalement",
2567
2598
  "Error connecting to chat, refresh the page to try again.": "Erreur de connexion au chat, rafraîchissez la page pour réessayer.",
2568
2599
  "Error deleting message": "Erreur lors de la suppression du message",
@@ -2687,8 +2718,6 @@ const frTranslations = {
2687
2718
  "Maximum number of votes (from 2 to 10)": "Nombre maximum de votes (de 2 à 10)",
2688
2719
  Menu: Menu$8,
2689
2720
  "Message deleted": "Message supprimé",
2690
- "Message Failed · Click to try again": "Échec de l'envoi du message - Cliquez pour réessayer",
2691
- "Message Failed · Unauthorized": "Échec de l'envoi du message - Non autorisé",
2692
2721
  "Message failed to send": "Échec de l'envoi du message",
2693
2722
  "Message has been successfully flagged": "Le message a été signalé avec succès",
2694
2723
  "Message pinned": "Message épinglé",
@@ -2865,6 +2894,7 @@ const frTranslations = {
2865
2894
  "View translation": "Voir la traduction",
2866
2895
  "Voice message": "Message vocal",
2867
2896
  "Voice message {{ duration }}": "Message vocal {{ duration }}",
2897
+ "Voice message deleted": "Message vocal supprimé",
2868
2898
  voiceMessageCount_one: voiceMessageCount_one$6,
2869
2899
  voiceMessageCount_many: voiceMessageCount_many$3,
2870
2900
  voiceMessageCount_other: voiceMessageCount_other$8,
@@ -3096,7 +3126,6 @@ const hiTranslations = {
3096
3126
  "End vote": "मत समाप्त करें",
3097
3127
  "Enforce unique vote is enabled": "अनोखा वोट सक्षम है",
3098
3128
  "Error": "त्रुटि",
3099
- "Error · Unsent": "फेल",
3100
3129
  "Error adding flag": "ध्वज जोड़ने में त्रुटि",
3101
3130
  "Error connecting to chat, refresh the page to try again.": "चैट से कनेक्ट करने में त्रुटि, पेज को रिफ्रेश करें",
3102
3131
  "Error deleting message": "संदेश हटाने में त्रुटि",
@@ -3219,8 +3248,6 @@ const hiTranslations = {
3219
3248
  "Maximum number of votes (from 2 to 10)": "अधिकतम वोटों की संख्या (2 से 10)",
3220
3249
  Menu: Menu$7,
3221
3250
  "Message deleted": "मैसेज हटा दिया गया",
3222
- "Message Failed · Click to try again": "मैसेज फ़ैल - पुनः कोशिश करें",
3223
- "Message Failed · Unauthorized": "मैसेज फ़ैल - अनधिकृत",
3224
3251
  "Message failed to send": "संदेश भेजने में विफल",
3225
3252
  "Message has been successfully flagged": "मैसेज को फ्लैग कर दिया गया है",
3226
3253
  "Message pinned": "संदेश पिन किया गया",
@@ -3390,6 +3417,7 @@ const hiTranslations = {
3390
3417
  "View translation": "अनुवाद देखें",
3391
3418
  "Voice message": "आवाज संदेश",
3392
3419
  "Voice message {{ duration }}": "वॉइस संदेश {{ duration }}",
3420
+ "Voice message deleted": "वॉइस संदेश हटा दिया गया",
3393
3421
  voiceMessageCount_one: voiceMessageCount_one$5,
3394
3422
  voiceMessageCount_other: voiceMessageCount_other$7,
3395
3423
  "Vote ended": "मतदान समाप्त",
@@ -3636,7 +3664,6 @@ const itTranslations = {
3636
3664
  "End vote": "Termina il voto",
3637
3665
  "Enforce unique vote is enabled": "Il voto unico è abilitato",
3638
3666
  "Error": "Errore",
3639
- "Error · Unsent": "Errore · Non inviato",
3640
3667
  "Error adding flag": "Errore durante l'aggiunta del flag",
3641
3668
  "Error connecting to chat, refresh the page to try again.": "Errore di connessione alla chat, aggiorna la pagina per riprovare.",
3642
3669
  "Error deleting message": "Errore durante l'eliminazione del messaggio",
@@ -3761,8 +3788,6 @@ const itTranslations = {
3761
3788
  "Maximum number of votes (from 2 to 10)": "Numero massimo di voti (da 2 a 10)",
3762
3789
  Menu: Menu$6,
3763
3790
  "Message deleted": "Messaggio cancellato",
3764
- "Message Failed · Click to try again": "Invio messaggio fallito · Clicca per riprovare",
3765
- "Message Failed · Unauthorized": "Invio messaggio fallito · Non autorizzato",
3766
3791
  "Message failed to send": "Invio del messaggio non riuscito",
3767
3792
  "Message has been successfully flagged": "Il messaggio è stato segnalato con successo",
3768
3793
  "Message pinned": "Messaggio bloccato",
@@ -3939,6 +3964,7 @@ const itTranslations = {
3939
3964
  "View translation": "Visualizza traduzione",
3940
3965
  "Voice message": "Messaggio vocale",
3941
3966
  "Voice message {{ duration }}": "Messaggio vocale {{ duration }}",
3967
+ "Voice message deleted": "Messaggio vocale eliminato",
3942
3968
  voiceMessageCount_one: voiceMessageCount_one$4,
3943
3969
  voiceMessageCount_many: voiceMessageCount_many$2,
3944
3970
  voiceMessageCount_other: voiceMessageCount_other$6,
@@ -4163,7 +4189,6 @@ const jaTranslations = {
4163
4189
  "End vote": "投票を終了",
4164
4190
  "Enforce unique vote is enabled": "一意の投票が有効になっています",
4165
4191
  "Error": "エラー",
4166
- "Error · Unsent": "エラー・未送信",
4167
4192
  "Error adding flag": "フラグを追加のエラーが発生しました",
4168
4193
  "Error connecting to chat, refresh the page to try again.": "チャットへの接続ができませんでした。ページを更新してください。",
4169
4194
  "Error deleting message": "メッセージを削除するエラーが発生しました",
@@ -4282,8 +4307,6 @@ const jaTranslations = {
4282
4307
  "Maximum number of votes (from 2 to 10)": "最大投票数(2から10まで)",
4283
4308
  Menu: Menu$5,
4284
4309
  "Message deleted": "メッセージが削除されました",
4285
- "Message Failed · Click to try again": "メッセージが失敗しました · クリックして再試行してください",
4286
- "Message Failed · Unauthorized": "メッセージが失敗しました · 許可されていません",
4287
4310
  "Message failed to send": "メッセージの送信に失敗しました",
4288
4311
  "Message has been successfully flagged": "メッセージに正常にフラグが付けられました",
4289
4312
  "Message pinned": "メッセージにピンが付けられました",
@@ -4450,6 +4473,7 @@ const jaTranslations = {
4450
4473
  "View translation": "翻訳を表示",
4451
4474
  "Voice message": "ボイスメッセージ",
4452
4475
  "Voice message {{ duration }}": "ボイスメッセージ {{ duration }}",
4476
+ "Voice message deleted": "ボイスメッセージが削除されました",
4453
4477
  voiceMessageCount_other: voiceMessageCount_other$5,
4454
4478
  "Vote ended": "投票が終了しました",
4455
4479
  Votes: Votes$5,
@@ -4672,7 +4696,6 @@ const koTranslations = {
4672
4696
  "End vote": "투표 종료",
4673
4697
  "Enforce unique vote is enabled": "고유 투표가 활성화되었습니다",
4674
4698
  "Error": "오류",
4675
- "Error · Unsent": "오류 · 전송되지 않음",
4676
4699
  "Error adding flag": "플래그를 추가하는 동안 오류가 발생했습니다.",
4677
4700
  "Error connecting to chat, refresh the page to try again.": "채팅에 연결하는 동안 오류가 발생했습니다. 페이지를 새로고침하여 다시 시도하세요.",
4678
4701
  "Error deleting message": "메시지를 삭제하는 중에 오류가 발생했습니다.",
@@ -4791,8 +4814,6 @@ const koTranslations = {
4791
4814
  "Maximum number of votes (from 2 to 10)": "최대 투표 수 (2에서 10까지)",
4792
4815
  Menu: Menu$4,
4793
4816
  "Message deleted": "메시지가 삭제되었습니다.",
4794
- "Message Failed · Click to try again": "메시지 실패 · 다시 시도하려면 클릭하세요.",
4795
- "Message Failed · Unauthorized": "메시지 실패 · 승인되지 않음",
4796
4817
  "Message failed to send": "메시지 전송 실패",
4797
4818
  "Message has been successfully flagged": "메시지에 플래그가 지정되었습니다.",
4798
4819
  "Message pinned": "메시지 핀했습니다",
@@ -4959,6 +4980,7 @@ const koTranslations = {
4959
4980
  "View translation": "번역 보기",
4960
4981
  "Voice message": "음성 메시지",
4961
4982
  "Voice message {{ duration }}": "음성 메시지 {{ duration }}",
4983
+ "Voice message deleted": "음성 메시지가 삭제됨",
4962
4984
  voiceMessageCount_other: voiceMessageCount_other$4,
4963
4985
  "Vote ended": "투표 종료",
4964
4986
  Votes: Votes$4,
@@ -5188,7 +5210,6 @@ const nlTranslations = {
5188
5210
  "End vote": "Einde stem",
5189
5211
  "Enforce unique vote is enabled": "Unieke stem is ingeschakeld",
5190
5212
  "Error": "Fout",
5191
- "Error · Unsent": "Fout · niet verzonden",
5192
5213
  "Error adding flag": "Fout bij toevoegen van vlag",
5193
5214
  "Error connecting to chat, refresh the page to try again.": "Fout bij het verbinden, ververs de pagina om nogmaals te proberen",
5194
5215
  "Error deleting message": "Fout bij verwijderen van bericht",
@@ -5310,8 +5331,6 @@ const nlTranslations = {
5310
5331
  "Maximum number of votes (from 2 to 10)": "Maximaal aantal stemmen (van 2 tot 10)",
5311
5332
  Menu: Menu$3,
5312
5333
  "Message deleted": "Bericht verwijderd",
5313
- "Message Failed · Click to try again": "Bericht mislukt, klik om het nogmaals te proberen",
5314
- "Message Failed · Unauthorized": "Bericht mislukt, ongeautoriseerd",
5315
5334
  "Message failed to send": "Bericht kon niet worden verzonden",
5316
5335
  "Message has been successfully flagged": "Bericht is succesvol gemarkeerd",
5317
5336
  "Message pinned": "Bericht vastgezet",
@@ -5483,6 +5502,7 @@ const nlTranslations = {
5483
5502
  "View translation": "Vertaling bekijken",
5484
5503
  "Voice message": "Spraakbericht",
5485
5504
  "Voice message {{ duration }}": "Spraakbericht {{ duration }}",
5505
+ "Voice message deleted": "Spraakbericht verwijderd",
5486
5506
  voiceMessageCount_one: voiceMessageCount_one$3,
5487
5507
  voiceMessageCount_other: voiceMessageCount_other$3,
5488
5508
  "Vote ended": "Stemmen beëindigd",
@@ -5729,7 +5749,6 @@ const ptTranslations = {
5729
5749
  "End vote": "Encerrar votação",
5730
5750
  "Enforce unique vote is enabled": "Voto único está habilitado",
5731
5751
  "Error": "Erro",
5732
- "Error · Unsent": "Erro · Não enviado",
5733
5752
  "Error adding flag": "Erro ao reportar",
5734
5753
  "Error connecting to chat, refresh the page to try again.": "Erro ao conectar ao bate-papo, atualize a página para tentar novamente.",
5735
5754
  "Error deleting message": "Erro ao deletar mensagem",
@@ -5854,8 +5873,6 @@ const ptTranslations = {
5854
5873
  "Maximum number of votes (from 2 to 10)": "Número máximo de votos (de 2 a 10)",
5855
5874
  Menu: Menu$2,
5856
5875
  "Message deleted": "Mensagem apagada",
5857
- "Message Failed · Click to try again": "A mensagem falhou · Clique para tentar novamente",
5858
- "Message Failed · Unauthorized": "A mensagem falhou · não autorizado",
5859
5876
  "Message failed to send": "Falha ao enviar a mensagem",
5860
5877
  "Message has been successfully flagged": "A mensagem foi reportada com sucesso",
5861
5878
  "Message pinned": "Mensagem fixada",
@@ -6032,6 +6049,7 @@ const ptTranslations = {
6032
6049
  "View translation": "Ver tradução",
6033
6050
  "Voice message": "Mensagem de voz",
6034
6051
  "Voice message {{ duration }}": "Mensagem de voz {{ duration }}",
6052
+ "Voice message deleted": "Mensagem de voz excluída",
6035
6053
  voiceMessageCount_one: voiceMessageCount_one$2,
6036
6054
  voiceMessageCount_many: voiceMessageCount_many$1,
6037
6055
  voiceMessageCount_other: voiceMessageCount_other$2,
@@ -6299,7 +6317,6 @@ const ruTranslations = {
6299
6317
  "End vote": "Закончить голосование",
6300
6318
  "Enforce unique vote is enabled": "Уникальное голосование включено",
6301
6319
  "Error": "Ошибка",
6302
- "Error · Unsent": "Ошибка · Не отправлено",
6303
6320
  "Error adding flag": "Ошибка добавления флага",
6304
6321
  "Error connecting to chat, refresh the page to try again.": "Ошибка подключения к чату, обновите страницу чтобы попробовать снова.",
6305
6322
  "Error deleting message": "Ошибка при удалении сообщения",
@@ -6430,8 +6447,6 @@ const ruTranslations = {
6430
6447
  "Maximum number of votes (from 2 to 10)": "Максимальное количество голосов (от 2 до 10)",
6431
6448
  Menu: Menu$1,
6432
6449
  "Message deleted": "Сообщение удалено",
6433
- "Message Failed · Click to try again": "Ошибка отправки сообщения · Нажмите чтобы повторить",
6434
- "Message Failed · Unauthorized": "Ошибка отправки сообщения · Неавторизованный",
6435
6450
  "Message failed to send": "Не удалось отправить сообщение",
6436
6451
  "Message has been successfully flagged": "Жалоба на сообщение была принята",
6437
6452
  "Message pinned": "Сообщение закреплено",
@@ -6615,6 +6630,7 @@ const ruTranslations = {
6615
6630
  "View translation": "Показать перевод",
6616
6631
  "Voice message": "Голосовое сообщение",
6617
6632
  "Voice message {{ duration }}": "Голосовое сообщение {{ duration }}",
6633
+ "Voice message deleted": "Голосовое сообщение удалено",
6618
6634
  voiceMessageCount_one: voiceMessageCount_one$1,
6619
6635
  voiceMessageCount_few,
6620
6636
  voiceMessageCount_many,
@@ -6847,7 +6863,6 @@ const trTranslations = {
6847
6863
  "End vote": "Oyu bitir",
6848
6864
  "Enforce unique vote is enabled": "Benzersiz oy etkinleştirildi",
6849
6865
  "Error": "Hata",
6850
- "Error · Unsent": "Hata · Gönderilemedi",
6851
6866
  "Error adding flag": "Bayrak eklenirken hata oluştu",
6852
6867
  "Error connecting to chat, refresh the page to try again.": "Bağlantı hatası, sayfayı yenileyip tekrar deneyin.",
6853
6868
  "Error deleting message": "Mesaj silinirken hata oluştu",
@@ -6969,8 +6984,6 @@ const trTranslations = {
6969
6984
  "Maximum number of votes (from 2 to 10)": "Maksimum oy sayısı (2 ile 10 arası)",
6970
6985
  Menu,
6971
6986
  "Message deleted": "Mesaj silindi",
6972
- "Message Failed · Click to try again": "Mesaj Başarısız · Tekrar denemek için tıklayın",
6973
- "Message Failed · Unauthorized": "Mesaj Başarısız · Yetkisiz",
6974
6987
  "Message failed to send": "Mesaj gönderilemedi",
6975
6988
  "Message has been successfully flagged": "Mesaj başarıyla bayraklandı",
6976
6989
  "Message pinned": "Mesaj sabitlendi",
@@ -7140,6 +7153,7 @@ const trTranslations = {
7140
7153
  "View translation": "Çeviriyi görüntüle",
7141
7154
  "Voice message": "Sesli mesaj",
7142
7155
  "Voice message {{ duration }}": "Sesli mesaj {{ duration }}",
7156
+ "Voice message deleted": "Sesli mesaj silindi",
7143
7157
  voiceMessageCount_one,
7144
7158
  voiceMessageCount_other,
7145
7159
  "Vote ended": "Oylama sona erdi",
@@ -8569,11 +8583,18 @@ function useActionHandler(message) {
8569
8583
  }
8570
8584
  const useDeleteHandler = (message, notifications = {}) => {
8571
8585
  const { getErrorNotification, notify } = notifications;
8572
- const { deleteMessage, updateMessage } = useChannelActionContext("useDeleteHandler");
8586
+ const { deleteMessage, removeMessage, updateMessage } = useChannelActionContext("useDeleteHandler");
8573
8587
  const { client } = useChatContext("useDeleteHandler");
8574
8588
  const { t } = useTranslationContext("useDeleteHandler");
8575
8589
  return async (options) => {
8576
- if (!message?.id || !client || !updateMessage) {
8590
+ if (!message) {
8591
+ return;
8592
+ }
8593
+ if (message.type === "error" || isNetworkSendFailure(message)) {
8594
+ removeMessage?.(message);
8595
+ return;
8596
+ }
8597
+ if (!message.id || !client || !updateMessage) {
8577
8598
  return;
8578
8599
  }
8579
8600
  try {
@@ -8582,6 +8603,7 @@ const useDeleteHandler = (message, notifications = {}) => {
8582
8603
  } catch (e) {
8583
8604
  const errorMessage = getErrorNotification && validateAndGetMessage(getErrorNotification, [message]);
8584
8605
  if (notify) notify(errorMessage || t("Error deleting message"), "error");
8606
+ throw e;
8585
8607
  }
8586
8608
  };
8587
8609
  };
@@ -8893,7 +8915,7 @@ const useUserRole = (message, onlySenderCanEdit, disableQuotedMessages) => {
8893
8915
  const canFlag = !isMyMessage && channelCapabilities["flag-message"];
8894
8916
  const canMute = !isMyMessage && channelCapabilities["mute-channel"];
8895
8917
  const canBlockUser = !isMyMessage;
8896
- const canMarkUnread = channelCapabilities["read-events"];
8918
+ const canMarkUnread = !isMyMessage && channelCapabilities["read-events"];
8897
8919
  const canQuote = !disableQuotedMessages && channelCapabilities["quote-message"];
8898
8920
  const canReact = channelCapabilities["send-reaction"];
8899
8921
  const canReply = channelCapabilities["send-reply"];
@@ -8990,73 +9012,6 @@ const useMessageReminder = (messageId) => {
8990
9012
  const { reminder } = useStateStore(client.reminders.state, reminderSelector);
8991
9013
  return reminder;
8992
9014
  };
8993
- const ActionsIcon = ({ className = "" }) => /* @__PURE__ */ jsx(
8994
- "svg",
8995
- {
8996
- className,
8997
- height: "4",
8998
- viewBox: "0 0 11 4",
8999
- width: "11",
9000
- xmlns: "http://www.w3.org/2000/svg",
9001
- children: /* @__PURE__ */ jsx(
9002
- "path",
9003
- {
9004
- 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",
9005
- fillRule: "nonzero"
9006
- }
9007
- )
9008
- }
9009
- );
9010
- const ReactionIcon = ({ className = "" }) => /* @__PURE__ */ jsx(
9011
- "svg",
9012
- {
9013
- className,
9014
- height: "12",
9015
- viewBox: "0 0 12 12",
9016
- width: "12",
9017
- xmlns: "http://www.w3.org/2000/svg",
9018
- children: /* @__PURE__ */ jsxs("g", { clipRule: "evenodd", fillRule: "evenodd", children: [
9019
- /* @__PURE__ */ 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" }),
9020
- /* @__PURE__ */ 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" })
9021
- ] })
9022
- }
9023
- );
9024
- const ThreadIcon = ({ className = "" }) => /* @__PURE__ */ jsx("svg", { className, height: "10", width: "14", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ jsx(
9025
- "path",
9026
- {
9027
- 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",
9028
- fillRule: "evenodd"
9029
- }
9030
- ) });
9031
- const PinIcon = () => /* @__PURE__ */ jsx("svg", { height: "13", viewBox: "0 0 14 13", width: "14", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ jsx(
9032
- "path",
9033
- {
9034
- 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",
9035
- fillRule: "evenodd"
9036
- }
9037
- ) });
9038
- const MessageErrorIcon = () => /* @__PURE__ */ jsx("div", { className: "str-chat__message-error-icon", children: /* @__PURE__ */ jsxs(
9039
- "svg",
9040
- {
9041
- "data-testid": "error",
9042
- fill: "none",
9043
- height: "24",
9044
- viewBox: "0 0 24 24",
9045
- width: "24",
9046
- xmlns: "http://www.w3.org/2000/svg",
9047
- children: [
9048
- /* @__PURE__ */ jsx(
9049
- "path",
9050
- {
9051
- 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",
9052
- fill: "black",
9053
- id: "background"
9054
- }
9055
- ),
9056
- /* @__PURE__ */ jsx("path", { d: "M13 17H11V15H13V17ZM13 13H11V7H13V13Z", fill: "white" })
9057
- ]
9058
- }
9059
- ) });
9060
9015
  const Root = forwardRef(function AlertRoot({ children, className, ...props }, ref) {
9061
9016
  return /* @__PURE__ */ jsx("div", { ...props, className: clsx("str-chat__alert-root", className), ref, children });
9062
9017
  });
@@ -9137,6 +9092,7 @@ function useDialogAnchor({
9137
9092
  setPopperElement(null);
9138
9093
  }
9139
9094
  return {
9095
+ placement: stabilisedChosenPlacement ?? chosenPlacement,
9140
9096
  setPopperElement,
9141
9097
  styles: {
9142
9098
  left: x ?? 0,
@@ -9149,6 +9105,8 @@ const DialogAnchor = ({
9149
9105
  allowFlip = true,
9150
9106
  children,
9151
9107
  className,
9108
+ closeOnClickOutside,
9109
+ closeTransitionMs = 0,
9152
9110
  dialogManagerId,
9153
9111
  focus = true,
9154
9112
  id,
@@ -9161,12 +9119,44 @@ const DialogAnchor = ({
9161
9119
  updatePositionOnContentResize,
9162
9120
  ...restDivProps
9163
9121
  }) => {
9164
- const dialog = useDialog({ dialogManagerId, id });
9122
+ const dialog = useDialog({ closeOnClickOutside, dialogManagerId, id });
9165
9123
  const open = useDialogIsOpen(id, dialogManagerId);
9166
- const { setPopperElement, styles } = useDialogAnchor({
9124
+ const [shouldRender, setShouldRender] = useState(open);
9125
+ const closeTimeoutRef = useRef(null);
9126
+ const isClosing = !open && shouldRender;
9127
+ useEffect(() => {
9128
+ if (open) {
9129
+ setShouldRender(true);
9130
+ if (closeTimeoutRef.current) {
9131
+ clearTimeout(closeTimeoutRef.current);
9132
+ closeTimeoutRef.current = null;
9133
+ }
9134
+ return;
9135
+ }
9136
+ if (!shouldRender) return;
9137
+ if (!closeTransitionMs) {
9138
+ setShouldRender(false);
9139
+ return;
9140
+ }
9141
+ closeTimeoutRef.current = setTimeout(() => {
9142
+ setShouldRender(false);
9143
+ closeTimeoutRef.current = null;
9144
+ }, closeTransitionMs);
9145
+ }, [closeTransitionMs, open, shouldRender]);
9146
+ useEffect(
9147
+ () => () => {
9148
+ if (closeTimeoutRef.current) clearTimeout(closeTimeoutRef.current);
9149
+ },
9150
+ []
9151
+ );
9152
+ const {
9153
+ placement: chosenPlacement,
9154
+ setPopperElement,
9155
+ styles
9156
+ } = useDialogAnchor({
9167
9157
  allowFlip,
9168
9158
  offset,
9169
- open,
9159
+ open: shouldRender,
9170
9160
  placement,
9171
9161
  referenceElement,
9172
9162
  updateKey,
@@ -9183,7 +9173,7 @@ const DialogAnchor = ({
9183
9173
  document.removeEventListener("keyup", hideOnEscape);
9184
9174
  };
9185
9175
  }, [dialog, open]);
9186
- if (!open) {
9176
+ if (!shouldRender) {
9187
9177
  return null;
9188
9178
  }
9189
9179
  return /* @__PURE__ */ jsx(DialogPortalEntry, { dialogId: id, dialogManagerId, children: /* @__PURE__ */ jsx(FocusScope, { autoFocus: focus, contain: trapFocus, restoreFocus: true, children: /* @__PURE__ */ jsx(
@@ -9191,6 +9181,8 @@ const DialogAnchor = ({
9191
9181
  {
9192
9182
  ...restDivProps,
9193
9183
  className: clsx("str-chat__dialog-contents", className),
9184
+ "data-str-chat-dialog-state": isClosing ? "closing" : "open",
9185
+ "data-str-chat-placement": chosenPlacement,
9194
9186
  "data-testid": "str-chat__dialog-contents",
9195
9187
  ref: setPopperElement,
9196
9188
  style: styles,
@@ -9317,6 +9309,11 @@ const Badge = ({
9317
9309
  children
9318
9310
  }
9319
9311
  );
9312
+ const ErrorBadge = ({
9313
+ className,
9314
+ size = "sm",
9315
+ ...rest
9316
+ }) => /* @__PURE__ */ jsx(Badge, { ...rest, className, size, variant: "error", children: /* @__PURE__ */ jsx(IconExclamation, {}) });
9320
9317
  function AvatarStack({
9321
9318
  badgeSize,
9322
9319
  component: Component2 = "div",
@@ -9528,16 +9525,152 @@ const EmojiContextMenuButton = ({
9528
9525
  ]
9529
9526
  }
9530
9527
  );
9531
- const ContextMenuButton = ({
9532
- onBlur,
9533
- onFocus,
9534
- ...props
9528
+ const ContextMenuButtonWithSubmenu = ({
9529
+ children,
9530
+ className,
9531
+ Submenu,
9532
+ submenuContainerProps,
9533
+ submenuPlacement = "right-start",
9534
+ submenuRollAxis = "x",
9535
+ ...buttonProps
9535
9536
  }) => {
9537
+ const { className: submenuClassName, ...submenuContainerRestProps } = submenuContainerProps ?? {};
9538
+ const buttonRef = useRef(null);
9539
+ const [dialogContainer, setDialogContainer] = useState(null);
9540
+ const keepSubmenuOpenFlag = useRef(false);
9541
+ const dialogCloseTimeout = useRef(null);
9542
+ const dialogId2 = useMemo(() => `submenu-${Math.random().toString(36).slice(2)}`, []);
9543
+ const { dialog, dialogManager } = useDialogOnNearestManager({ id: dialogId2 });
9544
+ const dialogIsOpen = useDialogIsOpen(dialogId2, dialogManager?.id);
9545
+ const {
9546
+ placement: chosenPlacement,
9547
+ setPopperElement,
9548
+ styles
9549
+ } = useDialogAnchor({
9550
+ open: dialogIsOpen,
9551
+ placement: submenuPlacement,
9552
+ referenceElement: buttonRef.current
9553
+ });
9554
+ const closeDialogLazily = useCallback(() => {
9555
+ if (dialogCloseTimeout.current) clearTimeout(dialogCloseTimeout.current);
9556
+ dialogCloseTimeout.current = setTimeout(() => {
9557
+ if (keepSubmenuOpenFlag.current) return;
9558
+ dialog.close();
9559
+ }, 100);
9560
+ }, [dialog]);
9561
+ const keepSubmenuOpen = useCallback(() => {
9562
+ keepSubmenuOpenFlag.current = true;
9563
+ }, []);
9564
+ const allowToCloseSubmenu = useCallback(() => {
9565
+ keepSubmenuOpenFlag.current = false;
9566
+ }, []);
9567
+ const closeSubmenu = useCallback(() => {
9568
+ allowToCloseSubmenu();
9569
+ closeDialogLazily();
9570
+ }, [allowToCloseSubmenu, closeDialogLazily]);
9571
+ const handleClose = useCallback(
9572
+ (event) => {
9573
+ const parentButton = buttonRef.current;
9574
+ if (!dialogIsOpen || !parentButton) return;
9575
+ event.stopPropagation();
9576
+ closeDialogLazily();
9577
+ parentButton.focus();
9578
+ },
9579
+ [closeDialogLazily, dialogIsOpen, buttonRef]
9580
+ );
9581
+ const handleFocusParentButton = () => {
9582
+ if (dialogIsOpen) return;
9583
+ dialog.open();
9584
+ keepSubmenuOpen();
9585
+ };
9586
+ useEffect(() => {
9587
+ const parentButton = buttonRef.current;
9588
+ if (!dialogIsOpen || !parentButton) return;
9589
+ const hideOnEscape = (event) => {
9590
+ if (event.key !== "Escape") return;
9591
+ handleClose(event);
9592
+ closeSubmenu();
9593
+ };
9594
+ document.addEventListener("keyup", hideOnEscape, { capture: true });
9595
+ return () => {
9596
+ document.removeEventListener("keyup", hideOnEscape, { capture: true });
9597
+ };
9598
+ }, [dialogIsOpen, handleClose, closeSubmenu]);
9599
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
9600
+ /* @__PURE__ */ jsx(
9601
+ BaseContextMenuButton,
9602
+ {
9603
+ "aria-selected": "false",
9604
+ className: clsx(className, "str_chat__button-with-submenu", {
9605
+ "str_chat__button-with-submenu--submenu-open": dialogIsOpen
9606
+ }),
9607
+ hasSubMenu: true,
9608
+ onBlur: closeSubmenu,
9609
+ onClick: (event) => {
9610
+ event.stopPropagation();
9611
+ dialog.toggle();
9612
+ },
9613
+ onFocus: handleFocusParentButton,
9614
+ onMouseEnter: handleFocusParentButton,
9615
+ onMouseLeave: closeSubmenu,
9616
+ role: "option",
9617
+ ...buttonProps,
9618
+ ref: buttonRef,
9619
+ children
9620
+ }
9621
+ ),
9622
+ dialogIsOpen && /* @__PURE__ */ jsx(
9623
+ "div",
9624
+ {
9625
+ className: clsx("str-chat__context-menu__submenu-container", submenuClassName),
9626
+ "data-str-chat-placement": chosenPlacement,
9627
+ "data-str-chat-roll-axis": submenuRollAxis,
9628
+ onBlur: (event) => {
9629
+ const isBlurredDescendant = event.relatedTarget instanceof Node && dialogContainer?.contains(event.relatedTarget);
9630
+ if (isBlurredDescendant) return;
9631
+ closeSubmenu();
9632
+ },
9633
+ onFocus: keepSubmenuOpen,
9634
+ onMouseEnter: keepSubmenuOpen,
9635
+ onMouseLeave: closeSubmenu,
9636
+ ref: (element) => {
9637
+ setPopperElement(element);
9638
+ setDialogContainer(element);
9639
+ },
9640
+ style: styles,
9641
+ tabIndex: -1,
9642
+ ...submenuContainerRestProps,
9643
+ children: /* @__PURE__ */ jsx(Submenu, {})
9644
+ }
9645
+ )
9646
+ ] });
9647
+ };
9648
+ const ContextMenuButton = (props) => {
9649
+ const {
9650
+ Submenu,
9651
+ submenuContainerProps,
9652
+ submenuPlacement,
9653
+ submenuRollAxis,
9654
+ ...buttonProps
9655
+ } = props;
9536
9656
  const [isFocused, setIsFocused] = useState(false);
9657
+ if (Submenu) {
9658
+ return /* @__PURE__ */ jsx(
9659
+ ContextMenuButtonWithSubmenu,
9660
+ {
9661
+ ...buttonProps,
9662
+ Submenu,
9663
+ submenuContainerProps,
9664
+ submenuPlacement,
9665
+ submenuRollAxis
9666
+ }
9667
+ );
9668
+ }
9669
+ const { onBlur, onFocus, ...baseButtonProps } = buttonProps;
9537
9670
  return /* @__PURE__ */ jsx(
9538
9671
  BaseContextMenuButton,
9539
9672
  {
9540
- ...props,
9673
+ ...baseButtonProps,
9541
9674
  "aria-selected": isFocused ? "true" : "false",
9542
9675
  onBlur: (e) => {
9543
9676
  setIsFocused(false);
@@ -9586,12 +9719,14 @@ function ContextMenuContent({
9586
9719
  backLabel = "Back",
9587
9720
  children,
9588
9721
  className,
9722
+ enableAnimations = true,
9589
9723
  Header: Header2,
9590
9724
  items,
9591
9725
  ItemsWrapper,
9592
9726
  menuClassName,
9593
9727
  onClose,
9594
9728
  onMenuLevelChange,
9729
+ transitionDirection,
9595
9730
  ...props
9596
9731
  }) {
9597
9732
  const rootLevel = useMemo(
@@ -9604,6 +9739,7 @@ function ContextMenuContent({
9604
9739
  [Header2, items, ItemsWrapper, menuClassName]
9605
9740
  );
9606
9741
  const [menuStack, setMenuStack] = useState(() => [rootLevel]);
9742
+ const [menuBodyAnimationKey, setMenuBodyAnimationKey] = useState(0);
9607
9743
  const activeMenu = menuStack[menuStack.length - 1];
9608
9744
  const ActiveMenuItemsWrapper = activeMenu.ItemsWrapper ?? React.Fragment;
9609
9745
  const closeMenu = useCallback(() => {
@@ -9627,10 +9763,9 @@ function ContextMenuContent({
9627
9763
  [ItemsWrapper]
9628
9764
  );
9629
9765
  const returnToParentMenu = useCallback(() => {
9630
- setMenuStack(
9631
- (current) => current.length > 1 ? current.slice(0, current.length - 1) : current
9632
- );
9633
- }, []);
9766
+ if (menuStack.length <= 1) return;
9767
+ setMenuStack((current) => current.slice(0, current.length - 1));
9768
+ }, [menuStack.length]);
9634
9769
  useEffect(() => {
9635
9770
  setMenuStack((current) => {
9636
9771
  if (current.length === 1 && current[0] === rootLevel) return current;
@@ -9640,42 +9775,116 @@ function ContextMenuContent({
9640
9775
  useEffect(() => {
9641
9776
  onMenuLevelChange?.(menuStack.length);
9642
9777
  }, [menuStack.length, onMenuLevelChange]);
9643
- return /* @__PURE__ */ jsx(ContextMenuContext.Provider, { value: { closeMenu, openSubmenu, returnToParentMenu }, children: /* @__PURE__ */ jsxs(ContextMenuRoot, { className: clsx(className, activeMenu.menuClassName), ...props, children: [
9644
- activeMenu.Header ? /* @__PURE__ */ jsx(activeMenu.Header, {}) : menuStack.length > 1 ? /* @__PURE__ */ jsx(ContextMenuHeader, { children: /* @__PURE__ */ jsxs(ContextMenuBackButton, { onClick: returnToParentMenu, children: [
9645
- /* @__PURE__ */ jsx(IconChevronLeft, {}),
9646
- /* @__PURE__ */ jsx("span", { children: backLabel })
9647
- ] }) }) : null,
9648
- /* @__PURE__ */ jsx(ContextMenuBody, { children: activeMenu.Submenu ? /* @__PURE__ */ jsx(activeMenu.Submenu, {}) : /* @__PURE__ */ jsx(ActiveMenuItemsWrapper, { children: typeof children !== "undefined" ? children : activeMenu.items?.map((Item2, index) => /* @__PURE__ */ jsx(Item2, {}, `context-menu-item-${index}`)) }) })
9649
- ] }) });
9778
+ useEffect(() => {
9779
+ if (!transitionDirection) return;
9780
+ setMenuBodyAnimationKey((value) => value + 1);
9781
+ }, [transitionDirection, menuStack.length]);
9782
+ return /* @__PURE__ */ jsx(ContextMenuContext.Provider, { value: { closeMenu, openSubmenu, returnToParentMenu }, children: /* @__PURE__ */ jsxs(
9783
+ ContextMenuRoot,
9784
+ {
9785
+ className: clsx(className, activeMenu.menuClassName),
9786
+ "data-str-chat-enable-animations": enableAnimations,
9787
+ ...props,
9788
+ children: [
9789
+ activeMenu.Header ? /* @__PURE__ */ jsx(activeMenu.Header, {}) : menuStack.length > 1 ? /* @__PURE__ */ jsx(ContextMenuHeader, { children: /* @__PURE__ */ jsxs(ContextMenuBackButton, { onClick: returnToParentMenu, children: [
9790
+ /* @__PURE__ */ jsx(IconChevronLeft, {}),
9791
+ /* @__PURE__ */ jsx("span", { children: backLabel })
9792
+ ] }) }) : null,
9793
+ /* @__PURE__ */ jsx(
9794
+ ContextMenuBody,
9795
+ {
9796
+ className: clsx({
9797
+ "str-chat__context-menu__body--submenu-backward": transitionDirection === "backward",
9798
+ "str-chat__context-menu__body--submenu-forward": transitionDirection === "forward"
9799
+ }),
9800
+ children: activeMenu.Submenu ? /* @__PURE__ */ jsx(activeMenu.Submenu, {}) : /* @__PURE__ */ jsx(ActiveMenuItemsWrapper, { children: typeof children !== "undefined" ? children : activeMenu.items?.map((Item2, index) => /* @__PURE__ */ jsx(Item2, {}, `context-menu-item-${index}`)) })
9801
+ },
9802
+ `context-menu-body-${menuStack.length}-${menuBodyAnimationKey}`
9803
+ )
9804
+ ]
9805
+ }
9806
+ ) });
9650
9807
  }
9651
9808
  const ContextMenu = (props) => {
9809
+ const { ContextMenuContent: ContextMenuContentComponent = ContextMenuContent } = useComponentContext();
9652
9810
  const {
9653
9811
  allowFlip,
9812
+ closeOnClickOutside,
9813
+ closeTransitionMs = 130,
9654
9814
  dialogManagerId,
9655
9815
  focus,
9656
9816
  id,
9657
9817
  placement,
9658
9818
  referenceElement,
9819
+ submenuTransitionDurationMs,
9659
9820
  tabIndex,
9660
9821
  trapFocus,
9661
9822
  ...menuProps
9662
9823
  } = props;
9824
+ const resolvedSubmenuTransitionDurationMs = submenuTransitionDurationMs ?? 460;
9663
9825
  const isAnchored = id != null;
9664
9826
  const [menuLevel, setMenuLevel] = useState(1);
9827
+ const [transitionDirection, setTransitionDirection] = useState(void 0);
9828
+ const [contentResetToken, setContentResetToken] = useState(0);
9829
+ const transitionTimeoutRef = useRef(null);
9830
+ const previousMenuLevelRef = useRef(1);
9665
9831
  const open = useDialogIsOpen(id ?? "", dialogManagerId);
9832
+ const previousOpenRef = useRef(open);
9666
9833
  useEffect(() => {
9667
- if (isAnchored && !open) setMenuLevel(1);
9834
+ if (!isAnchored) return;
9835
+ if (previousOpenRef.current && !open) {
9836
+ setMenuLevel(1);
9837
+ setTransitionDirection(void 0);
9838
+ setContentResetToken((value) => value + 1);
9839
+ previousMenuLevelRef.current = 1;
9840
+ if (transitionTimeoutRef.current) {
9841
+ clearTimeout(transitionTimeoutRef.current);
9842
+ transitionTimeoutRef.current = null;
9843
+ }
9844
+ }
9845
+ previousOpenRef.current = open;
9668
9846
  }, [isAnchored, open]);
9669
- const content = /* @__PURE__ */ jsx(
9670
- ContextMenuContent,
9847
+ useEffect(
9848
+ () => () => {
9849
+ if (transitionTimeoutRef.current) {
9850
+ clearTimeout(transitionTimeoutRef.current);
9851
+ }
9852
+ },
9853
+ []
9854
+ );
9855
+ const handleMenuLevelChange = useCallback(
9856
+ (level) => {
9857
+ if (isAnchored) {
9858
+ const previousLevel = previousMenuLevelRef.current;
9859
+ if (level !== previousLevel) {
9860
+ setTransitionDirection(level > previousLevel ? "forward" : "backward");
9861
+ if (transitionTimeoutRef.current) clearTimeout(transitionTimeoutRef.current);
9862
+ transitionTimeoutRef.current = setTimeout(() => {
9863
+ setTransitionDirection(void 0);
9864
+ transitionTimeoutRef.current = null;
9865
+ }, resolvedSubmenuTransitionDurationMs);
9866
+ }
9867
+ previousMenuLevelRef.current = level;
9868
+ setMenuLevel(level);
9869
+ return;
9870
+ }
9871
+ menuProps.onMenuLevelChange?.(level);
9872
+ },
9873
+ [isAnchored, menuProps, resolvedSubmenuTransitionDurationMs]
9874
+ );
9875
+ const content = /* @__PURE__ */ createElement(
9876
+ ContextMenuContentComponent,
9671
9877
  {
9672
9878
  ...menuProps,
9673
- onMenuLevelChange: isAnchored ? setMenuLevel : menuProps.onMenuLevelChange
9879
+ key: `context-menu-content-${contentResetToken}`,
9880
+ onMenuLevelChange: handleMenuLevelChange,
9881
+ transitionDirection
9674
9882
  }
9675
9883
  );
9676
9884
  if (isAnchored) {
9677
9885
  const {
9678
9886
  backLabel: _b,
9887
+ enableAnimations: _ea,
9679
9888
  Header: _h,
9680
9889
  items: _i,
9681
9890
  ItemsWrapper: _w,
@@ -9688,6 +9897,8 @@ const ContextMenu = (props) => {
9688
9897
  DialogAnchor,
9689
9898
  {
9690
9899
  allowFlip,
9900
+ closeOnClickOutside,
9901
+ closeTransitionMs,
9691
9902
  dialogManagerId,
9692
9903
  focus,
9693
9904
  id,
@@ -9841,9 +10052,9 @@ const GlobalModal = ({
9841
10052
  (source, event) => {
9842
10053
  const allow = onCloseAttempt?.(source, event);
9843
10054
  if (allow !== false) {
9844
- onClose?.(event);
9845
10055
  dialog.close();
9846
10056
  closingRef.current = true;
10057
+ onClose?.(event);
9847
10058
  }
9848
10059
  },
9849
10060
  [dialog, onClose, onCloseAttempt]
@@ -10019,16 +10230,19 @@ const useBaseMessageActionSetFilter = (messageActionSet, disable = false) => {
10019
10230
  const isMessageThreadReply = typeof message.parent_id === "string";
10020
10231
  const isBounced = isMessageBounced(message);
10021
10232
  const allowRetry = isMessageErrorRetryable(message);
10233
+ const hasNetworkSendFailure = isNetworkSendFailure(message);
10022
10234
  return useMemo(() => {
10023
10235
  if (disable) return messageActionSet;
10024
10236
  if (isBounced || isInitialMessage || // not sure whether this thing even works anymore
10025
10237
  !message.type || message.type === "system" || message.type === "ephemeral" || message.status === "sending")
10026
10238
  return [];
10027
- return messageActionSet.filter(({ type }) => {
10239
+ return messageActionSet.filter((action) => {
10240
+ if (action.placement === "quick-dropdown-toggle") return true;
10241
+ const type = action.type;
10028
10242
  if (ACTIONS_NOT_WORKING_IN_THREAD.includes(type) && isMessageThreadReply)
10029
10243
  return false;
10030
10244
  if (message.error) {
10031
- return type === "resendMessage" && canSendMessage && (allowRetry || isBounced) || type === "edit" && canEdit && isBounced || type === "delete" && canDelete && isBounced;
10245
+ return type === "resendMessage" && canSendMessage && (allowRetry || isBounced) || type === "edit" && (isBounced && canEdit || hasNetworkSendFailure) || type === "delete" && (isBounced && canDelete || hasNetworkSendFailure);
10032
10246
  }
10033
10247
  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"])
10034
10248
  return false;
@@ -10055,6 +10269,7 @@ const useBaseMessageActionSetFilter = (messageActionSet, disable = false) => {
10055
10269
  message.text,
10056
10270
  message.type,
10057
10271
  disable,
10272
+ hasNetworkSendFailure,
10058
10273
  messageActionSet
10059
10274
  ]);
10060
10275
  };
@@ -10370,14 +10585,14 @@ const matchMarkdownLinks = (message) => {
10370
10585
  const regexMdLinks = /\[([^[]+)\](\(.*\))/gm;
10371
10586
  const matches = message.match(regexMdLinks);
10372
10587
  const singleMatch = /\[([^[]+)\]\((.*)\)/;
10373
- const links = matches ? matches.map((match2) => {
10374
- const i = singleMatch.exec(match2);
10588
+ const links = matches ? matches.map((match) => {
10589
+ const i = singleMatch.exec(match);
10375
10590
  return i && [i[1], i[2]];
10376
10591
  }) : [];
10377
10592
  return links.flat();
10378
10593
  };
10379
10594
  const emojiMarkdownPlugin = () => {
10380
- const replace = (match2) => u("element", { properties: {}, tagName: "emoji" }, [u("text", match2)]);
10595
+ const replace = (match) => u("element", { properties: {}, tagName: "emoji" }, [u("text", match)]);
10381
10596
  const transform2 = (node) => findAndReplace(node, [emojiRegex(), replace]);
10382
10597
  return transform2;
10383
10598
  };
@@ -10387,13 +10602,13 @@ const mentionsMarkdownPlugin = (mentioned_users) => () => {
10387
10602
  mentioned_usernames.map((username) => `@${username}`).join("|"),
10388
10603
  "g"
10389
10604
  );
10390
- const replace = (match2) => {
10391
- const usernameOrId = match2.replace("@", "");
10605
+ const replace = (match) => {
10606
+ const usernameOrId = match.replace("@", "");
10392
10607
  const user = mentioned_users.find(
10393
10608
  ({ id, name }) => name === usernameOrId || id === usernameOrId
10394
10609
  );
10395
10610
  return u("element", { mentionedUser: user, properties: {}, tagName: "mention" }, [
10396
- u("text", match2)
10611
+ u("text", match)
10397
10612
  ]);
10398
10613
  };
10399
10614
  const transform2 = (tree) => {
@@ -10498,12 +10713,12 @@ const plusPlusToEmphasis = () => {
10498
10713
  if (node.type !== "text" || parent == null || typeof index !== "number") return;
10499
10714
  const value = node.value;
10500
10715
  INS_REGEX.lastIndex = 0;
10501
- let match2;
10716
+ let match;
10502
10717
  let last = 0;
10503
10718
  const out = [];
10504
- while (match2 = INS_REGEX.exec(value)) {
10505
- const [full, inner] = match2;
10506
- const start = match2.index;
10719
+ while (match = INS_REGEX.exec(value)) {
10720
+ const [full, inner] = match;
10721
+ const start = match.index;
10507
10722
  if (start > last) out.push({ type: "text", value: value.slice(last, start) });
10508
10723
  out.push({
10509
10724
  children: [{ type: "text", value: inner }],
@@ -10679,17 +10894,6 @@ const renderText = (text2, mentionedUsers, {
10679
10894
  }
10680
10895
  ) });
10681
10896
  };
10682
- const ROOT_CLASS_NAME = "str-chat__message--error-message";
10683
- function MessageErrorText({ message }) {
10684
- const { t } = useTranslationContext("MessageText");
10685
- if (message.type === "error" && !isMessageBounced(message)) {
10686
- return /* @__PURE__ */ jsx("div", { className: ROOT_CLASS_NAME, children: t("Error · Unsent") });
10687
- }
10688
- if (message.status === "failed") {
10689
- return /* @__PURE__ */ jsx("div", { className: ROOT_CLASS_NAME, children: message.error?.status !== 403 ? t("Message Failed · Click to try again") : t("Message Failed · Unauthorized") });
10690
- }
10691
- return null;
10692
- }
10693
10897
  const UnMemoizedMessageTextComponent = (props) => {
10694
10898
  const {
10695
10899
  customInnerClass,
@@ -10717,7 +10921,7 @@ const UnMemoizedMessageTextComponent = (props) => {
10717
10921
  const wrapperClass = customWrapperClass || "str-chat__message-text";
10718
10922
  const innerClass = customInnerClass;
10719
10923
  if (!messageTextToRender) return null;
10720
- return /* @__PURE__ */ jsx("div", { className: wrapperClass, tabIndex: 0, children: /* @__PURE__ */ jsxs(
10924
+ return /* @__PURE__ */ jsx("div", { className: wrapperClass, tabIndex: 0, children: /* @__PURE__ */ jsx(
10721
10925
  "div",
10722
10926
  {
10723
10927
  className: clsx(innerClass, {
@@ -10727,10 +10931,7 @@ const UnMemoizedMessageTextComponent = (props) => {
10727
10931
  "data-testid": "message-text-inner-wrapper",
10728
10932
  onClick: onMentionsClickMessage,
10729
10933
  onMouseOver: onMentionsHoverMessage,
10730
- children: [
10731
- /* @__PURE__ */ jsx(MessageErrorText, { message }),
10732
- unsafeHTML && message.html ? /* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: message.html } }) : /* @__PURE__ */ jsx("div", { children: messageText })
10733
- ]
10934
+ children: unsafeHTML && message.html ? /* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: message.html } }) : /* @__PURE__ */ jsx("div", { children: messageText })
10734
10935
  }
10735
10936
  ) });
10736
10937
  };
@@ -11945,6 +12146,7 @@ const toBaseImageDescriptors = (attachment, options = {}) => {
11945
12146
  title: attachment.title
11946
12147
  };
11947
12148
  }
12149
+ return void 0;
11948
12150
  };
11949
12151
  const BASE_FILE_ICON_CLASSNAME = "str-chat__file-icon";
11950
12152
  const FILE_ICON_GRAPHIC_CLASSNAME = "str-chat__file-icon__graphic";
@@ -14782,7 +14984,8 @@ const PollOptionWithVotes = ({
14782
14984
  "div",
14783
14985
  {
14784
14986
  className: clsx("str-chat__poll-option", {
14785
- "str-chat__poll-option--has-more-votes": isVotesPreview && voteCount > countVotesPreview
14987
+ "str-chat__poll-option--has-more-votes": isVotesPreview && voteCount > countVotesPreview,
14988
+ "str-chat__poll-option--has-votes": voteCount
14786
14989
  }),
14787
14990
  children: [
14788
14991
  /* @__PURE__ */ jsx(PollOptionWithVotesHeader, { option, optionOrderNumber: orderNumber }),
@@ -15076,10 +15279,12 @@ const PollOptionSelector = ({
15076
15279
  canCastVote && /* @__PURE__ */ jsx(Checkmark, { checked: !!ownVotesByOptionId[option.id] }),
15077
15280
  /* @__PURE__ */ jsxs("div", { className: "str-chat__poll-option-data", children: [
15078
15281
  /* @__PURE__ */ jsx("p", { className: "str-chat__poll-option-text", children: option.text }),
15079
- displayAvatarCount && voting_visibility === "public" && /* @__PURE__ */ jsx("div", { className: "str-chat__poll-option-voters", children: /* @__PURE__ */ jsx(AvatarStack$1, { displayInfo: avatarDisplayInfo, size: "xs" }) }),
15080
- /* @__PURE__ */ jsx("div", { className: "str-chat__poll-option-vote-count", children: voteCountVerbose ? t("{{count}} votes", {
15081
- count: vote_counts_by_option[option.id] ?? 0
15082
- }) : vote_counts_by_option[option.id] ?? 0 })
15282
+ /* @__PURE__ */ jsxs("div", { className: "str-chat__poll-option-votes", children: [
15283
+ displayAvatarCount && voting_visibility === "public" && /* @__PURE__ */ jsx("div", { className: "str-chat__poll-option-voters", children: /* @__PURE__ */ jsx(AvatarStack$1, { displayInfo: avatarDisplayInfo, size: "xs" }) }),
15284
+ /* @__PURE__ */ jsx("div", { className: "str-chat__poll-option-vote-count", children: voteCountVerbose ? t("{{count}} votes", {
15285
+ count: vote_counts_by_option[option.id] ?? 0
15286
+ }) : vote_counts_by_option[option.id] ?? 0 })
15287
+ ] })
15083
15288
  ] }),
15084
15289
  /* @__PURE__ */ jsx(
15085
15290
  AmountBar,
@@ -15594,31 +15799,34 @@ const CommandContextMenuItem = ({
15594
15799
  }
15595
15800
  );
15596
15801
  };
15597
- const AttachmentSelectorMenuInitButtonIcon = () => {
15802
+ const AttachmentSelectorMenuInitButtonIcon = ({ className }) => {
15598
15803
  const { AttachmentSelectorInitiationButtonContents } = useComponentContext();
15599
15804
  if (AttachmentSelectorInitiationButtonContents) {
15600
- return /* @__PURE__ */ jsx(AttachmentSelectorInitiationButtonContents, {});
15805
+ return /* @__PURE__ */ jsx("span", { className, children: /* @__PURE__ */ jsx(AttachmentSelectorInitiationButtonContents, {}) });
15601
15806
  }
15602
- return /* @__PURE__ */ jsx(IconPlusLarge, { className: "str-chat__attachment-selector__menu-button__icon" });
15807
+ return /* @__PURE__ */ jsx(
15808
+ IconPlusLarge,
15809
+ {
15810
+ className: clsx("str-chat__attachment-selector__menu-button__icon", className)
15811
+ }
15812
+ );
15603
15813
  };
15604
- const AttachmentSelectorButton = forwardRef(
15605
- function AttachmentSelectorButton2({ className, ...props }, ref) {
15606
- return /* @__PURE__ */ jsx(
15607
- Button,
15608
- {
15609
- appearance: "outline",
15610
- circular: true,
15611
- className: clsx("str-chat__attachment-selector__menu-button", className),
15612
- "data-testid": "invoke-attachment-selector-button",
15613
- size: "lg",
15614
- variant: "secondary",
15615
- ...props,
15616
- ref,
15617
- children: /* @__PURE__ */ jsx(AttachmentSelectorMenuInitButtonIcon, {})
15618
- }
15619
- );
15620
- }
15621
- );
15814
+ const AttachmentSelectorButton = forwardRef(function AttachmentSelectorButton2({ className, iconClassName, ...props }, ref) {
15815
+ return /* @__PURE__ */ jsx(
15816
+ Button,
15817
+ {
15818
+ appearance: "outline",
15819
+ circular: true,
15820
+ className: clsx("str-chat__attachment-selector__menu-button", className),
15821
+ "data-testid": "invoke-attachment-selector-button",
15822
+ size: "lg",
15823
+ variant: "secondary",
15824
+ ...props,
15825
+ ref,
15826
+ children: /* @__PURE__ */ jsx(AttachmentSelectorMenuInitButtonIcon, { className: iconClassName })
15827
+ }
15828
+ );
15829
+ });
15622
15830
  const SimpleAttachmentSelector = () => {
15623
15831
  const { channelCapabilities } = useChannelStateContext();
15624
15832
  const inputRef = useRef(null);
@@ -15790,7 +15998,7 @@ const AttachmentSelector = ({
15790
15998
  getModalPortalDestination
15791
15999
  }) => {
15792
16000
  const { t } = useTranslationContext();
15793
- const { Modal = GlobalModal } = useComponentContext();
16001
+ const { ContextMenu: ContextMenuComponent = ContextMenu, Modal = GlobalModal } = useComponentContext();
15794
16002
  const { channelCapabilities } = useChannelStateContext();
15795
16003
  const messageComposer = useMessageComposerController();
15796
16004
  const isCooldownActive = useIsCooldownActive();
@@ -15841,16 +16049,16 @@ const AttachmentSelector = ({
15841
16049
  "aria-expanded": menuDialogIsOpen,
15842
16050
  "aria-haspopup": "true",
15843
16051
  "aria-label": t("aria/Open Attachment Selector"),
15844
- className: clsx("str-chat__prepare-rotate45", {
16052
+ disabled: isCooldownActive,
16053
+ iconClassName: clsx("str-chat__prepare-rotate45", {
15845
16054
  "str-chat__rotate45": menuDialogIsOpen
15846
16055
  }),
15847
- disabled: isCooldownActive,
15848
16056
  onClick: () => menuDialog?.toggle(),
15849
16057
  ref: menuButtonRef
15850
16058
  }
15851
16059
  ),
15852
16060
  /* @__PURE__ */ jsx(
15853
- ContextMenu,
16061
+ ContextMenuComponent,
15854
16062
  {
15855
16063
  allowFlip: true,
15856
16064
  backLabel: t("Back"),
@@ -15969,562 +16177,175 @@ const PlayIcon = () => /* @__PURE__ */ jsx(
15969
16177
  }
15970
16178
  );
15971
16179
  const CheckSignIcon = () => /* @__PURE__ */ jsx("svg", { fill: "currentColor", viewBox: "0 0 18 14", xmlns: "http://www.w3.org/2000/svg", children: /* @__PURE__ */ 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" }) });
15972
- const toGalleryItemDescriptors = (...args) => toBaseImageDescriptors(...args);
15973
- const GalleryContext = createContext(void 0);
15974
- const useGalleryContext = () => {
15975
- const contextValue = useContext(GalleryContext);
15976
- if (!contextValue) {
15977
- console.warn(
15978
- `The useGalleryContext hook was called outside of the GalleryContext provider. Make sure this hook is called within a child of the Gallery component.`
15979
- );
15980
- return {};
16180
+ const INTERACTIVE_SELECTOR = 'button, a, input, textarea, select, [role="button"], [role="link"], [data-interactive="true"]';
16181
+ function hasInteractiveAncestorBeforeRoot(target, root) {
16182
+ if (!(target instanceof Element) || !root) return false;
16183
+ let el = target;
16184
+ while (el && el !== root) {
16185
+ if (el.matches(INTERACTIVE_SELECTOR)) return true;
16186
+ el = el.parentElement;
15981
16187
  }
15982
- return contextValue;
15983
- };
15984
- const Gallery = ({
15985
- closeOnBackgroundClick = true,
15986
- GalleryUI: GalleryUI2,
15987
- initialIndex = 0,
15988
- items,
15989
- onIndexChange,
15990
- onRequestClose
16188
+ return false;
16189
+ }
16190
+ const AttachmentPreviewRoot = ({
16191
+ attachment,
16192
+ onPressed,
16193
+ openPreview,
16194
+ tabIndex = 0,
16195
+ ...props
15991
16196
  }) => {
15992
- const [currentIndex, setCurrentIndex] = useState(initialIndex);
15993
- const itemCount = items.length;
15994
- const goToIndex = useCallback(
15995
- (index) => {
15996
- if (index >= 0 && index < itemCount) {
15997
- setCurrentIndex(index);
15998
- }
15999
- },
16000
- [itemCount]
16001
- );
16002
- const goToNext = useCallback(() => {
16003
- setCurrentIndex((prev) => prev < itemCount - 1 ? prev + 1 : prev);
16004
- }, [itemCount]);
16005
- const goToPrevious = useCallback(() => {
16006
- setCurrentIndex((prev) => prev > 0 ? prev - 1 : prev);
16007
- }, []);
16008
- useEffect(() => {
16009
- onIndexChange?.(currentIndex);
16010
- }, [currentIndex, onIndexChange]);
16011
- const hasNext = currentIndex < itemCount - 1;
16012
- const hasPrevious = currentIndex > 0;
16013
- const currentItem = items[currentIndex];
16014
- const contextValue = useMemo(
16015
- () => ({
16016
- closeOnBackgroundClick,
16017
- currentIndex,
16018
- currentItem,
16019
- goToIndex,
16020
- goToNext,
16021
- goToPrevious,
16022
- hasNext,
16023
- hasPrevious,
16024
- itemCount,
16025
- items,
16026
- onRequestClose
16027
- }),
16028
- [
16029
- closeOnBackgroundClick,
16030
- currentIndex,
16031
- currentItem,
16032
- goToIndex,
16033
- goToNext,
16034
- goToPrevious,
16035
- hasNext,
16036
- hasPrevious,
16037
- itemCount,
16038
- items,
16039
- onRequestClose
16040
- ]
16041
- );
16042
- return /* @__PURE__ */ jsx(GalleryContext.Provider, { value: contextValue, children: GalleryUI2 ? /* @__PURE__ */ jsx(GalleryUI2, {}) : null });
16043
- };
16044
- const GalleryHeader = ({ currentItem }) => {
16045
16197
  const { t } = useTranslationContext();
16046
- const { MessageTimestamp: MessageTimestamp$1 = MessageTimestamp } = useComponentContext();
16047
- const { isMyMessage, message } = useMessageContext();
16048
- const modalContext = useContext(ModalContext);
16049
- const headerTitle = isMyMessage?.() && t("You") || message?.user?.name || message?.user?.id || currentItem.title || t("User uploaded content");
16050
- const downloadUrl = useMemo(() => {
16051
- const rawDownloadUrl = currentItem.videoUrl ?? currentItem.imageUrl;
16052
- if (!rawDownloadUrl) return void 0;
16053
- const sanitizedUrl = sanitizeUrl(rawDownloadUrl);
16054
- return sanitizedUrl === "about:blank" ? void 0 : sanitizedUrl;
16055
- }, [currentItem.imageUrl, currentItem.videoUrl]);
16056
- const downloadLabel = t("aria/Download attachment");
16057
- return /* @__PURE__ */ jsxs("div", { className: "str-chat__gallery__header", children: [
16058
- /* @__PURE__ */ jsx("div", { "aria-hidden": "true", className: "str-chat__gallery__header-spacer" }),
16059
- /* @__PURE__ */ jsxs("div", { className: "str-chat__gallery__header-meta", children: [
16060
- /* @__PURE__ */ jsx("div", { className: "str-chat__gallery__title", children: headerTitle }),
16061
- message?.created_at ? /* @__PURE__ */ jsx(MessageTimestamp$1, { customClass: "str-chat__gallery__timestamp" }) : null
16062
- ] }),
16063
- /* @__PURE__ */ jsxs("div", { className: "str-chat__gallery__header-actions", children: [
16064
- downloadUrl ? /* @__PURE__ */ jsx(
16065
- "a",
16066
- {
16067
- "aria-label": downloadLabel,
16068
- className: "str-chat__gallery__action-button str-chat__gallery__action-button--download",
16069
- download: true,
16070
- href: downloadUrl,
16071
- rel: "noreferrer",
16072
- target: "_blank",
16073
- title: downloadLabel,
16074
- children: /* @__PURE__ */ jsx(IconArrowDownCircle, {})
16075
- }
16076
- ) : null,
16077
- modalContext?.close ? /* @__PURE__ */ jsx(
16078
- Button,
16079
- {
16080
- "aria-label": t("Close"),
16081
- className: "str-chat__gallery__action-button str-chat__gallery__action-button--close",
16082
- onClick: modalContext.close,
16083
- title: t("Close"),
16084
- children: /* @__PURE__ */ jsx(IconCrossMedium, {})
16085
- }
16086
- ) : null
16087
- ] })
16088
- ] });
16089
- };
16090
- const VideoPlayer = ({ isPlaying, thumbnailUrl, videoUrl }) => {
16091
- const { VideoPlayer: VideoPlayerContext } = useComponentContext();
16092
- return VideoPlayerContext ? /* @__PURE__ */ jsx(VideoPlayerContext, { thumbnailUrl, videoUrl }) : /* @__PURE__ */ jsx(
16093
- ReactPlayer,
16198
+ const [root, setRoot] = useState(null);
16199
+ const url = attachment.asset_url || attachment.image_url || attachment.localMetadata.previewUri;
16200
+ const canDownloadAttachment = false;
16201
+ const canPreviewAttachment = !!openPreview && (!!url && isImageAttachment(attachment) || isVideoAttachment(attachment));
16202
+ const handlePressed = (e) => {
16203
+ if (e.defaultPrevented) return;
16204
+ if (hasInteractiveAncestorBeforeRoot(e.target, root)) return;
16205
+ if (onPressed) {
16206
+ const shouldContinue = onPressed(e);
16207
+ if (!shouldContinue) return;
16208
+ }
16209
+ if (canPreviewAttachment) {
16210
+ openPreview();
16211
+ return;
16212
+ }
16213
+ };
16214
+ const isInteractive = canPreviewAttachment || canDownloadAttachment;
16215
+ return /* @__PURE__ */ jsx(
16216
+ "div",
16094
16217
  {
16095
- className: "react-player",
16096
- config: { file: { attributes: { poster: thumbnailUrl } } },
16097
- controls: true,
16098
- height: "100%",
16099
- playing: isPlaying,
16100
- url: videoUrl,
16101
- width: "100%"
16218
+ "aria-label": isInteractive ? t(canPreviewAttachment ? "aria/Show preview" : "aria/Download attachment") : void 0,
16219
+ ...props,
16220
+ onClick: handlePressed,
16221
+ onKeyDown: isInteractive ? (e) => {
16222
+ if (e.key !== "Enter" && e.key !== " ") return;
16223
+ e.preventDefault();
16224
+ handlePressed(e);
16225
+ } : void 0,
16226
+ ref: setRoot,
16227
+ tabIndex: isInteractive ? tabIndex : -1,
16228
+ children: props.children
16102
16229
  }
16103
16230
  );
16104
16231
  };
16105
- const VideoThumbnail = ({
16106
- className,
16107
- onPlay,
16108
- ...imageProps
16232
+ const FileAttachmentPreview = ({
16233
+ attachment,
16234
+ handleRetry,
16235
+ removeAttachments
16109
16236
  }) => {
16110
- const { t } = useTranslationContext();
16111
- return /* @__PURE__ */ jsxs("div", { className: "str-chat__message-attachment__video-thumbnail", children: [
16112
- /* @__PURE__ */ jsx(
16113
- BaseImage,
16114
- {
16115
- className: clsx("str-chat__message-attachment__video-thumbnail-image", className),
16116
- ...imageProps
16117
- }
16118
- ),
16119
- onPlay ? /* @__PURE__ */ jsx(
16120
- Button,
16121
- {
16122
- appearance: "solid",
16123
- "aria-label": t("Play video"),
16124
- circular: true,
16125
- className: clsx(
16126
- "str-chat__message-attachment__video-thumbnail__play-indicator"
16127
- ),
16128
- onClick: onPlay,
16129
- size: "lg",
16130
- variant: "secondary",
16131
- children: /* @__PURE__ */ jsx(IconPlaySolid, {})
16132
- }
16133
- ) : /* @__PURE__ */ jsx("div", { className: "str-chat__message-attachment__video-thumbnail__play-indicator", children: /* @__PURE__ */ jsx(IconPlaySolid, {}) })
16134
- ] });
16135
- };
16136
- const SWIPE_THRESHOLD = 50;
16137
- const TRANSITION_DURATION = 300;
16138
- const GalleryUI = () => {
16139
- const { t } = useTranslationContext();
16140
- const {
16141
- closeOnBackgroundClick,
16142
- currentIndex,
16143
- currentItem,
16144
- goToNext,
16145
- goToPrevious,
16146
- hasNext,
16147
- hasPrevious,
16148
- itemCount,
16149
- onRequestClose
16150
- } = useGalleryContext();
16151
- const modalContext = useContext(ModalContext);
16152
- const [showVideo, setShowVideo] = useState(false);
16153
- const isTransitioningRef = useRef(false);
16154
- const [slideOffset, setSlideOffset] = useState(0);
16155
- const [isDragging, setIsDragging] = useState(false);
16156
- const [slideDirection, setSlideDirection] = useState(
16157
- null
16237
+ const { t } = useTranslationContext("FilePreview");
16238
+ const { id, uploadPermissionCheck, uploadState } = attachment.localMetadata ?? {};
16239
+ const hasSizeLimitError = uploadPermissionCheck?.reason === "size_limit";
16240
+ const hasFatalError = uploadState === "blocked" || hasSizeLimitError;
16241
+ const hasRetriableError = uploadState === "failed" && !!handleRetry;
16242
+ const hasError = hasRetriableError || hasFatalError;
16243
+ return /* @__PURE__ */ jsxs(
16244
+ AttachmentPreviewRoot,
16245
+ {
16246
+ attachment,
16247
+ className: "str-chat__attachment-preview-file",
16248
+ "data-testid": "attachment-preview-file",
16249
+ children: [
16250
+ /* @__PURE__ */ jsx("div", { className: "str-chat__attachment-preview-file__icon", children: /* @__PURE__ */ jsx(FileIcon, { fileName: attachment.title, mimeType: attachment.mime_type }) }),
16251
+ /* @__PURE__ */ jsxs("div", { className: "str-chat__attachment-preview-file__info", children: [
16252
+ /* @__PURE__ */ jsx("div", { className: "str-chat__attachment-preview-file-name", title: attachment.title, children: attachment.title }),
16253
+ /* @__PURE__ */ jsxs("div", { className: "str-chat__attachment-preview-file__data", children: [
16254
+ uploadState === "uploading" && /* @__PURE__ */ jsx(LoadingIndicatorIcon, {}),
16255
+ !hasError && /* @__PURE__ */ jsx(FileSizeIndicator, { fileSize: attachment.file_size }),
16256
+ hasFatalError && /* @__PURE__ */ jsxs("div", { className: "str-chat__attachment-preview-file__fatal-error", children: [
16257
+ /* @__PURE__ */ jsx(IconExclamationCircle, {}),
16258
+ /* @__PURE__ */ jsx("span", { children: hasSizeLimitError ? t("File too large") : uploadState === "blocked" ? t("Upload blocked") : t("Upload failed") })
16259
+ ] }),
16260
+ hasRetriableError && /* @__PURE__ */ jsxs("div", { className: "str-chat__attachment-preview-file__retriable-error", children: [
16261
+ /* @__PURE__ */ jsx(IconExclamationTriangle, {}),
16262
+ /* @__PURE__ */ jsx("span", { children: t("Upload error") }),
16263
+ /* @__PURE__ */ jsx(
16264
+ "button",
16265
+ {
16266
+ "aria-label": t("aria/Retry upload"),
16267
+ className: "str-chat__attachment-preview-file__retry-upload-button",
16268
+ "data-testid": "file-preview-item-retry-button",
16269
+ onClick: () => {
16270
+ handleRetry(attachment);
16271
+ },
16272
+ type: "button",
16273
+ children: t("Retry upload")
16274
+ }
16275
+ )
16276
+ ] })
16277
+ ] })
16278
+ ] }),
16279
+ /* @__PURE__ */ jsx(
16280
+ RemoveAttachmentPreviewButton,
16281
+ {
16282
+ "data-testid": "file-preview-item-delete-button",
16283
+ onClick: () => {
16284
+ if (id) removeAttachments([id]);
16285
+ },
16286
+ uploadState
16287
+ }
16288
+ )
16289
+ ]
16290
+ }
16158
16291
  );
16159
- const ignoreNextClickRef = useRef(false);
16160
- const touchStartRef = useRef(null);
16161
- const isVerticalSwipeRef = useRef(false);
16162
- const containerRef = useRef(null);
16163
- useEffect(() => {
16164
- setShowVideo(false);
16165
- }, [currentIndex]);
16166
- const prevIndexRef = useRef(currentIndex);
16167
- useEffect(() => {
16168
- if (prevIndexRef.current === currentIndex) return;
16169
- const direction = currentIndex > prevIndexRef.current ? "forward" : "backward";
16170
- setSlideDirection(direction);
16171
- setSlideOffset(0);
16172
- setIsDragging(false);
16173
- isTransitioningRef.current = true;
16174
- const timer = setTimeout(() => {
16175
- setSlideDirection(null);
16176
- isTransitioningRef.current = false;
16177
- }, TRANSITION_DURATION);
16178
- prevIndexRef.current = currentIndex;
16179
- return () => clearTimeout(timer);
16180
- }, [currentIndex]);
16181
- const handleGoToNext = useCallback(() => {
16182
- if (isTransitioningRef.current) return;
16183
- goToNext();
16184
- }, [goToNext]);
16185
- const handleGoToPrevious = useCallback(() => {
16186
- if (isTransitioningRef.current) return;
16187
- goToPrevious();
16188
- }, [goToPrevious]);
16189
- const handleKeyDown = useCallback(
16190
- (event) => {
16191
- if (event.key === "ArrowLeft") {
16192
- event.preventDefault();
16193
- handleGoToPrevious();
16194
- } else if (event.key === "ArrowRight") {
16195
- event.preventDefault();
16196
- handleGoToNext();
16197
- }
16198
- },
16199
- [handleGoToNext, handleGoToPrevious]
16200
- );
16201
- useEffect(() => {
16202
- document.addEventListener("keydown", handleKeyDown);
16203
- return () => document.removeEventListener("keydown", handleKeyDown);
16204
- }, [handleKeyDown]);
16205
- const handleTouchStart = useCallback((event) => {
16206
- if (isTransitioningRef.current) return;
16207
- const touch = event.touches[0];
16208
- ignoreNextClickRef.current = false;
16209
- touchStartRef.current = { x: touch.clientX, y: touch.clientY };
16210
- isVerticalSwipeRef.current = false;
16211
- }, []);
16212
- const handleTouchMove = useCallback(
16213
- (event) => {
16214
- if (!touchStartRef.current || isTransitioningRef.current) return;
16215
- const touch = event.touches[0];
16216
- const deltaX = touch.clientX - touchStartRef.current.x;
16217
- const deltaY = touch.clientY - touchStartRef.current.y;
16218
- if (!isDragging && !isVerticalSwipeRef.current) {
16219
- if (Math.abs(deltaY) > Math.abs(deltaX) && Math.abs(deltaY) > 10) {
16220
- ignoreNextClickRef.current = true;
16221
- isVerticalSwipeRef.current = true;
16222
- return;
16223
- }
16224
- if (Math.abs(deltaX) > 10) {
16225
- ignoreNextClickRef.current = true;
16226
- setIsDragging(true);
16227
- }
16228
- }
16229
- if (isVerticalSwipeRef.current) return;
16230
- if (!hasNext && deltaX < 0 || !hasPrevious && deltaX > 0) {
16231
- setSlideOffset(deltaX * 0.3);
16232
- } else {
16233
- setSlideOffset(deltaX);
16234
- }
16235
- },
16236
- [isDragging, hasNext, hasPrevious]
16237
- );
16238
- const handleTouchEnd = useCallback(() => {
16239
- if (!touchStartRef.current || isVerticalSwipeRef.current) {
16240
- if (isVerticalSwipeRef.current) ignoreNextClickRef.current = true;
16241
- touchStartRef.current = null;
16242
- return;
16243
- }
16244
- const offset = slideOffset;
16245
- if (isDragging || Math.abs(offset) > 10) {
16246
- ignoreNextClickRef.current = true;
16247
- }
16248
- touchStartRef.current = null;
16249
- if (Math.abs(offset) >= SWIPE_THRESHOLD) {
16250
- if (offset < 0 && hasNext) {
16251
- goToNext();
16252
- } else if (offset > 0 && hasPrevious) {
16253
- goToPrevious();
16254
- } else {
16255
- setSlideOffset(0);
16256
- }
16257
- } else {
16258
- setSlideOffset(0);
16259
- }
16260
- setIsDragging(false);
16261
- }, [slideOffset, hasNext, hasPrevious, goToNext, goToPrevious, isDragging]);
16262
- const requestClose = modalContext?.close ?? onRequestClose;
16263
- const handleBackgroundClick = useCallback(
16264
- (event) => {
16265
- if (event.target !== event.currentTarget) return;
16266
- if (ignoreNextClickRef.current) {
16267
- ignoreNextClickRef.current = false;
16268
- return;
16269
- }
16270
- if (!closeOnBackgroundClick) return;
16271
- requestClose?.();
16272
- },
16273
- [closeOnBackgroundClick, requestClose]
16274
- );
16275
- const mediaStyle = isDragging || slideOffset !== 0 && slideDirection === null ? { transform: `translateX(${slideOffset}px)` } : {};
16276
- return /* @__PURE__ */ jsxs("div", { className: "str-chat__gallery", children: [
16277
- /* @__PURE__ */ jsxs("div", { className: "str-chat__gallery__main", children: [
16278
- /* @__PURE__ */ jsx(GalleryHeader, { currentItem }),
16279
- /* @__PURE__ */ jsx(
16280
- NavButton,
16281
- {
16282
- "aria-label": t("Previous image"),
16283
- className: clsx(
16284
- "str-chat__gallery__nav-button--prev",
16285
- !hasPrevious && "str-chat__gallery__nav-button--hidden"
16286
- ),
16287
- disabled: !hasPrevious,
16288
- onClick: handleGoToPrevious,
16289
- children: /* @__PURE__ */ jsx(IconChevronLeft, {})
16290
- }
16291
- ),
16292
- /* @__PURE__ */ jsx(
16293
- "div",
16294
- {
16295
- className: "str-chat__gallery__slide-container",
16296
- onClick: handleBackgroundClick,
16297
- onTouchEnd: handleTouchEnd,
16298
- onTouchMove: handleTouchMove,
16299
- onTouchStart: handleTouchStart,
16300
- ref: containerRef,
16301
- children: /* @__PURE__ */ jsx(
16302
- "div",
16303
- {
16304
- className: clsx({
16305
- "str-chat__gallery__media--dragging": isDragging,
16306
- "str-chat__gallery__media--slide-backward": !isDragging && slideDirection === "backward",
16307
- "str-chat__gallery__media--slide-forward": !isDragging && slideDirection === "forward"
16308
- }),
16309
- style: mediaStyle,
16310
- children: currentItem.videoUrl && currentItem.videoThumbnailUrl ? /* @__PURE__ */ jsx("div", { className: "str-chat__gallery__media str-chat__gallery__media--video", children: showVideo ? /* @__PURE__ */ jsx(VideoPlayer, { isPlaying: true, videoUrl: currentItem.videoUrl }) : /* @__PURE__ */ jsx(
16311
- VideoThumbnail,
16312
- {
16313
- alt: currentItem.title ?? "",
16314
- onPlay: () => setShowVideo(true),
16315
- src: currentItem.videoThumbnailUrl
16316
- }
16317
- ) }) : /* @__PURE__ */ jsx("div", { className: "str-chat__gallery__media str-chat__gallery__media--image", children: /* @__PURE__ */ jsx(BaseImage, { alt: currentItem.alt, src: currentItem.imageUrl }) })
16318
- }
16319
- )
16320
- }
16321
- ),
16322
- /* @__PURE__ */ jsx(
16323
- NavButton,
16324
- {
16325
- "aria-label": t("Next image"),
16326
- className: clsx(
16327
- "str-chat__gallery__nav-button--next",
16328
- !hasNext && "str-chat__gallery__nav-button--hidden"
16329
- ),
16330
- disabled: !hasNext,
16331
- onClick: handleGoToNext,
16332
- children: /* @__PURE__ */ jsx(IconChevronRight, {})
16333
- }
16334
- )
16335
- ] }),
16336
- itemCount > 1 && /* @__PURE__ */ jsxs("div", { className: "str-chat__gallery__position-indicator", children: [
16337
- currentIndex + 1,
16338
- " of ",
16339
- itemCount
16340
- ] })
16341
- ] });
16342
16292
  };
16343
- const NavButton = ({ className, ...props }) => /* @__PURE__ */ jsx(Button, { ...props, className: clsx("str-chat__gallery__nav-button", className) });
16344
- const INTERACTIVE_SELECTOR = 'button, a, input, textarea, select, [role="button"], [role="link"], [data-interactive="true"]';
16345
- function hasInteractiveAncestorBeforeRoot(target, root) {
16346
- if (!(target instanceof Element) || !root) return false;
16347
- let el = target;
16348
- while (el && el !== root) {
16349
- if (el.matches(INTERACTIVE_SELECTOR)) return true;
16350
- el = el.parentElement;
16293
+ const UnsupportedAttachmentPreview = ({
16294
+ attachment,
16295
+ handleRetry,
16296
+ removeAttachments
16297
+ }) => /* @__PURE__ */ jsx(
16298
+ FileAttachmentPreview,
16299
+ {
16300
+ attachment,
16301
+ handleRetry,
16302
+ removeAttachments
16351
16303
  }
16352
- return false;
16304
+ );
16305
+ function formatTime(totalSeconds, rounding = "ceil") {
16306
+ if (totalSeconds == null || Number.isNaN(totalSeconds) || totalSeconds < 0) {
16307
+ return null;
16308
+ }
16309
+ const roundedSeconds = rounding === "floor" ? Math.floor(totalSeconds) : Math.ceil(totalSeconds);
16310
+ const hours = Math.floor(roundedSeconds / 3600);
16311
+ const minutes = Math.floor(roundedSeconds % 3600 / 60);
16312
+ const seconds = roundedSeconds % 60;
16313
+ const minSec = `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(
16314
+ 2,
16315
+ "0"
16316
+ )}`;
16317
+ return hours ? `${String(hours).padStart(2, "0")}:${minSec}` : minSec;
16353
16318
  }
16354
- const AttachmentPreviewRoot = ({
16355
- attachment,
16356
- onPressed,
16357
- tabIndex = 0,
16358
- ...props
16359
- }) => {
16360
- const { t } = useTranslationContext("FilePreview");
16361
- const { Modal = GlobalModal } = useComponentContext();
16362
- const [showPreview, setShowPreview] = useState(false);
16363
- const [root, setRoot] = useState(null);
16364
- const url = attachment.asset_url || attachment.image_url || attachment.localMetadata.previewUri;
16365
- const canDownloadAttachment = false;
16366
- const canPreviewAttachment = !!url && isImageAttachment(attachment) || isVideoAttachment(attachment);
16367
- const handlePressed = (e) => {
16368
- if (e.defaultPrevented) return;
16369
- if (hasInteractiveAncestorBeforeRoot(e.target, root)) return;
16370
- if (onPressed) {
16371
- const shouldContinue = onPressed(e);
16372
- if (!shouldContinue) return;
16373
- }
16374
- if (canPreviewAttachment) {
16375
- setShowPreview(true);
16376
- return;
16377
- }
16378
- };
16319
+ function DurationDisplay({
16320
+ className,
16321
+ duration: duration2,
16322
+ isPlaying,
16323
+ secondsElapsed,
16324
+ showRemaining = false
16325
+ }) {
16326
+ const remainingSeconds = duration2 != null && secondsElapsed != null ? Math.max(0, duration2 - secondsElapsed) : void 0;
16327
+ const formattedDuration = formatTime(duration2);
16328
+ const formattedSecondsElapsed = formatTime(secondsElapsed);
16329
+ const formattedRemaining = formatTime(remainingSeconds);
16330
+ const shouldShowElapsed = !!secondsElapsed && secondsElapsed > 0 && secondsElapsed < (duration2 || 0);
16331
+ const canShowRemaining = showRemaining && duration2 != null && secondsElapsed != null;
16332
+ const primaryValue = showRemaining ? formattedRemaining : formattedSecondsElapsed;
16333
+ const showPrimary = (canShowRemaining || shouldShowElapsed) && !!primaryValue;
16334
+ const showDuration = !showPrimary && !!formattedDuration;
16379
16335
  return /* @__PURE__ */ jsxs(
16380
16336
  "div",
16381
16337
  {
16382
- "aria-label": t(showPreview ? "aria/Show preview" : "aria/Download attachment"),
16383
- ...props,
16384
- onClick: handlePressed,
16385
- onKeyDown: (e) => {
16386
- if (e.key !== "Enter" && e.key !== " ") return;
16387
- e.preventDefault();
16388
- handlePressed(e);
16389
- },
16390
- ref: setRoot,
16391
- role: showPreview ? "button" : props.role,
16392
- tabIndex: showPreview || canDownloadAttachment ? tabIndex : -1,
16338
+ className: clsx(
16339
+ "str-chat__duration-display",
16340
+ {
16341
+ "str-chat__duration-display--hasProgress": !!secondsElapsed,
16342
+ "str-chat__duration-display--isPlaying": isPlaying
16343
+ },
16344
+ className
16345
+ ),
16393
16346
  children: [
16394
- props.children,
16395
- /* @__PURE__ */ jsx(
16396
- Modal,
16397
- {
16398
- className: "str-chat__gallery-modal",
16399
- onClose: (e) => {
16400
- e.stopPropagation();
16401
- setShowPreview(false);
16402
- },
16403
- open: showPreview && canPreviewAttachment,
16404
- children: isImageAttachment(attachment) || isVideoAttachment(attachment) ? /* @__PURE__ */ jsx(Gallery, { items: [attachment] }) : null
16405
- }
16406
- )
16407
- ]
16408
- }
16409
- );
16410
- };
16411
- const FileAttachmentPreview = ({
16412
- attachment,
16413
- handleRetry,
16414
- removeAttachments
16415
- }) => {
16416
- const { t } = useTranslationContext("FilePreview");
16417
- const { id, uploadPermissionCheck, uploadState } = attachment.localMetadata ?? {};
16418
- const hasSizeLimitError = uploadPermissionCheck?.reason === "size_limit";
16419
- const hasFatalError = uploadState === "blocked" || hasSizeLimitError;
16420
- const hasRetriableError = uploadState === "failed" && !!handleRetry;
16421
- const hasError = hasRetriableError || hasFatalError;
16422
- return /* @__PURE__ */ jsxs(
16423
- AttachmentPreviewRoot,
16424
- {
16425
- attachment,
16426
- className: "str-chat__attachment-preview-file",
16427
- "data-testid": "attachment-preview-file",
16428
- children: [
16429
- /* @__PURE__ */ jsx("div", { className: "str-chat__attachment-preview-file__icon", children: /* @__PURE__ */ jsx(FileIcon, { fileName: attachment.title, mimeType: attachment.mime_type }) }),
16430
- /* @__PURE__ */ jsxs("div", { className: "str-chat__attachment-preview-file__info", children: [
16431
- /* @__PURE__ */ jsx("div", { className: "str-chat__attachment-preview-file-name", title: attachment.title, children: attachment.title }),
16432
- /* @__PURE__ */ jsxs("div", { className: "str-chat__attachment-preview-file__data", children: [
16433
- uploadState === "uploading" && /* @__PURE__ */ jsx(LoadingIndicatorIcon, {}),
16434
- !hasError && /* @__PURE__ */ jsx(FileSizeIndicator, { fileSize: attachment.file_size }),
16435
- hasFatalError && /* @__PURE__ */ jsxs("div", { className: "str-chat__attachment-preview-file__fatal-error", children: [
16436
- /* @__PURE__ */ jsx(IconExclamationCircle, {}),
16437
- /* @__PURE__ */ jsx("span", { children: hasSizeLimitError ? t("File too large") : uploadState === "blocked" ? t("Upload blocked") : t("Upload failed") })
16438
- ] }),
16439
- hasRetriableError && /* @__PURE__ */ jsxs("div", { className: "str-chat__attachment-preview-file__retriable-error", children: [
16440
- /* @__PURE__ */ jsx(IconExclamationTriangle, {}),
16441
- /* @__PURE__ */ jsx("span", { children: t("Upload error") }),
16442
- /* @__PURE__ */ jsx(
16443
- "button",
16444
- {
16445
- "aria-label": t("aria/Retry upload"),
16446
- className: "str-chat__attachment-preview-file__retry-upload-button",
16447
- "data-testid": "file-preview-item-retry-button",
16448
- onClick: () => {
16449
- handleRetry(attachment);
16450
- },
16451
- type: "button",
16452
- children: t("Retry upload")
16453
- }
16454
- )
16455
- ] })
16456
- ] })
16457
- ] }),
16458
- /* @__PURE__ */ jsx(
16459
- RemoveAttachmentPreviewButton,
16460
- {
16461
- "data-testid": "file-preview-item-delete-button",
16462
- onClick: () => {
16463
- if (id) removeAttachments([id]);
16464
- },
16465
- uploadState
16466
- }
16467
- )
16468
- ]
16469
- }
16470
- );
16471
- };
16472
- const UnsupportedAttachmentPreview = ({
16473
- attachment,
16474
- handleRetry,
16475
- removeAttachments
16476
- }) => /* @__PURE__ */ jsx(
16477
- FileAttachmentPreview,
16478
- {
16479
- attachment,
16480
- handleRetry,
16481
- removeAttachments
16482
- }
16483
- );
16484
- function formatTime(totalSeconds, rounding = "ceil") {
16485
- if (totalSeconds == null || Number.isNaN(totalSeconds) || totalSeconds < 0) {
16486
- return null;
16487
- }
16488
- const roundedSeconds = rounding === "floor" ? Math.floor(totalSeconds) : Math.ceil(totalSeconds);
16489
- const hours = Math.floor(roundedSeconds / 3600);
16490
- const minutes = Math.floor(roundedSeconds % 3600 / 60);
16491
- const seconds = roundedSeconds % 60;
16492
- const minSec = `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(
16493
- 2,
16494
- "0"
16495
- )}`;
16496
- return hours ? `${String(hours).padStart(2, "0")}:${minSec}` : minSec;
16497
- }
16498
- function DurationDisplay({
16499
- className,
16500
- duration: duration2,
16501
- isPlaying,
16502
- secondsElapsed,
16503
- showRemaining = false
16504
- }) {
16505
- const remainingSeconds = duration2 != null && secondsElapsed != null ? Math.max(0, duration2 - secondsElapsed) : void 0;
16506
- const formattedDuration = formatTime(duration2);
16507
- const formattedSecondsElapsed = formatTime(secondsElapsed);
16508
- const formattedRemaining = formatTime(remainingSeconds);
16509
- const shouldShowElapsed = !!secondsElapsed && secondsElapsed > 0 && secondsElapsed < (duration2 || 0);
16510
- const canShowRemaining = showRemaining && duration2 != null && secondsElapsed != null;
16511
- const primaryValue = showRemaining ? formattedRemaining : formattedSecondsElapsed;
16512
- const showPrimary = (canShowRemaining || shouldShowElapsed) && !!primaryValue;
16513
- const showDuration = !showPrimary && !!formattedDuration;
16514
- return /* @__PURE__ */ jsxs(
16515
- "div",
16516
- {
16517
- className: clsx(
16518
- "str-chat__duration-display",
16519
- {
16520
- "str-chat__duration-display--hasProgress": !!secondsElapsed,
16521
- "str-chat__duration-display--isPlaying": isPlaying
16522
- },
16523
- className
16524
- ),
16525
- children: [
16526
- showPrimary && /* @__PURE__ */ jsx("span", { className: "str-chat__duration-display__time-elapsed", children: primaryValue }),
16527
- showDuration && /* @__PURE__ */ jsx("span", { className: "str-chat__duration-display__duration", children: formattedDuration })
16347
+ showPrimary && /* @__PURE__ */ jsx("span", { className: "str-chat__duration-display__time-elapsed", children: primaryValue }),
16348
+ showDuration && /* @__PURE__ */ jsx("span", { className: "str-chat__duration-display__duration", children: formattedDuration })
16528
16349
  ]
16529
16350
  }
16530
16351
  );
@@ -17031,6 +16852,7 @@ const AudioAttachmentPreview = ({
17031
16852
  const MediaAttachmentPreview = ({
17032
16853
  attachment,
17033
16854
  handleRetry,
16855
+ openPreview,
17034
16856
  removeAttachments
17035
16857
  }) => {
17036
16858
  const { t } = useTranslationContext();
@@ -17071,6 +16893,7 @@ const MediaAttachmentPreview = ({
17071
16893
  }),
17072
16894
  "data-testid": "attachment-preview-media",
17073
16895
  onPressed: hasRetriableError ? retry : void 0,
16896
+ openPreview: !isUploading && !hasUploadError ? openPreview : void 0,
17074
16897
  children: [
17075
16898
  /* @__PURE__ */ jsxs("div", { className: "str-chat__attachment-preview-media__thumbnail-wrapper", children: [
17076
16899
  thumbnail.url && /* @__PURE__ */ jsx(
@@ -17120,6 +16943,380 @@ const MediaAttachmentPreview = ({
17120
16943
  }
17121
16944
  );
17122
16945
  };
16946
+ const toGalleryItemDescriptors = (...args) => toBaseImageDescriptors(...args);
16947
+ const GalleryContext = createContext(void 0);
16948
+ const useGalleryContext = () => {
16949
+ const contextValue = useContext(GalleryContext);
16950
+ if (!contextValue) {
16951
+ console.warn(
16952
+ `The useGalleryContext hook was called outside of the GalleryContext provider. Make sure this hook is called within a child of the Gallery component.`
16953
+ );
16954
+ return {};
16955
+ }
16956
+ return contextValue;
16957
+ };
16958
+ const GalleryHeader = ({ currentItem }) => {
16959
+ const { t } = useTranslationContext();
16960
+ const { MessageTimestamp: MessageTimestamp$1 = MessageTimestamp } = useComponentContext();
16961
+ const { isMyMessage, message } = useMessageContext();
16962
+ const modalContext = useContext(ModalContext);
16963
+ const headerTitle = isMyMessage?.() && t("You") || message?.user?.name || message?.user?.id || currentItem.title || t("User uploaded content");
16964
+ const downloadUrl = useMemo(() => {
16965
+ const rawDownloadUrl = currentItem.videoUrl ?? currentItem.imageUrl;
16966
+ if (!rawDownloadUrl) return void 0;
16967
+ const sanitizedUrl = sanitizeUrl(rawDownloadUrl);
16968
+ return sanitizedUrl === "about:blank" ? void 0 : sanitizedUrl;
16969
+ }, [currentItem.imageUrl, currentItem.videoUrl]);
16970
+ const downloadLabel = t("aria/Download attachment");
16971
+ return /* @__PURE__ */ jsxs("div", { className: "str-chat__gallery__header", children: [
16972
+ /* @__PURE__ */ jsx("div", { "aria-hidden": "true", className: "str-chat__gallery__header-spacer" }),
16973
+ /* @__PURE__ */ jsxs("div", { className: "str-chat__gallery__header-meta", children: [
16974
+ /* @__PURE__ */ jsx("div", { className: "str-chat__gallery__title", children: headerTitle }),
16975
+ message?.created_at ? /* @__PURE__ */ jsx(MessageTimestamp$1, { customClass: "str-chat__gallery__timestamp" }) : null
16976
+ ] }),
16977
+ /* @__PURE__ */ jsxs("div", { className: "str-chat__gallery__header-actions", children: [
16978
+ downloadUrl ? /* @__PURE__ */ jsx(
16979
+ "a",
16980
+ {
16981
+ "aria-label": downloadLabel,
16982
+ className: "str-chat__gallery__action-button str-chat__gallery__action-button--download",
16983
+ download: true,
16984
+ href: downloadUrl,
16985
+ rel: "noreferrer",
16986
+ target: "_blank",
16987
+ title: downloadLabel,
16988
+ children: /* @__PURE__ */ jsx(IconArrowDownCircle, {})
16989
+ }
16990
+ ) : null,
16991
+ modalContext?.close ? /* @__PURE__ */ jsx(
16992
+ Button,
16993
+ {
16994
+ "aria-label": t("Close"),
16995
+ className: "str-chat__gallery__action-button str-chat__gallery__action-button--close",
16996
+ onClick: modalContext.close,
16997
+ title: t("Close"),
16998
+ children: /* @__PURE__ */ jsx(IconCrossMedium, {})
16999
+ }
17000
+ ) : null
17001
+ ] })
17002
+ ] });
17003
+ };
17004
+ const VideoPlayer = ({ isPlaying, thumbnailUrl, videoUrl }) => {
17005
+ const { VideoPlayer: VideoPlayerContext } = useComponentContext();
17006
+ return VideoPlayerContext ? /* @__PURE__ */ jsx(VideoPlayerContext, { thumbnailUrl, videoUrl }) : /* @__PURE__ */ jsx(
17007
+ ReactPlayer,
17008
+ {
17009
+ className: "react-player",
17010
+ config: { file: { attributes: { poster: thumbnailUrl } } },
17011
+ controls: true,
17012
+ height: "100%",
17013
+ playing: isPlaying,
17014
+ url: videoUrl,
17015
+ width: "100%"
17016
+ }
17017
+ );
17018
+ };
17019
+ const VideoThumbnail = ({
17020
+ className,
17021
+ onPlay,
17022
+ ...imageProps
17023
+ }) => {
17024
+ const { t } = useTranslationContext();
17025
+ return /* @__PURE__ */ jsxs("div", { className: "str-chat__message-attachment__video-thumbnail", children: [
17026
+ /* @__PURE__ */ jsx(
17027
+ BaseImage,
17028
+ {
17029
+ className: clsx("str-chat__message-attachment__video-thumbnail-image", className),
17030
+ ...imageProps
17031
+ }
17032
+ ),
17033
+ onPlay ? /* @__PURE__ */ jsx(
17034
+ Button,
17035
+ {
17036
+ appearance: "solid",
17037
+ "aria-label": t("Play video"),
17038
+ circular: true,
17039
+ className: clsx(
17040
+ "str-chat__message-attachment__video-thumbnail__play-indicator"
17041
+ ),
17042
+ onClick: onPlay,
17043
+ size: "lg",
17044
+ variant: "secondary",
17045
+ children: /* @__PURE__ */ jsx(IconPlaySolid, {})
17046
+ }
17047
+ ) : /* @__PURE__ */ jsx("div", { className: "str-chat__message-attachment__video-thumbnail__play-indicator", children: /* @__PURE__ */ jsx(IconPlaySolid, {}) })
17048
+ ] });
17049
+ };
17050
+ const SWIPE_THRESHOLD = 50;
17051
+ const TRANSITION_DURATION = 300;
17052
+ const GalleryUI = () => {
17053
+ const { t } = useTranslationContext();
17054
+ const {
17055
+ closeOnBackgroundClick,
17056
+ currentIndex,
17057
+ currentItem,
17058
+ goToNext,
17059
+ goToPrevious,
17060
+ hasNext,
17061
+ hasPrevious,
17062
+ itemCount,
17063
+ onRequestClose
17064
+ } = useGalleryContext();
17065
+ const modalContext = useContext(ModalContext);
17066
+ const [showVideo, setShowVideo] = useState(false);
17067
+ const isTransitioningRef = useRef(false);
17068
+ const [slideOffset, setSlideOffset] = useState(0);
17069
+ const [isDragging, setIsDragging] = useState(false);
17070
+ const [slideDirection, setSlideDirection] = useState(
17071
+ null
17072
+ );
17073
+ const ignoreNextClickRef = useRef(false);
17074
+ const touchStartRef = useRef(null);
17075
+ const isVerticalSwipeRef = useRef(false);
17076
+ const containerRef = useRef(null);
17077
+ useEffect(() => {
17078
+ setShowVideo(false);
17079
+ }, [currentIndex]);
17080
+ const prevIndexRef = useRef(currentIndex);
17081
+ useEffect(() => {
17082
+ if (prevIndexRef.current === currentIndex) return;
17083
+ const direction = currentIndex > prevIndexRef.current ? "forward" : "backward";
17084
+ setSlideDirection(direction);
17085
+ setSlideOffset(0);
17086
+ setIsDragging(false);
17087
+ isTransitioningRef.current = true;
17088
+ const timer = setTimeout(() => {
17089
+ setSlideDirection(null);
17090
+ isTransitioningRef.current = false;
17091
+ }, TRANSITION_DURATION);
17092
+ prevIndexRef.current = currentIndex;
17093
+ return () => clearTimeout(timer);
17094
+ }, [currentIndex]);
17095
+ const handleGoToNext = useCallback(() => {
17096
+ if (isTransitioningRef.current) return;
17097
+ goToNext();
17098
+ }, [goToNext]);
17099
+ const handleGoToPrevious = useCallback(() => {
17100
+ if (isTransitioningRef.current) return;
17101
+ goToPrevious();
17102
+ }, [goToPrevious]);
17103
+ const handleKeyDown = useCallback(
17104
+ (event) => {
17105
+ if (event.key === "ArrowLeft") {
17106
+ event.preventDefault();
17107
+ handleGoToPrevious();
17108
+ } else if (event.key === "ArrowRight") {
17109
+ event.preventDefault();
17110
+ handleGoToNext();
17111
+ }
17112
+ },
17113
+ [handleGoToNext, handleGoToPrevious]
17114
+ );
17115
+ useEffect(() => {
17116
+ document.addEventListener("keydown", handleKeyDown);
17117
+ return () => document.removeEventListener("keydown", handleKeyDown);
17118
+ }, [handleKeyDown]);
17119
+ const handleTouchStart = useCallback((event) => {
17120
+ if (isTransitioningRef.current) return;
17121
+ const touch = event.touches[0];
17122
+ ignoreNextClickRef.current = false;
17123
+ touchStartRef.current = { x: touch.clientX, y: touch.clientY };
17124
+ isVerticalSwipeRef.current = false;
17125
+ }, []);
17126
+ const handleTouchMove = useCallback(
17127
+ (event) => {
17128
+ if (!touchStartRef.current || isTransitioningRef.current) return;
17129
+ const touch = event.touches[0];
17130
+ const deltaX = touch.clientX - touchStartRef.current.x;
17131
+ const deltaY = touch.clientY - touchStartRef.current.y;
17132
+ if (!isDragging && !isVerticalSwipeRef.current) {
17133
+ if (Math.abs(deltaY) > Math.abs(deltaX) && Math.abs(deltaY) > 10) {
17134
+ ignoreNextClickRef.current = true;
17135
+ isVerticalSwipeRef.current = true;
17136
+ return;
17137
+ }
17138
+ if (Math.abs(deltaX) > 10) {
17139
+ ignoreNextClickRef.current = true;
17140
+ setIsDragging(true);
17141
+ }
17142
+ }
17143
+ if (isVerticalSwipeRef.current) return;
17144
+ if (!hasNext && deltaX < 0 || !hasPrevious && deltaX > 0) {
17145
+ setSlideOffset(deltaX * 0.3);
17146
+ } else {
17147
+ setSlideOffset(deltaX);
17148
+ }
17149
+ },
17150
+ [isDragging, hasNext, hasPrevious]
17151
+ );
17152
+ const handleTouchEnd = useCallback(() => {
17153
+ if (!touchStartRef.current || isVerticalSwipeRef.current) {
17154
+ if (isVerticalSwipeRef.current) ignoreNextClickRef.current = true;
17155
+ touchStartRef.current = null;
17156
+ return;
17157
+ }
17158
+ const offset = slideOffset;
17159
+ if (isDragging || Math.abs(offset) > 10) {
17160
+ ignoreNextClickRef.current = true;
17161
+ }
17162
+ touchStartRef.current = null;
17163
+ if (Math.abs(offset) >= SWIPE_THRESHOLD) {
17164
+ if (offset < 0 && hasNext) {
17165
+ goToNext();
17166
+ } else if (offset > 0 && hasPrevious) {
17167
+ goToPrevious();
17168
+ } else {
17169
+ setSlideOffset(0);
17170
+ }
17171
+ } else {
17172
+ setSlideOffset(0);
17173
+ }
17174
+ setIsDragging(false);
17175
+ }, [slideOffset, hasNext, hasPrevious, goToNext, goToPrevious, isDragging]);
17176
+ const requestClose = modalContext?.close ?? onRequestClose;
17177
+ const handleBackgroundClick = useCallback(
17178
+ (event) => {
17179
+ if (event.target !== event.currentTarget) return;
17180
+ if (ignoreNextClickRef.current) {
17181
+ ignoreNextClickRef.current = false;
17182
+ return;
17183
+ }
17184
+ if (!closeOnBackgroundClick) return;
17185
+ requestClose?.();
17186
+ },
17187
+ [closeOnBackgroundClick, requestClose]
17188
+ );
17189
+ const mediaStyle = isDragging || slideOffset !== 0 && slideDirection === null ? { transform: `translateX(${slideOffset}px)` } : {};
17190
+ return /* @__PURE__ */ jsxs("div", { className: "str-chat__gallery", children: [
17191
+ /* @__PURE__ */ jsxs("div", { className: "str-chat__gallery__main", children: [
17192
+ /* @__PURE__ */ jsx(GalleryHeader, { currentItem }),
17193
+ /* @__PURE__ */ jsx(
17194
+ NavButton,
17195
+ {
17196
+ "aria-label": t("Previous image"),
17197
+ className: clsx(
17198
+ "str-chat__gallery__nav-button--prev",
17199
+ !hasPrevious && "str-chat__gallery__nav-button--hidden"
17200
+ ),
17201
+ disabled: !hasPrevious,
17202
+ onClick: handleGoToPrevious,
17203
+ children: /* @__PURE__ */ jsx(IconChevronLeft, {})
17204
+ }
17205
+ ),
17206
+ /* @__PURE__ */ jsx(
17207
+ "div",
17208
+ {
17209
+ className: "str-chat__gallery__slide-container",
17210
+ onClick: handleBackgroundClick,
17211
+ onTouchEnd: handleTouchEnd,
17212
+ onTouchMove: handleTouchMove,
17213
+ onTouchStart: handleTouchStart,
17214
+ ref: containerRef,
17215
+ children: /* @__PURE__ */ jsx(
17216
+ "div",
17217
+ {
17218
+ className: clsx({
17219
+ "str-chat__gallery__media--dragging": isDragging,
17220
+ "str-chat__gallery__media--slide-backward": !isDragging && slideDirection === "backward",
17221
+ "str-chat__gallery__media--slide-forward": !isDragging && slideDirection === "forward"
17222
+ }),
17223
+ style: mediaStyle,
17224
+ children: currentItem.videoUrl && currentItem.videoThumbnailUrl ? /* @__PURE__ */ jsx("div", { className: "str-chat__gallery__media str-chat__gallery__media--video", children: showVideo ? /* @__PURE__ */ jsx(VideoPlayer, { isPlaying: true, videoUrl: currentItem.videoUrl }) : /* @__PURE__ */ jsx(
17225
+ VideoThumbnail,
17226
+ {
17227
+ alt: currentItem.title ?? "",
17228
+ onPlay: () => setShowVideo(true),
17229
+ src: currentItem.videoThumbnailUrl
17230
+ }
17231
+ ) }) : /* @__PURE__ */ jsx("div", { className: "str-chat__gallery__media str-chat__gallery__media--image", children: /* @__PURE__ */ jsx(BaseImage, { alt: currentItem.alt, src: currentItem.imageUrl }) })
17232
+ }
17233
+ )
17234
+ }
17235
+ ),
17236
+ /* @__PURE__ */ jsx(
17237
+ NavButton,
17238
+ {
17239
+ "aria-label": t("Next image"),
17240
+ className: clsx(
17241
+ "str-chat__gallery__nav-button--next",
17242
+ !hasNext && "str-chat__gallery__nav-button--hidden"
17243
+ ),
17244
+ disabled: !hasNext,
17245
+ onClick: handleGoToNext,
17246
+ children: /* @__PURE__ */ jsx(IconChevronRight, {})
17247
+ }
17248
+ )
17249
+ ] }),
17250
+ itemCount > 1 && /* @__PURE__ */ jsxs("div", { className: "str-chat__gallery__position-indicator", children: [
17251
+ currentIndex + 1,
17252
+ " of ",
17253
+ itemCount
17254
+ ] })
17255
+ ] });
17256
+ };
17257
+ const NavButton = ({ className, ...props }) => /* @__PURE__ */ jsx(Button, { ...props, className: clsx("str-chat__gallery__nav-button", className) });
17258
+ const Gallery = ({
17259
+ closeOnBackgroundClick = true,
17260
+ GalleryUI: GalleryUI$1,
17261
+ initialIndex = 0,
17262
+ items,
17263
+ onIndexChange,
17264
+ onRequestClose
17265
+ }) => {
17266
+ const { GalleryUI: ContextGalleryUI } = useComponentContext();
17267
+ const ResolvedGalleryUI = GalleryUI$1 ?? ContextGalleryUI ?? GalleryUI;
17268
+ const [currentIndex, setCurrentIndex] = useState(initialIndex);
17269
+ const itemCount = items.length;
17270
+ const goToIndex = useCallback(
17271
+ (index) => {
17272
+ if (index >= 0 && index < itemCount) {
17273
+ setCurrentIndex(index);
17274
+ }
17275
+ },
17276
+ [itemCount]
17277
+ );
17278
+ const goToNext = useCallback(() => {
17279
+ setCurrentIndex((prev) => prev < itemCount - 1 ? prev + 1 : prev);
17280
+ }, [itemCount]);
17281
+ const goToPrevious = useCallback(() => {
17282
+ setCurrentIndex((prev) => prev > 0 ? prev - 1 : prev);
17283
+ }, []);
17284
+ useEffect(() => {
17285
+ onIndexChange?.(currentIndex);
17286
+ }, [currentIndex, onIndexChange]);
17287
+ const hasNext = currentIndex < itemCount - 1;
17288
+ const hasPrevious = currentIndex > 0;
17289
+ const currentItem = items[currentIndex];
17290
+ const contextValue = useMemo(
17291
+ () => ({
17292
+ closeOnBackgroundClick,
17293
+ currentIndex,
17294
+ currentItem,
17295
+ goToIndex,
17296
+ goToNext,
17297
+ goToPrevious,
17298
+ hasNext,
17299
+ hasPrevious,
17300
+ itemCount,
17301
+ items,
17302
+ onRequestClose
17303
+ }),
17304
+ [
17305
+ closeOnBackgroundClick,
17306
+ currentIndex,
17307
+ currentItem,
17308
+ goToIndex,
17309
+ goToNext,
17310
+ goToPrevious,
17311
+ hasNext,
17312
+ hasPrevious,
17313
+ itemCount,
17314
+ items,
17315
+ onRequestClose
17316
+ ]
17317
+ );
17318
+ return /* @__PURE__ */ jsx(GalleryContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(ResolvedGalleryUI, {}) });
17319
+ };
17123
17320
  const AttachmentPreviewList = ({
17124
17321
  AudioAttachmentPreview: AudioAttachmentPreview2 = FileAttachmentPreview,
17125
17322
  FileAttachmentPreview: FileAttachmentPreview$1 = FileAttachmentPreview,
@@ -17128,73 +17325,107 @@ const AttachmentPreviewList = ({
17128
17325
  VideoAttachmentPreview = MediaAttachmentPreview
17129
17326
  }) => {
17130
17327
  const messageComposer = useMessageComposerController();
17328
+ const { Modal = GlobalModal } = useComponentContext();
17329
+ const [showPreview, setShowPreview] = useState(false);
17330
+ const initialIndexRef = useRef(0);
17131
17331
  const { attachments } = useAttachmentsForPreview();
17132
17332
  const filteredAttachments = useMemo(
17133
17333
  () => attachments.filter((a) => !isVoiceRecordingAttachment(a)),
17134
17334
  [attachments]
17135
17335
  );
17336
+ const { galleryItems, previewIndexById } = useMemo(() => {
17337
+ const items = [];
17338
+ const indexById = {};
17339
+ for (const a of attachments) {
17340
+ if (isLocalImageAttachment(a) || isLocalVideoAttachment(a)) {
17341
+ const descriptor = toBaseImageDescriptors(a);
17342
+ if (descriptor) {
17343
+ indexById[a.localMetadata.id] = items.length;
17344
+ items.push(descriptor);
17345
+ }
17346
+ }
17347
+ }
17348
+ return { galleryItems: items, previewIndexById: indexById };
17349
+ }, [attachments]);
17350
+ const openPreviewAtIndex = useCallback((index) => {
17351
+ initialIndexRef.current = index;
17352
+ setShowPreview(true);
17353
+ }, []);
17136
17354
  if (!filteredAttachments.length) return null;
17137
- return /* @__PURE__ */ jsx(
17355
+ return /* @__PURE__ */ jsxs(
17138
17356
  "div",
17139
17357
  {
17140
17358
  className: "str-chat__attachment-preview-list",
17141
17359
  "data-testid": "attachment-preview-list",
17142
- children: attachments.map((attachment) => {
17143
- if (isScrapedContent(attachment)) return null;
17144
- if (isLocalVoiceRecordingAttachment(attachment)) return null;
17145
- if (isLocalAudioAttachment(attachment)) {
17146
- return /* @__PURE__ */ jsx(
17147
- AudioAttachmentPreview2,
17148
- {
17149
- attachment,
17150
- handleRetry: messageComposer.attachmentManager.uploadAttachment,
17151
- removeAttachments: messageComposer.attachmentManager.removeAttachments
17152
- },
17153
- attachment.localMetadata.id || attachment.asset_url
17154
- );
17155
- } else if (isLocalVideoAttachment(attachment)) {
17156
- return /* @__PURE__ */ jsx(
17157
- VideoAttachmentPreview,
17158
- {
17159
- attachment,
17160
- handleRetry: messageComposer.attachmentManager.uploadAttachment,
17161
- removeAttachments: messageComposer.attachmentManager.removeAttachments
17162
- },
17163
- attachment.localMetadata.id || attachment.asset_url
17164
- );
17165
- } else if (isLocalImageAttachment(attachment)) {
17166
- return /* @__PURE__ */ jsx(
17167
- ImageAttachmentPreview,
17168
- {
17169
- attachment,
17170
- handleRetry: messageComposer.attachmentManager.uploadAttachment,
17171
- removeAttachments: messageComposer.attachmentManager.removeAttachments
17172
- },
17173
- attachment.localMetadata.id || attachment.image_url
17174
- );
17175
- } else if (isLocalFileAttachment(attachment)) {
17176
- return /* @__PURE__ */ jsx(
17177
- FileAttachmentPreview$1,
17178
- {
17179
- attachment,
17180
- handleRetry: messageComposer.attachmentManager.uploadAttachment,
17181
- removeAttachments: messageComposer.attachmentManager.removeAttachments
17182
- },
17183
- attachment.localMetadata.id || attachment.asset_url
17184
- );
17185
- } else if (isLocalAttachment(attachment)) {
17186
- return /* @__PURE__ */ jsx(
17187
- UnsupportedAttachmentPreview$1,
17188
- {
17189
- attachment,
17190
- handleRetry: messageComposer.attachmentManager.uploadAttachment,
17191
- removeAttachments: messageComposer.attachmentManager.removeAttachments
17192
- },
17193
- attachment.localMetadata.id
17194
- );
17195
- }
17196
- return null;
17197
- })
17360
+ children: [
17361
+ attachments.map((attachment) => {
17362
+ if (isScrapedContent(attachment)) return null;
17363
+ if (isLocalVoiceRecordingAttachment(attachment)) return null;
17364
+ if (isLocalAudioAttachment(attachment)) {
17365
+ return /* @__PURE__ */ jsx(
17366
+ AudioAttachmentPreview2,
17367
+ {
17368
+ attachment,
17369
+ handleRetry: messageComposer.attachmentManager.uploadAttachment,
17370
+ removeAttachments: messageComposer.attachmentManager.removeAttachments
17371
+ },
17372
+ attachment.localMetadata.id || attachment.asset_url
17373
+ );
17374
+ } else if (isLocalVideoAttachment(attachment)) {
17375
+ return /* @__PURE__ */ jsx(
17376
+ VideoAttachmentPreview,
17377
+ {
17378
+ attachment,
17379
+ handleRetry: messageComposer.attachmentManager.uploadAttachment,
17380
+ openPreview: () => openPreviewAtIndex(previewIndexById[attachment.localMetadata.id] ?? 0),
17381
+ removeAttachments: messageComposer.attachmentManager.removeAttachments
17382
+ },
17383
+ attachment.localMetadata.id || attachment.asset_url
17384
+ );
17385
+ } else if (isLocalImageAttachment(attachment)) {
17386
+ return /* @__PURE__ */ jsx(
17387
+ ImageAttachmentPreview,
17388
+ {
17389
+ attachment,
17390
+ handleRetry: messageComposer.attachmentManager.uploadAttachment,
17391
+ openPreview: () => openPreviewAtIndex(previewIndexById[attachment.localMetadata.id] ?? 0),
17392
+ removeAttachments: messageComposer.attachmentManager.removeAttachments
17393
+ },
17394
+ attachment.localMetadata.id || attachment.image_url
17395
+ );
17396
+ } else if (isLocalFileAttachment(attachment)) {
17397
+ return /* @__PURE__ */ jsx(
17398
+ FileAttachmentPreview$1,
17399
+ {
17400
+ attachment,
17401
+ handleRetry: messageComposer.attachmentManager.uploadAttachment,
17402
+ removeAttachments: messageComposer.attachmentManager.removeAttachments
17403
+ },
17404
+ attachment.localMetadata.id || attachment.asset_url
17405
+ );
17406
+ } else if (isLocalAttachment(attachment)) {
17407
+ return /* @__PURE__ */ jsx(
17408
+ UnsupportedAttachmentPreview$1,
17409
+ {
17410
+ attachment,
17411
+ handleRetry: messageComposer.attachmentManager.uploadAttachment,
17412
+ removeAttachments: messageComposer.attachmentManager.removeAttachments
17413
+ },
17414
+ attachment.localMetadata.id
17415
+ );
17416
+ }
17417
+ return null;
17418
+ }),
17419
+ galleryItems.length > 0 && /* @__PURE__ */ jsx(
17420
+ Modal,
17421
+ {
17422
+ className: "str-chat__gallery-modal",
17423
+ onClose: () => setShowPreview(false),
17424
+ open: showPreview,
17425
+ children: /* @__PURE__ */ jsx(Gallery, { initialIndex: initialIndexRef.current, items: galleryItems })
17426
+ }
17427
+ )
17428
+ ]
17198
17429
  }
17199
17430
  );
17200
17431
  };
@@ -17222,11 +17453,9 @@ const VoiceRecordingPreviewSlot = ({
17222
17453
  }
17223
17454
  );
17224
17455
  };
17225
- const textComposerStateSelector$4 = ({ command }) => ({ command });
17226
- const CommandChip = () => {
17456
+ const CommandChip = ({ command }) => {
17227
17457
  const { textComposer } = useMessageComposerController();
17228
17458
  const { textareaRef } = useMessageComposerContext();
17229
- const { command } = useStateStore(textComposer.state, textComposerStateSelector$4);
17230
17459
  if (!command) return null;
17231
17460
  return /* @__PURE__ */ jsxs("div", { className: "str-chat__command-chip", children: [
17232
17461
  /* @__PURE__ */ jsx(IconThunder, {}),
@@ -17525,9 +17754,12 @@ const ToggleRecordingButton = () => {
17525
17754
  );
17526
17755
  };
17527
17756
  const AudioRecorderRecordingControls = () => {
17757
+ const { client } = useChatContext();
17758
+ const { t } = useTranslationContext();
17528
17759
  const {
17529
17760
  recordingController: { completeRecording, recorder, recording, recordingState }
17530
17761
  } = useMessageComposerContext();
17762
+ const panel = useNotificationTarget();
17531
17763
  const isUploadingFile = recording?.localMetadata?.uploadState === "uploading";
17532
17764
  if (!recorder) return null;
17533
17765
  return /* @__PURE__ */ jsxs("div", { className: "str-chat__audio_recorder__recording-controls", children: [
@@ -17539,7 +17771,17 @@ const AudioRecorderRecordingControls = () => {
17539
17771
  className: "str-chat__audio_recorder__cancel-button",
17540
17772
  "data-testid": "cancel-recording-audio-button",
17541
17773
  disabled: isUploadingFile,
17542
- onClick: recorder.cancel,
17774
+ onClick: () => {
17775
+ recorder.cancel();
17776
+ client.notifications.addInfo({
17777
+ message: t("Voice message deleted"),
17778
+ options: {
17779
+ tags: addNotificationTargetTag(panel),
17780
+ type: "audioRecording:cancel:success"
17781
+ },
17782
+ origin: { emitter: "AudioRecorder" }
17783
+ });
17784
+ },
17543
17785
  size: "sm",
17544
17786
  variant: "secondary",
17545
17787
  children: /* @__PURE__ */ jsx(IconTrashBin, {})
@@ -17846,7 +18088,10 @@ const SuggestionList = ({
17846
18088
  setFocusedItemIndex,
17847
18089
  suggestionItemComponents = defaultComponents
17848
18090
  }) => {
17849
- const { AutocompleteSuggestionItem = SuggestionListItem } = useComponentContext();
18091
+ const {
18092
+ AutocompleteSuggestionItem = SuggestionListItem,
18093
+ ContextMenu: ContextMenuComponent = ContextMenu
18094
+ } = useComponentContext();
17850
18095
  const { textareaRef } = useMessageComposerContext();
17851
18096
  const messageComposer = useMessageComposerController();
17852
18097
  const { textComposer } = messageComposer;
@@ -17964,7 +18209,7 @@ const SuggestionList = ({
17964
18209
  zIndex: 1e3
17965
18210
  },
17966
18211
  children: /* @__PURE__ */ jsx(
17967
- ContextMenu,
18212
+ ContextMenuComponent,
17968
18213
  {
17969
18214
  className: clsx("str-chat__suggestion-list", className),
17970
18215
  Header: suggestions.searchSource.type === "commands" ? CommandsMenuHeader : void 0,
@@ -18063,6 +18308,12 @@ const TextareaComposer = ({
18063
18308
  textComposer.state,
18064
18309
  textComposerStateSelector$1
18065
18310
  );
18311
+ const autosizeRows = !text2 && minRows == null ? 1 : void 0;
18312
+ const textareaStyle = text2 ? void 0 : {
18313
+ overflow: "hidden",
18314
+ textOverflow: "ellipsis",
18315
+ whiteSpace: "nowrap"
18316
+ };
18066
18317
  const { enabled } = useStateStore(messageComposer.configState, configStateSelector);
18067
18318
  const { quotedMessage } = useStateStore(
18068
18319
  messageComposer.state,
@@ -18216,8 +18467,8 @@ const TextareaComposer = ({
18216
18467
  ),
18217
18468
  "data-testid": "message-input",
18218
18469
  disabled: !enabled || !!cooldownRemaining,
18219
- maxRows,
18220
- minRows,
18470
+ maxRows: autosizeRows ?? maxRows,
18471
+ minRows: autosizeRows ?? minRows,
18221
18472
  onBlur,
18222
18473
  onChange: changeHandler,
18223
18474
  onCompositionEnd,
@@ -18229,7 +18480,8 @@ const TextareaComposer = ({
18229
18480
  placeholder,
18230
18481
  ref: (ref) => {
18231
18482
  textareaRef.current = ref;
18232
- }
18483
+ },
18484
+ style: textareaStyle
18233
18485
  }
18234
18486
  ),
18235
18487
  !isComposing && /* @__PURE__ */ jsx(
@@ -18561,7 +18813,7 @@ const MessageComposerUI = () => {
18561
18813
  /* @__PURE__ */ jsxs("div", { className: "str-chat__message-composer-controls", children: [
18562
18814
  /* @__PURE__ */ jsxs("div", { className: "str-chat__message-composer-controls__text-composition-controls", children: [
18563
18815
  /* @__PURE__ */ jsxs("div", { className: "str-chat__message-composer-controls__text-composition-controls__text", children: [
18564
- /* @__PURE__ */ jsx("div", { className: "str-chat__message-composer-controls__text-composition-controls__command-chip-container", children: /* @__PURE__ */ jsx(CommandChip$1, {}) }),
18816
+ command && /* @__PURE__ */ jsx("div", { className: "str-chat__message-composer-controls__text-composition-controls__command-chip-container", children: /* @__PURE__ */ jsx(CommandChip$1, { command }) }),
18565
18817
  /* @__PURE__ */ jsx(TextareaComposer$1, {})
18566
18818
  ] }),
18567
18819
  /* @__PURE__ */ jsx(SendToChannelCheckbox$1, {})
@@ -18636,7 +18888,7 @@ const MessageComposerProvider = (props) => {
18636
18888
  const messageComposer = useMessageComposerController();
18637
18889
  useEffect(
18638
18890
  () => () => {
18639
- messageComposer.createDraft();
18891
+ messageComposer.createDraft().finally(() => messageComposer.clear());
18640
18892
  },
18641
18893
  [messageComposer]
18642
18894
  );
@@ -18954,7 +19206,7 @@ const useChat = ({
18954
19206
  };
18955
19207
  useEffect(() => {
18956
19208
  if (!client) return;
18957
- const version = "14.0.0-beta.1";
19209
+ const version = "14.0.0-beta.3";
18958
19210
  const userAgent = client.getUserAgent();
18959
19211
  if (!userAgent.includes("stream-chat-react")) {
18960
19212
  client.setUserAgent(`stream-chat-react-${version}-${userAgent}`);
@@ -19455,13 +19707,13 @@ const ReactionSelector = (props) => {
19455
19707
  ) })
19456
19708
  ] });
19457
19709
  };
19458
- ReactionSelector.getDialogId = (({ messageId, threadList }) => {
19710
+ ReactionSelector.getDialogId = ({ messageId, threadList }) => {
19459
19711
  const dialogIdNamespace = threadList ? "-thread" : "";
19460
- return `reaction-selector${dialogIdNamespace}--${messageId}`;
19461
- });
19712
+ return `reaction-selector${dialogIdNamespace}-${messageId}`;
19713
+ };
19462
19714
  ReactionSelector.displayName = "ReactionSelector";
19463
19715
  const ReactionSelectorWithButton = ({
19464
- ReactionIcon: ReactionIcon2
19716
+ ReactionIcon
19465
19717
  }) => {
19466
19718
  const { t } = useTranslationContext("ReactionSelectorWithButton");
19467
19719
  const { isMyMessage, message, threadList } = useMessageContext();
@@ -19495,7 +19747,7 @@ const ReactionSelectorWithButton = ({
19495
19747
  "data-testid": "message-reaction-action",
19496
19748
  onClick: () => dialog?.toggle(),
19497
19749
  ref: buttonRef,
19498
- children: /* @__PURE__ */ jsx(ReactionIcon2, { className: "str-chat__message-action-icon" })
19750
+ children: /* @__PURE__ */ jsx(ReactionIcon, { className: "str-chat__message-action-icon" })
19499
19751
  }
19500
19752
  )
19501
19753
  ] });
@@ -19543,6 +19795,7 @@ const QuickMessageActionsButton = ({ className, ...props }) => /* @__PURE__ */ j
19543
19795
  appearance: "ghost",
19544
19796
  circular: true,
19545
19797
  className: clsx("str-chat__message-actions-box-button", className),
19798
+ size: "sm",
19546
19799
  variant: "secondary",
19547
19800
  ...props
19548
19801
  }
@@ -19832,8 +20085,7 @@ const DefaultMessageActionComponents = {
19832
20085
  const { closeMenu } = useContextMenuContext();
19833
20086
  const { client } = useChatContext();
19834
20087
  const { Modal = GlobalModal } = useComponentContext();
19835
- const { removeMessage } = useChannelActionContext();
19836
- const { handleDelete, message } = useMessageContext();
20088
+ const { handleDelete } = useMessageContext();
19837
20089
  const panel = useNotificationTarget();
19838
20090
  const { t } = useTranslationContext();
19839
20091
  const [openModal, setOpenModal] = useState(false);
@@ -19859,29 +20111,20 @@ const DefaultMessageActionComponents = {
19859
20111
  closeMenu();
19860
20112
  },
19861
20113
  onDelete: async () => {
19862
- if (message.type === "error") removeMessage(message);
19863
- else {
19864
- try {
19865
- await handleDelete();
19866
- client.notifications.addSuccess({
19867
- message: t("Message deleted"),
19868
- options: {
19869
- tags: addNotificationTargetTag(panel)
19870
- },
19871
- origin: { emitter: "MessageActions" }
19872
- });
19873
- } catch (error) {
19874
- client.notifications.addError({
19875
- message: t("Failed to delete the message"),
19876
- options: {
19877
- tags: addNotificationTargetTag(panel)
19878
- },
19879
- origin: { emitter: "MessageActions" }
19880
- });
19881
- }
20114
+ try {
20115
+ await handleDelete();
20116
+ client.notifications.addSuccess({
20117
+ message: t("Message deleted"),
20118
+ options: {
20119
+ tags: addNotificationTargetTag(panel)
20120
+ },
20121
+ origin: { emitter: "MessageActions" }
20122
+ });
20123
+ } catch {
20124
+ } finally {
20125
+ setOpenModal(false);
20126
+ closeMenu();
19882
20127
  }
19883
- setOpenModal(false);
19884
- closeMenu();
19885
20128
  }
19886
20129
  }
19887
20130
  ) })
@@ -19913,8 +20156,34 @@ const DefaultMessageActionComponents = {
19913
20156
  }
19914
20157
  },
19915
20158
  quick: {
20159
+ // eslint-disable-next-line react/display-name
20160
+ DropdownToggle: forwardRef((_, ref) => {
20161
+ const { t } = useTranslationContext();
20162
+ const { message } = useMessageContext();
20163
+ const dropdownDialogIsOpen = useDialogIsOpen(
20164
+ MessageActions.getDialogId({ messageId: message.id })
20165
+ );
20166
+ const { dialog } = useDialogOnNearestManager({
20167
+ id: MessageActions.getDialogId({ messageId: message.id })
20168
+ });
20169
+ return /* @__PURE__ */ jsx(
20170
+ QuickMessageActionsButton,
20171
+ {
20172
+ "aria-expanded": dropdownDialogIsOpen,
20173
+ "aria-haspopup": "true",
20174
+ "aria-label": t("aria/Open Message Actions Menu"),
20175
+ className: "str-chat__message-actions-box-button",
20176
+ "data-testid": "message-actions-toggle-button",
20177
+ onClick: () => {
20178
+ dialog?.toggle();
20179
+ },
20180
+ ref,
20181
+ children: /* @__PURE__ */ jsx(IconDotGrid1x3Horizontal, { className: "str-chat__message-action-icon" })
20182
+ }
20183
+ );
20184
+ }),
19916
20185
  React() {
19917
- return /* @__PURE__ */ jsx(ReactionSelectorWithButton, { ReactionIcon });
20186
+ return /* @__PURE__ */ jsx(ReactionSelectorWithButton, { ReactionIcon: IconEmojiSmile });
19918
20187
  },
19919
20188
  Reply() {
19920
20189
  const { handleOpenThread } = useMessageContext();
@@ -19926,14 +20195,17 @@ const DefaultMessageActionComponents = {
19926
20195
  className: "str-chat__message-reply-in-thread-button",
19927
20196
  "data-testid": "thread-action",
19928
20197
  onClick: handleOpenThread,
19929
- children: /* @__PURE__ */ jsx(ThreadIcon, { className: "str-chat__message-action-icon" })
20198
+ children: /* @__PURE__ */ jsx(IconArrowShareLeft, { className: "str-chat__message-action-icon" })
19930
20199
  }
19931
20200
  );
19932
20201
  }
19933
20202
  }
19934
20203
  };
19935
20204
  const defaultMessageActionSet = [
19936
- // { placement: 'dropdown', type: 'block' },
20205
+ {
20206
+ Component: DefaultMessageActionComponents.quick.DropdownToggle,
20207
+ placement: "quick-dropdown-toggle"
20208
+ },
19937
20209
  {
19938
20210
  Component: DefaultMessageActionComponents.quick.Reply,
19939
20211
  placement: "quick",
@@ -20023,14 +20295,14 @@ function useFetchReactions(options) {
20023
20295
  const handleFetchReactions = propHandleFetchReactions ?? contextHandleFetchReactions;
20024
20296
  const [refetchNonce, setRefetchNonce] = useState(null);
20025
20297
  useEffect(() => {
20026
- if (!shouldFetch || !reactionType) {
20298
+ if (!shouldFetch) {
20027
20299
  return;
20028
20300
  }
20029
20301
  let cancel = false;
20030
20302
  (async () => {
20031
20303
  try {
20032
20304
  setIsLoading(true);
20033
- const reactions2 = await handleFetchReactions(reactionType, sort);
20305
+ const reactions2 = await handleFetchReactions(reactionType ?? void 0, sort);
20034
20306
  if (!cancel) {
20035
20307
  setReactions(reactions2);
20036
20308
  }
@@ -20054,6 +20326,16 @@ function useFetchReactions(options) {
20054
20326
  return { isLoading, reactions, refetch };
20055
20327
  }
20056
20328
  const defaultReactionDetailsSort = { created_at: -1 };
20329
+ const MessageReactionsDetailLoadingIndicator = () => {
20330
+ const elements = useMemo(
20331
+ () => Array.from({ length: 3 }, (_, index) => /* @__PURE__ */ jsxs("div", { className: "str-chat__message-reactions-detail__skeleton-item", children: [
20332
+ /* @__PURE__ */ jsx("div", { className: "str-chat__message-reactions-detail__skeleton-avatar" }),
20333
+ /* @__PURE__ */ jsx("div", { className: "str-chat__message-reactions-detail__skeleton-line" })
20334
+ ] }, index)),
20335
+ []
20336
+ );
20337
+ return /* @__PURE__ */ jsx(Fragment, { children: elements });
20338
+ };
20057
20339
  function MessageReactionsDetail({
20058
20340
  handleFetchReactions,
20059
20341
  onSelectedReactionTypeChange,
@@ -20064,7 +20346,11 @@ function MessageReactionsDetail({
20064
20346
  totalReactionCount
20065
20347
  }) {
20066
20348
  const { client } = useChatContext();
20067
- const { Avatar: Avatar$1 = Avatar } = useComponentContext(MessageReactionsDetail.name);
20349
+ const {
20350
+ Avatar: Avatar$1 = Avatar,
20351
+ LoadingIndicator: LoadingIndicator2 = MessageReactionsDetailLoadingIndicator,
20352
+ reactionOptions = defaultReactionOptions
20353
+ } = useComponentContext(MessageReactionsDetail.name);
20068
20354
  const { t } = useTranslationContext();
20069
20355
  const {
20070
20356
  handleReaction: contextHandleReaction,
@@ -20091,7 +20377,7 @@ function MessageReactionsDetail({
20091
20377
  "div",
20092
20378
  {
20093
20379
  className: "str-chat__message-reactions-detail",
20094
- "data-testid": "reactions-list-modal",
20380
+ "data-testid": "message-reactions-detail",
20095
20381
  children: [
20096
20382
  typeof totalReactionCount === "number" && /* @__PURE__ */ jsx("div", { className: "str-chat__message-reactions-detail__total-count", children: t("{{ count }} reactions", { count: totalReactionCount }) }),
20097
20383
  /* @__PURE__ */ jsx("div", { className: "str-chat__message-reactions-detail__reaction-type-list-container", children: /* @__PURE__ */ jsx("ul", { className: "str-chat__message-reactions-detail__reaction-type-list", children: reactions.map(
@@ -20104,7 +20390,9 @@ function MessageReactionsDetail({
20104
20390
  {
20105
20391
  "aria-pressed": reactionType === selectedReactionType,
20106
20392
  className: "str-chat__message-reactions-detail__reaction-type-list-item-button",
20107
- onClick: () => onSelectedReactionTypeChange?.(reactionType),
20393
+ onClick: () => onSelectedReactionTypeChange?.(
20394
+ selectedReactionType === reactionType ? null : reactionType
20395
+ ),
20108
20396
  children: [
20109
20397
  /* @__PURE__ */ jsx("span", { className: "str-chat__message-reactions-detail__reaction-type-list-item-icon", children: /* @__PURE__ */ jsx(EmojiComponent, {}) }),
20110
20398
  reactionCount > 1 && /* @__PURE__ */ jsx(
@@ -20128,22 +20416,10 @@ function MessageReactionsDetail({
20128
20416
  className: "str-chat__message-reactions-detail__user-list",
20129
20417
  "data-testid": "all-reacting-users",
20130
20418
  children: [
20131
- areReactionsLoading && /* @__PURE__ */ jsxs(Fragment, { children: [
20132
- /* @__PURE__ */ jsxs("div", { className: "str-chat__message-reactions-detail__skeleton-item", children: [
20133
- /* @__PURE__ */ jsx("div", { className: "str-chat__message-reactions-detail__skeleton-avatar" }),
20134
- /* @__PURE__ */ jsx("div", { className: "str-chat__message-reactions-detail__skeleton-line" })
20135
- ] }),
20136
- /* @__PURE__ */ jsxs("div", { className: "str-chat__message-reactions-detail__skeleton-item", children: [
20137
- /* @__PURE__ */ jsx("div", { className: "str-chat__message-reactions-detail__skeleton-avatar" }),
20138
- /* @__PURE__ */ jsx("div", { className: "str-chat__message-reactions-detail__skeleton-line" })
20139
- ] }),
20140
- /* @__PURE__ */ jsxs("div", { className: "str-chat__message-reactions-detail__skeleton-item", children: [
20141
- /* @__PURE__ */ jsx("div", { className: "str-chat__message-reactions-detail__skeleton-avatar" }),
20142
- /* @__PURE__ */ jsx("div", { className: "str-chat__message-reactions-detail__skeleton-line" })
20143
- ] })
20144
- ] }),
20145
- !areReactionsLoading && /* @__PURE__ */ jsx(Fragment, { children: reactionDetailsWithLegacyFallback.map(({ user }) => {
20419
+ areReactionsLoading && /* @__PURE__ */ jsx(LoadingIndicator2, {}),
20420
+ !areReactionsLoading && /* @__PURE__ */ jsx(Fragment, { children: reactionDetailsWithLegacyFallback.map(({ type, user }) => {
20146
20421
  const belongsToCurrentUser = client.user?.id === user?.id;
20422
+ const EmojiComponent = Array.isArray(reactionOptions) ? void 0 : reactionOptions.quick[type]?.Component ?? reactionOptions.extended?.[type]?.Component;
20147
20423
  return /* @__PURE__ */ jsxs(
20148
20424
  "div",
20149
20425
  {
@@ -20168,23 +20444,23 @@ function MessageReactionsDetail({
20168
20444
  children: belongsToCurrentUser ? t("You") : user?.name || user?.id
20169
20445
  }
20170
20446
  ),
20171
- belongsToCurrentUser && selectedReactionType && /* @__PURE__ */ jsx(
20447
+ belongsToCurrentUser && /* @__PURE__ */ jsx(
20172
20448
  "button",
20173
20449
  {
20174
20450
  className: "str-chat__message-reactions-detail__user-list-item-button",
20175
20451
  "data-testid": "remove-reaction-button",
20176
- onClick: (e) => {
20177
- contextHandleReaction(selectedReactionType, e).then(() => {
20178
- refetch();
20179
- });
20452
+ onClick: async (e) => {
20453
+ await contextHandleReaction(type, e);
20454
+ refetch();
20180
20455
  },
20181
20456
  children: t("Tap to remove")
20182
20457
  }
20183
20458
  )
20184
- ] })
20459
+ ] }),
20460
+ /* @__PURE__ */ jsx("span", { className: "str-chat__message-reactions-detail__user-list-item-icon", children: EmojiComponent && !selectedReactionType && /* @__PURE__ */ jsx(EmojiComponent, {}) })
20185
20461
  ]
20186
20462
  },
20187
- user?.id
20463
+ `${user?.id}-${type}`
20188
20464
  );
20189
20465
  }) })
20190
20466
  ]
@@ -20333,6 +20609,7 @@ const UnMemoizedMessageReactions = (props) => {
20333
20609
  const divRef = useRef(null);
20334
20610
  const dialogId2 = `message-reactions-detail-${message.id}`;
20335
20611
  const { dialog, dialogManager } = useDialogOnNearestManager({ id: dialogId2 });
20612
+ const isDialogOpen = useDialogIsOpen(dialogId2, dialogManager?.id);
20336
20613
  const handleReactionButtonClick = (reactionType) => {
20337
20614
  if (totalReactionCount > MAX_MESSAGE_REACTIONS_TO_FETCH) {
20338
20615
  return;
@@ -20367,6 +20644,8 @@ const UnMemoizedMessageReactions = (props) => {
20367
20644
  children: /* @__PURE__ */ jsxs(
20368
20645
  FragmentOrButton,
20369
20646
  {
20647
+ "aria-expanded": isDialogOpen,
20648
+ "aria-pressed": isDialogOpen,
20370
20649
  buttonIf: visualStyle === "clustered",
20371
20650
  className: "str-chat__message-reactions__list-button",
20372
20651
  onClick: () => handleReactionButtonClick(existingReactions[0]?.reactionType ?? null),
@@ -20404,9 +20683,7 @@ const UnMemoizedMessageReactions = (props) => {
20404
20683
  "button",
20405
20684
  {
20406
20685
  className: "str-chat__message-reactions__list-item-button",
20407
- onClick: () => handleReactionButtonClick(
20408
- existingReactions.at(-1)?.reactionType ?? null
20409
- ),
20686
+ onClick: () => handleReactionButtonClick(null),
20410
20687
  children: /* @__PURE__ */ jsxs("span", { className: "str-chat__message-reactions__overflow-count", children: [
20411
20688
  "+",
20412
20689
  totalReactionCount - cappedExistingReactions.reactionCountToDisplay
@@ -20531,91 +20808,74 @@ const StreamEmoji = ({
20531
20808
  const useSplitActionSet = (actionSet) => useMemo(() => {
20532
20809
  const quickActionSet = [];
20533
20810
  const dropdownActionSet = [];
20811
+ let quickDropdownToggleAction;
20534
20812
  for (const action of actionSet) {
20535
20813
  if (action.placement === "quick")
20536
20814
  quickActionSet.push(action);
20537
20815
  if (action.placement === "dropdown")
20538
20816
  dropdownActionSet.push(action);
20817
+ if (action.placement === "quick-dropdown-toggle") {
20818
+ quickDropdownToggleAction ?? (quickDropdownToggleAction = action);
20819
+ }
20539
20820
  }
20540
- return { dropdownActionSet, quickActionSet };
20821
+ return { dropdownActionSet, quickActionSet, quickDropdownToggleAction };
20541
20822
  }, [actionSet]);
20542
20823
  const MessageActions = ({
20543
20824
  disableBaseMessageActionSetFilter = false,
20544
20825
  messageActionSet = defaultMessageActionSet
20545
20826
  }) => {
20546
- const { theme } = useChatContext();
20547
20827
  const { isMyMessage, message, threadList } = useMessageContext();
20828
+ const { ContextMenu: ContextMenuComponent = ContextMenu } = useComponentContext();
20548
20829
  const { t } = useTranslationContext();
20549
20830
  const [actionsBoxButtonElement, setActionsBoxButtonElement] = useState(null);
20550
20831
  const filteredMessageActionSet = useBaseMessageActionSetFilter(
20551
20832
  messageActionSet,
20552
20833
  disableBaseMessageActionSetFilter
20553
20834
  );
20554
- const { dropdownActionSet, quickActionSet } = useSplitActionSet(
20555
- filteredMessageActionSet
20556
- );
20557
- const dropdownDialogId = `message-actions--${message.id}`;
20835
+ const { dropdownActionSet, quickActionSet, quickDropdownToggleAction } = useSplitActionSet(filteredMessageActionSet);
20836
+ const messageActionsDialogId = MessageActions.getDialogId({ messageId: message.id });
20558
20837
  const reactionSelectorDialogId = ReactionSelector.getDialogId({
20559
20838
  messageId: message.id,
20560
20839
  threadList
20561
20840
  });
20562
- const { dialog, dialogManager } = useDialogOnNearestManager({ id: dropdownDialogId });
20563
- const dropdownDialogIsOpen = useDialogIsOpen(dropdownDialogId, dialogManager?.id);
20841
+ const { dialog, dialogManager } = useDialogOnNearestManager({
20842
+ id: messageActionsDialogId
20843
+ });
20844
+ const messageActionsDialogIsOpen = useDialogIsOpen(
20845
+ messageActionsDialogId,
20846
+ dialogManager?.id
20847
+ );
20564
20848
  const reactionSelectorDialogIsOpen = useDialogIsOpen(
20565
20849
  reactionSelectorDialogId,
20566
20850
  dialogManager?.id
20567
20851
  );
20568
- const contextMenuItems = useMemo(
20569
- () => dropdownActionSet.map(({ Component: Component2 }) => {
20570
- const ActionItem = (menuProps) => /* @__PURE__ */ jsx(Component2, { ...menuProps });
20571
- return ActionItem;
20572
- }),
20573
- [dropdownActionSet]
20574
- );
20575
20852
  if (dropdownActionSet.length + quickActionSet.length === 0) {
20576
20853
  return null;
20577
20854
  }
20578
20855
  return /* @__PURE__ */ jsxs(
20579
20856
  "div",
20580
20857
  {
20581
- className: clsx(`str-chat__message-${theme}__actions str-chat__message-options`, {
20582
- "str-chat__message-options--active": dropdownDialogIsOpen || reactionSelectorDialogIsOpen
20858
+ className: clsx("str-chat__message-options", {
20859
+ "str-chat__message-options--active": messageActionsDialogIsOpen || reactionSelectorDialogIsOpen
20583
20860
  }),
20584
20861
  children: [
20585
- dropdownActionSet.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
20586
- /* @__PURE__ */ jsx(
20587
- Button,
20588
- {
20589
- appearance: "ghost",
20590
- "aria-expanded": dropdownDialogIsOpen,
20591
- "aria-haspopup": "true",
20592
- "aria-label": t("aria/Open Message Actions Menu"),
20593
- circular: true,
20594
- className: "str-chat__message-actions-box-button",
20595
- "data-testid": "message-actions-toggle-button",
20596
- onClick: () => {
20597
- dialog?.toggle();
20598
- },
20599
- ref: setActionsBoxButtonElement,
20600
- variant: "secondary",
20601
- children: /* @__PURE__ */ jsx(ActionsIcon, { className: "str-chat__message-action-icon" })
20602
- }
20603
- ),
20862
+ quickDropdownToggleAction && dropdownActionSet.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
20863
+ /* @__PURE__ */ jsx(quickDropdownToggleAction.Component, { ref: setActionsBoxButtonElement }),
20604
20864
  /* @__PURE__ */ jsx(
20605
- ContextMenu,
20865
+ ContextMenuComponent,
20606
20866
  {
20607
20867
  backLabel: t("Back"),
20608
20868
  className: clsx("str-chat__message-actions-box", {
20609
- "str-chat__message-actions-box--open": dropdownDialogIsOpen
20869
+ "str-chat__message-actions-box--open": messageActionsDialogIsOpen
20610
20870
  }),
20611
20871
  dialogManagerId: dialogManager?.id,
20612
- id: dropdownDialogId,
20613
- items: contextMenuItems,
20872
+ id: messageActionsDialogId,
20614
20873
  onClose: dialog?.close,
20615
20874
  placement: isMyMessage() ? "top-end" : "top-start",
20616
20875
  referenceElement: actionsBoxButtonElement,
20617
20876
  tabIndex: -1,
20618
- trapFocus: true
20877
+ trapFocus: true,
20878
+ children: dropdownActionSet.map(({ Component: Component2, type }) => /* @__PURE__ */ jsx(Component2, {}, type))
20619
20879
  }
20620
20880
  )
20621
20881
  ] }),
@@ -20624,6 +20884,8 @@ const MessageActions = ({
20624
20884
  }
20625
20885
  );
20626
20886
  };
20887
+ MessageActions.getDialogId = ({ messageId }) => `message-actions-${messageId}`;
20888
+ MessageActions.displayName = "MessageActions";
20627
20889
  const MessageUIWithContext = ({
20628
20890
  endOfGroup,
20629
20891
  firstOfGroup,
@@ -20787,7 +21049,7 @@ const MessageUIWithContext = ({
20787
21049
  isAIGenerated ? /* @__PURE__ */ jsx(StreamedMessageText$1, { message, renderText: renderText2 }) : /* @__PURE__ */ jsx(MessageText, { message, renderText: renderText2 })
20788
21050
  ] }),
20789
21051
  /* @__PURE__ */ jsx("div", { className: "str-chat__message-reactions-host", children: hasReactions && /* @__PURE__ */ jsx(MessageReactions$1, { reverse: true }) }),
20790
- /* @__PURE__ */ jsx(MessageErrorIcon, {})
21052
+ /* @__PURE__ */ jsx("div", { className: "str-chat__message-error-indicator", children: /* @__PURE__ */ jsx(ErrorBadge, {}) })
20791
21053
  ] })
20792
21054
  ]
20793
21055
  }
@@ -21450,6 +21712,7 @@ const useScrollLocationLogic = (params) => {
21450
21712
  const closeToBottom = useRef(false);
21451
21713
  const closeToTop = useRef(false);
21452
21714
  const previousScrollTopRef = useRef(0);
21715
+ const previousMessagesLengthRef = useRef(messages.length);
21453
21716
  const anchorRestoreCleanupRef = useRef(null);
21454
21717
  const captureAnchor = useCallback(() => {
21455
21718
  if (!listElement) return null;
@@ -21601,6 +21864,60 @@ const useScrollLocationLogic = (params) => {
21601
21864
  scrollToBottom();
21602
21865
  }
21603
21866
  }, [disableAutoScrollToBottom, justReachedLatestMessageSet, listElement, hasMoreNewer]);
21867
+ useLayoutEffect(() => {
21868
+ if (!listElement || disableAutoScrollToBottom || hasMoreNewer || suppressAutoscroll || justReachedLatestMessageSet || isRestoringOlderAnchorRef.current) {
21869
+ return;
21870
+ }
21871
+ const initialDistanceToBottom = listElement.scrollHeight - (listElement.scrollTop + listElement.clientHeight);
21872
+ const messagesHydrated = previousMessagesLengthRef.current === 0 && messages.length > 0;
21873
+ if (initialDistanceToBottom > scrolledUpThreshold && !messagesHydrated) {
21874
+ return;
21875
+ }
21876
+ let keepPinnedToBottom = true;
21877
+ const maybeScrollToBottom = () => {
21878
+ if (keepPinnedToBottom) {
21879
+ scrollToBottom();
21880
+ }
21881
+ };
21882
+ maybeScrollToBottom();
21883
+ const settleDelays = [80, messagesHydrated ? 260 : 420, 900, 1700];
21884
+ const settleTimeoutIds = settleDelays.map(
21885
+ (delay) => setTimeout(maybeScrollToBottom, delay)
21886
+ );
21887
+ const stopKeepingPinnedToBottom = () => {
21888
+ keepPinnedToBottom = false;
21889
+ };
21890
+ listElement.addEventListener("pointerdown", stopKeepingPinnedToBottom, {
21891
+ passive: true
21892
+ });
21893
+ listElement.addEventListener("touchstart", stopKeepingPinnedToBottom, {
21894
+ passive: true
21895
+ });
21896
+ listElement.addEventListener("wheel", stopKeepingPinnedToBottom, {
21897
+ passive: true
21898
+ });
21899
+ listElement.addEventListener("keydown", stopKeepingPinnedToBottom);
21900
+ const pinWindowTimeoutId = setTimeout(() => {
21901
+ stopKeepingPinnedToBottom();
21902
+ }, 2200);
21903
+ return () => {
21904
+ settleTimeoutIds.forEach(clearTimeout);
21905
+ clearTimeout(pinWindowTimeoutId);
21906
+ listElement.removeEventListener("pointerdown", stopKeepingPinnedToBottom);
21907
+ listElement.removeEventListener("touchstart", stopKeepingPinnedToBottom);
21908
+ listElement.removeEventListener("wheel", stopKeepingPinnedToBottom);
21909
+ listElement.removeEventListener("keydown", stopKeepingPinnedToBottom);
21910
+ };
21911
+ }, [
21912
+ disableAutoScrollToBottom,
21913
+ hasMoreNewer,
21914
+ justReachedLatestMessageSet,
21915
+ listElement,
21916
+ messages.length,
21917
+ scrollToBottom,
21918
+ scrolledUpThreshold,
21919
+ suppressAutoscroll
21920
+ ]);
21604
21921
  const updateScrollTop = useMessageListScrollManager({
21605
21922
  captureAnchor,
21606
21923
  disableScrollManagement: disableScrollManagement || isRestoringOlderAnchorRef.current,
@@ -21627,6 +21944,9 @@ const useScrollLocationLogic = (params) => {
21627
21944
  useLayoutEffect(() => {
21628
21945
  previousHasMoreNewerRef.current = hasMoreNewer;
21629
21946
  }, [hasMoreNewer]);
21947
+ useLayoutEffect(() => {
21948
+ previousMessagesLengthRef.current = messages.length;
21949
+ }, [messages.length]);
21630
21950
  const onScroll = useCallback(
21631
21951
  (event) => {
21632
21952
  const element = event.target;
@@ -21635,8 +21955,10 @@ const useScrollLocationLogic = (params) => {
21635
21955
  updateScrollTop(scrollTop, captureAnchor);
21636
21956
  const offsetHeight = element.offsetHeight;
21637
21957
  const scrollHeight = element.scrollHeight;
21958
+ const distanceToBottom = scrollHeight - (scrollTop + offsetHeight);
21959
+ const bottomEnterThreshold = Math.max(Math.floor(scrolledUpThreshold * 0.6), 24);
21638
21960
  const prevCloseToBottom = closeToBottom.current;
21639
- closeToBottom.current = scrollHeight - (scrollTop + offsetHeight) < scrolledUpThreshold;
21961
+ closeToBottom.current = prevCloseToBottom ? distanceToBottom < scrolledUpThreshold : distanceToBottom < bottomEnterThreshold;
21640
21962
  closeToTop.current = scrollTop < scrolledUpThreshold;
21641
21963
  if (closeToBottom.current) {
21642
21964
  setHasNewMessages(false);
@@ -22471,7 +22793,7 @@ function defaultRenderMessages({
22471
22793
  return renderedMessages;
22472
22794
  }
22473
22795
  const findReverse = (items, matches) => {
22474
- for (let i = items.length - 1; i > 0; i -= 1) {
22796
+ for (let i = items.length - 1; i >= 0; i -= 1) {
22475
22797
  if (matches(items[i])) {
22476
22798
  return items[i];
22477
22799
  }
@@ -23571,11 +23893,17 @@ const useConnectionRecoveredListener = (forceUpdate) => {
23571
23893
  const MOBILE_NAV_BREAKPOINT = 768;
23572
23894
  const useMobileNavigation = (channelListRef, navOpen, closeMobileNav) => {
23573
23895
  useEffect(() => {
23896
+ const isClickInsideChannelList = (event) => {
23897
+ const channelListElement = channelListRef.current;
23898
+ if (!channelListElement) return false;
23899
+ const eventPath = event.composedPath();
23900
+ return eventPath.includes(channelListElement);
23901
+ };
23574
23902
  const handleClickOutside = (event) => {
23575
23903
  if (typeof window !== "undefined" && window.innerWidth >= MOBILE_NAV_BREAKPOINT) {
23576
23904
  return;
23577
23905
  }
23578
- if (closeMobileNav && channelListRef.current && !channelListRef.current.contains(event.target) && navOpen) {
23906
+ if (closeMobileNav && channelListRef.current && !isClickInsideChannelList(event) && navOpen) {
23579
23907
  closeMobileNav();
23580
23908
  }
23581
23909
  };
@@ -24705,7 +25033,7 @@ const UnMemoizedLoadMoreButton = ({
24705
25033
  return /* @__PURE__ */ jsx("div", { className: "str-chat__load-more-button", children: /* @__PURE__ */ jsx(
24706
25034
  Button,
24707
25035
  {
24708
- appearance: "outline",
25036
+ appearance: "ghost",
24709
25037
  "aria-label": t("aria/Load More Channels"),
24710
25038
  "data-testid": "load-more-button",
24711
25039
  disabled: loading,
@@ -25313,6 +25641,36 @@ const useArchiveActionButtonBehavior = () => {
25313
25641
  };
25314
25642
  };
25315
25643
  const defaultChannelActionSet = [
25644
+ {
25645
+ // eslint-disable-next-line react/display-name
25646
+ Component: forwardRef((_, ref) => {
25647
+ const { channel } = useChannelListItemContext();
25648
+ const dialogId2 = ChannelListItemActionButtons.getDialogId({
25649
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
25650
+ channelId: channel.id
25651
+ });
25652
+ const { dialog, dialogManager } = useDialogOnNearestManager({ id: dialogId2 });
25653
+ const dialogIsOpen = useDialogIsOpen(dialogId2, dialogManager?.id);
25654
+ return /* @__PURE__ */ jsx(
25655
+ Button,
25656
+ {
25657
+ appearance: "ghost",
25658
+ "aria-expanded": dialogIsOpen,
25659
+ "aria-pressed": dialogIsOpen,
25660
+ circular: true,
25661
+ onClick: (e) => {
25662
+ e.stopPropagation();
25663
+ dialog.toggle();
25664
+ },
25665
+ ref,
25666
+ size: "sm",
25667
+ variant: "secondary",
25668
+ children: /* @__PURE__ */ jsx(IconDotGrid1x3Horizontal, {})
25669
+ }
25670
+ );
25671
+ }),
25672
+ placement: "quick-dropdown-toggle"
25673
+ },
25316
25674
  {
25317
25675
  Component() {
25318
25676
  const behaviorProps = useArchiveActionButtonBehavior();
@@ -25445,7 +25803,7 @@ const defaultChannelActionSet = [
25445
25803
  const membership = useChannelMembershipState(channel);
25446
25804
  const dialogId2 = ChannelListItemActionButtons.getDialogId(
25447
25805
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
25448
- channel.id
25806
+ { channelId: channel.id }
25449
25807
  );
25450
25808
  const { dialog } = useDialogOnNearestManager({ id: dialogId2 });
25451
25809
  const [inProgress, setInProgress] = useState(false);
@@ -25547,59 +25905,23 @@ const useBaseChannelActionSetFilter = (channelActionSet) => {
25547
25905
  const connectedUserIsMember = typeof membership.user !== "undefined";
25548
25906
  const ownCapabilities = channel.data?.own_capabilities;
25549
25907
  return useMemo(() => {
25550
- const filtered = channelActionSet.filter(
25551
- (action) => match({
25552
- action,
25553
- connectedUserIsMember,
25554
- isDirectMessageChannel,
25555
- memberCount,
25556
- ownCapabilities
25557
- }).returnType().with(
25558
- {
25559
- action: { connectedUserIsMember: true, placement: "quick", type: "archive" },
25560
- isDirectMessageChannel: true
25561
- },
25562
- {
25563
- action: {
25564
- connectedUserIsMember: true,
25565
- placement: "dropdown",
25566
- type: "archive"
25567
- },
25568
- isDirectMessageChannel: false
25569
- },
25570
- {
25571
- action: { placement: "dropdown", type: "mute" },
25572
- isDirectMessageChannel: true,
25573
- ownCapabilities: P.when(
25574
- (capabilities) => capabilities?.includes("mute-channel")
25575
- )
25576
- },
25577
- {
25578
- action: { placement: "quick", type: "mute" },
25579
- isDirectMessageChannel: false,
25580
- ownCapabilities: P.when(
25581
- (capabilities) => capabilities?.includes("mute-channel")
25582
- )
25583
- },
25584
- {
25585
- action: { type: "ban" },
25586
- memberCount: P.number.gt(0).and(P.number.lte(2)),
25587
- ownCapabilities: P.when(
25588
- (capabilities) => capabilities?.includes("ban-channel-members")
25589
- )
25590
- },
25591
- {
25592
- action: { type: "leave" },
25593
- ownCapabilities: P.when(
25594
- (capabilities) => capabilities?.includes("leave-channel")
25595
- )
25596
- },
25597
- {
25598
- action: { connectedUserIsMember: true, type: "pin" }
25599
- },
25600
- () => true
25601
- ).otherwise(() => false)
25602
- );
25908
+ const filtered = channelActionSet.filter((action) => {
25909
+ if (action.placement === "quick-dropdown-toggle") return true;
25910
+ switch (action.type) {
25911
+ case "archive":
25912
+ return connectedUserIsMember && (action.placement === "quick" && isDirectMessageChannel || action.placement === "dropdown" && !isDirectMessageChannel);
25913
+ case "mute":
25914
+ return ownCapabilities?.includes("mute-channel") && (action.placement === "dropdown" && isDirectMessageChannel || action.placement === "quick" && !isDirectMessageChannel);
25915
+ case "ban":
25916
+ return memberCount > 0 && memberCount <= 2 && ownCapabilities?.includes("ban-channel-members");
25917
+ case "leave":
25918
+ return ownCapabilities?.includes("leave-channel");
25919
+ case "pin":
25920
+ return connectedUserIsMember;
25921
+ default:
25922
+ return true;
25923
+ }
25924
+ });
25603
25925
  return filtered;
25604
25926
  }, [
25605
25927
  channelActionSet,
@@ -25610,17 +25932,18 @@ const useBaseChannelActionSetFilter = (channelActionSet) => {
25610
25932
  ]);
25611
25933
  };
25612
25934
  const ChannelListItemActionButtons = () => {
25935
+ const { ContextMenu: ContextMenuComponent = ContextMenu } = useComponentContext();
25613
25936
  const { channel } = useChannelListItemContext();
25614
25937
  const [referenceElement, setReferenceElement] = React.useState(null);
25615
- const dialogId2 = ChannelListItemActionButtons.getDialogId(
25938
+ const dialogId2 = ChannelListItemActionButtons.getDialogId({
25616
25939
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
25617
- channel.id
25618
- );
25940
+ channelId: channel.id
25941
+ });
25619
25942
  const { dialog, dialogManager } = useDialogOnNearestManager({ id: dialogId2 });
25620
25943
  const dialogIsOpen = useDialogIsOpen(dialogId2, dialogManager?.id);
25621
25944
  const filteredActionSet = useBaseChannelActionSetFilter(defaultChannelActionSet);
25622
- const splitActionSet = useSplitActionSet(filteredActionSet);
25623
- if (splitActionSet.quickActionSet.length + splitActionSet.dropdownActionSet.length === 0) {
25945
+ const { dropdownActionSet, quickActionSet, quickDropdownToggleAction } = useSplitActionSet(filteredActionSet);
25946
+ if (quickActionSet.length + dropdownActionSet.length === 0) {
25624
25947
  return null;
25625
25948
  }
25626
25949
  return /* @__PURE__ */ jsxs(
@@ -25630,26 +25953,10 @@ const ChannelListItemActionButtons = () => {
25630
25953
  "str-chat__channel-list-item__action-buttons--active": dialogIsOpen
25631
25954
  }),
25632
25955
  children: [
25633
- splitActionSet.dropdownActionSet.length > 0 && /* @__PURE__ */ jsx(
25634
- Button,
25635
- {
25636
- appearance: "ghost",
25637
- "aria-expanded": dialogIsOpen,
25638
- "aria-pressed": dialogIsOpen,
25639
- circular: true,
25640
- onClick: (e) => {
25641
- e.stopPropagation();
25642
- dialog.toggle();
25643
- },
25644
- ref: setReferenceElement,
25645
- size: "sm",
25646
- variant: "secondary",
25647
- children: /* @__PURE__ */ jsx(IconDotGrid1x3Horizontal, {})
25648
- }
25649
- ),
25650
- splitActionSet.quickActionSet.map(({ Component: Component2, type }) => /* @__PURE__ */ jsx(Component2, {}, type)),
25956
+ quickDropdownToggleAction && dropdownActionSet.length > 0 && /* @__PURE__ */ jsx(quickDropdownToggleAction.Component, { ref: setReferenceElement }),
25957
+ quickActionSet.map(({ Component: Component2, type }) => /* @__PURE__ */ jsx(Component2, {}, type)),
25651
25958
  /* @__PURE__ */ jsx(
25652
- ContextMenu,
25959
+ ContextMenuComponent,
25653
25960
  {
25654
25961
  className: "str-chat__channel-list-item__action-buttons-context-menu",
25655
25962
  dialogManagerId: dialogManager?.id,
@@ -25659,14 +25966,14 @@ const ChannelListItemActionButtons = () => {
25659
25966
  referenceElement,
25660
25967
  tabIndex: -1,
25661
25968
  trapFocus: true,
25662
- children: splitActionSet.dropdownActionSet.map(({ Component: Component2, type }) => /* @__PURE__ */ jsx(Component2, {}, type))
25969
+ children: dropdownActionSet.map(({ Component: Component2, type }) => /* @__PURE__ */ jsx(Component2, {}, type))
25663
25970
  }
25664
25971
  )
25665
25972
  ]
25666
25973
  }
25667
25974
  );
25668
25975
  };
25669
- ChannelListItemActionButtons.getDialogId = (channelId) => `channel-action-buttons-${channelId}`;
25976
+ ChannelListItemActionButtons.getDialogId = ({ channelId }) => `channel-action-buttons-${channelId}`;
25670
25977
  ChannelListItemActionButtons.displayName = "ChannelListItemActionButtons";
25671
25978
  function ChannelListItemTimestamp({ lastMessage }) {
25672
25979
  const { t, tDateTimeParser } = useTranslationContext("ChannelListItemTimestamp");
@@ -26605,7 +26912,8 @@ const ThreadList = ({ virtuosoProps }) => {
26605
26912
  };
26606
26913
  const IconsBySeverity = {
26607
26914
  error: IconExclamationCircle,
26608
- info: IconCircleInfoTooltip,
26915
+ info: null,
26916
+ // IconCircleInfoTooltip,
26609
26917
  loading: IconArrowRotateRightLeftRepeatRefresh,
26610
26918
  success: IconCheckmark2,
26611
26919
  warning: IconExclamationTriangle
@@ -26613,7 +26921,7 @@ const IconsBySeverity = {
26613
26921
  const DefaultNotificationIcon = ({ notification }) => {
26614
26922
  if (!notification.severity) return null;
26615
26923
  const Icon = IconsBySeverity[notification.severity] ?? null;
26616
- return Icon && /* @__PURE__ */ jsx(Icon, {});
26924
+ return Icon && /* @__PURE__ */ jsx("div", { className: "str-chat__notification-icon", children: /* @__PURE__ */ jsx(Icon, {}) });
26617
26925
  };
26618
26926
  const Notification = forwardRef(
26619
26927
  ({
@@ -26657,7 +26965,7 @@ const Notification = forwardRef(
26657
26965
  ref,
26658
26966
  children: [
26659
26967
  /* @__PURE__ */ jsxs("div", { className: "str-chat__notification-content", children: [
26660
- Icon && /* @__PURE__ */ jsx("div", { className: "str-chat__notification-icon", children: /* @__PURE__ */ jsx(Icon, { notification }) }),
26968
+ Icon && /* @__PURE__ */ jsx(Icon, { notification }),
26661
26969
  /* @__PURE__ */ jsx("div", { className: "str-chat__notification-message", children: displayMessage })
26662
26970
  ] }),
26663
26971
  notification.actions && notification.actions.length > 0 && /* @__PURE__ */ jsx("div", { className: "str-chat__notification-actions", children: notification.actions.map((action, index) => /* @__PURE__ */ jsx(
@@ -26991,7 +27299,7 @@ const VoiceRecordingPlayerUI = ({ audioPlayer }) => {
26991
27299
  secondsElapsed
26992
27300
  } = useStateStore(audioPlayer?.state, audioPlayerStateSelector) ?? {};
26993
27301
  return /* @__PURE__ */ jsxs("div", { className: rootClassName, "data-testid": "voice-recording-widget", children: [
26994
- /* @__PURE__ */ jsx(PlayButton, { isPlaying: !!isPlaying, onClick: audioPlayer.togglePlay }),
27302
+ /* @__PURE__ */ jsx("div", { className: "str-chat__message-attachment__voice-recording-widget__play-button-container", children: /* @__PURE__ */ jsx(PlayButton, { isPlaying: !!isPlaying, onClick: audioPlayer.togglePlay }) }),
26995
27303
  /* @__PURE__ */ jsx("div", { className: "str-chat__message-attachment__voice-recording-widget__metadata", children: /* @__PURE__ */ jsxs("div", { className: "str-chat__message-attachment__voice-recording-widget__audio-state", children: [
26996
27304
  /* @__PURE__ */ jsx("div", { className: "str-chat__message-attachment__voice-recording-widget__timer", children: durationSeconds ? /* @__PURE__ */ jsx(
26997
27305
  DurationDisplay,
@@ -27400,23 +27708,44 @@ const FileAttachment = ({ attachment }) => {
27400
27708
  );
27401
27709
  };
27402
27710
  const Giphy = ({ attachment }) => {
27403
- const { giphyVersion: giphyVersionName } = useChannelStateContext();
27711
+ const { giphyVersion: giphyVersionName, imageAttachmentSizeHandler } = useChannelStateContext();
27404
27712
  const { BaseImage: BaseImage$1 = BaseImage } = useComponentContext();
27405
27713
  const { t } = useTranslationContext();
27406
27714
  const usesDefaultBaseImage = BaseImage$1 === BaseImage;
27715
+ const imageElement = useRef(null);
27716
+ const [attachmentConfiguration, setAttachmentConfiguration] = useState(void 0);
27407
27717
  const imageDescriptors = useMemo(
27408
27718
  () => toGalleryItemDescriptors(attachment, { giphyVersionName }),
27409
27719
  [attachment, giphyVersionName]
27410
27720
  );
27411
- if (!imageDescriptors?.imageUrl) return null;
27412
- const { alt, dimensions, imageUrl, title } = imageDescriptors;
27721
+ const alt = imageDescriptors && imageDescriptors.alt;
27722
+ const dimensions = imageDescriptors && imageDescriptors.dimensions;
27723
+ const imageUrl = imageDescriptors && imageDescriptors.imageUrl;
27724
+ const title = imageDescriptors && imageDescriptors.title;
27725
+ const resolvedImageUrl = attachmentConfiguration?.url || imageUrl;
27726
+ const imageStyleVariables = useMemo(() => {
27727
+ const originalHeight = Number(dimensions?.height);
27728
+ const originalWidth = Number(dimensions?.width);
27729
+ return {
27730
+ "--original-height": String(originalHeight > 1 ? originalHeight : 1e6),
27731
+ "--original-width": String(originalWidth > 1 ? originalWidth : 1e6)
27732
+ };
27733
+ }, [dimensions?.height, dimensions?.width]);
27734
+ useLayoutEffect(() => {
27735
+ if (!imageElement.current || !imageAttachmentSizeHandler) return;
27736
+ const config = imageAttachmentSizeHandler(attachment, imageElement.current);
27737
+ setAttachmentConfiguration(config);
27738
+ }, [attachment, imageAttachmentSizeHandler]);
27739
+ if (!imageUrl) return null;
27413
27740
  return /* @__PURE__ */ jsxs("div", { className: clsx(`str-chat__message-attachment-giphy`), children: [
27414
27741
  /* @__PURE__ */ jsx(
27415
27742
  BaseImage$1,
27416
27743
  {
27417
27744
  alt: alt ?? title ?? t("User uploaded content"),
27418
27745
  height: dimensions?.height,
27419
- src: imageUrl,
27746
+ ref: imageElement,
27747
+ src: resolvedImageUrl,
27748
+ style: imageStyleVariables,
27420
27749
  width: dimensions?.width,
27421
27750
  ...usesDefaultBaseImage ? { showDownloadButtonOnError: false } : {}
27422
27751
  }
@@ -28016,7 +28345,6 @@ export {
28016
28345
  AIStateIndicator,
28017
28346
  AIStates,
28018
28347
  ATTACHMENT_GROUPS_ORDER,
28019
- ActionsIcon,
28020
28348
  AddCommentPrompt,
28021
28349
  Alert,
28022
28350
  AmountBar,
@@ -28029,7 +28357,7 @@ export {
28029
28357
  AttachmentWithinContainer,
28030
28358
  Audio,
28031
28359
  AudioContainer,
28032
- bp as AudioPlayer,
28360
+ bu as AudioPlayer,
28033
28361
  AudioRecorder,
28034
28362
  Avatar,
28035
28363
  AvatarStack,
@@ -28041,13 +28369,13 @@ export {
28041
28369
  Callout,
28042
28370
  Card,
28043
28371
  CardContainer,
28044
- bs as Channel,
28045
- d8 as ChannelActionContext,
28046
- d9 as ChannelActionProvider,
28372
+ bx as Channel,
28373
+ dc as ChannelActionContext,
28374
+ dd as ChannelActionProvider,
28047
28375
  ChannelAvatar,
28048
28376
  ChannelHeader,
28049
28377
  ChannelList,
28050
- ChannelListContext,
28378
+ de as ChannelListContext,
28051
28379
  ChannelListContextProvider,
28052
28380
  ChannelListItem,
28053
28381
  ChannelListItemActionButtons,
@@ -28055,27 +28383,28 @@ export {
28055
28383
  ChannelListItemUI,
28056
28384
  ChannelListUI,
28057
28385
  ChannelSearchResultItem,
28058
- da as ChannelStateContext,
28059
- db as ChannelStateProvider,
28386
+ df as ChannelStateContext,
28387
+ dg as ChannelStateProvider,
28060
28388
  Chat,
28061
- dc as ChatContext,
28389
+ dh as ChatContext,
28062
28390
  ChatProvider,
28063
- bu as ChatView,
28064
- bx as ChatViewChannelsSelectorButton,
28065
- bt as ChatViewContext,
28066
- bw as ChatViewSelectorButton,
28067
- by as ChatViewThreadsSelectorButton,
28391
+ bz as ChatView,
28392
+ bC as ChatViewChannelsSelectorButton,
28393
+ by as ChatViewContext,
28394
+ bB as ChatViewSelectorButton,
28395
+ bD as ChatViewThreadsSelectorButton,
28068
28396
  CheckSignIcon,
28069
28397
  Checkmark,
28070
28398
  CommandChip,
28071
28399
  CommandItem,
28072
28400
  ComponentContext,
28073
- dd as ComponentProvider,
28401
+ di as ComponentProvider,
28074
28402
  ConnectionStatus,
28075
28403
  ContextMenu,
28076
28404
  ContextMenuBackButton,
28077
28405
  ContextMenuBody,
28078
28406
  ContextMenuButton,
28407
+ ContextMenuContent,
28079
28408
  ContextMenuHeader,
28080
28409
  ContextMenuRoot,
28081
28410
  CooldownTimer,
@@ -28094,6 +28423,7 @@ export {
28094
28423
  EmoticonItem,
28095
28424
  EmptyStateIndicator,
28096
28425
  EndPollAlert,
28426
+ ErrorBadge,
28097
28427
  EventComponent,
28098
28428
  FILE_ICON_GRAPHIC_CLASSNAME,
28099
28429
  FILE_ICON_NO_LABEL_CLASSNAME,
@@ -28114,162 +28444,163 @@ export {
28114
28444
  GlobalModal,
28115
28445
  GroupAvatar,
28116
28446
  Header$1 as Header,
28117
- bB as IconApiAggregate,
28118
- bC as IconApples,
28447
+ bG as IconApiAggregate,
28448
+ bH as IconApples,
28119
28449
  IconArchive,
28120
28450
  IconArrowBoxLeft,
28121
28451
  IconArrowDown,
28122
28452
  IconArrowDownCircle,
28123
28453
  IconArrowLeft,
28124
- bD as IconArrowRight,
28454
+ bI as IconArrowRight,
28125
28455
  IconArrowRightUp,
28126
28456
  IconArrowRotateClockwise,
28127
28457
  IconArrowRotateRightLeftRepeatRefresh,
28128
- bE as IconArrowShareLeft,
28458
+ IconArrowShareLeft,
28129
28459
  IconArrowUp,
28130
- bF as IconArrowsRepeatLeftRight,
28131
- bG as IconAt,
28132
- bH as IconAtSolid,
28460
+ bJ as IconArrowsRepeatLeftRight,
28461
+ bK as IconAt,
28462
+ bL as IconAtSolid,
28133
28463
  IconBellNotification,
28134
28464
  IconBellOff,
28135
28465
  IconBookmark,
28136
28466
  IconBookmarkRemove,
28137
- bI as IconBrowserAISparkle,
28138
- bJ as IconBubble2ChatMessage,
28139
- bK as IconBubble2Solid,
28140
- bL as IconBubble3ChatMessage,
28141
- bM as IconBubble3Solid,
28142
- bN as IconBubbleAnnotation2ChatMessage,
28467
+ bM as IconBrowserAISparkle,
28468
+ bN as IconBubble2ChatMessage,
28469
+ bO as IconBubble2Solid,
28470
+ bP as IconBubble3ChatMessage,
28471
+ bQ as IconBubble3Solid,
28472
+ bR as IconBubbleAnnotation2ChatMessage,
28143
28473
  IconBubbleText6ChatMessage,
28144
- bP as IconBubbleText6Solid,
28145
- bO as IconBubbleText6SolidChatMessage,
28474
+ bT as IconBubbleText6Solid,
28475
+ bS as IconBubbleText6SolidChatMessage,
28146
28476
  IconBubbleWideNotificationChatMessage,
28147
- bQ as IconBubbleWideSparkleChatMessage,
28477
+ bU as IconBubbleWideSparkleChatMessage,
28148
28478
  IconBubbles,
28149
- bR as IconCalendar1,
28150
- bS as IconCallCancel,
28479
+ bV as IconCalendar1,
28480
+ bW as IconCallCancel,
28151
28481
  IconCamera1,
28152
- bT as IconCar1,
28153
- bU as IconCat,
28482
+ bX as IconCar1,
28483
+ bY as IconCat,
28154
28484
  IconChainLink,
28155
28485
  IconChart5,
28156
28486
  IconCheckmark1Small,
28157
28487
  IconCheckmark2,
28158
- bV as IconCheckmark2Small,
28488
+ bZ as IconCheckmark2Small,
28159
28489
  IconChevronDown,
28160
- bW as IconChevronGrabberVerticalSelector,
28490
+ b_ as IconChevronGrabberVerticalSelector,
28161
28491
  IconChevronLeft,
28162
28492
  IconChevronRight,
28163
- bX as IconChevronTop,
28493
+ b$ as IconChevronTop,
28164
28494
  IconCircleBanSign,
28165
- bY as IconCircleCheck,
28166
- IconCircleInfoTooltip,
28495
+ c0 as IconCircleCheck,
28496
+ c1 as IconCircleInfoTooltip,
28167
28497
  IconCircleMinus,
28168
- bZ as IconCircleQuestionmark,
28169
- b_ as IconCircleQuestionmarkFilled,
28498
+ c2 as IconCircleQuestionmark,
28499
+ c3 as IconCircleQuestionmarkFilled,
28170
28500
  IconCircleX,
28171
28501
  IconClock,
28172
- b$ as IconClockSolid,
28502
+ c4 as IconClockSolid,
28173
28503
  IconCloseQuote2,
28174
- c0 as IconCode,
28175
- c1 as IconCodeBrackets,
28176
- c2 as IconCodeEditorInsert,
28177
- c3 as IconCompass,
28178
- c4 as IconCreditCard2Billing,
28504
+ c5 as IconCode,
28505
+ c6 as IconCodeBrackets,
28506
+ c7 as IconCodeEditorInsert,
28507
+ c8 as IconCompass,
28508
+ c9 as IconCreditCard2Billing,
28179
28509
  IconCrossMedium,
28180
28510
  IconCrossSmall,
28181
28511
  IconDotGrid1x3Horizontal,
28182
28512
  IconDotGrid2x3,
28183
- c5 as IconDotsGrid1x3Vertical,
28513
+ ca as IconDotsGrid1x3Vertical,
28184
28514
  IconDoubleCheckmark1Small,
28185
28515
  IconEditBig,
28186
- c6 as IconEditBigSolid,
28187
- c7 as IconEmojiAddReaction,
28188
- c8 as IconEmojiSad,
28189
- c9 as IconEmojiSmile,
28516
+ cb as IconEditBigSolid,
28517
+ cc as IconEmojiAddReaction,
28518
+ cd as IconEmojiSad,
28519
+ IconEmojiSmile,
28520
+ IconExclamation,
28190
28521
  IconExclamationCircle,
28191
28522
  IconExclamationCircle1,
28192
28523
  IconExclamationTriangle,
28193
- ca as IconExclamationTriangle1,
28524
+ ce as IconExclamationTriangle1,
28194
28525
  IconEyeOpen,
28195
28526
  IconFileArrowLeftIn,
28196
28527
  IconFileBend,
28197
- cb as IconFilledCircleInfoTooltip,
28198
- cc as IconFilter1,
28528
+ cf as IconFilledCircleInfoTooltip,
28529
+ cg as IconFilter1,
28199
28530
  IconFlag2,
28200
- cd as IconGauge,
28531
+ ch as IconGauge,
28201
28532
  IconGiphy,
28202
- ce as IconGoogle,
28203
- cf as IconHashtagChannel,
28204
- cg as IconHeart2,
28205
- ch as IconHistory,
28533
+ ci as IconGoogle,
28534
+ cj as IconHashtagChannel,
28535
+ ck as IconHeart2,
28536
+ cl as IconHistory,
28206
28537
  IconImages1Alt,
28207
- ci as IconInvite,
28208
- cj as IconLayersBehind,
28538
+ cm as IconInvite,
28539
+ cn as IconLayersBehind,
28209
28540
  IconLayoutAlignLeft,
28210
- ck as IconLayoutGrid1,
28211
- cl as IconLightBulbSimple,
28212
- cm as IconLimits,
28213
- cn as IconLineChart3,
28541
+ co as IconLayoutGrid1,
28542
+ cp as IconLightBulbSimple,
28543
+ cq as IconLimits,
28544
+ cr as IconLineChart3,
28214
28545
  IconLoadingCircle,
28215
- co as IconLock,
28546
+ cs as IconLock,
28216
28547
  IconMagnifyingGlassSearch,
28217
28548
  IconMapPin,
28218
28549
  IconMicrophone,
28219
- cp as IconMicrophoneSolid,
28220
- cq as IconMinusLarge,
28221
- cr as IconMinusSmall,
28550
+ ct as IconMicrophoneSolid,
28551
+ cu as IconMinusLarge,
28552
+ cv as IconMinusSmall,
28222
28553
  IconMute,
28223
- cs as IconNewspaper2,
28224
- ct as IconOrganization,
28554
+ cw as IconNewspaper2,
28555
+ cx as IconOrganization,
28225
28556
  IconPaperPlane,
28226
- cu as IconPaperPlaneTopRight,
28557
+ cy as IconPaperPlaneTopRight,
28227
28558
  IconPaperclip,
28228
- cv as IconParagraphsText,
28559
+ cz as IconParagraphsText,
28229
28560
  IconPause,
28230
- cw as IconPencil,
28561
+ cA as IconPencil,
28231
28562
  IconPeople,
28232
- cx as IconPeople2,
28563
+ cB as IconPeople2,
28233
28564
  IconPeopleAdd,
28234
28565
  IconPeopleAdded,
28235
- cy as IconPeopleCircle,
28236
- cz as IconPeopleCopy,
28237
- cA as IconPeopleEditUserRights,
28566
+ cC as IconPeopleCircle,
28567
+ cD as IconPeopleCopy,
28568
+ cE as IconPeopleEditUserRights,
28238
28569
  IconPeopleRemove,
28239
- cB as IconPersona,
28570
+ cF as IconPersona,
28240
28571
  IconPin,
28241
28572
  IconPlaySolid,
28242
28573
  IconPlusLarge,
28243
28574
  IconPlusSmall,
28244
28575
  IconRunShortcut,
28245
- cC as IconSearchText,
28246
- cD as IconSettingsGear2,
28247
- cE as IconSettingsSliderVer,
28248
- cF as IconShapesPlusCloseSquareCircle,
28249
- cG as IconShapesTriangleSquareCircle,
28250
- cH as IconShareRedirectLink,
28251
- cI as IconShield,
28576
+ cG as IconSearchText,
28577
+ cH as IconSettingsGear2,
28578
+ cI as IconSettingsSliderVer,
28579
+ cJ as IconShapesPlusCloseSquareCircle,
28580
+ cK as IconShapesTriangleSquareCircle,
28581
+ cL as IconShareRedirectLink,
28582
+ cM as IconShield,
28252
28583
  IconSquareBehindSquare2_Copy,
28253
- cJ as IconSquareCircleTopRightFeeds,
28254
- cK as IconStop,
28255
- cL as IconTable,
28256
- cM as IconTeam,
28257
- cN as IconTennis,
28258
- cO as IconTextToImageURLEnrichment,
28584
+ cN as IconSquareCircleTopRightFeeds,
28585
+ cO as IconStop,
28586
+ cP as IconTable,
28587
+ cQ as IconTeam,
28588
+ cR as IconTennis,
28589
+ cS as IconTextToImageURLEnrichment,
28259
28590
  IconThunder,
28260
28591
  IconTranslate,
28261
28592
  IconTrashBin,
28262
- cP as IconTrending4,
28593
+ cT as IconTrending4,
28263
28594
  IconTrophy,
28264
- cQ as IconUnlocked,
28595
+ cU as IconUnlocked,
28265
28596
  IconUnpin,
28266
- cR as IconUsers,
28597
+ cV as IconUsers,
28267
28598
  IconVideo,
28268
28599
  IconVideoSolid,
28269
- cS as IconVoiceAndVideo,
28270
- cT as IconVoiceHigh,
28600
+ cW as IconVoiceAndVideo,
28601
+ cX as IconVoiceHigh,
28271
28602
  IconVolumeFull,
28272
- cU as IconWebhook,
28603
+ cY as IconWebhook,
28273
28604
  ImageComponent,
28274
28605
  ImageContainer,
28275
28606
  ImagePlaceholder,
@@ -28278,9 +28609,9 @@ export {
28278
28609
  LinkPreviewList,
28279
28610
  LoadMoreButton,
28280
28611
  LoadMorePaginator,
28281
- cV as LoadingChannel,
28612
+ cZ as LoadingChannel,
28282
28613
  LoadingChannels,
28283
- cW as LoadingErrorIndicator,
28614
+ c_ as LoadingErrorIndicator,
28284
28615
  LoadingIndicator,
28285
28616
  LoadingIndicatorIcon,
28286
28617
  MAX_MESSAGE_REACTIONS_TO_FETCH,
@@ -28295,20 +28626,20 @@ export {
28295
28626
  MessageBouncePrompt,
28296
28627
  MessageBounceProvider,
28297
28628
  MessageComposer,
28298
- de as MessageComposerContext,
28629
+ dj as MessageComposerContext,
28299
28630
  MessageComposerContextProvider,
28300
28631
  MessageComposerUI,
28301
28632
  MessageContext,
28302
28633
  MessageDeletedBubble,
28303
28634
  MessageDeliveryStatus,
28304
28635
  MessageEditedIndicator,
28305
- MessageErrorIcon,
28306
28636
  MessageList,
28307
28637
  MessageListContext,
28308
28638
  MessageListContextProvider,
28309
28639
  MessageProvider,
28310
28640
  MessageReactions,
28311
28641
  MessageReactionsDetail,
28642
+ MessageReactionsDetailLoadingIndicator,
28312
28643
  MessageRepliesCountButton,
28313
28644
  MessageSearchResultItem,
28314
28645
  MessageStatus,
@@ -28328,10 +28659,9 @@ export {
28328
28659
  NotificationList,
28329
28660
  NotificationTranslationTopic,
28330
28661
  NumericInput,
28331
- cX as OPTIONAL_MESSAGE_ACTIONS,
28662
+ c$ as OPTIONAL_MESSAGE_ACTIONS,
28332
28663
  OtherFilesContainer,
28333
28664
  PauseIcon,
28334
- PinIcon,
28335
28665
  PinIndicator,
28336
28666
  PlayButton,
28337
28667
  PlayIcon,
@@ -28358,7 +28688,6 @@ export {
28358
28688
  QuotedMessagePreview,
28359
28689
  QuotedMessagePreviewUI,
28360
28690
  QuotedVoiceRecording,
28361
- ReactionIcon,
28362
28691
  ReactionSelector,
28363
28692
  RecordingPermission,
28364
28693
  RecordingPermissionDeniedNotification,
@@ -28397,23 +28726,22 @@ export {
28397
28726
  TextInputFieldSet,
28398
28727
  TextareaComposer,
28399
28728
  Thread,
28400
- d6 as ThreadContext,
28729
+ da as ThreadContext,
28401
28730
  ThreadHeader,
28402
- ThreadIcon,
28403
28731
  ThreadList,
28404
28732
  ThreadListItem,
28405
28733
  ThreadListItemUI,
28406
- d7 as ThreadProvider,
28734
+ db as ThreadProvider,
28407
28735
  ThreadStart,
28408
28736
  Tooltip,
28409
28737
  TranslationBuilder,
28410
- df as TranslationContext,
28738
+ dk as TranslationContext,
28411
28739
  TranslationProvider,
28412
28740
  TranslationTopic,
28413
- dg as TypingContext,
28741
+ dl as TypingContext,
28414
28742
  TypingIndicator,
28415
28743
  TypingIndicatorHeader,
28416
- dh as TypingProvider,
28744
+ dm as TypingProvider,
28417
28745
  UNREAD_MESSAGE_SEPARATOR_CLASS,
28418
28746
  UnMemoizedLoadMorePaginator,
28419
28747
  UnreadMessagesNotification,
@@ -28433,31 +28761,31 @@ export {
28433
28761
  VoiceRecordingPlayer,
28434
28762
  WaveProgressBar,
28435
28763
  Window,
28436
- bm as WithAudioPlayback,
28764
+ br as WithAudioPlayback,
28437
28765
  WithComponents,
28438
28766
  WithDragAndDropUpload,
28439
28767
  addNotificationTargetTag,
28440
28768
  areMessagePropsEqual,
28441
28769
  areMessageUIPropsEqual,
28442
28770
  countEmojis,
28443
- bA as createIcon,
28771
+ bF as createIcon,
28444
28772
  deTranslations,
28445
28773
  defaultAllowedTagNames,
28446
28774
  defaultAttachmentActionsDefaultFocus,
28447
28775
  defaultAttachmentSelectorActionSet,
28448
- bz as defaultChatViewSelectorItemSet,
28776
+ bE as defaultChatViewSelectorItemSet,
28449
28777
  defaultComponents,
28450
28778
  defaultDateTimeParser,
28451
28779
  defaultMessageActionSet,
28452
28780
  defaultPinPermissions,
28453
28781
  defaultReactionOptions,
28454
- bn as defaultRegisterAudioPlayerError,
28782
+ bs as defaultRegisterAudioPlayerError,
28455
28783
  defaultRenderMessages,
28456
28784
  defaultTranslatorFunction,
28457
28785
  displayDuration,
28458
28786
  divMod,
28459
28787
  downSample,
28460
- bo as elementIsPlaying,
28788
+ bt as elementIsPlaying,
28461
28789
  emojiMarkdownPlugin,
28462
28790
  emojiToUnicode,
28463
28791
  enTranslations,
@@ -28471,21 +28799,21 @@ export {
28471
28799
  getCssDimensionsVariables,
28472
28800
  getGroupChannelDisplayInfo,
28473
28801
  getGroupStyles,
28474
- cY as getImages,
28802
+ d0 as getImages,
28475
28803
  getIsFirstUnreadMessage,
28476
28804
  getLastReceived,
28477
28805
  getLatestMessagePreview,
28478
28806
  getMessageActions,
28479
- cZ as getNonImageAttachments,
28480
- d3 as getNotificationTargetPanel,
28481
- d4 as getNotificationTargetTag,
28807
+ d1 as getNonImageAttachments,
28808
+ d7 as getNotificationTargetPanel,
28809
+ d8 as getNotificationTargetTag,
28482
28810
  getReadByTooltipText,
28483
28811
  getTextareaCaretRect,
28484
28812
  getTranslatedMessageText,
28485
28813
  getWholeChar,
28486
28814
  handleActionWarning,
28487
- d0 as hasMoreMessagesProbably,
28488
- d1 as hasNotMoreMessages,
28815
+ d4 as hasMoreMessagesProbably,
28816
+ d5 as hasNotMoreMessages,
28489
28817
  hiTranslations,
28490
28818
  htmlToTextPlugin,
28491
28819
  imageToLink,
@@ -28495,7 +28823,7 @@ export {
28495
28823
  isChrome,
28496
28824
  isDate,
28497
28825
  isDateSeparatorMessage,
28498
- di as isDayOrMoment,
28826
+ dn as isDayOrMoment,
28499
28827
  isFirefox,
28500
28828
  isGalleryAttachmentType,
28501
28829
  isIntroMessage,
@@ -28505,9 +28833,10 @@ export {
28505
28833
  isMessageBounced,
28506
28834
  isMessageEdited,
28507
28835
  isMessageErrorRetryable,
28836
+ isNetworkSendFailure,
28508
28837
  isNotificationForPanel,
28509
- d2 as isNotificationTargetPanel,
28510
- dj as isNumberOrString,
28838
+ d6 as isNotificationTargetPanel,
28839
+ dp as isNumberOrString,
28511
28840
  isSafari,
28512
28841
  isSvgAttachment,
28513
28842
  isUserMuted,
@@ -28515,8 +28844,8 @@ export {
28515
28844
  jaTranslations,
28516
28845
  keepLineBreaksPlugin,
28517
28846
  koTranslations,
28518
- c$ as makeDateMessageId,
28519
- c_ as makeIntroMessage,
28847
+ d3 as makeDateMessageId,
28848
+ d2 as makeIntroMessage,
28520
28849
  mapEmojiMartData,
28521
28850
  mapToUserNameOrId,
28522
28851
  markDownRenderers,
@@ -28553,8 +28882,8 @@ export {
28553
28882
  upSample,
28554
28883
  useAIState,
28555
28884
  useActionHandler,
28556
- bl as useActiveAudioPlayer,
28557
- bv as useActiveThread,
28885
+ bq as useActiveAudioPlayer,
28886
+ bA as useActiveThread,
28558
28887
  useAttachmentManagerState,
28559
28888
  useAttachmentsForPreview,
28560
28889
  useAudioPlayer,
@@ -28563,13 +28892,13 @@ export {
28563
28892
  useChannelActionContext,
28564
28893
  useChannelDeletedListener,
28565
28894
  useChannelDisplayName,
28566
- bq as useChannelEditMessageHandler,
28895
+ bv as useChannelEditMessageHandler,
28567
28896
  useChannelHiddenListener,
28568
28897
  useChannelListContext,
28569
28898
  useChannelListItemContext,
28570
28899
  useChannelMembersState,
28571
28900
  useChannelMembershipState,
28572
- br as useChannelMentionsHandler,
28901
+ bw as useChannelMentionsHandler,
28573
28902
  useChannelPreviewInfo,
28574
28903
  useChannelStateContext,
28575
28904
  useChannelTruncatedListener,
@@ -28594,7 +28923,7 @@ export {
28594
28923
  useFlagHandler,
28595
28924
  useGalleryContext,
28596
28925
  useLastReadData,
28597
- d5 as useLegacyThreadContext,
28926
+ d9 as useLegacyThreadContext,
28598
28927
  useLiveLocationSharingManager,
28599
28928
  useMarkUnreadHandler,
28600
28929
  useMentionsHandler,