@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/web/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
@@ -1123,7 +1123,7 @@ var ContextMenuSubmenu = ({
1123
1123
  ContextMenuSubmenu.displayName = "ContextMenuSubmenu";
1124
1124
 
1125
1125
  // src/hooks/useContextMenuPosition.ts
1126
- import { useEffect as useEffect3, useState as useState3 } from "react";
1126
+ import { useLayoutEffect as useLayoutEffect3, useState as useState3 } from "react";
1127
1127
  var splitPlacement = (placement) => {
1128
1128
  const [vertical, horizontal] = placement.split("-");
1129
1129
  return { vertical, horizontal };
@@ -1132,12 +1132,13 @@ var joinPlacement = (vertical, horizontal) => `${vertical}-${horizontal}`;
1132
1132
  var useContextMenuPosition = ({
1133
1133
  triggerRef,
1134
1134
  panelRef,
1135
+ containerRef,
1135
1136
  isOpen,
1136
1137
  placement = "bottom-start",
1137
1138
  offset = 4
1138
1139
  }) => {
1139
1140
  const [resolved, setResolved] = useState3();
1140
- useEffect3(() => {
1141
+ useLayoutEffect3(() => {
1141
1142
  if (!isOpen) {
1142
1143
  setResolved(void 0);
1143
1144
  return;
@@ -1145,6 +1146,7 @@ var useContextMenuPosition = ({
1145
1146
  const compute = () => {
1146
1147
  const trigger = triggerRef.current;
1147
1148
  const panel = panelRef.current;
1149
+ const container = containerRef.current;
1148
1150
  if (!trigger || !panel) return;
1149
1151
  const triggerRect = trigger.getBoundingClientRect();
1150
1152
  const panelRect = panel.getBoundingClientRect();
@@ -1184,6 +1186,15 @@ var useContextMenuPosition = ({
1184
1186
  }
1185
1187
  }
1186
1188
  }
1189
+ if (container) {
1190
+ const containerRect = container.getBoundingClientRect();
1191
+ const cOffsetW = container.offsetWidth;
1192
+ const cOffsetH = container.offsetHeight;
1193
+ const scaleX = cOffsetW > 0 ? containerRect.width / cOffsetW : 1;
1194
+ const scaleY = cOffsetH > 0 ? containerRect.height / cOffsetH : 1;
1195
+ left = (left - containerRect.left) / (scaleX || 1);
1196
+ top = (top - containerRect.top) / (scaleY || 1);
1197
+ }
1187
1198
  const next = {
1188
1199
  top,
1189
1200
  left,
@@ -1193,14 +1204,13 @@ var useContextMenuPosition = ({
1193
1204
  (prev) => prev && prev.top === next.top && prev.left === next.left && prev.placement === next.placement ? prev : next
1194
1205
  );
1195
1206
  };
1196
- const rafId = window.requestAnimationFrame(compute);
1207
+ compute();
1197
1208
  const onResize = () => compute();
1198
1209
  window.addEventListener("resize", onResize);
1199
1210
  return () => {
1200
- window.cancelAnimationFrame(rafId);
1201
1211
  window.removeEventListener("resize", onResize);
1202
1212
  };
1203
- }, [isOpen, placement, offset, triggerRef, panelRef]);
1213
+ }, [isOpen, placement, offset, triggerRef, panelRef, containerRef]);
1204
1214
  return resolved;
1205
1215
  };
1206
1216
 
@@ -1332,6 +1342,7 @@ var SEARCH_DEBOUNCE_MS = 200;
1332
1342
  var ARROW_SIZE = 10;
1333
1343
  var ARROW_HALF = ARROW_SIZE / 2;
1334
1344
  var ARROW_EDGE_OFFSET = 12;
1345
+ var ARROW_TIP_PROTRUSION = ARROW_HALF * Math.SQRT2;
1335
1346
  var PANEL_SHADOW = "0 4px 12px rgba(0,0,0,0.15)";
1336
1347
  var textFromNode = (node) => {
1337
1348
  if (node === null || node === void 0 || typeof node === "boolean")
@@ -1381,7 +1392,9 @@ var ContextMenuRoot = forwardRef(
1381
1392
  isOpen: propIsOpen,
1382
1393
  onOpenChange,
1383
1394
  trigger,
1395
+ triggerFullWidth = false,
1384
1396
  placement = "bottom-start",
1397
+ offset = 4,
1385
1398
  position,
1386
1399
  width,
1387
1400
  maxHeight = 300,
@@ -1415,11 +1428,14 @@ var ContextMenuRoot = forwardRef(
1415
1428
  const sizeStyles = xuiTheme.sizing.contextMenu(size);
1416
1429
  const borderRadius = xuiTheme.shape?.contextMenu?.[size]?.borderRadius ?? 8;
1417
1430
  const shouldCloseOnSelect = closeOnSelect ?? (type === "checkbox" ? false : true);
1431
+ const positionOffset = withArrow ? offset + ARROW_TIP_PROTRUSION : offset;
1418
1432
  const positioned = useContextMenuPosition({
1419
1433
  triggerRef,
1420
1434
  panelRef,
1435
+ containerRef,
1421
1436
  isOpen: isOpen && !!trigger && !position,
1422
- placement
1437
+ placement,
1438
+ offset: positionOffset
1423
1439
  });
1424
1440
  const resolvedPlacement = positioned?.placement ?? placement;
1425
1441
  const getArrowStyle = (arrowPlacement) => {
@@ -1494,7 +1510,7 @@ var ContextMenuRoot = forwardRef(
1494
1510
  onClose: closeMenu,
1495
1511
  triggerRef
1496
1512
  });
1497
- useEffect4(() => {
1513
+ useEffect3(() => {
1498
1514
  if (!isOpen) {
1499
1515
  cellsRef.current = [];
1500
1516
  setCellsVersion((version) => version + 1);
@@ -1503,14 +1519,14 @@ var ContextMenuRoot = forwardRef(
1503
1519
  setActiveSubmenuId(null);
1504
1520
  }
1505
1521
  }, [isOpen]);
1506
- useEffect4(() => {
1522
+ useEffect3(() => {
1507
1523
  const timer = setTimeout(
1508
1524
  () => setDebouncedQuery(query),
1509
1525
  SEARCH_DEBOUNCE_MS
1510
1526
  );
1511
1527
  return () => clearTimeout(timer);
1512
1528
  }, [query]);
1513
- useEffect4(() => {
1529
+ useEffect3(() => {
1514
1530
  if (!isOpen || !trigger) return;
1515
1531
  const onMouseDown = (event) => {
1516
1532
  const target = event.target;
@@ -1525,7 +1541,7 @@ var ContextMenuRoot = forwardRef(
1525
1541
  window.removeEventListener("scroll", onScroll);
1526
1542
  };
1527
1543
  }, [isOpen, trigger, closeMenu]);
1528
- useLayoutEffect3(() => {
1544
+ useLayoutEffect4(() => {
1529
1545
  if (!isOpen || !panelRef.current) return;
1530
1546
  const searchbox = panelRef.current.querySelector('[role="searchbox"]');
1531
1547
  const firstOption = panelRef.current.querySelector(
@@ -1533,13 +1549,13 @@ var ContextMenuRoot = forwardRef(
1533
1549
  );
1534
1550
  (searchbox ?? firstOption)?.focus();
1535
1551
  }, [isOpen]);
1536
- useLayoutEffect3(() => {
1552
+ useLayoutEffect4(() => {
1537
1553
  if (!isOpen || !panelRef.current) return;
1538
1554
  const activeElement = document.activeElement;
1539
1555
  if (activeElement && panelRef.current.contains(activeElement)) return;
1540
1556
  panelRef.current.focus();
1541
1557
  }, [isOpen, cellsVersion]);
1542
- useEffect4(() => {
1558
+ useEffect3(() => {
1543
1559
  if (isOpen) return;
1544
1560
  triggerRef.current?.focus();
1545
1561
  }, [isOpen]);
@@ -1675,7 +1691,10 @@ var ContextMenuRoot = forwardRef(
1675
1691
  onClick: (event) => {
1676
1692
  if (!event.defaultPrevented) toggleMenu();
1677
1693
  },
1678
- style: { display: "inline-flex" },
1694
+ style: {
1695
+ display: "inline-flex",
1696
+ width: triggerFullWidth ? "100%" : "auto"
1697
+ },
1679
1698
  children: cloneElement(trigger, {
1680
1699
  "aria-haspopup": "menu",
1681
1700
  "aria-expanded": isOpen ? "true" : "false"
@@ -1697,10 +1716,13 @@ var ContextMenuRoot = forwardRef(
1697
1716
  position: "fixed",
1698
1717
  left: position.x,
1699
1718
  top: position.y
1700
- } : trigger ? {
1701
- position: "fixed",
1702
- left: positioned?.left ?? 0,
1703
- top: positioned?.top ?? 0
1719
+ } : trigger ? positioned ? {
1720
+ position: "absolute",
1721
+ left: positioned.left,
1722
+ top: positioned.top
1723
+ } : {
1724
+ position: "absolute",
1725
+ visibility: "hidden"
1704
1726
  } : void 0;
1705
1727
  return /* @__PURE__ */ jsx4(ContextMenuContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs3(
1706
1728
  "div",