@xsolla/xui-context-menu 0.170.6-pr334.1781172208 → 0.170.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/native/index.js CHANGED
@@ -692,32 +692,18 @@ var useContextMenuPosition = ({
692
692
  left = flippedLeft;
693
693
  }
694
694
  }
695
- const next = {
695
+ setResolved({
696
696
  top,
697
697
  left,
698
698
  placement: joinPlacement(vertical, horizontal)
699
- };
700
- setResolved(
701
- (prev) => prev && prev.top === next.top && prev.left === next.left && prev.placement === next.placement ? prev : next
702
- );
703
- };
704
- let rafId = window.requestAnimationFrame(compute);
705
- const onResize = () => compute();
706
- let scrollRafPending = false;
707
- const onScroll = () => {
708
- if (scrollRafPending) return;
709
- scrollRafPending = true;
710
- rafId = window.requestAnimationFrame(() => {
711
- scrollRafPending = false;
712
- compute();
713
699
  });
714
700
  };
701
+ const rafId = window.requestAnimationFrame(compute);
702
+ const onResize = () => compute();
715
703
  window.addEventListener("resize", onResize);
716
- window.addEventListener("scroll", onScroll, true);
717
704
  return () => {
718
705
  window.cancelAnimationFrame(rafId);
719
706
  window.removeEventListener("resize", onResize);
720
- window.removeEventListener("scroll", onScroll, true);
721
707
  };
722
708
  }, [isOpen, placement, offset, triggerRef, panelRef]);
723
709
  return resolved;
