UNPKG

stream-chat-react

Version:

React components to create chat conversations or livestream style chat

1,197 lines 1.27 MB
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); const require_useNotificationApi = require("./useNotificationApi.9ffe5761.js"); const require_audioProcessing = require("./audioProcessing.22303d69.js"); let react = require("react"); react = require_useNotificationApi.__toESM(react); let react_jsx_runtime = require("react/jsx-runtime"); let stream_chat = require("stream-chat"); let nanoid = require("nanoid"); let use_sync_external_store_shim = require("use-sync-external-store/shim"); let dayjs = require("dayjs"); dayjs = require_useNotificationApi.__toESM(dayjs); let dayjs_plugin_calendar = require("dayjs/plugin/calendar"); dayjs_plugin_calendar = require_useNotificationApi.__toESM(dayjs_plugin_calendar); let dayjs_plugin_localizedFormat = require("dayjs/plugin/localizedFormat"); dayjs_plugin_localizedFormat = require_useNotificationApi.__toESM(dayjs_plugin_localizedFormat); let clsx = require("clsx"); clsx = require_useNotificationApi.__toESM(clsx); let _react_aria_focus = require("@react-aria/focus"); let lodash_debounce = require("lodash.debounce"); lodash_debounce = require_useNotificationApi.__toESM(lodash_debounce); let lodash_throttle = require("lodash.throttle"); lodash_throttle = require_useNotificationApi.__toESM(lodash_throttle); let i18next = require("i18next"); i18next = require_useNotificationApi.__toESM(i18next); let dayjs_plugin_updateLocale = require("dayjs/plugin/updateLocale"); dayjs_plugin_updateLocale = require_useNotificationApi.__toESM(dayjs_plugin_updateLocale); let dayjs_plugin_localeData = require("dayjs/plugin/localeData"); dayjs_plugin_localeData = require_useNotificationApi.__toESM(dayjs_plugin_localeData); let dayjs_plugin_relativeTime = require("dayjs/plugin/relativeTime"); dayjs_plugin_relativeTime = require_useNotificationApi.__toESM(dayjs_plugin_relativeTime); let dayjs_plugin_duration = require("dayjs/plugin/duration"); dayjs_plugin_duration = require_useNotificationApi.__toESM(dayjs_plugin_duration); let dayjs_plugin_utc = require("dayjs/plugin/utc"); dayjs_plugin_utc = require_useNotificationApi.__toESM(dayjs_plugin_utc); let dayjs_plugin_timezone = require("dayjs/plugin/timezone"); dayjs_plugin_timezone = require_useNotificationApi.__toESM(dayjs_plugin_timezone); require("dayjs/locale/de"); require("dayjs/locale/es"); require("dayjs/locale/fr"); require("dayjs/locale/hi"); require("dayjs/locale/it"); require("dayjs/locale/ja"); require("dayjs/locale/ko"); require("dayjs/locale/nl"); require("dayjs/locale/pt"); require("dayjs/locale/ru"); require("dayjs/locale/tr"); require("dayjs/locale/en"); let hast_util_find_and_replace = require("hast-util-find-and-replace"); let unist_builder = require("unist-builder"); let unist_util_visit = require("unist-util-visit"); let react_markdown = require("react-markdown"); react_markdown = require_useNotificationApi.__toESM(react_markdown); let linkifyjs = require("linkifyjs"); linkifyjs = require_useNotificationApi.__toESM(linkifyjs); let remark_gfm = require("remark-gfm"); remark_gfm = require_useNotificationApi.__toESM(remark_gfm); let _braintree_sanitize_url = require("@braintree/sanitize-url"); let fix_webm_duration = require("fix-webm-duration"); fix_webm_duration = require_useNotificationApi.__toESM(fix_webm_duration); let lodash_mergewith = require("lodash.mergewith"); lodash_mergewith = require_useNotificationApi.__toESM(lodash_mergewith); let lodash_uniqby = require("lodash.uniqby"); lodash_uniqby = require_useNotificationApi.__toESM(lodash_uniqby); let react_textarea_autosize = require("react-textarea-autosize"); react_textarea_autosize = require_useNotificationApi.__toESM(react_textarea_autosize); let react_dropzone = require("react-dropzone"); let react_virtuoso = require("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] = (0, react.useState)(AIStates.Idle); (0, react.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 = (0, react.createContext)({ fileInput: null }); var AttachmentSelectorContextProvider = ({ children, value }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AttachmentSelectorContext.Provider, { value, children }); var useAttachmentSelectorContext = () => (0, react.useContext)(AttachmentSelectorContext); //#endregion //#region src/context/MessageContext.tsx var MessageContext = react.default.createContext(void 0); var MessageProvider = ({ children, value }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MessageContext.Provider, { value, children }); var useMessageContext = (_componentName) => { const contextValue = (0, react.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 = (0, react.createContext)(void 0); function useMessageBounceContext(componentName) { const contextValue = (0, react.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 = require_useNotificationApi.useMessageComposerController(); const { handleRetry: doHandleRetry, message } = useMessageContext("MessageBounceProvider"); if (!require_useNotificationApi.isMessageBounced(message)) console.warn(`The MessageBounceProvider was rendered for a message that is not bounced. Have you missed the "isMessageBounced" check?`); const { removeMessage } = require_useNotificationApi.useChannelActionContext("MessageBounceProvider"); const handleDelete = (0, react.useCallback)(() => { removeMessage(message); }, [message, removeMessage]); const handleEdit = (0, react.useCallback)((e) => { e.preventDefault(); savePreEditSnapshot(messageComposer); messageComposer.initState({ composition: message }); }, [message, messageComposer]); const handleRetry = (0, react.useCallback)(() => { doHandleRetry(message); }, [doHandleRetry, message]); const value = (0, react.useMemo)(() => ({ handleDelete, handleEdit, handleRetry, message }), [ handleDelete, handleEdit, handleRetry, message ]); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MessageBounceContext.Provider, { value, children }); } //#endregion //#region src/context/MessageListContext.tsx var MessageListContext = (0, react.createContext)(void 0); /** * Context provider for components rendered within the `MessageList` */ var MessageListContextProvider = ({ children, value }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MessageListContext.Provider, { value, children }); var useMessageListContext = (componentName) => { const contextValue = (0, react.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 = (0, react.createContext)(defaultContextValue); var MessageTranslationViewProvider = ({ children }) => { const [viewByMessageId, setViewByMessageId] = (0, react.useState)({}); const setTranslationView = (0, react.useCallback)((messageId, view) => { setViewByMessageId((prev) => ({ ...prev, [messageId]: view })); }, []); const getTranslationView = (0, react.useCallback)((messageId, hasI18n) => viewByMessageId[messageId] ?? (hasI18n ? "translated" : "original"), [viewByMessageId]); const stableValue = react.default.useMemo(() => ({ getTranslationView, setTranslationView }), [getTranslationView, setTranslationView]); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MessageTranslationViewContext.Provider, { value: stableValue, children }); }; var useMessageTranslationViewContext = () => { return (0, react.useContext)(MessageTranslationViewContext) ?? defaultContextValue; }; //#endregion //#region src/context/ModalContext.tsx var ModalContext = react.default.createContext(void 0); var ModalContextProvider = ({ children, value }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModalContext.Provider, { value, children }); var useModalContext = () => { const contextValue = (0, react.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.default.createContext(void 0); var PollProvider = ({ children, poll }) => poll ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PollContext.Provider, { value: { poll }, children }) : null; var usePollContext = () => { return (0, react.useContext)(PollContext); }; //#endregion //#region src/context/WithComponents.tsx function WithComponents({ children, overrides }) { const actualOverrides = { ...(0, react.useContext)(require_useNotificationApi.ComponentContext), ...overrides }; return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_useNotificationApi.ComponentContext.Provider, { value: actualOverrides, children }); } //#endregion //#region src/components/AIStateIndicator/AIStateIndicator.tsx var AIStateIndicator = ({ channel: channelFromProps }) => { const { t } = require_useNotificationApi.useTranslationContext(); const { channel: channelFromContext } = require_useNotificationApi.useChannelStateContext("AIStateIndicator"); const { aiState } = useAIState(channelFromProps || channelFromContext); const allowedStates = { [AIStates.Thinking]: t("Thinking..."), [AIStates.Generating]: t("Generating...") }; return aiState in allowedStates ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "str-chat__ai-state-indicator-container", children: /* @__PURE__ */ (0, react_jsx_runtime.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__ */ (0, react_jsx_runtime.jsx)("span", { ...rest, style: { ...visuallyHiddenStyle, ...style }, children }); //#endregion //#region src/components/Accessibility/useAriaLiveAnnouncer.ts var noopAnnounce = () => void 0; var AriaLiveAnnouncerContext = (0, react.createContext)(void 0); var useAriaLiveAnnouncer = () => { const contextValue = (0, react.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] = (0, react.useState)(""); const [politeMessage, setPoliteMessage] = (0, react.useState)(""); const sequenceByPriorityRef = (0, react.useRef)({ assertive: 0, polite: 0 }); const timeoutByPriorityRef = (0, react.useRef)({ assertive: void 0, polite: void 0 }); const unmountedRef = (0, react.useRef)(false); const clearPendingTimeout = (0, react.useCallback)((priority) => { if (!timeoutByPriorityRef.current[priority]) return; clearTimeout(timeoutByPriorityRef.current[priority]); timeoutByPriorityRef.current[priority] = void 0; }, []); const clearPendingTimeouts = (0, react.useCallback)(() => { clearPendingTimeout("assertive"); clearPendingTimeout("polite"); }, [clearPendingTimeout]); const announce = (0, react.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]); (0, react.useEffect)(() => { unmountedRef.current = false; return () => { unmountedRef.current = true; clearPendingTimeouts(); }; }, [clearPendingTimeouts]); const contextValue = (0, react.useMemo)(() => ({ announce }), [announce]); const getPortalDestination = (0, react.useCallback)(() => document.body, []); return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(AriaLiveAnnouncerContext.Provider, { value: contextValue, children: [children, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_useNotificationApi.Portal, { getPortalDestination, isOpen: true, children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(VisuallyHidden, { children: [/* @__PURE__ */ (0, react_jsx_runtime.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__ */ (0, react_jsx_runtime.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.default.createContext(defaultNotificationConfigurationContextValue); var NotificationConfigurationProvider = ({ children, displayFilter }) => { const parentConfiguration = (0, react.useContext)(NotificationConfigurationContext); const value = (0, react.useMemo)(() => ({ displayFilter: displayFilter ?? parentConfiguration.displayFilter }), [displayFilter, parentConfiguration.displayFilter]); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NotificationConfigurationContext.Provider, { value, children }); }; var useNotificationConfigurationContext = () => (0, react.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 } = require_useNotificationApi.useChatContext(); const { displayFilter } = useNotificationConfigurationContext(); const { applyDisplayFilter, fallbackPanel, filter, panel } = options ?? {}; const selector = (0, react.useCallback)((state) => { return { notifications: state.notifications.filter((notification) => { if (panel && !require_useNotificationApi.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 } = require_useNotificationApi.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 (0, react.useMemo)(() => ({ descriptionId: buildAriaIdentifier(sanitizedRootId, "description"), titleId: buildAriaIdentifier(sanitizedRootId, "title") }), [sanitizedRootId]); }; //#endregion //#region src/components/Dialog/components/Alert.tsx var Root = (0, react.forwardRef)(function AlertRoot({ children, className, ...props }, ref) { return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { ...props, className: (0, clsx.default)("str-chat__alert-root", className), ref, children }); }); var Header = (0, react.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__ */ (0, react_jsx_runtime.jsx)("div", { ...props, className: (0, clsx.default)("str-chat__alert-header", className), ref, children: title ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [Icon && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "str-chat__alert-header__copy", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", { className: "str-chat__alert-header__title", id: resolvedTitleId, children: title }), description && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { className: "str-chat__alert-header__description", id: resolvedDescriptionId, children: description })] })] }) : children }); }); var Alert = { Actions: (0, react.forwardRef)(function AlertActions({ children, className, ...props }, ref) { return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { ...props, className: (0, clsx.default)("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] = (0, react.useState)(null); const [stabilisedChosenPlacement, setStabilisedChosenPlacement] = (0, react.useState)(null); const { placement: chosenPlacement, refs, strategy, update, x, y } = require_useNotificationApi.usePopoverPosition({ allowFlip, freeze: true, offset, placement: stabilisedChosenPlacement ?? placement }); if (!stabilisedChosenPlacement && popperElement && placement !== chosenPlacement) setStabilisedChosenPlacement(chosenPlacement); else if (stabilisedChosenPlacement && !popperElement) setStabilisedChosenPlacement(null); const frozenReferenceRef = (0, react.useRef)(null); if (open && referenceElement && !frozenReferenceRef.current) frozenReferenceRef.current = referenceElement; if (!open) frozenReferenceRef.current = null; const effectiveReference = open ? frozenReferenceRef.current : referenceElement; (0, react.useEffect)(() => { refs.setReference(effectiveReference); }, [effectiveReference, refs]); (0, react.useEffect)(() => { refs.setFloating(popperElement); }, [popperElement, refs]); (0, react.useEffect)(() => { if (open && popperElement && effectiveReference) update?.(); }, [ open, placement, popperElement, update, updateKey, effectiveReference ]); (0, react.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 = require_useNotificationApi.useDialog({ closeOnClickOutside, dialogManagerId, id }); const open = require_useNotificationApi.useDialogIsOpen(id, dialogManagerId); const [shouldRender, setShouldRender] = (0, react.useState)(open); const closeTimeoutRef = (0, react.useRef)(null); const isClosing = !open && shouldRender; (0, react.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 ]); (0, react.useEffect)(() => () => { if (closeTimeoutRef.current) clearTimeout(closeTimeoutRef.current); }, []); const { placement: chosenPlacement, setPopperElement, styles } = useDialogAnchor({ allowFlip, offset, open: shouldRender, placement, referenceElement, updateKey, updatePositionOnContentResize }); (0, react.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__ */ (0, react_jsx_runtime.jsx)(require_useNotificationApi.DialogPortalEntry, { dialogId: id, dialogManagerId, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_react_aria_focus.FocusScope, { autoFocus: focus, contain: trapFocus, restoreFocus: true, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { ...anchorDivProps, "aria-describedby": ariaDescribedBy, "aria-label": resolvedAriaLabel, "aria-labelledby": ariaLabelledBy, "aria-modal": resolvedAriaModal, className: (0, clsx.default)("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 } = require_useNotificationApi.useTranslationContext(); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_useNotificationApi.Button, { appearance: "outline", "aria-label": isPlaying ? t("aria/Pause") : t("aria/Play"), circular: true, className: (0, clsx.default)("str-chat__button-play", className), "data-testid": isPlaying ? "pause-audio" : "play-audio", size: "sm", variant: "secondary", ...props, children: isPlaying ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_useNotificationApi.IconPauseFill, {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_useNotificationApi.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 } = require_useNotificationApi.useComponentContext(); const dialogIsOpen = require_useNotificationApi.useDialogIsOpen(dialogId, dialogManagerId); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DialogAnchor, { ...anchorProps, dialogManagerId, id: dialogId, children: dialogIsOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CalloutDialog, { className, onClose, children }) }); }; var DefaultCalloutDialog = ({ children, className, onClose }) => { const { t } = require_useNotificationApi.useTranslationContext(); return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "str-chat__callout", children: [children, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_useNotificationApi.Button, { appearance: "ghost", "aria-label": t("aria/Close callout dialog"), circular: true, className: (0, clsx.default)(className, "str-chat__callout__close-button"), onClick: onClose, size: "sm", variant: "secondary", children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_useNotificationApi.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] = (0, react.useState)(false); (0, react.useEffect)(() => () => setError(false), [imageUrl]); const nameString = userName?.toString() || ""; const avatarImageAlt = nameString.trim(); const sizeAwareInitials = (0, react.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__ */ (0, react_jsx_runtime.jsxs)("div", { className: (0, clsx.default)(`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__ */ (0, react_jsx_runtime.jsx)("div", { className: (0, clsx.default)("str-chat__avatar-status-badge", { "str-chat__avatar-status-badge--offline": !isOnline, "str-chat__avatar-status-badge--online": isOnline }) }), showImage ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", { alt: avatarImageAlt, className: "str-chat__avatar-image", "data-testid": "avatar-img", onError: () => setError(true), src: imageUrl }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [!!sizeAwareInitials.length && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "str-chat__avatar-initials", "data-testid": "avatar-fallback", children: sizeAwareInitials }), !sizeAwareInitials.length && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_useNotificationApi.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__ */ (0, react_jsx_runtime.jsx)("div", { ...spanProps, className: (0, clsx.default)("str-chat__badge", `str-chat__badge--variant-${variant}`, { [`str-chat__badge--size-${size}`]: size }, className), children }); var ErrorBadge = ({ className, size = "sm", ...rest }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, { ...rest, className, size, variant: "error", children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_useNotificationApi.IconExclamationMarkFill, {}) }); //#endregion //#region src/components/Avatar/AvatarStack.tsx function AvatarStack({ badgeSize, capLimit = 3, component: Component = "div", displayInfo = [], size }) { const { Avatar: Avatar$3 = Avatar } = require_useNotificationApi.useComponentContext(AvatarStack.name); const displayInfoToRender = (0, react.useMemo)(() => displayInfo.length > capLimit ? displayInfo.slice(0, capLimit) : displayInfo, [displayInfo, capLimit]); const overflowCount = displayInfo.length - displayInfoToRender.length; if (!displayInfo.length) return null; return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Component, { className: (0, clsx.default)("str-chat__avatar-stack", { [`str-chat__avatar-stack--size-${size}`]: typeof size === "string" }), "data-testid": "avatar-stack", children: [displayInfoToRender.map((info, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Avatar$3, { imageUrl: info.imageUrl, size, userName: info.userName }, info.id ?? `${info.userName}-${info.imageUrl}-${index}`)), typeof overflowCount === "number" && overflowCount > 0 && /* @__PURE__ */ (0, react_jsx_runtime.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__ */ (0, react_jsx_runtime.jsx)(GroupAvatar, { displayMembers: (0, react.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 = (0, react.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__ */ (0, react_jsx_runtime.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__ */ (0, react_jsx_runtime.jsxs)("div", { className: (0, clsx.default)("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__ */ (0, react_jsx_runtime.jsx)(Avatar, { imageUrl, size: avatarSize, userName }, id || `${userName}-${imageUrl}-${index}`)), typeof overflowCount === "number" && overflowCount > 0 && /* @__PURE__ */ (0, react_jsx_runtime.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 = require_useNotificationApi.IconChevronRight, variant, ...props }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", { ...props, className: (0, clsx.default)("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__ */ (0, react_jsx_runtime.jsx)(Icon, { className: "str-chat__context-menu__button__icon" }), label ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "str-chat__context-menu__button__label", children: label }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "str-chat__context-menu__button__details", children: details })] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "str-chat__context-menu__button__label", children }), !!hasSubMenu && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SubmenuIcon, { className: "str-chat__context-menu__button__submenu-icon" }) ] }); var UserContextMenuButton = ({ children, className, imageUrl, role = "menuitem", userName, ...props }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", { ...props, className: (0, clsx.default)("str-chat__context-menu__button str-chat__user-context-menu__button", className), role, type: "button", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Avatar, { imageUrl, size: "sm", userName }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "str-chat__context-menu__button__label", children: children ?? userName })] }); var EmojiContextMenuButton = ({ children, className, emoji, label, role = "menuitem", ...props }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", { ...props, className: (0, clsx.default)("str-chat__context-menu__button str-chat__emoji-context-menu__button", className), role, type: "button", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "str-chat__context-menu__button__emoji str-chat__emoji-item--entity", children: emoji }), /* @__PURE__ */ (0, react_jsx_runtime.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 = (0, react.useRef)(null); const [dialogContainer, setDialogContainer] = (0, react.useState)(null); const keepSubmenuOpenFlag = (0, react.useRef)(false); const dialogCloseTimeout = (0, react.useRef)(null); const dialogId = (0, react.useMemo)(() => `submenu-${Math.random().toString(36).slice(2)}`, []); const { dialog, dialogManager } = require_useNotificationApi.useDialogOnNearestManager({ id: dialogId }); const dialogIsOpen = require_useNotificationApi.useDialogIsOpen(dialogId, dialogManager?.id); (0, react.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 = (0, react.useCallback)(() => { if (dialogCloseTimeout.current) clearTimeout(dialogCloseTimeout.current); dialogCloseTimeout.current = setTimeout(() => { if (keepSubmenuOpenFlag.current) return; dialog.close(); }, 100); }, [dialog]); const keepSubmenuOpen = (0, react.useCallback)(() => { keepSubmenuOpenFlag.current = true; }, []); const allowToCloseSubmenu = (0, react.useCallback)(() => { keepSubmenuOpenFlag.current = false; }, []); const closeSubmenu = (0, react.useCallback)(() => { allowToCloseSubmenu(); closeDialogLazily(); }, [allowToCloseSubmenu, closeDialogLazily]); const handleClose = (0, react.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(); }; (0, react.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__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(BaseContextMenuButton, { "aria-expanded": dialogIsOpen, "aria-haspopup": "menu", className: (0, clsx.default)(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__ */ (0, react_jsx_runtime.jsx)("div", { className: (0, clsx.default)("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__ */ (0, react_jsx_runtime.jsx)(Submenu, {}) })] }); }; var ContextMenuButton = (props) => { const { Submenu, submenuContainerProps, submenuPlacement, submenuRollAxis, ...buttonProps } = props; if (Submenu) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ContextMenuButtonWithSubmenu, { ...buttonProps, Submenu, submenuContainerProps, submenuPlacement, submenuRollAxis }); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BaseContextMenuButton, { ...buttonProps }); }; var ContextMenuBackButton = ({ children, className, role = "menuitem", ...props }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { ...props, className: (0, clsx.default)("str-chat__context-menu__back-button", className), role, type: "button", children }); var ContextMenuHeader = ({ children, className, ...props }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { ...props, className: (0, clsx.default)("str-chat__context-menu__header", className), children }); var ContextMenuBody = ({ children, className, ...props }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { ...props, className: (0, clsx.default)("str-chat__context-menu__body", className), children }); var ContextMenuRoot = react.default.forwardRef(function ContextMenuRoot({ className, role = "menu", ...props }, ref) { return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { ...props, className: (0, clsx.default)("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) => { if (item.offsetParent !== null || item.offsetHeight > 0) return true; if (item.hidden) return false; if (item.style.display === "none") return false; return true; }; var getVisibleContextMenuKeyboardNavigationItems = (contextMenuRoot, itemSelector = DEFAULT_CONTEXT_MENU_KEYBOARD_NAVIGATION_ITEM_SELECTOR) => Array.from(contextMenuRoot?.querySelectorAll(itemSelector) ?? []).filter(isVisibleC