@xsolla/xui-context-menu 0.173.2 → 0.174.0

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.js CHANGED
@@ -986,6 +986,7 @@ var import_jsx_runtime3 = require("react/jsx-runtime");
986
986
  var SUBMENU_GAP2 = 4;
987
987
  var OPEN_DELAY_MS = 200;
988
988
  var CLOSE_GRACE_MS = 100;
989
+ var _submenuIdCounter = 0;
989
990
  var ContextMenuSubmenu = ({
990
991
  label,
991
992
  icon,
@@ -1000,6 +1001,11 @@ var ContextMenuSubmenu = ({
1000
1001
  const size = propSize || context?.size || "md";
1001
1002
  const sizeStyles = xuiTheme.sizing.contextMenu(size);
1002
1003
  const borderRadius = xuiTheme.shape?.contextMenu?.[size]?.borderRadius ?? xuiTheme.radius?.button ?? 8;
1004
+ const submenuIdRef = (0, import_react5.useRef)(null);
1005
+ if (submenuIdRef.current === null) {
1006
+ submenuIdRef.current = `xui-cm-submenu-${_submenuIdCounter += 1}`;
1007
+ }
1008
+ const submenuId = submenuIdRef.current;
1003
1009
  const [isOpen, setIsOpen] = (0, import_react5.useState)(false);
1004
1010
  const [visible, setVisible] = (0, import_react5.useState)(false);
1005
1011
  const [openLeft, setOpenLeft] = (0, import_react5.useState)(false);
@@ -1012,12 +1018,19 @@ var ContextMenuSubmenu = ({
1012
1018
  if (openTimerRef.current) clearTimeout(openTimerRef.current);
1013
1019
  if (closeTimerRef.current) clearTimeout(closeTimerRef.current);
1014
1020
  }, []);
1021
+ (0, import_react5.useEffect)(() => {
1022
+ if (!context?.activeSubmenuId) return;
1023
+ if (context.activeSubmenuId !== submenuId) {
1024
+ setIsOpen(false);
1025
+ }
1026
+ }, [context?.activeSubmenuId, submenuId]);
1015
1027
  const calculatePlacement = (0, import_react5.useCallback)(() => {
1016
1028
  if (!triggerRef.current) return;
1017
1029
  const triggerRect = triggerRef.current.getBoundingClientRect();
1018
1030
  const estimatedWidth = sizeStyles.minWidth + 32;
1019
1031
  const wouldOverflowRight = triggerRect.right + estimatedWidth + SUBMENU_GAP2 > window.innerWidth - 8;
1020
- setOpenLeft(wouldOverflowRight);
1032
+ const hasSpaceOnLeft = triggerRect.left - SUBMENU_GAP2 - estimatedWidth >= 8;
1033
+ setOpenLeft(wouldOverflowRight && hasSpaceOnLeft);
1021
1034
  setTopOffset(0);
1022
1035
  }, [sizeStyles.minWidth]);
1023
1036
  (0, import_react5.useLayoutEffect)(() => {
@@ -1025,7 +1038,8 @@ var ContextMenuSubmenu = ({
1025
1038
  const submenuRect = submenuRef.current.getBoundingClientRect();
1026
1039
  const triggerRect = triggerRef.current.getBoundingClientRect();
1027
1040
  const wouldOverflowRight = triggerRect.right + submenuRect.width + SUBMENU_GAP2 > window.innerWidth - 8;
1028
- setOpenLeft(wouldOverflowRight);
1041
+ const hasSpaceOnLeft = triggerRect.left - SUBMENU_GAP2 - submenuRect.width >= 8;
1042
+ setOpenLeft(wouldOverflowRight && hasSpaceOnLeft);
1029
1043
  const overflowBottom = triggerRect.top + submenuRect.height - (window.innerHeight - 8);
1030
1044
  if (overflowBottom > 0) {
1031
1045
  setTopOffset(-Math.min(overflowBottom, triggerRect.top - 8));
@@ -1048,16 +1062,27 @@ var ContextMenuSubmenu = ({
1048
1062
  openTimerRef.current = setTimeout(() => {
1049
1063
  calculatePlacement();
1050
1064
  setIsOpen(true);
1065
+ context?.setActiveSubmenuId(submenuId);
1051
1066
  }, OPEN_DELAY_MS);
1052
1067
  };
1053
1068
  const handleTriggerLeave = () => {
1054
1069
  clearTimers();
1055
- closeTimerRef.current = setTimeout(() => setIsOpen(false), CLOSE_GRACE_MS);
1070
+ closeTimerRef.current = setTimeout(() => {
1071
+ setIsOpen(false);
1072
+ if (context?.activeSubmenuId === submenuId) {
1073
+ context.setActiveSubmenuId(null);
1074
+ }
1075
+ }, CLOSE_GRACE_MS);
1056
1076
  };
1057
1077
  const handleSubmenuEnter = () => clearTimers();
1058
1078
  const handleSubmenuLeave = () => {
1059
1079
  clearTimers();
1060
- closeTimerRef.current = setTimeout(() => setIsOpen(false), CLOSE_GRACE_MS);
1080
+ closeTimerRef.current = setTimeout(() => {
1081
+ setIsOpen(false);
1082
+ if (context?.activeSubmenuId === submenuId) {
1083
+ context.setActiveSubmenuId(null);
1084
+ }
1085
+ }, CLOSE_GRACE_MS);
1061
1086
  };
1062
1087
  const submenuPositionStyle = openLeft ? { right: `calc(100% + ${SUBMENU_GAP2}px)` } : { left: `calc(100% + ${SUBMENU_GAP2}px)` };
1063
1088
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
@@ -1388,6 +1413,7 @@ var ContextMenuRoot = (0, import_react8.forwardRef)(
1388
1413
  const [cellsVersion, setCellsVersion] = (0, import_react8.useState)(0);
1389
1414
  const [query, setQuery] = (0, import_react8.useState)("");
1390
1415
  const [debouncedQuery, setDebouncedQuery] = (0, import_react8.useState)("");
1416
+ const [activeSubmenuId, setActiveSubmenuId] = (0, import_react8.useState)(null);
1391
1417
  const containerRef = (0, import_react8.useRef)(null);
1392
1418
  const triggerRef = (0, import_react8.useRef)(null);
1393
1419
  const panelRef = (0, import_react8.useRef)(null);
@@ -1454,6 +1480,7 @@ var ContextMenuRoot = (0, import_react8.forwardRef)(
1454
1480
  setCellsVersion((version) => version + 1);
1455
1481
  setQuery("");
1456
1482
  setDebouncedQuery("");
1483
+ setActiveSubmenuId(null);
1457
1484
  }
1458
1485
  }, [isOpen]);
1459
1486
  (0, import_react8.useEffect)(() => {
@@ -1508,7 +1535,9 @@ var ContextMenuRoot = (0, import_react8.forwardRef)(
1508
1535
  getCellIndex,
1509
1536
  cellsVersion,
1510
1537
  query,
1511
- setQuery
1538
+ setQuery,
1539
+ activeSubmenuId,
1540
+ setActiveSubmenuId
1512
1541
  }),
1513
1542
  [
1514
1543
  size,
@@ -1519,7 +1548,8 @@ var ContextMenuRoot = (0, import_react8.forwardRef)(
1519
1548
  unregisterCell,
1520
1549
  getCellIndex,
1521
1550
  cellsVersion,
1522
- query
1551
+ query,
1552
+ activeSubmenuId
1523
1553
  ]
1524
1554
  );
1525
1555
  const renderPresetItem = (item, index2) => {