stream-chat-react 14.7.0 → 14.9.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/{ReactPlayerWrapper.c340ab05.js → ReactPlayerWrapper.60a93742.js} +2 -2
  2. package/dist/cjs/{ReactPlayerWrapper.c340ab05.js.map → ReactPlayerWrapper.60a93742.js.map} +1 -1
  3. package/dist/cjs/channel-detail.js +2 -2
  4. package/dist/cjs/emojis.js +2 -2
  5. package/dist/cjs/index.js +2909 -744
  6. package/dist/cjs/index.js.map +1 -1
  7. package/dist/cjs/{useChannelHeaderOnlineStatus.d39ede24.js → useChannelHeaderOnlineStatus.72753090.js} +373 -63
  8. package/dist/cjs/useChannelHeaderOnlineStatus.72753090.js.map +1 -0
  9. package/dist/cjs/{useMessageComposerController.f1a3d182.js → useMessageComposerController.f8f20e3d.js} +2 -2
  10. package/dist/cjs/{useMessageComposerController.f1a3d182.js.map → useMessageComposerController.f8f20e3d.js.map} +1 -1
  11. package/dist/cjs/{useNotificationApi.87ba0b39.js → useNotificationApi.bf76a16b.js} +294 -42
  12. package/dist/cjs/useNotificationApi.bf76a16b.js.map +1 -0
  13. package/dist/css/index.css +56 -26
  14. package/dist/css/index.css.map +1 -1
  15. package/dist/es/channel-detail.mjs +2 -2
  16. package/dist/es/emojis.mjs +2 -2
  17. package/dist/es/index.mjs +2874 -735
  18. package/dist/es/index.mjs.map +1 -1
  19. package/dist/es/{useChannelHeaderOnlineStatus.b3a51ca7.mjs → useChannelHeaderOnlineStatus.c86db868.mjs} +337 -64
  20. package/dist/es/useChannelHeaderOnlineStatus.c86db868.mjs.map +1 -0
  21. package/dist/es/{useMessageComposerController.2309f433.mjs → useMessageComposerController.d50f6168.mjs} +2 -2
  22. package/dist/es/{useMessageComposerController.2309f433.mjs.map → useMessageComposerController.d50f6168.mjs.map} +1 -1
  23. package/dist/es/{useNotificationApi.eff88f29.mjs → useNotificationApi.ed6bff88.mjs} +271 -43
  24. package/dist/es/useNotificationApi.ed6bff88.mjs.map +1 -0
  25. package/dist/types/a11y/accessibleLabel.d.ts +38 -0
  26. package/dist/types/a11y/accessibleLabel.d.ts.map +1 -0
  27. package/dist/types/a11y/hooks/useListboxKeyboardNavigation.d.ts +73 -0
  28. package/dist/types/a11y/hooks/useListboxKeyboardNavigation.d.ts.map +1 -0
  29. package/dist/types/a11y/hooks/useVirtualizedListboxKeyboardNavigation.d.ts +41 -0
  30. package/dist/types/a11y/hooks/useVirtualizedListboxKeyboardNavigation.d.ts.map +1 -0
  31. package/dist/types/components/Accessibility/AriaLiveAnnouncerProvider.d.ts +13 -0
  32. package/dist/types/components/Accessibility/AriaLiveAnnouncerProvider.d.ts.map +1 -0
  33. package/dist/types/components/Accessibility/AriaLiveOutlet.d.ts +24 -0
  34. package/dist/types/components/Accessibility/AriaLiveOutlet.d.ts.map +1 -0
  35. package/dist/types/components/Accessibility/AriaLiveOutletContext.d.ts +25 -0
  36. package/dist/types/components/Accessibility/AriaLiveOutletContext.d.ts.map +1 -0
  37. package/dist/types/components/Accessibility/NotificationAnnouncer.d.ts +21 -1
  38. package/dist/types/components/Accessibility/NotificationAnnouncer.d.ts.map +1 -1
  39. package/dist/types/components/Accessibility/hooks/useAudioPlaybackChangeAnnouncements.d.ts +3 -0
  40. package/dist/types/components/Accessibility/hooks/useAudioPlaybackChangeAnnouncements.d.ts.map +1 -0
  41. package/dist/types/components/Accessibility/hooks/useFocusReturn.d.ts +44 -0
  42. package/dist/types/components/Accessibility/hooks/useFocusReturn.d.ts.map +1 -0
  43. package/dist/types/components/Accessibility/hooks/useInertWhenHidden.d.ts +88 -0
  44. package/dist/types/components/Accessibility/hooks/useInertWhenHidden.d.ts.map +1 -0
  45. package/dist/types/components/Accessibility/hooks/useInteractionAnnouncements.d.ts +114 -0
  46. package/dist/types/components/Accessibility/hooks/useInteractionAnnouncements.d.ts.map +1 -0
  47. package/dist/types/components/Accessibility/index.d.ts +7 -1
  48. package/dist/types/components/Accessibility/index.d.ts.map +1 -1
  49. package/dist/types/components/Accessibility/scheduling/index.d.ts +4 -0
  50. package/dist/types/components/Accessibility/scheduling/index.d.ts.map +1 -0
  51. package/dist/types/components/Accessibility/scheduling/useAnnouncementQueue.d.ts +25 -0
  52. package/dist/types/components/Accessibility/scheduling/useAnnouncementQueue.d.ts.map +1 -0
  53. package/dist/types/components/Accessibility/scheduling/useDebouncedAnnounce.d.ts +54 -0
  54. package/dist/types/components/Accessibility/scheduling/useDebouncedAnnounce.d.ts.map +1 -0
  55. package/dist/types/components/Accessibility/scheduling/useSettledAnnouncement.d.ts +74 -0
  56. package/dist/types/components/Accessibility/scheduling/useSettledAnnouncement.d.ts.map +1 -0
  57. package/dist/types/components/Accessibility/useAriaLiveAnnouncer.d.ts +24 -1
  58. package/dist/types/components/Accessibility/useAriaLiveAnnouncer.d.ts.map +1 -1
  59. package/dist/types/components/Attachment/AttachmentActions.d.ts.map +1 -1
  60. package/dist/types/components/Attachment/Giphy.d.ts.map +1 -1
  61. package/dist/types/components/Attachment/giphyAccessibility.d.ts +11 -0
  62. package/dist/types/components/Attachment/giphyAccessibility.d.ts.map +1 -0
  63. package/dist/types/components/AudioPlayback/WithAudioPlayback.d.ts.map +1 -1
  64. package/dist/types/components/ChannelList/ChannelListUI.d.ts.map +1 -1
  65. package/dist/types/components/ChannelList/hooks/index.d.ts +1 -0
  66. package/dist/types/components/ChannelList/hooks/index.d.ts.map +1 -1
  67. package/dist/types/components/ChannelList/hooks/useChannelListKeyboardNavigation.d.ts +17 -0
  68. package/dist/types/components/ChannelList/hooks/useChannelListKeyboardNavigation.d.ts.map +1 -0
  69. package/dist/types/components/ChannelListItem/ChannelListItem.d.ts +7 -0
  70. package/dist/types/components/ChannelListItem/ChannelListItem.d.ts.map +1 -1
  71. package/dist/types/components/ChannelListItem/ChannelListItemActionButtons.defaults.d.ts.map +1 -1
  72. package/dist/types/components/ChannelListItem/ChannelListItemUI.d.ts.map +1 -1
  73. package/dist/types/components/ChannelListItem/index.d.ts +1 -0
  74. package/dist/types/components/ChannelListItem/index.d.ts.map +1 -1
  75. package/dist/types/components/ChannelListItem/utils.a11y.d.ts +112 -0
  76. package/dist/types/components/ChannelListItem/utils.a11y.d.ts.map +1 -0
  77. package/dist/types/components/ChannelListItem/utils.d.ts +10 -2
  78. package/dist/types/components/ChannelListItem/utils.d.ts.map +1 -1
  79. package/dist/types/components/Chat/Chat.d.ts.map +1 -1
  80. package/dist/types/components/ChatView/ChatView.a11y.utility.d.ts +9 -17
  81. package/dist/types/components/ChatView/ChatView.a11y.utility.d.ts.map +1 -1
  82. package/dist/types/components/ChatView/ChatView.d.ts +7 -7
  83. package/dist/types/components/ChatView/ChatView.d.ts.map +1 -1
  84. package/dist/types/components/Dialog/components/ContextMenu.d.ts +17 -2
  85. package/dist/types/components/Dialog/components/ContextMenu.d.ts.map +1 -1
  86. package/dist/types/components/Dialog/components/Prompt.d.ts.map +1 -1
  87. package/dist/types/components/Dialog/service/DialogAnchor.d.ts.map +1 -1
  88. package/dist/types/components/Form/SwitchField.d.ts.map +1 -1
  89. package/dist/types/components/Form/TextInput.d.ts.map +1 -1
  90. package/dist/types/components/InfiniteScrollPaginator/InfiniteScrollPaginator.d.ts +7 -0
  91. package/dist/types/components/InfiniteScrollPaginator/InfiniteScrollPaginator.d.ts.map +1 -1
  92. package/dist/types/components/MediaRecorder/hooks/useMediaRecorder.d.ts +2 -2
  93. package/dist/types/components/MediaRecorder/hooks/useMediaRecorder.d.ts.map +1 -1
  94. package/dist/types/components/MessageActions/DownloadSubmenu.d.ts.map +1 -1
  95. package/dist/types/components/MessageActions/RemindMeSubmenu.d.ts.map +1 -1
  96. package/dist/types/components/MessageComposer/AttachmentSelector/AttachmentSelector.d.ts +8 -2
  97. package/dist/types/components/MessageComposer/AttachmentSelector/AttachmentSelector.d.ts.map +1 -1
  98. package/dist/types/components/MessageComposer/AttachmentSelector/CommandsMenu.d.ts.map +1 -1
  99. package/dist/types/components/MessageComposer/CommandChip.d.ts.map +1 -1
  100. package/dist/types/components/MessageComposer/MessageComposerActions.d.ts.map +1 -1
  101. package/dist/types/components/MessageComposer/hooks/useAttachmentManagerState.d.ts +2 -0
  102. package/dist/types/components/MessageComposer/hooks/useAttachmentManagerState.d.ts.map +1 -1
  103. package/dist/types/components/MessageComposer/index.d.ts +2 -1
  104. package/dist/types/components/MessageComposer/index.d.ts.map +1 -1
  105. package/dist/types/components/MessageList/MessageList.d.ts.map +1 -1
  106. package/dist/types/components/MessageList/VirtualizedMessageList.d.ts.map +1 -1
  107. package/dist/types/components/Modal/GlobalModal.d.ts +6 -1
  108. package/dist/types/components/Modal/GlobalModal.d.ts.map +1 -1
  109. package/dist/types/components/Notifications/Notification.d.ts.map +1 -1
  110. package/dist/types/components/Notifications/hooks/useNotificationApi.d.ts +7 -0
  111. package/dist/types/components/Notifications/hooks/useNotificationApi.d.ts.map +1 -1
  112. package/dist/types/components/Poll/PollCreationDialog/NameField.d.ts.map +1 -1
  113. package/dist/types/components/Poll/PollCreationDialog/OptionFieldSet.d.ts.map +1 -1
  114. package/dist/types/components/Poll/PollCreationDialog/PollCreationDialog.d.ts.map +1 -1
  115. package/dist/types/components/Poll/PollCreationDialog/PollCreationDialogControls.d.ts.map +1 -1
  116. package/dist/types/components/Poll/PollCreationDialog/PollOptionReorderHandle.d.ts +17 -0
  117. package/dist/types/components/Poll/PollCreationDialog/PollOptionReorderHandle.d.ts.map +1 -0
  118. package/dist/types/components/ReactFileUtilities/UploadButton.d.ts.map +1 -1
  119. package/dist/types/components/Search/Search.d.ts +9 -1
  120. package/dist/types/components/Search/Search.d.ts.map +1 -1
  121. package/dist/types/components/Search/SearchBar/SearchBar.d.ts.map +1 -1
  122. package/dist/types/components/Search/SearchContext.d.ts +1 -1
  123. package/dist/types/components/Search/SearchContext.d.ts.map +1 -1
  124. package/dist/types/components/Search/SearchResults/SearchResults.d.ts.map +1 -1
  125. package/dist/types/components/Search/SearchResults/SearchSourceResultListFooter.d.ts.map +1 -1
  126. package/dist/types/components/Search/hooks/index.d.ts +2 -0
  127. package/dist/types/components/Search/hooks/index.d.ts.map +1 -1
  128. package/dist/types/components/Search/hooks/useAnnounceSearchResultCount.d.ts +27 -0
  129. package/dist/types/components/Search/hooks/useAnnounceSearchResultCount.d.ts.map +1 -0
  130. package/dist/types/components/Search/hooks/useSearchResultsKeyboardNavigation.d.ts +13 -0
  131. package/dist/types/components/Search/hooks/useSearchResultsKeyboardNavigation.d.ts.map +1 -0
  132. package/dist/types/components/TextareaComposer/SuggestionList/MentionItem/UserItem.d.ts.map +1 -1
  133. package/dist/types/components/TextareaComposer/SuggestionList/SuggestionList.d.ts +21 -1
  134. package/dist/types/components/TextareaComposer/SuggestionList/SuggestionList.d.ts.map +1 -1
  135. package/dist/types/components/TextareaComposer/TextareaComposer.d.ts +9 -1
  136. package/dist/types/components/TextareaComposer/TextareaComposer.d.ts.map +1 -1
  137. package/dist/types/components/Threads/ThreadList/ThreadList.d.ts.map +1 -1
  138. package/dist/types/components/Threads/ThreadList/ThreadListItemUI.d.ts +7 -1
  139. package/dist/types/components/Threads/ThreadList/ThreadListItemUI.d.ts.map +1 -1
  140. package/dist/types/components/Threads/ThreadList/index.d.ts +1 -0
  141. package/dist/types/components/Threads/ThreadList/index.d.ts.map +1 -1
  142. package/dist/types/components/Threads/ThreadList/utils.a11y.d.ts +111 -0
  143. package/dist/types/components/Threads/ThreadList/utils.a11y.d.ts.map +1 -0
  144. package/dist/types/components/Threads/UnreadCountBadge.d.ts.map +1 -1
  145. package/dist/types/components/UtilityComponents/hooks/useMutationObserver.d.ts +23 -0
  146. package/dist/types/components/UtilityComponents/hooks/useMutationObserver.d.ts.map +1 -0
  147. package/dist/types/components/index.d.ts +1 -0
  148. package/dist/types/components/index.d.ts.map +1 -1
  149. package/dist/types/context/index.d.ts +1 -0
  150. package/dist/types/context/index.d.ts.map +1 -1
  151. package/dist/types/i18n/Streami18n.d.ts +79 -13
  152. package/dist/types/i18n/Streami18n.d.ts.map +1 -1
  153. package/package.json +6 -3
  154. package/dist/cjs/useChannelHeaderOnlineStatus.d39ede24.js.map +0 -1
  155. package/dist/cjs/useNotificationApi.87ba0b39.js.map +0 -1
  156. package/dist/es/useChannelHeaderOnlineStatus.b3a51ca7.mjs.map +0 -1
  157. package/dist/es/useNotificationApi.eff88f29.mjs.map +0 -1
  158. package/dist/types/components/Accessibility/AriaLiveRegion.d.ts +0 -3
  159. package/dist/types/components/Accessibility/AriaLiveRegion.d.ts.map +0 -1
