stream-chat-react
Version:
React components to create chat conversations or livestream style chat
1,170 lines • 1.18 MB
JavaScript
import { $ as isMessageEdited, $n as isNotificationForPanel, $t as IconFlag, A as useMessageComposerController, An as IconThreadFill, Ar as useDialogIsTopmost, At as IconBookmark, B as ACTIONS_NOT_WORKING_IN_THREAD, Bn as IconVideo, Br as useComponentContext, Bt as IconCommand, C as insertIntro, Cn as IconRefresh, Cr as useNearestDialogManagerContext, Ct as IconArrowUp, D as makeDateMessageId, Dn as IconSearch, Dr as modalDialogId, Dt as IconBell, E as isLocalMessage, En as IconRetry, Er as Portal, Et as IconAudio, F as AudioPlayer, Fn as IconUpload, Fr as useOpenedDialogCount, Ft as IconChecks, G as countEmojis, Gn as IconXmarkSmall, Gr as ChannelStateProvider, Gt as IconEmoji, H as OPTIONAL_MESSAGE_ACTIONS, Hn as IconVoice, Hr as ChatProvider, Ht as IconDelete, I as defaultRegisterAudioPlayerError, In as IconUser, Ir as useStateStore, It as IconChevronDown, J as getNonImageAttachments, Jn as usePopoverPosition, Jr as ChannelListContextProvider, Jt as IconExclamationMark, K as getImages, Kn as createIcon, Kr as useChannelStateContext, Kt as IconEmojiAdd, L as elementIsPlaying, Ln as IconUserAdd, Lr as DialogManager, Lt as IconChevronLeft, M as WithAudioPlayback, Mn as IconTrophy, Mr as useModalDialog, Mt as IconCamera, N as useActiveAudioPlayer, Nn as IconUnpin, Nr as useModalDialogIsOpen, Nt as IconCheckmark, O as makeIntroMessage, On as IconSend, Or as useDialog, Ot as IconBellOff, P as useAudioPlayer, Pn as IconUnsupportedAttachment, Pr as useModalDialogIsTopmost, Pt as IconCheckmark1Small, Q as isMessageDeleted, Qn as getNotificationTargetTag, Qr as useChannelActionContext, Qt as IconFile, R as LegacyThreadContext, Rn as IconUserCheck, Rr as ComponentContext, Rt as IconChevronRight, S as hasMoreMessagesProbably, Sn as IconQuote, Sr as useModalDialogManager, St as IconArrowLeft, T as isIntroMessage, Tn as IconReply, Tr as DialogPortalEntry, Tt as IconAttachment, U as areMessagePropsEqual, Un as IconXCircle, Ur as useChatContext, Ut as IconDownload, V as MESSAGE_ACTIONS, Vn as IconVideoFill, Vr as ChatContext, Vt as IconCopy, W as areMessageUIPropsEqual, Wn as IconXmark, Wr as ChannelStateContext, Wt as IconEdit, X as isMessageBlocked, Xn as getNotificationTargetPanel, Xr as ChannelActionContext, Xt as IconExclamationTriangleFill, Y as getReadByTooltipText, Yn as addNotificationTargetTag, Yr as useChannelListContext, Yt as IconExclamationMarkFill, Z as isMessageBounced, Zn as getNotificationTargetPanels, Zr as ChannelActionProvider, Zt as IconEyeFill, _ as Channel, _n as IconPin, _r as useMessageComposerContext, _t as EmptyStateIndicator, a as ChatView, an as IconLocation, ar as TranslationProvider, at as messageHasGiphyAttachment, b as getIsFirstUnreadMessage, bn as IconPlusSmall, br as modalDialogManagerId, bt as IconArrowDown, c as ChatViewSelectorButton, cn as IconMessageBubbles, cr as defaultTranslatorFunction, ct as messageHasSingleAttachment, d as useActiveThread, dn as IconMinusCircle, dr as isDayOrMoment, dt as EMOJI_REGEX, en as IconGiphy, er as isNotificationTargetPanel, et as isMessageErrorRetryable, f as useChatViewContext, fn as IconMore, fr as isLanguageSupported, ft as CHANNEL_CONTAINER_ID, g as useThreadContext, gn as IconPauseFill, gr as MessageComposerContextProvider, gt as useEditMessageHandler, h as ThreadProvider, hn as IconNotification, hr as MessageComposerContext, ht as useMentionsHandlers, i as useNotificationTarget, in as IconLoading, ir as TranslationContext, it as messageHasAttachments, j as useIsCooldownActive, jn as IconTranslate, jr as useDialogOnNearestManager, jt as IconBookmarkRemove, k as processMessages, kn as IconThread, kr as useDialogIsOpen, kt as IconBolt, l as ChatViewThreadsSelectorButton, ln as IconMicrophoneSolid, lr as getDateString, lt as messageTextHasEmojisOnly, m as ThreadContext, mn as IconNoSign, mr as predefinedFormatters, mt as LoadingChannel, n as hasSystemNotificationTag, nn as IconLeave, nr as TypingProvider, nt as isUserMuted, o as ChatViewChannelsSelectorButton, on as IconMessageBubble, or as useTranslationContext, ot as messageHasQuotedMessage, p as useThreadsViewContext, pn as IconMute, pr as isNumberOrString, pt as LoadingErrorIndicator, q as getMessageActions, qn as Button, qr as ChannelListContext, qt as IconExclamationCircleFill, r as useNotificationApi, rn as IconLink, rr as useTypingContext, rt as mapToUserNameOrId, s as ChatViewContext, sn as IconMessageBubbleFill, sr as defaultDateTimeParser, st as messageHasReactions, t as SYSTEM_NOTIFICATION_TAG, tn as IconImage, tr as TypingContext, tt as isNetworkSendFailure, u as defaultChatViewSelectorItemSet, un as IconMinus, ur as isDate, ut as validateAndGetMessage, v as getChannel, vn as IconPlayFill, vr as DialogManagerProvider, vt as useStableId, w as isDateSeparatorMessage, wn as IconReorder, wr as DialogPortalDestination, wt as IconArrowUpRight, x as getLastReceived, xn as IconPoll, xr as useDialogManager, xt as IconArrowDownCircle, y as getGroupStyles, yn as IconPlus, yr as ModalDialogManagerProvider, yt as IconArchive, z as useLegacyThreadContext, zn as IconUserRemove, zr as ComponentProvider, zt as IconClock } from "./useNotificationApi.88c33caa.mjs";
import { a as getExtensionFromMimeType, i as dataTransferItemsToFiles, n as toAudioBuffer, o as getRecordedMediaTypeFromMimeType, r as createFileFromBlobs, s as useHandleFileChangeWrapper, t as renderAudio } from "./audioProcessing.766ca76c.mjs";
import React, { Component, Fragment, createContext, createElement, forwardRef, isValidElement, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
import { ChannelSearchSource, LinkPreviewsManager, LiveLocationManager, MessageComposer as MessageComposer$1, MessageSearchSource, SearchController, StateStore, StreamChat, UserSearchSource, VotingVisibility, formatMessage, isAudioAttachment, isFileAttachment, isGiphyAttachment, isImageAttachment, isLocalAttachment, isLocalAudioAttachment, isLocalFileAttachment, isLocalImageAttachment, isLocalVideoAttachment, isLocalVoiceRecordingAttachment, isScrapedContent, isSharedLocationResponse, isVideoAttachment, isVoiceRecordingAttachment, isVoteAnswer } from "stream-chat";
import { nanoid } from "nanoid";
import { useSyncExternalStore } from "use-sync-external-store/shim";
import Dayjs from "dayjs";
import calendar from "dayjs/plugin/calendar";
import LocalizedFormat from "dayjs/plugin/localizedFormat";
import clsx from "clsx";
import { FocusScope } from "@react-aria/focus";
import debounce from "lodash.debounce";
import throttle from "lodash.throttle";
import i18n from "i18next";
import updateLocale from "dayjs/plugin/updateLocale";
import localeData from "dayjs/plugin/localeData";
import relativeTime from "dayjs/plugin/relativeTime";
import duration from "dayjs/plugin/duration";
import utc from "dayjs/plugin/utc";
import timezone from "dayjs/plugin/timezone";
import "dayjs/locale/de";
import "dayjs/locale/es";
import "dayjs/locale/fr";
import "dayjs/locale/hi";
import "dayjs/locale/it";
import "dayjs/locale/ja";
import "dayjs/locale/ko";
import "dayjs/locale/nl";
import "dayjs/locale/pt";
import "dayjs/locale/ru";
import "dayjs/locale/tr";
import "dayjs/locale/en";
import { findAndReplace } from "hast-util-find-and-replace";
import { u } from "unist-builder";
import { SKIP, visit } from "unist-util-visit";
import ReactMarkdown, { defaultUrlTransform } from "react-markdown";
import * as linkify from "linkifyjs";
import { find } from "linkifyjs";
import remarkGfm from "remark-gfm";
import { sanitizeUrl } from "@braintree/sanitize-url";
import fixWebmDuration from "fix-webm-duration";
import mergeWith from "lodash.mergewith";
import uniqBy from "lodash.uniqby";
import Textarea from "react-textarea-autosize";
import { useDropzone } from "react-dropzone";
import { Virtuoso } from "react-virtuoso";
//#region src/components/AIStateIndicator/hooks/useAIState.ts
var AIStates = {
Error: "AI_STATE_ERROR",
ExternalSources: "AI_STATE_EXTERNAL_SOURCES",
Generating: "AI_STATE_GENERATING",
Idle: "AI_STATE_IDLE",
Stop: "AI_STATE_STOP",
Thinking: "AI_STATE_THINKING"
};
/**
* A hook that returns the current state of the AI.
* @param {Channel} channel - The channel for which we want to know the AI state.
* @returns {{ aiState: AIState }} The current AI state for the given channel.
*/
var useAIState = (channel) => {
const [aiState, setAiState] = useState(AIStates.Idle);
useEffect(() => {
if (!channel) return;
const indicatorChangedListener = channel.on("ai_indicator.update", (event) => {
const { cid } = event;
const state = event.ai_state;
if (channel.cid === cid) setAiState(state);
});
const indicatorClearedListener = channel.on("ai_indicator.clear", (event) => {
const { cid } = event;
if (channel.cid === cid) setAiState(AIStates.Idle);
});
const indicatorStoppedListener = channel.on("ai_indicator.stop", (event) => {
const { cid } = event;
if (channel.cid === cid) setAiState(AIStates.Stop);
});
return () => {
indicatorChangedListener.unsubscribe();
indicatorClearedListener.unsubscribe();
indicatorStoppedListener.unsubscribe();
};
}, [channel]);
return { aiState };
};
//#endregion
//#region src/context/AttachmentSelectorContext.tsx
var AttachmentSelectorContext = createContext({ fileInput: null });
var AttachmentSelectorContextProvider = ({ children, value }) => /* @__PURE__ */ jsx(AttachmentSelectorContext.Provider, {
value,
children
});
var useAttachmentSelectorContext = () => useContext(AttachmentSelectorContext);
//#endregion
//#region src/context/MessageContext.tsx
var MessageContext = React.createContext(void 0);
var MessageProvider = ({ children, value }) => /* @__PURE__ */ jsx(MessageContext.Provider, {
value,
children
});
var useMessageContext = (_componentName) => {
const contextValue = useContext(MessageContext);
if (!contextValue) return {};
return contextValue;
};
//#endregion
//#region src/components/MessageComposer/preEditSnapshot.ts
var snapshots = /* @__PURE__ */ new WeakMap();
/**
* Captures a full state snapshot of the composer before entering edit mode.
* Does nothing if a snapshot already exists (e.g. switching between edits).
*/
var savePreEditSnapshot = (messageComposer) => {
if (snapshots.has(messageComposer)) return;
const composerState = messageComposer.state.getLatestValue();
const textState = messageComposer.textComposer.state.getLatestValue();
const attachmentState = messageComposer.attachmentManager.state.getLatestValue();
const linkPreviewState = messageComposer.linkPreviewsManager.state.getLatestValue();
const locationState = messageComposer.locationComposer.state.getLatestValue();
const pollState = messageComposer.pollComposer.state.getLatestValue();
const customDataState = messageComposer.customDataManager.state.getLatestValue();
snapshots.set(messageComposer, () => {
messageComposer.state.next(composerState);
messageComposer.textComposer.state.next(textState);
messageComposer.attachmentManager.state.next(attachmentState);
messageComposer.linkPreviewsManager.state.next(linkPreviewState);
messageComposer.locationComposer.state.next(locationState);
messageComposer.pollComposer.state.next(pollState);
messageComposer.customDataManager.state.next(customDataState);
});
};
/**
* Restores the composer to the state captured before editing began.
* Falls back to `clear()` if no snapshot exists.
*/
var restorePreEditSnapshot = (messageComposer) => {
const restore = snapshots.get(messageComposer);
snapshots.delete(messageComposer);
if (restore) restore();
else messageComposer.clear();
};
/**
* Discards the snapshot without restoring (e.g. after a successful edit save).
*/
var discardPreEditSnapshot = (messageComposer) => {
snapshots.delete(messageComposer);
};
//#endregion
//#region src/context/MessageBounceContext.tsx
var MessageBounceContext = createContext(void 0);
function useMessageBounceContext(componentName) {
const contextValue = useContext(MessageBounceContext);
if (!contextValue) {
console.warn(`The useMessageBounceContext hook was called outside of the MessageBounceContext provider. The errored call is located in the ${componentName} component.`);
return {};
}
return contextValue;
}
function MessageBounceProvider({ children }) {
const messageComposer = useMessageComposerController();
const { handleRetry: doHandleRetry, message } = useMessageContext("MessageBounceProvider");
if (!isMessageBounced(message)) console.warn(`The MessageBounceProvider was rendered for a message that is not bounced. Have you missed the "isMessageBounced" check?`);
const { removeMessage } = useChannelActionContext("MessageBounceProvider");
const handleDelete = useCallback(() => {
removeMessage(message);
}, [message, removeMessage]);
const handleEdit = useCallback((e) => {
e.preventDefault();
savePreEditSnapshot(messageComposer);
messageComposer.initState({ composition: message });
}, [message, messageComposer]);
const handleRetry = useCallback(() => {
doHandleRetry(message);
}, [doHandleRetry, message]);
const value = useMemo(() => ({
handleDelete,
handleEdit,
handleRetry,
message
}), [
handleDelete,
handleEdit,
handleRetry,
message
]);
return /* @__PURE__ */ jsx(MessageBounceContext.Provider, {
value,
children
});
}
//#endregion
//#region src/context/MessageListContext.tsx
var MessageListContext = createContext(void 0);
/**
* Context provider for components rendered within the `MessageList`
*/
var MessageListContextProvider = ({ children, value }) => /* @__PURE__ */ jsx(MessageListContext.Provider, {
value,
children
});
var useMessageListContext = (componentName) => {
const contextValue = useContext(MessageListContext);
if (!contextValue) {
console.warn(`The useMessageListContext hook was called outside of the MessageListContext provider. Make sure this hook is called within the MessageList component. The errored call is located in the ${componentName} component.`);
return {};
}
return contextValue;
};
//#endregion
//#region src/context/MessageTranslationViewContext.tsx
/**
* Message translation view context: user-specific state for whether each message
* shows original text or a translation.
*
* ## Spec
*
* - **State**: Per message list (channel vs thread), we store a map
* `messageId → 'original' | 'translated'`. Default for messages with `message.i18n`
* is `'translated'`; otherwise `'original'`.
*
* - **Provider placement**: The provider is tied to the **message list**, not the channel.
* It is rendered inside `MessageList` and `VirtualizedMessageList`. That way the
* main channel list and the thread list each have their own translation view state
* (e.g. Thread.tsx gets correct behavior without sharing channel state).
*
* - **Multiple translations**: `message.i18n` can contain multiple languages, e.g.:
* `{ en_text: "Good morning", fr_text: "Bonjour", it_text: "Buongiorno", language: "en" }`.
* Which translation is shown is determined by the app’s **user language**
* (`useTranslationContext().userLanguage`). We use `message.i18n[userLanguage + '_text']`;
* if missing, we fall back to `message.text`. Only one translation is shown at a time.
*
* - **Source language**: When present, `message.i18n.language` is the original/source
* language of `message.text`. It can be used for the indicator label, e.g.
* "Translated from English · View original".
*
* - **Invariants**: The original message content, layout, grouping, and order stay
* unchanged. Removing or toggling translation only changes the annotation and which
* text is displayed. "View original" / "View translation" switch the displayed
* text and update the annotation (e.g. "Original · View translation" when showing
* original).
*
* - **Translation indicator visibility**: The translation indicator (e.g. "Translated ·
* View original") is **not** shown when the currently viewed text already corresponds
* to `userLanguage` — for example when viewing original and the original text is the
* user-language version (i.e. `getTranslatedMessageText({ language: userLanguage, message })`
* equals `message.text`). In that case there is no meaningful original/translated choice,
* so the indicator is hidden.
*/
/**
* Returns the translated message text for a given language from `message.i18n`, or
* undefined if not present. Used to resolve which of the multiple translations to show.
*/
var getTranslatedMessageText = ({ language, message }) => message?.i18n?.[`${language}_text`];
var defaultContextValue = {
getTranslationView: (_messageId, hasI18n) => hasI18n ? "translated" : "original",
setTranslationView: () => {}
};
var MessageTranslationViewContext = createContext(defaultContextValue);
var MessageTranslationViewProvider = ({ children }) => {
const [viewByMessageId, setViewByMessageId] = useState({});
const setTranslationView = useCallback((messageId, view) => {
setViewByMessageId((prev) => ({
...prev,
[messageId]: view
}));
}, []);
const getTranslationView = useCallback((messageId, hasI18n) => viewByMessageId[messageId] ?? (hasI18n ? "translated" : "original"), [viewByMessageId]);
const stableValue = React.useMemo(() => ({
getTranslationView,
setTranslationView
}), [getTranslationView, setTranslationView]);
return /* @__PURE__ */ jsx(MessageTranslationViewContext.Provider, {
value: stableValue,
children
});
};
var useMessageTranslationViewContext = () => {
return useContext(MessageTranslationViewContext) ?? defaultContextValue;
};
//#endregion
//#region src/context/ModalContext.tsx
var ModalContext = React.createContext(void 0);
var ModalContextProvider = ({ children, value }) => /* @__PURE__ */ jsx(ModalContext.Provider, {
value,
children
});
var useModalContext = () => {
const contextValue = useContext(ModalContext);
if (!contextValue) {
console.warn(`The useModalContext hook was called outside of the ModalContext provider. Make sure this hook is called within a child of the GlobalModal.`);
return { close: () => null };
}
return contextValue;
};
//#endregion
//#region src/context/PollContext.tsx
var PollContext = React.createContext(void 0);
var PollProvider = ({ children, poll }) => poll ? /* @__PURE__ */ jsx(PollContext.Provider, {
value: { poll },
children
}) : null;
var usePollContext = () => {
return useContext(PollContext);
};
//#endregion
//#region src/context/WithComponents.tsx
function WithComponents({ children, overrides }) {
const actualOverrides = {
...useContext(ComponentContext),
...overrides
};
return /* @__PURE__ */ jsx(ComponentContext.Provider, {
value: actualOverrides,
children
});
}
//#endregion
//#region src/components/AIStateIndicator/AIStateIndicator.tsx
var AIStateIndicator = ({ channel: channelFromProps }) => {
const { t } = useTranslationContext();
const { channel: channelFromContext } = useChannelStateContext("AIStateIndicator");
const { aiState } = useAIState(channelFromProps || channelFromContext);
const allowedStates = {
[AIStates.Thinking]: t("Thinking..."),
[AIStates.Generating]: t("Generating...")
};
return aiState in allowedStates ? /* @__PURE__ */ jsx("div", {
className: "str-chat__ai-state-indicator-container",
children: /* @__PURE__ */ jsx("p", {
className: "str-chat__ai-state-indicator-text",
children: allowedStates[aiState]
})
}) : null;
};
//#endregion
//#region src/components/VisuallyHidden/VisuallyHidden.tsx
var visuallyHiddenStyle = {
border: 0,
clip: "rect(0, 0, 0, 0)",
height: "1px",
margin: "-1px",
overflow: "hidden",
padding: 0,
position: "absolute",
whiteSpace: "nowrap",
width: "1px"
};
/**
* Hides content visually while preserving it for assistive technologies.
*/
var VisuallyHidden = ({ children, style, ...rest }) => /* @__PURE__ */ jsx("span", {
...rest,
style: {
...visuallyHiddenStyle,
...style
},
children
});
//#endregion
//#region src/components/Accessibility/useAriaLiveAnnouncer.ts
var noopAnnounce = () => void 0;
var AriaLiveAnnouncerContext = createContext(void 0);
var useAriaLiveAnnouncer = () => {
const contextValue = useContext(AriaLiveAnnouncerContext);
if (!contextValue) {
console.warn("The useAriaLiveAnnouncer hook was called outside of the AriaLiveRegion provider.");
return noopAnnounce;
}
return contextValue.announce;
};
//#endregion
//#region src/components/Accessibility/AriaLiveRegion.tsx
var AriaLiveRegion = ({ children }) => {
const [assertiveMessage, setAssertiveMessage] = useState("");
const [politeMessage, setPoliteMessage] = useState("");
const sequenceByPriorityRef = useRef({
assertive: 0,
polite: 0
});
const timeoutByPriorityRef = useRef({
assertive: void 0,
polite: void 0
});
const unmountedRef = useRef(false);
const clearPendingTimeout = useCallback((priority) => {
if (!timeoutByPriorityRef.current[priority]) return;
clearTimeout(timeoutByPriorityRef.current[priority]);
timeoutByPriorityRef.current[priority] = void 0;
}, []);
const clearPendingTimeouts = useCallback(() => {
clearPendingTimeout("assertive");
clearPendingTimeout("polite");
}, [clearPendingTimeout]);
const announce = useCallback((message, priority = "polite") => {
const setAnnouncement = priority === "assertive" ? setAssertiveMessage : setPoliteMessage;
const sequence = sequenceByPriorityRef.current[priority] + 1;
sequenceByPriorityRef.current[priority] = sequence;
clearPendingTimeout(priority);
setAnnouncement("");
timeoutByPriorityRef.current[priority] = setTimeout(() => {
if (unmountedRef.current) return;
if (sequenceByPriorityRef.current[priority] !== sequence) return;
setAnnouncement(message);
timeoutByPriorityRef.current[priority] = void 0;
}, 50);
}, [clearPendingTimeout]);
useEffect(() => {
unmountedRef.current = false;
return () => {
unmountedRef.current = true;
clearPendingTimeouts();
};
}, [clearPendingTimeouts]);
const contextValue = useMemo(() => ({ announce }), [announce]);
const getPortalDestination = useCallback(() => document.body, []);
return /* @__PURE__ */ jsxs(AriaLiveAnnouncerContext.Provider, {
value: contextValue,
children: [children, /* @__PURE__ */ jsx(Portal, {
getPortalDestination,
isOpen: true,
children: /* @__PURE__ */ jsxs(VisuallyHidden, { children: [/* @__PURE__ */ jsx("div", {
"aria-atomic": "true",
"aria-live": "polite",
"aria-relevant": "additions text",
"data-testid": "str-chat__aria-live-region--polite",
role: "status",
children: politeMessage
}), /* @__PURE__ */ jsx("div", {
"aria-atomic": "true",
"aria-live": "assertive",
"aria-relevant": "additions text",
"data-testid": "str-chat__aria-live-region--assertive",
role: "alert",
children: assertiveMessage
})] })
})]
});
};
//#endregion
//#region src/components/Notifications/NotificationConfigurationContext.tsx
var defaultNotificationDisplayFilter = () => true;
var defaultNotificationConfigurationContextValue = { displayFilter: defaultNotificationDisplayFilter };
var NotificationConfigurationContext = React.createContext(defaultNotificationConfigurationContextValue);
var NotificationConfigurationProvider = ({ children, displayFilter }) => {
const parentConfiguration = useContext(NotificationConfigurationContext);
const value = useMemo(() => ({ displayFilter: displayFilter ?? parentConfiguration.displayFilter }), [displayFilter, parentConfiguration.displayFilter]);
return /* @__PURE__ */ jsx(NotificationConfigurationContext.Provider, {
value,
children
});
};
var useNotificationConfigurationContext = () => useContext(NotificationConfigurationContext);
//#endregion
//#region src/components/Notifications/hooks/useNotifications.ts
/**
* Subscribes to client.notifications.store and returns the list of notifications.
* Optionally pass a filter so only notifications that match are returned (e.g. for a specific NotificationList).
*/
var useNotifications = (options) => {
const { client } = useChatContext();
const { displayFilter } = useNotificationConfigurationContext();
const { applyDisplayFilter, fallbackPanel, filter, panel } = options ?? {};
const selector = useCallback((state) => {
return { notifications: state.notifications.filter((notification) => {
if (panel && !isNotificationForPanel(notification, panel, { fallbackPanel })) return false;
if (applyDisplayFilter && !displayFilter({
fallbackPanel,
filter,
notification,
panel
})) return false;
return filter ? filter(notification) : true;
}) };
}, [
applyDisplayFilter,
displayFilter,
fallbackPanel,
filter,
panel
]);
const { notifications } = useStateStore(client.notifications.store, selector);
return notifications;
};
//#endregion
//#region src/a11y/hooks/useAriaIdentifiers.ts
var sanitizeAriaRootId = (rootId) => rootId?.trim().replace(/[^A-Za-z0-9:_-]/g, "-") ?? "";
var buildAriaIdentifier = (sanitizedRootId, descriptor) => sanitizedRootId ? `${sanitizedRootId}-${descriptor}` : void 0;
/**
* Derives stable ARIA identifier IDs from a single root ID.
*
* Use this to keep dialog/component labeling conventions consistent without
* manually building `*-title` and `*-description` IDs at each call site.
*
* Behavior:
* - Root ID is trimmed and sanitized to `[A-Za-z0-9:_-]` before use.
* - Returns `undefined` IDs when root ID is missing/empty after sanitization.
*/
var useAriaIdentifiers = (rootId) => {
const sanitizedRootId = sanitizeAriaRootId(rootId);
return useMemo(() => ({
descriptionId: buildAriaIdentifier(sanitizedRootId, "description"),
titleId: buildAriaIdentifier(sanitizedRootId, "title")
}), [sanitizedRootId]);
};
//#endregion
//#region src/components/Dialog/components/Alert.tsx
var Root = forwardRef(function AlertRoot({ children, className, ...props }, ref) {
return /* @__PURE__ */ jsx("div", {
...props,
className: clsx("str-chat__alert-root", className),
ref,
children
});
});
var Header = forwardRef(function AlertHeader({ children, className, description, descriptionId, Icon, title, titleId, ...props }, ref) {
const { dialogId } = useModalContext();
const { descriptionId: derivedDescriptionId, titleId: derivedTitleId } = useAriaIdentifiers(dialogId);
const resolvedTitleId = titleId ?? derivedTitleId;
const resolvedDescriptionId = descriptionId ?? derivedDescriptionId;
return /* @__PURE__ */ jsx("div", {
...props,
className: clsx("str-chat__alert-header", className),
ref,
children: title ? /* @__PURE__ */ jsxs(Fragment$1, { children: [Icon && /* @__PURE__ */ jsx(Icon, {}), /* @__PURE__ */ jsxs("div", {
className: "str-chat__alert-header__copy",
children: [/* @__PURE__ */ jsx("h2", {
className: "str-chat__alert-header__title",
id: resolvedTitleId,
children: title
}), description && /* @__PURE__ */ jsx("p", {
className: "str-chat__alert-header__description",
id: resolvedDescriptionId,
children: description
})]
})] }) : children
});
});
var Alert = {
Actions: forwardRef(function AlertActions({ children, className, ...props }, ref) {
return /* @__PURE__ */ jsx("div", {
...props,
className: clsx("str-chat__alert-actions", className),
ref,
children
});
}),
Header,
Root
};
//#endregion
//#region src/components/Dialog/service/DialogAnchor.tsx
function useDialogAnchor({ allowFlip, offset, open, placement, referenceElement, updateKey, updatePositionOnContentResize = false }) {
const [popperElement, setPopperElement] = useState(null);
const [stabilisedChosenPlacement, setStabilisedChosenPlacement] = useState(null);
const { placement: chosenPlacement, refs, strategy, update, x, y } = usePopoverPosition({
allowFlip,
freeze: true,
offset,
placement: stabilisedChosenPlacement ?? placement
});
if (!stabilisedChosenPlacement && popperElement && placement !== chosenPlacement) setStabilisedChosenPlacement(chosenPlacement);
else if (stabilisedChosenPlacement && !popperElement) setStabilisedChosenPlacement(null);
const frozenReferenceRef = useRef(null);
if (open && referenceElement && !frozenReferenceRef.current) frozenReferenceRef.current = referenceElement;
if (!open) frozenReferenceRef.current = null;
const effectiveReference = open ? frozenReferenceRef.current : referenceElement;
useEffect(() => {
refs.setReference(effectiveReference);
}, [effectiveReference, refs]);
useEffect(() => {
refs.setFloating(popperElement);
}, [popperElement, refs]);
useEffect(() => {
if (open && popperElement && effectiveReference) update?.();
}, [
open,
placement,
popperElement,
update,
updateKey,
effectiveReference
]);
useEffect(() => {
if (!popperElement || !updatePositionOnContentResize) return;
const resizeObserver = new ResizeObserver(update);
resizeObserver.observe(popperElement);
return () => {
resizeObserver.disconnect();
};
}, [
popperElement,
update,
updatePositionOnContentResize
]);
if (popperElement && !open) setPopperElement(null);
return {
placement: stabilisedChosenPlacement ?? chosenPlacement,
setPopperElement,
styles: {
left: x ?? 0,
position: strategy,
top: y ?? 0
}
};
}
var DialogAnchor = ({ allowFlip = true, children, className, closeOnClickOutside, closeTransitionMs = 0, dialogManagerId, focus = true, id, offset, placement = "auto", referenceElement = null, tabIndex, trapFocus, updateKey, updatePositionOnContentResize, ...restDivProps }) => {
/**
* Rendering lifecycle notes:
* - `open=true` renders dialog contents immediately.
* - `open=false` can keep contents mounted for `closeTransitionMs` to allow CSS exit
* animations before unmount.
*
* State exposed to CSS:
* - `data-str-chat-dialog-state="open"` while actively open.
* - `data-str-chat-dialog-state="closing"` during delayed unmount window.
*
* Consumers like `ContextMenu` combine:
* - `data-str-chat-dialog-state` from this component, and
* - `data-str-chat-placement` (e.g. `top-start`, `right-end`)
* to select the correct closing keyframe in CSS (horizontal vs vertical roll direction).
* In practice, JS only toggles state and timing (`closeTransitionMs`); CSS owns the
* visual motion details (transform/easing), so animation behavior stays declarative.
*/
const dialog = useDialog({
closeOnClickOutside,
dialogManagerId,
id
});
const open = useDialogIsOpen(id, dialogManagerId);
const [shouldRender, setShouldRender] = useState(open);
const closeTimeoutRef = useRef(null);
const isClosing = !open && shouldRender;
useEffect(() => {
if (open) {
setShouldRender(true);
if (closeTimeoutRef.current) {
clearTimeout(closeTimeoutRef.current);
closeTimeoutRef.current = null;
}
return;
}
if (!shouldRender) return;
if (!closeTransitionMs) {
setShouldRender(false);
return;
}
closeTimeoutRef.current = setTimeout(() => {
setShouldRender(false);
closeTimeoutRef.current = null;
}, closeTransitionMs);
}, [
closeTransitionMs,
open,
shouldRender
]);
useEffect(() => () => {
if (closeTimeoutRef.current) clearTimeout(closeTimeoutRef.current);
}, []);
const { placement: chosenPlacement, setPopperElement, styles } = useDialogAnchor({
allowFlip,
offset,
open: shouldRender,
placement,
referenceElement,
updateKey,
updatePositionOnContentResize
});
useEffect(() => {
if (!open) return;
const hideOnEscape = (event) => {
if (event.key !== "Escape" || event.defaultPrevented) return;
dialog?.close();
};
document.addEventListener("keyup", hideOnEscape);
return () => {
document.removeEventListener("keyup", hideOnEscape);
};
}, [dialog, open]);
if (!shouldRender) return null;
const { ["aria-describedby"]: ariaDescribedBy, ["aria-label"]: ariaLabel, ["aria-labelledby"]: ariaLabelledBy, ["aria-modal"]: ariaModal, role, ...anchorDivProps } = restDivProps;
const resolvedRole = trapFocus ? role ?? "dialog" : role;
const resolvedAriaModal = trapFocus ? true : ariaModal;
const resolvedAriaLabel = ariaLabelledBy ? void 0 : ariaLabel;
return /* @__PURE__ */ jsx(DialogPortalEntry, {
dialogId: id,
dialogManagerId,
children: /* @__PURE__ */ jsx(FocusScope, {
autoFocus: focus,
contain: trapFocus,
restoreFocus: true,
children: /* @__PURE__ */ jsx("div", {
...anchorDivProps,
"aria-describedby": ariaDescribedBy,
"aria-label": resolvedAriaLabel,
"aria-labelledby": ariaLabelledBy,
"aria-modal": resolvedAriaModal,
className: clsx("str-chat__dialog-contents", className),
"data-str-chat-dialog-state": isClosing ? "closing" : "open",
"data-str-chat-placement": chosenPlacement,
"data-testid": "str-chat__dialog-contents",
ref: setPopperElement,
role: resolvedRole,
style: styles,
tabIndex: typeof tabIndex !== "undefined" ? tabIndex : 0,
children
})
})
});
};
//#endregion
//#region src/components/Button/PlayButton.tsx
var PlayButton = ({ className, isPlaying, ...props }) => {
const { t } = useTranslationContext();
return /* @__PURE__ */ jsx(Button, {
appearance: "outline",
"aria-label": isPlaying ? t("aria/Pause") : t("aria/Play"),
circular: true,
className: clsx("str-chat__button-play", className),
"data-testid": isPlaying ? "pause-audio" : "play-audio",
size: "sm",
variant: "secondary",
...props,
children: isPlaying ? /* @__PURE__ */ jsx(IconPauseFill, {}) : /* @__PURE__ */ jsx(IconPlayFill, {})
});
};
//#endregion
//#region src/components/Dialog/components/Callout.tsx
/**
* Callout is a general purpose component that displays content in dialog that has been previously opened by clicking on a reference element
* and has to be dismissed to disappear.
* Tooltip on the other side is a dialog that appears upon pointer device cursor hovering above a reference element.
* @param children
* @param className
* @param dialogManagerId
* @param dialogId
* @param onClose
* @param anchorProps
* @constructor
*/
var Callout = ({ children, className, dialogManagerId, id: dialogId, onClose, ...anchorProps }) => {
const { CalloutDialog = DefaultCalloutDialog } = useComponentContext();
const dialogIsOpen = useDialogIsOpen(dialogId, dialogManagerId);
return /* @__PURE__ */ jsx(DialogAnchor, {
...anchorProps,
dialogManagerId,
id: dialogId,
children: dialogIsOpen && /* @__PURE__ */ jsx(CalloutDialog, {
className,
onClose,
children
})
});
};
var DefaultCalloutDialog = ({ children, className, onClose }) => {
const { t } = useTranslationContext();
return /* @__PURE__ */ jsxs("div", {
className: "str-chat__callout",
children: [children, /* @__PURE__ */ jsx(Button, {
appearance: "ghost",
"aria-label": t("aria/Close callout dialog"),
circular: true,
className: clsx(className, "str-chat__callout__close-button"),
onClick: onClose,
size: "sm",
variant: "secondary",
children: /* @__PURE__ */ jsx(IconXmark, {})
})]
});
};
//#endregion
//#region src/components/Avatar/Avatar.tsx
var getInitials = (name) => {
const regex = /(\p{L}{1})\p{L}+/gu;
if (!name || name.trim().length === 0) return "";
const initials = Array.from(name?.matchAll(regex) || []);
if (!initials.length) return "";
return `${initials.at(0)[1]}${initials.length > 1 ? initials.at(-1)[1] : ""}`;
};
/**
* A round avatar image with fallback to username's first letter
*/
var Avatar = ({ className, imageUrl, isOnline, size, userName, ...rest }) => {
const [error, setError] = useState(false);
useEffect(() => () => setError(false), [imageUrl]);
const nameString = userName?.toString() || "";
const avatarImageAlt = nameString.trim();
const sizeAwareInitials = useMemo(() => {
const initials = getInitials(nameString);
if (size === "sm" || size === "xs") return initials.charAt(0);
return initials;
}, [nameString, size]);
const showImage = typeof imageUrl === "string" && imageUrl && !error;
return /* @__PURE__ */ jsxs("div", {
className: clsx(`str-chat__avatar`, className, {
"str-chat__avatar--multiple-letters": sizeAwareInitials.length > 1,
"str-chat__avatar--no-letters": !sizeAwareInitials.length,
"str-chat__avatar--one-letter": sizeAwareInitials.length === 1,
[`str-chat__avatar--size-${size}`]: typeof size === "string"
}),
"data-testid": "avatar",
role: "button",
title: userName,
...rest,
children: [typeof isOnline === "boolean" && /* @__PURE__ */ jsx("div", { className: clsx("str-chat__avatar-status-badge", {
"str-chat__avatar-status-badge--offline": !isOnline,
"str-chat__avatar-status-badge--online": isOnline
}) }), showImage ? /* @__PURE__ */ jsx("img", {
alt: avatarImageAlt,
className: "str-chat__avatar-image",
"data-testid": "avatar-img",
onError: () => setError(true),
src: imageUrl
}) : /* @__PURE__ */ jsxs(Fragment$1, { children: [!!sizeAwareInitials.length && /* @__PURE__ */ jsx("div", {
className: "str-chat__avatar-initials",
"data-testid": "avatar-fallback",
children: sizeAwareInitials
}), !sizeAwareInitials.length && /* @__PURE__ */ jsx(IconUser, {})] })]
});
};
//#endregion
//#region src/components/Badge/Badge.tsx
/**
* Compact pill/circle badge for counts and labels.
* Uses design tokens: --badge-bg-*, --badge-text-*, --badge-border.
*/
var Badge = ({ children, className, size = "md", variant = "default", ...spanProps }) => /* @__PURE__ */ jsx("div", {
...spanProps,
className: clsx("str-chat__badge", `str-chat__badge--variant-${variant}`, { [`str-chat__badge--size-${size}`]: size }, className),
children
});
var ErrorBadge = ({ className, size = "sm", ...rest }) => /* @__PURE__ */ jsx(Badge, {
...rest,
className,
size,
variant: "error",
children: /* @__PURE__ */ jsx(IconExclamationMarkFill, {})
});
//#endregion
//#region src/components/Avatar/AvatarStack.tsx
function AvatarStack({ badgeSize, capLimit = 3, component: Component = "div", displayInfo = [], size }) {
const { Avatar: Avatar$3 = Avatar } = useComponentContext(AvatarStack.name);
const displayInfoToRender = useMemo(() => displayInfo.length > capLimit ? displayInfo.slice(0, capLimit) : displayInfo, [displayInfo, capLimit]);
const overflowCount = displayInfo.length - displayInfoToRender.length;
if (!displayInfo.length) return null;
return /* @__PURE__ */ jsxs(Component, {
className: clsx("str-chat__avatar-stack", { [`str-chat__avatar-stack--size-${size}`]: typeof size === "string" }),
"data-testid": "avatar-stack",
children: [displayInfoToRender.map((info, index) => /* @__PURE__ */ jsx(Avatar$3, {
imageUrl: info.imageUrl,
size,
userName: info.userName
}, info.id ?? `${info.userName}-${info.imageUrl}-${index}`)), typeof overflowCount === "number" && overflowCount > 0 && /* @__PURE__ */ jsxs(Badge, {
className: "str-chat__avatar-stack__count-badge",
"data-testid": "avatar-stack-count-badge",
size: badgeSize ?? size,
variant: "counter",
children: ["+", overflowCount]
})]
});
}
//#endregion
//#region src/components/Avatar/ChannelAvatar.tsx
var ChannelAvatar = ({ displayMembers, imageUrl, size, userName, ...sharedProps }) => {
return /* @__PURE__ */ jsx(GroupAvatar, {
displayMembers: useMemo(() => {
if (displayMembers && displayMembers.length > 0) return displayMembers;
return [{
imageUrl,
userName
}];
}, [
displayMembers,
imageUrl,
userName
]),
size,
...sharedProps
});
};
//#endregion
//#region src/components/Avatar/GroupAvatar.tsx
/**
* Avatar component to display multiple users' avatars in a group.
* Renders a single Avatar if fewer than 2 members. Otherwise, renders up to 2 avatars (when overflowCount is set) or 4, plus an optional +N badge.
*/
var GroupAvatar = ({ badgeSize, className, displayMembers = [], isOnline, size, ...rest }) => {
const displayMembersToRender = useMemo(() => displayMembers.length > 4 ? displayMembers.slice(0, 2) : displayMembers, [displayMembers]);
const overflowCount = displayMembers.length - displayMembersToRender.length;
if (displayMembers.length < 2) {
const firstUser = displayMembers[0];
return /* @__PURE__ */ jsx(Avatar, {
imageUrl: firstUser?.imageUrl,
isOnline,
size,
userName: firstUser?.userName,
...rest
});
}
let avatarSize = null;
if (size === "2xl") avatarSize = "lg";
else if (size === "xl") avatarSize = "md";
else if (size === "lg") avatarSize = "sm";
return /* @__PURE__ */ jsxs("div", {
className: clsx("str-chat__avatar-group", {
"str-chat__avatar-group--offline": typeof isOnline === "boolean" && !isOnline,
"str-chat__avatar-group--online": typeof isOnline === "boolean" && isOnline,
[`str-chat__avatar-group--size-${size}`]: typeof size === "string"
}, className),
"data-testid": "group-avatar",
role: "button",
...rest,
children: [displayMembersToRender.map(({ id, imageUrl, userName }, index) => /* @__PURE__ */ jsx(Avatar, {
imageUrl,
size: avatarSize,
userName
}, id || `${userName}-${imageUrl}-${index}`)), typeof overflowCount === "number" && overflowCount > 0 && /* @__PURE__ */ jsxs(Badge, {
className: "str-chat__avatar-group__count-badge",
"data-testid": "group-avatar-count-badge",
size: badgeSize,
variant: "counter",
children: ["+", overflowCount]
})]
});
};
//#endregion
//#region src/a11y/a11yUtils.ts
var MENU_KEYBOARD_NAVIGATION_KEYS = [
"ArrowDown",
"ArrowUp",
"End",
"Home"
];
var isMenuKeyboardNavigationKey = (key) => MENU_KEYBOARD_NAVIGATION_KEYS.includes(key);
var getNextRovingFocusIndex = ({ activeIndex, itemCount, key }) => {
if (itemCount <= 0 || !isMenuKeyboardNavigationKey(key)) return null;
const lastIndex = itemCount - 1;
if (key === "Home") return 0;
if (key === "End") return lastIndex;
if (activeIndex === -1) return key === "ArrowUp" ? lastIndex : 0;
if (key === "ArrowUp") return activeIndex <= 0 ? lastIndex : activeIndex - 1;
return activeIndex >= lastIndex ? 0 : activeIndex + 1;
};
var getDefaultActiveIndex = (items) => {
const activeElement = document.activeElement;
if (!(activeElement instanceof Element)) return -1;
return items.findIndex((item) => item instanceof Element && item === activeElement);
};
var createRovingFocusKeyDownHandler = ({ focusItem = (item) => item.focus(), getActiveIndex = (items) => getDefaultActiveIndex(items), getItems }) => {
const handleKeyDown = (event) => {
const items = getItems(event);
const nextIndex = getNextRovingFocusIndex({
activeIndex: getActiveIndex(items, event),
itemCount: items.length,
key: event.key
});
if (nextIndex === null) return;
event.preventDefault();
const nextItem = items[nextIndex];
if (!nextItem) return;
focusItem(nextItem, event);
};
return handleKeyDown;
};
//#endregion
//#region src/components/Dialog/components/ContextMenu.tsx
var BaseContextMenuButton = ({ children, className, details, hasSubMenu, Icon, label, role = "menuitem", SubmenuIcon = IconChevronRight, variant, ...props }) => /* @__PURE__ */ jsxs("button", {
...props,
className: clsx("str-chat__context-menu__button", {
"str-chat__context-menu__button--with-submenu": hasSubMenu,
[`str-chat__context-menu__button--${variant}`]: typeof variant === "string"
}, className),
role,
type: "button",
children: [
Icon && /* @__PURE__ */ jsx(Icon, { className: "str-chat__context-menu__button__icon" }),
label ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
className: "str-chat__context-menu__button__label",
children: label
}), /* @__PURE__ */ jsx("div", {
className: "str-chat__context-menu__button__details",
children: details
})] }) : /* @__PURE__ */ jsx("div", {
className: "str-chat__context-menu__button__label",
children
}),
!!hasSubMenu && /* @__PURE__ */ jsx(SubmenuIcon, { className: "str-chat__context-menu__button__submenu-icon" })
]
});
var UserContextMenuButton = ({ children, className, imageUrl, role = "menuitem", userName, ...props }) => /* @__PURE__ */ jsxs("button", {
...props,
className: clsx("str-chat__context-menu__button str-chat__user-context-menu__button", className),
role,
type: "button",
children: [/* @__PURE__ */ jsx(Avatar, {
imageUrl,
size: "sm",
userName
}), /* @__PURE__ */ jsx("div", {
className: "str-chat__context-menu__button__label",
children: children ?? userName
})]
});
var EmojiContextMenuButton = ({ children, className, emoji, label, role = "menuitem", ...props }) => /* @__PURE__ */ jsxs("button", {
...props,
className: clsx("str-chat__context-menu__button str-chat__emoji-context-menu__button", className),
role,
type: "button",
children: [/* @__PURE__ */ jsx("span", {
className: "str-chat__context-menu__button__emoji str-chat__emoji-item--entity",
children: emoji
}), /* @__PURE__ */ jsx("div", {
className: "str-chat__context-menu__button__label",
children: children ?? label
})]
});
var ContextMenuButtonWithSubmenu = ({ children, className, Submenu, submenuContainerProps, submenuPlacement = "right-start", submenuRollAxis = "x", ...buttonProps }) => {
const { className: submenuClassName, ...submenuContainerRestProps } = submenuContainerProps ?? {};
const { registerDialogSubmenu, unregisterDialogSubmenu } = useContextMenuContext();
const buttonRef = useRef(null);
const [dialogContainer, setDialogContainer] = useState(null);
const keepSubmenuOpenFlag = useRef(false);
const dialogCloseTimeout = useRef(null);
const dialogId = useMemo(() => `submenu-${Math.random().toString(36).slice(2)}`, []);
const { dialog, dialogManager } = useDialogOnNearestManager({ id: dialogId });
const dialogIsOpen = useDialogIsOpen(dialogId, dialogManager?.id);
useEffect(() => {
if (!dialogIsOpen) return;
registerDialogSubmenu();
return () => unregisterDialogSubmenu();
}, [
dialogIsOpen,
registerDialogSubmenu,
unregisterDialogSubmenu
]);
const { placement: chosenPlacement, setPopperElement, styles } = useDialogAnchor({
offset: 8,
open: dialogIsOpen,
placement: submenuPlacement,
referenceElement: buttonRef.current
});
const closeDialogLazily = useCallback(() => {
if (dialogCloseTimeout.current) clearTimeout(dialogCloseTimeout.current);
dialogCloseTimeout.current = setTimeout(() => {
if (keepSubmenuOpenFlag.current) return;
dialog.close();
}, 100);
}, [dialog]);
const keepSubmenuOpen = useCallback(() => {
keepSubmenuOpenFlag.current = true;
}, []);
const allowToCloseSubmenu = useCallback(() => {
keepSubmenuOpenFlag.current = false;
}, []);
const closeSubmenu = useCallback(() => {
allowToCloseSubmenu();
closeDialogLazily();
}, [allowToCloseSubmenu, closeDialogLazily]);
const handleClose = useCallback((event) => {
const parentButton = buttonRef.current;
if (!dialogIsOpen || !parentButton) return;
event.stopPropagation();
closeDialogLazily();
parentButton.focus();
}, [
closeDialogLazily,
dialogIsOpen,
buttonRef
]);
const handleFocusParentButton = () => {
if (dialogIsOpen) return;
dialog.open();
keepSubmenuOpen();
};
useEffect(() => {
const parentButton = buttonRef.current;
if (!dialogIsOpen || !parentButton) return;
const hideOnEscape = (event) => {
if (event.key !== "Escape") return;
handleClose(event);
closeSubmenu();
};
document.addEventListener("keyup", hideOnEscape, { capture: true });
return () => {
document.removeEventListener("keyup", hideOnEscape, { capture: true });
};
}, [
dialogIsOpen,
handleClose,
closeSubmenu
]);
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(BaseContextMenuButton, {
"aria-expanded": dialogIsOpen,
"aria-haspopup": "menu",
className: clsx(className, "str_chat__button-with-submenu", { "str_chat__button-with-submenu--submenu-open": dialogIsOpen }),
hasSubMenu: true,
onBlur: closeSubmenu,
onClick: (event) => {
event.stopPropagation();
dialog.toggle();
},
onFocus: handleFocusParentButton,
onMouseEnter: handleFocusParentButton,
onMouseLeave: closeSubmenu,
...buttonProps,
ref: buttonRef,
children
}), dialogIsOpen && /* @__PURE__ */ jsx("div", {
className: clsx("str-chat__context-menu__submenu-container", submenuClassName),
"data-str-chat-placement": chosenPlacement,
"data-str-chat-roll-axis": submenuRollAxis,
onBlur: (event) => {
if (event.relatedTarget instanceof Node && dialogContainer?.contains(event.relatedTarget)) return;
closeSubmenu();
},
onFocus: keepSubmenuOpen,
onMouseEnter: keepSubmenuOpen,
onMouseLeave: closeSubmenu,
ref: (element) => {
setPopperElement(element);
setDialogContainer(element);
},
style: styles,
tabIndex: -1,
...submenuContainerRestProps,
children: /* @__PURE__ */ jsx(Submenu, {})
})] });
};
var ContextMenuButton = (props) => {
const { Submenu, submenuContainerProps, submenuPlacement, submenuRollAxis, ...buttonProps } = props;
if (Submenu) return /* @__PURE__ */ jsx(ContextMenuButtonWithSubmenu, {
...buttonProps,
Submenu,
submenuContainerProps,
submenuPlacement,
submenuRollAxis
});
return /* @__PURE__ */ jsx(BaseContextMenuButton, { ...buttonProps });
};
var ContextMenuBackButton = ({ children, className, role = "menuitem", ...props }) => /* @__PURE__ */ jsx("button", {
...props,
className: clsx("str-chat__context-menu__back-button", className),
role,
type: "button",
children
});
var ContextMenuHeader = ({ children, className, ...props }) => /* @__PURE__ */ jsx("div", {
...props,
className: clsx("str-chat__context-menu__header", className),
children
});
var ContextMenuBody = ({ children, className, ...props }) => /* @__PURE__ */ jsx("div", {
...props,
className: clsx("str-chat__context-menu__body", className),
children
});
var ContextMenuRoot = React.forwardRef(function ContextMenuRoot({ className, role = "menu", ...props }, ref) {
return /* @__PURE__ */ jsx("div", {
...props,
className: clsx("str-chat__context-menu", className),
ref,
role
});
});
var DEFAULT_CONTEXT_MENU_KEYBOARD_NAVIGATION_ITEM_SELECTOR = [
"[role=\"menuitem\"]:not(:disabled)",
"[role=\"menuitemradio\"]:not(:disabled)",
"[role=\"menuitemcheckbox\"]:not(:disabled)"
].join(",");
var isVisibleContextMenuKeyboardNavigationItem = (item) => {