stream-chat-react 14.0.1 → 14.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/dist/cjs/emojis.js +8 -8
  2. package/dist/cjs/emojis.js.map +1 -1
  3. package/dist/cjs/index.js +18361 -16460
  4. package/dist/cjs/index.js.map +1 -1
  5. package/dist/cjs/{WithAudioPlayback.ba05c770.js → useNotificationApi.fd802923.js} +1439 -1360
  6. package/dist/cjs/useNotificationApi.fd802923.js.map +1 -0
  7. package/dist/css/index.css +455 -489
  8. package/dist/css/index.css.map +1 -1
  9. package/dist/es/emojis.mjs +1 -1
  10. package/dist/es/index.mjs +18031 -16130
  11. package/dist/es/index.mjs.map +1 -1
  12. package/dist/es/{WithAudioPlayback.610fdf2c.mjs → useNotificationApi.e0c52de6.mjs} +1573 -1494
  13. package/dist/es/useNotificationApi.e0c52de6.mjs.map +1 -0
  14. package/dist/types/a11y/a11yUtils.d.ts +23 -0
  15. package/dist/types/a11y/a11yUtils.d.ts.map +1 -0
  16. package/dist/types/a11y/hooks/useAriaIdentifiers.d.ts +15 -0
  17. package/dist/types/a11y/hooks/useAriaIdentifiers.d.ts.map +1 -0
  18. package/dist/types/a11y/hooks/useResolvedModalAriaProps.d.ts +22 -0
  19. package/dist/types/a11y/hooks/useResolvedModalAriaProps.d.ts.map +1 -0
  20. package/dist/types/components/Accessibility/AriaLiveRegion.d.ts +3 -0
  21. package/dist/types/components/Accessibility/AriaLiveRegion.d.ts.map +1 -0
  22. package/dist/types/components/Accessibility/NotificationAnnouncer.d.ts +14 -0
  23. package/dist/types/components/Accessibility/NotificationAnnouncer.d.ts.map +1 -0
  24. package/dist/types/components/Accessibility/hooks/useIncomingMessageAnnouncements.d.ts +9 -0
  25. package/dist/types/components/Accessibility/hooks/useIncomingMessageAnnouncements.d.ts.map +1 -0
  26. package/dist/types/components/Accessibility/index.d.ts +5 -0
  27. package/dist/types/components/Accessibility/index.d.ts.map +1 -0
  28. package/dist/types/components/Accessibility/useAriaLiveAnnouncer.d.ts +9 -0
  29. package/dist/types/components/Accessibility/useAriaLiveAnnouncer.d.ts.map +1 -0
  30. package/dist/types/components/Attachment/Audio.d.ts.map +1 -1
  31. package/dist/types/components/Attachment/LinkPreview/CardAudio.d.ts.map +1 -1
  32. package/dist/types/components/Attachment/ModalGallery.d.ts.map +1 -1
  33. package/dist/types/components/Attachment/VoiceRecording.d.ts.map +1 -1
  34. package/dist/types/components/AudioPlayback/components/DurationDisplay.d.ts.map +1 -1
  35. package/dist/types/components/AudioPlayback/components/ProgressBar.d.ts +5 -1
  36. package/dist/types/components/AudioPlayback/components/ProgressBar.d.ts.map +1 -1
  37. package/dist/types/components/AudioPlayback/components/WaveProgressBar.d.ts +5 -1
  38. package/dist/types/components/AudioPlayback/components/WaveProgressBar.d.ts.map +1 -1
  39. package/dist/types/components/AudioPlayback/components/formatTime.d.ts +2 -0
  40. package/dist/types/components/AudioPlayback/components/formatTime.d.ts.map +1 -0
  41. package/dist/types/components/AudioPlayback/components/keyboardSeek.d.ts +12 -0
  42. package/dist/types/components/AudioPlayback/components/keyboardSeek.d.ts.map +1 -0
  43. package/dist/types/components/AudioPlayback/components/progressBarA11y.d.ts +10 -0
  44. package/dist/types/components/AudioPlayback/components/progressBarA11y.d.ts.map +1 -0
  45. package/dist/types/components/Avatar/Avatar.d.ts +1 -1
  46. package/dist/types/components/Avatar/Avatar.d.ts.map +1 -1
  47. package/dist/types/components/Button/PlayButton.d.ts.map +1 -1
  48. package/dist/types/components/ChannelListItem/ChannelListItemActionButtons.d.ts.map +1 -1
  49. package/dist/types/components/ChannelListItem/ChannelListItemActionButtons.defaults.d.ts.map +1 -1
  50. package/dist/types/components/ChannelListItem/ChannelListItemUI.d.ts.map +1 -1
  51. package/dist/types/components/Chat/Chat.d.ts.map +1 -1
  52. package/dist/types/components/ChatView/ChatView.a11y.utility.d.ts +26 -0
  53. package/dist/types/components/ChatView/ChatView.a11y.utility.d.ts.map +1 -0
  54. package/dist/types/components/ChatView/ChatView.d.ts +9 -0
  55. package/dist/types/components/ChatView/ChatView.d.ts.map +1 -1
  56. package/dist/types/components/Dialog/components/Alert.d.ts +3 -1
  57. package/dist/types/components/Dialog/components/Alert.d.ts.map +1 -1
  58. package/dist/types/components/Dialog/components/ContextMenu.d.ts +31 -6
  59. package/dist/types/components/Dialog/components/ContextMenu.d.ts.map +1 -1
  60. package/dist/types/components/Dialog/components/Prompt.d.ts +3 -1
  61. package/dist/types/components/Dialog/components/Prompt.d.ts.map +1 -1
  62. package/dist/types/components/Dialog/components/Viewer.d.ts +3 -1
  63. package/dist/types/components/Dialog/components/Viewer.d.ts.map +1 -1
  64. package/dist/types/components/Dialog/service/DialogAnchor.d.ts.map +1 -1
  65. package/dist/types/components/Form/Dropdown.d.ts.map +1 -1
  66. package/dist/types/components/Form/NumericInput.d.ts.map +1 -1
  67. package/dist/types/components/Form/SwitchField.d.ts +5 -2
  68. package/dist/types/components/Form/SwitchField.d.ts.map +1 -1
  69. package/dist/types/components/Form/TextInput.d.ts.map +1 -1
  70. package/dist/types/components/Icons/BaseIcon.d.ts +4 -1
  71. package/dist/types/components/Icons/BaseIcon.d.ts.map +1 -1
  72. package/dist/types/components/Icons/createIcon.d.ts +6 -3
  73. package/dist/types/components/Icons/createIcon.d.ts.map +1 -1
  74. package/dist/types/components/Icons/icons.d.ts +81 -82
  75. package/dist/types/components/Icons/icons.d.ts.map +1 -1
  76. package/dist/types/components/InfiniteScrollPaginator/InfiniteScroll.d.ts.map +1 -1
  77. package/dist/types/components/Loading/LoadingIndicator.d.ts +2 -2
  78. package/dist/types/components/Loading/LoadingIndicator.d.ts.map +1 -1
  79. package/dist/types/components/Location/ShareLocationDialog.d.ts +1 -0
  80. package/dist/types/components/Location/ShareLocationDialog.d.ts.map +1 -1
  81. package/dist/types/components/MediaRecorder/AudioRecorder/AudioRecorderRecordingControls.d.ts.map +1 -1
  82. package/dist/types/components/MediaRecorder/AudioRecorder/AudioRecordingButtonWithNotification.d.ts.map +1 -1
  83. package/dist/types/components/MediaRecorder/AudioRecorder/AudioRecordingPlayback.d.ts.map +1 -1
  84. package/dist/types/components/Message/Message.d.ts.map +1 -1
  85. package/dist/types/components/Message/MessageText.d.ts.map +1 -1
  86. package/dist/types/components/Message/MessageUI.d.ts.map +1 -1
  87. package/dist/types/components/Message/types.d.ts +4 -2
  88. package/dist/types/components/Message/types.d.ts.map +1 -1
  89. package/dist/types/components/MessageActions/DeleteMessageAlert.d.ts.map +1 -1
  90. package/dist/types/components/MessageActions/MessageActions.d.ts.map +1 -1
  91. package/dist/types/components/MessageActions/MessageActions.defaults.d.ts.map +1 -1
  92. package/dist/types/components/MessageBounce/MessageBouncePrompt.d.ts.map +1 -1
  93. package/dist/types/components/MessageComposer/AttachmentPreviewList/AttachmentPreviewList.d.ts.map +1 -1
  94. package/dist/types/components/MessageComposer/AttachmentPreviewList/AudioAttachmentPreview.d.ts.map +1 -1
  95. package/dist/types/components/MessageComposer/AttachmentPreviewList/utils/AttachmentPreviewRoot.d.ts.map +1 -1
  96. package/dist/types/components/MessageComposer/AttachmentSelector/AttachmentSelector.d.ts.map +1 -1
  97. package/dist/types/components/MessageComposer/CommandChip.d.ts.map +1 -1
  98. package/dist/types/components/MessageComposer/QuotedMessagePreview.d.ts.map +1 -1
  99. package/dist/types/components/MessageList/MessageList.d.ts.map +1 -1
  100. package/dist/types/components/MessageList/NewMessageNotification.d.ts.map +1 -1
  101. package/dist/types/components/MessageList/ScrollToLatestMessageButton.d.ts +1 -2
  102. package/dist/types/components/MessageList/ScrollToLatestMessageButton.d.ts.map +1 -1
  103. package/dist/types/components/MessageList/UnreadMessagesNotification.d.ts.map +1 -1
  104. package/dist/types/components/MessageList/UnreadMessagesSeparator.d.ts.map +1 -1
  105. package/dist/types/components/MessageList/VirtualizedMessageList.d.ts +1 -1
  106. package/dist/types/components/MessageList/VirtualizedMessageList.d.ts.map +1 -1
  107. package/dist/types/components/MessageList/hooks/MessageList/useMessageListScrollManager.d.ts +1 -0
  108. package/dist/types/components/MessageList/hooks/MessageList/useMessageListScrollManager.d.ts.map +1 -1
  109. package/dist/types/components/MessageList/hooks/MessageList/useScrollLocationLogic.d.ts.map +1 -1
  110. package/dist/types/components/MessageList/hooks/useReducedMotionPreference.d.ts +2 -0
  111. package/dist/types/components/MessageList/hooks/useReducedMotionPreference.d.ts.map +1 -0
  112. package/dist/types/components/Modal/CloseButtonOnModalOverlay.d.ts.map +1 -1
  113. package/dist/types/components/Modal/GlobalModal.d.ts +9 -1
  114. package/dist/types/components/Modal/GlobalModal.d.ts.map +1 -1
  115. package/dist/types/components/Notifications/Notification.d.ts.map +1 -1
  116. package/dist/types/components/Notifications/NotificationList.d.ts.map +1 -1
  117. package/dist/types/components/Poll/PollActions/AddCommentPrompt.d.ts.map +1 -1
  118. package/dist/types/components/Poll/PollActions/PollAnswerList.d.ts.map +1 -1
  119. package/dist/types/components/Poll/PollActions/PollOptionsFullList.d.ts.map +1 -1
  120. package/dist/types/components/Poll/PollActions/PollResults/PollResults.d.ts.map +1 -1
  121. package/dist/types/components/Poll/PollActions/SuggestPollOptionPrompt.d.ts.map +1 -1
  122. package/dist/types/components/Poll/PollCreationDialog/MultipleAnswersField.d.ts.map +1 -1
  123. package/dist/types/components/Poll/PollCreationDialog/NameField.d.ts.map +1 -1
  124. package/dist/types/components/Poll/PollCreationDialog/OptionFieldSet.d.ts.map +1 -1
  125. package/dist/types/components/Poll/PollCreationDialog/PollCreationDialog.d.ts.map +1 -1
  126. package/dist/types/components/Poll/PollOptionSelector.d.ts.map +1 -1
  127. package/dist/types/components/Reactions/MessageReactions.d.ts.map +1 -1
  128. package/dist/types/components/Reactions/MessageReactionsDetail.d.ts.map +1 -1
  129. package/dist/types/components/Reactions/ReactionSelector.d.ts.map +1 -1
  130. package/dist/types/components/Search/SearchBar/SearchBar.d.ts.map +1 -1
  131. package/dist/types/components/Search/SearchResults/SearchResultItem.d.ts.map +1 -1
  132. package/dist/types/components/Search/SearchResults/SearchResultsHeader.d.ts.map +1 -1
  133. package/dist/types/components/SkipNavigation/SkipNavigation.d.ts +38 -0
  134. package/dist/types/components/SkipNavigation/SkipNavigation.d.ts.map +1 -0
  135. package/dist/types/components/SkipNavigation/index.d.ts +2 -0
  136. package/dist/types/components/SkipNavigation/index.d.ts.map +1 -0
  137. package/dist/types/components/TextareaComposer/SuggestionList/SuggestionList.d.ts.map +1 -1
  138. package/dist/types/components/Threads/ThreadList/ThreadList.d.ts.map +1 -1
  139. package/dist/types/components/TypingIndicator/TypingIndicator.d.ts.map +1 -1
  140. package/dist/types/components/TypingIndicator/TypingIndicatorHeader.d.ts.map +1 -1
  141. package/dist/types/components/TypingIndicator/utils/getTypingStatusMessage.d.ts +8 -0
  142. package/dist/types/components/TypingIndicator/utils/getTypingStatusMessage.d.ts.map +1 -0
  143. package/dist/types/components/VisuallyHidden/VisuallyHidden.d.ts +7 -0
  144. package/dist/types/components/VisuallyHidden/VisuallyHidden.d.ts.map +1 -0
  145. package/dist/types/components/VisuallyHidden/index.d.ts +2 -0
  146. package/dist/types/components/VisuallyHidden/index.d.ts.map +1 -0
  147. package/dist/types/components/index.d.ts +3 -0
  148. package/dist/types/components/index.d.ts.map +1 -1
  149. package/dist/types/context/ComponentContext.d.ts +10 -1
  150. package/dist/types/context/ComponentContext.d.ts.map +1 -1
  151. package/dist/types/context/ModalContext.d.ts +1 -0
  152. package/dist/types/context/ModalContext.d.ts.map +1 -1
  153. package/dist/types/context/index.d.ts +1 -0
  154. package/dist/types/context/index.d.ts.map +1 -1
  155. package/dist/types/i18n/Streami18n.d.ts +55 -2
  156. package/dist/types/i18n/Streami18n.d.ts.map +1 -1
  157. package/package.json +1 -1
  158. package/dist/cjs/WithAudioPlayback.ba05c770.js.map +0 -1
  159. package/dist/es/WithAudioPlayback.610fdf2c.mjs.map +0 -1
@@ -3,12 +3,12 @@ const jsxRuntime = require("react/jsx-runtime");
3
3
  const React = require("react");
4
4
  const react = require("@floating-ui/react");
5
5
  const streamChat = require("stream-chat");
6
- const throttle = require("lodash.throttle");
7
- const shim = require("use-sync-external-store/shim");
8
6
  const clsx = require("clsx");
9
7
  const debounce = require("lodash.debounce");
8
+ const throttle = require("lodash.throttle");
10
9
  const Dayjs = require("dayjs");
11
10
  const linkify = require("linkifyjs");
11
+ const shim = require("use-sync-external-store/shim");
12
12
  const nanoid = require("nanoid");
13
13
  const deepequal = require("react-fast-compare");
14
14
  const emojiRegex = require("emoji-regex");
@@ -390,15 +390,96 @@ const useTypingContext = (componentName) => {
390
390
  }
391
391
  return contextValue;
392
392
  };
393
- const BaseIcon = ({ className, ...props }) => /* @__PURE__ */ jsxRuntime.jsx(
394
- "svg",
395
- {
396
- viewBox: "0 0 20 20",
397
- xmlns: "http://www.w3.org/2000/svg",
398
- ...props,
399
- className: clsx("str-chat__icon", className)
393
+ const NOTIFICATION_TARGET_PANELS = [
394
+ "channel",
395
+ "thread",
396
+ "channel-list",
397
+ "thread-list"
398
+ ];
399
+ const isNotificationTargetPanel = (value) => typeof value === "string" && NOTIFICATION_TARGET_PANELS.includes(value);
400
+ const getNotificationTargetPanel = (notification) => {
401
+ const targetTag = notification.tags?.find((tag) => tag.startsWith("target:"));
402
+ if (targetTag) {
403
+ const candidate = targetTag.slice("target:".length);
404
+ if (isNotificationTargetPanel(candidate)) return candidate;
400
405
  }
401
- );
406
+ const panel = notification.origin.context?.panel;
407
+ return isNotificationTargetPanel(panel) ? panel : void 0;
408
+ };
409
+ const getNotificationTargetPanels = (notification) => {
410
+ const targetPanels = (notification.tags ?? []).filter((tag) => tag.startsWith("target:")).map((tag) => tag.slice("target:".length)).filter(
411
+ (value) => isNotificationTargetPanel(value)
412
+ );
413
+ if (targetPanels.length > 0) {
414
+ return Array.from(new Set(targetPanels));
415
+ }
416
+ const panel = notification.origin.context?.panel;
417
+ return isNotificationTargetPanel(panel) ? [panel] : [];
418
+ };
419
+ const getNotificationTargetTag = (panel) => `target:${panel}`;
420
+ const addNotificationTargetTag = (panel, tags) => {
421
+ if (!panel) return tags ?? [];
422
+ return Array.from(/* @__PURE__ */ new Set([getNotificationTargetTag(panel), ...tags ?? []]));
423
+ };
424
+ const isNotificationForPanel = (notification, panel, options) => {
425
+ const explicitTargetPanels = getNotificationTargetPanels(notification);
426
+ if (explicitTargetPanels.length > 0) {
427
+ return explicitTargetPanels.includes(panel);
428
+ }
429
+ const resolvedPanel = options?.fallbackPanel ?? "channel";
430
+ return resolvedPanel === panel;
431
+ };
432
+ const variantToClass = {
433
+ danger: "str-chat__button--destructive",
434
+ primary: "str-chat__button--primary",
435
+ secondary: "str-chat__button--secondary"
436
+ };
437
+ const appearanceToClass = {
438
+ ghost: "str-chat__button--ghost",
439
+ outline: "str-chat__button--outline",
440
+ solid: "str-chat__button--solid"
441
+ };
442
+ const sizeToClass = {
443
+ lg: "str-chat__button--size-lg",
444
+ md: "str-chat__button--size-md",
445
+ sm: "str-chat__button--size-sm",
446
+ xs: "str-chat__button--size-xs"
447
+ };
448
+ const Button = React.forwardRef(function Button2({ appearance, children, circular, className, inverseTheme, size, variant, ...props }, ref) {
449
+ return /* @__PURE__ */ jsxRuntime.jsx(
450
+ "button",
451
+ {
452
+ ref,
453
+ type: "button",
454
+ ...props,
455
+ className: clsx(
456
+ "str-chat__button",
457
+ variant != null && variantToClass[variant],
458
+ appearance != null && appearanceToClass[appearance],
459
+ circular && "str-chat__button--circular",
460
+ inverseTheme && "str-chat__theme-inverse",
461
+ size != null && sizeToClass[size],
462
+ className
463
+ ),
464
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__button__content", children })
465
+ }
466
+ );
467
+ });
468
+ const BaseIcon = ({ className, decorative = true, ...props }) => {
469
+ const ariaHidden = props["aria-hidden"] ?? (decorative ? true : void 0);
470
+ const focusable = props.focusable ?? (decorative ? false : void 0);
471
+ return /* @__PURE__ */ jsxRuntime.jsx(
472
+ "svg",
473
+ {
474
+ viewBox: "0 0 20 20",
475
+ xmlns: "http://www.w3.org/2000/svg",
476
+ ...props,
477
+ "aria-hidden": ariaHidden,
478
+ className: clsx("str-chat__icon", className),
479
+ focusable
480
+ }
481
+ );
482
+ };
402
483
  function toIconClass(name) {
403
484
  return "str-chat__icon--" + name.replace(/^Icon/, "").replace(/([a-z])([A-Z])/g, "$1-$2").replace(/([A-Za-z])(\d)/g, "$1-$2").replace(/(\d)([A-Za-z])/g, "$1-$2").replace(/_/g, "-").toLowerCase();
404
485
  }
@@ -1513,977 +1594,259 @@ const IconGiphy = createIcon(
1513
1594
  )
1514
1595
  ] })