@@ -1,5 +1,5 @@
1
- import { Ar as useDialogIsOpen, Er as DialogPortalEntry, Fr as useModalDialogIsTopmost, Ft as IconChevronLeft, It as IconChevronRight, Jn as IconXmark, Jt as IconExclamationTriangleFill, Kt as IconExclamationMark, Lr as useStateStore, Mr as useDialogOnNearestManager, Nr as useModalDialog, Pr as useModalDialogIsOpen, Qn as useStableId, R as usePopoverPosition, Tn as IconRefresh, Vr as useComponentContext, Vt as IconDownload, Wr as useChatContext, Z as isMessageDeleted, Zn as Button, bn as IconPlayFill, bt as IconArrowLeft, en as IconImage, in as IconLoading, jt as IconCheckmark, kr as useDialog, mr as isDate, n as hasSystemNotificationTag, pr as getDateString, qr as useChannelStateContext, qt as IconExclamationMarkFill, r as useNotificationApi, rr as isNotificationForPanel, ur as useTranslationContext, xr as modalDialogManagerId, yr as DialogManagerProvider, yt as IconArrowDownCircle, zn as IconUser } from "./useNotificationApi.eff88f29.mjs";
2
- import React, { createContext, createElement, forwardRef, isValidElement, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
1
+ import { $n as useStableId, Ar as DialogPortalEntry, Bn as IconUser, Cr as DialogManagerProvider, En as IconRefresh, Gr as useComponentContext, Ht as IconDownload, Ir as useDialogOnNearestManager, It as IconChevronLeft, Jr as useChatContext, Jt as IconExclamationMarkFill, Lr as useModalDialog, Lt as IconChevronRight, Mt as IconCheckmark, Nr as useDialog, Pr as useDialogIsOpen, Q as isMessageDeleted, Qn as Button, Rr as useModalDialogIsOpen, Tr as modalDialogManagerId, Vr as useStateStore, Yn as IconXmark, Yt as IconExclamationTriangleFill, Zr as useChannelStateContext, _r as getDateString, an as IconLoading, bt as IconArrowDownCircle, ir as isNotificationForPanel, jr as Portal, mr as useTranslationContext, n as hasSystemNotificationTag, qt as IconExclamationMark, r as useNotificationApi, sr as AriaLiveAnnouncerContext, tn as IconImage, vr as isDate, xn as IconPlayFill, xt as IconArrowLeft, z as usePopoverPosition, zr as useModalDialogIsTopmost } from "./useNotificationApi.ed6bff88.mjs";
2
+ import React, { createContext, createElement, forwardRef, isValidElement, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
3
3
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
4
4
  import { isGiphyAttachment, isImageAttachment, isLocalImageAttachment, isLocalVideoAttachment, isScrapedContent, isVideoAttachment } from "stream-chat";
5
5
  import clsx from "clsx";
@@ -9,6 +9,9 @@ import { SKIP, visit } from "unist-util-visit";
9
9
  import ReactMarkdown from "react-markdown";
10
10
  import remarkGfm from "remark-gfm";
11
11
  import { sanitizeUrl } from "@braintree/sanitize-url";
12
+ import { toString } from "mdast-util-to-string";
13
+ import remarkParse from "remark-parse";
14
+ import { unified } from "unified";
12
15
  //#region src/context/MessageContext.tsx
13
16
  var MessageContext = React.createContext(void 0);
14
17
  var MessageProvider = ({ children, value }) => /* @__PURE__ */ jsx(MessageContext.Provider, {
@@ -107,6 +110,84 @@ var useModalContext = () => {
107
110
  return contextValue;
108
111
  };
109
112
  //#endregion
113
+ //#region src/components/Accessibility/AriaLiveOutletContext.ts
114
+ var AriaLiveOutletContext = createContext(void 0);
115
+ var useAriaLiveOutletContext = () => useContext(AriaLiveOutletContext);
116
+ //#endregion
117
+ //#region src/components/VisuallyHidden/VisuallyHidden.tsx
118
+ var visuallyHiddenStyle = {
119
+ border: 0,
120
+ clip: "rect(0, 0, 0, 0)",
121
+ height: "1px",
122
+ margin: "-1px",
123
+ overflow: "hidden",
124
+ padding: 0,
125
+ position: "absolute",
126
+ whiteSpace: "nowrap",
127
+ width: "1px"
128
+ };
129
+ /**
130
+ * Hides content visually while preserving it for assistive technologies.
131
+ */
132
+ var VisuallyHidden = ({ children, style, ...rest }) => /* @__PURE__ */ jsx("span", {
133
+ ...rest,
134
+ style: {
135
+ ...visuallyHiddenStyle,
136
+ ...style
137
+ },
138
+ children
139
+ });
140
+ //#endregion
141
+ //#region src/components/Accessibility/AriaLiveOutlet.tsx
142
+ /**
143
+ * Dumb render target for the unified announcer. Registers itself with the nearest
144
+ * {@link AriaLiveAnnouncerProvider} and renders the polite/assertive live regions ONLY when
145
+ * it is the active (innermost) outlet, so exactly one region is live at a time — always in
146
+ * the active accessibility-tree scope. Mount one at the `Chat` root and one inside each
147
+ * `aria-modal` dialog (with a higher `layer`).
148
+ */
149
+ var AriaLiveOutlet = ({ layer = 0, portal = false }) => {
150
+ const context = useAriaLiveOutletContext();
151
+ const idRef = useRef(null);
152
+ if (idRef.current === null) idRef.current = Symbol("aria-live-outlet");
153
+ const registerOutlet = context?.registerOutlet;
154
+ const unregisterOutlet = context?.unregisterOutlet;
155
+ useEffect(() => {
156
+ const id = idRef.current;
157
+ if (!id || !registerOutlet || !unregisterOutlet) return;
158
+ registerOutlet(id, layer);
159
+ return () => unregisterOutlet(id);
160
+ }, [
161
+ layer,
162
+ registerOutlet,
163
+ unregisterOutlet
164
+ ]);
165
+ const getPortalDestination = useCallback(() => document.body, []);
166
+ if (!context || context.activeOutletId !== idRef.current) return null;
167
+ const { announcementsByPriority } = context;
168
+ const liveRegions = /* @__PURE__ */ jsxs(VisuallyHidden, { children: [/* @__PURE__ */ jsx("div", {
169
+ "aria-atomic": "false",
170
+ "aria-live": "polite",
171
+ "aria-relevant": "additions",
172
+ "data-testid": "str-chat__aria-live-region--polite",
173
+ role: "status",
174
+ children: announcementsByPriority.polite.map((announcement) => /* @__PURE__ */ jsx("div", { children: announcement.message }, announcement.id))
175
+ }), /* @__PURE__ */ jsx("div", {
176
+ "aria-atomic": "false",
177
+ "aria-live": "assertive",
178
+ "aria-relevant": "additions",
179
+ "data-testid": "str-chat__aria-live-region--assertive",
180
+ role: "alert",
181
+ children: announcementsByPriority.assertive.map((announcement) => /* @__PURE__ */ jsx("div", { children: announcement.message }, announcement.id))
182
+ })] });
183
+ if (!portal) return liveRegions;
184
+ return /* @__PURE__ */ jsx(Portal, {
185
+ getPortalDestination,
186
+ isOpen: true,
187
+ children: liveRegions
188
+ });
189
+ };
190
+ //#endregion
110
191
  //#region src/components/Notifications/NotificationConfigurationContext.tsx
111
192
  var defaultNotificationDisplayFilter = () => true;
112
193
  var defaultNotificationConfigurationContextValue = { displayFilter: defaultNotificationDisplayFilter };
@@ -418,7 +499,7 @@ var DialogAnchor = ({ allowFlip = true, children, className, closeOnClickOutside
418
499
  autoFocus: focus,
419
500
  contain: trapFocus,
420
501
  restoreFocus: true,
421
- children: /* @__PURE__ */ jsx("div", {
502
+ children: /* @__PURE__ */ jsxs("div", {
422
503
  ...anchorDivProps,
423
504
  "aria-describedby": ariaDescribedBy,
424
505
  "aria-label": resolvedAriaLabel,
@@ -432,7 +513,7 @@ var DialogAnchor = ({ allowFlip = true, children, className, closeOnClickOutside
432
513
  role: resolvedRole,
433
514
  style: styles,
434
515
  tabIndex: typeof tabIndex !== "undefined" ? tabIndex : 0,
435
- children
516
+ children: [children, resolvedAriaModal && /* @__PURE__ */ jsx(AriaLiveOutlet, { layer: 1 })]
436
517
  })
437
518
  })
438
519
  });
@@ -639,6 +720,7 @@ var UserContextMenuButton = ({ children, className, imageUrl, role = "menuitem",
639
720
  role,
640
721
  type: "button",
641
722
  children: [/* @__PURE__ */ jsx(Avatar, {
723
+ "aria-hidden": true,
642
724
  imageUrl,
643
725
  size: "sm",
644
726
  userName
@@ -783,13 +865,28 @@ var ContextMenuButton = (props) => {
783
865
  });
784
866
  return /* @__PURE__ */ jsx(BaseContextMenuButton, { ...buttonProps });
785
867
  };
786
- var ContextMenuBackButton = ({ children, className, role = "menuitem", ...props }) => /* @__PURE__ */ jsx("button", {
787
- ...props,
788
- className: clsx("str-chat__context-menu__back-button", className),
789
- role,
790
- type: "button",
791
- children
792
- });
868
+ var ContextMenuBackButton = ({ "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, children, className, role = "menuitem", ...props }) => {
869
+ const { t } = useTranslationContext();
870
+ const generatedBackNavigationLabelId = useStableId();
871
+ const generatedVisibleLabelId = useStableId();
872
+ const resolvedAriaLabel = ariaLabel ?? t("aria/Back to parent menu button");
873
+ const resolvedAriaLabelledBy = ariaLabelledBy ?? `${generatedVisibleLabelId} ${generatedBackNavigationLabelId}`;
874
+ return /* @__PURE__ */ jsxs("button", {
875
+ ...props,
876
+ "aria-labelledby": resolvedAriaLabelledBy,
877
+ className: clsx("str-chat__context-menu__back-button", className),
878
+ role,
879
+ type: "button",
880
+ children: [!ariaLabelledBy && /* @__PURE__ */ jsx(VisuallyHidden, {
881
+ id: generatedBackNavigationLabelId,
882
+ children: resolvedAriaLabel
883
+ }), /* @__PURE__ */ jsx("span", {
884
+ className: "str-chat__context-menu__back-button__content",
885
+ id: generatedVisibleLabelId,
886
+ children
887
+ })]
888
+ });
889
+ };
793
890
  var ContextMenuHeader = ({ children, className, ...props }) => /* @__PURE__ */ jsx("div", {
794
891
  ...props,
795
892
  className: clsx("str-chat__context-menu__header", className),
@@ -837,6 +934,13 @@ var resolveContextMenuFocusRestoreTarget = ({ contextMenuRoot, request }) => {
837
934
  if (request.index >= 0) return getVisibleContextMenuKeyboardNavigationItems(contextMenuRoot)[request.index] ?? null;
838
935
  return null;
839
936
  };
937
+ var CONTEXT_MENU_HEADER_SELECTOR = ".str-chat__context-menu__header";
938
+ var resolveSubmenuInitialFocusTarget = ({ contextMenuRoot, initialFocus, itemSelector }) => {
939
+ if (initialFocus === "none") return null;
940
+ const items = getVisibleContextMenuKeyboardNavigationItems(contextMenuRoot, itemSelector);
941
+ if (initialFocus === "first") return items[0] ?? null;
942
+ return items.find((item) => !item.closest(CONTEXT_MENU_HEADER_SELECTOR)) ?? items[0] ?? null;
943
+ };
840
944
  var ContextMenuContext = React.createContext(void 0);
841
945
  var useContextMenuContext = () => useContext(ContextMenuContext);
842
946
  /**
@@ -846,7 +950,10 @@ var useContextMenuContext = () => useContext(ContextMenuContext);
846
950
  * customize submenu/back navigation behavior while keeping the same anchor/focus
847
951
  * handling from `ContextMenu`.
848
952
  */
849
- function ContextMenuContent({ anchorReferenceElement, backLabel = "Back", children, className, enableAnimations = true, Header, items, ItemsWrapper, keyboardNavigation, menuClassName, onClose, onMenuLevelChange, transitionDirection, ...props }) {
953
+ function ContextMenuContent({ anchorReferenceElement, backButtonAriaLabel, backLabel, children, className, enableAnimations = true, Header, items, ItemsWrapper, keyboardNavigation, menuClassName, onClose, onMenuLevelChange, transitionDirection, ...props }) {
954
+ const { t } = useTranslationContext();
955
+ const resolvedBackLabel = backLabel ?? t("Back");
956
+ const { ["aria-describedby"]: rootAriaDescribedBy, ["aria-label"]: rootAriaLabel, ["aria-labelledby"]: rootAriaLabelledBy, ...contextMenuRootProps } = props;
850
957
  const rootLevel = useMemo(() => ({
851
958
  Header,
852
959
  items,
@@ -862,18 +969,23 @@ function ContextMenuContent({ anchorReferenceElement, backLabel = "Back", childr
862
969
  const [menuBodyAnimationKey, setMenuBodyAnimationKey] = useState(0);
863
970
  const contextMenuRootRef = useRef(null);
864
971
  const focusRestoreRequestRef = useRef(null);
972
+ const pendingForwardFocusRef = useRef(null);
865
973
  const activeMenu = menuStack[menuStack.length - 1];
974
+ const keyboardNavigationItemSelector = keyboardNavigation?.itemSelector ?? DEFAULT_CONTEXT_MENU_KEYBOARD_NAVIGATION_ITEM_SELECTOR;
866
975
  const ActiveMenuItemsWrapper = activeMenu.ItemsWrapper ?? React.Fragment;
867
976
  const closeMenu = useCallback(() => {
868
977
  onClose?.();
869
978
  }, [onClose]);
870
- const openSubmenu = useCallback(({ focusReturnTarget, Header, ItemsWrapper: SubmenuItemsWrapper, menuClassName, Submenu }) => {
979
+ const openSubmenu = useCallback(({ focusReturnTarget, Header, initialFocus = "first-item", ItemsWrapper: SubmenuItemsWrapper, menuClassName, Submenu }) => {
980
+ pendingForwardFocusRef.current = initialFocus;
981
+ focusRestoreRequestRef.current = null;
871
982
  const nextLevel = {
872
983
  focusRestoreRequest: createContextMenuFocusRestoreRequest({
873
984
  contextMenuRoot: contextMenuRootRef.current,
874
985
  focusReturnTarget
875
986
  }),
876
987
  Header,
988
+ initialFocus,
877
989
  ItemsWrapper: SubmenuItemsWrapper ?? ItemsWrapper,
878
990
  menuClassName,
879
991
  Submenu
@@ -884,6 +996,7 @@ function ContextMenuContent({ anchorReferenceElement, backLabel = "Back", childr
884
996
  setMenuStack((current) => {
885
997
  if (current.length <= 1) return current;
886
998
  focusRestoreRequestRef.current = current[current.length - 1]?.focusRestoreRequest ?? null;
999
+ pendingForwardFocusRef.current = null;
887
1000
  return current.slice(0, -1);
888
1001
  });
889
1002
  }, []);
@@ -896,17 +1009,24 @@ function ContextMenuContent({ anchorReferenceElement, backLabel = "Back", childr
896
1009
  useEffect(() => {
897
1010
  onMenuLevelChange?.(menuStack.length);
898
1011
  }, [menuStack.length, onMenuLevelChange]);
899
- useEffect(() => {
1012
+ useLayoutEffect(() => {
900
1013
  const focusRestoreRequest = focusRestoreRequestRef.current;
901
- if (!focusRestoreRequest) return;
902
- focusRestoreRequestRef.current = null;
903
- requestAnimationFrame(() => {
904
- resolveContextMenuFocusRestoreTarget({
905
- contextMenuRoot: contextMenuRootRef.current,
906
- request: focusRestoreRequest
907
- })?.focus();
908
- });
909
- }, [menuStack.length]);
1014
+ const pendingForwardFocus = pendingForwardFocusRef.current;
1015
+ if (!focusRestoreRequest && !pendingForwardFocus) return;
1016
+ const contextMenuRoot = contextMenuRootRef.current;
1017
+ (focusRestoreRequest ? resolveContextMenuFocusRestoreTarget({
1018
+ contextMenuRoot,
1019
+ request: focusRestoreRequest
1020
+ }) : resolveSubmenuInitialFocusTarget({
1021
+ contextMenuRoot,
1022
+ initialFocus: pendingForwardFocus,
1023
+ itemSelector: keyboardNavigationItemSelector
1024
+ }))?.focus();
1025
+ }, [
1026
+ keyboardNavigationItemSelector,
1027
+ menuBodyAnimationKey,
1028
+ menuStack.length
1029
+ ]);
910
1030
  useEffect(() => {
911
1031
  if (!transitionDirection) return;
912
1032
  setMenuBodyAnimationKey((value) => value + 1);
@@ -918,10 +1038,7 @@ function ContextMenuContent({ anchorReferenceElement, backLabel = "Back", childr
918
1038
  const unregisterDialogSubmenu = useCallback(() => {
919
1039
  dialogSubmenuOpenCountRef.current = Math.max(0, dialogSubmenuOpenCountRef.current - 1);
920
1040
  }, []);
921
- const rovingFocusKeyDownHandler = useMemo(() => {
922
- const itemSelector = keyboardNavigation?.itemSelector ?? DEFAULT_CONTEXT_MENU_KEYBOARD_NAVIGATION_ITEM_SELECTOR;
923
- return createRovingFocusKeyDownHandler({ getItems: (event) => getVisibleContextMenuKeyboardNavigationItems(event.currentTarget, itemSelector) });
924
- }, [keyboardNavigation]);
1041
+ const rovingFocusKeyDownHandler = useMemo(() => createRovingFocusKeyDownHandler({ getItems: (event) => getVisibleContextMenuKeyboardNavigationItems(event.currentTarget, keyboardNavigationItemSelector) }), [keyboardNavigationItemSelector]);
925
1042
  const escapeConsumedRef = useRef(false);
926
1043
  const keyboardNavigationHandler = useCallback((event) => {
927
1044
  if (event.key === "Escape") {
@@ -947,6 +1064,7 @@ function ContextMenuContent({ anchorReferenceElement, backLabel = "Back", childr
947
1064
  event.stopPropagation();
948
1065
  }
949
1066
  }, []);
1067
+ const isSubmenuLevel = menuStack.length > 1;
950
1068
  return /* @__PURE__ */ jsx(ContextMenuContext.Provider, {
951
1069
  value: {
952
1070
  anchorReferenceElement,
@@ -957,15 +1075,19 @@ function ContextMenuContent({ anchorReferenceElement, backLabel = "Back", childr
957
1075
  unregisterDialogSubmenu
958
1076
  },
959
1077
  children: /* @__PURE__ */ jsxs(ContextMenuRoot, {
1078
+ "aria-describedby": isSubmenuLevel ? void 0 : rootAriaDescribedBy,
1079
+ "aria-label": isSubmenuLevel ? t("aria/Submenu") : rootAriaLabel,
1080
+ "aria-labelledby": isSubmenuLevel ? void 0 : rootAriaLabelledBy,
960
1081
  className: clsx(className, activeMenu.menuClassName),
961
1082
  "data-str-chat-enable-animations": enableAnimations,
962
1083
  onKeyDownCapture: keyboardNavigationHandler,
963
1084
  onKeyUpCapture: suppressEscapeKeyUp,
964
1085
  ref: contextMenuRootRef,
965
- ...props,
1086
+ ...contextMenuRootProps,
966
1087
  children: [activeMenu.Header ? /* @__PURE__ */ jsx(activeMenu.Header, {}) : menuStack.length > 1 ? /* @__PURE__ */ jsx(ContextMenuHeader, { children: /* @__PURE__ */ jsxs(ContextMenuBackButton, {
1088
+ "aria-label": backButtonAriaLabel,
967
1089
  onClick: returnToParentMenu,
968
- children: [/* @__PURE__ */ jsx(IconChevronLeft, {}), /* @__PURE__ */ jsx("span", { children: backLabel })]
1090
+ children: [/* @__PURE__ */ jsx(IconChevronLeft, {}), /* @__PURE__ */ jsx("span", { children: resolvedBackLabel })]
969
1091
  }) }) : null, /* @__PURE__ */ jsx(ContextMenuBody, {
970
1092
  className: clsx({
971
1093
  "str-chat__context-menu__body--submenu-backward": transitionDirection === "backward",
@@ -1070,9 +1192,13 @@ var ContextMenu = (props) => {
1070
1192
  transitionDirection
1071
1193
  });
1072
1194
  if (isAnchored) {
1073
- const { backLabel: _b, enableAnimations: _ea, Header: _h, items: _i, ItemsWrapper: _w, keyboardNavigation: _kn, menuClassName: _m, onClose: _c, role: _r, ...anchorDivProps } = menuProps;
1195
+ const { ["aria-describedby"]: ariaDescribedBy, ["aria-label"]: ariaLabel, ["aria-labelledby"]: ariaLabelledBy, backButtonAriaLabel: _bbal, backLabel: _b, enableAnimations: _ea, Header: _h, items: _i, ItemsWrapper: _w, keyboardNavigation: _kn, menuClassName: _m, onClose: _c, role: _r, ...anchorDivProps } = menuProps;
1196
+ const isSubmenuLevel = menuLevel > 1;
1074
1197
  return /* @__PURE__ */ jsx(DialogAnchor, {
1075
1198
  allowFlip,
1199
+ "aria-describedby": isSubmenuLevel ? void 0 : ariaDescribedBy,
1200
+ "aria-label": isSubmenuLevel ? void 0 : ariaLabel,
1201
+ "aria-labelledby": isSubmenuLevel ? void 0 : ariaLabelledBy,
1076
1202
  closeOnClickOutside,
1077
1203
  closeTransitionMs,
1078
1204
  dialogManagerId,
@@ -1143,8 +1269,7 @@ var PromptHeader = ({ className, close, description, descriptionId, goBack, Lead
1143
1269
  className: "str-chat__prompt__header__trailing-content",
1144
1270
  children: [TrailingContent && /* @__PURE__ */ jsx(TrailingContent, {}), close && /* @__PURE__ */ jsx(Button, {
1145
1271
  appearance: "ghost",
1146
- "aria-describedby": hasDescription ? resolvedDescriptionId : void 0,
1147
- "aria-label": typeof title === "string" ? t("Close prompt: {{ title }}", { title }) : t("Close"),
1272
+ "aria-label": t("Close"),
1148
1273
  circular: true,
1149
1274
  className: "str-chat__prompt__header__close-button",
1150
1275
  onClick: close,
@@ -1220,19 +1345,21 @@ var useResolvedModalAriaProps = ({ ariaDescribedby, ariaLabel, ariaLabelledby, d
1220
1345
  };
1221
1346
  //#endregion
1222
1347
  //#region src/components/Modal/GlobalModal.tsx
1223
- var GlobalModal = ({ "aria-describedby": ariaDescribedby, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, children, className, CloseButtonOnOverlay, dialogId, dialogRootProps, onClose, onCloseAttempt, open, role = "dialog" }) => {
1348
+ var GlobalModal = ({ "aria-describedby": ariaDescribedby, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, children, className, CloseButtonOnOverlay, dialogId, dialogRootProps, getInitialFocusElement, initialFocusStrategy, onClose, onCloseAttempt, open, role = "dialog" }) => {
1224
1349
  const generatedDialogId = useStableId();
1225
1350
  const resolvedDialogId = dialogId ?? `modal-dialog-${generatedDialogId}`;
1226
1351
  const dialog = useModalDialog(resolvedDialogId);
1227
1352
  const isOpen = useModalDialogIsOpen(resolvedDialogId);
1228
1353
  const isTopmost = useModalDialogIsTopmost(resolvedDialogId);
1229
1354
  const overlayRef = useRef(null);
1355
+ const dialogRef = useRef(null);
1230
1356
  const closeButtonRef = useRef(null);
1231
1357
  const closingRef = useRef(false);
1232
1358
  const { theme } = useChatContext();
1233
1359
  const { NotificationList: NotificationList$1 = NotificationList } = useComponentContext();
1234
1360
  const { className: dialogRootClassName, onKeyDown: dialogRootOnKeyDown, ...dialogRootPropsRest } = dialogRootProps ?? {};
1235
- const dialogLabelingBaseId = dialog.id;
1361
+ const dialogLabelingBaseId = resolvedDialogId;
1362
+ const resolvedInitialFocusStrategy = initialFocusStrategy ?? "firstElement";
1236
1363
  const resolvedModalAriaProps = useResolvedModalAriaProps({
1237
1364
  ariaDescribedby,
1238
1365
  ariaLabel,
@@ -1265,8 +1392,18 @@ var GlobalModal = ({ "aria-describedby": ariaDescribedby, "aria-label": ariaLabe
1265
1392
  };
1266
1393
  const handleDialogKeyDown = (event) => {
1267
1394
  dialogRootOnKeyDown?.(event);
1268
- if (event.defaultPrevented || event.key !== "Escape" || !isTopmost) return;
1269
- maybeClose("escape", event);
1395
+ if (event.defaultPrevented || !isTopmost) return;
1396
+ if (event.key === "Escape") {
1397
+ maybeClose("escape", event);
1398
+ return;
1399
+ }
1400
+ if (event.key === "Enter" && event.target === dialogRef.current) {
1401
+ const target = dialogRef.current?.querySelector("[data-autofocus]");
1402
+ if (target) {
1403
+ event.preventDefault();
1404
+ target.focus();
1405
+ }
1406
+ }
1270
1407
  };
1271
1408
  useEffect(() => {
1272
1409
  if (!open) {
@@ -1280,6 +1417,21 @@ var GlobalModal = ({ "aria-describedby": ariaDescribedby, "aria-label": ariaLabe
1280
1417
  isOpen,
1281
1418
  open
1282
1419
  ]);
1420
+ useEffect(() => {
1421
+ if (!open || !isOpen || !isTopmost || !getInitialFocusElement && resolvedInitialFocusStrategy === "firstElement") return;
1422
+ const frame = requestAnimationFrame(() => {
1423
+ const dialogElement = dialogRef.current;
1424
+ if (!dialogElement) return;
1425
+ (getInitialFocusElement?.(dialogElement) ?? dialogElement).focus();
1426
+ });
1427
+ return () => cancelAnimationFrame(frame);
1428
+ }, [
1429
+ getInitialFocusElement,
1430
+ isOpen,
1431
+ isTopmost,
1432
+ open,
1433
+ resolvedInitialFocusStrategy
1434
+ ]);
1283
1435
  if (!open || !isOpen) return null;
1284
1436
  return /* @__PURE__ */ jsx(DialogPortalEntry, {
1285
1437
  dialogId: resolvedDialogId,
@@ -1292,7 +1444,7 @@ var GlobalModal = ({ "aria-describedby": ariaDescribedby, "aria-label": ariaLabe
1292
1444
  ref: overlayRef,
1293
1445
  children: [
1294
1446
  /* @__PURE__ */ jsx(FocusScope, {
1295
- autoFocus: isTopmost,
1447
+ autoFocus: isTopmost && !getInitialFocusElement && resolvedInitialFocusStrategy === "firstElement",
1296
1448
  contain: isTopmost,
1297
1449
  restoreFocus: true,
1298
1450
  children: /* @__PURE__ */ jsx("div", {
@@ -1304,15 +1456,16 @@ var GlobalModal = ({ "aria-describedby": ariaDescribedby, "aria-label": ariaLabe
1304
1456
  className: clsx("str-chat__modal__dialog", dialogRootClassName),
1305
1457
  inert: isTopmost ? void 0 : true,
1306
1458
  onKeyDown: handleDialogKeyDown,
1459
+ ref: dialogRef,
1307
1460
  role,
1308
1461
  tabIndex: isTopmost ? 0 : -1,
1309
- children: /* @__PURE__ */ jsx(DialogManagerProvider, {
1462
+ children: /* @__PURE__ */ jsxs(DialogManagerProvider, {
1310
1463
  id: `${resolvedDialogId}-floating-dialog-manager`,
1311
1464
  portalDestinationProps: {
1312
1465
  captureOutsideClicks: true,
1313
1466
  className: "str-chat__modal__floating-dialog-overlay"
1314
1467
  },
1315
- children
1468
+ children: [children, isTopmost && /* @__PURE__ */ jsx(AriaLiveOutlet, { layer: 1 })]
1316
1469
  })
1317
1470
  })
1318
1471
  }),
@@ -2676,13 +2829,28 @@ var SwitchField = ({ children, description, fieldClassName, title, ...props }) =
2676
2829
  const switchId = id ?? `str-chat__switch-field-${generatedSwitchId}`;
2677
2830
  const switchLabelId = `${switchId}-label`;
2678
2831
  const inputRef = useRef(null);
2832
+ const announce = useContext(AriaLiveAnnouncerContext)?.announce;
2833
+ const { t } = useTranslationContext("SwitchField");
2679
2834
  const [uncontrolledChecked, setUncontrolledChecked] = useState(Boolean(defaultChecked));
2680
2835
  const isControlled = checked !== void 0;
2681
2836
  const isOn = isControlled ? checked : uncontrolledChecked;
2682
2837
  const isReadOnly = isControlled && onChange === void 0;
2838
+ const childLabelId = (isValidElement(children) ? children : void 0)?.props.id;
2839
+ const resolvedAriaLabelledBy = ariaLabelledBy ?? (!ariaLabel ? title ? switchLabelId : childLabelId : void 0);
2840
+ const resolveAnnouncementName = () => {
2841
+ if (title) return title;
2842
+ if (ariaLabel) return ariaLabel;
2843
+ if (resolvedAriaLabelledBy) {
2844
+ const labelEl = document.getElementById(resolvedAriaLabelledBy);
2845
+ return ((labelEl?.querySelector(".str-chat__form__switch-field__label__text"))?.textContent || labelEl?.textContent || "").trim() || void 0;
2846
+ }
2847
+ };
2683
2848
  const handleChange = (event) => {
2684
2849
  if (!isControlled) setUncontrolledChecked(event.target.checked);
2685
2850
  onChange?.(event);
2851
+ const setting = resolveAnnouncementName();
2852
+ if (!announce || !setting) return;
2853
+ announce(event.target.checked ? t("aria/{{ setting }} enabled", { setting }) : t("aria/{{ setting }} disabled", { setting }), { priority: "polite" });
2686
2854
  };
2687
2855
  const handleKeyDown = (event) => {
2688
2856
  onKeyDown?.(event);
@@ -2694,8 +2862,6 @@ var SwitchField = ({ children, description, fieldClassName, title, ...props }) =
2694
2862
  if (disabled || event.target === inputRef.current) return;
2695
2863
  inputRef.current?.click();
2696
2864
  };
2697
- const childLabelId = (isValidElement(children) ? children : void 0)?.props.id;
2698
- const resolvedAriaLabelledBy = ariaLabelledBy ?? (!ariaLabel ? title ? switchLabelId : childLabelId : void 0);
2699
2865
  return /* @__PURE__ */ jsxs("div", {
2700
2866
  className: clsx("str-chat__form__switch-field", fieldClassName, disabled && "str-chat__form__switch-field--disabled"),
2701
2867
  children: [title ? /* @__PURE__ */ jsx(SwitchFieldLabel, {
@@ -2835,7 +3001,6 @@ var TextInput = forwardRef(function TextInput({ className, disabled, error = fal
2835
3001
  className: "str-chat__form-text-input__control-row",
2836
3002
  children: [
2837
3003
  leading ? /* @__PURE__ */ jsx("span", {
2838
- "aria-hidden": true,
2839
3004
  className: "str-chat__form-text-input__leading",
2840
3005
  children: leading
2841
3006
  }) : null,
@@ -2854,7 +3019,6 @@ var TextInput = forwardRef(function TextInput({ className, disabled, error = fal
2854
3019
  children: trailingText
2855
3020
  }) : null,
2856
3021
  trailing ? /* @__PURE__ */ jsx("span", {
2857
- "aria-hidden": true,
2858
3022
  className: "str-chat__form-text-input__trailing",
2859
3023
  children: trailing
2860
3024
  }) : null
@@ -2934,6 +3098,16 @@ var renderPreviewText = (text) => /* @__PURE__ */ jsx(ReactMarkdown, {
2934
3098
  skipHtml: true,
2935
3099
  children: text
2936
3100
  });
3101
+ var createPlainTextProcessor = () => unified().use(remarkParse).use(remarkPlugins);
3102
+ var plainTextProcessor;
3103
+ var stripMarkdownToText = (text) => {
3104
+ try {
3105
+ const processor = plainTextProcessor ?? (plainTextProcessor = createPlainTextProcessor());
3106
+ return toString(processor.runSync(processor.parse(text))) || text;
3107
+ } catch {
3108
+ return text;
3109
+ }
3110
+ };
2937
3111
  var getLatestPollVote = (latestVotesByOption) => {
2938
3112
  let latestVote;
2939
3113
  for (const optionVotes of Object.values(latestVotesByOption)) optionVotes.forEach((vote) => {
@@ -2942,32 +3116,130 @@ var getLatestPollVote = (latestVotesByOption) => {
2942
3116
  });
2943
3117
  return latestVote;
2944
3118
  };
2945
- var getLatestMessagePreview = (channel, t, userLanguage = "en", isMessageAIGenerated) => {
2946
- const latestMessage = channel.state.latestMessages[channel.state.latestMessages.length - 1];
3119
+ /**
3120
+ * Resolves the channel's latest-message preview once: the localized DISPLAY string, a `kind`
3121
+ * discriminant, and the bits announcements need (poll question, attachment type). Single source of
3122
+ * truth for the display variant ({@link getLatestMessagePreview}) and the announcement variant
3123
+ * ({@link getLatestMessagePreviewText}), so the translation lookup and branching run once per call.
3124
+ */
3125
+ var getLatestMessagePreviewParts = (channel, t, userLanguage = "en", latestMessageArg) => {
3126
+ const latestMessage = latestMessageArg ?? channel.state.latestMessages[channel.state.latestMessages.length - 1];
2947
3127
  const previewTextToRender = getTranslatedMessageText({
2948
3128
  language: userLanguage,
2949
3129
  message: latestMessage
2950
3130
  }) || latestMessage?.text;
2951
3131
  const poll = latestMessage?.poll;
2952
- if (!latestMessage) return t("Nothing yet...");
2953
- if (isMessageDeleted(latestMessage)) return t("Message deleted");
2954
- if (poll) if (!poll.vote_count) return t("📊 {{createdBy}} created: {{ pollName}}", {
2955
- createdBy: poll.created_by?.id === channel.getClient().userID ? t("You") : poll.created_by?.name ?? t("Poll"),
2956
- pollName: poll.name
2957
- });
2958
- else {
3132
+ if (!latestMessage) return {
3133
+ isUserMessageText: false,
3134
+ kind: "empty",
3135
+ latestMessage,
3136
+ text: t("Nothing yet...")
3137
+ };
3138
+ if (isMessageDeleted(latestMessage)) return {
3139
+ isUserMessageText: false,
3140
+ kind: "deleted",
3141
+ latestMessage,
3142
+ text: t("Message deleted")
3143
+ };
3144
+ if (poll) if (!poll.vote_count) {
3145
+ const createdBy = poll.created_by?.id === channel.getClient().userID ? t("You") : poll.created_by?.name ?? t("Poll");
3146
+ return {
3147
+ isUserMessageText: false,
3148
+ kind: "poll",
3149
+ latestMessage,
3150
+ pollName: poll.name,
3151
+ text: t("📊 {{createdBy}} created: {{ pollName}}", {
3152
+ createdBy,
3153
+ pollName: poll.name
3154
+ })
3155
+ };
3156
+ } else {
2959
3157
  const latestVote = getLatestPollVote(poll.latest_votes_by_option);
2960
3158
  const option = latestVote && poll.options.find((opt) => opt.id === latestVote.option_id);
2961
- if (option && latestVote) return t("📊 {{votedBy}} voted: {{pollOptionText}}", {
2962
- pollOptionText: option.text,
2963
- votedBy: latestVote?.user?.id === channel.getClient().userID ? t("You") : latestVote.user?.name ?? t("Poll")
2964
- });
3159
+ if (option && latestVote) return {
3160
+ isUserMessageText: false,
3161
+ kind: "poll",
3162
+ latestMessage,
3163
+ pollName: poll.name,
3164
+ text: t("📊 {{votedBy}} voted: {{pollOptionText}}", {
3165
+ pollOptionText: option.text,
3166
+ votedBy: latestVote?.user?.id === channel.getClient().userID ? t("You") : latestVote.user?.name ?? t("Poll")
3167
+ })
3168
+ };
3169
+ }
3170
+ if (previewTextToRender) return {
3171
+ isUserMessageText: true,
3172
+ kind: "text",
3173
+ latestMessage,
3174
+ text: previewTextToRender
3175
+ };
3176
+ if (latestMessage.command) return {
3177
+ isUserMessageText: false,
3178
+ kind: "command",
3179
+ latestMessage,
3180
+ text: `/${latestMessage.command}`
3181
+ };
3182
+ if (latestMessage.attachments?.length) return {
3183
+ isUserMessageText: false,
3184
+ kind: "attachment",
3185
+ latestMessage,
3186
+ text: t("🏙 Attachment...")
3187
+ };
3188
+ if (latestMessage.shared_location) return {
3189
+ isUserMessageText: false,
3190
+ kind: "location",
3191
+ latestMessage,
3192
+ text: t("📍Shared location")
3193
+ };
3194
+ return {
3195
+ isUserMessageText: false,
3196
+ kind: "text",
3197
+ latestMessage,
3198
+ text: t("Empty message...")
3199
+ };
3200
+ };
3201
+ /**
3202
+ * Maps a known attachment `type` to a localized, human-readable word (e.g. "image" → "Image"). The
3203
+ * cases are literal `t('aria/…')` calls so `i18next-cli` extracts them. Unknown/custom types return
3204
+ * `undefined`, so the announcement falls back to a generic "Attachment".
3205
+ */
3206
+ var getAttachmentTypeLabel = (type, t) => {
3207
+ switch (type) {
3208
+ case "audio": return t("aria/audio");
3209
+ case "file": return t("aria/file");
3210
+ case "giphy": return t("aria/GIF");
3211
+ case "image": return t("aria/image");
3212
+ case "video": return t("aria/video");
3213
+ case "voiceRecording": return t("aria/voice message");
3214
+ default: return;
2965
3215
  }
2966
- if (previewTextToRender) return isMessageAIGenerated?.(latestMessage) ? previewTextToRender : renderPreviewText(previewTextToRender);
2967
- if (latestMessage.command) return `/${latestMessage.command}`;
2968
- if (latestMessage.attachments?.length) return t("🏙 Attachment...");
2969
- if (latestMessage.shared_location) return t("📍Shared location");
2970
- return t("Empty message...");
3216
+ };
3217
+ /**
3218
+ * The text announced for the channel's latest message (for an accessible name / live region).
3219
+ * Non-text content gets a concise, screen-reader-friendly phrasing that may differ from the visible
3220
+ * preview: a poll announces its question, an attachment its type, a shared location and a deleted
3221
+ * message a plain phrase. User-message text is stripped of markdown so the words are read, not the
3222
+ * syntax (AI-generated text is returned verbatim, matching the display path).
3223
+ */
3224
+ var getLatestMessagePreviewText = (channel, t, userLanguage = "en", isMessageAIGenerated, latestMessage) => {
3225
+ const { isUserMessageText, kind, latestMessage: resolvedLatestMessage, pollName, text } = getLatestMessagePreviewParts(channel, t, userLanguage, latestMessage);
3226
+ switch (kind) {
3227
+ case "poll": return t("aria/Poll: {{ pollName }}", { pollName: pollName ?? "" });
3228
+ case "attachment": {
3229
+ const realAttachments = resolvedLatestMessage?.attachments?.filter((attachment) => !attachment.og_scrape_url) ?? [];
3230
+ if (realAttachments.length > 1) return t("aria/Message with attachments");
3231
+ const typeLabel = getAttachmentTypeLabel(realAttachments[0]?.type, t);
3232
+ return typeLabel ? t("aria/Attachment {{ attachmentType }}", { attachmentType: typeLabel }) : t("aria/Attachment");
3233
+ }
3234
+ case "location": return t("aria/Shared location");
3235
+ case "empty": return t("aria/There are no messages in this chat.");
3236
+ case "text": return isUserMessageText && resolvedLatestMessage && !isMessageAIGenerated?.(resolvedLatestMessage) ? stripMarkdownToText(text) : text;
3237
+ default: return text;
3238
+ }
3239
+ };
3240
+ var getLatestMessagePreview = (channel, t, userLanguage = "en", isMessageAIGenerated, latestMessage) => {
3241
+ const { isUserMessageText, latestMessage: resolvedLatestMessage, text } = getLatestMessagePreviewParts(channel, t, userLanguage, latestMessage);
3242
+ return isUserMessageText && resolvedLatestMessage && !isMessageAIGenerated?.(resolvedLatestMessage) ? renderPreviewText(text) : text;
2971
3243
  };
2972
3244
  /**
2973
3245
  * Channel display image: channel.data.image, or for DM (2 members) the other member's user.image.
@@ -3192,7 +3464,8 @@ var Notification = forwardRef(({ className, entryDirection, Icon = DefaultNotifi
3192
3464
  };
3193
3465
  const isPersistent = !notification.duration;
3194
3466
  const severity = notification.severity;
3195
- const livePriority = severity === "error" ? "assertive" : "polite";
3467
+ const ariaLiveOverride = notification.metadata?.ariaLive;
3468
+ const livePriority = ariaLiveOverride === "assertive" || ariaLiveOverride === "polite" ? ariaLiveOverride : severity === "error" ? "assertive" : "polite";
3196
3469
  return /* @__PURE__ */ jsxs("div", {
3197
3470
  className: clsx("str-chat__notification", entryDirection && `str-chat__notification--enter-from-${entryDirection}`, transitionState === "enter" && "str-chat__notification--is-entering", transitionState === "exit" && "str-chat__notification--is-exiting", severity && `str-chat__notification--${severity}`, severity === "loading" && "str-chat__notification--loading", className),
3198
3471
  "data-testid": "notification",
@@ -3611,6 +3884,6 @@ function useChannelHeaderOnlineStatus({ channel: channelOverride, watcherCount:
3611
3884
  return `${t("{{ memberCount }} members", { memberCount })} · ${t("{{ watcherCount }} online", { watcherCount })}`;
3612
3885
  }
3613
3886
  //#endregion
3614
- export { ContextMenuBackButton as $, GalleryUI as A, MessageTranslationViewContext as At, FileIcon as B, Checkmark as C, LoadingIndicator as Ct, SwitchFieldLabel as D, ModalContext as Dt, SwitchFieldDescription as E, useNotificationConfigurationContext as Et, useGalleryContext as F, MessageProvider as Ft, Timestamp as G, FILE_ICON_NO_LABEL_CLASSNAME as H, toBaseImageDescriptors as I, useMessageContext as It, htmlToTextPlugin as J, plusPlusToEmphasis as K, BaseImage as L, VideoPlayer as M, getTranslatedMessageText as Mt, GalleryContext as N, useMessageTranslationViewContext as Nt, SwitchFieldTitle as O, ModalContextProvider as Ot, toGalleryItemDescriptors as P, MessageContext as Pt, ContextMenu as Q, ImagePlaceholder as R, Checkbox as S, FileSizeIndicator as St, SwitchField as T, NotificationConfigurationProvider as Tt, DownloadButton as U, FILE_ICON_GRAPHIC_CLASSNAME as V, MessageTimestamp as W, Prompt as X, GlobalModal as Y, BaseContextMenuButton as Z, getLatestMessagePreview as _, Alert as _t, pickNewest as a, DEFAULT_CONTEXT_MENU_KEYBOARD_NAVIGATION_ITEM_SELECTOR as at, ListItemLayoutContent as b, useAriaIdentifiers as bt, useIsUserMuted as c, useContextMenuContext as ct, useSelectedChannelState as d, ChannelAvatar as dt, ContextMenuBody as et, isDmChannel as f, Badge as ft, getGroupChannelDisplayInfo as g, useDialogAnchor as gt, getChannelDisplayImage as h, DialogAnchor as ht, createDefaultPickNext as i, ContextMenuRoot as it, VideoThumbnail as j, MessageTranslationViewProvider as jt, Gallery as k, useModalContext as kt, useIsChannelMuted as l, createRovingFocusKeyDownHandler as lt, useChannelDisplayName as m, Avatar as mt, useChannelHasMembersOnline as n, ContextMenuContent as nt, pickOldest as o, EmojiContextMenuButton as ot, useChannelPreviewInfo as p, ErrorBadge as pt, imageToLink as q, NotificationList as r, ContextMenuHeader as rt, Notification as s, UserContextMenuButton as st, useChannelHeaderOnlineStatus as t, ContextMenuButton as tt, useChannelMembershipState as u, GroupAvatar as ut, renderPreviewText as v, Header as vt, Switch as w, useNotifications as wt, TextInput as x, useStableCallback as xt, ListItemLayout as y, Root as yt, formatTime as z };
3887
+ export { ContextMenu as $, Gallery as A, VisuallyHidden as At, formatTime as B, MessageContext as Bt, Checkbox as C, useStableCallback as Ct, SwitchFieldDescription as D, NotificationConfigurationProvider as Dt, SwitchField as E, useNotifications as Et, toGalleryItemDescriptors as F, useModalContext as Ft, MessageTimestamp as G, FILE_ICON_GRAPHIC_CLASSNAME as H, useMessageContext as Ht, useGalleryContext as I, MessageTranslationViewContext as It, imageToLink as J, Timestamp as K, toBaseImageDescriptors as L, MessageTranslationViewProvider as Lt, VideoThumbnail as M, useAriaLiveOutletContext as Mt, VideoPlayer as N, ModalContext as Nt, SwitchFieldLabel as O, useNotificationConfigurationContext as Ot, GalleryContext as P, ModalContextProvider as Pt, BaseContextMenuButton as Q, BaseImage as R, getTranslatedMessageText as Rt, TextInput as S, useAriaIdentifiers as St, Switch as T, LoadingIndicator as Tt, FILE_ICON_NO_LABEL_CLASSNAME as U, FileIcon as V, MessageProvider as Vt, DownloadButton as W, GlobalModal as X, htmlToTextPlugin as Y, Prompt as Z, getLatestMessagePreview as _, DialogAnchor as _t, pickNewest as a, ContextMenuRoot as at, ListItemLayout as b, Header as bt, useIsUserMuted as c, UserContextMenuButton as ct, useSelectedChannelState as d, getNextRovingFocusIndex as dt, ContextMenuBackButton as et, isDmChannel as f, GroupAvatar as ft, getGroupChannelDisplayInfo as g, Avatar as gt, getChannelDisplayImage as h, ErrorBadge as ht, createDefaultPickNext as i, ContextMenuHeader as it, GalleryUI as j, AriaLiveOutletContext as jt, SwitchFieldTitle as k, AriaLiveOutlet as kt, useIsChannelMuted as l, useContextMenuContext as lt, useChannelDisplayName as m, Badge as mt, useChannelHasMembersOnline as n, ContextMenuButton as nt, pickOldest as o, DEFAULT_CONTEXT_MENU_KEYBOARD_NAVIGATION_ITEM_SELECTOR as ot, useChannelPreviewInfo as p, ChannelAvatar as pt, plusPlusToEmphasis as q, NotificationList as r, ContextMenuContent as rt, Notification as s, EmojiContextMenuButton as st, useChannelHeaderOnlineStatus as t, ContextMenuBody as tt, useChannelMembershipState as u, createRovingFocusKeyDownHandler as ut, getLatestMessagePreviewText as v, useDialogAnchor as vt, Checkmark as w, FileSizeIndicator as wt, ListItemLayoutContent as x, Root as xt, renderPreviewText as y, Alert as yt, ImagePlaceholder as z, useMessageTranslationViewContext as zt };
3615
3888
 
3616
- //# sourceMappingURL=useChannelHeaderOnlineStatus.b3a51ca7.mjs.map
3889
+ //# sourceMappingURL=useChannelHeaderOnlineStatus.c86db868.mjs.map