@xsolla/xui-context-menu 0.185.0 → 0.185.2

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/web/index.mjs CHANGED
@@ -426,6 +426,7 @@ var OptionCell = ({
426
426
  const closeTimerRef = React3.useRef(
427
427
  null
428
428
  );
429
+ const ownsSubmenu = Boolean(hasSubmenu && submenu != null);
429
430
  const cancelClose = () => {
430
431
  if (closeTimerRef.current) {
431
432
  clearTimeout(closeTimerRef.current);
@@ -434,9 +435,22 @@ var OptionCell = ({
434
435
  };
435
436
  const scheduleClose = () => {
436
437
  cancelClose();
437
- closeTimerRef.current = setTimeout(() => setSubmenuOpen(false), 120);
438
+ closeTimerRef.current = setTimeout(() => {
439
+ setSubmenuOpen(false);
440
+ if (ownsSubmenu && ctx?.activeSubmenuId === id) {
441
+ ctx.setActiveSubmenuId?.(null);
442
+ }
443
+ }, 120);
438
444
  };
439
445
  useEffect(() => () => cancelClose(), []);
446
+ useEffect(() => {
447
+ if (!ownsSubmenu || !submenuOpen) return;
448
+ const activeId = ctx?.activeSubmenuId;
449
+ if (activeId != null && activeId !== id) {
450
+ cancelClose();
451
+ setSubmenuOpen(false);
452
+ }
453
+ }, [ctx?.activeSubmenuId, ownsSubmenu, submenuOpen, id]);
440
454
  useEffect(() => {
441
455
  if (!hasSubmenu || !submenuOpen) return;
442
456
  const onMouseDown = (e) => {
@@ -445,13 +459,16 @@ var OptionCell = ({
445
459
  const inOption = optionRef.current?.contains(target);
446
460
  const inSubmenu = submenuWrapperRef.current?.contains(target);
447
461
  if (!inOption && !inSubmenu) {
462
+ if (target.closest("[data-xui-context-menu-portal]")) return;
448
463
  setSubmenuOpen(false);
449
464
  return;
450
465
  }
451
- if (inSubmenu && target.closest(
452
- '[role="menuitem"],[role="menuitemcheckbox"],[role="menuitemradio"]'
453
- )) {
454
- setSubmenuOpen(false);
466
+ if (inSubmenu) {
467
+ const activated = target.closest(
468
+ '[role="menuitem"],[role="menuitemcheckbox"],[role="menuitemradio"]'
469
+ );
470
+ const isLeafAction = activated?.getAttribute("role") === "menuitem" && activated.getAttribute("aria-haspopup") !== "menu";
471
+ if (isLeafAction) setSubmenuOpen(false);
455
472
  }
456
473
  };
457
474
  document.addEventListener("mousedown", onMouseDown);
@@ -527,6 +544,7 @@ var OptionCell = ({
527
544
  if (ctx && index2 >= 0) ctx.setActiveIndex(index2);
528
545
  if (!ctx) setIsHovered(true);
529
546
  if (hasSubmenu) setSubmenuOpen(true);
547
+ if (ownsSubmenu) ctx?.setActiveSubmenuId?.(id);
530
548
  };
531
549
  const handleLeave = () => {
532
550
  if (!ctx) setIsHovered(false);
@@ -1344,6 +1362,7 @@ var ARROW_HALF = ARROW_SIZE / 2;
1344
1362
  var ARROW_EDGE_OFFSET = 12;
1345
1363
  var ARROW_TIP_PROTRUSION = ARROW_HALF * Math.SQRT2;
1346
1364
  var PANEL_SHADOW = "0 4px 12px rgba(0,0,0,0.15)";
1365
+ var PORTALED_LAYER_SELECTOR = '[data-modal-id],[role="listbox"],[data-xui-context-menu-portal]';
1347
1366
  var textFromNode = (node) => {
1348
1367
  if (node === null || node === void 0 || typeof node === "boolean")
1349
1368
  return "";
@@ -1530,7 +1549,13 @@ var ContextMenuRoot = forwardRef(
1530
1549
  if (!isOpen || !trigger) return;
1531
1550
  const onMouseDown = (event) => {
1532
1551
  const target = event.target;
1533
- if (!target || containerRef.current?.contains(target)) return;
1552
+ if (!target) return;
1553
+ if (containerRef.current?.contains(target)) return;
1554
+ const path = typeof event.composedPath === "function" ? event.composedPath() : [];
1555
+ const isInsidePortaledLayer = path.length ? path.some(
1556
+ (node) => node instanceof Element && node.matches(PORTALED_LAYER_SELECTOR)
1557
+ ) : target instanceof Element && !!target.closest(PORTALED_LAYER_SELECTOR);
1558
+ if (isInsidePortaledLayer) return;
1534
1559
  closeMenu();
1535
1560
  };
1536
1561
  const onScroll = () => closeMenu();