stream-chat-react 14.0.0-beta.2 → 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 (69) hide show
  1. package/dist/{WithAudioPlayback-myzUS2m6.js → cjs/WithAudioPlayback.4a84360f.js} +4 -9
  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 +620 -225
  7. package/dist/cjs/index.js.map +1 -1
  8. package/dist/cjs/mp3-encoder.js +1 -1
  9. package/dist/css/index.css +181 -26
  10. package/dist/css/index.css.map +1 -1
  11. package/dist/{WithAudioPlayback-C1hfFIcu.mjs → es/WithAudioPlayback.a3d5a2fc.mjs} +14 -19
  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 +632 -237
  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/BaseImage/toBaseImageDescriptors.d.ts +1 -1
  23. package/dist/types/components/BaseImage/toBaseImageDescriptors.d.ts.map +1 -1
  24. package/dist/types/components/ChannelList/hooks/useMobileNavigation.d.ts.map +1 -1
  25. package/dist/types/components/ChannelListItem/ChannelListItemActionButtons.d.ts +3 -1
  26. package/dist/types/components/ChannelListItem/ChannelListItemActionButtons.d.ts.map +1 -1
  27. package/dist/types/components/ChannelListItem/ChannelListItemActionButtons.defaults.d.ts +6 -3
  28. package/dist/types/components/ChannelListItem/ChannelListItemActionButtons.defaults.d.ts.map +1 -1
  29. package/dist/types/components/Chat/hooks/useSplitActionSet.d.ts +5 -0
  30. package/dist/types/components/Chat/hooks/useSplitActionSet.d.ts.map +1 -1
  31. package/dist/types/components/Dialog/components/ContextMenu.d.ts +119 -3
  32. package/dist/types/components/Dialog/components/ContextMenu.d.ts.map +1 -1
  33. package/dist/types/components/Dialog/hooks/useDialog.d.ts +1 -1
  34. package/dist/types/components/Dialog/hooks/useDialog.d.ts.map +1 -1
  35. package/dist/types/components/Dialog/service/DialogAnchor.d.ts +14 -1
  36. package/dist/types/components/Dialog/service/DialogAnchor.d.ts.map +1 -1
  37. package/dist/types/components/Dialog/service/DialogManager.d.ts +14 -3
  38. package/dist/types/components/Dialog/service/DialogManager.d.ts.map +1 -1
  39. package/dist/types/components/Dialog/service/DialogPortal.d.ts.map +1 -1
  40. package/dist/types/components/Gallery/GalleryContext.d.ts +1 -1
  41. package/dist/types/components/Gallery/GalleryContext.d.ts.map +1 -1
  42. package/dist/types/components/MessageActions/MessageActions.d.ts +14 -3
  43. package/dist/types/components/MessageActions/MessageActions.d.ts.map +1 -1
  44. package/dist/types/components/MessageActions/MessageActions.defaults.d.ts +1 -1
  45. package/dist/types/components/MessageActions/MessageActions.defaults.d.ts.map +1 -1
  46. package/dist/types/components/MessageActions/QuickMessageActionButton.d.ts.map +1 -1
  47. package/dist/types/components/MessageActions/hooks/useBaseMessageActionSetFilter.d.ts.map +1 -1
  48. package/dist/types/components/MessageComposer/AttachmentSelector/AttachmentSelector.d.ts.map +1 -1
  49. package/dist/types/components/MessageList/hooks/MessageList/useScrollLocationLogic.d.ts +18 -0
  50. package/dist/types/components/MessageList/hooks/MessageList/useScrollLocationLogic.d.ts.map +1 -1
  51. package/dist/types/components/Poll/PollActions/PollResults/PollOptionWithVotes.d.ts.map +1 -1
  52. package/dist/types/components/Reactions/MessageReactions.d.ts.map +1 -1
  53. package/dist/types/components/Reactions/MessageReactionsDetail.d.ts +1 -0
  54. package/dist/types/components/Reactions/MessageReactionsDetail.d.ts.map +1 -1
  55. package/dist/types/components/Reactions/ReactionSelector.d.ts +1 -1
  56. package/dist/types/components/Reactions/ReactionSelector.d.ts.map +1 -1
  57. package/dist/types/components/TextareaComposer/SuggestionList/SuggestionList.d.ts.map +1 -1
  58. package/dist/types/components/TextareaComposer/TextareaComposer.d.ts.map +1 -1
  59. package/dist/types/context/ChannelListContext.d.ts +1 -1
  60. package/dist/types/context/ChannelListContext.d.ts.map +1 -1
  61. package/dist/types/context/ComponentContext.d.ts +5 -1
  62. package/dist/types/context/ComponentContext.d.ts.map +1 -1
  63. package/dist/types/context/DialogManagerContext.d.ts +11 -7
  64. package/dist/types/context/DialogManagerContext.d.ts.map +1 -1
  65. package/package.json +3 -1
  66. package/dist/WithAudioPlayback-C1hfFIcu.mjs.map +0 -1
  67. package/dist/WithAudioPlayback-myzUS2m6.js.map +0 -1
  68. package/dist/audioProcessing-BbOs2wMd.js.map +0 -1
  69. package/dist/audioProcessing-ByEVSjGG.mjs.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, useCallback, useLayoutEffect, useMemo, useContext, createContext, 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, 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 IconPeopleAdded, aI as IconBookmarkRemove, aJ as IconBellOff, aK as IconBubbleWideNotificationChatMessage, aL as IconEditBig, aM as IconSquareBehindSquare2_Copy, aN as IconUnpin, aO as IconCloseQuote2, aP as IconBubbleText6ChatMessage, aQ as IconDotGrid1x3Horizontal, 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-C1hfFIcu.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-C1hfFIcu.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";
@@ -72,10 +71,11 @@ const useAIState = (channel) => {
72
71
  return { aiState };
73
72
  };
