UNPKG

stream-chat-react

Version:

React components to create chat conversations or livestream style chat

1,170 lines 1.18 MB
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) => {