@@ -1023,14 +1009,10 @@ var ContextMenu = (props) => {
1023
1009
  const radiusObj = theme.radius;
1024
1010
  const radiusVal = sizing.borderRadius ?? radiusObj?.contextMenu ?? 8;
1025
1011
  const shadowObj = theme.shadow;
1026
- const shadowVal = shadowObj?.popover ?? "";
1012
+ const shadowVal = shadowObj?.contextMenu ?? "";
1027
1013
  const panelPaddingVertical = sizing.paddingVertical ?? 8;
1028
- const glassBackground = theme.colors.layer?.float ?? theme.colors.background.primary;
1029
1014
  const panelStyle = {
1030
- background: glassBackground,
1031
- backdropFilter: "blur(12px)",
1032
- WebkitBackdropFilter: "blur(12px)",
1033
- border: `1px solid ${theme.colors.border.secondary}`,
1015
+ background: theme.colors.background.primary,
1034
1016
  borderRadius: radiusVal,
1035
1017
  boxShadow: shadowVal,
1036
1018
  width: width ?? sizing.panelWidth,
@@ -1182,7 +1164,6 @@ var ContextMenu = (props) => {
1182
1164
  }
1183
1165
  const hasStickySearch = !!searchNode;
1184
1166
  const prevOpenRef = (0, import_react5.useRef)(false);
1185
- const skipFocusRestoreRef = (0, import_react5.useRef)(false);
1186
1167
  (0, import_react5.useEffect)(() => {
1187
1168
  const wasOpen = prevOpenRef.current;
1188
1169
  prevOpenRef.current = open;
@@ -1208,10 +1189,7 @@ var ContextMenu = (props) => {
1208
1189
  return () => clearTimeout(timer);
1209
1190
  }
1210
1191
  if (wasOpen && !open) {
1211
- if (!skipFocusRestoreRef.current) {
1212
- triggerRef.current?.focus();
1213
- }
1214
- skipFocusRestoreRef.current = false;
1192
+ triggerRef.current?.focus();
1215
1193
  }
1216
1194
  }, [open]);
1217
1195
  (0, import_react5.useEffect)(() => {
@@ -1232,7 +1210,6 @@ var ContextMenu = (props) => {
1232
1210
  }
1233
1211
  }
1234
1212
  }
1235
- skipFocusRestoreRef.current = true;
1236
1213
  closeMenu();
1237
1214
  };
1238
1215
  document.addEventListener("mousedown", handlePointerDown);
@@ -1248,11 +1225,7 @@ var ContextMenu = (props) => {
1248
1225
  position: "sticky",
1249
1226
  top: 0,
1250
1227
  zIndex: 1,
1251
- // Match the glass panel so options scrolling underneath blur instead of
1252
- // showing through the translucent header.
1253
- background: glassBackground,
1254
- backdropFilter: "blur(12px)",
1255
- WebkitBackdropFilter: "blur(12px)"
1228
+ background: theme.colors.background.primary
1256
1229
  };
1257
1230
  const panel = open ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ContextMenuContext.Provider, { value: ctx, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1258
1231
  "div",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/index.tsx","../../src/ContextMenu.tsx","../../src/ContextMenuContext.tsx","../../src/ContextMenuItem.tsx","../../src/hooks/useContextMenuPosition.ts","../../src/hooks/useKeyboardNavigation.ts"],"sourcesContent":["export { ContextMenu } from \"./ContextMenu\";\nexport { ContextMenuItem } from \"./ContextMenuItem\";\n\nexport {\n useContextMenu,\n useContextMenuRequired,\n ContextMenuContext,\n} from \"./ContextMenuContext\";\n\nexport { useContextMenuPosition } from \"./hooks/useContextMenuPosition\";\nexport { useKeyboardNavigation } from \"./hooks/useKeyboardNavigation\";\n\nexport type {\n ContextMenuSize,\n ContextMenuItemType,\n ContextMenuItemLeadingControl,\n ContextMenuOptionItemProps,\n ContextMenuSearchItemProps,\n ContextMenuHeadingItemProps,\n ContextMenuDividerItemProps,\n ContextMenuItemProps,\n ContextMenuPanelType,\n ContextMenuPlacement,\n ContextMenuPosition,\n ContextMenuProps,\n ContextMenuContextValue,\n ContextMenuSizing,\n} from \"./types\";\n","import React, {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n isValidElement,\n cloneElement,\n type ReactElement,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport {\n useResolvedTheme,\n useId,\n type ThemeOverrideProps,\n} from \"@xsolla/xui-core\";\nimport { Spinner } from \"@xsolla/xui-spinner\";\nimport { ContextMenuContext } from \"./ContextMenuContext\";\nimport { ContextMenuItem } from \"./ContextMenuItem\";\nimport { useContextMenuPosition } from \"./hooks/useContextMenuPosition\";\nimport { useKeyboardNavigation } from \"./hooks/useKeyboardNavigation\";\nimport type {\n ContextMenuCellMeta,\n ContextMenuContextValue,\n ContextMenuDividerItemProps,\n ContextMenuHeadingItemProps,\n ContextMenuOptionItemProps,\n ContextMenuProps,\n ContextMenuSize,\n} from \"./types\";\n\ntype Props = ContextMenuProps & ThemeOverrideProps;\n\ninterface CellEntry {\n id: string;\n meta: ContextMenuCellMeta;\n}\n\ntype PresetItem =\n | ContextMenuOptionItemProps\n | ContextMenuHeadingItemProps\n | ContextMenuDividerItemProps;\n\nconst SEARCH_DEBOUNCE_MS = 200;\n\nconst EmptyMessage: React.FC<{ children: React.ReactNode; color: string }> = ({\n children,\n color,\n}) => (\n <div\n style={{\n padding: 12,\n color,\n fontSize: 14,\n textAlign: \"center\",\n }}\n >\n {children}\n </div>\n);\n\nexport const ContextMenu: React.FC<Props> = (props) => {\n const {\n type,\n items,\n children,\n size = \"md\",\n searchable,\n loading,\n emptyMessage,\n empty,\n trigger,\n isOpen,\n onOpenChange,\n closeOnSelect,\n width,\n maxHeight,\n placement = \"bottom-start\",\n onSelect,\n \"aria-label\": ariaLabel,\n \"data-testid\": testId,\n testID,\n themeMode,\n themeProductContext,\n } = props;\n\n const { theme } = useResolvedTheme({ themeMode, themeProductContext });\n\n const isControlled = isOpen !== undefined;\n const [internalOpen, setInternalOpen] = useState(false);\n const open = isControlled ? !!isOpen : internalOpen;\n\n const setOpen = useCallback(\n (next: boolean) => {\n if (!isControlled) setInternalOpen(next);\n onOpenChange?.(next);\n },\n [isControlled, onOpenChange]\n );\n\n const [activeIndex, setActiveIndex] = useState<number>(-1);\n const cellsRef = useRef<CellEntry[]>([]);\n const [cellsVersion, setCellsVersion] = useState(0);\n\n const triggerRef = useRef<HTMLElement | null>(null);\n const panelRef = useRef<HTMLDivElement | null>(null);\n const menuId = useId();\n\n // Search query state\n const [query, setQuery] = useState(\"\");\n const [debouncedQuery, setDebouncedQuery] = useState(\"\");\n const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(() => {\n if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current);\n debounceTimerRef.current = setTimeout(() => {\n setDebouncedQuery(query);\n }, SEARCH_DEBOUNCE_MS);\n return () => {\n if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current);\n };\n }, [query]);\n\n const closeMenu = useCallback(() => {\n setOpen(false);\n setActiveIndex(-1);\n }, [setOpen]);\n\n const registerCell = useCallback((id: string, meta: ContextMenuCellMeta) => {\n const existing = cellsRef.current.findIndex((c) => c.id === id);\n if (existing === -1) {\n cellsRef.current.push({ id, meta });\n setCellsVersion((v) => v + 1);\n return cellsRef.current.length - 1;\n }\n // Update metadata in place — keep registry order stable. Only bump the\n // version when nav-relevant metadata (disabled) actually changes.\n const prev = cellsRef.current[existing].meta;\n cellsRef.current[existing] = { id, meta };\n if (prev.disabled !== meta.disabled || prev.type !== meta.type) {\n setCellsVersion((v) => v + 1);\n }\n return existing;\n }, []);\n\n const unregisterCell = useCallback((id: string) => {\n const idx = cellsRef.current.findIndex((c) => c.id === id);\n if (idx !== -1) {\n // splice keeps survivors contiguous; consumers derive their index live\n // via getCellIndex so they reindex automatically.\n cellsRef.current.splice(idx, 1);\n setCellsVersion((v) => v + 1);\n }\n }, []);\n\n const getCellIndex = useCallback(\n (id: string) => cellsRef.current.findIndex((c) => c.id === id),\n []\n );\n\n const ctx: ContextMenuContextValue = useMemo(\n () => ({\n size: size as ContextMenuSize,\n menuId,\n closeMenu,\n registerCell,\n unregisterCell,\n getCellIndex,\n cellsVersion,\n activeIndex,\n setActiveIndex,\n query,\n setQuery,\n }),\n [\n size,\n menuId,\n closeMenu,\n registerCell,\n unregisterCell,\n getCellIndex,\n cellsVersion,\n activeIndex,\n query,\n ]\n );\n\n const triggerNode = useMemo(() => {\n if (!trigger) return null;\n const inner = isValidElement(trigger)\n ? cloneElement(\n trigger as ReactElement<{\n \"aria-haspopup\"?: string;\n \"aria-expanded\"?: string | boolean;\n }>,\n {\n \"aria-haspopup\": \"menu\",\n \"aria-expanded\": open ? \"true\" : \"false\",\n }\n )\n : trigger;\n return (\n <span\n ref={(node) => {\n if (!node) {\n triggerRef.current = null;\n return;\n }\n const focusable = node.querySelector<HTMLElement>(\n \"button, [href], input, select, textarea, [tabindex]:not([tabindex='-1'])\"\n );\n triggerRef.current = focusable ?? node;\n }}\n onClick={() => setOpen(!open)}\n style={{ display: \"inline-flex\" }}\n >\n {inner}\n </span>\n );\n }, [trigger, open, setOpen]);\n\n const usePortal = !!trigger && typeof document !== \"undefined\";\n\n const position = useContextMenuPosition({\n triggerRef,\n panelRef,\n isOpen: open && usePortal,\n placement,\n });\n\n const cellsForNav = useMemo(\n () => cellsRef.current.map((c) => ({ id: c.id, meta: c.meta })),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [cellsVersion]\n );\n\n const { handleKeyDown } = useKeyboardNavigation({\n isOpen: open,\n cells: cellsForNav,\n activeIndex,\n setActiveIndex,\n onClose: closeMenu,\n triggerRef,\n });\n\n const sizingFn = (theme.sizing as { contextMenu?: (s: string) => unknown })\n .contextMenu;\n const sizing = sizingFn\n ? (sizingFn(size) as {\n panelWidth?: number;\n borderRadius?: number;\n paddingVertical?: number;\n })\n : {};\n const radiusObj = (theme as { radius?: { contextMenu?: number } }).radius;\n const radiusVal = sizing.borderRadius ?? radiusObj?.contextMenu ?? 8;\n const shadowObj = (theme as { shadow?: { popover?: string } }).shadow;\n const shadowVal = shadowObj?.popover ?? \"\";\n const panelPaddingVertical = sizing.paddingVertical ?? 8;\n\n // Figma ContextMenu panel is \"glass\": semi-transparent layer/float fill\n // with a backdrop blur (Figma BACKGROUND_BLUR 24 = CSS blur(12px)).\n const glassBackground =\n theme.colors.layer?.float ?? theme.colors.background.primary;\n\n const panelStyle: React.CSSProperties = {\n background: glassBackground,\n backdropFilter: \"blur(12px)\",\n WebkitBackdropFilter: \"blur(12px)\",\n border: `1px solid ${theme.colors.border.secondary}`,\n borderRadius: radiusVal,\n boxShadow: shadowVal,\n width: width ?? sizing.panelWidth,\n maxHeight,\n overflow: \"hidden\",\n display: open ? \"flex\" : \"none\",\n flexDirection: \"column\",\n outline: \"none\",\n fontFamily: theme.fonts.body,\n paddingTop: panelPaddingVertical,\n paddingBottom: panelPaddingVertical,\n };\n\n if (usePortal) {\n panelStyle.position = \"fixed\";\n panelStyle.top = position?.top ?? 0;\n panelStyle.left = position?.left ?? 0;\n }\n\n // Filter preset items based on debounced query (only when searchable + items)\n const filteredItems = useMemo<PresetItem[] | undefined>(() => {\n if (!items) return undefined;\n if (!searchable || !debouncedQuery) return items.slice();\n const q = debouncedQuery.toLowerCase();\n const matchedFlags = items.map((item) => {\n if (item.type === \"option\") {\n return String(item.label ?? \"\")\n .toLowerCase()\n .includes(q);\n }\n return false; // headings/dividers themselves never match\n });\n // Now hide headings whose options are all filtered out and trailing dividers\n const result: PresetItem[] = [];\n let pendingHeading: {\n item: ContextMenuHeadingItemProps;\n idx: number;\n } | null = null;\n let lastEmittedWasContent = false; // whether last pushed was option (so divider can follow)\n let groupHasOption = false;\n for (let i = 0; i < items.length; i += 1) {\n const item = items[i];\n if (item.type === \"heading\") {\n // Starting a new group\n pendingHeading = { item, idx: i };\n groupHasOption = false;\n } else if (item.type === \"divider\") {\n // Emit divider only if the previously emitted thing was content\n if (lastEmittedWasContent) {\n result.push(item);\n lastEmittedWasContent = false;\n }\n // dividers also reset pending heading so a heading sits with its group\n pendingHeading = null;\n groupHasOption = false;\n } else if (item.type === \"option\") {\n if (matchedFlags[i]) {\n if (pendingHeading) {\n result.push(pendingHeading.item);\n pendingHeading = null;\n }\n result.push(item);\n lastEmittedWasContent = true;\n groupHasOption = true;\n }\n }\n }\n // Strip trailing divider if any\n while (result.length > 0 && result[result.length - 1].type === \"divider\") {\n result.pop();\n }\n // Suppress unused\n void groupHasOption;\n return result;\n }, [items, searchable, debouncedQuery]);\n\n const effectiveCloseOnSelect =\n closeOnSelect !== undefined ? closeOnSelect : type !== \"checkbox\";\n\n const renderPresetItem = (item: PresetItem, key: number) => {\n if (item.type === \"heading\") {\n return (\n <ContextMenuItem\n key={`h-${key}`}\n {...item}\n size={item.size ?? (size as ContextMenuSize)}\n />\n );\n }\n if (item.type === \"divider\") {\n return (\n <ContextMenuItem\n key={`d-${key}`}\n {...item}\n size={item.size ?? (size as ContextMenuSize)}\n />\n );\n }\n const composed = composeItemForPreset(type, item);\n const originalSelect = composed.onSelect;\n const wrappedSelect = () => {\n originalSelect?.();\n onSelect?.(item);\n if (effectiveCloseOnSelect) closeMenu();\n };\n return (\n <ContextMenuItem\n key={`o-${key}`}\n {...composed}\n size={composed.size ?? (size as ContextMenuSize)}\n onSelect={wrappedSelect}\n />\n );\n };\n\n // Determine what to render in the panel body\n const isLoadingState = loading;\n\n let bodyContent: React.ReactNode = null;\n let isBodyEmpty = false;\n let searchNode: React.ReactNode = null;\n\n if (isLoadingState) {\n bodyContent = (\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n padding: 16,\n }}\n >\n <Spinner size={size === \"xl\" ? \"lg\" : size === \"lg\" ? \"md\" : \"sm\"} />\n </div>\n );\n } else if (children !== undefined && children !== null) {\n // Custom path. Empty when children is falsy/empty array.\n const childArr = React.Children.toArray(children);\n if (childArr.length === 0) {\n isBodyEmpty = true;\n } else {\n bodyContent = children;\n }\n } else if (type && items) {\n // Preset path\n if (searchable) {\n searchNode = (\n <ContextMenuItem\n type=\"search\"\n value={query}\n onValueChange={setQuery}\n size={size as ContextMenuSize}\n />\n );\n }\n const visible = filteredItems ?? [];\n const optionCount = visible.filter((i) => i.type === \"option\").length;\n if (optionCount === 0) {\n isBodyEmpty = true;\n } else {\n bodyContent = visible.map((it, idx) => renderPresetItem(it, idx));\n }\n } else {\n // No children, no items — empty\n isBodyEmpty = true;\n }\n\n if (isBodyEmpty) {\n bodyContent = empty ?? (\n <EmptyMessage color={theme.colors.content.tertiary}>\n {emptyMessage ?? \"No results\"}\n </EmptyMessage>\n );\n }\n\n const hasStickySearch = !!searchNode;\n\n // Focus management on open/close transitions\n const prevOpenRef = useRef(false);\n const skipFocusRestoreRef = useRef(false);\n useEffect(() => {\n const wasOpen = prevOpenRef.current;\n prevOpenRef.current = open;\n if (!wasOpen && open) {\n // open transition: focus search if present, else first option\n // wait for cells to register\n const timer = setTimeout(() => {\n const panel = panelRef.current;\n if (!panel) return;\n const search =\n panel.querySelector<HTMLInputElement>(\"[role='searchbox']\");\n if (search) {\n search.focus();\n return;\n }\n const firstOption = panel.querySelector<HTMLElement>(\n \"[role='menuitem'], [role='menuitemcheckbox'], [role='menuitemradio']\"\n );\n if (firstOption) {\n firstOption.focus();\n // Reset activeIndex so keyboard nav starts fresh; onFocus will have\n // set it to the focused option's index, but tests expect ArrowDown\n // to move to the first option from -1.\n setActiveIndex(-1);\n } else {\n panel.focus();\n }\n }, 0);\n return () => clearTimeout(timer);\n }\n if (wasOpen && !open) {\n // close transition: focus the trigger — unless the menu was dismissed\n // by clicking outside. Stealing focus back then would scroll the\n // trigger into view while the user is interacting elsewhere.\n if (!skipFocusRestoreRef.current) {\n triggerRef.current?.focus();\n }\n skipFocusRestoreRef.current = false;\n }\n }, [open]);\n\n // Dismiss on outside press. Per-instance + DOM-guarded so multiple open\n // menus each close only themselves, native builds no-op, and React <18 is\n // unaffected (plain document listener, no concurrent APIs). Only for\n // portal-anchored (trigger) menus; inline/controlled menus stay\n // consumer-managed.\n useEffect(() => {\n if (!open || !usePortal || typeof document === \"undefined\") return;\n const handlePointerDown = (event: MouseEvent) => {\n const target = event.target as Node | null;\n if (!target) return;\n if (panelRef.current?.contains(target)) return;\n if (triggerRef.current?.contains(target)) return;\n // This menu's submenus render in a separate DOM subtree (document.body);\n // treat clicks inside them as inside this menu.\n if (target instanceof Element) {\n const portals = document.querySelectorAll(\n \"[data-xui-context-menu-portal]\"\n );\n for (let i = 0; i < portals.length; i += 1) {\n const portal = portals[i];\n if (\n portal.getAttribute(\"data-xui-context-menu-portal\") === menuId &&\n portal.contains(target)\n ) {\n return;\n }\n }\n }\n skipFocusRestoreRef.current = true;\n closeMenu();\n };\n document.addEventListener(\"mousedown\", handlePointerDown);\n return () => document.removeEventListener(\"mousedown\", handlePointerDown);\n }, [open, usePortal, closeMenu, menuId]);\n\n const resolvedPlacement = position?.placement ?? placement;\n\n const scrollContainerStyle: React.CSSProperties = {\n overflowY: \"auto\",\n flex: 1,\n minHeight: 0,\n };\n\n const stickyHeaderStyle: React.CSSProperties = {\n position: \"sticky\",\n top: 0,\n zIndex: 1,\n // Match the glass panel so options scrolling underneath blur instead of\n // showing through the translucent header.\n background: glassBackground,\n backdropFilter: \"blur(12px)\",\n WebkitBackdropFilter: \"blur(12px)\",\n };\n\n const panel = open ? (\n <ContextMenuContext.Provider value={ctx}>\n <div\n ref={panelRef}\n role=\"menu\"\n aria-label={ariaLabel}\n data-testid={testId || testID}\n data-placement={usePortal ? resolvedPlacement : undefined}\n tabIndex={-1}\n onKeyDown={handleKeyDown}\n onMouseLeave={() => setActiveIndex(-1)}\n style={panelStyle}\n >\n {hasStickySearch && (\n <div data-sticky=\"top\" style={stickyHeaderStyle}>\n {searchNode}\n </div>\n )}\n <div style={scrollContainerStyle}>{bodyContent}</div>\n </div>\n </ContextMenuContext.Provider>\n ) : null;\n\n return (\n <>\n {triggerNode}\n {usePortal ? panel && createPortal(panel, document.body) : panel}\n </>\n );\n};\n\nContextMenu.displayName = \"ContextMenu\";\n\nfunction composeItemForPreset(\n type: ContextMenuProps[\"type\"],\n item: ContextMenuOptionItemProps\n): ContextMenuOptionItemProps {\n switch (type) {\n case \"checkbox\":\n return { ...item, leadingControl: \"checkbox\" };\n case \"radio\":\n return { ...item, leadingControl: \"radio\" };\n case \"list\":\n case \"phone\":\n case \"status\":\n case \"brandLogo\":\n case \"avatar\":\n default:\n return { ...item };\n }\n}\n","import { createContext, useContext } from \"react\";\nimport type { ContextMenuContextValue } from \"./types\";\n\nexport const ContextMenuContext = createContext<\n ContextMenuContextValue | undefined\n>(undefined);\n\nexport const useContextMenu = () => {\n const context = useContext(ContextMenuContext);\n return context;\n};\n\nexport const useContextMenuRequired = () => {\n const context = useContext(ContextMenuContext);\n if (!context) {\n throw new Error(\n \"useContextMenuRequired must be used within a ContextMenu component\"\n );\n }\n return context;\n};\n","import React, { useEffect, useLayoutEffect, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport {\n useResolvedTheme,\n useId,\n type ThemeOverrideProps,\n} from \"@xsolla/xui-core\";\nimport { Typography } from \"@xsolla/xui-typography\";\nimport { Checkbox } from \"@xsolla/xui-checkbox\";\nimport { Radio } from \"@xsolla/xui-radio\";\nimport { Search } from \"@xsolla/xui-icons-base\";\nimport { useContextMenu } from \"./ContextMenuContext\";\nimport type {\n ContextMenuDividerItemProps,\n ContextMenuHeadingItemProps,\n ContextMenuItemProps,\n ContextMenuOptionItemProps,\n ContextMenuSearchItemProps,\n ContextMenuSize,\n} from \"./types\";\n\ntype BodyVariant = \"bodyLg\" | \"bodyMd\" | \"bodySm\" | \"bodyXs\";\ntype AccentVariant =\n | \"bodyLgAccent\"\n | \"bodyMdAccent\"\n | \"bodySmAccent\"\n | \"bodyXsAccent\";\n\nconst sizeToVariants: Record<\n ContextMenuSize,\n { label: BodyVariant; description: BodyVariant; headingAccent: AccentVariant }\n> = {\n xl: { label: \"bodyLg\", description: \"bodyLg\", headingAccent: \"bodyLgAccent\" },\n lg: { label: \"bodyLg\", description: \"bodyMd\", headingAccent: \"bodyMdAccent\" },\n md: { label: \"bodyMd\", description: \"bodySm\", headingAccent: \"bodySmAccent\" },\n sm: { label: \"bodySm\", description: \"bodyXs\", headingAccent: \"bodyXsAccent\" },\n};\n\nconst sizeLabelOverride: Partial<\n Record<ContextMenuSize, { fontSize: number; lineHeight: string }>\n> = {\n xl: { fontSize: 20, lineHeight: \"26px\" },\n};\n\ntype Props = ContextMenuItemProps & ThemeOverrideProps;\n\nexport const ContextMenuItem: React.FC<Props> = (props) => {\n if (props.type === \"option\") return <OptionCell {...props} />;\n if (props.type === \"heading\") return <HeadingCell {...props} />;\n if (props.type === \"divider\") return <DividerCell {...props} />;\n if (props.type === \"search\") return <SearchCell {...props} />;\n return null;\n};\n\nContextMenuItem.displayName = \"ContextMenuItem\";\n\nconst SubmenuChevron: React.FC<{ color: string; size: number }> = ({\n color,\n size,\n}) => (\n <span\n data-testid=\"ctxmenu-submenu-chevron\"\n aria-hidden=\"true\"\n style={{\n color,\n display: \"inline-flex\",\n alignItems: \"center\",\n width: size,\n height: size,\n }}\n >\n <svg\n width={size}\n height={size}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M17.0605 11.6464C17.2558 11.8417 17.2558 12.1583 17.0605 12.3536L9.70703 19.707L8.29297 18.293L14.5859 12L8.29297 5.70703L9.70703 4.29297L17.0605 11.6464Z\"\n fill=\"currentColor\"\n />\n </svg>\n </span>\n);\n\nconst OptionCell: React.FC<ContextMenuOptionItemProps & ThemeOverrideProps> = ({\n size: propSize,\n label,\n description,\n disabled,\n destructive,\n checked,\n leadingControl,\n leadingIcon,\n status,\n iconWrapper,\n slotContent,\n value,\n hint,\n trailingIcon,\n keyboardShortcut,\n hasSubmenu,\n submenu,\n onSelect,\n testID,\n themeMode,\n themeProductContext,\n \"data-testid\": testId,\n}) => {\n const { theme } = useResolvedTheme({ themeMode, themeProductContext });\n const ctx = useContextMenu();\n const size: ContextMenuSize = propSize ?? ctx?.size ?? \"md\";\n const sizing = theme.sizing.contextMenu(size);\n const variants = sizeToVariants[size];\n\n const id = useId();\n const registerCell = ctx?.registerCell;\n const unregisterCell = ctx?.unregisterCell;\n const getCellIndex = ctx?.getCellIndex;\n const [isHovered, setIsHovered] = useState(false);\n const [submenuOpen, setSubmenuOpen] = useState(false);\n const [submenuPos, setSubmenuPos] = useState<{\n top: number;\n left: number;\n } | null>(null);\n const optionRef = React.useRef<HTMLDivElement | null>(null);\n const submenuWrapperRef = React.useRef<HTMLDivElement | null>(null);\n const closeTimerRef = React.useRef<ReturnType<typeof setTimeout> | null>(\n null\n );\n\n const cancelClose = () => {\n if (closeTimerRef.current) {\n clearTimeout(closeTimerRef.current);\n closeTimerRef.current = null;\n }\n };\n\n const scheduleClose = () => {\n cancelClose();\n closeTimerRef.current = setTimeout(() => setSubmenuOpen(false), 120);\n };\n\n useEffect(() => () => cancelClose(), []);\n\n useEffect(() => {\n if (!hasSubmenu || !submenuOpen) return;\n const onMouseDown = (e: MouseEvent) => {\n const target = e.target as HTMLElement | null;\n if (!target) return;\n const inOption = optionRef.current?.contains(target);\n const inSubmenu = submenuWrapperRef.current?.contains(target);\n if (!inOption && !inSubmenu) {\n setSubmenuOpen(false);\n return;\n }\n if (\n inSubmenu &&\n target.closest(\n '[role=\"menuitem\"],[role=\"menuitemcheckbox\"],[role=\"menuitemradio\"]'\n )\n ) {\n setSubmenuOpen(false);\n }\n };\n document.addEventListener(\"mousedown\", onMouseDown);\n return () => document.removeEventListener(\"mousedown\", onMouseDown);\n }, [hasSubmenu, submenuOpen]);\n\n useLayoutEffect(() => {\n if (!hasSubmenu || !submenuOpen) {\n setSubmenuPos(null);\n return;\n }\n const update = () => {\n const node = optionRef.current;\n if (!node) return;\n const rect = node.getBoundingClientRect();\n setSubmenuPos({ top: rect.top, left: rect.right });\n };\n update();\n window.addEventListener(\"scroll\", update, true);\n window.addEventListener(\"resize\", update);\n return () => {\n window.removeEventListener(\"scroll\", update, true);\n window.removeEventListener(\"resize\", update);\n };\n }, [hasSubmenu, submenuOpen]);\n const onSelectRef = React.useRef(onSelect);\n onSelectRef.current = onSelect;\n // Register on mount / unregister on unmount. Deps are the stable registry\n // callbacks + id only — NOT `ctx`, whose identity changes on every hover\n // (activeIndex). Depending on `ctx` here churned register/unregister and, on\n // React <18 (interleaved passive-effect flush), collapsed every row to the\n // same index so one hover highlighted all rows (FEP-764).\n useEffect(() => {\n if (!registerCell || !unregisterCell) return;\n registerCell(id, {\n type: \"option\",\n onSelect: () => onSelectRef.current?.(),\n });\n return () => unregisterCell(id);\n }, [registerCell, unregisterCell, id]);\n\n // Keep registry metadata (disabled) in sync without reordering the cell.\n useEffect(() => {\n if (!registerCell) return;\n registerCell(id, {\n type: \"option\",\n disabled,\n onSelect: () => onSelectRef.current?.(),\n });\n }, [registerCell, id, disabled]);\n\n // Derive index live from the registry instead of caching it in state, so it\n // is always correct after dynamic add/remove without per-item re-registration.\n const index = getCellIndex ? getCellIndex(id) : -1;\n const isActive = ctx ? index >= 0 && ctx.activeIndex === index : false;\n const inHoverState =\n isActive || (!ctx && isHovered) || (hasSubmenu && submenuOpen);\n\n const handleEnter = () => {\n if (disabled) return;\n if (ctx && index >= 0) ctx.setActiveIndex(index);\n if (!ctx) setIsHovered(true);\n if (hasSubmenu) setSubmenuOpen(true);\n };\n\n const handleLeave = () => {\n if (!ctx) setIsHovered(false);\n if (hasSubmenu) scheduleClose();\n };\n\n const labelColor = disabled\n ? theme.colors.control.input.textDisable\n : destructive\n ? theme.colors.content.alert.primary\n : theme.colors.content.primary;\n\n const bg = inHoverState ? theme.colors.control.input.bgHover : \"transparent\";\n\n const role =\n !hasSubmenu && checked !== undefined ? \"menuitemcheckbox\" : \"menuitem\";\n const ariaChecked =\n !hasSubmenu && checked !== undefined\n ? checked\n ? \"true\"\n : \"false\"\n : undefined;\n\n const handleClick = () => {\n if (disabled) return;\n if (hasSubmenu) {\n setSubmenuOpen(true);\n return;\n }\n onSelect?.();\n };\n\n const closeSubmenuAndFocus = () => {\n setSubmenuOpen(false);\n optionRef.current?.focus();\n };\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (disabled) {\n if (e.key === \"Enter\" || e.key === \" \") e.preventDefault();\n return;\n }\n if (hasSubmenu) {\n if (e.key === \"ArrowRight\" || e.key === \"Enter\") {\n e.preventDefault();\n e.stopPropagation();\n setSubmenuOpen(true);\n return;\n }\n if (e.key === \"ArrowLeft\" || e.key === \"Escape\") {\n if (submenuOpen) {\n e.preventDefault();\n e.stopPropagation();\n closeSubmenuAndFocus();\n return;\n }\n }\n }\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onSelect?.();\n }\n };\n\n return (\n <div\n ref={optionRef}\n role={role}\n aria-checked={ariaChecked}\n aria-disabled={disabled ? \"true\" : undefined}\n aria-haspopup={hasSubmenu ? \"menu\" : undefined}\n aria-expanded={hasSubmenu ? (submenuOpen ? \"true\" : \"false\") : undefined}\n tabIndex={0}\n data-testid={testId || testID}\n data-state={inHoverState ? \"hover\" : undefined}\n data-destructive={destructive ? \"true\" : undefined}\n aria-keyshortcuts={keyboardShortcut}\n onMouseEnter={() => {\n cancelClose();\n handleEnter();\n }}\n onMouseLeave={handleLeave}\n onFocus={handleEnter}\n onBlur={() => {\n if (!ctx) setIsHovered(false);\n }}\n onClick={handleClick}\n onKeyDown={handleKeyDown}\n style={{\n position: \"relative\",\n display: \"flex\",\n flexDirection: \"row\",\n alignItems: \"center\",\n gap: sizing.gap,\n paddingLeft: sizing.itemPaddingHorizontal,\n paddingRight: sizing.itemPaddingHorizontal,\n paddingTop: sizing.itemPaddingVertical,\n paddingBottom: sizing.itemPaddingVertical,\n backgroundColor: bg,\n cursor: disabled ? \"not-allowed\" : \"pointer\",\n outline: \"none\",\n }}\n >\n {leadingControl === \"checkbox\" && (\n <span\n data-testid=\"ctxmenu-leading-checkbox\"\n aria-hidden=\"true\"\n style={{ pointerEvents: \"none\", display: \"inline-flex\" }}\n >\n <Checkbox\n size={size}\n checked={!!checked}\n disabled={!!disabled}\n themeMode={themeMode}\n themeProductContext={themeProductContext}\n />\n </span>\n )}\n {leadingControl === \"radio\" && (\n <span\n data-testid=\"ctxmenu-leading-radio\"\n aria-hidden=\"true\"\n style={{ pointerEvents: \"none\", display: \"inline-flex\" }}\n >\n <Radio\n size={size}\n checked={!!checked}\n disabled={!!disabled}\n themeMode={themeMode}\n themeProductContext={themeProductContext}\n />\n </span>\n )}\n {leadingIcon}\n {status}\n {iconWrapper}\n {slotContent}\n <span\n style={{\n flex: 1,\n display: \"flex\",\n flexDirection: \"column\",\n gap: 2,\n minWidth: 0,\n }}\n >\n <Typography\n variant={variants.label}\n color={labelColor}\n noWrap={description === undefined}\n style={{\n ...(description === undefined\n ? { display: \"block\", minWidth: 0 }\n : {}),\n ...(sizeLabelOverride[size] ?? {}),\n }}\n >\n {label}\n </Typography>\n {description !== undefined && (\n <Typography\n variant={variants.description}\n color={theme.colors.content.tertiary}\n >\n {description}\n </Typography>\n )}\n </span>\n {(value !== undefined || hint !== undefined) && (\n <span\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"flex-end\",\n }}\n >\n {value !== undefined && (\n <Typography\n variant={variants.label}\n color={theme.colors.content.secondary}\n style={sizeLabelOverride[size]}\n >\n {value}\n </Typography>\n )}\n {hint !== undefined && (\n <Typography\n variant={variants.description}\n color={theme.colors.content.tertiary}\n >\n {hint}\n </Typography>\n )}\n </span>\n )}\n {keyboardShortcut && (\n <Typography\n as=\"kbd\"\n variant={variants.description}\n color={theme.colors.content.tertiary}\n >\n {keyboardShortcut}\n </Typography>\n )}\n {hasSubmenu && (\n <SubmenuChevron\n color={theme.colors.content.tertiary}\n size={sizing.iconSize}\n />\n )}\n {trailingIcon}\n {hasSubmenu &&\n submenuOpen &&\n submenu &&\n submenuPos &&\n typeof document !== \"undefined\" &&\n createPortal(\n <div\n ref={submenuWrapperRef}\n data-xui-context-menu-portal={ctx?.menuId}\n onMouseEnter={cancelClose}\n onMouseLeave={scheduleClose}\n style={{\n position: \"fixed\",\n top: submenuPos.top,\n left: submenuPos.left,\n zIndex: 2000,\n }}\n >\n {submenu}\n </div>,\n document.body\n )}\n </div>\n );\n};\n\nconst HeadingCell: React.FC<\n ContextMenuHeadingItemProps & ThemeOverrideProps\n> = ({\n size: propSize,\n label,\n description,\n testID,\n themeMode,\n themeProductContext,\n \"data-testid\": testId,\n}) => {\n const { theme } = useResolvedTheme({ themeMode, themeProductContext });\n const ctx = useContextMenu();\n const size: ContextMenuSize = propSize ?? ctx?.size ?? \"md\";\n const sizing = theme.sizing.contextMenu(size);\n const variants = sizeToVariants[size];\n\n const id = useId();\n const registerCell = ctx?.registerCell;\n const unregisterCell = ctx?.unregisterCell;\n useEffect(() => {\n if (!registerCell || !unregisterCell) return;\n registerCell(id, { type: \"heading\" });\n return () => unregisterCell(id);\n }, [registerCell, unregisterCell, id]);\n\n return (\n <div\n role=\"presentation\"\n data-testid={testId || testID}\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: 2,\n paddingLeft: sizing.itemPaddingHorizontal,\n paddingRight: sizing.itemPaddingHorizontal,\n paddingTop: sizing.itemPaddingVertical,\n paddingBottom: sizing.itemPaddingVertical,\n }}\n >\n <Typography\n variant={variants.headingAccent}\n color={theme.colors.content.secondary}\n style={{ textTransform: \"uppercase\", letterSpacing: 0.5 }}\n >\n {label}\n </Typography>\n {description !== undefined && (\n <Typography\n variant={variants.description}\n color={theme.colors.content.tertiary}\n >\n {description}\n </Typography>\n )}\n </div>\n );\n};\n\nconst SearchCell: React.FC<ContextMenuSearchItemProps & ThemeOverrideProps> = ({\n size: propSize,\n value,\n onValueChange,\n placeholder = \"Search\",\n autoFocus,\n \"aria-label\": ariaLabel = \"Search options\",\n \"data-testid\": testId,\n testID,\n themeMode,\n themeProductContext,\n}) => {\n const { theme } = useResolvedTheme({ themeMode, themeProductContext });\n const ctx = useContextMenu();\n const size: ContextMenuSize = propSize ?? ctx?.size ?? \"md\";\n const sizing = theme.sizing.contextMenu(size);\n\n const id = useId();\n const registerCell = ctx?.registerCell;\n const unregisterCell = ctx?.unregisterCell;\n useEffect(() => {\n if (!registerCell || !unregisterCell) return;\n registerCell(id, { type: \"search\" });\n return () => unregisterCell(id);\n }, [registerCell, unregisterCell, id]);\n\n return (\n <div\n style={{\n boxSizing: \"border-box\",\n paddingLeft: sizing.searchPaddingHorizontal,\n paddingRight: sizing.searchPaddingHorizontal,\n paddingTop: sizing.searchPaddingVertical,\n paddingBottom: sizing.searchPaddingVertical,\n }}\n >\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: 8,\n paddingBottom: 6,\n borderBottom: `1px solid ${theme.colors.border.secondary}`,\n }}\n >\n <Search\n variant=\"line\"\n size={sizing.searchIconSize}\n color={theme.colors.control.input.placeholder}\n aria-hidden\n />\n <input\n type=\"search\"\n role=\"searchbox\"\n aria-label={ariaLabel}\n placeholder={placeholder}\n value={value}\n autoFocus={autoFocus}\n onChange={(e) => onValueChange(e.target.value)}\n data-testid={testId || testID}\n style={{\n flex: 1,\n minWidth: 0,\n boxSizing: \"border-box\",\n border: \"none\",\n outline: \"none\",\n background: \"transparent\",\n padding: 0,\n color: theme.colors.content.primary,\n fontSize: sizing.searchFontSize,\n lineHeight: `${sizing.searchLineHeight}px`,\n }}\n />\n </div>\n </div>\n );\n};\n\nconst DividerCell: React.FC<\n ContextMenuDividerItemProps & ThemeOverrideProps\n> = ({ themeMode, themeProductContext, \"data-testid\": testId }) => {\n const { theme } = useResolvedTheme({ themeMode, themeProductContext });\n const ctx = useContextMenu();\n const id = useId();\n const registerCell = ctx?.registerCell;\n const unregisterCell = ctx?.unregisterCell;\n useEffect(() => {\n if (!registerCell || !unregisterCell) return;\n registerCell(id, { type: \"divider\" });\n return () => unregisterCell(id);\n }, [registerCell, unregisterCell, id]);\n return (\n <div\n role=\"separator\"\n data-testid={testId}\n style={{\n height: 1,\n backgroundColor: theme.colors.border.secondary,\n margin: \"4px 0\",\n }}\n />\n );\n};\n","import { useEffect, useState, type RefObject } from \"react\";\nimport type { ContextMenuPlacement } from \"../types\";\n\ninterface UseContextMenuPositionOptions {\n triggerRef: RefObject<HTMLElement | null>;\n panelRef: RefObject<HTMLElement | null>;\n isOpen: boolean;\n placement?: ContextMenuPlacement;\n offset?: number;\n}\n\ninterface ResolvedPosition {\n top: number;\n left: number;\n placement: ContextMenuPlacement;\n}\n\nconst splitPlacement = (\n placement: ContextMenuPlacement\n): { vertical: \"top\" | \"bottom\"; horizontal: \"start\" | \"end\" } => {\n const [vertical, horizontal] = placement.split(\"-\") as [\n \"top\" | \"bottom\",\n \"start\" | \"end\",\n ];\n return { vertical, horizontal };\n};\n\nconst joinPlacement = (\n vertical: \"top\" | \"bottom\",\n horizontal: \"start\" | \"end\"\n): ContextMenuPlacement => `${vertical}-${horizontal}` as ContextMenuPlacement;\n\nexport const useContextMenuPosition = ({\n triggerRef,\n panelRef,\n isOpen,\n placement = \"bottom-start\",\n offset = 4,\n}: UseContextMenuPositionOptions): ResolvedPosition | undefined => {\n const [resolved, setResolved] = useState<ResolvedPosition | undefined>();\n\n useEffect(() => {\n if (!isOpen) {\n setResolved(undefined);\n return;\n }\n\n const compute = () => {\n const trigger = triggerRef.current;\n const panel = panelRef.current;\n if (!trigger || !panel) return;\n\n const triggerRect = trigger.getBoundingClientRect();\n const panelRect = panel.getBoundingClientRect();\n const viewportWidth = window.innerWidth;\n const viewportHeight = window.innerHeight;\n\n let { vertical, horizontal } = splitPlacement(placement);\n\n const computeTop = (v: \"top\" | \"bottom\") =>\n v === \"bottom\"\n ? triggerRect.bottom + offset\n : triggerRect.top - panelRect.height - offset;\n\n const computeLeft = (h: \"start\" | \"end\") =>\n h === \"start\" ? triggerRect.left : triggerRect.right - panelRect.width;\n\n let top = computeTop(vertical);\n const wantedBottom = top + panelRect.height;\n if (top < 0 || wantedBottom > viewportHeight) {\n const flipped = vertical === \"bottom\" ? \"top\" : \"bottom\";\n const flippedTop = computeTop(flipped);\n const flippedBottom = flippedTop + panelRect.height;\n if (flippedTop >= 0 && flippedBottom <= viewportHeight) {\n vertical = flipped;\n top = flippedTop;\n }\n }\n\n let left = computeLeft(horizontal);\n const wantedRight = left + panelRect.width;\n if (left < 0 || wantedRight > viewportWidth) {\n const flipped = horizontal === \"start\" ? \"end\" : \"start\";\n const flippedLeft = computeLeft(flipped);\n const flippedRight = flippedLeft + panelRect.width;\n if (flippedLeft >= 0 && flippedRight <= viewportWidth) {\n horizontal = flipped;\n left = flippedLeft;\n }\n }\n\n const next = {\n top,\n left,\n placement: joinPlacement(vertical, horizontal),\n };\n setResolved((prev) =>\n prev &&\n prev.top === next.top &&\n prev.left === next.left &&\n prev.placement === next.placement\n ? prev\n : next\n );\n };\n\n let rafId = window.requestAnimationFrame(compute);\n const onResize = () => compute();\n // Recompute on scroll so the panel follows its trigger. Capture phase\n // catches scrolling inside nested containers, not just the window.\n // rAF-coalesced: at most one compute per frame.\n let scrollRafPending = false;\n const onScroll = () => {\n if (scrollRafPending) return;\n scrollRafPending = true;\n rafId = window.requestAnimationFrame(() => {\n scrollRafPending = false;\n compute();\n });\n };\n window.addEventListener(\"resize\", onResize);\n window.addEventListener(\"scroll\", onScroll, true);\n\n return () => {\n window.cancelAnimationFrame(rafId);\n window.removeEventListener(\"resize\", onResize);\n window.removeEventListener(\"scroll\", onScroll, true);\n };\n }, [isOpen, placement, offset, triggerRef, panelRef]);\n\n return resolved;\n};\n","import { useCallback, type RefObject } from \"react\";\n\nexport type CellType = \"option\" | \"search\" | \"heading\" | \"divider\";\n\nexport interface CellMeta {\n type: CellType;\n onSelect?: () => void;\n disabled?: boolean;\n}\n\ninterface UseKeyboardNavigationOptions {\n isOpen: boolean;\n cells: Array<{ id: string; meta: CellMeta }>;\n activeIndex: number;\n setActiveIndex: (index: number) => void;\n onClose: () => void;\n triggerRef?: RefObject<HTMLElement | null>;\n}\n\nconst isNavigableOption = (meta: CellMeta) =>\n meta.type === \"option\" && !meta.disabled;\n\nconst isTextInputTarget = (target: EventTarget | null): boolean => {\n if (!(target instanceof HTMLElement)) return false;\n if (target.tagName === \"INPUT\" || target.tagName === \"TEXTAREA\") return true;\n return target.getAttribute(\"role\") === \"searchbox\";\n};\n\nexport const useKeyboardNavigation = ({\n isOpen,\n cells,\n activeIndex,\n setActiveIndex,\n onClose,\n triggerRef,\n}: UseKeyboardNavigationOptions) => {\n const findFirstOption = useCallback(() => {\n for (let i = 0; i < cells.length; i += 1) {\n if (isNavigableOption(cells[i].meta)) return i;\n }\n return -1;\n }, [cells]);\n\n const findLastOption = useCallback(() => {\n for (let i = cells.length - 1; i >= 0; i -= 1) {\n if (isNavigableOption(cells[i].meta)) return i;\n }\n return -1;\n }, [cells]);\n\n const findNextOption = useCallback(\n (from: number) => {\n const len = cells.length;\n if (len === 0) return -1;\n for (let step = 1; step <= len; step += 1) {\n const idx = (from + step + len) % len;\n if (isNavigableOption(cells[idx].meta)) return idx;\n }\n return -1;\n },\n [cells]\n );\n\n const findPrevOption = useCallback(\n (from: number) => {\n const len = cells.length;\n if (len === 0) return -1;\n for (let step = 1; step <= len; step += 1) {\n const idx = (from - step + len * 2) % len;\n if (isNavigableOption(cells[idx].meta)) return idx;\n }\n return -1;\n },\n [cells]\n );\n\n const handleKeyDown = useCallback(\n (event: React.KeyboardEvent) => {\n if (!isOpen) return;\n\n switch (event.key) {\n case \"ArrowDown\": {\n event.preventDefault();\n const next =\n activeIndex < 0 ? findFirstOption() : findNextOption(activeIndex);\n if (next >= 0) setActiveIndex(next);\n break;\n }\n case \"ArrowUp\": {\n event.preventDefault();\n const prev =\n activeIndex < 0 ? findLastOption() : findPrevOption(activeIndex);\n if (prev >= 0) setActiveIndex(prev);\n break;\n }\n case \"Home\": {\n event.preventDefault();\n const first = findFirstOption();\n if (first >= 0) setActiveIndex(first);\n break;\n }\n case \"End\": {\n event.preventDefault();\n const last = findLastOption();\n if (last >= 0) setActiveIndex(last);\n break;\n }\n case \"Enter\":\n case \" \": {\n if (event.defaultPrevented) break;\n if (event.key === \" \" && isTextInputTarget(event.target)) break;\n if (event.key === \" \") event.preventDefault();\n if (activeIndex >= 0 && activeIndex < cells.length) {\n const meta = cells[activeIndex].meta;\n if (isNavigableOption(meta)) meta.onSelect?.();\n }\n break;\n }\n case \"Escape\": {\n event.preventDefault();\n onClose();\n triggerRef?.current?.focus();\n break;\n }\n case \"Tab\": {\n onClose();\n break;\n }\n default:\n break;\n }\n },\n [\n isOpen,\n cells,\n activeIndex,\n setActiveIndex,\n onClose,\n triggerRef,\n findFirstOption,\n findLastOption,\n findNextOption,\n findPrevOption,\n ]\n );\n\n return { handleKeyDown };\n};\n"],"mappings":"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","names":["import_react","import_react_dom","import_xui_core","import_react","React","import_react","import_react","import_jsx_runtime","React","panel"]}