1515
1596
  );
1516
- const DEFAULT_PLAYBACK_RATES = [1, 1.5, 2];
1517
- const isSeekable = (audioElement) => !(audioElement.duration === Infinity || isNaN(audioElement.duration));
1518
- const defaultRegisterAudioPlayerError = ({
1519
- error
1520
- } = {}) => {
1521
- if (!error) return;
1522
- console.error("[AUDIO PLAYER]", error);
1597
+ const UnMemoizedEmptyStateIndicator = (props) => {
1598
+ const { listType, messageText } = props;
1599
+ const { t } = useTranslationContext("EmptyStateIndicator");
1600
+ if (listType === "thread") return null;
1601
+ if (listType === "channel") {
1602
+ const text = t("No conversations yet");
1603
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__channel-list-empty", children: [
1604
+ /* @__PURE__ */ jsxRuntime.jsx(IconMessageBubbles, {}),
1605
+ /* @__PURE__ */ jsxRuntime.jsx("p", { role: "listitem", children: text })
1606
+ ] });
1607
+ }
1608
+ if (listType === "message") {
1609
+ const text = t(messageText || "Send a message to start the conversation");
1610
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__empty-channel", children: [
1611
+ /* @__PURE__ */ jsxRuntime.jsx(IconMessageBubble, {}),
1612
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "str-chat__empty-channel-text", role: "listitem", children: text })
1613
+ ] });
1614
+ }
1615
+ return /* @__PURE__ */ jsxRuntime.jsx("p", { children: t("No items exist") });
1523
1616
  };
