@xsolla/xui-context-menu 0.183.0 → 0.185.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/native/index.mjs CHANGED
@@ -4,8 +4,8 @@ import {
4
4
  forwardRef,
5
5
  isValidElement,
6
6
  useCallback as useCallback3,
7
- useEffect as useEffect4,
8
- useLayoutEffect as useLayoutEffect3,
7
+ useEffect as useEffect3,
8
+ useLayoutEffect as useLayoutEffect4,
9
9
  useMemo,
10
10
  useRef as useRef2,
11
11
  useState as useState4
@@ -1050,7 +1050,7 @@ var ContextMenuSubmenu = ({
1050
1050
  ContextMenuSubmenu.displayName = "ContextMenuSubmenu";
1051
1051
 
1052
1052
  // src/hooks/useContextMenuPosition.ts
1053
- import { useEffect as useEffect3, useState as useState3 } from "react";
1053
+ import { useLayoutEffect as useLayoutEffect3, useState as useState3 } from "react";
1054
1054
  var splitPlacement = (placement) => {
1055
1055
  const [vertical, horizontal] = placement.split("-");
1056
1056
  return { vertical, horizontal };
@@ -1059,12 +1059,13 @@ var joinPlacement = (vertical, horizontal) => `${vertical}-${horizontal}`;
1059
1059
  var useContextMenuPosition = ({
1060
1060
  triggerRef,
1061
1061
  panelRef,
1062
+ containerRef,
1062
1063
  isOpen,
1063
1064
  placement = "bottom-start",
1064
1065
  offset = 4
1065
1066
  }) => {
1066
1067
  const [resolved, setResolved] = useState3();
1067
- useEffect3(() => {
1068
+ useLayoutEffect3(() => {
1068
1069
  if (!isOpen) {
1069
1070
  setResolved(void 0);
1070
1071
  return;
@@ -1072,6 +1073,7 @@ var useContextMenuPosition = ({
1072
1073
  const compute = () => {
1073
1074
  const trigger = triggerRef.current;
1074
1075
  const panel = panelRef.current;
1076
+ const container = containerRef.current;
1075
1077
  if (!trigger || !panel) return;
1076
1078
  const triggerRect = trigger.getBoundingClientRect();
1077
1079
  const panelRect = panel.getBoundingClientRect();
@@ -1111,6 +1113,15 @@ var useContextMenuPosition = ({
1111
1113
  }
1112
1114
  }
1113
1115
  }
1116
+ if (container) {
1117
+ const containerRect = container.getBoundingClientRect();
1118
+ const cOffsetW = container.offsetWidth;
1119
+ const cOffsetH = container.offsetHeight;
1120
+ const scaleX = cOffsetW > 0 ? containerRect.width / cOffsetW : 1;
1121
+ const scaleY = cOffsetH > 0 ? containerRect.height / cOffsetH : 1;
1122
+ left = (left - containerRect.left) / (scaleX || 1);
1123
+ top = (top - containerRect.top) / (scaleY || 1);
1124
+ }
1114
1125
  const next = {
1115
1126
  top,
1116
1127
  left,
@@ -1120,14 +1131,13 @@ var useContextMenuPosition = ({
1120
1131
  (prev) => prev && prev.top === next.top && prev.left === next.left && prev.placement === next.placement ? prev : next
1121
1132
  );
1122
1133
  };
1123
- const rafId = window.requestAnimationFrame(compute);
1134
+ compute();
1124
1135
  const onResize = () => compute();
1125
1136
  window.addEventListener("resize", onResize);
1126
1137
  return () => {
1127
- window.cancelAnimationFrame(rafId);
1128
1138
  window.removeEventListener("resize", onResize);
1129
1139
  };
1130
- }, [isOpen, placement, offset, triggerRef, panelRef]);
1140
+ }, [isOpen, placement, offset, triggerRef, panelRef, containerRef]);
1131
1141
  return resolved;
1132
1142
  };
1133
1143
 
@@ -1259,6 +1269,7 @@ var SEARCH_DEBOUNCE_MS = 200;
1259
1269
  var ARROW_SIZE = 10;
1260
1270
  var ARROW_HALF = ARROW_SIZE / 2;
1261
1271
  var ARROW_EDGE_OFFSET = 12;
1272
+ var ARROW_TIP_PROTRUSION = ARROW_HALF * Math.SQRT2;
1262
1273
  var PANEL_SHADOW = "0 4px 12px rgba(0,0,0,0.15)";
1263
1274
  var textFromNode = (node) => {
1264
1275
  if (node === null || node === void 0 || typeof node === "boolean")
@@ -1308,7 +1319,9 @@ var ContextMenuRoot = forwardRef(
1308
1319
  isOpen: propIsOpen,
1309
1320
  onOpenChange,
1310
1321
  trigger,
1322
+ triggerFullWidth = false,
1311
1323
  placement = "bottom-start",
1324
+ offset = 4,
1312
1325
  position,
1313
1326
  width,
1314
1327
  maxHeight = 300,
@@ -1342,11 +1355,14 @@ var ContextMenuRoot = forwardRef(
1342
1355
  const sizeStyles = xuiTheme.sizing.contextMenu(size);
1343
1356
  const borderRadius = xuiTheme.shape?.contextMenu?.[size]?.borderRadius ?? 8;
1344
1357
  const shouldCloseOnSelect = closeOnSelect ?? (type === "checkbox" ? false : true);
1358
+ const positionOffset = withArrow ? offset + ARROW_TIP_PROTRUSION : offset;
1345
1359
  const positioned = useContextMenuPosition({
1346
1360
  triggerRef,
1347
1361
  panelRef,
1362
+ containerRef,
1348
1363
  isOpen: isOpen && !!trigger && !position,
1349
- placement
1364
+ placement,
1365
+ offset: positionOffset
1350
1366
  });
1351
1367
  const resolvedPlacement = positioned?.placement ?? placement;
1352
1368
  const getArrowStyle = (arrowPlacement) => {
@@ -1421,7 +1437,7 @@ var ContextMenuRoot = forwardRef(
1421
1437
  onClose: closeMenu,
1422
1438
  triggerRef
1423
1439
  });
1424
- useEffect4(() => {
1440
+ useEffect3(() => {
1425
1441
  if (!isOpen) {
1426
1442
  cellsRef.current = [];
1427
1443
  setCellsVersion((version) => version + 1);
@@ -1430,14 +1446,14 @@ var ContextMenuRoot = forwardRef(
1430
1446
  setActiveSubmenuId(null);
1431
1447
  }
1432
1448
  }, [isOpen]);
1433
- useEffect4(() => {
1449
+ useEffect3(() => {
1434
1450
  const timer = setTimeout(
1435
1451
  () => setDebouncedQuery(query),
1436
1452
  SEARCH_DEBOUNCE_MS
1437
1453
  );
1438
1454
  return () => clearTimeout(timer);
1439
1455
  }, [query]);
1440
- useEffect4(() => {
1456
+ useEffect3(() => {
1441
1457
  if (!isOpen || !trigger) return;
1442
1458
  const onMouseDown = (event) => {
1443
1459
  const target = event.target;
@@ -1452,7 +1468,7 @@ var ContextMenuRoot = forwardRef(
1452
1468
  window.removeEventListener("scroll", onScroll);
1453
1469
  };
1454
1470
  }, [isOpen, trigger, closeMenu]);
1455
- useLayoutEffect3(() => {
1471
+ useLayoutEffect4(() => {
1456
1472
  if (!isOpen || !panelRef.current) return;
1457
1473
  const searchbox = panelRef.current.querySelector('[role="searchbox"]');
1458
1474
  const firstOption = panelRef.current.querySelector(
@@ -1460,13 +1476,13 @@ var ContextMenuRoot = forwardRef(
1460
1476
  );
1461
1477
  (searchbox ?? firstOption)?.focus();
1462
1478
  }, [isOpen]);
1463
- useLayoutEffect3(() => {
1479
+ useLayoutEffect4(() => {
1464
1480
  if (!isOpen || !panelRef.current) return;
1465
1481
  const activeElement = document.activeElement;
1466
1482
  if (activeElement && panelRef.current.contains(activeElement)) return;
1467
1483
  panelRef.current.focus();
1468
1484
  }, [isOpen, cellsVersion]);
1469
- useEffect4(() => {
1485
+ useEffect3(() => {
1470
1486
  if (isOpen) return;
1471
1487
  triggerRef.current?.focus();
1472
1488
  }, [isOpen]);
@@ -1602,7 +1618,10 @@ var ContextMenuRoot = forwardRef(
1602
1618
  onClick: (event) => {
1603
1619
  if (!event.defaultPrevented) toggleMenu();
1604
1620
  },
1605
- style: { display: "inline-flex" },
1621
+ style: {
1622
+ display: "inline-flex",
1623
+ width: triggerFullWidth ? "100%" : "auto"
1624
+ },
1606
1625
  children: cloneElement(trigger, {
1607
1626
  "aria-haspopup": "menu",
1608
1627
  "aria-expanded": isOpen ? "true" : "false"
@@ -1624,10 +1643,13 @@ var ContextMenuRoot = forwardRef(
1624
1643
  position: "fixed",
1625
1644
  left: position.x,
1626
1645
  top: position.y
1627
- } : trigger ? {
1628
- position: "fixed",
1629
- left: positioned?.left ?? 0,
1630
- top: positioned?.top ?? 0
1646
+ } : trigger ? positioned ? {
1647
+ position: "absolute",
1648
+ left: positioned.left,
1649
+ top: positioned.top
1650
+ } : {
1651
+ position: "absolute",
1652
+ visibility: "hidden"
1631
1653
  } : void 0;
1632
1654
  return /* @__PURE__ */ jsx4(ContextMenuContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs3(
1633
1655
  "div",