1
+ {"version":3,"sources":["../../src/index.tsx","../../src/ContextMenu.tsx","../../src/ContextMenuContext.tsx","../../src/ContextMenuItem.tsx","../../src/hooks/useContextMenuPosition.ts","../../src/hooks/useKeyboardNavigation.ts"],"sourcesContent":["export { ContextMenu } from \"./ContextMenu\";\nexport { ContextMenuItem } from \"./ContextMenuItem\";\n\nexport {\n useContextMenu,\n useContextMenuRequired,\n ContextMenuContext,\n} from \"./ContextMenuContext\";\n\nexport { useContextMenuPosition } from \"./hooks/useContextMenuPosition\";\nexport { useKeyboardNavigation } from \"./hooks/useKeyboardNavigation\";\n\nexport type {\n ContextMenuSize,\n ContextMenuItemType,\n ContextMenuItemLeadingControl,\n ContextMenuOptionItemProps,\n ContextMenuSearchItemProps,\n ContextMenuHeadingItemProps,\n ContextMenuDividerItemProps,\n ContextMenuItemProps,\n ContextMenuPanelType,\n ContextMenuPlacement,\n ContextMenuPosition,\n ContextMenuProps,\n ContextMenuContextValue,\n ContextMenuSizing,\n} from \"./types\";\n","import React, {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n isValidElement,\n cloneElement,\n type ReactElement,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport {\n useResolvedTheme,\n useId,\n type ThemeOverrideProps,\n} from \"@xsolla/xui-core\";\nimport { Spinner } from \"@xsolla/xui-spinner\";\nimport { ContextMenuContext } from \"./ContextMenuContext\";\nimport { ContextMenuItem } from \"./ContextMenuItem\";\nimport { useContextMenuPosition } from \"./hooks/useContextMenuPosition\";\nimport { useKeyboardNavigation } from \"./hooks/useKeyboardNavigation\";\nimport type {\n ContextMenuCellMeta,\n ContextMenuContextValue,\n ContextMenuDividerItemProps,\n ContextMenuHeadingItemProps,\n ContextMenuOptionItemProps,\n ContextMenuProps,\n ContextMenuSize,\n} from \"./types\";\n\ntype Props = ContextMenuProps & ThemeOverrideProps;\n\ninterface CellEntry {\n id: string;\n meta: ContextMenuCellMeta;\n}\n\ntype PresetItem =\n | ContextMenuOptionItemProps\n | ContextMenuHeadingItemProps\n | ContextMenuDividerItemProps;\n\nconst SEARCH_DEBOUNCE_MS = 200;\n\nconst EmptyMessage: React.FC<{ children: React.ReactNode; color: string }> = ({\n children,\n color,\n}) => (\n <div\n style={{\n padding: 12,\n color,\n fontSize: 14,\n textAlign: \"center\",\n }}\n >\n {children}\n </div>\n);\n\nexport const ContextMenu: React.FC<Props> = (props) => {\n const {\n type,\n items,\n children,\n size = \"md\",\n searchable,\n loading,\n emptyMessage,\n empty,\n trigger,\n isOpen,\n onOpenChange,\n closeOnSelect,\n width,\n maxHeight,\n placement = \"bottom-start\",\n onSelect,\n \"aria-label\": ariaLabel,\n \"data-testid\": testId,\n testID,\n themeMode,\n themeProductContext,\n } = props;\n\n const { theme } = useResolvedTheme({ themeMode, themeProductContext });\n\n const isControlled = isOpen !== undefined;\n const [internalOpen, setInternalOpen] = useState(false);\n const open = isControlled ? !!isOpen : internalOpen;\n\n const setOpen = useCallback(\n (next: boolean) => {\n if (!isControlled) setInternalOpen(next);\n onOpenChange?.(next);\n },\n [isControlled, onOpenChange]\n );\n\n const [activeIndex, setActiveIndex] = useState<number>(-1);\n const cellsRef = useRef<CellEntry[]>([]);\n const [cellsVersion, setCellsVersion] = useState(0);\n\n const triggerRef = useRef<HTMLElement | null>(null);\n const panelRef = useRef<HTMLDivElement | null>(null);\n const menuId = useId();\n\n // Search query state\n const [query, setQuery] = useState(\"\");\n const [debouncedQuery, setDebouncedQuery] = useState(\"\");\n const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(() => {\n if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current);\n debounceTimerRef.current = setTimeout(() => {\n setDebouncedQuery(query);\n }, SEARCH_DEBOUNCE_MS);\n return () => {\n if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current);\n };\n }, [query]);\n\n const closeMenu = useCallback(() => {\n setOpen(false);\n setActiveIndex(-1);\n }, [setOpen]);\n\n const registerCell = useCallback((id: string, meta: ContextMenuCellMeta) => {\n const existing = cellsRef.current.findIndex((c) => c.id === id);\n if (existing === -1) {\n cellsRef.current.push({ id, meta });\n setCellsVersion((v) => v + 1);\n return cellsRef.current.length - 1;\n }\n // Update metadata in place — keep registry order stable. Only bump the\n // version when nav-relevant metadata (disabled) actually changes.\n const prev = cellsRef.current[existing].meta;\n cellsRef.current[existing] = { id, meta };\n if (prev.disabled !== meta.disabled || prev.type !== meta.type) {\n setCellsVersion((v) => v + 1);\n }\n return existing;\n }, []);\n\n const unregisterCell = useCallback((id: string) => {\n const idx = cellsRef.current.findIndex((c) => c.id === id);\n if (idx !== -1) {\n // splice keeps survivors contiguous; consumers derive their index live\n // via getCellIndex so they reindex automatically.\n cellsRef.current.splice(idx, 1);\n setCellsVersion((v) => v + 1);\n }\n }, []);\n\n const getCellIndex = useCallback(\n (id: string) => cellsRef.current.findIndex((c) => c.id === id),\n []\n );\n\n const ctx: ContextMenuContextValue = useMemo(\n () => ({\n size: size as ContextMenuSize,\n menuId,\n closeMenu,\n registerCell,\n unregisterCell,\n getCellIndex,\n cellsVersion,\n activeIndex,\n setActiveIndex,\n query,\n setQuery,\n }),\n [\n size,\n menuId,\n closeMenu,\n registerCell,\n unregisterCell,\n getCellIndex,\n cellsVersion,\n activeIndex,\n query,\n ]\n );\n\n const triggerNode = useMemo(() => {\n if (!trigger) return null;\n const inner = isValidElement(trigger)\n ? cloneElement(\n trigger as ReactElement<{\n \"aria-haspopup\"?: string;\n \"aria-expanded\"?: string | boolean;\n }>,\n {\n \"aria-haspopup\": \"menu\",\n \"aria-expanded\": open ? \"true\" : \"false\",\n }\n )\n : trigger;\n return (\n <span\n ref={(node) => {\n if (!node) {\n triggerRef.current = null;\n return;\n }\n const focusable = node.querySelector<HTMLElement>(\n \"button, [href], input, select, textarea, [tabindex]:not([tabindex='-1'])\"\n );\n triggerRef.current = focusable ?? node;\n }}\n onClick={() => setOpen(!open)}\n style={{ display: \"inline-flex\" }}\n >\n {inner}\n </span>\n );\n }, [trigger, open, setOpen]);\n\n const usePortal = !!trigger && typeof document !== \"undefined\";\n\n const position = useContextMenuPosition({\n triggerRef,\n panelRef,\n isOpen: open && usePortal,\n placement,\n });\n\n const cellsForNav = useMemo(\n () => cellsRef.current.map((c) => ({ id: c.id, meta: c.meta })),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [cellsVersion]\n );\n\n const { handleKeyDown } = useKeyboardNavigation({\n isOpen: open,\n cells: cellsForNav,\n activeIndex,\n setActiveIndex,\n onClose: closeMenu,\n triggerRef,\n });\n\n const sizingFn = (theme.sizing as { contextMenu?: (s: string) => unknown })\n .contextMenu;\n const sizing = sizingFn\n ? (sizingFn(size) as {\n panelWidth?: number;\n borderRadius?: number;\n paddingVertical?: number;\n })\n : {};\n const radiusObj = (theme as { radius?: { contextMenu?: number } }).radius;\n const radiusVal = sizing.borderRadius ?? radiusObj?.contextMenu ?? 8;\n const shadowObj = (theme as { shadow?: { contextMenu?: string } }).shadow;\n const shadowVal = shadowObj?.contextMenu ?? \"\";\n const panelPaddingVertical = sizing.paddingVertical ?? 8;\n\n const panelStyle: React.CSSProperties = {\n background: theme.colors.background.primary,\n borderRadius: radiusVal,\n boxShadow: shadowVal,\n width: width ?? sizing.panelWidth,\n maxHeight,\n overflow: \"hidden\",\n display: open ? \"flex\" : \"none\",\n flexDirection: \"column\",\n outline: \"none\",\n fontFamily: theme.fonts.body,\n paddingTop: panelPaddingVertical,\n paddingBottom: panelPaddingVertical,\n };\n\n if (usePortal) {\n panelStyle.position = \"fixed\";\n panelStyle.top = position?.top ?? 0;\n panelStyle.left = position?.left ?? 0;\n }\n\n // Filter preset items based on debounced query (only when searchable + items)\n const filteredItems = useMemo<PresetItem[] | undefined>(() => {\n if (!items) return undefined;\n if (!searchable || !debouncedQuery) return items.slice();\n const q = debouncedQuery.toLowerCase();\n const matchedFlags = items.map((item) => {\n if (item.type === \"option\") {\n return String(item.label ?? \"\")\n .toLowerCase()\n .includes(q);\n }\n return false; // headings/dividers themselves never match\n });\n // Now hide headings whose options are all filtered out and trailing dividers\n const result: PresetItem[] = [];\n let pendingHeading: {\n item: ContextMenuHeadingItemProps;\n idx: number;\n } | null = null;\n let lastEmittedWasContent = false; // whether last pushed was option (so divider can follow)\n let groupHasOption = false;\n for (let i = 0; i < items.length; i += 1) {\n const item = items[i];\n if (item.type === \"heading\") {\n // Starting a new group\n pendingHeading = { item, idx: i };\n groupHasOption = false;\n } else if (item.type === \"divider\") {\n // Emit divider only if the previously emitted thing was content\n if (lastEmittedWasContent) {\n result.push(item);\n lastEmittedWasContent = false;\n }\n // dividers also reset pending heading so a heading sits with its group\n pendingHeading = null;\n groupHasOption = false;\n } else if (item.type === \"option\") {\n if (matchedFlags[i]) {\n if (pendingHeading) {\n result.push(pendingHeading.item);\n pendingHeading = null;\n }\n result.push(item);\n lastEmittedWasContent = true;\n groupHasOption = true;\n }\n }\n }\n // Strip trailing divider if any\n while (result.length > 0 && result[result.length - 1].type === \"divider\") {\n result.pop();\n }\n // Suppress unused\n void groupHasOption;\n return result;\n }, [items, searchable, debouncedQuery]);\n\n const effectiveCloseOnSelect =\n closeOnSelect !== undefined ? closeOnSelect : type !== \"checkbox\";\n\n const renderPresetItem = (item: PresetItem, key: number) => {\n if (item.type === \"heading\") {\n return (\n <ContextMenuItem\n key={`h-${key}`}\n {...item}\n size={item.size ?? (size as ContextMenuSize)}\n />\n );\n }\n if (item.type === \"divider\") {\n return (\n <ContextMenuItem\n key={`d-${key}`}\n {...item}\n size={item.size ?? (size as ContextMenuSize)}\n />\n );\n }\n const composed = composeItemForPreset(type, item);\n const originalSelect = composed.onSelect;\n const wrappedSelect = () => {\n originalSelect?.();\n onSelect?.(item);\n if (effectiveCloseOnSelect) closeMenu();\n };\n return (\n <ContextMenuItem\n key={`o-${key}`}\n {...composed}\n size={composed.size ?? (size as ContextMenuSize)}\n onSelect={wrappedSelect}\n />\n );\n };\n\n // Determine what to render in the panel body\n const isLoadingState = loading;\n\n let bodyContent: React.ReactNode = null;\n let isBodyEmpty = false;\n let searchNode: React.ReactNode = null;\n\n if (isLoadingState) {\n bodyContent = (\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n padding: 16,\n }}\n >\n <Spinner size={size === \"xl\" ? \"lg\" : size === \"lg\" ? \"md\" : \"sm\"} />\n </div>\n );\n } else if (children !== undefined && children !== null) {\n // Custom path. Empty when children is falsy/empty array.\n const childArr = React.Children.toArray(children);\n if (childArr.length === 0) {\n isBodyEmpty = true;\n } else {\n bodyContent = children;\n }\n } else if (type && items) {\n // Preset path\n if (searchable) {\n searchNode = (\n <ContextMenuItem\n type=\"search\"\n value={query}\n onValueChange={setQuery}\n size={size as ContextMenuSize}\n />\n );\n }\n const visible = filteredItems ?? [];\n const optionCount = visible.filter((i) => i.type === \"option\").length;\n if (optionCount === 0) {\n isBodyEmpty = true;\n } else {\n bodyContent = visible.map((it, idx) => renderPresetItem(it, idx));\n }\n } else {\n // No children, no items — empty\n isBodyEmpty = true;\n }\n\n if (isBodyEmpty) {\n bodyContent = empty ?? (\n <EmptyMessage color={theme.colors.content.tertiary}>\n {emptyMessage ?? \"No results\"}\n </EmptyMessage>\n );\n }\n\n const hasStickySearch = !!searchNode;\n\n // Focus management on open/close transitions\n const prevOpenRef = useRef(false);\n useEffect(() => {\n const wasOpen = prevOpenRef.current;\n prevOpenRef.current = open;\n if (!wasOpen && open) {\n // open transition: focus search if present, else first option\n // wait for cells to register\n const timer = setTimeout(() => {\n const panel = panelRef.current;\n if (!panel) return;\n const search =\n panel.querySelector<HTMLInputElement>(\"[role='searchbox']\");\n if (search) {\n search.focus();\n return;\n }\n const firstOption = panel.querySelector<HTMLElement>(\n \"[role='menuitem'], [role='menuitemcheckbox'], [role='menuitemradio']\"\n );\n if (firstOption) {\n firstOption.focus();\n // Reset activeIndex so keyboard nav starts fresh; onFocus will have\n // set it to the focused option's index, but tests expect ArrowDown\n // to move to the first option from -1.\n setActiveIndex(-1);\n } else {\n panel.focus();\n }\n }, 0);\n return () => clearTimeout(timer);\n }\n if (wasOpen && !open) {\n // close transition: focus the trigger\n triggerRef.current?.focus();\n }\n }, [open]);\n\n // Dismiss on outside press. Per-instance + DOM-guarded so multiple open\n // menus each close only themselves, native builds no-op, and React <18 is\n // unaffected (plain document listener, no concurrent APIs). Only for\n // portal-anchored (trigger) menus; inline/controlled menus stay\n // consumer-managed.\n useEffect(() => {\n if (!open || !usePortal || typeof document === \"undefined\") return;\n const handlePointerDown = (event: MouseEvent) => {\n const target = event.target as Node | null;\n if (!target) return;\n if (panelRef.current?.contains(target)) return;\n if (triggerRef.current?.contains(target)) return;\n // This menu's submenus render in a separate DOM subtree (document.body);\n // treat clicks inside them as inside this menu.\n if (target instanceof Element) {\n const portals = document.querySelectorAll(\n \"[data-xui-context-menu-portal]\"\n );\n for (let i = 0; i < portals.length; i += 1) {\n const portal = portals[i];\n if (\n portal.getAttribute(\"data-xui-context-menu-portal\") === menuId &&\n portal.contains(target)\n ) {\n return;\n }\n }\n }\n closeMenu();\n };\n document.addEventListener(\"mousedown\", handlePointerDown);\n return () => document.removeEventListener(\"mousedown\", handlePointerDown);\n }, [open, usePortal, closeMenu, menuId]);\n\n const resolvedPlacement = position?.placement ?? placement;\n\n const scrollContainerStyle: React.CSSProperties = {\n overflowY: \"auto\",\n flex: 1,\n minHeight: 0,\n };\n\n const stickyHeaderStyle: React.CSSProperties = {\n position: \"sticky\",\n top: 0,\n zIndex: 1,\n background: theme.colors.background.primary,\n };\n\n const panel = open ? (\n <ContextMenuContext.Provider value={ctx}>\n <div\n ref={panelRef}\n role=\"menu\"\n aria-label={ariaLabel}\n data-testid={testId || testID}\n data-placement={usePortal ? resolvedPlacement : undefined}\n tabIndex={-1}\n onKeyDown={handleKeyDown}\n onMouseLeave={() => setActiveIndex(-1)}\n style={panelStyle}\n >\n {hasStickySearch && (\n <div data-sticky=\"top\" style={stickyHeaderStyle}>\n {searchNode}\n </div>\n )}\n <div style={scrollContainerStyle}>{bodyContent}</div>\n </div>\n </ContextMenuContext.Provider>\n ) : null;\n\n return (\n <>\n {triggerNode}\n {usePortal ? panel && createPortal(panel, document.body) : panel}\n </>\n );\n};\n\nContextMenu.displayName = \"ContextMenu\";\n\nfunction composeItemForPreset(\n type: ContextMenuProps[\"type\"],\n item: ContextMenuOptionItemProps\n): ContextMenuOptionItemProps {\n switch (type) {\n case \"checkbox\":\n return { ...item, leadingControl: \"checkbox\" };\n case \"radio\":\n return { ...item, leadingControl: \"radio\" };\n case \"list\":\n case \"phone\":\n case \"status\":\n case \"brandLogo\":\n case \"avatar\":\n default:\n return { ...item };\n }\n}\n","import { createContext, useContext } from \"react\";\nimport type { ContextMenuContextValue } from \"./types\";\n\nexport const ContextMenuContext = createContext<\n ContextMenuContextValue | undefined\n>(undefined);\n\nexport const useContextMenu = () => {\n const context = useContext(ContextMenuContext);\n return context;\n};\n\nexport const useContextMenuRequired = () => {\n const context = useContext(ContextMenuContext);\n if (!context) {\n throw new Error(\n \"useContextMenuRequired must be used within a ContextMenu component\"\n );\n }\n return context;\n};\n","import React, { useEffect, useLayoutEffect, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport {\n useResolvedTheme,\n useId,\n type ThemeOverrideProps,\n} from \"@xsolla/xui-core\";\nimport { Typography } from \"@xsolla/xui-typography\";\nimport { Checkbox } from \"@xsolla/xui-checkbox\";\nimport { Radio } from \"@xsolla/xui-radio\";\nimport { Search } from \"@xsolla/xui-icons-base\";\nimport { useContextMenu } from \"./ContextMenuContext\";\nimport type {\n ContextMenuDividerItemProps,\n ContextMenuHeadingItemProps,\n ContextMenuItemProps,\n ContextMenuOptionItemProps,\n ContextMenuSearchItemProps,\n ContextMenuSize,\n} from \"./types\";\n\ntype BodyVariant = \"bodyLg\" | \"bodyMd\" | \"bodySm\" | \"bodyXs\";\ntype AccentVariant =\n | \"bodyLgAccent\"\n | \"bodyMdAccent\"\n | \"bodySmAccent\"\n | \"bodyXsAccent\";\n\nconst sizeToVariants: Record<\n ContextMenuSize,\n { label: BodyVariant; description: BodyVariant; headingAccent: AccentVariant }\n> = {\n xl: { label: \"bodyLg\", description: \"bodyLg\", headingAccent: \"bodyLgAccent\" },\n lg: { label: \"bodyLg\", description: \"bodyMd\", headingAccent: \"bodyMdAccent\" },\n md: { label: \"bodyMd\", description: \"bodySm\", headingAccent: \"bodySmAccent\" },\n sm: { label: \"bodySm\", description: \"bodyXs\", headingAccent: \"bodyXsAccent\" },\n};\n\nconst sizeLabelOverride: Partial<\n Record<ContextMenuSize, { fontSize: number; lineHeight: string }>\n> = {\n xl: { fontSize: 20, lineHeight: \"26px\" },\n};\n\ntype Props = ContextMenuItemProps & ThemeOverrideProps;\n\nexport const ContextMenuItem: React.FC<Props> = (props) => {\n if (props.type === \"option\") return <OptionCell {...props} />;\n if (props.type === \"heading\") return <HeadingCell {...props} />;\n if (props.type === \"divider\") return <DividerCell {...props} />;\n if (props.type === \"search\") return <SearchCell {...props} />;\n return null;\n};\n\nContextMenuItem.displayName = \"ContextMenuItem\";\n\nconst SubmenuChevron: React.FC<{ color: string; size: number }> = ({\n color,\n size,\n}) => (\n <span\n data-testid=\"ctxmenu-submenu-chevron\"\n aria-hidden=\"true\"\n style={{\n color,\n display: \"inline-flex\",\n alignItems: \"center\",\n width: size,\n height: size,\n }}\n >\n <svg\n width={size}\n height={size}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M17.0605 11.6464C17.2558 11.8417 17.2558 12.1583 17.0605 12.3536L9.70703 19.707L8.29297 18.293L14.5859 12L8.29297 5.70703L9.70703 4.29297L17.0605 11.6464Z\"\n fill=\"currentColor\"\n />\n </svg>\n </span>\n);\n\nconst OptionCell: React.FC<ContextMenuOptionItemProps & ThemeOverrideProps> = ({\n size: propSize,\n label,\n description,\n disabled,\n destructive,\n checked,\n leadingControl,\n leadingIcon,\n status,\n iconWrapper,\n slotContent,\n value,\n hint,\n trailingIcon,\n keyboardShortcut,\n hasSubmenu,\n submenu,\n onSelect,\n testID,\n themeMode,\n themeProductContext,\n \"data-testid\": testId,\n}) => {\n const { theme } = useResolvedTheme({ themeMode, themeProductContext });\n const ctx = useContextMenu();\n const size: ContextMenuSize = propSize ?? ctx?.size ?? \"md\";\n const sizing = theme.sizing.contextMenu(size);\n const variants = sizeToVariants[size];\n\n const id = useId();\n const registerCell = ctx?.registerCell;\n const unregisterCell = ctx?.unregisterCell;\n const getCellIndex = ctx?.getCellIndex;\n const [isHovered, setIsHovered] = useState(false);\n const [submenuOpen, setSubmenuOpen] = useState(false);\n const [submenuPos, setSubmenuPos] = useState<{\n top: number;\n left: number;\n } | null>(null);\n const optionRef = React.useRef<HTMLDivElement | null>(null);\n const submenuWrapperRef = React.useRef<HTMLDivElement | null>(null);\n const closeTimerRef = React.useRef<ReturnType<typeof setTimeout> | null>(\n null\n );\n\n const cancelClose = () => {\n if (closeTimerRef.current) {\n clearTimeout(closeTimerRef.current);\n closeTimerRef.current = null;\n }\n };\n\n const scheduleClose = () => {\n cancelClose();\n closeTimerRef.current = setTimeout(() => setSubmenuOpen(false), 120);\n };\n\n useEffect(() => () => cancelClose(), []);\n\n useEffect(() => {\n if (!hasSubmenu || !submenuOpen) return;\n const onMouseDown = (e: MouseEvent) => {\n const target = e.target as HTMLElement | null;\n if (!target) return;\n const inOption = optionRef.current?.contains(target);\n const inSubmenu = submenuWrapperRef.current?.contains(target);\n if (!inOption && !inSubmenu) {\n setSubmenuOpen(false);\n return;\n }\n if (\n inSubmenu &&\n target.closest(\n '[role=\"menuitem\"],[role=\"menuitemcheckbox\"],[role=\"menuitemradio\"]'\n )\n ) {\n setSubmenuOpen(false);\n }\n };\n document.addEventListener(\"mousedown\", onMouseDown);\n return () => document.removeEventListener(\"mousedown\", onMouseDown);\n }, [hasSubmenu, submenuOpen]);\n\n useLayoutEffect(() => {\n if (!hasSubmenu || !submenuOpen) {\n setSubmenuPos(null);\n return;\n }\n const update = () => {\n const node = optionRef.current;\n if (!node) return;\n const rect = node.getBoundingClientRect();\n setSubmenuPos({ top: rect.top, left: rect.right });\n };\n update();\n window.addEventListener(\"scroll\", update, true);\n window.addEventListener(\"resize\", update);\n return () => {\n window.removeEventListener(\"scroll\", update, true);\n window.removeEventListener(\"resize\", update);\n };\n }, [hasSubmenu, submenuOpen]);\n const onSelectRef = React.useRef(onSelect);\n onSelectRef.current = onSelect;\n // Register on mount / unregister on unmount. Deps are the stable registry\n // callbacks + id only — NOT `ctx`, whose identity changes on every hover\n // (activeIndex). Depending on `ctx` here churned register/unregister and, on\n // React <18 (interleaved passive-effect flush), collapsed every row to the\n // same index so one hover highlighted all rows (FEP-764).\n useEffect(() => {\n if (!registerCell || !unregisterCell) return;\n registerCell(id, {\n type: \"option\",\n onSelect: () => onSelectRef.current?.(),\n });\n return () => unregisterCell(id);\n }, [registerCell, unregisterCell, id]);\n\n // Keep registry metadata (disabled) in sync without reordering the cell.\n useEffect(() => {\n if (!registerCell) return;\n registerCell(id, {\n type: \"option\",\n disabled,\n onSelect: () => onSelectRef.current?.(),\n });\n }, [registerCell, id, disabled]);\n\n // Derive index live from the registry instead of caching it in state, so it\n // is always correct after dynamic add/remove without per-item re-registration.\n const index = getCellIndex ? getCellIndex(id) : -1;\n const isActive = ctx ? index >= 0 && ctx.activeIndex === index : false;\n const inHoverState =\n isActive || (!ctx && isHovered) || (hasSubmenu && submenuOpen);\n\n const handleEnter = () => {\n if (disabled) return;\n if (ctx && index >= 0) ctx.setActiveIndex(index);\n if (!ctx) setIsHovered(true);\n if (hasSubmenu) setSubmenuOpen(true);\n };\n\n const handleLeave = () => {\n if (!ctx) setIsHovered(false);\n if (hasSubmenu) scheduleClose();\n };\n\n const labelColor = disabled\n ? theme.colors.control.input.textDisable\n : destructive\n ? theme.colors.content.alert.primary\n : theme.colors.content.primary;\n\n const bg = inHoverState ? theme.colors.control.input.bgHover : \"transparent\";\n\n const role =\n !hasSubmenu && checked !== undefined ? \"menuitemcheckbox\" : \"menuitem\";\n const ariaChecked =\n !hasSubmenu && checked !== undefined\n ? checked\n ? \"true\"\n : \"false\"\n : undefined;\n\n const handleClick = () => {\n if (disabled) return;\n if (hasSubmenu) {\n setSubmenuOpen(true);\n return;\n }\n onSelect?.();\n };\n\n const closeSubmenuAndFocus = () => {\n setSubmenuOpen(false);\n optionRef.current?.focus();\n };\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (disabled) {\n if (e.key === \"Enter\" || e.key === \" \") e.preventDefault();\n return;\n }\n if (hasSubmenu) {\n if (e.key === \"ArrowRight\" || e.key === \"Enter\") {\n e.preventDefault();\n e.stopPropagation();\n setSubmenuOpen(true);\n return;\n }\n if (e.key === \"ArrowLeft\" || e.key === \"Escape\") {\n if (submenuOpen) {\n e.preventDefault();\n e.stopPropagation();\n closeSubmenuAndFocus();\n return;\n }\n }\n }\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onSelect?.();\n }\n };\n\n return (\n <div\n ref={optionRef}\n role={role}\n aria-checked={ariaChecked}\n aria-disabled={disabled ? \"true\" : undefined}\n aria-haspopup={hasSubmenu ? \"menu\" : undefined}\n aria-expanded={hasSubmenu ? (submenuOpen ? \"true\" : \"false\") : undefined}\n tabIndex={0}\n data-testid={testId || testID}\n data-state={inHoverState ? \"hover\" : undefined}\n data-destructive={destructive ? \"true\" : undefined}\n aria-keyshortcuts={keyboardShortcut}\n onMouseEnter={() => {\n cancelClose();\n handleEnter();\n }}\n onMouseLeave={handleLeave}\n onFocus={handleEnter}\n onBlur={() => {\n if (!ctx) setIsHovered(false);\n }}\n onClick={handleClick}\n onKeyDown={handleKeyDown}\n style={{\n position: \"relative\",\n display: \"flex\",\n flexDirection: \"row\",\n alignItems: \"center\",\n gap: sizing.gap,\n paddingLeft: sizing.itemPaddingHorizontal,\n paddingRight: sizing.itemPaddingHorizontal,\n paddingTop: sizing.itemPaddingVertical,\n paddingBottom: sizing.itemPaddingVertical,\n backgroundColor: bg,\n cursor: disabled ? \"not-allowed\" : \"pointer\",\n outline: \"none\",\n }}\n >\n {leadingControl === \"checkbox\" && (\n <span\n data-testid=\"ctxmenu-leading-checkbox\"\n aria-hidden=\"true\"\n style={{ pointerEvents: \"none\", display: \"inline-flex\" }}\n >\n <Checkbox\n size={size}\n checked={!!checked}\n disabled={!!disabled}\n themeMode={themeMode}\n themeProductContext={themeProductContext}\n />\n </span>\n )}\n {leadingControl === \"radio\" && (\n <span\n data-testid=\"ctxmenu-leading-radio\"\n aria-hidden=\"true\"\n style={{ pointerEvents: \"none\", display: \"inline-flex\" }}\n >\n <Radio\n size={size}\n checked={!!checked}\n disabled={!!disabled}\n themeMode={themeMode}\n themeProductContext={themeProductContext}\n />\n </span>\n )}\n {leadingIcon}\n {status}\n {iconWrapper}\n {slotContent}\n <span\n style={{\n flex: 1,\n display: \"flex\",\n flexDirection: \"column\",\n gap: 2,\n minWidth: 0,\n }}\n >\n <Typography\n variant={variants.label}\n color={labelColor}\n noWrap={description === undefined}\n style={{\n ...(description === undefined\n ? { display: \"block\", minWidth: 0 }\n : {}),\n ...(sizeLabelOverride[size] ?? {}),\n }}\n >\n {label}\n </Typography>\n {description !== undefined && (\n <Typography\n variant={variants.description}\n color={theme.colors.content.tertiary}\n >\n {description}\n </Typography>\n )}\n </span>\n {(value !== undefined || hint !== undefined) && (\n <span\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"flex-end\",\n }}\n >\n {value !== undefined && (\n <Typography\n variant={variants.label}\n color={theme.colors.content.secondary}\n style={sizeLabelOverride[size]}\n >\n {value}\n </Typography>\n )}\n {hint !== undefined && (\n <Typography\n variant={variants.description}\n color={theme.colors.content.tertiary}\n >\n {hint}\n </Typography>\n )}\n </span>\n )}\n {keyboardShortcut && (\n <Typography\n as=\"kbd\"\n variant={variants.description}\n color={theme.colors.content.tertiary}\n >\n {keyboardShortcut}\n </Typography>\n )}\n {hasSubmenu && (\n <SubmenuChevron\n color={theme.colors.content.tertiary}\n size={sizing.iconSize}\n />\n )}\n {trailingIcon}\n {hasSubmenu &&\n submenuOpen &&\n submenu &&\n submenuPos &&\n typeof document !== \"undefined\" &&\n createPortal(\n <div\n ref={submenuWrapperRef}\n data-xui-context-menu-portal={ctx?.menuId}\n onMouseEnter={cancelClose}\n onMouseLeave={scheduleClose}\n style={{\n position: \"fixed\",\n top: submenuPos.top,\n left: submenuPos.left,\n zIndex: 2000,\n }}\n >\n {submenu}\n </div>,\n document.body\n )}\n </div>\n );\n};\n\nconst HeadingCell: React.FC<\n ContextMenuHeadingItemProps & ThemeOverrideProps\n> = ({\n size: propSize,\n label,\n description,\n testID,\n themeMode,\n themeProductContext,\n \"data-testid\": testId,\n}) => {\n const { theme } = useResolvedTheme({ themeMode, themeProductContext });\n const ctx = useContextMenu();\n const size: ContextMenuSize = propSize ?? ctx?.size ?? \"md\";\n const sizing = theme.sizing.contextMenu(size);\n const variants = sizeToVariants[size];\n\n const id = useId();\n const registerCell = ctx?.registerCell;\n const unregisterCell = ctx?.unregisterCell;\n useEffect(() => {\n if (!registerCell || !unregisterCell) return;\n registerCell(id, { type: \"heading\" });\n return () => unregisterCell(id);\n }, [registerCell, unregisterCell, id]);\n\n return (\n <div\n role=\"presentation\"\n data-testid={testId || testID}\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: 2,\n paddingLeft: sizing.itemPaddingHorizontal,\n paddingRight: sizing.itemPaddingHorizontal,\n paddingTop: sizing.itemPaddingVertical,\n paddingBottom: sizing.itemPaddingVertical,\n }}\n >\n <Typography\n variant={variants.headingAccent}\n color={theme.colors.content.secondary}\n style={{ textTransform: \"uppercase\", letterSpacing: 0.5 }}\n >\n {label}\n </Typography>\n {description !== undefined && (\n <Typography\n variant={variants.description}\n color={theme.colors.content.tertiary}\n >\n {description}\n </Typography>\n )}\n </div>\n );\n};\n\nconst SearchCell: React.FC<ContextMenuSearchItemProps & ThemeOverrideProps> = ({\n size: propSize,\n value,\n onValueChange,\n placeholder = \"Search\",\n autoFocus,\n \"aria-label\": ariaLabel = \"Search options\",\n \"data-testid\": testId,\n testID,\n themeMode,\n themeProductContext,\n}) => {\n const { theme } = useResolvedTheme({ themeMode, themeProductContext });\n const ctx = useContextMenu();\n const size: ContextMenuSize = propSize ?? ctx?.size ?? \"md\";\n const sizing = theme.sizing.contextMenu(size);\n\n const id = useId();\n const registerCell = ctx?.registerCell;\n const unregisterCell = ctx?.unregisterCell;\n useEffect(() => {\n if (!registerCell || !unregisterCell) return;\n registerCell(id, { type: \"search\" });\n return () => unregisterCell(id);\n }, [registerCell, unregisterCell, id]);\n\n return (\n <div\n style={{\n boxSizing: \"border-box\",\n paddingLeft: sizing.searchPaddingHorizontal,\n paddingRight: sizing.searchPaddingHorizontal,\n paddingTop: sizing.searchPaddingVertical,\n paddingBottom: sizing.searchPaddingVertical,\n }}\n >\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: 8,\n paddingBottom: 6,\n borderBottom: `1px solid ${theme.colors.border.secondary}`,\n }}\n >\n <Search\n variant=\"line\"\n size={sizing.searchIconSize}\n color={theme.colors.control.input.placeholder}\n aria-hidden\n />\n <input\n type=\"search\"\n role=\"searchbox\"\n aria-label={ariaLabel}\n placeholder={placeholder}\n value={value}\n autoFocus={autoFocus}\n onChange={(e) => onValueChange(e.target.value)}\n data-testid={testId || testID}\n style={{\n flex: 1,\n minWidth: 0,\n boxSizing: \"border-box\",\n border: \"none\",\n outline: \"none\",\n background: \"transparent\",\n padding: 0,\n color: theme.colors.content.primary,\n fontSize: sizing.searchFontSize,\n lineHeight: `${sizing.searchLineHeight}px`,\n }}\n />\n </div>\n </div>\n );\n};\n\nconst DividerCell: React.FC<\n ContextMenuDividerItemProps & ThemeOverrideProps\n> = ({ themeMode, themeProductContext, \"data-testid\": testId }) => {\n const { theme } = useResolvedTheme({ themeMode, themeProductContext });\n const ctx = useContextMenu();\n const id = useId();\n const registerCell = ctx?.registerCell;\n const unregisterCell = ctx?.unregisterCell;\n useEffect(() => {\n if (!registerCell || !unregisterCell) return;\n registerCell(id, { type: \"divider\" });\n return () => unregisterCell(id);\n }, [registerCell, unregisterCell, id]);\n return (\n <div\n role=\"separator\"\n data-testid={testId}\n style={{\n height: 1,\n backgroundColor: theme.colors.border.secondary,\n margin: \"4px 0\",\n }}\n />\n );\n};\n","import { useEffect, useState, type RefObject } from \"react\";\nimport type { ContextMenuPlacement } from \"../types\";\n\ninterface UseContextMenuPositionOptions {\n triggerRef: RefObject<HTMLElement | null>;\n panelRef: RefObject<HTMLElement | null>;\n isOpen: boolean;\n placement?: ContextMenuPlacement;\n offset?: number;\n}\n\ninterface ResolvedPosition {\n top: number;\n left: number;\n placement: ContextMenuPlacement;\n}\n\nconst splitPlacement = (\n placement: ContextMenuPlacement\n): { vertical: \"top\" | \"bottom\"; horizontal: \"start\" | \"end\" } => {\n const [vertical, horizontal] = placement.split(\"-\") as [\n \"top\" | \"bottom\",\n \"start\" | \"end\",\n ];\n return { vertical, horizontal };\n};\n\nconst joinPlacement = (\n vertical: \"top\" | \"bottom\",\n horizontal: \"start\" | \"end\"\n): ContextMenuPlacement => `${vertical}-${horizontal}` as ContextMenuPlacement;\n\nexport const useContextMenuPosition = ({\n triggerRef,\n panelRef,\n isOpen,\n placement = \"bottom-start\",\n offset = 4,\n}: UseContextMenuPositionOptions): ResolvedPosition | undefined => {\n const [resolved, setResolved] = useState<ResolvedPosition | undefined>();\n\n useEffect(() => {\n if (!isOpen) {\n setResolved(undefined);\n return;\n }\n\n const compute = () => {\n const trigger = triggerRef.current;\n const panel = panelRef.current;\n if (!trigger || !panel) return;\n\n const triggerRect = trigger.getBoundingClientRect();\n const panelRect = panel.getBoundingClientRect();\n const viewportWidth = window.innerWidth;\n const viewportHeight = window.innerHeight;\n\n let { vertical, horizontal } = splitPlacement(placement);\n\n const computeTop = (v: \"top\" | \"bottom\") =>\n v === \"bottom\"\n ? triggerRect.bottom + offset\n : triggerRect.top - panelRect.height - offset;\n\n const computeLeft = (h: \"start\" | \"end\") =>\n h === \"start\" ? triggerRect.left : triggerRect.right - panelRect.width;\n\n let top = computeTop(vertical);\n const wantedBottom = top + panelRect.height;\n if (top < 0 || wantedBottom > viewportHeight) {\n const flipped = vertical === \"bottom\" ? \"top\" : \"bottom\";\n const flippedTop = computeTop(flipped);\n const flippedBottom = flippedTop + panelRect.height;\n if (flippedTop >= 0 && flippedBottom <= viewportHeight) {\n vertical = flipped;\n top = flippedTop;\n }\n }\n\n let left = computeLeft(horizontal);\n const wantedRight = left + panelRect.width;\n if (left < 0 || wantedRight > viewportWidth) {\n const flipped = horizontal === \"start\" ? \"end\" : \"start\";\n const flippedLeft = computeLeft(flipped);\n const flippedRight = flippedLeft + panelRect.width;\n if (flippedLeft >= 0 && flippedRight <= viewportWidth) {\n horizontal = flipped;\n left = flippedLeft;\n }\n }\n\n setResolved({\n top,\n left,\n placement: joinPlacement(vertical, horizontal),\n });\n };\n\n const rafId = window.requestAnimationFrame(compute);\n const onResize = () => compute();\n window.addEventListener(\"resize\", onResize);\n\n return () => {\n window.cancelAnimationFrame(rafId);\n window.removeEventListener(\"resize\", onResize);\n };\n }, [isOpen, placement, offset, triggerRef, panelRef]);\n\n return resolved;\n};\n","import { useCallback, type RefObject } from \"react\";\n\nexport type CellType = \"option\" | \"search\" | \"heading\" | \"divider\";\n\nexport interface CellMeta {\n type: CellType;\n onSelect?: () => void;\n disabled?: boolean;\n}\n\ninterface UseKeyboardNavigationOptions {\n isOpen: boolean;\n cells: Array<{ id: string; meta: CellMeta }>;\n activeIndex: number;\n setActiveIndex: (index: number) => void;\n onClose: () => void;\n triggerRef?: RefObject<HTMLElement | null>;\n}\n\nconst isNavigableOption = (meta: CellMeta) =>\n meta.type === \"option\" && !meta.disabled;\n\nconst isTextInputTarget = (target: EventTarget | null): boolean => {\n if (!(target instanceof HTMLElement)) return false;\n if (target.tagName === \"INPUT\" || target.tagName === \"TEXTAREA\") return true;\n return target.getAttribute(\"role\") === \"searchbox\";\n};\n\nexport const useKeyboardNavigation = ({\n isOpen,\n cells,\n activeIndex,\n setActiveIndex,\n onClose,\n triggerRef,\n}: UseKeyboardNavigationOptions) => {\n const findFirstOption = useCallback(() => {\n for (let i = 0; i < cells.length; i += 1) {\n if (isNavigableOption(cells[i].meta)) return i;\n }\n return -1;\n }, [cells]);\n\n const findLastOption = useCallback(() => {\n for (let i = cells.length - 1; i >= 0; i -= 1) {\n if (isNavigableOption(cells[i].meta)) return i;\n }\n return -1;\n }, [cells]);\n\n const findNextOption = useCallback(\n (from: number) => {\n const len = cells.length;\n if (len === 0) return -1;\n for (let step = 1; step <= len; step += 1) {\n const idx = (from + step + len) % len;\n if (isNavigableOption(cells[idx].meta)) return idx;\n }\n return -1;\n },\n [cells]\n );\n\n const findPrevOption = useCallback(\n (from: number) => {\n const len = cells.length;\n if (len === 0) return -1;\n for (let step = 1; step <= len; step += 1) {\n const idx = (from - step + len * 2) % len;\n if (isNavigableOption(cells[idx].meta)) return idx;\n }\n return -1;\n },\n [cells]\n );\n\n const handleKeyDown = useCallback(\n (event: React.KeyboardEvent) => {\n if (!isOpen) return;\n\n switch (event.key) {\n case \"ArrowDown\": {\n event.preventDefault();\n const next =\n activeIndex < 0 ? findFirstOption() : findNextOption(activeIndex);\n if (next >= 0) setActiveIndex(next);\n break;\n }\n case \"ArrowUp\": {\n event.preventDefault();\n const prev =\n activeIndex < 0 ? findLastOption() : findPrevOption(activeIndex);\n if (prev >= 0) setActiveIndex(prev);\n break;\n }\n case \"Home\": {\n event.preventDefault();\n const first = findFirstOption();\n if (first >= 0) setActiveIndex(first);\n break;\n }\n case \"End\": {\n event.preventDefault();\n const last = findLastOption();\n if (last >= 0) setActiveIndex(last);\n break;\n }\n case \"Enter\":\n case \" \": {\n if (event.defaultPrevented) break;\n if (event.key === \" \" && isTextInputTarget(event.target)) break;\n if (event.key === \" \") event.preventDefault();\n if (activeIndex >= 0 && activeIndex < cells.length) {\n const meta = cells[activeIndex].meta;\n if (isNavigableOption(meta)) meta.onSelect?.();\n }\n break;\n }\n case \"Escape\": {\n event.preventDefault();\n onClose();\n triggerRef?.current?.focus();\n break;\n }\n case \"Tab\": {\n onClose();\n break;\n }\n default:\n break;\n }\n },\n [\n isOpen,\n cells,\n activeIndex,\n setActiveIndex,\n onClose,\n triggerRef,\n findFirstOption,\n findLastOption,\n findNextOption,\n findPrevOption,\n ]\n );\n\n return { handleKeyDown };\n};\n"],"mappings":"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","names":["import_react","import_react_dom","import_xui_core","import_react","React","import_react","import_react","import_jsx_runtime","React","panel"]}
package/native/index.mjs CHANGED
@@ -664,32 +664,18 @@ var useContextMenuPosition = ({
664
664
  left = flippedLeft;
665
665
  }
666
666
  }