1524
- const elementIsPlaying = (audioElement) => audioElement && !(audioElement.paused || audioElement.ended);
1525
- class AudioPlayer {
1526
- constructor({
1527
- durationSeconds,
1528
- fileSize,
1529
- id,
1530
- mimeType,
1531
- playbackRates: customPlaybackRates,
1532
- plugins,
1533
- pool,
1534
- src,
1535
- title,
1536
- waveformData
1537
- }) {
1538
- this._plugins = /* @__PURE__ */ new Map();
1539
- this.playTimeout = void 0;
1540
- this.unsubscribeEventListeners = null;
1541
- this._disposed = false;
1542
- this._metadataProbe = null;
1543
- this._restoringPosition = false;
1544
- this._removalTimeout = void 0;
1545
- this.setDurationSeconds = (durationSeconds2) => {
1546
- this._data.durationSeconds = durationSeconds2;
1547
- this.state.partialNext({ durationSeconds: durationSeconds2 });
1548
- };
1549
- this.setPlaybackStartSafetyTimeout = () => {
1550
- clearTimeout(this.playTimeout);
1551
- this.playTimeout = setTimeout(() => {
1552
- if (!this.elementRef) return;
1553
- try {
1554
- this.elementRef.pause();
1555
- this.state.partialNext({ isPlaying: false });
1556
- } catch (e) {
1557
- this.registerError({ errCode: "failed-to-start" });
1558
- }
1559
- }, 2e3);
1560
- };
1561
- this.updateDurationFromElement = (element) => {
1562
- const duration = element.duration;
1563
- if (typeof duration !== "number" || isNaN(duration) || !isFinite(duration) || duration <= 0) {
1564
- return;
1565
- }
1566
- this.setDurationSeconds(duration);
1567
- };
1568
- this.clearMetadataProbe = () => {
1569
- const probe = this._metadataProbe;
1570
- this._metadataProbe = null;
1571
- this._metadataProbePromise = void 0;
1572
- if (!probe) return;
1573
- try {
1574
- probe.pause();
1575
- } catch {
1576
- }
1577
- probe.removeAttribute("src");
1578
- try {
1579
- probe.load();
1580
- } catch {
1581
- }
1582
- };
1583
- this.preloadMetadata = () => {
1584
- if (this._disposed || this.durationSeconds != null || !this.src || this._metadataProbePromise || typeof document === "undefined") {
1585
- return;
1586
- }
1587
- const probe = document.createElement("audio");
1588
- probe.preload = "metadata";
1589
- this._metadataProbe = probe;
1590
- this._metadataProbePromise = new Promise((resolve) => {
1591
- const cleanup = () => {
1592
- probe.removeEventListener("loadedmetadata", handleLoadedMetadata);
1593
- probe.removeEventListener("error", handleError);
1594
- if (this._metadataProbe === probe) {
1595
- this.clearMetadataProbe();
1596
- } else {
1597
- this._metadataProbePromise = void 0;
1598
- }
1599
- resolve();
1600
- };
1601
- const handleLoadedMetadata = () => {
1602
- this.updateDurationFromElement(probe);
1603
- cleanup();
1604
- };
1605
- const handleError = () => {
1606
- cleanup();
1607
- };
1608
- probe.addEventListener("loadedmetadata", handleLoadedMetadata, { once: true });
1609
- probe.addEventListener("error", handleError, { once: true });
1610
- probe.src = this.src;
1611
- try {
1612
- probe.load();
1613
- } catch {
1614
- cleanup();
1615
- }
1616
- });
1617
- };
1618
- this.clearPlaybackStartSafetyTimeout = () => {
1619
- if (!this.elementRef) return;
1620
- clearTimeout(this.playTimeout);
1621
- this.playTimeout = void 0;
1622
- };
1623
- this.clearPendingLoadedMeta = () => {
1624
- const pending = this._pendingLoadedMeta;
1625
- if (pending?.element && pending.onLoaded) {
1626
- pending.element.removeEventListener("loadedmetadata", pending.onLoaded);
1627
- }
1628
- this._pendingLoadedMeta = void 0;
1629
- };
1630
- this.restoreSavedPosition = (elementRef) => {
1631
- const saved = this.secondsElapsed;
1632
- if (!saved || saved <= 0) return;
1633
- const apply = () => {
1634
- const duration = elementRef.duration;
1635
- const clamped = typeof duration === "number" && !isNaN(duration) && isFinite(duration) ? Math.min(saved, duration) : saved;
1636
- try {
1637
- if (elementRef.currentTime === clamped) return;
1638
- elementRef.currentTime = clamped;
1639
- this.setSecondsElapsed(clamped);
1640
- } catch {
1641
- }
1617
+ const EmptyStateIndicator = React.memo(
1618
+ UnMemoizedEmptyStateIndicator
1619
+ );
1620
+ const makeChannelReducer = () => (state, action) => {
1621
+ switch (action.type) {
1622
+ case "closeThread": {
1623
+ return {
1624
+ ...state,
1625
+ thread: null,
1626
+ threadLoadingMore: false,
1627
+ threadMessages: []
1642
1628
  };
1643
- if (elementRef.readyState < 1) {
1644
- this.clearPendingLoadedMeta();
1645
- this._restoringPosition = true;
1646
- const onLoaded = () => {
1647
- if (this._pendingLoadedMeta?.onLoaded !== onLoaded) return;
1648
- this._pendingLoadedMeta = void 0;
1649
- if (this.elementRef !== elementRef) {
1650
- this._restoringPosition = false;
1651
- return;
1652
- }
1653
- apply();
1654
- this._restoringPosition = false;
1655
- };
1656
- elementRef.addEventListener("loadedmetadata", onLoaded, { once: true });
1657
- this._pendingLoadedMeta = { element: elementRef, onLoaded };
1658
- } else {
1659
- this._restoringPosition = true;
1660
- apply();
1661
- this._restoringPosition = false;
1662
- }
1663
- };
1664
- this.elementIsReady = () => {
1665
- if (this._elementIsReadyPromise) return this._elementIsReadyPromise;
1666
- this._elementIsReadyPromise = new Promise((resolve) => {
1667
- if (!this.elementRef) return resolve(false);
1668
- const element = this.elementRef;
1669
- const handleLoaded = () => {
1670
- element.removeEventListener("loadedmetadata", handleLoaded);
1671
- resolve(element.readyState > 0);
1672
- };
1673
- element.addEventListener("loadedmetadata", handleLoaded);
1674
- });
1675
- return this._elementIsReadyPromise;
1676
- };
1677
- this.setRef = (elementRef) => {
1678
- if (elementIsPlaying(this.elementRef)) {
1679
- this.releaseElement({ resetState: false });
1680
- }
1681
- this.clearPendingLoadedMeta();
1682
- this.clearMetadataProbe();
1683
- this._restoringPosition = false;
1684
- this._elementIsReadyPromise = void 0;
1685
- this.state.partialNext({ elementRef });
1686
- if (elementRef) {
1687
- this.registerSubscriptions();
1688
- }
1689
- };
1690
- this.setSecondsElapsed = (secondsElapsed) => {
1691
- const duration = this.elementRef?.duration ?? this.durationSeconds;
1692
- this.state.partialNext({
1693
- progressPercent: duration && secondsElapsed ? secondsElapsed / duration * 100 : 0,
1694
- secondsElapsed
1695
- });
1696
- };
1697
- this.canPlayMimeType = (mimeType2) => {
1698
- if (!mimeType2) return false;
1699
- if (this.elementRef) return !!this.elementRef.canPlayType(mimeType2);
1700
- return !!new Audio().canPlayType(mimeType2);
1701
- };
1702
- this.play = async (params) => {
1703
- if (this._disposed) return;
1704
- const elementRef = this.ensureElementRef();
1705
- if (elementIsPlaying(this.elementRef)) {
1706
- if (this.isPlaying) return;
1707
- this.state.partialNext({ isPlaying: true });
1708
- return;
1709
- }
1710
- const { currentPlaybackRate, playbackRates: playbackRates2 } = {
1711
- currentPlaybackRate: this.currentPlaybackRate,
1712
- playbackRates: this.playbackRates,
1713
- ...params
1629
+ }
1630
+ case "copyMessagesFromChannel": {
1631
+ const { channel, parentId } = action;
1632
+ return {
1633
+ ...state,
1634
+ messages: [...channel.state.messages],
1635
+ pinnedMessages: [...channel.state.pinnedMessages],
1636
+ // copying messages from channel happens with new message - this resets the suppressAutoscroll
1637
+ suppressAutoscroll: false,
1638
+ threadMessages: parentId ? { ...channel.state.threads }[parentId] || [] : state.threadMessages
1714
1639
  };
1715
- if (!this.canPlayRecord) {
1716
- this.registerError({ errCode: "not-playable" });
1717
- return;
1718
- }
1719
- this.restoreSavedPosition(elementRef);
1720
- elementRef.playbackRate = currentPlaybackRate ?? this.currentPlaybackRate;
1721
- this.setPlaybackStartSafetyTimeout();
1722
- try {
1723
- await elementRef.play();
1724
- this.state.partialNext({
1725
- currentPlaybackRate,
1726
- isPlaying: true,
1727
- playbackRates: playbackRates2
1728
- });
1729
- this._pool.setActiveAudioPlayer(this);
1730
- } catch (e) {
1731
- this.registerError({ error: e });
1732
- this.state.partialNext({ isPlaying: false });
1733
- } finally {
1734
- this.clearPlaybackStartSafetyTimeout();
1735
- }
1736
- };
1737
- this.pause = () => {
1738
- if (!elementIsPlaying(this.elementRef)) return;
1739
- this.clearPlaybackStartSafetyTimeout();
1740
- this.elementRef.pause();
1741
- this.state.partialNext({ isPlaying: false });
1742
- };
1743
- this.stop = () => {
1744
- this.pause();
1745
- this.state.partialNext({ isPlaying: false });
1746
- this.setSecondsElapsed(0);
1747
- if (this.elementRef) this.elementRef.currentTime = 0;
1748
- };
1749
- this.togglePlay = async () => this.isPlaying ? this.pause() : await this.play();
1750
- this.increasePlaybackRate = () => {
1751
- let currentPlaybackRateIndex = this.state.getLatestValue().playbackRates.findIndex((rate) => rate === this.currentPlaybackRate);
1752
- if (currentPlaybackRateIndex === -1) {
1753
- currentPlaybackRateIndex = 0;
1754
- }
1755
- const nextIndex = currentPlaybackRateIndex === this.playbackRates.length - 1 ? 0 : currentPlaybackRateIndex + 1;
1756
- const currentPlaybackRate = this.playbackRates[nextIndex];
1757
- this.state.partialNext({ currentPlaybackRate });
1758
- if (this.elementRef) {
1759
- this.elementRef.playbackRate = currentPlaybackRate;
1760
- }
1761
- };
1762
- this.seek = throttle(async ({ clientX, currentTarget }) => {
1763
- let element = this.elementRef;
1764
- if (!this.elementRef) {
1765
- element = this.ensureElementRef();
1766
- const isReady = await this.elementIsReady();
1767
- if (!isReady) return;
1768
- }
1769
- if (!currentTarget || !element) return;
1770
- if (!isSeekable(element)) {
1771
- this.registerError({ errCode: "seek-not-supported" });
1772
- return;
1773
- }
1774
- const { width, x } = currentTarget.getBoundingClientRect();
1775
- const ratio = (clientX - x) / width;
1776
- if (ratio > 1 || ratio < 0) return;
1777
- const currentTime = ratio * element.duration;
1778
- this.setSecondsElapsed(currentTime);
1779
- element.currentTime = currentTime;
1780
- }, 16);
1781
- this.registerError = (params) => {
1782
- defaultRegisterAudioPlayerError(params);
1783
- this.plugins.forEach(({ onError }) => onError?.({ player: this, ...params }));
1784
- };
1785
- this.requestRemoval = () => {
1786
- this._disposed = true;
1787
- this.cancelScheduledRemoval();
1788
- this.clearPendingLoadedMeta();
1789
- this.clearMetadataProbe();
1790
- this._restoringPosition = false;
1791
- this.releaseElement({ resetState: true });
1792
- this.unsubscribeEventListeners?.();
1793
- this.unsubscribeEventListeners = null;
1794
- this.plugins.forEach(({ onRemove }) => onRemove?.({ player: this }));
1795
- this._pool.deregister(this.id);
1796
- };
1797
- this.cancelScheduledRemoval = () => {
1798
- clearTimeout(this._removalTimeout);
1799
- this._removalTimeout = void 0;
1800
- };
1801
- this.scheduleRemoval = (ms = 0) => {
1802
- this.cancelScheduledRemoval();
1803
- this._removalTimeout = setTimeout(() => {
1804
- if (this.disposed) return;
1805
- this.requestRemoval();
1806
- }, ms);
1807
- };
1808
- this.releaseElementForHandoff = () => {
1809
- if (!this.elementRef) return;
1810
- this.releaseElement({ resetState: false });
1811
- this.unsubscribeEventListeners?.();
1812
- this.unsubscribeEventListeners = null;
1813
- };
1814
- this.registerSubscriptions = () => {
1815
- this.unsubscribeEventListeners?.();
1816
- const audioElement = this.elementRef;
1817
- if (!audioElement) return;
1818
- const handleEnded = () => {
1819
- if (audioElement) {
1820
- this.updateDurationFromElement(audioElement);
1821
- }
1822
- this.stop();
1640
+ }
1641
+ case "copyStateFromChannelOnEvent": {
1642
+ const { channel } = action;
1643
+ return {
1644
+ ...state,
1645
+ members: { ...channel.state.members },
1646
+ messages: [...channel.state.messages],
1647
+ pinnedMessages: [...channel.state.pinnedMessages],
1648
+ read: { ...channel.state.read },
1649
+ watcherCount: channel.state.watcher_count,
1650
+ watchers: { ...channel.state.watchers }
1823
1651
  };
1824
- const handleError = (e) => {
1825
- const audio = e.currentTarget;
1826
- const state = { isPlaying: false };
1827
- if (!audio?.error?.code) {
1828
- this.state.partialNext(state);
1829
- return;
1830
- }
1831
- if (audio.error.code === 4) {
1832
- state.canPlayRecord = false;
1833
- this.state.partialNext(state);
1834
- }
1835
- const errorMsg = [
1836
- void 0,
1837
- "MEDIA_ERR_ABORTED: fetch aborted by user",
1838
- "MEDIA_ERR_NETWORK: network failed while fetching",
1839
- "MEDIA_ERR_DECODE: audio fetched but couldn’t decode",
1840
- "MEDIA_ERR_SRC_NOT_SUPPORTED: source not supported"
1841
- ][audio?.error?.code];
1842
- if (!errorMsg) return;
1843
- defaultRegisterAudioPlayerError({ error: new Error(errorMsg + ` (${audio.src})`) });
1652
+ }
1653
+ case "initStateFromChannel": {
1654
+ const { channel, hasMore } = action;
1655
+ return {
1656
+ ...state,
1657
+ hasMore,
1658
+ loading: false,
1659
+ members: { ...channel.state.members },
1660
+ messages: [...channel.state.messages],
1661
+ pinnedMessages: [...channel.state.pinnedMessages],
1662
+ read: { ...channel.state.read },
1663
+ watcherCount: channel.state.watcher_count,
1664
+ watchers: { ...channel.state.watchers }
1844
1665
  };
1845
- const handleTimeupdate = () => {
1846
- const t = audioElement?.currentTime ?? 0;
1847
- if (this._restoringPosition && t === 0) return;
1848
- if (!this.isPlaying && t === 0 && this.secondsElapsed > 0) return;
1849
- this.setSecondsElapsed(t);
1666
+ }
1667
+ case "jumpToLatestMessageFinished": {
1668
+ const { hasMore, hasMoreNewer, messages } = action;
1669
+ return {
1670
+ ...state,
1671
+ hasMore,
1672
+ hasMoreNewer,
1673
+ highlightedMessageId: void 0,
1674
+ loading: false,
1675
+ messages,
1676
+ suppressAutoscroll: false
1850
1677
  };
1851
- const handleLoadedMetadata = () => {
1852
- if (audioElement) {
1853
- this.updateDurationFromElement(audioElement);
1854
- }
1678
+ }
1679
+ case "jumpToMessageFinished": {
1680
+ return {
1681
+ ...state,
1682
+ hasMore: action.channel.state.messagePagination.hasPrev,
1683
+ hasMoreNewer: action.channel.state.messagePagination.hasNext,
1684
+ highlightedMessageId: action.highlightedMessageId,
1685
+ loadingMore: false,
1686
+ loadingMoreForJumpToChannelMessage: false,
1687
+ messages: action.channel.state.messages,
1688
+ suppressAutoscroll: false
1855
1689
  };
1856
- audioElement.addEventListener("ended", handleEnded);
1857
- audioElement.addEventListener("error", handleError);
1858
- audioElement.addEventListener("loadedmetadata", handleLoadedMetadata);
1859
- audioElement.addEventListener("timeupdate", handleTimeupdate);
1860
- this.unsubscribeEventListeners = () => {
1861
- audioElement.pause();
1862
- audioElement.removeEventListener("ended", handleEnded);
1863
- audioElement.removeEventListener("error", handleError);
1864
- audioElement.removeEventListener("loadedmetadata", handleLoadedMetadata);
1865
- audioElement.removeEventListener("timeupdate", handleTimeupdate);
1690
+ }
1691
+ case "clearHighlightedMessage": {
1692
+ return {
1693
+ ...state,
1694
+ highlightedMessageId: void 0
1866
1695
  };
1867
- };
1868
- this._data = {
1869
- durationSeconds,
1870
- fileSize,
1871
- id,
1872
- mimeType,
1873
- src,
1874
- title,
1875
- waveformData
1876
- };
1877
- this._pool = pool;
1878
- this.setPlugins(() => plugins ?? []);
1879
- const playbackRates = customPlaybackRates?.length ? customPlaybackRates : DEFAULT_PLAYBACK_RATES;
1880
- const canPlayRecord = mimeType ? !!new Audio().canPlayType(mimeType) : true;
1881
- this.state = new streamChat.StateStore({
1882
- canPlayRecord,
1883
- currentPlaybackRate: playbackRates[0],
1884
- durationSeconds,
1885
- elementRef: null,
1886
- isPlaying: false,
1887
- playbackError: null,
1888
- playbackRates,
1889
- progressPercent: 0,
1890
- secondsElapsed: 0
1891
- });
1892
- this.plugins.forEach((p) => p.onInit?.({ player: this }));
1893
- this.preloadMetadata();
1894
- }
1895
- get plugins() {
1896
- return Array.from(this._plugins.values());
1897
- }
1898
- get canPlayRecord() {
1899
- return this.state.getLatestValue().canPlayRecord;
1900
- }
1901
- get elementRef() {
1902
- return this.state.getLatestValue().elementRef;
1903
- }
1904
- get isPlaying() {
1905
- return this.state.getLatestValue().isPlaying;
1906
- }
1907
- get currentPlaybackRate() {
1908
- return this.state.getLatestValue().currentPlaybackRate;
1909
- }
1910
- get playbackRates() {
1911
- return this.state.getLatestValue().playbackRates;
1912
- }
1913
- get durationSeconds() {
1914
- return this.state.getLatestValue().durationSeconds;
1915
- }
1916
- get fileSize() {
1917
- return this._data.fileSize;
1918
- }
1919
- get id() {
1920
- return this._data.id;
1921
- }
1922
- get src() {
1923
- return this._data.src;
1924
- }
1925
- get mimeType() {
1926
- return this._data.mimeType;
1927
- }
1928
- get title() {
1929
- return this._data.title;
1930
- }
1931
- get waveformData() {
1932
- return this._data.waveformData;
1933
- }
1934
- get secondsElapsed() {
1935
- return this.state.getLatestValue().secondsElapsed;
1936
- }
1937
- get progressPercent() {
1938
- return this.state.getLatestValue().progressPercent;
1939
- }
1940
- get disposed() {
1941
- return this._disposed;
1942
- }
1943
- ensureElementRef() {
1944
- if (this._disposed) {
1945
- throw new Error("AudioPlayer is disposed");
1946
1696
  }
1947
- if (!this.elementRef) {
1948
- const el = this._pool.acquireElement({
1949
- ownerId: this.id,
1950
- src: this.src
1951
- });
1952
- this.setRef(el);
1697
+ case "loadMoreFinished": {
1698
+ const { hasMore, messages } = action;
1699
+ return {
1700
+ ...state,
1701
+ hasMore,
1702
+ loadingMore: false,
1703
+ messages,
1704
+ suppressAutoscroll: false
1705
+ };
1953
1706
  }
1954
- return this.elementRef;
1955
- }
1956
- setDescriptor(descriptor) {
1957
- const previousSrc = this.src;
1958
- this._data = { ...this._data, ...descriptor };
1959
- if (descriptor.src !== previousSrc && this.elementRef) {
1960
- this.elementRef.src = descriptor.src;
1707
+ case "loadMoreNewerFinished": {
1708
+ const { hasMoreNewer, messages } = action;
1709
+ return {
1710
+ ...state,
1711
+ hasMoreNewer,
1712
+ loadingMoreNewer: false,
1713
+ messages
1714
+ };
1961
1715
  }
1962
- if (descriptor.src && descriptor.src !== previousSrc) {
1963
- this.clearMetadataProbe();
1964
- if (descriptor.durationSeconds == null) {
1965
- this.setDurationSeconds(void 0);
1966
- this.preloadMetadata();
1967
- } else {
1968
- this.setDurationSeconds(descriptor.durationSeconds);
1969
- }
1970
- return;
1716
+ case "loadMoreThreadFinished": {
1717
+ const { threadHasMore, threadMessages } = action;
1718
+ return {
1719
+ ...state,
1720
+ threadHasMore,
1721
+ threadLoadingMore: false,
1722
+ threadMessages
1723
+ };
1971
1724
  }
1972
- if (descriptor.durationSeconds != null) {
1973
- this.setDurationSeconds(descriptor.durationSeconds);
1725
+ case "openThread": {
1726
+ const { channel, message } = action;
1727
+ return {
1728
+ ...state,
1729
+ thread: message,
1730
+ threadHasMore: true,
1731
+ threadMessages: message.id ? { ...channel.state.threads }[message.id] || [] : [],
1732
+ threadSuppressAutoscroll: false
1733
+ };
1974
1734
  }
1975
- }
1976
- releaseElement({ resetState }) {
1977
- this.clearPendingLoadedMeta();
1978
- this.clearMetadataProbe();
1979
- this._restoringPosition = false;
1980
- if (resetState) {
1981
- this.stop();
1982
- } else {
1983
- this.state.partialNext({ isPlaying: false });
1984
- if (this.elementRef) {
1985
- try {
1986
- this.elementRef.pause();
1987
- } catch {
1988
- }
1989
- }
1735
+ case "setError": {
1736
+ const { error } = action;
1737
+ return { ...state, error };
1990
1738
  }
1991
- if (this.elementRef) {
1992
- this._pool.releaseElement(this.id);
1993
- this.setRef(null);
1739
+ case "setLoadingMore": {
1740
+ const { loadingMore } = action;
1741
+ return { ...state, loadingMore, suppressAutoscroll: loadingMore };
1994
1742
  }
1743
+ case "setLoadingMoreForJumpToChannelMessage": {
1744
+ const { loadingMoreForJumpToChannelMessage } = action;
1745
+ return {
1746
+ ...state,
1747
+ loadingMoreForJumpToChannelMessage,
1748
+ suppressAutoscroll: loadingMoreForJumpToChannelMessage
1749
+ };
1750
+ }
1751
+ case "setLoadingMoreNewer": {
1752
+ const { loadingMoreNewer } = action;
1753
+ return { ...state, loadingMoreNewer };
1754
+ }
1755
+ case "setThread": {
1756
+ const { message } = action;
1757
+ return { ...state, thread: message };
1758
+ }
1759
+ case "setTyping": {
1760
+ const { channel } = action;
1761
+ return {
1762
+ ...state,
1763
+ typing: { ...channel.state.typing }
1764
+ };
1765
+ }
1766
+ case "startLoadingThread": {
1767
+ return {
1768
+ ...state,
1769
+ threadLoadingMore: true,
1770
+ threadSuppressAutoscroll: true
1771
+ };
1772
+ }
1773
+ case "updateThreadOnEvent": {
1774
+ const { channel, message } = action;
1775
+ if (!state.thread) return state;
1776
+ return {
1777
+ ...state,
1778
+ thread: message?.id === state.thread.id ? channel.state.formatMessage(message) : state.thread,
1779
+ threadMessages: state.thread?.id ? { ...channel.state.threads }[state.thread.id] || [] : []
1780
+ };
1781
+ }
1782
+ default:
1783
+ return state;
1995
1784
  }
1996
- setPlugins(setter) {
1997
- this._plugins = setter(this.plugins).reduce((acc, plugin) => {
1998
- if (plugin.id) {
1999
- acc.set(plugin.id, plugin);
2000
- }
2001
- return acc;
2002
- }, /* @__PURE__ */ new Map());
2003
- }
2004
- }
2005
- class AudioPlayerPool {
2006
- constructor(config) {
2007
- this.state = new streamChat.StateStore({
2008
- activeAudioPlayer: null
2009
- });
2010
- this.pool = /* @__PURE__ */ new Map();
2011
- this.audios = /* @__PURE__ */ new Map();
2012
- this.sharedAudio = null;
2013
- this.sharedOwnerId = null;
2014
- this.getOrAdd = (params) => {
2015
- const { playbackRates, plugins, ...descriptor } = params;
2016
- let player = this.pool.get(params.id);
2017
- if (player) {
2018
- if (!player.disposed) {
2019
- player.setDescriptor(descriptor);
2020
- return player;
2021
- }
2022
- this.deregister(params.id);
2023
- }
2024
- player = new AudioPlayer({
2025
- playbackRates,
2026
- plugins,
2027
- ...descriptor,
2028
- pool: this
2029
- });
2030
- this.pool.set(params.id, player);
2031
- return player;
2032
- };
2033
- this.acquireElement = ({ ownerId, src }) => {
2034
- if (!this.allowConcurrentPlayback) {
2035
- if (!this.sharedAudio) {
2036
- this.sharedAudio = new Audio();
2037
- }
2038
- if (this.sharedOwnerId && this.sharedOwnerId !== ownerId) {
2039
- const previous = this.pool.get(this.sharedOwnerId);
2040
- previous?.pause();
2041
- previous?.releaseElementForHandoff();
2042
- }
2043
- this.sharedOwnerId = ownerId;
2044
- if (this.sharedAudio.src !== src) {
2045
- this.sharedAudio.src = src;
2046
- }
2047
- return this.sharedAudio;
2048
- }
2049
- let audio = this.audios.get(ownerId);
2050
- if (!audio) {
2051
- audio = new Audio();
2052
- this.audios.set(ownerId, audio);
2053
- }
2054
- if (audio.src !== src) {
2055
- audio.src = src;
2056
- }
2057
- return audio;
2058
- };
2059
- this.releaseElement = (ownerId) => {
2060
- if (!this.allowConcurrentPlayback) {
2061
- if (this.sharedOwnerId !== ownerId) return;
2062
- const el2 = this.sharedAudio;
2063
- if (el2) {
2064
- try {
2065
- el2.pause();
2066
- } catch {
2067
- }
2068
- el2.removeAttribute("src");
2069
- el2.load();
2070
- }
2071
- this.sharedOwnerId = null;
2072
- return;
2073
- }
2074
- const el = this.audios.get(ownerId);
2075
- if (!el) return;
2076
- try {
2077
- el.pause();
2078
- } catch {
2079
- }
2080
- el.removeAttribute("src");
2081
- el.load();
2082
- this.audios.delete(ownerId);
2083
- };
2084
- this.setActiveAudioPlayer = (activeAudioPlayer) => {
2085
- if (this.allowConcurrentPlayback) return;
2086
- this.state.partialNext({ activeAudioPlayer });
2087
- };
2088
- this.remove = (id) => {
2089
- const player = this.pool.get(id);
2090
- if (!player) return;
2091
- player.requestRemoval();
2092
- };
2093
- this.clear = () => {
2094
- this.players.forEach((player) => {
2095
- this.remove(player.id);
2096
- });
2097
- };
2098
- this.registerSubscriptions = () => {
2099
- this.players.forEach((p) => {
2100
- if (p.elementRef) {
2101
- p.registerSubscriptions();
2102
- }
2103
- });
2104
- };
2105
- this.allowConcurrentPlayback = !!config?.allowConcurrentPlayback;
2106
- }
2107
- get players() {
2108
- return Array.from(this.pool.values());
2109
- }
2110
- get activeAudioPlayer() {
2111
- return this.state.getLatestValue().activeAudioPlayer;
2112
- }
2113
- /** Removes the AudioPlayer instance from the pool of players */
2114
- deregister(id) {
2115
- if (this.pool.has(id)) {
2116
- this.pool.delete(id);
2117
- }
2118
- if (this.activeAudioPlayer?.id === id) {
2119
- this.setActiveAudioPlayer(null);
2120
- }
2121
- }
2122
- }
2123
- const SEEK_NOT_SUPPORTED_NOTIFICATION_DEBOUNCE_INTERVAL_MS = 1e3;
2124
- const audioPlayerNotificationsPluginFactory = ({
2125
- addNotification,
2126
- panel = "channel",
2127
- t
2128
- }) => {
2129
- const errors = {
2130
- "failed-to-start": new Error(t("Failed to play the recording")),
2131
- "not-playable": new Error(
2132
- t("Recording format is not supported and cannot be reproduced")
2133
- ),
2134
- "seek-not-supported": new Error(t("Cannot seek in the recording"))
2135
- };
2136
- let lastSeekNotSupportedNotificationAt;
2137
- return {
2138
- id: "AudioPlayerNotificationsPlugin",
2139
- onError: ({ errCode, error: e }) => {
2140
- if (errCode === "seek-not-supported") {
2141
- const now = Date.now();
2142
- if (typeof lastSeekNotSupportedNotificationAt === "number" && now - lastSeekNotSupportedNotificationAt < SEEK_NOT_SUPPORTED_NOTIFICATION_DEBOUNCE_INTERVAL_MS) {
2143
- return;
2144
- }
2145
- lastSeekNotSupportedNotificationAt = now;
2146
- }
2147
- const error = (errCode && errors[errCode]) ?? e ?? new Error(t("Error reproducing the recording"));
2148
- addNotification({
2149
- emitter: "AudioPlayer",
2150
- error,
2151
- message: error.message,
2152
- severity: "error",
2153
- targetPanels: [panel],
2154
- type: "browser:audio:playback:error"
2155
- });
2156
- }
2157
- };
2158
- };
2159
- const NOTIFICATION_TARGET_PANELS = [
2160
- "channel",
2161
- "thread",
2162
- "channel-list",
2163
- "thread-list"
2164
- ];
2165
- const isNotificationTargetPanel = (value) => typeof value === "string" && NOTIFICATION_TARGET_PANELS.includes(value);
2166
- const getNotificationTargetPanel = (notification) => {
2167
- const targetTag = notification.tags?.find((tag) => tag.startsWith("target:"));
2168
- if (targetTag) {
2169
- const candidate = targetTag.slice("target:".length);
2170
- if (isNotificationTargetPanel(candidate)) return candidate;
2171
- }
2172
- const panel = notification.origin.context?.panel;
2173
- return isNotificationTargetPanel(panel) ? panel : void 0;
2174
- };
2175
- const getNotificationTargetPanels = (notification) => {
2176
- const targetPanels = (notification.tags ?? []).filter((tag) => tag.startsWith("target:")).map((tag) => tag.slice("target:".length)).filter(
2177
- (value) => isNotificationTargetPanel(value)
2178
- );
2179
- if (targetPanels.length > 0) {
2180
- return Array.from(new Set(targetPanels));
2181
- }
2182
- const panel = notification.origin.context?.panel;
2183
- return isNotificationTargetPanel(panel) ? [panel] : [];
2184
1785
  };
2185
- const getNotificationTargetTag = (panel) => `target:${panel}`;
2186
- const addNotificationTargetTag = (panel, tags) => {
2187
- if (!panel) return tags ?? [];
2188
- return Array.from(/* @__PURE__ */ new Set([getNotificationTargetTag(panel), ...tags ?? []]));
1786
+ const initialState = {
1787
+ error: null,
1788
+ hasMore: true,
1789
+ hasMoreNewer: false,
1790
+ loading: true,
1791
+ loadingMore: false,
1792
+ loadingMoreForJumpToChannelMessage: false,
1793
+ members: {},
1794
+ messages: [],
1795
+ pinnedMessages: [],
1796
+ read: {},
1797
+ suppressAutoscroll: false,
1798
+ thread: null,
1799
+ threadHasMore: true,
1800
+ threadLoadingMore: false,
1801
+ threadMessages: [],
1802
+ threadSuppressAutoscroll: false,
1803
+ typing: {},
1804
+ watcherCount: 0,
1805
+ watchers: {}
2189
1806
  };
2190
- const isNotificationForPanel = (notification, panel, options) => {
2191
- const explicitTargetPanels = getNotificationTargetPanels(notification);
2192
- if (explicitTargetPanels.length > 0) {
2193
- return explicitTargetPanels.includes(panel);
2194
- }
2195
- const resolvedPanel = options?.fallbackPanel ?? "channel";
2196
- return resolvedPanel === panel;
2197
- };
2198
- const variantToClass = {
2199
- danger: "str-chat__button--destructive",
2200
- primary: "str-chat__button--primary",
2201
- secondary: "str-chat__button--secondary"
2202
- };
2203
- const appearanceToClass = {
2204
- ghost: "str-chat__button--ghost",
2205
- outline: "str-chat__button--outline",
2206
- solid: "str-chat__button--solid"
2207
- };
2208
- const sizeToClass = {
2209
- lg: "str-chat__button--size-lg",
2210
- md: "str-chat__button--size-md",
2211
- sm: "str-chat__button--size-sm",
2212
- xs: "str-chat__button--size-xs"
2213
- };
2214
- const Button = React.forwardRef(function Button2({ appearance, children, circular, className, inverseTheme, size, variant, ...props }, ref) {
2215
- return /* @__PURE__ */ jsxRuntime.jsx(
2216
- "button",
2217
- {
2218
- ref,
2219
- type: "button",
2220
- ...props,
2221
- className: clsx(
2222
- "str-chat__button",
2223
- variant != null && variantToClass[variant],
2224
- appearance != null && appearanceToClass[appearance],
2225
- circular && "str-chat__button--circular",
2226
- inverseTheme && "str-chat__theme-inverse",
2227
- size != null && sizeToClass[size],
2228
- className
2229
- ),
2230
- children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__button__content", children })
2231
- }
2232
- );
2233
- });
2234
- const UnMemoizedEmptyStateIndicator = (props) => {
2235
- const { listType, messageText } = props;
2236
- const { t } = useTranslationContext("EmptyStateIndicator");
2237
- if (listType === "thread") return null;
2238
- if (listType === "channel") {
2239
- const text = t("No conversations yet");
2240
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__channel-list-empty", children: [
2241
- /* @__PURE__ */ jsxRuntime.jsx(IconMessageBubbles, {}),
2242
- /* @__PURE__ */ jsxRuntime.jsx("p", { role: "listitem", children: text })
2243
- ] });
2244
- }
2245
- if (listType === "message") {
2246
- const text = t(messageText || "Send a message to start the conversation");
2247
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__empty-channel", children: [
2248
- /* @__PURE__ */ jsxRuntime.jsx(IconMessageBubble, {}),
2249
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "str-chat__empty-channel-text", role: "listitem", children: text })
2250
- ] });
2251
- }
2252
- return /* @__PURE__ */ jsxRuntime.jsx("p", { children: t("No items exist") });
2253
- };
2254
- const EmptyStateIndicator = React.memo(
2255
- UnMemoizedEmptyStateIndicator
2256
- );
2257
- const makeChannelReducer = () => (state, action) => {
2258
- switch (action.type) {
2259
- case "closeThread": {
2260
- return {
2261
- ...state,
2262
- thread: null,
2263
- threadLoadingMore: false,
2264
- threadMessages: []
2265
- };
2266
- }
2267
- case "copyMessagesFromChannel": {
2268
- const { channel, parentId } = action;
2269
- return {
2270
- ...state,
2271
- messages: [...channel.state.messages],
2272
- pinnedMessages: [...channel.state.pinnedMessages],
2273
- // copying messages from channel happens with new message - this resets the suppressAutoscroll
2274
- suppressAutoscroll: false,
2275
- threadMessages: parentId ? { ...channel.state.threads }[parentId] || [] : state.threadMessages
2276
- };
2277
- }
2278
- case "copyStateFromChannelOnEvent": {
2279
- const { channel } = action;
2280
- return {
2281
- ...state,
2282
- members: { ...channel.state.members },
2283
- messages: [...channel.state.messages],
2284
- pinnedMessages: [...channel.state.pinnedMessages],
2285
- read: { ...channel.state.read },
2286
- watcherCount: channel.state.watcher_count,
2287
- watchers: { ...channel.state.watchers }
2288
- };
2289
- }
2290
- case "initStateFromChannel": {
2291
- const { channel, hasMore } = action;
2292
- return {
2293
- ...state,
2294
- hasMore,
2295
- loading: false,
2296
- members: { ...channel.state.members },
2297
- messages: [...channel.state.messages],
2298
- pinnedMessages: [...channel.state.pinnedMessages],
2299
- read: { ...channel.state.read },
2300
- watcherCount: channel.state.watcher_count,
2301
- watchers: { ...channel.state.watchers }
2302
- };
2303
- }
2304
- case "jumpToLatestMessageFinished": {
2305
- const { hasMore, hasMoreNewer, messages } = action;
2306
- return {
2307
- ...state,
2308
- hasMore,
2309
- hasMoreNewer,
2310
- highlightedMessageId: void 0,
2311
- loading: false,
2312
- messages,
2313
- suppressAutoscroll: false
2314
- };
2315
- }
2316
- case "jumpToMessageFinished": {
2317
- return {
2318
- ...state,
2319
- hasMore: action.channel.state.messagePagination.hasPrev,
2320
- hasMoreNewer: action.channel.state.messagePagination.hasNext,
2321
- highlightedMessageId: action.highlightedMessageId,
2322
- loadingMore: false,
2323
- loadingMoreForJumpToChannelMessage: false,
2324
- messages: action.channel.state.messages,
2325
- suppressAutoscroll: false
2326
- };
2327
- }
2328
- case "clearHighlightedMessage": {
2329
- return {
2330
- ...state,
2331
- highlightedMessageId: void 0
2332
- };
2333
- }
2334
- case "loadMoreFinished": {
2335
- const { hasMore, messages } = action;
2336
- return {
2337
- ...state,
2338
- hasMore,
2339
- loadingMore: false,
2340
- messages,
2341
- suppressAutoscroll: false
2342
- };
2343
- }
2344
- case "loadMoreNewerFinished": {
2345
- const { hasMoreNewer, messages } = action;
2346
- return {
2347
- ...state,
2348
- hasMoreNewer,
2349
- loadingMoreNewer: false,
2350
- messages
2351
- };
2352
- }
2353
- case "loadMoreThreadFinished": {
2354
- const { threadHasMore, threadMessages } = action;
2355
- return {
2356
- ...state,
2357
- threadHasMore,
2358
- threadLoadingMore: false,
2359
- threadMessages
2360
- };
2361
- }
2362
- case "openThread": {
2363
- const { channel, message } = action;
2364
- return {
2365
- ...state,
2366
- thread: message,
2367
- threadHasMore: true,
2368
- threadMessages: message.id ? { ...channel.state.threads }[message.id] || [] : [],
2369
- threadSuppressAutoscroll: false
2370
- };
2371
- }
2372
- case "setError": {
2373
- const { error } = action;
2374
- return { ...state, error };
2375
- }
2376
- case "setLoadingMore": {
2377
- const { loadingMore } = action;
2378
- return { ...state, loadingMore, suppressAutoscroll: loadingMore };
2379
- }
2380
- case "setLoadingMoreForJumpToChannelMessage": {
2381
- const { loadingMoreForJumpToChannelMessage } = action;
2382
- return {
2383
- ...state,
2384
- loadingMoreForJumpToChannelMessage,
2385
- suppressAutoscroll: loadingMoreForJumpToChannelMessage
2386
- };
2387
- }
2388
- case "setLoadingMoreNewer": {
2389
- const { loadingMoreNewer } = action;
2390
- return { ...state, loadingMoreNewer };
2391
- }
2392
- case "setThread": {
2393
- const { message } = action;
2394
- return { ...state, thread: message };
2395
- }
2396
- case "setTyping": {
2397
- const { channel } = action;
2398
- return {
2399
- ...state,
2400
- typing: { ...channel.state.typing }
2401
- };
2402
- }
2403
- case "startLoadingThread": {
2404
- return {
2405
- ...state,
2406
- threadLoadingMore: true,
2407
- threadSuppressAutoscroll: true
2408
- };
2409
- }
2410
- case "updateThreadOnEvent": {
2411
- const { channel, message } = action;
2412
- if (!state.thread) return state;
2413
- return {
2414
- ...state,
2415
- thread: message?.id === state.thread.id ? channel.state.formatMessage(message) : state.thread,
2416
- threadMessages: state.thread?.id ? { ...channel.state.threads }[state.thread.id] || [] : []
2417
- };
2418
- }
2419
- default:
2420
- return state;
2421
- }
2422
- };
2423
- const initialState = {
2424
- error: null,
2425
- hasMore: true,
2426
- hasMoreNewer: false,
2427
- loading: true,
2428
- loadingMore: false,
2429
- loadingMoreForJumpToChannelMessage: false,
2430
- members: {},
2431
- messages: [],
2432
- pinnedMessages: [],
2433
- read: {},
2434
- suppressAutoscroll: false,
2435
- thread: null,
2436
- threadHasMore: true,
2437
- threadLoadingMore: false,
2438
- threadMessages: [],
2439
- threadSuppressAutoscroll: false,
2440
- typing: {},
2441
- watcherCount: 0,
2442
- watchers: {}
2443
- };
2444
- const useCreateChannelStateContext = (value) => {
2445
- const {
2446
- channel,
2447
- channelCapabilitiesArray = [],
2448
- channelConfig,
2449
- channelUnreadUiState,
2450
- error,
2451
- giphyVersion,
2452
- hasMore,
2453
- hasMoreNewer,
2454
- highlightedMessageId,
2455
- imageAttachmentSizeHandler,
2456
- loading,
2457
- loadingMore,
2458
- loadingMoreForJumpToChannelMessage,
2459
- members,
2460
- messages = [],
2461
- mutes,
2462
- notifications,
2463
- pinnedMessages,
2464
- read = {},
2465
- shouldGenerateVideoThumbnail,
2466
- skipMessageDataMemoization,
2467
- suppressAutoscroll,
2468
- thread,
2469
- threadHasMore,
2470
- threadLoadingMore,
2471
- threadMessages = [],
2472
- videoAttachmentSizeHandler,
2473
- watcher_count,
2474
- watcherCount,
2475
- watchers
2476
- } = value;
2477
- const channelId = channel.cid;
2478
- const lastRead = channel.initialized && channel.lastRead()?.getTime();
2479
- const membersLength = Object.keys(members || []).length;
2480
- const notificationsLength = notifications.length;
2481
- const readUsers = Object.values(read);
2482
- const readUsersLength = readUsers.length;
2483
- const readUsersLastReadDateStrings = [];
2484
- for (const { last_read } of readUsers) {
2485
- if (!lastRead) continue;
2486
- readUsersLastReadDateStrings.push(last_read?.toISOString());
1807
+ const useCreateChannelStateContext = (value) => {
1808
+ const {
1809
+ channel,
1810
+ channelCapabilitiesArray = [],
1811
+ channelConfig,
1812
+ channelUnreadUiState,
1813
+ error,
1814
+ giphyVersion,
1815
+ hasMore,
1816
+ hasMoreNewer,
1817
+ highlightedMessageId,
1818
+ imageAttachmentSizeHandler,
1819
+ loading,
1820
+ loadingMore,
1821
+ loadingMoreForJumpToChannelMessage,
1822
+ members,
1823
+ messages = [],
1824
+ mutes,
1825
+ notifications,
1826
+ pinnedMessages,
1827
+ read = {},
1828
+ shouldGenerateVideoThumbnail,
1829
+ skipMessageDataMemoization,
1830
+ suppressAutoscroll,
1831
+ thread,
1832
+ threadHasMore,
1833
+ threadLoadingMore,
1834
+ threadMessages = [],
1835
+ videoAttachmentSizeHandler,
1836
+ watcher_count,
1837
+ watcherCount,
1838
+ watchers
1839
+ } = value;
1840
+ const channelId = channel.cid;
1841
+ const lastRead = channel.initialized && channel.lastRead()?.getTime();
1842
+ const membersLength = Object.keys(members || []).length;
1843
+ const notificationsLength = notifications.length;
1844
+ const readUsers = Object.values(read);
1845
+ const readUsersLength = readUsers.length;
1846
+ const readUsersLastReadDateStrings = [];
1847
+ for (const { last_read } of readUsers) {
1848
+ if (!lastRead) continue;
1849
+ readUsersLastReadDateStrings.push(last_read?.toISOString());
2487
1850
  }
2488
1851
  const readUsersLastReads = readUsersLastReadDateStrings.join();
2489
1852
  const threadMessagesLength = threadMessages?.length;
@@ -2600,359 +1963,1067 @@ const useIsMounted = () => {
2600
1963
  return () => {
2601
1964
  isMounted.current = false;
2602
1965
  };
2603
- }, []);
2604
- return isMounted;
2605
- };
2606
- const useMentionsHandlers = (onMentionsHover, onMentionsClick) => React.useCallback(
2607
- (event, mentioned_users) => {
2608
- if (!onMentionsHover && !onMentionsClick || !(event.target instanceof HTMLElement)) {
2609
- return;
2610
- }
2611
- const target = event.target;
2612
- const textContent = target.innerHTML.replace("*", "");
2613
- if (textContent[0] === "@") {
2614
- const userName = textContent.replace("@", "");
2615
- const user = mentioned_users?.find(
2616
- ({ id, name }) => name === userName || id === userName
2617
- );
2618
- if (onMentionsHover && typeof onMentionsHover === "function" && event.type === "mouseover") {
2619
- onMentionsHover(event, user);
2620
- }
2621
- if (onMentionsClick && event.type === "click" && typeof onMentionsClick === "function") {
2622
- onMentionsClick(event, user);
2623
- }
2624
- }
2625
- },
2626
- [onMentionsClick, onMentionsHover]
2627
- );
2628
- const LoadingMessage = ({
2629
- bubbleSize,
2630
- metadataSize,
2631
- outgoing = false
2632
- }) => /* @__PURE__ */ jsxRuntime.jsxs(
2633
- "div",
2634
- {
2635
- className: `str-chat__loading-channel-message ${outgoing ? "str-chat__loading-channel-message--outgoing" : "str-chat__loading-channel-message--incoming"}`,
2636
- children: [
2637
- !outgoing ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__loading-channel-message-avatar" }) : null,
2638
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__loading-channel-message-content", children: [
2639
- /* @__PURE__ */ jsxRuntime.jsx(
2640
- "div",
2641
- {
2642
- className: `str-chat__loading-channel-message-bubble str-chat__loading-channel-message-bubble--${bubbleSize}`
2643
- }
2644
- ),
2645
- /* @__PURE__ */ jsxRuntime.jsx(
2646
- "div",
2647
- {
2648
- className: `str-chat__loading-channel-message-metadata str-chat__loading-channel-message-metadata--${metadataSize}`
2649
- }
2650
- )
2651
- ] })
2652
- ]
2653
- }
2654
- );
2655
- const LoadingMessageInput = () => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__message-composer-container str-chat__message-composer-container--loading", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__message-composer", children: [
2656
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__loading-channel-message-input-button" }),
2657
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__loading-channel-message-input-pill" })
2658
- ] }) });
2659
- const LoadingChannelHeader = () => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__channel-header str-chat__channel-header--loading", children: [
2660
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__channel-header__data str-chat__channel-header__data--loading", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__loading-channel-header-name" }) }),
2661
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__loading-channel-header-avatar" })
2662
- ] });
2663
- const LoadingChannel = () => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__loading-channel", children: [
2664
- /* @__PURE__ */ jsxRuntime.jsx(LoadingChannelHeader, {}),
2665
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__message-list str-chat__message-list--loading", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__message-list-scroll", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__loading-channel-message-list", children: [
2666
- /* @__PURE__ */ jsxRuntime.jsx(LoadingMessage, { bubbleSize: "lg", metadataSize: "md" }),
2667
- /* @__PURE__ */ jsxRuntime.jsx(LoadingMessage, { bubbleSize: "md", metadataSize: "sm", outgoing: true }),
2668
- /* @__PURE__ */ jsxRuntime.jsx(LoadingMessage, { bubbleSize: "lg", metadataSize: "md" })
2669
- ] }) }) }),
2670
- /* @__PURE__ */ jsxRuntime.jsx(LoadingMessageInput, {})
2671
- ] });
2672
- const UnMemoizedLoadingErrorIndicator = ({ error }) => {
2673
- const { t } = useTranslationContext("LoadingErrorIndicator");
2674
- if (!error) return null;
2675
- return /* @__PURE__ */ jsxRuntime.jsx("div", { children: t("Error: {{ errorMessage }}", { errorMessage: error.message }) });
2676
- };
2677
- const LoadingErrorIndicator = React.memo(
2678
- UnMemoizedLoadingErrorIndicator,
2679
- (prevProps, nextProps) => prevProps.error?.message === nextProps.error?.message
2680
- );
2681
- const CHANNEL_CONTAINER_ID = "str-chat__channel";
2682
- const DEFAULT_NEXT_CHANNEL_PAGE_SIZE = 25;
2683
- const DEFAULT_JUMP_TO_PAGE_SIZE = 25;
2684
- const DEFAULT_THREAD_PAGE_SIZE = 25;
2685
- const DEFAULT_LOAD_PAGE_SCROLL_THRESHOLD = 250;
2686
- const DEFAULT_HIGHLIGHT_DURATION = 500;
2687
- const validateAndGetMessage = (func, args) => {
2688
- if (!func || typeof func !== "function") return null;
2689
- if (!Array.isArray(args)) {
2690
- args = [args];
2691
- }
2692
- const returnValue = func(...args);
2693
- if (typeof returnValue !== "string") return null;
2694
- return returnValue;
2695
- };
2696
- const isUserMuted = (message, mutes) => {
2697
- if (!mutes || !message) return false;
2698
- const userMuted = mutes.filter((el) => el.target.id === message.user?.id);
2699
- return !!userMuted.length;
2700
- };
2701
- const OPTIONAL_MESSAGE_ACTIONS = {
2702
- deleteForMe: "deleteForMe"
2703
- };
2704
- const MESSAGE_ACTIONS = {
2705
- delete: "delete",
2706
- download: "download",
2707
- edit: "edit",
2708
- flag: "flag",
2709
- markUnread: "markUnread",
2710
- mute: "mute",
2711
- pin: "pin",
2712
- quote: "quote",
2713
- react: "react",
2714
- remindMe: "remindMe",
2715
- reply: "reply",
2716
- saveForLater: "saveForLater"
2717
- };
2718
- const getMessageActions = (actions, {
2719
- canDelete,
2720
- canEdit,
2721
- canFlag,
2722
- canMarkUnread,
2723
- canMute,
2724
- canPin,
2725
- canQuote,
2726
- canReact,
2727
- canReply
2728
- }, channelConfig) => {
2729
- const messageActionsAfterPermission = [];
2730
- let messageActions = [];
2731
- if (actions && typeof actions === "boolean") {
2732
- messageActions = Object.keys(MESSAGE_ACTIONS);
2733
- } else if (actions && Array.isArray(actions) && actions.length > 0) {
2734
- messageActions = [...actions];
2735
- } else {
2736
- return [];
2737
- }
2738
- if (canDelete && messageActions.indexOf(MESSAGE_ACTIONS.delete) > -1) {
2739
- messageActionsAfterPermission.push(MESSAGE_ACTIONS.delete);
1966
+ }, []);
1967
+ return isMounted;
1968
+ };
1969
+ const useMentionsHandlers = (onMentionsHover, onMentionsClick) => React.useCallback(
1970
+ (event, mentioned_users) => {
1971
+ if (!onMentionsHover && !onMentionsClick || !(event.target instanceof HTMLElement)) {
1972
+ return;
1973
+ }
1974
+ const target = event.target;
1975
+ const textContent = target.innerHTML.replace("*", "");
1976
+ if (textContent[0] === "@") {
1977
+ const userName = textContent.replace("@", "");
1978
+ const user = mentioned_users?.find(
1979
+ ({ id, name }) => name === userName || id === userName
1980
+ );
1981
+ if (onMentionsHover && typeof onMentionsHover === "function" && event.type === "mouseover") {
1982
+ onMentionsHover(event, user);
1983
+ }
1984
+ if (onMentionsClick && event.type === "click" && typeof onMentionsClick === "function") {
1985
+ onMentionsClick(event, user);
1986
+ }
1987
+ }
1988
+ },
1989
+ [onMentionsClick, onMentionsHover]
1990
+ );
1991
+ const LoadingMessage = ({
1992
+ bubbleSize,
1993
+ metadataSize,
1994
+ outgoing = false
1995
+ }) => /* @__PURE__ */ jsxRuntime.jsxs(
1996
+ "div",
1997
+ {
1998
+ className: `str-chat__loading-channel-message ${outgoing ? "str-chat__loading-channel-message--outgoing" : "str-chat__loading-channel-message--incoming"}`,
1999
+ children: [
2000
+ !outgoing ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__loading-channel-message-avatar" }) : null,
2001
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__loading-channel-message-content", children: [
2002
+ /* @__PURE__ */ jsxRuntime.jsx(
2003
+ "div",
2004
+ {
2005
+ className: `str-chat__loading-channel-message-bubble str-chat__loading-channel-message-bubble--${bubbleSize}`
2006
+ }
2007
+ ),
2008
+ /* @__PURE__ */ jsxRuntime.jsx(
2009
+ "div",
2010
+ {
2011
+ className: `str-chat__loading-channel-message-metadata str-chat__loading-channel-message-metadata--${metadataSize}`
2012
+ }
2013
+ )
2014
+ ] })
2015
+ ]
2016
+ }
2017
+ );
2018
+ const LoadingMessageInput = () => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__message-composer-container str-chat__message-composer-container--loading", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__message-composer", children: [
2019
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__loading-channel-message-input-button" }),
2020
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__loading-channel-message-input-pill" })
2021
+ ] }) });
2022
+ const LoadingChannelHeader = () => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__channel-header str-chat__channel-header--loading", children: [
2023
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__channel-header__data str-chat__channel-header__data--loading", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__loading-channel-header-name" }) }),
2024
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__loading-channel-header-avatar" })
2025
+ ] });
2026
+ const LoadingChannel = () => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__loading-channel", children: [
2027
+ /* @__PURE__ */ jsxRuntime.jsx(LoadingChannelHeader, {}),
2028
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__message-list str-chat__message-list--loading", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__message-list-scroll", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__loading-channel-message-list", children: [
2029
+ /* @__PURE__ */ jsxRuntime.jsx(LoadingMessage, { bubbleSize: "lg", metadataSize: "md" }),
2030
+ /* @__PURE__ */ jsxRuntime.jsx(LoadingMessage, { bubbleSize: "md", metadataSize: "sm", outgoing: true }),
2031
+ /* @__PURE__ */ jsxRuntime.jsx(LoadingMessage, { bubbleSize: "lg", metadataSize: "md" })
2032
+ ] }) }) }),
2033
+ /* @__PURE__ */ jsxRuntime.jsx(LoadingMessageInput, {})
2034
+ ] });
2035
+ const UnMemoizedLoadingErrorIndicator = ({ error }) => {
2036
+ const { t } = useTranslationContext("LoadingErrorIndicator");
2037
+ if (!error) return null;
2038
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { children: t("Error: {{ errorMessage }}", { errorMessage: error.message }) });
2039
+ };
2040
+ const LoadingErrorIndicator = React.memo(
2041
+ UnMemoizedLoadingErrorIndicator,
2042
+ (prevProps, nextProps) => prevProps.error?.message === nextProps.error?.message
2043
+ );
2044
+ const CHANNEL_CONTAINER_ID = "str-chat__channel";
2045
+ const DEFAULT_NEXT_CHANNEL_PAGE_SIZE = 25;
2046
+ const DEFAULT_JUMP_TO_PAGE_SIZE = 25;
2047
+ const DEFAULT_THREAD_PAGE_SIZE = 25;
2048
+ const DEFAULT_LOAD_PAGE_SCROLL_THRESHOLD = 250;
2049
+ const DEFAULT_HIGHLIGHT_DURATION = 500;
2050
+ const validateAndGetMessage = (func, args) => {
2051
+ if (!func || typeof func !== "function") return null;
2052
+ if (!Array.isArray(args)) {
2053
+ args = [args];
2054
+ }
2055
+ const returnValue = func(...args);
2056
+ if (typeof returnValue !== "string") return null;
2057
+ return returnValue;
2058
+ };
2059
+ const isUserMuted = (message, mutes) => {
2060
+ if (!mutes || !message) return false;
2061
+ const userMuted = mutes.filter((el) => el.target.id === message.user?.id);
2062
+ return !!userMuted.length;
2063
+ };
2064
+ const OPTIONAL_MESSAGE_ACTIONS = {
2065
+ deleteForMe: "deleteForMe"
2066
+ };
2067
+ const MESSAGE_ACTIONS = {
2068
+ delete: "delete",
2069
+ download: "download",
2070
+ edit: "edit",
2071
+ flag: "flag",
2072
+ markUnread: "markUnread",
2073
+ mute: "mute",
2074
+ pin: "pin",
2075
+ quote: "quote",
2076
+ react: "react",
2077
+ remindMe: "remindMe",
2078
+ reply: "reply",
2079
+ saveForLater: "saveForLater"
2080
+ };
2081
+ const getMessageActions = (actions, {
2082
+ canDelete,
2083
+ canEdit,
2084
+ canFlag,
2085
+ canMarkUnread,
2086
+ canMute,
2087
+ canPin,
2088
+ canQuote,
2089
+ canReact,
2090
+ canReply
2091
+ }, channelConfig) => {
2092
+ const messageActionsAfterPermission = [];
2093
+ let messageActions = [];
2094
+ if (actions && typeof actions === "boolean") {
2095
+ messageActions = Object.keys(MESSAGE_ACTIONS);
2096
+ } else if (actions && Array.isArray(actions) && actions.length > 0) {
2097
+ messageActions = [...actions];
2098
+ } else {
2099
+ return [];
2100
+ }
2101
+ if (canDelete && messageActions.indexOf(MESSAGE_ACTIONS.delete) > -1) {
2102
+ messageActionsAfterPermission.push(MESSAGE_ACTIONS.delete);
2103
+ }
2104
+ if (messageActions.indexOf(MESSAGE_ACTIONS.download) > -1) {
2105
+ messageActionsAfterPermission.push(MESSAGE_ACTIONS.download);
2106
+ }
2107
+ if (canDelete && messageActions.indexOf(OPTIONAL_MESSAGE_ACTIONS.deleteForMe) > -1) {
2108
+ messageActionsAfterPermission.push(OPTIONAL_MESSAGE_ACTIONS.deleteForMe);
2109
+ }
2110
+ if (canEdit && messageActions.indexOf(MESSAGE_ACTIONS.edit) > -1) {
2111
+ messageActionsAfterPermission.push(MESSAGE_ACTIONS.edit);
2112
+ }
2113
+ if (canFlag && messageActions.indexOf(MESSAGE_ACTIONS.flag) > -1) {
2114
+ messageActionsAfterPermission.push(MESSAGE_ACTIONS.flag);
2115
+ }
2116
+ if (canMarkUnread && messageActions.indexOf(MESSAGE_ACTIONS.markUnread) > -1) {
2117
+ messageActionsAfterPermission.push(MESSAGE_ACTIONS.markUnread);
2118
+ }
2119
+ if (canMute && messageActions.indexOf(MESSAGE_ACTIONS.mute) > -1) {
2120
+ messageActionsAfterPermission.push(MESSAGE_ACTIONS.mute);
2121
+ }
2122
+ if (canPin && messageActions.indexOf(MESSAGE_ACTIONS.pin) > -1) {
2123
+ messageActionsAfterPermission.push(MESSAGE_ACTIONS.pin);
2124
+ }
2125
+ if (canQuote && messageActions.indexOf(MESSAGE_ACTIONS.quote) > -1) {
2126
+ messageActionsAfterPermission.push(MESSAGE_ACTIONS.quote);
2127
+ }
2128
+ if (canReact && messageActions.indexOf(MESSAGE_ACTIONS.react) > -1) {
2129
+ messageActionsAfterPermission.push(MESSAGE_ACTIONS.react);
2130
+ }
2131
+ if (channelConfig?.["user_message_reminders"] && messageActions.indexOf(MESSAGE_ACTIONS.remindMe) > -1) {
2132
+ messageActionsAfterPermission.push(MESSAGE_ACTIONS.remindMe);
2133
+ }
2134
+ if (canReply && messageActions.indexOf(MESSAGE_ACTIONS.reply) > -1) {
2135
+ messageActionsAfterPermission.push(MESSAGE_ACTIONS.reply);
2136
+ }
2137
+ if (channelConfig?.["user_message_reminders"] && messageActions.indexOf(MESSAGE_ACTIONS.saveForLater) > -1) {
2138
+ messageActionsAfterPermission.push(MESSAGE_ACTIONS.saveForLater);
2139
+ }
2140
+ return messageActionsAfterPermission;
2141
+ };
2142
+ const ACTIONS_NOT_WORKING_IN_THREAD = [
2143
+ MESSAGE_ACTIONS.pin,
2144
+ MESSAGE_ACTIONS.reply,
2145
+ MESSAGE_ACTIONS.markUnread
2146
+ ];
2147
+ function areMessagesEqual(prevMessage, nextMessage) {
2148
+ const areBaseMessagesEqual = (prevMessage2, nextMessage2) => prevMessage2.deleted_at === nextMessage2.deleted_at && prevMessage2.latest_reactions?.length === nextMessage2.latest_reactions?.length && prevMessage2.own_reactions?.length === nextMessage2.own_reactions?.length && prevMessage2.pinned === nextMessage2.pinned && prevMessage2.reply_count === nextMessage2.reply_count && prevMessage2.show_in_channel === nextMessage2.show_in_channel && prevMessage2.status === nextMessage2.status && prevMessage2.text === nextMessage2.text && prevMessage2.type === nextMessage2.type && prevMessage2.updated_at === nextMessage2.updated_at && prevMessage2.user?.updated_at === nextMessage2.user?.updated_at;
2149
+ return areBaseMessagesEqual(prevMessage, nextMessage) && Boolean(prevMessage.quoted_message) === Boolean(nextMessage.quoted_message) && (!prevMessage.quoted_message && !nextMessage.quoted_message || areBaseMessagesEqual(
2150
+ prevMessage.quoted_message,
2151
+ nextMessage.quoted_message
2152
+ ));
2153
+ }
2154
+ const areMessagePropsEqual = (prevProps, nextProps) => {
2155
+ const { message: prevMessage, Message: prevMessageUI } = prevProps;
2156
+ const { message: nextMessage, Message: nextMessageUI } = nextProps;
2157
+ if (prevMessageUI !== nextMessageUI) return false;
2158
+ if (nextProps.showDetailedReactions !== prevProps.showDetailedReactions) {
2159
+ return false;
2160
+ }
2161
+ if (nextProps.closeReactionSelectorOnClick !== prevProps.closeReactionSelectorOnClick) {
2162
+ return false;
2163
+ }
2164
+ const messagesAreEqual = areMessagesEqual(prevMessage, nextMessage);
2165
+ if (!messagesAreEqual) return false;
2166
+ const deepEqualProps = deepequal(nextProps.messageActions, prevProps.messageActions) && deepequal(nextProps.readBy, prevProps.readBy) && deepequal(nextProps.deliveredTo, prevProps.deliveredTo) && deepequal(nextProps.highlighted, prevProps.highlighted) && deepequal(nextProps.groupStyles, prevProps.groupStyles) && // last 3 messages can have different group styles
2167
+ deepequal(nextProps.mutes, prevProps.mutes) && deepequal(nextProps.lastReceivedId, prevProps.lastReceivedId);
2168
+ if (!deepEqualProps) return false;
2169
+ return prevProps.messageListRect === nextProps.messageListRect;
2170
+ };
2171
+ const areMessageUIPropsEqual = (prevProps, nextProps) => {
2172
+ const { lastReceivedId: prevLastReceivedId, message: prevMessage } = prevProps;
2173
+ const { lastReceivedId: nextLastReceivedId, message: nextMessage } = nextProps;
2174
+ if (prevProps.highlighted !== nextProps.highlighted) return false;
2175
+ if (prevProps.threadList !== nextProps.threadList) return false;
2176
+ if (prevProps.endOfGroup !== nextProps.endOfGroup) return false;
2177
+ if (prevProps.mutes?.length !== nextProps.mutes?.length) return false;
2178
+ if (prevProps.readBy?.length !== nextProps.readBy?.length) return false;
2179
+ if (prevProps.deliveredTo?.length !== nextProps.deliveredTo?.length) return false;
2180
+ if (prevProps.groupStyles !== nextProps.groupStyles) return false;
2181
+ if (prevProps.showDetailedReactions !== nextProps.showDetailedReactions) {
2182
+ return false;
2183
+ }
2184
+ if ((prevMessage.id === prevLastReceivedId || prevMessage.id === nextLastReceivedId) && prevLastReceivedId !== nextLastReceivedId) {
2185
+ return false;
2186
+ }
2187
+ return areMessagesEqual(prevMessage, nextMessage);
2188
+ };
2189
+ const messageHasReactions = (message) => Object.values(message?.reaction_groups ?? {}).some(({ count }) => count > 0);
2190
+ const messageHasQuotedMessage = (message) => !!message?.quoted_message;
2191
+ const messageHasAttachments = (message) => !!message?.attachments && !!message.attachments.length;
2192
+ const messageHasSingleAttachment = (message) => message?.attachments?.length === 1;
2193
+ const messageHasGiphyAttachment = (message) => !!message?.attachments?.some((att) => att.type === "giphy");
2194
+ const getImages = (message) => {
2195
+ if (!message?.attachments) {
2196
+ return [];
2197
+ }
2198
+ return message.attachments.filter((item) => item.type === "image");
2199
+ };
2200
+ const getNonImageAttachments = (message) => {
2201
+ if (!message?.attachments) {
2202
+ return [];
2203
+ }
2204
+ return message.attachments.filter((item) => item.type !== "image");
2205
+ };
2206
+ const mapToUserNameOrId = (user) => user.name || user.id;
2207
+ const getReadByTooltipText = (users, t, client, tooltipUserNameMapper) => {
2208
+ let outStr = "";
2209
+ if (!t) {
2210
+ throw new Error(
2211
+ "getReadByTooltipText was called, but translation function is not available"
2212
+ );
2213
+ }
2214
+ if (!tooltipUserNameMapper) {
2215
+ throw new Error(
2216
+ "getReadByTooltipText was called, but tooltipUserNameMapper function is not available"
2217
+ );
2218
+ }
2219
+ const otherUsers = users.filter((item) => item && client?.user && item.id !== client.user.id).map(tooltipUserNameMapper);
2220
+ const slicedArr = otherUsers.slice(0, 5);
2221
+ const restLength = otherUsers.length - slicedArr.length;
2222
+ if (slicedArr.length === 1) {
2223
+ outStr = `${slicedArr[0]} `;
2224
+ } else if (slicedArr.length === 2) {
2225
+ outStr = t("{{ firstUser }} and {{ secondUser }}", {
2226
+ firstUser: slicedArr[0],
2227
+ secondUser: slicedArr[1]
2228
+ });
2229
+ } else if (slicedArr.length > 2) {
2230
+ if (restLength === 0) {
2231
+ const lastUser = slicedArr.splice(slicedArr.length - 1, 1);
2232
+ outStr = t("{{ commaSeparatedUsers }}, and {{ lastUser }}", {
2233
+ commaSeparatedUsers: slicedArr.join(", "),
2234
+ lastUser
2235
+ });
2236
+ } else {
2237
+ outStr = t("{{ commaSeparatedUsers }} and {{ moreCount }} more", {
2238
+ commaSeparatedUsers: slicedArr.join(", "),
2239
+ moreCount: restLength
2240
+ });
2241
+ }
2242
+ }
2243
+ return outStr;
2244
+ };
2245
+ const countEmojis = (text) => {
2246
+ const matches = text?.match(emojiRegex());
2247
+ return matches ? matches.length : 0;
2248
+ };
2249
+ const messageTextHasEmojisOnly = (message) => {
2250
+ if (!message.text) return false;
2251
+ const noEmojis = message.text.replace(emojiRegex(), "");
2252
+ const noSpace = noEmojis.replace(/[\s\n]/gm, "");
2253
+ return !noSpace;
2254
+ };
2255
+ const isMessageErrorRetryable = (message) => message.status === "failed" && message.error?.status !== 403;
2256
+ const isNetworkSendFailure = (message) => message.status === "failed" && message.error?.status === 0;
2257
+ const isMessageBounced = (message) => message.type === "error" && (message.moderation_details?.action === "MESSAGE_RESPONSE_ACTION_BOUNCE" || message.moderation?.action === "bounce");
2258
+ const isMessageBlocked = (message) => message.shadowed || message.type === "error" && (message.moderation_details?.action === "MESSAGE_RESPONSE_ACTION_REMOVE" || message.moderation?.action === "remove");
2259
+ const isMessageDeleted = (message) => Boolean(message.deleted_at || message.type === "deleted" || message.deleted_for_me);
2260
+ const isMessageEdited = (message) => !!message.message_text_updated_at;
2261
+ const hasResizeObserver = typeof window !== "undefined" && "ResizeObserver" in window;
2262
+ function autoMiddlewareFor(p) {
2263
+ if (!String(p).startsWith("auto")) return null;
2264
+ const alignment = p === "auto-start" ? "start" : p === "auto-end" ? "end" : void 0;
2265
+ return react.autoPlacement({ alignment });
2266
+ }
2267
+ function toOffsetMw(opt) {
2268
+ if (opt == null) return null;
2269
+ if (Array.isArray(opt)) {
2270
+ const [crossAxis, mainAxis] = opt;
2271
+ return react.offset({ crossAxis, mainAxis });
2272
+ }
2273
+ if (typeof opt === "number") return react.offset(opt);
2274
+ return react.offset(opt);
2275
+ }
2276
+ function usePopoverPosition({
2277
+ allowFlip = true,
2278
+ allowShift = true,
2279
+ autoUpdateOptions,
2280
+ fitAvailableSpace = false,
2281
+ freeze = false,
2282
+ offset,
2283
+ placement = "bottom-start",
2284
+ shiftOptions
2285
+ }) {
2286
+ const autoMw = autoMiddlewareFor(placement);
2287
+ const offsetMiddleware = toOffsetMw(offset);
2288
+ const isSidePlacement = placement.startsWith("left") || placement.startsWith("right");
2289
+ const mergedShiftOptions = shiftOptions ? { padding: 8, ...shiftOptions } : { padding: 8 };
2290
+ const middleware = [
2291
+ // offset first (mirrors common Popper setups)
2292
+ ...offsetMiddleware ? [offsetMiddleware] : [],
2293
+ // choose between autoPlacement (Popper's "auto*") OR flip()
2294
+ // only allow flip when not explicitly 'left*' or 'right*'
2295
+ ...autoMw ? [autoMw] : allowFlip && !isSidePlacement ? [react.flip()] : [],
2296
+ // viewport collision adjustments
2297
+ ...allowShift ? [react.shift(mergedShiftOptions)] : [],
2298
+ // optional size constraining
2299
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
2300
+ ...fitAvailableSpace ? [react.size({ apply: () => {
2301
+ } })] : []
2302
+ ];
2303
+ const seedPlacement = String(placement).startsWith("auto") ? "bottom" : placement;
2304
+ return react.useFloating({
2305
+ middleware,
2306
+ placement: seedPlacement,
2307
+ strategy: "fixed",
2308
+ whileElementsMounted: freeze ? void 0 : (reference, floating, update) => react.autoUpdate(reference, floating, update, {
2309
+ ancestorResize: true,
2310
+ ancestorScroll: true,
2311
+ animationFrame: false,
2312
+ elementResize: hasResizeObserver,
2313
+ ...autoUpdateOptions
2314
+ })
2315
+ });
2316
+ }
2317
+ const LegacyThreadContext = React.createContext({ legacyThread: void 0 });
2318
+ const useLegacyThreadContext = () => React.useContext(LegacyThreadContext);
2319
+ const DEFAULT_PLAYBACK_RATES = [1, 1.5, 2];
2320
+ const isSeekable = (audioElement) => !(audioElement.duration === Infinity || isNaN(audioElement.duration));
2321
+ const defaultRegisterAudioPlayerError = ({
2322
+ error
2323
+ } = {}) => {
2324
+ if (!error) return;
2325
+ console.error("[AUDIO PLAYER]", error);
2326
+ };
2327
+ const elementIsPlaying = (audioElement) => audioElement && !(audioElement.paused || audioElement.ended);
2328
+ class AudioPlayer {
2329
+ constructor({
2330
+ durationSeconds,
2331
+ fileSize,
2332
+ id,
2333
+ mimeType,
2334
+ playbackRates: customPlaybackRates,
2335
+ plugins,
2336
+ pool,
2337
+ src,
2338
+ title,
2339
+ waveformData
2340
+ }) {
2341
+ this._plugins = /* @__PURE__ */ new Map();
2342
+ this.playTimeout = void 0;
2343
+ this.unsubscribeEventListeners = null;
2344
+ this._disposed = false;
2345
+ this._metadataProbe = null;
2346
+ this._restoringPosition = false;
2347
+ this._removalTimeout = void 0;
2348
+ this.setDurationSeconds = (durationSeconds2) => {
2349
+ this._data.durationSeconds = durationSeconds2;
2350
+ this.state.partialNext({ durationSeconds: durationSeconds2 });
2351
+ };
2352
+ this.setPlaybackStartSafetyTimeout = () => {
2353
+ clearTimeout(this.playTimeout);
2354
+ this.playTimeout = setTimeout(() => {
2355
+ if (!this.elementRef) return;
2356
+ try {
2357
+ this.elementRef.pause();
2358
+ this.state.partialNext({ isPlaying: false });
2359
+ } catch (e) {
2360
+ this.registerError({ errCode: "failed-to-start" });
2361
+ }
2362
+ }, 2e3);
2363
+ };
2364
+ this.updateDurationFromElement = (element) => {
2365
+ const duration = element.duration;
2366
+ if (typeof duration !== "number" || isNaN(duration) || !isFinite(duration) || duration <= 0) {
2367
+ return;
2368
+ }
2369
+ this.setDurationSeconds(duration);
2370
+ };
2371
+ this.clearMetadataProbe = () => {
2372
+ const probe = this._metadataProbe;
2373
+ this._metadataProbe = null;
2374
+ this._metadataProbePromise = void 0;
2375
+ if (!probe) return;
2376
+ try {
2377
+ probe.pause();
2378
+ } catch {
2379
+ }
2380
+ probe.removeAttribute("src");
2381
+ try {
2382
+ probe.load();
2383
+ } catch {
2384
+ }
2385
+ };
2386
+ this.preloadMetadata = () => {
2387
+ if (this._disposed || this.durationSeconds != null || !this.src || this._metadataProbePromise || typeof document === "undefined") {
2388
+ return;
2389
+ }
2390
+ const probe = document.createElement("audio");
2391
+ probe.preload = "metadata";
2392
+ this._metadataProbe = probe;
2393
+ this._metadataProbePromise = new Promise((resolve) => {
2394
+ const cleanup = () => {
2395
+ probe.removeEventListener("loadedmetadata", handleLoadedMetadata);
2396
+ probe.removeEventListener("error", handleError);
2397
+ if (this._metadataProbe === probe) {
2398
+ this.clearMetadataProbe();
2399
+ } else {
2400
+ this._metadataProbePromise = void 0;
2401
+ }
2402
+ resolve();
2403
+ };
2404
+ const handleLoadedMetadata = () => {
2405
+ this.updateDurationFromElement(probe);
2406
+ cleanup();
2407
+ };
2408
+ const handleError = () => {
2409
+ cleanup();
2410
+ };
2411
+ probe.addEventListener("loadedmetadata", handleLoadedMetadata, { once: true });
2412
+ probe.addEventListener("error", handleError, { once: true });
2413
+ probe.src = this.src;
2414
+ try {
2415
+ probe.load();
2416
+ } catch {
2417
+ cleanup();
2418
+ }
2419
+ });
2420
+ };
2421
+ this.clearPlaybackStartSafetyTimeout = () => {
2422
+ if (!this.elementRef) return;
2423
+ clearTimeout(this.playTimeout);
2424
+ this.playTimeout = void 0;
2425
+ };
2426
+ this.clearPendingLoadedMeta = () => {
2427
+ const pending = this._pendingLoadedMeta;
2428
+ if (pending?.element && pending.onLoaded) {
2429
+ pending.element.removeEventListener("loadedmetadata", pending.onLoaded);
2430
+ }
2431
+ this._pendingLoadedMeta = void 0;
2432
+ };
2433
+ this.restoreSavedPosition = (elementRef) => {
2434
+ const saved = this.secondsElapsed;
2435
+ if (!saved || saved <= 0) return;
2436
+ const apply = () => {
2437
+ const duration = elementRef.duration;
2438
+ const clamped = typeof duration === "number" && !isNaN(duration) && isFinite(duration) ? Math.min(saved, duration) : saved;
2439
+ try {
2440
+ if (elementRef.currentTime === clamped) return;
2441
+ elementRef.currentTime = clamped;
2442
+ this.setSecondsElapsed(clamped);
2443
+ } catch {
2444
+ }
2445
+ };
2446
+ if (elementRef.readyState < 1) {
2447
+ this.clearPendingLoadedMeta();
2448
+ this._restoringPosition = true;
2449
+ const onLoaded = () => {
2450
+ if (this._pendingLoadedMeta?.onLoaded !== onLoaded) return;
2451
+ this._pendingLoadedMeta = void 0;
2452
+ if (this.elementRef !== elementRef) {
2453
+ this._restoringPosition = false;
2454
+ return;
2455
+ }
2456
+ apply();
2457
+ this._restoringPosition = false;
2458
+ };
2459
+ elementRef.addEventListener("loadedmetadata", onLoaded, { once: true });
2460
+ this._pendingLoadedMeta = { element: elementRef, onLoaded };
2461
+ } else {
2462
+ this._restoringPosition = true;
2463
+ apply();
2464
+ this._restoringPosition = false;
2465
+ }
2466
+ };
2467
+ this.elementIsReady = () => {
2468
+ if (this._elementIsReadyPromise) return this._elementIsReadyPromise;
2469
+ this._elementIsReadyPromise = new Promise((resolve) => {
2470
+ if (!this.elementRef) return resolve(false);
2471
+ const element = this.elementRef;
2472
+ const handleLoaded = () => {
2473
+ element.removeEventListener("loadedmetadata", handleLoaded);
2474
+ resolve(element.readyState > 0);
2475
+ };
2476
+ element.addEventListener("loadedmetadata", handleLoaded);
2477
+ });
2478
+ return this._elementIsReadyPromise;
2479
+ };
2480
+ this.setRef = (elementRef) => {
2481
+ if (elementIsPlaying(this.elementRef)) {
2482
+ this.releaseElement({ resetState: false });
2483
+ }
2484
+ this.clearPendingLoadedMeta();
2485
+ this.clearMetadataProbe();
2486
+ this._restoringPosition = false;
2487
+ this._elementIsReadyPromise = void 0;
2488
+ this.state.partialNext({ elementRef });
2489
+ if (elementRef) {
2490
+ this.registerSubscriptions();
2491
+ }
2492
+ };
2493
+ this.setSecondsElapsed = (secondsElapsed) => {
2494
+ const duration = this.elementRef?.duration ?? this.durationSeconds;
2495
+ this.state.partialNext({
2496
+ progressPercent: duration && secondsElapsed ? secondsElapsed / duration * 100 : 0,
2497
+ secondsElapsed
2498
+ });
2499
+ };
2500
+ this.canPlayMimeType = (mimeType2) => {
2501
+ if (!mimeType2) return false;
2502
+ if (this.elementRef) return !!this.elementRef.canPlayType(mimeType2);
2503
+ return !!new Audio().canPlayType(mimeType2);
2504
+ };
2505
+ this.play = async (params) => {
2506
+ if (this._disposed) return;
2507
+ const elementRef = this.ensureElementRef();
2508
+ if (elementIsPlaying(this.elementRef)) {
2509
+ if (this.isPlaying) return;
2510
+ this.state.partialNext({ isPlaying: true });
2511
+ return;
2512
+ }
2513
+ const { currentPlaybackRate, playbackRates: playbackRates2 } = {
2514
+ currentPlaybackRate: this.currentPlaybackRate,
2515
+ playbackRates: this.playbackRates,
2516
+ ...params
2517
+ };
2518
+ if (!this.canPlayRecord) {
2519
+ this.registerError({ errCode: "not-playable" });
2520
+ return;
2521
+ }
2522
+ this.restoreSavedPosition(elementRef);
2523
+ elementRef.playbackRate = currentPlaybackRate ?? this.currentPlaybackRate;
2524
+ this.setPlaybackStartSafetyTimeout();
2525
+ try {
2526
+ await elementRef.play();
2527
+ this.state.partialNext({
2528
+ currentPlaybackRate,
2529
+ isPlaying: true,
2530
+ playbackRates: playbackRates2
2531
+ });
2532
+ this._pool.setActiveAudioPlayer(this);
2533
+ } catch (e) {
2534
+ this.registerError({ error: e });
2535
+ this.state.partialNext({ isPlaying: false });
2536
+ } finally {
2537
+ this.clearPlaybackStartSafetyTimeout();
2538
+ }
2539
+ };
2540
+ this.pause = () => {
2541
+ if (!elementIsPlaying(this.elementRef)) return;
2542
+ this.clearPlaybackStartSafetyTimeout();
2543
+ this.elementRef.pause();
2544
+ this.state.partialNext({ isPlaying: false });
2545
+ };
2546
+ this.stop = () => {
2547
+ this.pause();
2548
+ this.state.partialNext({ isPlaying: false });
2549
+ this.setSecondsElapsed(0);
2550
+ if (this.elementRef) this.elementRef.currentTime = 0;
2551
+ };
2552
+ this.togglePlay = async () => this.isPlaying ? this.pause() : await this.play();
2553
+ this.increasePlaybackRate = () => {
2554
+ let currentPlaybackRateIndex = this.state.getLatestValue().playbackRates.findIndex((rate) => rate === this.currentPlaybackRate);
2555
+ if (currentPlaybackRateIndex === -1) {
2556
+ currentPlaybackRateIndex = 0;
2557
+ }
2558
+ const nextIndex = currentPlaybackRateIndex === this.playbackRates.length - 1 ? 0 : currentPlaybackRateIndex + 1;
2559
+ const currentPlaybackRate = this.playbackRates[nextIndex];
2560
+ this.state.partialNext({ currentPlaybackRate });
2561
+ if (this.elementRef) {
2562
+ this.elementRef.playbackRate = currentPlaybackRate;
2563
+ }
2564
+ };
2565
+ this.seek = throttle(async ({ clientX, currentTarget }) => {
2566
+ let element = this.elementRef;
2567
+ if (!this.elementRef) {
2568
+ element = this.ensureElementRef();
2569
+ const isReady = await this.elementIsReady();
2570
+ if (!isReady) return;
2571
+ }
2572
+ if (!currentTarget || !element) return;
2573
+ if (!isSeekable(element)) {
2574
+ this.registerError({ errCode: "seek-not-supported" });
2575
+ return;
2576
+ }
2577
+ const { width, x } = currentTarget.getBoundingClientRect();
2578
+ const ratio = (clientX - x) / width;
2579
+ if (ratio > 1 || ratio < 0) return;
2580
+ const currentTime = ratio * element.duration;
2581
+ this.setSecondsElapsed(currentTime);
2582
+ element.currentTime = currentTime;
2583
+ }, 16);
2584
+ this.registerError = (params) => {
2585
+ defaultRegisterAudioPlayerError(params);
2586
+ this.plugins.forEach(({ onError }) => onError?.({ player: this, ...params }));
2587
+ };
2588
+ this.requestRemoval = () => {
2589
+ this._disposed = true;
2590
+ this.cancelScheduledRemoval();
2591
+ this.clearPendingLoadedMeta();
2592
+ this.clearMetadataProbe();
2593
+ this._restoringPosition = false;
2594
+ this.releaseElement({ resetState: true });
2595
+ this.unsubscribeEventListeners?.();
2596
+ this.unsubscribeEventListeners = null;
2597
+ this.plugins.forEach(({ onRemove }) => onRemove?.({ player: this }));
2598
+ this._pool.deregister(this.id);
2599
+ };
2600
+ this.cancelScheduledRemoval = () => {
2601
+ clearTimeout(this._removalTimeout);
2602
+ this._removalTimeout = void 0;
2603
+ };
2604
+ this.scheduleRemoval = (ms = 0) => {
2605
+ this.cancelScheduledRemoval();
2606
+ this._removalTimeout = setTimeout(() => {
2607
+ if (this.disposed) return;
2608
+ this.requestRemoval();
2609
+ }, ms);
2610
+ };
2611
+ this.releaseElementForHandoff = () => {
2612
+ if (!this.elementRef) return;
2613
+ this.releaseElement({ resetState: false });
2614
+ this.unsubscribeEventListeners?.();
2615
+ this.unsubscribeEventListeners = null;
2616
+ };
2617
+ this.registerSubscriptions = () => {
2618
+ this.unsubscribeEventListeners?.();
2619
+ const audioElement = this.elementRef;
2620
+ if (!audioElement) return;
2621
+ const handleEnded = () => {
2622
+ if (audioElement) {
2623
+ this.updateDurationFromElement(audioElement);
2624
+ }
2625
+ this.stop();
2626
+ };
2627
+ const handleError = (e) => {
2628
+ const audio = e.currentTarget;
2629
+ const state = { isPlaying: false };
2630
+ if (!audio?.error?.code) {
2631
+ this.state.partialNext(state);
2632
+ return;
2633
+ }
2634
+ if (audio.error.code === 4) {
2635
+ state.canPlayRecord = false;
2636
+ this.state.partialNext(state);
2637
+ }
2638
+ const errorMsg = [
2639
+ void 0,
2640
+ "MEDIA_ERR_ABORTED: fetch aborted by user",
2641
+ "MEDIA_ERR_NETWORK: network failed while fetching",
2642
+ "MEDIA_ERR_DECODE: audio fetched but couldn’t decode",
2643
+ "MEDIA_ERR_SRC_NOT_SUPPORTED: source not supported"
2644
+ ][audio?.error?.code];
2645
+ if (!errorMsg) return;
2646
+ defaultRegisterAudioPlayerError({ error: new Error(errorMsg + ` (${audio.src})`) });
2647
+ };
2648
+ const handleTimeupdate = () => {
2649
+ const t = audioElement?.currentTime ?? 0;
2650
+ if (this._restoringPosition && t === 0) return;
2651
+ if (!this.isPlaying && t === 0 && this.secondsElapsed > 0) return;
2652
+ this.setSecondsElapsed(t);
2653
+ };
2654
+ const handleLoadedMetadata = () => {
2655
+ if (audioElement) {
2656
+ this.updateDurationFromElement(audioElement);
2657
+ }
2658
+ };
2659
+ audioElement.addEventListener("ended", handleEnded);
2660
+ audioElement.addEventListener("error", handleError);
2661
+ audioElement.addEventListener("loadedmetadata", handleLoadedMetadata);
2662
+ audioElement.addEventListener("timeupdate", handleTimeupdate);
2663
+ this.unsubscribeEventListeners = () => {
2664
+ audioElement.pause();
2665
+ audioElement.removeEventListener("ended", handleEnded);
2666
+ audioElement.removeEventListener("error", handleError);
2667
+ audioElement.removeEventListener("loadedmetadata", handleLoadedMetadata);
2668
+ audioElement.removeEventListener("timeupdate", handleTimeupdate);
2669
+ };
2670
+ };
2671
+ this._data = {
2672
+ durationSeconds,
2673
+ fileSize,
2674
+ id,
2675
+ mimeType,
2676
+ src,
2677
+ title,
2678
+ waveformData
2679
+ };
2680
+ this._pool = pool;
2681
+ this.setPlugins(() => plugins ?? []);
2682
+ const playbackRates = customPlaybackRates?.length ? customPlaybackRates : DEFAULT_PLAYBACK_RATES;
2683
+ const canPlayRecord = mimeType ? !!new Audio().canPlayType(mimeType) : true;
2684
+ this.state = new streamChat.StateStore({
2685
+ canPlayRecord,
2686
+ currentPlaybackRate: playbackRates[0],
2687
+ durationSeconds,
2688
+ elementRef: null,
2689
+ isPlaying: false,
2690
+ playbackError: null,
2691
+ playbackRates,
2692
+ progressPercent: 0,
2693
+ secondsElapsed: 0
2694
+ });
2695
+ this.plugins.forEach((p) => p.onInit?.({ player: this }));
2696
+ this.preloadMetadata();
2740
2697
  }
2741
- if (messageActions.indexOf(MESSAGE_ACTIONS.download) > -1) {
2742
- messageActionsAfterPermission.push(MESSAGE_ACTIONS.download);
2698
+ get plugins() {
2699
+ return Array.from(this._plugins.values());
2743
2700
  }
2744
- if (canDelete && messageActions.indexOf(OPTIONAL_MESSAGE_ACTIONS.deleteForMe) > -1) {
2745
- messageActionsAfterPermission.push(OPTIONAL_MESSAGE_ACTIONS.deleteForMe);
2701
+ get canPlayRecord() {
2702
+ return this.state.getLatestValue().canPlayRecord;
2746
2703
  }
2747
- if (canEdit && messageActions.indexOf(MESSAGE_ACTIONS.edit) > -1) {
2748
- messageActionsAfterPermission.push(MESSAGE_ACTIONS.edit);
2704
+ get elementRef() {
2705
+ return this.state.getLatestValue().elementRef;
2749
2706
  }
2750
- if (canFlag && messageActions.indexOf(MESSAGE_ACTIONS.flag) > -1) {
2751
- messageActionsAfterPermission.push(MESSAGE_ACTIONS.flag);
2707
+ get isPlaying() {
2708
+ return this.state.getLatestValue().isPlaying;
2752
2709
  }
2753
- if (canMarkUnread && messageActions.indexOf(MESSAGE_ACTIONS.markUnread) > -1) {
2754
- messageActionsAfterPermission.push(MESSAGE_ACTIONS.markUnread);
2710
+ get currentPlaybackRate() {
2711
+ return this.state.getLatestValue().currentPlaybackRate;
2755
2712
  }
2756
- if (canMute && messageActions.indexOf(MESSAGE_ACTIONS.mute) > -1) {
2757
- messageActionsAfterPermission.push(MESSAGE_ACTIONS.mute);
2713
+ get playbackRates() {
2714
+ return this.state.getLatestValue().playbackRates;
2758
2715
  }
2759
- if (canPin && messageActions.indexOf(MESSAGE_ACTIONS.pin) > -1) {
2760
- messageActionsAfterPermission.push(MESSAGE_ACTIONS.pin);
2716
+ get durationSeconds() {
2717
+ return this.state.getLatestValue().durationSeconds;
2761
2718
  }
2762
- if (canQuote && messageActions.indexOf(MESSAGE_ACTIONS.quote) > -1) {
2763
- messageActionsAfterPermission.push(MESSAGE_ACTIONS.quote);
2719
+ get fileSize() {
2720
+ return this._data.fileSize;
2764
2721
  }
2765
- if (canReact && messageActions.indexOf(MESSAGE_ACTIONS.react) > -1) {
2766
- messageActionsAfterPermission.push(MESSAGE_ACTIONS.react);
2722
+ get id() {
2723
+ return this._data.id;
2767
2724
  }
2768
- if (channelConfig?.["user_message_reminders"] && messageActions.indexOf(MESSAGE_ACTIONS.remindMe) > -1) {
2769
- messageActionsAfterPermission.push(MESSAGE_ACTIONS.remindMe);
2725
+ get src() {
2726
+ return this._data.src;
2770
2727
  }
2771
- if (canReply && messageActions.indexOf(MESSAGE_ACTIONS.reply) > -1) {
2772
- messageActionsAfterPermission.push(MESSAGE_ACTIONS.reply);
2728
+ get mimeType() {
2729
+ return this._data.mimeType;
2773
2730
  }
2774
- if (channelConfig?.["user_message_reminders"] && messageActions.indexOf(MESSAGE_ACTIONS.saveForLater) > -1) {
2775
- messageActionsAfterPermission.push(MESSAGE_ACTIONS.saveForLater);
2731
+ get title() {
2732
+ return this._data.title;
2776
2733
  }
2777
- return messageActionsAfterPermission;
2778
- };
2779
- const ACTIONS_NOT_WORKING_IN_THREAD = [
2780
- MESSAGE_ACTIONS.pin,
2781
- MESSAGE_ACTIONS.reply,
2782
- MESSAGE_ACTIONS.markUnread
2783
- ];
2784
- function areMessagesEqual(prevMessage, nextMessage) {
2785
- const areBaseMessagesEqual = (prevMessage2, nextMessage2) => prevMessage2.deleted_at === nextMessage2.deleted_at && prevMessage2.latest_reactions?.length === nextMessage2.latest_reactions?.length && prevMessage2.own_reactions?.length === nextMessage2.own_reactions?.length && prevMessage2.pinned === nextMessage2.pinned && prevMessage2.reply_count === nextMessage2.reply_count && prevMessage2.show_in_channel === nextMessage2.show_in_channel && prevMessage2.status === nextMessage2.status && prevMessage2.text === nextMessage2.text && prevMessage2.type === nextMessage2.type && prevMessage2.updated_at === nextMessage2.updated_at && prevMessage2.user?.updated_at === nextMessage2.user?.updated_at;
2786
- return areBaseMessagesEqual(prevMessage, nextMessage) && Boolean(prevMessage.quoted_message) === Boolean(nextMessage.quoted_message) && (!prevMessage.quoted_message && !nextMessage.quoted_message || areBaseMessagesEqual(
2787
- prevMessage.quoted_message,
2788
- nextMessage.quoted_message
2789
- ));
2790
- }
2791
- const areMessagePropsEqual = (prevProps, nextProps) => {
2792
- const { message: prevMessage, Message: prevMessageUI } = prevProps;
2793
- const { message: nextMessage, Message: nextMessageUI } = nextProps;
2794
- if (prevMessageUI !== nextMessageUI) return false;
2795
- if (nextProps.showDetailedReactions !== prevProps.showDetailedReactions) {
2796
- return false;
2734
+ get waveformData() {
2735
+ return this._data.waveformData;
2797
2736
  }
2798
- if (nextProps.closeReactionSelectorOnClick !== prevProps.closeReactionSelectorOnClick) {
2799
- return false;
2737
+ get secondsElapsed() {
2738
+ return this.state.getLatestValue().secondsElapsed;
2800
2739
  }
2801
- const messagesAreEqual = areMessagesEqual(prevMessage, nextMessage);
2802
- if (!messagesAreEqual) return false;
2803
- const deepEqualProps = deepequal(nextProps.messageActions, prevProps.messageActions) && deepequal(nextProps.readBy, prevProps.readBy) && deepequal(nextProps.deliveredTo, prevProps.deliveredTo) && deepequal(nextProps.highlighted, prevProps.highlighted) && deepequal(nextProps.groupStyles, prevProps.groupStyles) && // last 3 messages can have different group styles
2804
- deepequal(nextProps.mutes, prevProps.mutes) && deepequal(nextProps.lastReceivedId, prevProps.lastReceivedId);
2805
- if (!deepEqualProps) return false;
2806
- return prevProps.messageListRect === nextProps.messageListRect;
2807
- };
2808
- const areMessageUIPropsEqual = (prevProps, nextProps) => {
2809
- const { lastReceivedId: prevLastReceivedId, message: prevMessage } = prevProps;
2810
- const { lastReceivedId: nextLastReceivedId, message: nextMessage } = nextProps;
2811
- if (prevProps.highlighted !== nextProps.highlighted) return false;
2812
- if (prevProps.threadList !== nextProps.threadList) return false;
2813
- if (prevProps.endOfGroup !== nextProps.endOfGroup) return false;
2814
- if (prevProps.mutes?.length !== nextProps.mutes?.length) return false;
2815
- if (prevProps.readBy?.length !== nextProps.readBy?.length) return false;
2816
- if (prevProps.deliveredTo?.length !== nextProps.deliveredTo?.length) return false;
2817
- if (prevProps.groupStyles !== nextProps.groupStyles) return false;
2818
- if (prevProps.showDetailedReactions !== nextProps.showDetailedReactions) {
2819
- return false;
2740
+ get progressPercent() {
2741
+ return this.state.getLatestValue().progressPercent;
2820
2742
  }
2821
- if ((prevMessage.id === prevLastReceivedId || prevMessage.id === nextLastReceivedId) && prevLastReceivedId !== nextLastReceivedId) {
2822
- return false;
2743
+ get disposed() {
2744
+ return this._disposed;
2823
2745
  }
2824
- return areMessagesEqual(prevMessage, nextMessage);
2825
- };
2826
- const messageHasReactions = (message) => Object.values(message?.reaction_groups ?? {}).some(({ count }) => count > 0);
2827
- const messageHasQuotedMessage = (message) => !!message?.quoted_message;
2828
- const messageHasAttachments = (message) => !!message?.attachments && !!message.attachments.length;
2829
- const messageHasSingleAttachment = (message) => message?.attachments?.length === 1;
2830
- const messageHasGiphyAttachment = (message) => !!message?.attachments?.some((att) => att.type === "giphy");
2831
- const getImages = (message) => {
2832
- if (!message?.attachments) {
2833
- return [];
2746
+ ensureElementRef() {
2747
+ if (this._disposed) {
2748
+ throw new Error("AudioPlayer is disposed");
2749
+ }
2750
+ if (!this.elementRef) {
2751
+ const el = this._pool.acquireElement({
2752
+ ownerId: this.id,
2753
+ src: this.src
2754
+ });
2755
+ this.setRef(el);
2756
+ }
2757
+ return this.elementRef;
2834
2758
  }
2835
- return message.attachments.filter((item) => item.type === "image");
2836
- };
2837
- const getNonImageAttachments = (message) => {
2838
- if (!message?.attachments) {
2839
- return [];
2759
+ setDescriptor(descriptor) {
2760
+ const previousSrc = this.src;
2761
+ this._data = { ...this._data, ...descriptor };
2762
+ if (descriptor.src !== previousSrc && this.elementRef) {
2763
+ this.elementRef.src = descriptor.src;
2764
+ }
2765
+ if (descriptor.src && descriptor.src !== previousSrc) {
2766
+ this.clearMetadataProbe();
2767
+ if (descriptor.durationSeconds == null) {
2768
+ this.setDurationSeconds(void 0);
2769
+ this.preloadMetadata();
2770
+ } else {
2771
+ this.setDurationSeconds(descriptor.durationSeconds);
2772
+ }
2773
+ return;
2774
+ }
2775
+ if (descriptor.durationSeconds != null) {
2776
+ this.setDurationSeconds(descriptor.durationSeconds);
2777
+ }
2840
2778
  }
2841
- return message.attachments.filter((item) => item.type !== "image");
2842
- };
2843
- const mapToUserNameOrId = (user) => user.name || user.id;
2844
- const getReadByTooltipText = (users, t, client, tooltipUserNameMapper) => {
2845
- let outStr = "";
2846
- if (!t) {
2847
- throw new Error(
2848
- "getReadByTooltipText was called, but translation function is not available"
2849
- );
2779
+ releaseElement({ resetState }) {
2780
+ this.clearPendingLoadedMeta();
2781
+ this.clearMetadataProbe();
2782
+ this._restoringPosition = false;
2783
+ if (resetState) {
2784
+ this.stop();
2785
+ } else {
2786
+ this.state.partialNext({ isPlaying: false });
2787
+ if (this.elementRef) {
2788
+ try {
2789
+ this.elementRef.pause();
2790
+ } catch {
2791
+ }
2792
+ }
2793
+ }
2794
+ if (this.elementRef) {
2795
+ this._pool.releaseElement(this.id);
2796
+ this.setRef(null);
2797
+ }
2850
2798
  }
2851
- if (!tooltipUserNameMapper) {
2852
- throw new Error(
2853
- "getReadByTooltipText was called, but tooltipUserNameMapper function is not available"
2854
- );
2799
+ setPlugins(setter) {
2800
+ this._plugins = setter(this.plugins).reduce((acc, plugin) => {
2801
+ if (plugin.id) {
2802
+ acc.set(plugin.id, plugin);
2803
+ }
2804
+ return acc;
2805
+ }, /* @__PURE__ */ new Map());
2855
2806
  }
2856
- const otherUsers = users.filter((item) => item && client?.user && item.id !== client.user.id).map(tooltipUserNameMapper);
2857
- const slicedArr = otherUsers.slice(0, 5);
2858
- const restLength = otherUsers.length - slicedArr.length;
2859
- if (slicedArr.length === 1) {
2860
- outStr = `${slicedArr[0]} `;
2861
- } else if (slicedArr.length === 2) {
2862
- outStr = t("{{ firstUser }} and {{ secondUser }}", {
2863
- firstUser: slicedArr[0],
2864
- secondUser: slicedArr[1]
2807
+ }
2808
+ class AudioPlayerPool {
2809
+ constructor(config) {
2810
+ this.state = new streamChat.StateStore({
2811
+ activeAudioPlayer: null
2865
2812
  });
2866
- } else if (slicedArr.length > 2) {
2867
- if (restLength === 0) {
2868
- const lastUser = slicedArr.splice(slicedArr.length - 1, 1);
2869
- outStr = t("{{ commaSeparatedUsers }}, and {{ lastUser }}", {
2870
- commaSeparatedUsers: slicedArr.join(", "),
2871
- lastUser
2813
+ this.pool = /* @__PURE__ */ new Map();
2814
+ this.audios = /* @__PURE__ */ new Map();
2815
+ this.sharedAudio = null;
2816
+ this.sharedOwnerId = null;
2817
+ this.getOrAdd = (params) => {
2818
+ const { playbackRates, plugins, ...descriptor } = params;
2819
+ let player = this.pool.get(params.id);
2820
+ if (player) {
2821
+ if (!player.disposed) {
2822
+ player.setDescriptor(descriptor);
2823
+ return player;
2824
+ }
2825
+ this.deregister(params.id);
2826
+ }
2827
+ player = new AudioPlayer({
2828
+ playbackRates,
2829
+ plugins,
2830
+ ...descriptor,
2831
+ pool: this
2872
2832
  });
2873
- } else {
2874
- outStr = t("{{ commaSeparatedUsers }} and {{ moreCount }} more", {
2875
- commaSeparatedUsers: slicedArr.join(", "),
2876
- moreCount: restLength
2833
+ this.pool.set(params.id, player);
2834
+ return player;
2835
+ };
2836
+ this.acquireElement = ({ ownerId, src }) => {
2837
+ if (!this.allowConcurrentPlayback) {
2838
+ if (!this.sharedAudio) {
2839
+ this.sharedAudio = new Audio();
2840
+ }
2841
+ if (this.sharedOwnerId && this.sharedOwnerId !== ownerId) {
2842
+ const previous = this.pool.get(this.sharedOwnerId);
2843
+ previous?.pause();
2844
+ previous?.releaseElementForHandoff();
2845
+ }
2846
+ this.sharedOwnerId = ownerId;
2847
+ if (this.sharedAudio.src !== src) {
2848
+ this.sharedAudio.src = src;
2849
+ }
2850
+ return this.sharedAudio;
2851
+ }
2852
+ let audio = this.audios.get(ownerId);
2853
+ if (!audio) {
2854
+ audio = new Audio();
2855
+ this.audios.set(ownerId, audio);
2856
+ }
2857
+ if (audio.src !== src) {
2858
+ audio.src = src;
2859
+ }
2860
+ return audio;
2861
+ };
2862
+ this.releaseElement = (ownerId) => {
2863
+ if (!this.allowConcurrentPlayback) {
2864
+ if (this.sharedOwnerId !== ownerId) return;
2865
+ const el2 = this.sharedAudio;
2866
+ if (el2) {
2867
+ try {
2868
+ el2.pause();
2869
+ } catch {
2870
+ }
2871
+ el2.removeAttribute("src");
2872
+ el2.load();
2873
+ }
2874
+ this.sharedOwnerId = null;
2875
+ return;
2876
+ }
2877
+ const el = this.audios.get(ownerId);
2878
+ if (!el) return;
2879
+ try {
2880
+ el.pause();
2881
+ } catch {
2882
+ }
2883
+ el.removeAttribute("src");
2884
+ el.load();
2885
+ this.audios.delete(ownerId);
2886
+ };
2887
+ this.setActiveAudioPlayer = (activeAudioPlayer) => {
2888
+ if (this.allowConcurrentPlayback) return;
2889
+ this.state.partialNext({ activeAudioPlayer });
2890
+ };
2891
+ this.remove = (id) => {
2892
+ const player = this.pool.get(id);
2893
+ if (!player) return;
2894
+ player.requestRemoval();
2895
+ };
2896
+ this.clear = () => {
2897
+ this.players.forEach((player) => {
2898
+ this.remove(player.id);
2899
+ });
2900
+ };
2901
+ this.registerSubscriptions = () => {
2902
+ this.players.forEach((p) => {
2903
+ if (p.elementRef) {
2904
+ p.registerSubscriptions();
2905
+ }
2906
+ });
2907
+ };
2908
+ this.allowConcurrentPlayback = !!config?.allowConcurrentPlayback;
2909
+ }
2910
+ get players() {
2911
+ return Array.from(this.pool.values());
2912
+ }
2913
+ get activeAudioPlayer() {
2914
+ return this.state.getLatestValue().activeAudioPlayer;
2915
+ }
2916
+ /** Removes the AudioPlayer instance from the pool of players */
2917
+ deregister(id) {
2918
+ if (this.pool.has(id)) {
2919
+ this.pool.delete(id);
2920
+ }
2921
+ if (this.activeAudioPlayer?.id === id) {
2922
+ this.setActiveAudioPlayer(null);
2923
+ }
2924
+ }
2925
+ }
2926
+ const SEEK_NOT_SUPPORTED_NOTIFICATION_DEBOUNCE_INTERVAL_MS = 1e3;
2927
+ const audioPlayerNotificationsPluginFactory = ({
2928
+ addNotification,
2929
+ panel = "channel",
2930
+ t
2931
+ }) => {
2932
+ const errors = {
2933
+ "failed-to-start": new Error(t("Failed to play the recording")),
2934
+ "not-playable": new Error(
2935
+ t("Recording format is not supported and cannot be reproduced")
2936
+ ),
2937
+ "seek-not-supported": new Error(t("Cannot seek in the recording"))
2938
+ };
2939
+ let lastSeekNotSupportedNotificationAt;
2940
+ return {
2941
+ id: "AudioPlayerNotificationsPlugin",
2942
+ onError: ({ errCode, error: e }) => {
2943
+ if (errCode === "seek-not-supported") {
2944
+ const now = Date.now();
2945
+ if (typeof lastSeekNotSupportedNotificationAt === "number" && now - lastSeekNotSupportedNotificationAt < SEEK_NOT_SUPPORTED_NOTIFICATION_DEBOUNCE_INTERVAL_MS) {
2946
+ return;
2947
+ }
2948
+ lastSeekNotSupportedNotificationAt = now;
2949
+ }
2950
+ const error = (errCode && errors[errCode]) ?? e ?? new Error(t("Error reproducing the recording"));
2951
+ addNotification({
2952
+ emitter: "AudioPlayer",
2953
+ error,
2954
+ message: error.message,
2955
+ severity: "error",
2956
+ targetPanels: [panel],
2957
+ type: "browser:audio:playback:error"
2877
2958
  });
2878
2959
  }
2879
- }
2880
- return outStr;
2960
+ };
2881
2961
  };
2882
- const countEmojis = (text) => {
2883
- const matches = text?.match(emojiRegex());
2884
- return matches ? matches.length : 0;
2962
+ const AudioPlayerContext = React.createContext({
2963
+ audioPlayers: null
2964
+ });
2965
+ const WithAudioPlayback = ({
2966
+ allowConcurrentPlayback,
2967
+ children
2968
+ }) => {
2969
+ const [audioPlayers] = React.useState(() => new AudioPlayerPool({ allowConcurrentPlayback }));
2970
+ React.useEffect(
2971
+ () => () => {
2972
+ audioPlayers.clear();
2973
+ },
2974
+ [audioPlayers]
2975
+ );
2976
+ return /* @__PURE__ */ jsxRuntime.jsx(AudioPlayerContext.Provider, { value: { audioPlayers }, children });
2885
2977
  };
2886
- const messageTextHasEmojisOnly = (message) => {
2887
- if (!message.text) return false;
2888
- const noEmojis = message.text.replace(emojiRegex(), "");
2889
- const noSpace = noEmojis.replace(/[\s\n]/gm, "");
2890
- return !noSpace;
2978
+ const makeAudioPlayerId = ({ requester, src }) => `${requester ?? "requester-unknown"}:${src}`;
2979
+ const useAudioPlayer = ({
2980
+ durationSeconds,
2981
+ fileSize,
2982
+ mimeType,
2983
+ playbackRates,
2984
+ plugins,
2985
+ requester = "",
2986
+ src,
2987
+ title,
2988
+ waveformData
2989
+ }) => {
2990
+ const { addNotification } = useNotificationApi();
2991
+ const panel = useNotificationTarget();
2992
+ const { t } = useTranslationContext();
2993
+ const { audioPlayers } = React.useContext(AudioPlayerContext);
2994
+ const audioPlayer = src && audioPlayers ? audioPlayers.getOrAdd({
2995
+ durationSeconds,
2996
+ fileSize,
2997
+ id: makeAudioPlayerId({ requester, src }),
2998
+ mimeType,
2999
+ playbackRates,
3000
+ plugins,
3001
+ src,
3002
+ title,
3003
+ waveformData
3004
+ }) : void 0;
3005
+ React.useEffect(() => {
3006
+ if (!audioPlayer) return;
3007
+ const notificationsPlugin = audioPlayerNotificationsPluginFactory({
3008
+ addNotification,
3009
+ panel,
3010
+ t
3011
+ });
3012
+ audioPlayer.setPlugins((currentPlugins) => [
3013
+ ...currentPlugins.filter((plugin) => plugin.id !== notificationsPlugin.id),
3014
+ notificationsPlugin
3015
+ ]);
3016
+ }, [addNotification, audioPlayer, panel, t]);
3017
+ return audioPlayer;
3018
+ };
3019
+ const activeAudioPlayerSelector = ({ activeAudioPlayer }) => ({
3020
+ activeAudioPlayer
3021
+ });
3022
+ const useActiveAudioPlayer = () => {
3023
+ const { audioPlayers } = React.useContext(AudioPlayerContext);
3024
+ const { activeAudioPlayer } = useStateStore(audioPlayers?.state, activeAudioPlayerSelector) ?? {};
3025
+ return activeAudioPlayer;
2891
3026
  };
2892
- const isMessageErrorRetryable = (message) => message.status === "failed" && message.error?.status !== 403;
2893
- const isNetworkSendFailure = (message) => message.status === "failed" && message.error?.status === 0;
2894
- const isMessageBounced = (message) => message.type === "error" && (message.moderation_details?.action === "MESSAGE_RESPONSE_ACTION_BOUNCE" || message.moderation?.action === "bounce");
2895
- const isMessageBlocked = (message) => message.shadowed || message.type === "error" && (message.moderation_details?.action === "MESSAGE_RESPONSE_ACTION_REMOVE" || message.moderation?.action === "remove");
2896
- const isMessageDeleted = (message) => Boolean(message.deleted_at || message.type === "deleted" || message.deleted_for_me);
2897
- const isMessageEdited = (message) => !!message.message_text_updated_at;
2898
- const hasResizeObserver = typeof window !== "undefined" && "ResizeObserver" in window;
2899
- function autoMiddlewareFor(p) {
2900
- if (!String(p).startsWith("auto")) return null;
2901
- const alignment = p === "auto-start" ? "start" : p === "auto-end" ? "end" : void 0;
2902
- return react.autoPlacement({ alignment });
2903
- }
2904
- function toOffsetMw(opt) {
2905
- if (opt == null) return null;
2906
- if (Array.isArray(opt)) {
2907
- const [crossAxis, mainAxis] = opt;
2908
- return react.offset({ crossAxis, mainAxis });
2909
- }
2910
- if (typeof opt === "number") return react.offset(opt);
2911
- return react.offset(opt);
2912
- }
2913
- function usePopoverPosition({
2914
- allowFlip = true,
2915
- allowShift = true,
2916
- autoUpdateOptions,
2917
- fitAvailableSpace = false,
2918
- freeze = false,
2919
- offset,
2920
- placement = "bottom-start",
2921
- shiftOptions
2922
- }) {
2923
- const autoMw = autoMiddlewareFor(placement);
2924
- const offsetMiddleware = toOffsetMw(offset);
2925
- const isSidePlacement = placement.startsWith("left") || placement.startsWith("right");
2926
- const mergedShiftOptions = shiftOptions ? { padding: 8, ...shiftOptions } : { padding: 8 };
2927
- const middleware = [
2928
- // offset first (mirrors common Popper setups)
2929
- ...offsetMiddleware ? [offsetMiddleware] : [],
2930
- // choose between autoPlacement (Popper's "auto*") OR flip()
2931
- // only allow flip when not explicitly 'left*' or 'right*'
2932
- ...autoMw ? [autoMw] : allowFlip && !isSidePlacement ? [react.flip()] : [],
2933
- // viewport collision adjustments
2934
- ...allowShift ? [react.shift(mergedShiftOptions)] : [],
2935
- // optional size constraining
2936
- // eslint-disable-next-line @typescript-eslint/no-empty-function
2937
- ...fitAvailableSpace ? [react.size({ apply: () => {
2938
- } })] : []
2939
- ];
2940
- const seedPlacement = String(placement).startsWith("auto") ? "bottom" : placement;
2941
- return react.useFloating({
2942
- middleware,
2943
- placement: seedPlacement,
2944
- strategy: "fixed",
2945
- whileElementsMounted: freeze ? void 0 : (reference, floating, update) => react.autoUpdate(reference, floating, update, {
2946
- ancestorResize: true,
2947
- ancestorScroll: true,
2948
- animationFrame: false,
2949
- elementResize: hasResizeObserver,
2950
- ...autoUpdateOptions
2951
- })
2952
- });
2953
- }
2954
- const LegacyThreadContext = React.createContext({ legacyThread: void 0 });
2955
- const useLegacyThreadContext = () => React.useContext(LegacyThreadContext);
2956
3027
  const cooldownTimerStateSelector = (state) => ({
2957
3028
  isCooldownActive: !!state.cooldownRemaining
2958
3029
  });
@@ -4200,7 +4271,31 @@ const UnreadCountBadge = ({
4200
4271
  }
4201
4272
  )
4202
4273
  ] });
4274
+ const DEFAULT_CHAT_VIEW_A11Y_CONTEXT_VALUE = {
4275
+ chatViewPanelIds: {
4276
+ channels: "str-chat__chat-view-panel-channels",
4277
+ threads: "str-chat__chat-view-panel-threads"
4278
+ },
4279
+ chatViewTabIds: {
4280
+ channels: "str-chat__chat-view-tab-channels",
4281
+ threads: "str-chat__chat-view-tab-threads"
4282
+ }
4283
+ };
4284
+ const createChatViewA11yContextValue = (chatViewId) => ({
4285
+ // Keep IDs unique per ChatView instance so ARIA references do not collide.
4286
+ chatViewPanelIds: {
4287
+ channels: `str-chat__chat-view-${chatViewId}-panel-channels`,
4288
+ threads: `str-chat__chat-view-${chatViewId}-panel-threads`
4289
+ },
4290
+ chatViewTabIds: {
4291
+ channels: `str-chat__chat-view-${chatViewId}-tab-channels`,
4292
+ threads: `str-chat__chat-view-${chatViewId}-tab-threads`
4293
+ }
4294
+ });
4203
4295
  const ChatViewContext = React.createContext(void 0);
4296
+ const ChatViewA11yContext = React.createContext(
4297
+ DEFAULT_CHAT_VIEW_A11Y_CONTEXT_VALUE
4298
+ );
4204
4299
  const useChatViewContext = () => {
4205
4300
  const value = React.useContext(ChatViewContext);
4206
4301
  if (!value) {
@@ -4211,16 +4306,33 @@ const useChatViewContext = () => {
4211
4306
  }
4212
4307
  return value;
4213
4308
  };
4309
+ const useChatViewA11yContext = () => React.useContext(ChatViewA11yContext);
4214
4310
  const ChatView = ({ children }) => {
4215
4311
  const [activeChatView, setActiveChatView] = React.useState("channels");
4312
+ const chatViewId = React.useId().replace(/:/g, "");
4216
4313
  const { theme } = useChatContext();
4314
+ const a11yValue = React.useMemo(
4315
+ () => createChatViewA11yContextValue(chatViewId),
4316
+ [chatViewId]
4317
+ );
4217
4318
  const value = React.useMemo(() => ({ activeChatView, setActiveChatView }), [activeChatView]);
4218
- return /* @__PURE__ */ jsxRuntime.jsx(ChatViewContext.Provider, { value, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: clsx("str-chat", theme, "str-chat__chat-view"), children }) });
4319
+ return /* @__PURE__ */ jsxRuntime.jsx(ChatViewA11yContext.Provider, { value: a11yValue, children: /* @__PURE__ */ jsxRuntime.jsx(ChatViewContext.Provider, { value, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: clsx("str-chat", theme, "str-chat__chat-view"), children }) }) });
4219
4320
  };
4220
4321
  const ChannelsView = ({ children }) => {
4221
4322
  const { activeChatView } = useChatViewContext();
4222
- if (activeChatView !== "channels") return null;
4223
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__chat-view__channels", children });
4323
+ const { chatViewPanelIds, chatViewTabIds } = useChatViewA11yContext();
4324
+ const isActive = activeChatView === "channels";
4325
+ if (!isActive) return null;
4326
+ return /* @__PURE__ */ jsxRuntime.jsx(
4327
+ "div",
4328
+ {
4329
+ "aria-labelledby": chatViewTabIds.channels,
4330
+ className: "str-chat__chat-view__channels",
4331
+ id: chatViewPanelIds.channels,
4332
+ role: "tabpanel",
4333
+ children
4334
+ }
4335
+ );
4224
4336
  };
4225
4337
  const ThreadsViewContext = React.createContext({
4226
4338
  activeThread: void 0,
@@ -4229,10 +4341,21 @@ const ThreadsViewContext = React.createContext({
4229
4341
  const useThreadsViewContext = () => React.useContext(ThreadsViewContext);
4230
4342
  const ThreadsView = ({ children }) => {
4231
4343
  const { activeChatView } = useChatViewContext();
4344
+ const { chatViewPanelIds, chatViewTabIds } = useChatViewA11yContext();
4232
4345
  const [activeThread, setActiveThread] = React.useState(void 0);
4233
4346
  const value = React.useMemo(() => ({ activeThread, setActiveThread }), [activeThread]);
4234
- if (activeChatView !== "threads") return null;
4235
- return /* @__PURE__ */ jsxRuntime.jsx(ThreadsViewContext.Provider, { value, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__chat-view__threads", children }) });
4347
+ const isActive = activeChatView === "threads";
4348
+ if (!isActive) return null;
4349
+ return /* @__PURE__ */ jsxRuntime.jsx(ThreadsViewContext.Provider, { value, children: /* @__PURE__ */ jsxRuntime.jsx(
4350
+ "div",
4351
+ {
4352
+ "aria-labelledby": chatViewTabIds.threads,
4353
+ className: "str-chat__chat-view__threads",
4354
+ id: chatViewPanelIds.threads,
4355
+ role: "tabpanel",
4356
+ children
4357
+ }
4358
+ ) });
4236
4359
  };
4237
4360
  const useActiveThread = ({ activeThread }) => {
4238
4361
  React.useEffect(() => {
@@ -4328,17 +4451,22 @@ const ChatViewChannelsSelectorButton = ({
4328
4451
  iconOnly = true
4329
4452
  }) => {
4330
4453
  const { activeChatView, setActiveChatView } = useChatViewContext();
4454
+ const { chatViewPanelIds, chatViewTabIds } = useChatViewA11yContext();
4331
4455
  const { t } = useTranslationContext();
4332
4456
  const isActive = activeChatView === "channels";
4333
4457
  return /* @__PURE__ */ jsxRuntime.jsx(
4334
4458
  ChatViewSelectorButton,
4335
4459
  {
4336
4460
  ActiveIcon: IconMessageBubbleFill,
4461
+ "aria-controls": chatViewPanelIds.channels,
4337
4462
  "aria-selected": isActive,
4338
4463
  Icon: IconMessageBubble,
4339
4464
  iconOnly,
4465
+ id: chatViewTabIds.channels,
4340
4466
  isActive,
4467
+ onClick: () => setActiveChatView("channels"),
4341
4468
  onPointerDown: () => setActiveChatView("channels"),
4469
+ tabIndex: 0,
4342
4470
  text: t("Channels")
4343
4471
  }
4344
4472
  );
@@ -4354,17 +4482,22 @@ const ChatViewThreadsSelectorButton = ({
4354
4482
  unreadThreadCount: 0
4355
4483
  };
4356
4484
  const { activeChatView, setActiveChatView } = useChatViewContext();
4485
+ const { chatViewPanelIds, chatViewTabIds } = useChatViewA11yContext();
4357
4486
  const { t } = useTranslationContext();
4358
4487
  const isActive = activeChatView === "threads";
4359
4488
  return /* @__PURE__ */ jsxRuntime.jsx(
4360
4489
  ChatViewSelectorButton,
4361
4490
  {
4362
4491
  ActiveIcon: IconThreadFill,
4492
+ "aria-controls": chatViewPanelIds.threads,
4363
4493
  "aria-selected": isActive,
4364
4494
  Icon: IconThread,
4365
4495
  iconOnly,
4496
+ id: chatViewTabIds.threads,
4366
4497
  isActive,
4498
+ onClick: () => setActiveChatView("threads"),
4367
4499
  onPointerDown: () => setActiveChatView("threads"),
4500
+ tabIndex: 0,
4368
4501
  text: t("Threads"),
4369
4502
  children: /* @__PURE__ */ jsxRuntime.jsx(UnreadCountBadge, { count: unreadThreadCount, position: "top-right", children: isActive ? /* @__PURE__ */ jsxRuntime.jsx(IconThreadFill, {}) : /* @__PURE__ */ jsxRuntime.jsx(IconThread, {}) })
4370
4503
  }
@@ -4383,7 +4516,18 @@ const defaultChatViewSelectorItemSet = [
4383
4516
  const ChatViewSelector = ({
4384
4517
  iconOnly = true,
4385
4518
  itemSet = defaultChatViewSelectorItemSet
4386
- }) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__chat-view__selector", children: itemSet.map(({ Component, type }) => /* @__PURE__ */ jsxRuntime.jsx(Component, { iconOnly }, type)) });
4519
+ }) => {
4520
+ const { t } = useTranslationContext();
4521
+ return /* @__PURE__ */ jsxRuntime.jsx(
4522
+ "div",
4523
+ {
4524
+ "aria-label": t("aria/Chat view tabs"),
4525
+ className: "str-chat__chat-view__selector",
4526
+ role: "tablist",
4527
+ children: itemSet.map(({ Component, type }) => /* @__PURE__ */ jsxRuntime.jsx(Component, { iconOnly }, type))
4528
+ }
4529
+ );
4530
+ };
4387
4531
  ChatView.Channels = ChannelsView;
4388
4532
  ChatView.Threads = ThreadsView;
4389
4533
  ChatView.ThreadAdapter = ThreadAdapter;
@@ -4509,71 +4653,6 @@ const useNotificationApi = () => {
4509
4653
  startNotificationTimeout
4510
4654
  };
4511
4655
  };
4512
- const AudioPlayerContext = React.createContext({
4513
- audioPlayers: null
4514
- });
4515
- const WithAudioPlayback = ({
4516
- allowConcurrentPlayback,
4517
- children
4518
- }) => {
4519
- const [audioPlayers] = React.useState(() => new AudioPlayerPool({ allowConcurrentPlayback }));
4520
- React.useEffect(
4521
- () => () => {
4522
- audioPlayers.clear();
4523
- },
4524
- [audioPlayers]
4525
- );
4526
- return /* @__PURE__ */ jsxRuntime.jsx(AudioPlayerContext.Provider, { value: { audioPlayers }, children });
4527
- };
4528
- const makeAudioPlayerId = ({ requester, src }) => `${requester ?? "requester-unknown"}:${src}`;
4529
- const useAudioPlayer = ({
4530
- durationSeconds,
4531
- fileSize,
4532
- mimeType,
4533
- playbackRates,
4534
- plugins,
4535
- requester = "",
4536
- src,
4537
- title,
4538
- waveformData
4539
- }) => {
4540
- const { addNotification } = useNotificationApi();
4541
- const panel = useNotificationTarget();
4542
- const { t } = useTranslationContext();
4543
- const { audioPlayers } = React.useContext(AudioPlayerContext);
4544
- const audioPlayer = src && audioPlayers ? audioPlayers.getOrAdd({
4545
- durationSeconds,
4546
- fileSize,
4547
- id: makeAudioPlayerId({ requester, src }),
4548
- mimeType,
4549
- playbackRates,
4550
- plugins,
4551
- src,
4552
- title,
4553
- waveformData
4554
- }) : void 0;
4555
- React.useEffect(() => {
4556
- if (!audioPlayer) return;
4557
- const notificationsPlugin = audioPlayerNotificationsPluginFactory({
4558
- addNotification,
4559
- panel,
4560
- t
4561
- });
4562
- audioPlayer.setPlugins((currentPlugins) => [
4563
- ...currentPlugins.filter((plugin) => plugin.id !== notificationsPlugin.id),
4564
- notificationsPlugin
4565
- ]);
4566
- }, [addNotification, audioPlayer, panel, t]);
4567
- return audioPlayer;
4568
- };
4569
- const activeAudioPlayerSelector = ({ activeAudioPlayer }) => ({
4570
- activeAudioPlayer
4571
- });
4572
- const useActiveAudioPlayer = () => {
4573
- const { audioPlayers } = React.useContext(AudioPlayerContext);
4574
- const { activeAudioPlayer } = useStateStore(audioPlayers?.state, activeAudioPlayerSelector) ?? {};
4575
- return activeAudioPlayer;
4576
- };
4577
4656
  exports.ACTIONS_NOT_WORKING_IN_THREAD = ACTIONS_NOT_WORKING_IN_THREAD;
4578
4657
  exports.AudioPlayer = AudioPlayer;
4579
4658
  exports.Button = Button;
@@ -4773,4 +4852,4 @@ exports.useThreadsViewContext = useThreadsViewContext;
4773
4852
  exports.useTranslationContext = useTranslationContext;
4774
4853
  exports.useTypingContext = useTypingContext;
4775
4854
  exports.validateAndGetMessage = validateAndGetMessage;
4776
- //# sourceMappingURL=WithAudioPlayback.ba05c770.js.map
4855
+ //# sourceMappingURL=useNotificationApi.fd802923.js.map