74
73
  class DialogManager {
75
- constructor({ id } = {}) {
74
+ constructor({ closeOnClickOutside = true, id } = {}) {
76
75
  this.state = new StateStore({
77
76
  dialogsById: {}
78
77
  });
78
+ this.closeOnClickOutside = closeOnClickOutside;
79
79
  this.id = id ?? nanoid();
80
80
  }
81
81
  get openDialogCount() {
@@ -90,13 +90,14 @@ class DialogManager {
90
90
  get(id) {
91
91
  return this.state.getLatestValue().dialogsById[id];
92
92
  }
93
- getOrCreate({ id }) {
93
+ getOrCreate({ closeOnClickOutside, id }) {
94
94
  let dialog = this.state.getLatestValue().dialogsById[id];
95
95
  if (!dialog) {
96
96
  dialog = {
97
97
  close: () => {
98
98
  this.close(id);
99
99
  },
100
+ closeOnClickOutside,
100
101
  id,
101
102
  isOpen: false,
102
103
  open: () => {
@@ -112,21 +113,22 @@ class DialogManager {
112
113
  };
113
114
  this.state.next((current) => ({
114
115
  ...current,
115
- ...{ dialogsById: { ...current.dialogsById, [id]: dialog } }
116
+ dialogsById: { ...current.dialogsById, [id]: dialog }
116
117
  }));
117
118
  }
118
- if (dialog.removalTimeout) {
119
- 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
+ };
120
127
  this.state.next((current) => ({
121
128
  ...current,
122
- ...{
123
- dialogsById: {
124
- ...current.dialogsById,
125
- [id]: {
126
- ...dialog,
127
- removalTimeout: void 0
128
- }
129
- }
129
+ dialogsById: {
130
+ ...current.dialogsById,
131
+ [id]: dialog
130
132
  }
131
133
  }));
132
134
  }
@@ -203,7 +205,11 @@ class DialogManager {
203
205
  }));
204
206
  }
205
207
  }
206
- const useDialog = ({ dialogManagerId, id }) => {
208
+ const useDialog = ({
209
+ closeOnClickOutside,
210
+ dialogManagerId,
211
+ id
212
+ }) => {
207
213
  const { dialogManager } = useDialogManager({ dialogManagerId });
208
214
  useEffect(
209
215
  () => () => {
@@ -211,7 +217,7 @@ const useDialog = ({ dialogManagerId, id }) => {
211
217
  },
212
218
  [dialogManager, id]
213
219
  );
214
- return dialogManager.getOrCreate({ id });
220
+ return dialogManager.getOrCreate({ closeOnClickOutside, id });
215
221
  };
216
222
  const useDialogOnNearestManager = ({ id }) => {
217
223
  const { dialogManager } = useNearestDialogManagerContext() ?? {};
@@ -258,9 +264,37 @@ const Portal = ({
258
264
  if (!portalDestination) return null;
259
265
  return createPortal(children, portalDestination);
260
266
  };
267
+ const shouldCloseOnOutsideClick = ({
268
+ dialog,
269
+ managerCloseOnClickOutside
270
+ }) => dialog.closeOnClickOutside ?? managerCloseOnClickOutside;
261
271
  const DialogPortalDestination = () => {
262
272
  const { dialogManager } = useNearestDialogManagerContext() ?? {};
263
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]);
264
298
  if (!openedDialogCount) return null;
265
299
  return /* @__PURE__ */ jsx(
266
300
  "div",
@@ -268,7 +302,22 @@ const DialogPortalDestination = () => {
268
302
  className: "str-chat__dialog-overlay",
269
303
  "data-str-chat__portal-id": dialogManager?.id,
270
304
  "data-testid": "str-chat__dialog-overlay",
271
- 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,
272
321
  style: {
273
322
  "--str-chat__dialog-overlay-height": openedDialogCount > 0 ? "100%" : "0"
274
323
  }
@@ -290,11 +339,16 @@ const DialogPortalEntry = ({
290
339
  };
291
340
  const dialogManagersRegistry = new StateStore({});
292
341
  const getDialogManager = (id) => dialogManagersRegistry.getLatestValue()[id];
293
- const getOrCreateDialogManager = (id) => {
342
+ const getOrCreateDialogManager = ({
343
+ closeOnClickOutside,
344
+ id
345
+ }) => {
294
346
  let manager = getDialogManager(id);
295
347
  if (!manager) {
296
- manager = new DialogManager({ id });
348
+ manager = new DialogManager({ closeOnClickOutside, id });
297
349
  dialogManagersRegistry.partialNext({ [id]: manager });
350
+ } else if (typeof closeOnClickOutside === "boolean") {
351
+ manager.closeOnClickOutside = closeOnClickOutside;
298
352
  }
299
353
  return manager;
300
354
  };
@@ -305,20 +359,21 @@ const removeDialogManager = (id) => {
305
359
  const DialogManagerProviderContext = React.createContext(void 0);
306
360
  const DialogManagerProvider = ({
307
361
  children,
362
+ closeOnClickOutside,
308
363
  id
309
364
  }) => {
310
365
  const [dialogManager, setDialogManager] = useState(() => {
311
366
  if (id) return getDialogManager(id) ?? null;
312
- return new DialogManager();
367
+ return new DialogManager({ closeOnClickOutside });
313
368
  });
314
369
  useEffect(() => {
315
370
  if (!id) return;
316
- setDialogManager(getOrCreateDialogManager(id));
371
+ setDialogManager(getOrCreateDialogManager({ closeOnClickOutside, id }));
317
372
  return () => {
318
373
  removeDialogManager(id);
319
374
  setDialogManager(null);
320
375
  };
321
- }, [id]);
376
+ }, [closeOnClickOutside, id]);
322
377
  if (!dialogManager) return null;
323
378
  return /* @__PURE__ */ jsxs(DialogManagerProviderContext.Provider, { value: { dialogManager }, children: [
324
379
  children,
@@ -9037,6 +9092,7 @@ function useDialogAnchor({
9037
9092
  setPopperElement(null);
9038
9093
  }
9039
9094
  return {
9095
+ placement: stabilisedChosenPlacement ?? chosenPlacement,
9040
9096
  setPopperElement,
9041
9097
  styles: {
9042
9098
  left: x ?? 0,
@@ -9049,6 +9105,8 @@ const DialogAnchor = ({
9049
9105
  allowFlip = true,
9050
9106
  children,
9051
9107
  className,
9108
+ closeOnClickOutside,
9109
+ closeTransitionMs = 0,
9052
9110
  dialogManagerId,
9053
9111
  focus = true,
9054
9112
  id,
@@ -9061,12 +9119,44 @@ const DialogAnchor = ({
9061
9119
  updatePositionOnContentResize,
9062
9120
  ...restDivProps
9063
9121
  }) => {
9064
- const dialog = useDialog({ dialogManagerId, id });
9122
+ const dialog = useDialog({ closeOnClickOutside, dialogManagerId, id });
9065
9123
  const open = useDialogIsOpen(id, dialogManagerId);
9066
- 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({
9067
9157
  allowFlip,
9068
9158
  offset,
9069
- open,
9159
+ open: shouldRender,
9070
9160
  placement,
9071
9161
  referenceElement,
9072
9162
  updateKey,
@@ -9083,7 +9173,7 @@ const DialogAnchor = ({
9083
9173
  document.removeEventListener("keyup", hideOnEscape);
9084
9174
  };
9085
9175
  }, [dialog, open]);
9086
- if (!open) {
9176
+ if (!shouldRender) {
9087
9177
  return null;
9088
9178
  }
9089
9179
  return /* @__PURE__ */ jsx(DialogPortalEntry, { dialogId: id, dialogManagerId, children: /* @__PURE__ */ jsx(FocusScope, { autoFocus: focus, contain: trapFocus, restoreFocus: true, children: /* @__PURE__ */ jsx(
@@ -9091,6 +9181,8 @@ const DialogAnchor = ({
9091
9181
  {
9092
9182
  ...restDivProps,
9093
9183
  className: clsx("str-chat__dialog-contents", className),
9184
+ "data-str-chat-dialog-state": isClosing ? "closing" : "open",
9185
+ "data-str-chat-placement": chosenPlacement,
9094
9186
  "data-testid": "str-chat__dialog-contents",
9095
9187
  ref: setPopperElement,
9096
9188
  style: styles,
@@ -9433,16 +9525,152 @@ const EmojiContextMenuButton = ({
9433
9525
  ]
9434
9526
  }
9435
9527
  );
9436
- const ContextMenuButton = ({
9437
- onBlur,
9438
- onFocus,
9439
- ...props
9528
+ const ContextMenuButtonWithSubmenu = ({
9529
+ children,
9530
+ className,
9531
+ Submenu,
9532
+ submenuContainerProps,
9533
+ submenuPlacement = "right-start",
9534
+ submenuRollAxis = "x",
9535
+ ...buttonProps
9440
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;
9441
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;
9442
9670
  return /* @__PURE__ */ jsx(
9443
9671
  BaseContextMenuButton,
9444
9672
  {
9445
- ...props,
9673
+ ...baseButtonProps,
9446
9674
  "aria-selected": isFocused ? "true" : "false",
9447
9675
  onBlur: (e) => {
9448
9676
  setIsFocused(false);
@@ -9491,12 +9719,14 @@ function ContextMenuContent({
9491
9719
  backLabel = "Back",
9492
9720
  children,
9493
9721
  className,
9722
+ enableAnimations = true,
9494
9723
  Header: Header2,
9495
9724
  items,
9496
9725
  ItemsWrapper,
9497
9726
  menuClassName,
9498
9727
  onClose,
9499
9728
  onMenuLevelChange,
9729
+ transitionDirection,
9500
9730
  ...props
9501
9731
  }) {
9502
9732
  const rootLevel = useMemo(
@@ -9509,6 +9739,7 @@ function ContextMenuContent({
9509
9739
  [Header2, items, ItemsWrapper, menuClassName]
9510
9740
  );
9511
9741
  const [menuStack, setMenuStack] = useState(() => [rootLevel]);
9742
+ const [menuBodyAnimationKey, setMenuBodyAnimationKey] = useState(0);
9512
9743
  const activeMenu = menuStack[menuStack.length - 1];
9513
9744
  const ActiveMenuItemsWrapper = activeMenu.ItemsWrapper ?? React.Fragment;
9514
9745
  const closeMenu = useCallback(() => {
@@ -9532,10 +9763,9 @@ function ContextMenuContent({
9532
9763
  [ItemsWrapper]
9533
9764
  );
9534
9765
  const returnToParentMenu = useCallback(() => {
9535
- setMenuStack(
9536
- (current) => current.length > 1 ? current.slice(0, current.length - 1) : current
9537
- );
9538
- }, []);
9766
+ if (menuStack.length <= 1) return;
9767
+ setMenuStack((current) => current.slice(0, current.length - 1));
9768
+ }, [menuStack.length]);
9539
9769
  useEffect(() => {
9540
9770
  setMenuStack((current) => {
9541
9771
  if (current.length === 1 && current[0] === rootLevel) return current;
@@ -9545,42 +9775,116 @@ function ContextMenuContent({
9545
9775
  useEffect(() => {
9546
9776
  onMenuLevelChange?.(menuStack.length);
9547
9777
  }, [menuStack.length, onMenuLevelChange]);
9548
- return /* @__PURE__ */ jsx(ContextMenuContext.Provider, { value: { closeMenu, openSubmenu, returnToParentMenu }, children: /* @__PURE__ */ jsxs(ContextMenuRoot, { className: clsx(className, activeMenu.menuClassName), ...props, children: [
9549
- activeMenu.Header ? /* @__PURE__ */ jsx(activeMenu.Header, {}) : menuStack.length > 1 ? /* @__PURE__ */ jsx(ContextMenuHeader, { children: /* @__PURE__ */ jsxs(ContextMenuBackButton, { onClick: returnToParentMenu, children: [
9550
- /* @__PURE__ */ jsx(IconChevronLeft, {}),
9551
- /* @__PURE__ */ jsx("span", { children: backLabel })
9552
- ] }) }) : null,
9553
- /* @__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}`)) }) })
9554
- ] }) });
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
+ ) });
9555
9807
  }
9556
9808
  const ContextMenu = (props) => {
9809
+ const { ContextMenuContent: ContextMenuContentComponent = ContextMenuContent } = useComponentContext();
9557
9810
  const {
9558
9811
  allowFlip,
9812
+ closeOnClickOutside,
9813
+ closeTransitionMs = 130,
9559
9814
  dialogManagerId,
9560
9815
  focus,
9561
9816
  id,
9562
9817
  placement,
9563
9818
  referenceElement,
9819
+ submenuTransitionDurationMs,
9564
9820
  tabIndex,
9565
9821
  trapFocus,
9566
9822
  ...menuProps
9567
9823
  } = props;
9824
+ const resolvedSubmenuTransitionDurationMs = submenuTransitionDurationMs ?? 460;
9568
9825
  const isAnchored = id != null;
9569
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);
9570
9831
  const open = useDialogIsOpen(id ?? "", dialogManagerId);
9832
+ const previousOpenRef = useRef(open);
9571
9833
  useEffect(() => {
9572
- 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;
9573
9846
  }, [isAnchored, open]);
9574
- const content = /* @__PURE__ */ jsx(
9575
- 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,
9576
9877
  {
9577
9878
  ...menuProps,
9578
- onMenuLevelChange: isAnchored ? setMenuLevel : menuProps.onMenuLevelChange
9879
+ key: `context-menu-content-${contentResetToken}`,
9880
+ onMenuLevelChange: handleMenuLevelChange,
9881
+ transitionDirection
9579
9882
  }
9580
9883
  );
9581
9884
  if (isAnchored) {
9582
9885
  const {
9583
9886
  backLabel: _b,
9887
+ enableAnimations: _ea,
9584
9888
  Header: _h,
9585
9889
  items: _i,
9586
9890
  ItemsWrapper: _w,
@@ -9593,6 +9897,8 @@ const ContextMenu = (props) => {
9593
9897
  DialogAnchor,
9594
9898
  {
9595
9899
  allowFlip,
9900
+ closeOnClickOutside,
9901
+ closeTransitionMs,
9596
9902
  dialogManagerId,
9597
9903
  focus,
9598
9904
  id,
@@ -9930,7 +10236,9 @@ const useBaseMessageActionSetFilter = (messageActionSet, disable = false) => {
9930
10236
  if (isBounced || isInitialMessage || // not sure whether this thing even works anymore
9931
10237
  !message.type || message.type === "system" || message.type === "ephemeral" || message.status === "sending")
9932
10238
  return [];
9933
- return messageActionSet.filter(({ type }) => {
10239
+ return messageActionSet.filter((action) => {
10240
+ if (action.placement === "quick-dropdown-toggle") return true;
10241
+ const type = action.type;
9934
10242
  if (ACTIONS_NOT_WORKING_IN_THREAD.includes(type) && isMessageThreadReply)
9935
10243
  return false;
9936
10244
  if (message.error) {
@@ -10277,14 +10585,14 @@ const matchMarkdownLinks = (message) => {
10277
10585
  const regexMdLinks = /\[([^[]+)\](\(.*\))/gm;
10278
10586
  const matches = message.match(regexMdLinks);
10279
10587
  const singleMatch = /\[([^[]+)\]\((.*)\)/;
10280
- const links = matches ? matches.map((match2) => {
10281
- const i = singleMatch.exec(match2);
10588
+ const links = matches ? matches.map((match) => {
10589
+ const i = singleMatch.exec(match);
10282
10590
  return i && [i[1], i[2]];
10283
10591
  }) : [];
10284
10592
  return links.flat();
10285
10593
  };
10286
10594
  const emojiMarkdownPlugin = () => {
10287
- const replace = (match2) => u("element", { properties: {}, tagName: "emoji" }, [u("text", match2)]);
10595
+ const replace = (match) => u("element", { properties: {}, tagName: "emoji" }, [u("text", match)]);
10288
10596
  const transform2 = (node) => findAndReplace(node, [emojiRegex(), replace]);
10289
10597
  return transform2;
10290
10598
  };
@@ -10294,13 +10602,13 @@ const mentionsMarkdownPlugin = (mentioned_users) => () => {
10294
10602
  mentioned_usernames.map((username) => `@${username}`).join("|"),
10295
10603
  "g"
10296
10604
  );
10297
- const replace = (match2) => {
10298
- const usernameOrId = match2.replace("@", "");
10605
+ const replace = (match) => {
10606
+ const usernameOrId = match.replace("@", "");
10299
10607
  const user = mentioned_users.find(
10300
10608
  ({ id, name }) => name === usernameOrId || id === usernameOrId
10301
10609
  );
10302
10610
  return u("element", { mentionedUser: user, properties: {}, tagName: "mention" }, [
10303
- u("text", match2)
10611
+ u("text", match)
10304
10612
  ]);
10305
10613
  };
10306
10614
  const transform2 = (tree) => {
@@ -10405,12 +10713,12 @@ const plusPlusToEmphasis = () => {
10405
10713
  if (node.type !== "text" || parent == null || typeof index !== "number") return;
10406
10714
  const value = node.value;
10407
10715
  INS_REGEX.lastIndex = 0;
10408
- let match2;
10716
+ let match;
10409
10717
  let last = 0;
10410
10718
  const out = [];
10411
- while (match2 = INS_REGEX.exec(value)) {
10412
- const [full, inner] = match2;
10413
- const start = match2.index;
10719
+ while (match = INS_REGEX.exec(value)) {
10720
+ const [full, inner] = match;
10721
+ const start = match.index;
10414
10722
  if (start > last) out.push({ type: "text", value: value.slice(last, start) });
10415
10723
  out.push({
10416
10724
  children: [{ type: "text", value: inner }],
@@ -11838,6 +12146,7 @@ const toBaseImageDescriptors = (attachment, options = {}) => {
11838
12146
  title: attachment.title
11839
12147
  };
11840
12148
  }
12149
+ return void 0;
11841
12150
  };
11842
12151
  const BASE_FILE_ICON_CLASSNAME = "str-chat__file-icon";
11843
12152
  const FILE_ICON_GRAPHIC_CLASSNAME = "str-chat__file-icon__graphic";
@@ -14675,7 +14984,8 @@ const PollOptionWithVotes = ({
14675
14984
  "div",
14676
14985
  {
14677
14986
  className: clsx("str-chat__poll-option", {
14678
- "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
14679
14989
  }),
14680
14990
  children: [
14681
14991
  /* @__PURE__ */ jsx(PollOptionWithVotesHeader, { option, optionOrderNumber: orderNumber }),
@@ -15688,7 +15998,7 @@ const AttachmentSelector = ({
15688
15998
  getModalPortalDestination
15689
15999
  }) => {
15690
16000
  const { t } = useTranslationContext();
15691
- const { Modal = GlobalModal } = useComponentContext();
16001
+ const { ContextMenu: ContextMenuComponent = ContextMenu, Modal = GlobalModal } = useComponentContext();
15692
16002
  const { channelCapabilities } = useChannelStateContext();
15693
16003
  const messageComposer = useMessageComposerController();
15694
16004
  const isCooldownActive = useIsCooldownActive();
@@ -15748,7 +16058,7 @@ const AttachmentSelector = ({
15748
16058
  }
15749
16059
  ),
15750
16060
  /* @__PURE__ */ jsx(
15751
- ContextMenu,
16061
+ ContextMenuComponent,
15752
16062
  {
15753
16063
  allowFlip: true,
15754
16064
  backLabel: t("Back"),
@@ -17778,7 +18088,10 @@ const SuggestionList = ({
17778
18088
  setFocusedItemIndex,
17779
18089
  suggestionItemComponents = defaultComponents
17780
18090
  }) => {
17781
- const { AutocompleteSuggestionItem = SuggestionListItem } = useComponentContext();
18091
+ const {
18092
+ AutocompleteSuggestionItem = SuggestionListItem,
18093
+ ContextMenu: ContextMenuComponent = ContextMenu
18094
+ } = useComponentContext();
17782
18095
  const { textareaRef } = useMessageComposerContext();
17783
18096
  const messageComposer = useMessageComposerController();
17784
18097
  const { textComposer } = messageComposer;
@@ -17896,7 +18209,7 @@ const SuggestionList = ({
17896
18209
  zIndex: 1e3
17897
18210
  },
17898
18211
  children: /* @__PURE__ */ jsx(
17899
- ContextMenu,
18212
+ ContextMenuComponent,
17900
18213
  {
17901
18214
  className: clsx("str-chat__suggestion-list", className),
17902
18215
  Header: suggestions.searchSource.type === "commands" ? CommandsMenuHeader : void 0,
@@ -18122,12 +18435,6 @@ const TextareaComposer = ({
18122
18435
  if (!textareaRef.current || textareaIsFocused || !focus) return;
18123
18436
  textareaRef.current.focus();
18124
18437
  }, [attachments, focus, quotedMessage, textareaRef]);
18125
- useEffect(
18126
- () => () => {
18127
- messageComposer.clear();
18128
- },
18129
- [messageComposer]
18130
- );
18131
18438
  useLayoutEffect(() => {
18132
18439
  const textarea = textareaRef.current;
18133
18440
  if (!textarea || isComposing) return;
@@ -18581,7 +18888,7 @@ const MessageComposerProvider = (props) => {
18581
18888
  const messageComposer = useMessageComposerController();
18582
18889
  useEffect(
18583
18890
  () => () => {
18584
- messageComposer.createDraft();
18891
+ messageComposer.createDraft().finally(() => messageComposer.clear());
18585
18892
  },
18586
18893
  [messageComposer]
18587
18894
  );
@@ -18899,7 +19206,7 @@ const useChat = ({
18899
19206
  };
18900
19207
  useEffect(() => {
18901
19208
  if (!client) return;
18902
- const version = "14.0.0-beta.2";
19209
+ const version = "14.0.0-beta.3";
18903
19210
  const userAgent = client.getUserAgent();
18904
19211
  if (!userAgent.includes("stream-chat-react")) {
18905
19212
  client.setUserAgent(`stream-chat-react-${version}-${userAgent}`);
@@ -19400,10 +19707,10 @@ const ReactionSelector = (props) => {
19400
19707
  ) })
19401
19708
  ] });
19402
19709
  };
19403
- ReactionSelector.getDialogId = (({ messageId, threadList }) => {
19710
+ ReactionSelector.getDialogId = ({ messageId, threadList }) => {
19404
19711
  const dialogIdNamespace = threadList ? "-thread" : "";
19405
- return `reaction-selector${dialogIdNamespace}--${messageId}`;
19406
- });
19712
+ return `reaction-selector${dialogIdNamespace}-${messageId}`;
19713
+ };
19407
19714
  ReactionSelector.displayName = "ReactionSelector";
19408
19715
  const ReactionSelectorWithButton = ({
19409
19716
  ReactionIcon
@@ -19488,6 +19795,7 @@ const QuickMessageActionsButton = ({ className, ...props }) => /* @__PURE__ */ j
19488
19795
  appearance: "ghost",
19489
19796
  circular: true,
19490
19797
  className: clsx("str-chat__message-actions-box-button", className),
19798
+ size: "sm",
19491
19799
  variant: "secondary",
19492
19800
  ...props
19493
19801
  }
@@ -19848,6 +20156,32 @@ const DefaultMessageActionComponents = {
19848
20156
  }
19849
20157
  },
19850
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
+ }),
19851
20185
  React() {
19852
20186
  return /* @__PURE__ */ jsx(ReactionSelectorWithButton, { ReactionIcon: IconEmojiSmile });
19853
20187
  },
@@ -19868,7 +20202,10 @@ const DefaultMessageActionComponents = {
19868
20202
  }
19869
20203
  };
19870
20204
  const defaultMessageActionSet = [
19871
- // { placement: 'dropdown', type: 'block' },
20205
+ {
20206
+ Component: DefaultMessageActionComponents.quick.DropdownToggle,
20207
+ placement: "quick-dropdown-toggle"
20208
+ },
19872
20209
  {
19873
20210
  Component: DefaultMessageActionComponents.quick.Reply,
19874
20211
  placement: "quick",
@@ -19958,14 +20295,14 @@ function useFetchReactions(options) {
19958
20295
  const handleFetchReactions = propHandleFetchReactions ?? contextHandleFetchReactions;
19959
20296
  const [refetchNonce, setRefetchNonce] = useState(null);
19960
20297
  useEffect(() => {
19961
- if (!shouldFetch || !reactionType) {
20298
+ if (!shouldFetch) {
19962
20299
  return;
19963
20300
  }
19964
20301
  let cancel = false;
19965
20302
  (async () => {
19966
20303
  try {
19967
20304
  setIsLoading(true);
19968
- const reactions2 = await handleFetchReactions(reactionType, sort);
20305
+ const reactions2 = await handleFetchReactions(reactionType ?? void 0, sort);
19969
20306
  if (!cancel) {
19970
20307
  setReactions(reactions2);
19971
20308
  }
@@ -19989,6 +20326,16 @@ function useFetchReactions(options) {
19989
20326
  return { isLoading, reactions, refetch };
19990
20327
  }
19991
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
+ };
19992
20339
  function MessageReactionsDetail({
19993
20340
  handleFetchReactions,
19994
20341
  onSelectedReactionTypeChange,
@@ -19999,7 +20346,11 @@ function MessageReactionsDetail({
19999
20346
  totalReactionCount
20000
20347
  }) {
20001
20348
  const { client } = useChatContext();
20002
- 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);
20003
20354
  const { t } = useTranslationContext();
20004
20355
  const {
20005
20356
  handleReaction: contextHandleReaction,
@@ -20026,7 +20377,7 @@ function MessageReactionsDetail({
20026
20377
  "div",
20027
20378
  {
20028
20379
  className: "str-chat__message-reactions-detail",
20029
- "data-testid": "reactions-list-modal",
20380
+ "data-testid": "message-reactions-detail",
20030
20381
  children: [
20031
20382
  typeof totalReactionCount === "number" && /* @__PURE__ */ jsx("div", { className: "str-chat__message-reactions-detail__total-count", children: t("{{ count }} reactions", { count: totalReactionCount }) }),
20032
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(
@@ -20039,7 +20390,9 @@ function MessageReactionsDetail({
20039
20390
  {
20040
20391
  "aria-pressed": reactionType === selectedReactionType,
20041
20392
  className: "str-chat__message-reactions-detail__reaction-type-list-item-button",
20042
- onClick: () => onSelectedReactionTypeChange?.(reactionType),
20393
+ onClick: () => onSelectedReactionTypeChange?.(
20394
+ selectedReactionType === reactionType ? null : reactionType
20395
+ ),
20043
20396
  children: [
20044
20397
  /* @__PURE__ */ jsx("span", { className: "str-chat__message-reactions-detail__reaction-type-list-item-icon", children: /* @__PURE__ */ jsx(EmojiComponent, {}) }),
20045
20398
  reactionCount > 1 && /* @__PURE__ */ jsx(
@@ -20063,22 +20416,10 @@ function MessageReactionsDetail({
20063
20416
  className: "str-chat__message-reactions-detail__user-list",
20064
20417
  "data-testid": "all-reacting-users",
20065
20418
  children: [
20066
- areReactionsLoading && /* @__PURE__ */ jsxs(Fragment, { children: [
20067
- /* @__PURE__ */ jsxs("div", { className: "str-chat__message-reactions-detail__skeleton-item", children: [
20068
- /* @__PURE__ */ jsx("div", { className: "str-chat__message-reactions-detail__skeleton-avatar" }),
20069
- /* @__PURE__ */ jsx("div", { className: "str-chat__message-reactions-detail__skeleton-line" })
20070
- ] }),
20071
- /* @__PURE__ */ jsxs("div", { className: "str-chat__message-reactions-detail__skeleton-item", children: [
20072
- /* @__PURE__ */ jsx("div", { className: "str-chat__message-reactions-detail__skeleton-avatar" }),
20073
- /* @__PURE__ */ jsx("div", { className: "str-chat__message-reactions-detail__skeleton-line" })
20074
- ] }),
20075
- /* @__PURE__ */ jsxs("div", { className: "str-chat__message-reactions-detail__skeleton-item", children: [
20076
- /* @__PURE__ */ jsx("div", { className: "str-chat__message-reactions-detail__skeleton-avatar" }),
20077
- /* @__PURE__ */ jsx("div", { className: "str-chat__message-reactions-detail__skeleton-line" })
20078
- ] })
20079
- ] }),
20080
- !areReactionsLoading && /* @__PURE__ */ jsx(Fragment, { children: reactionDetailsWithLegacyFallback.map(({ user }) => {
20419
+ areReactionsLoading && /* @__PURE__ */ jsx(LoadingIndicator2, {}),
20420
+ !areReactionsLoading && /* @__PURE__ */ jsx(Fragment, { children: reactionDetailsWithLegacyFallback.map(({ type, user }) => {
20081
20421
  const belongsToCurrentUser = client.user?.id === user?.id;
20422
+ const EmojiComponent = Array.isArray(reactionOptions) ? void 0 : reactionOptions.quick[type]?.Component ?? reactionOptions.extended?.[type]?.Component;
20082
20423
  return /* @__PURE__ */ jsxs(
20083
20424
  "div",
20084
20425
  {
@@ -20103,23 +20444,23 @@ function MessageReactionsDetail({
20103
20444
  children: belongsToCurrentUser ? t("You") : user?.name || user?.id
20104
20445
  }
20105
20446
  ),
20106
- belongsToCurrentUser && selectedReactionType && /* @__PURE__ */ jsx(
20447
+ belongsToCurrentUser && /* @__PURE__ */ jsx(
20107
20448
  "button",
20108
20449
  {
20109
20450
  className: "str-chat__message-reactions-detail__user-list-item-button",
20110
20451
  "data-testid": "remove-reaction-button",
20111
- onClick: (e) => {
20112
- contextHandleReaction(selectedReactionType, e).then(() => {
20113
- refetch();
20114
- });
20452
+ onClick: async (e) => {
20453
+ await contextHandleReaction(type, e);
20454
+ refetch();
20115
20455
  },
20116
20456
  children: t("Tap to remove")
20117
20457
  }
20118
20458
  )
20119
- ] })
20459
+ ] }),
20460
+ /* @__PURE__ */ jsx("span", { className: "str-chat__message-reactions-detail__user-list-item-icon", children: EmojiComponent && !selectedReactionType && /* @__PURE__ */ jsx(EmojiComponent, {}) })
20120
20461
  ]
20121
20462
  },
20122
- user?.id
20463
+ `${user?.id}-${type}`
20123
20464
  );
20124
20465
  }) })
20125
20466
  ]
@@ -20268,6 +20609,7 @@ const UnMemoizedMessageReactions = (props) => {
20268
20609
  const divRef = useRef(null);
20269
20610
  const dialogId2 = `message-reactions-detail-${message.id}`;
20270
20611
  const { dialog, dialogManager } = useDialogOnNearestManager({ id: dialogId2 });
20612
+ const isDialogOpen = useDialogIsOpen(dialogId2, dialogManager?.id);
20271
20613
  const handleReactionButtonClick = (reactionType) => {
20272
20614
  if (totalReactionCount > MAX_MESSAGE_REACTIONS_TO_FETCH) {
20273
20615
  return;
@@ -20302,6 +20644,8 @@ const UnMemoizedMessageReactions = (props) => {
20302
20644
  children: /* @__PURE__ */ jsxs(
20303
20645
  FragmentOrButton,
20304
20646
  {
20647
+ "aria-expanded": isDialogOpen,
20648
+ "aria-pressed": isDialogOpen,
20305
20649
  buttonIf: visualStyle === "clustered",
20306
20650
  className: "str-chat__message-reactions__list-button",
20307
20651
  onClick: () => handleReactionButtonClick(existingReactions[0]?.reactionType ?? null),
@@ -20339,9 +20683,7 @@ const UnMemoizedMessageReactions = (props) => {
20339
20683
  "button",
20340
20684
  {
20341
20685
  className: "str-chat__message-reactions__list-item-button",
20342
- onClick: () => handleReactionButtonClick(
20343
- existingReactions.at(-1)?.reactionType ?? null
20344
- ),
20686
+ onClick: () => handleReactionButtonClick(null),
20345
20687
  children: /* @__PURE__ */ jsxs("span", { className: "str-chat__message-reactions__overflow-count", children: [
20346
20688
  "+",
20347
20689
  totalReactionCount - cappedExistingReactions.reactionCountToDisplay
@@ -20466,91 +20808,74 @@ const StreamEmoji = ({
20466
20808
  const useSplitActionSet = (actionSet) => useMemo(() => {
20467
20809
  const quickActionSet = [];
20468
20810
  const dropdownActionSet = [];
20811
+ let quickDropdownToggleAction;
20469
20812
  for (const action of actionSet) {
20470
20813
  if (action.placement === "quick")
20471
20814
  quickActionSet.push(action);
20472
20815
  if (action.placement === "dropdown")
20473
20816
  dropdownActionSet.push(action);
20817
+ if (action.placement === "quick-dropdown-toggle") {
20818
+ quickDropdownToggleAction ?? (quickDropdownToggleAction = action);
20819
+ }
20474
20820
  }
20475
- return { dropdownActionSet, quickActionSet };
20821
+ return { dropdownActionSet, quickActionSet, quickDropdownToggleAction };
20476
20822
  }, [actionSet]);
20477
20823
  const MessageActions = ({
20478
20824
  disableBaseMessageActionSetFilter = false,
20479
20825
  messageActionSet = defaultMessageActionSet
20480
20826
  }) => {
20481
- const { theme } = useChatContext();
20482
20827
  const { isMyMessage, message, threadList } = useMessageContext();
20828
+ const { ContextMenu: ContextMenuComponent = ContextMenu } = useComponentContext();
20483
20829
  const { t } = useTranslationContext();
20484
20830
  const [actionsBoxButtonElement, setActionsBoxButtonElement] = useState(null);
20485
20831
  const filteredMessageActionSet = useBaseMessageActionSetFilter(
20486
20832
  messageActionSet,
20487
20833
  disableBaseMessageActionSetFilter
20488
20834
  );
20489
- const { dropdownActionSet, quickActionSet } = useSplitActionSet(
20490
- filteredMessageActionSet
20491
- );
20492
- const dropdownDialogId = `message-actions--${message.id}`;
20835
+ const { dropdownActionSet, quickActionSet, quickDropdownToggleAction } = useSplitActionSet(filteredMessageActionSet);
20836
+ const messageActionsDialogId = MessageActions.getDialogId({ messageId: message.id });
20493
20837
  const reactionSelectorDialogId = ReactionSelector.getDialogId({
20494
20838
  messageId: message.id,
20495
20839
  threadList
20496
20840
  });
20497
- const { dialog, dialogManager } = useDialogOnNearestManager({ id: dropdownDialogId });
20498
- 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
+ );
20499
20848
  const reactionSelectorDialogIsOpen = useDialogIsOpen(
20500
20849
  reactionSelectorDialogId,
20501
20850
  dialogManager?.id
20502
20851
  );
20503
- const contextMenuItems = useMemo(
20504
- () => dropdownActionSet.map(({ Component: Component2 }) => {
20505
- const ActionItem = (menuProps) => /* @__PURE__ */ jsx(Component2, { ...menuProps });
20506
- return ActionItem;
20507
- }),
20508
- [dropdownActionSet]
20509
- );
20510
20852
  if (dropdownActionSet.length + quickActionSet.length === 0) {
20511
20853
  return null;
20512
20854
  }
20513
20855
  return /* @__PURE__ */ jsxs(
20514
20856
  "div",
20515
20857
  {
20516
- className: clsx(`str-chat__message-${theme}__actions str-chat__message-options`, {
20517
- "str-chat__message-options--active": dropdownDialogIsOpen || reactionSelectorDialogIsOpen
20858
+ className: clsx("str-chat__message-options", {
20859
+ "str-chat__message-options--active": messageActionsDialogIsOpen || reactionSelectorDialogIsOpen
20518
20860
  }),
20519
20861
  children: [
20520
- dropdownActionSet.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
20521
- /* @__PURE__ */ jsx(
20522
- Button,
20523
- {
20524
- appearance: "ghost",
20525
- "aria-expanded": dropdownDialogIsOpen,
20526
- "aria-haspopup": "true",
20527
- "aria-label": t("aria/Open Message Actions Menu"),
20528
- circular: true,
20529
- className: "str-chat__message-actions-box-button",
20530
- "data-testid": "message-actions-toggle-button",
20531
- onClick: () => {
20532
- dialog?.toggle();
20533
- },
20534
- ref: setActionsBoxButtonElement,
20535
- variant: "secondary",
20536
- children: /* @__PURE__ */ jsx(IconDotGrid1x3Horizontal, { className: "str-chat__message-action-icon" })
20537
- }
20538
- ),
20862
+ quickDropdownToggleAction && dropdownActionSet.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
20863
+ /* @__PURE__ */ jsx(quickDropdownToggleAction.Component, { ref: setActionsBoxButtonElement }),
20539
20864
  /* @__PURE__ */ jsx(
20540
- ContextMenu,
20865
+ ContextMenuComponent,
20541
20866
  {
20542
20867
  backLabel: t("Back"),
20543
20868
  className: clsx("str-chat__message-actions-box", {
20544
- "str-chat__message-actions-box--open": dropdownDialogIsOpen
20869
+ "str-chat__message-actions-box--open": messageActionsDialogIsOpen
20545
20870
  }),
20546
20871
  dialogManagerId: dialogManager?.id,
20547
- id: dropdownDialogId,
20548
- items: contextMenuItems,
20872
+ id: messageActionsDialogId,
20549
20873
  onClose: dialog?.close,
20550
20874
  placement: isMyMessage() ? "top-end" : "top-start",
20551
20875
  referenceElement: actionsBoxButtonElement,
20552
20876
  tabIndex: -1,
20553
- trapFocus: true
20877
+ trapFocus: true,
20878
+ children: dropdownActionSet.map(({ Component: Component2, type }) => /* @__PURE__ */ jsx(Component2, {}, type))
20554
20879
  }
20555
20880
  )
20556
20881
  ] }),
@@ -20559,6 +20884,8 @@ const MessageActions = ({
20559
20884
  }
20560
20885
  );
20561
20886
  };
20887
+ MessageActions.getDialogId = ({ messageId }) => `message-actions-${messageId}`;
20888
+ MessageActions.displayName = "MessageActions";
20562
20889
  const MessageUIWithContext = ({
20563
20890
  endOfGroup,
20564
20891
  firstOfGroup,
@@ -21385,6 +21712,7 @@ const useScrollLocationLogic = (params) => {
21385
21712
  const closeToBottom = useRef(false);
21386
21713
  const closeToTop = useRef(false);
21387
21714
  const previousScrollTopRef = useRef(0);
21715
+ const previousMessagesLengthRef = useRef(messages.length);
21388
21716
  const anchorRestoreCleanupRef = useRef(null);
21389
21717
  const captureAnchor = useCallback(() => {
21390
21718
  if (!listElement) return null;
@@ -21536,6 +21864,60 @@ const useScrollLocationLogic = (params) => {
21536
21864
  scrollToBottom();
21537
21865
  }
21538
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
+ ]);
21539
21921
  const updateScrollTop = useMessageListScrollManager({
21540
21922
  captureAnchor,
21541
21923
  disableScrollManagement: disableScrollManagement || isRestoringOlderAnchorRef.current,
@@ -21562,6 +21944,9 @@ const useScrollLocationLogic = (params) => {
21562
21944
  useLayoutEffect(() => {
21563
21945
  previousHasMoreNewerRef.current = hasMoreNewer;
21564
21946
  }, [hasMoreNewer]);
21947
+ useLayoutEffect(() => {
21948
+ previousMessagesLengthRef.current = messages.length;
21949
+ }, [messages.length]);
21565
21950
  const onScroll = useCallback(
21566
21951
  (event) => {
21567
21952
  const element = event.target;
@@ -21570,8 +21955,10 @@ const useScrollLocationLogic = (params) => {
21570
21955
  updateScrollTop(scrollTop, captureAnchor);
21571
21956
  const offsetHeight = element.offsetHeight;
21572
21957
  const scrollHeight = element.scrollHeight;
21958
+ const distanceToBottom = scrollHeight - (scrollTop + offsetHeight);
21959
+ const bottomEnterThreshold = Math.max(Math.floor(scrolledUpThreshold * 0.6), 24);
21573
21960
  const prevCloseToBottom = closeToBottom.current;
21574
- closeToBottom.current = scrollHeight - (scrollTop + offsetHeight) < scrolledUpThreshold;
21961
+ closeToBottom.current = prevCloseToBottom ? distanceToBottom < scrolledUpThreshold : distanceToBottom < bottomEnterThreshold;
21575
21962
  closeToTop.current = scrollTop < scrolledUpThreshold;
21576
21963
  if (closeToBottom.current) {
21577
21964
  setHasNewMessages(false);
@@ -23506,11 +23893,17 @@ const useConnectionRecoveredListener = (forceUpdate) => {
23506
23893
  const MOBILE_NAV_BREAKPOINT = 768;
23507
23894
  const useMobileNavigation = (channelListRef, navOpen, closeMobileNav) => {
23508
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
+ };
23509
23902
  const handleClickOutside = (event) => {
23510
23903
  if (typeof window !== "undefined" && window.innerWidth >= MOBILE_NAV_BREAKPOINT) {
23511
23904
  return;
23512
23905
  }
23513
- if (closeMobileNav && channelListRef.current && !channelListRef.current.contains(event.target) && navOpen) {
23906
+ if (closeMobileNav && channelListRef.current && !isClickInsideChannelList(event) && navOpen) {
23514
23907
  closeMobileNav();
23515
23908
  }
23516
23909
  };
@@ -24640,7 +25033,7 @@ const UnMemoizedLoadMoreButton = ({
24640
25033
  return /* @__PURE__ */ jsx("div", { className: "str-chat__load-more-button", children: /* @__PURE__ */ jsx(
24641
25034
  Button,
24642
25035
  {
24643
- appearance: "outline",
25036
+ appearance: "ghost",
24644
25037
  "aria-label": t("aria/Load More Channels"),
24645
25038
  "data-testid": "load-more-button",
24646
25039
  disabled: loading,
@@ -25248,6 +25641,36 @@ const useArchiveActionButtonBehavior = () => {
25248
25641
  };
25249
25642
  };
25250
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
+ },
25251
25674
  {
25252
25675
  Component() {
25253
25676
  const behaviorProps = useArchiveActionButtonBehavior();
@@ -25380,7 +25803,7 @@ const defaultChannelActionSet = [
25380
25803
  const membership = useChannelMembershipState(channel);
25381
25804
  const dialogId2 = ChannelListItemActionButtons.getDialogId(
25382
25805
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
25383
- channel.id
25806
+ { channelId: channel.id }
25384
25807
  );
25385
25808
  const { dialog } = useDialogOnNearestManager({ id: dialogId2 });
25386
25809
  const [inProgress, setInProgress] = useState(false);
@@ -25482,59 +25905,23 @@ const useBaseChannelActionSetFilter = (channelActionSet) => {
25482
25905
  const connectedUserIsMember = typeof membership.user !== "undefined";
25483
25906
  const ownCapabilities = channel.data?.own_capabilities;
25484
25907
  return useMemo(() => {
25485
- const filtered = channelActionSet.filter(
25486
- (action) => match({
25487
- action,
25488
- connectedUserIsMember,
25489
- isDirectMessageChannel,
25490
- memberCount,
25491
- ownCapabilities
25492
- }).returnType().with(
25493
- {
25494
- action: { connectedUserIsMember: true, placement: "quick", type: "archive" },
25495
- isDirectMessageChannel: true
25496
- },
25497
- {
25498
- action: {
25499
- connectedUserIsMember: true,
25500
- placement: "dropdown",
25501
- type: "archive"
25502
- },
25503
- isDirectMessageChannel: false
25504
- },
25505
- {
25506
- action: { placement: "dropdown", type: "mute" },
25507
- isDirectMessageChannel: true,
25508
- ownCapabilities: P.when(
25509
- (capabilities) => capabilities?.includes("mute-channel")
25510
- )
25511
- },
25512
- {
25513
- action: { placement: "quick", type: "mute" },
25514
- isDirectMessageChannel: false,
25515
- ownCapabilities: P.when(
25516
- (capabilities) => capabilities?.includes("mute-channel")
25517
- )
25518
- },
25519
- {
25520
- action: { type: "ban" },
25521
- memberCount: P.number.gt(0).and(P.number.lte(2)),
25522
- ownCapabilities: P.when(
25523
- (capabilities) => capabilities?.includes("ban-channel-members")
25524
- )
25525
- },
25526
- {
25527
- action: { type: "leave" },
25528
- ownCapabilities: P.when(
25529
- (capabilities) => capabilities?.includes("leave-channel")
25530
- )
25531
- },
25532
- {
25533
- action: { connectedUserIsMember: true, type: "pin" }
25534
- },
25535
- () => true
25536
- ).otherwise(() => false)
25537
- );
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
+ });
25538
25925
  return filtered;
25539
25926
  }, [
25540
25927
  channelActionSet,
@@ -25545,17 +25932,18 @@ const useBaseChannelActionSetFilter = (channelActionSet) => {
25545
25932
  ]);
25546
25933
  };
25547
25934
  const ChannelListItemActionButtons = () => {
25935
+ const { ContextMenu: ContextMenuComponent = ContextMenu } = useComponentContext();
25548
25936
  const { channel } = useChannelListItemContext();
25549
25937
  const [referenceElement, setReferenceElement] = React.useState(null);
25550
- const dialogId2 = ChannelListItemActionButtons.getDialogId(
25938
+ const dialogId2 = ChannelListItemActionButtons.getDialogId({
25551
25939
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
25552
- channel.id
25553
- );
25940
+ channelId: channel.id
25941
+ });
25554
25942
  const { dialog, dialogManager } = useDialogOnNearestManager({ id: dialogId2 });
25555
25943
  const dialogIsOpen = useDialogIsOpen(dialogId2, dialogManager?.id);
25556
25944
  const filteredActionSet = useBaseChannelActionSetFilter(defaultChannelActionSet);
25557
- const splitActionSet = useSplitActionSet(filteredActionSet);
25558
- if (splitActionSet.quickActionSet.length + splitActionSet.dropdownActionSet.length === 0) {
25945
+ const { dropdownActionSet, quickActionSet, quickDropdownToggleAction } = useSplitActionSet(filteredActionSet);
25946
+ if (quickActionSet.length + dropdownActionSet.length === 0) {
25559
25947
  return null;
25560
25948
  }
25561
25949
  return /* @__PURE__ */ jsxs(
@@ -25565,26 +25953,10 @@ const ChannelListItemActionButtons = () => {
25565
25953
  "str-chat__channel-list-item__action-buttons--active": dialogIsOpen
25566
25954
  }),
25567
25955
  children: [
25568
- splitActionSet.dropdownActionSet.length > 0 && /* @__PURE__ */ jsx(
25569
- Button,
25570
- {
25571
- appearance: "ghost",
25572
- "aria-expanded": dialogIsOpen,
25573
- "aria-pressed": dialogIsOpen,
25574
- circular: true,
25575
- onClick: (e) => {
25576
- e.stopPropagation();
25577
- dialog.toggle();
25578
- },
25579
- ref: setReferenceElement,
25580
- size: "sm",
25581
- variant: "secondary",
25582
- children: /* @__PURE__ */ jsx(IconDotGrid1x3Horizontal, {})
25583
- }
25584
- ),
25585
- 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)),
25586
25958
  /* @__PURE__ */ jsx(
25587
- ContextMenu,
25959
+ ContextMenuComponent,
25588
25960
  {
25589
25961
  className: "str-chat__channel-list-item__action-buttons-context-menu",
25590
25962
  dialogManagerId: dialogManager?.id,
@@ -25594,14 +25966,14 @@ const ChannelListItemActionButtons = () => {
25594
25966
  referenceElement,
25595
25967
  tabIndex: -1,
25596
25968
  trapFocus: true,
25597
- children: splitActionSet.dropdownActionSet.map(({ Component: Component2, type }) => /* @__PURE__ */ jsx(Component2, {}, type))
25969
+ children: dropdownActionSet.map(({ Component: Component2, type }) => /* @__PURE__ */ jsx(Component2, {}, type))
25598
25970
  }
25599
25971
  )
25600
25972
  ]
25601
25973
  }
25602
25974
  );
25603
25975
  };
25604
- ChannelListItemActionButtons.getDialogId = (channelId) => `channel-action-buttons-${channelId}`;
25976
+ ChannelListItemActionButtons.getDialogId = ({ channelId }) => `channel-action-buttons-${channelId}`;
25605
25977
  ChannelListItemActionButtons.displayName = "ChannelListItemActionButtons";
25606
25978
  function ChannelListItemTimestamp({ lastMessage }) {
25607
25979
  const { t, tDateTimeParser } = useTranslationContext("ChannelListItemTimestamp");
@@ -26927,7 +27299,7 @@ const VoiceRecordingPlayerUI = ({ audioPlayer }) => {
26927
27299
  secondsElapsed
26928
27300
  } = useStateStore(audioPlayer?.state, audioPlayerStateSelector) ?? {};
26929
27301
  return /* @__PURE__ */ jsxs("div", { className: rootClassName, "data-testid": "voice-recording-widget", children: [
26930
- /* @__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 }) }),
26931
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: [
26932
27304
  /* @__PURE__ */ jsx("div", { className: "str-chat__message-attachment__voice-recording-widget__timer", children: durationSeconds ? /* @__PURE__ */ jsx(
26933
27305
  DurationDisplay,
@@ -27336,23 +27708,44 @@ const FileAttachment = ({ attachment }) => {
27336
27708
  );
27337
27709
  };
27338
27710
  const Giphy = ({ attachment }) => {
27339
- const { giphyVersion: giphyVersionName } = useChannelStateContext();
27711
+ const { giphyVersion: giphyVersionName, imageAttachmentSizeHandler } = useChannelStateContext();
27340
27712
  const { BaseImage: BaseImage$1 = BaseImage } = useComponentContext();
27341
27713
  const { t } = useTranslationContext();
27342
27714
  const usesDefaultBaseImage = BaseImage$1 === BaseImage;
27715
+ const imageElement = useRef(null);
27716
+ const [attachmentConfiguration, setAttachmentConfiguration] = useState(void 0);
27343
27717
  const imageDescriptors = useMemo(
27344
27718
  () => toGalleryItemDescriptors(attachment, { giphyVersionName }),
27345
27719
  [attachment, giphyVersionName]
27346
27720
  );
27347
- if (!imageDescriptors?.imageUrl) return null;
27348
- 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;
27349
27740
  return /* @__PURE__ */ jsxs("div", { className: clsx(`str-chat__message-attachment-giphy`), children: [
27350
27741
  /* @__PURE__ */ jsx(
27351
27742
  BaseImage$1,
27352
27743
  {
27353
27744
  alt: alt ?? title ?? t("User uploaded content"),
27354
27745
  height: dimensions?.height,
27355
- src: imageUrl,
27746
+ ref: imageElement,
27747
+ src: resolvedImageUrl,
27748
+ style: imageStyleVariables,
27356
27749
  width: dimensions?.width,
27357
27750
  ...usesDefaultBaseImage ? { showDownloadButtonOnError: false } : {}
27358
27751
  }
@@ -28011,6 +28404,7 @@ export {
28011
28404
  ContextMenuBackButton,
28012
28405
  ContextMenuBody,
28013
28406
  ContextMenuButton,
28407
+ ContextMenuContent,
28014
28408
  ContextMenuHeader,
28015
28409
  ContextMenuRoot,
28016
28410
  CooldownTimer,
@@ -28245,6 +28639,7 @@ export {
28245
28639
  MessageProvider,
28246
28640
  MessageReactions,
28247
28641
  MessageReactionsDetail,
28642
+ MessageReactionsDetailLoadingIndicator,
28248
28643
  MessageRepliesCountButton,
28249
28644
  MessageSearchResultItem,
28250
28645
  MessageStatus,