667
- const next = {
667
+ setResolved({
668
668
  top,
669
669
  left,
670
670
  placement: joinPlacement(vertical, horizontal)
671
- };
672
- setResolved(
673
- (prev) => prev && prev.top === next.top && prev.left === next.left && prev.placement === next.placement ? prev : next
674
- );
675
- };
676
- let rafId = window.requestAnimationFrame(compute);
677
- const onResize = () => compute();
678
- let scrollRafPending = false;
679
- const onScroll = () => {
680
- if (scrollRafPending) return;
681
- scrollRafPending = true;
682
- rafId = window.requestAnimationFrame(() => {
683
- scrollRafPending = false;
684
- compute();
685
671
  });
686
672
  };
673
+ const rafId = window.requestAnimationFrame(compute);
674
+ const onResize = () => compute();
687
675
  window.addEventListener("resize", onResize);
688
- window.addEventListener("scroll", onScroll, true);
689
676
  return () => {
690
677
  window.cancelAnimationFrame(rafId);
691
678
  window.removeEventListener("resize", onResize);
692
- window.removeEventListener("scroll", onScroll, true);
693
679
  };
694
680
  }, [isOpen, placement, offset, triggerRef, panelRef]);
695
681
  return resolved;
@@ -995,14 +981,10 @@ var ContextMenu = (props) => {
995
981
  const radiusObj = theme.radius;
996
982
  const radiusVal = sizing.borderRadius ?? radiusObj?.contextMenu ?? 8;
997
983
  const shadowObj = theme.shadow;
998
- const shadowVal = shadowObj?.popover ?? "";
984
+ const shadowVal = shadowObj?.contextMenu ?? "";
999
985
  const panelPaddingVertical = sizing.paddingVertical ?? 8;
1000
- const glassBackground = theme.colors.layer?.float ?? theme.colors.background.primary;
1001
986
  const panelStyle = {
1002
- background: glassBackground,
1003
- backdropFilter: "blur(12px)",
1004
- WebkitBackdropFilter: "blur(12px)",
1005
- border: `1px solid ${theme.colors.border.secondary}`,
987
+ background: theme.colors.background.primary,
1006
988
  borderRadius: radiusVal,
1007
989
  boxShadow: shadowVal,
1008
990
  width: width ?? sizing.panelWidth,
@@ -1154,7 +1136,6 @@ var ContextMenu = (props) => {
1154
1136
  }
1155
1137
  const hasStickySearch = !!searchNode;
1156
1138
  const prevOpenRef = useRef(false);
1157
- const skipFocusRestoreRef = useRef(false);
1158
1139
  useEffect3(() => {
1159
1140
  const wasOpen = prevOpenRef.current;
1160
1141
  prevOpenRef.current = open;
@@ -1180,10 +1161,7 @@ var ContextMenu = (props) => {
1180
1161
  return () => clearTimeout(timer);
1181
1162
  }
1182
1163
  if (wasOpen && !open) {
1183
- if (!skipFocusRestoreRef.current) {
1184
- triggerRef.current?.focus();
1185
- }
1186
- skipFocusRestoreRef.current = false;
1164
+ triggerRef.current?.focus();
1187
1165
  }
1188
1166
  }, [open]);
1189
1167
  useEffect3(() => {
@@ -1204,7 +1182,6 @@ var ContextMenu = (props) => {
1204
1182
  }
1205
1183
  }
1206
1184
  }
1207
- skipFocusRestoreRef.current = true;
1208
1185
  closeMenu();
1209
1186
  };
1210
1187
  document.addEventListener("mousedown", handlePointerDown);
@@ -1220,11 +1197,7 @@ var ContextMenu = (props) => {
1220
1197
  position: "sticky",
1221
1198
  top: 0,
1222
1199
  zIndex: 1,
1223
- // Match the glass panel so options scrolling underneath blur instead of
1224
- // showing through the translucent header.
1225
- background: glassBackground,
1226
- backdropFilter: "blur(12px)",
1227
- WebkitBackdropFilter: "blur(12px)"
1200
+ background: theme.colors.background.primary
1228
1201
  };
1229
1202
  const panel = open ? /* @__PURE__ */ jsx2(ContextMenuContext.Provider, { value: ctx, children: /* @__PURE__ */ jsxs2(
1230
1203
  "div",