@xsolla/xui-context-menu 0.184.0 → 0.185.1
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.d.mts +14 -1
- package/native/index.d.ts +14 -1
- package/native/index.js +48 -11
- package/native/index.js.map +1 -1
- package/native/index.mjs +57 -20
- package/native/index.mjs.map +1 -1
- package/package.json +10 -10
- package/web/index.d.mts +14 -1
- package/web/index.d.ts +14 -1
- package/web/index.js +48 -11
- package/web/index.js.map +1 -1
- package/web/index.mjs +57 -20
- package/web/index.mjs.map +1 -1
package/native/index.d.mts
CHANGED
|
@@ -87,6 +87,11 @@ interface ContextMenuProps extends ThemeOverrideProps {
|
|
|
87
87
|
emptyMessage?: string;
|
|
88
88
|
empty?: ReactNode;
|
|
89
89
|
trigger?: ReactNode;
|
|
90
|
+
/**
|
|
91
|
+
* When `true`, the trigger fills the full width of its parent instead of
|
|
92
|
+
* shrinking to its content width. Defaults to `false`.
|
|
93
|
+
*/
|
|
94
|
+
triggerFullWidth?: boolean;
|
|
90
95
|
/**
|
|
91
96
|
* Menu placement relative to the trigger element. The `*-center` variants
|
|
92
97
|
* horizontally center the panel on the trigger; every variant auto-flips on
|
|
@@ -95,6 +100,13 @@ interface ContextMenuProps extends ThemeOverrideProps {
|
|
|
95
100
|
* when `position` is set). Defaults to `"bottom-start"`.
|
|
96
101
|
*/
|
|
97
102
|
placement?: ContextMenuPlacement;
|
|
103
|
+
/**
|
|
104
|
+
* Gap in pixels between the trigger and the menu. When `withArrow` is set
|
|
105
|
+
* this is measured to the arrow's tip; otherwise to the panel edge. Only
|
|
106
|
+
* applies to trigger-anchored menus (ignored when `position` is set).
|
|
107
|
+
* Defaults to `4`.
|
|
108
|
+
*/
|
|
109
|
+
offset?: number;
|
|
98
110
|
position?: ContextMenuPosition;
|
|
99
111
|
isOpen?: boolean;
|
|
100
112
|
onOpenChange?: (open: boolean) => void;
|
|
@@ -174,6 +186,7 @@ declare const useContextMenuRequired: () => ContextMenuContextValue;
|
|
|
174
186
|
interface UseContextMenuPositionOptions {
|
|
175
187
|
triggerRef: RefObject<HTMLElement | null>;
|
|
176
188
|
panelRef: RefObject<HTMLElement | null>;
|
|
189
|
+
containerRef: RefObject<HTMLElement | null>;
|
|
177
190
|
isOpen: boolean;
|
|
178
191
|
placement?: ContextMenuPlacement;
|
|
179
192
|
offset?: number;
|
|
@@ -183,6 +196,6 @@ interface ResolvedPosition {
|
|
|
183
196
|
left: number;
|
|
184
197
|
placement: ContextMenuPlacement;
|
|
185
198
|
}
|
|
186
|
-
declare const useContextMenuPosition: ({ triggerRef, panelRef, isOpen, placement, offset, }: UseContextMenuPositionOptions) => ResolvedPosition | undefined;
|
|
199
|
+
declare const useContextMenuPosition: ({ triggerRef, panelRef, containerRef, isOpen, placement, offset, }: UseContextMenuPositionOptions) => ResolvedPosition | undefined;
|
|
187
200
|
|
|
188
201
|
export { ContextMenu, ContextMenuContext, type ContextMenuContextValue, type ContextMenuDividerItemProps, type ContextMenuHeadingItemProps, ContextMenuItem, type ContextMenuItemProps, type ContextMenuOptionItemProps, type ContextMenuPlacement, type ContextMenuPosition, type ContextMenuPresetType, type ContextMenuProps, type ContextMenuSearchItemProps, type ContextMenuSize, type ContextMenuSizing, ContextMenuSubmenu, type ContextMenuSubmenuProps, useContextMenu, useContextMenuPosition, useContextMenuRequired, useKeyboardNavigation };
|
package/native/index.d.ts
CHANGED
|
@@ -87,6 +87,11 @@ interface ContextMenuProps extends ThemeOverrideProps {
|
|
|
87
87
|
emptyMessage?: string;
|
|
88
88
|
empty?: ReactNode;
|
|
89
89
|
trigger?: ReactNode;
|
|
90
|
+
/**
|
|
91
|
+
* When `true`, the trigger fills the full width of its parent instead of
|
|
92
|
+
* shrinking to its content width. Defaults to `false`.
|
|
93
|
+
*/
|
|
94
|
+
triggerFullWidth?: boolean;
|
|
90
95
|
/**
|
|
91
96
|
* Menu placement relative to the trigger element. The `*-center` variants
|
|
92
97
|
* horizontally center the panel on the trigger; every variant auto-flips on
|
|
@@ -95,6 +100,13 @@ interface ContextMenuProps extends ThemeOverrideProps {
|
|
|
95
100
|
* when `position` is set). Defaults to `"bottom-start"`.
|
|
96
101
|
*/
|
|
97
102
|
placement?: ContextMenuPlacement;
|
|
103
|
+
/**
|
|
104
|
+
* Gap in pixels between the trigger and the menu. When `withArrow` is set
|
|
105
|
+
* this is measured to the arrow's tip; otherwise to the panel edge. Only
|
|
106
|
+
* applies to trigger-anchored menus (ignored when `position` is set).
|
|
107
|
+
* Defaults to `4`.
|
|
108
|
+
*/
|
|
109
|
+
offset?: number;
|
|
98
110
|
position?: ContextMenuPosition;
|
|
99
111
|
isOpen?: boolean;
|
|
100
112
|
onOpenChange?: (open: boolean) => void;
|
|
@@ -174,6 +186,7 @@ declare const useContextMenuRequired: () => ContextMenuContextValue;
|
|
|
174
186
|
interface UseContextMenuPositionOptions {
|
|
175
187
|
triggerRef: RefObject<HTMLElement | null>;
|
|
176
188
|
panelRef: RefObject<HTMLElement | null>;
|
|
189
|
+
containerRef: RefObject<HTMLElement | null>;
|
|
177
190
|
isOpen: boolean;
|
|
178
191
|
placement?: ContextMenuPlacement;
|
|
179
192
|
offset?: number;
|
|
@@ -183,6 +196,6 @@ interface ResolvedPosition {
|
|
|
183
196
|
left: number;
|
|
184
197
|
placement: ContextMenuPlacement;
|
|
185
198
|
}
|
|
186
|
-
declare const useContextMenuPosition: ({ triggerRef, panelRef, isOpen, placement, offset, }: UseContextMenuPositionOptions) => ResolvedPosition | undefined;
|
|
199
|
+
declare const useContextMenuPosition: ({ triggerRef, panelRef, containerRef, isOpen, placement, offset, }: UseContextMenuPositionOptions) => ResolvedPosition | undefined;
|
|
187
200
|
|
|
188
201
|
export { ContextMenu, ContextMenuContext, type ContextMenuContextValue, type ContextMenuDividerItemProps, type ContextMenuHeadingItemProps, ContextMenuItem, type ContextMenuItemProps, type ContextMenuOptionItemProps, type ContextMenuPlacement, type ContextMenuPosition, type ContextMenuPresetType, type ContextMenuProps, type ContextMenuSearchItemProps, type ContextMenuSize, type ContextMenuSizing, ContextMenuSubmenu, type ContextMenuSubmenuProps, useContextMenu, useContextMenuPosition, useContextMenuRequired, useKeyboardNavigation };
|
package/native/index.js
CHANGED
|
@@ -379,6 +379,7 @@ var OptionCell = ({
|
|
|
379
379
|
const closeTimerRef = import_react2.default.useRef(
|
|
380
380
|
null
|
|
381
381
|
);
|
|
382
|
+
const ownsSubmenu = Boolean(hasSubmenu && submenu != null);
|
|
382
383
|
const cancelClose = () => {
|
|
383
384
|
if (closeTimerRef.current) {
|
|
384
385
|
clearTimeout(closeTimerRef.current);
|
|
@@ -387,9 +388,22 @@ var OptionCell = ({
|
|
|
387
388
|
};
|
|
388
389
|
const scheduleClose = () => {
|
|
389
390
|
cancelClose();
|
|
390
|
-
closeTimerRef.current = setTimeout(() =>
|
|
391
|
+
closeTimerRef.current = setTimeout(() => {
|
|
392
|
+
setSubmenuOpen(false);
|
|
393
|
+
if (ownsSubmenu && ctx?.activeSubmenuId === id) {
|
|
394
|
+
ctx.setActiveSubmenuId?.(null);
|
|
395
|
+
}
|
|
396
|
+
}, 120);
|
|
391
397
|
};
|
|
392
398
|
(0, import_react2.useEffect)(() => () => cancelClose(), []);
|
|
399
|
+
(0, import_react2.useEffect)(() => {
|
|
400
|
+
if (!ownsSubmenu || !submenuOpen) return;
|
|
401
|
+
const activeId = ctx?.activeSubmenuId;
|
|
402
|
+
if (activeId != null && activeId !== id) {
|
|
403
|
+
cancelClose();
|
|
404
|
+
setSubmenuOpen(false);
|
|
405
|
+
}
|
|
406
|
+
}, [ctx?.activeSubmenuId, ownsSubmenu, submenuOpen, id]);
|
|
393
407
|
(0, import_react2.useEffect)(() => {
|
|
394
408
|
if (!hasSubmenu || !submenuOpen) return;
|
|
395
409
|
const onMouseDown = (e) => {
|
|
@@ -480,6 +494,7 @@ var OptionCell = ({
|
|
|
480
494
|
if (ctx && index >= 0) ctx.setActiveIndex(index);
|
|
481
495
|
if (!ctx) setIsHovered(true);
|
|
482
496
|
if (hasSubmenu) setSubmenuOpen(true);
|
|
497
|
+
if (ownsSubmenu) ctx?.setActiveSubmenuId?.(id);
|
|
483
498
|
};
|
|
484
499
|
const handleLeave = () => {
|
|
485
500
|
if (!ctx) setIsHovered(false);
|
|
@@ -1079,12 +1094,13 @@ var joinPlacement = (vertical, horizontal) => `${vertical}-${horizontal}`;
|
|
|
1079
1094
|
var useContextMenuPosition = ({
|
|
1080
1095
|
triggerRef,
|
|
1081
1096
|
panelRef,
|
|
1097
|
+
containerRef,
|
|
1082
1098
|
isOpen,
|
|
1083
1099
|
placement = "bottom-start",
|
|
1084
1100
|
offset = 4
|
|
1085
1101
|
}) => {
|
|
1086
1102
|
const [resolved, setResolved] = (0, import_react4.useState)();
|
|
1087
|
-
(0, import_react4.
|
|
1103
|
+
(0, import_react4.useLayoutEffect)(() => {
|
|
1088
1104
|
if (!isOpen) {
|
|
1089
1105
|
setResolved(void 0);
|
|
1090
1106
|
return;
|
|
@@ -1092,6 +1108,7 @@ var useContextMenuPosition = ({
|
|
|
1092
1108
|
const compute = () => {
|
|
1093
1109
|
const trigger = triggerRef.current;
|
|
1094
1110
|
const panel = panelRef.current;
|
|
1111
|
+
const container = containerRef.current;
|
|
1095
1112
|
if (!trigger || !panel) return;
|
|
1096
1113
|
const triggerRect = trigger.getBoundingClientRect();
|
|
1097
1114
|
const panelRect = panel.getBoundingClientRect();
|
|
@@ -1131,6 +1148,15 @@ var useContextMenuPosition = ({
|
|
|
1131
1148
|
}
|
|
1132
1149
|
}
|
|
1133
1150
|
}
|
|
1151
|
+
if (container) {
|
|
1152
|
+
const containerRect = container.getBoundingClientRect();
|
|
1153
|
+
const cOffsetW = container.offsetWidth;
|
|
1154
|
+
const cOffsetH = container.offsetHeight;
|
|
1155
|
+
const scaleX = cOffsetW > 0 ? containerRect.width / cOffsetW : 1;
|
|
1156
|
+
const scaleY = cOffsetH > 0 ? containerRect.height / cOffsetH : 1;
|
|
1157
|
+
left = (left - containerRect.left) / (scaleX || 1);
|
|
1158
|
+
top = (top - containerRect.top) / (scaleY || 1);
|
|
1159
|
+
}
|
|
1134
1160
|
const next = {
|
|
1135
1161
|
top,
|
|
1136
1162
|
left,
|
|
@@ -1140,14 +1166,13 @@ var useContextMenuPosition = ({
|
|
|
1140
1166
|
(prev) => prev && prev.top === next.top && prev.left === next.left && prev.placement === next.placement ? prev : next
|
|
1141
1167
|
);
|
|
1142
1168
|
};
|
|
1143
|
-
|
|
1169
|
+
compute();
|
|
1144
1170
|
const onResize = () => compute();
|
|
1145
1171
|
window.addEventListener("resize", onResize);
|
|
1146
1172
|
return () => {
|
|
1147
|
-
window.cancelAnimationFrame(rafId);
|
|
1148
1173
|
window.removeEventListener("resize", onResize);
|
|
1149
1174
|
};
|
|
1150
|
-
}, [isOpen, placement, offset, triggerRef, panelRef]);
|
|
1175
|
+
}, [isOpen, placement, offset, triggerRef, panelRef, containerRef]);
|
|
1151
1176
|
return resolved;
|
|
1152
1177
|
};
|
|
1153
1178
|
|
|
@@ -1279,6 +1304,7 @@ var SEARCH_DEBOUNCE_MS = 200;
|
|
|
1279
1304
|
var ARROW_SIZE = 10;
|
|
1280
1305
|
var ARROW_HALF = ARROW_SIZE / 2;
|
|
1281
1306
|
var ARROW_EDGE_OFFSET = 12;
|
|
1307
|
+
var ARROW_TIP_PROTRUSION = ARROW_HALF * Math.SQRT2;
|
|
1282
1308
|
var PANEL_SHADOW = "0 4px 12px rgba(0,0,0,0.15)";
|
|
1283
1309
|
var textFromNode = (node) => {
|
|
1284
1310
|
if (node === null || node === void 0 || typeof node === "boolean")
|
|
@@ -1328,7 +1354,9 @@ var ContextMenuRoot = (0, import_react6.forwardRef)(
|
|
|
1328
1354
|
isOpen: propIsOpen,
|
|
1329
1355
|
onOpenChange,
|
|
1330
1356
|
trigger,
|
|
1357
|
+
triggerFullWidth = false,
|
|
1331
1358
|
placement = "bottom-start",
|
|
1359
|
+
offset = 4,
|
|
1332
1360
|
position,
|
|
1333
1361
|
width,
|
|
1334
1362
|
maxHeight = 300,
|
|
@@ -1362,11 +1390,14 @@ var ContextMenuRoot = (0, import_react6.forwardRef)(
|
|
|
1362
1390
|
const sizeStyles = xuiTheme.sizing.contextMenu(size);
|
|
1363
1391
|
const borderRadius = xuiTheme.shape?.contextMenu?.[size]?.borderRadius ?? 8;
|
|
1364
1392
|
const shouldCloseOnSelect = closeOnSelect ?? (type === "checkbox" ? false : true);
|
|
1393
|
+
const positionOffset = withArrow ? offset + ARROW_TIP_PROTRUSION : offset;
|
|
1365
1394
|
const positioned = useContextMenuPosition({
|
|
1366
1395
|
triggerRef,
|
|
1367
1396
|
panelRef,
|
|
1397
|
+
containerRef,
|
|
1368
1398
|
isOpen: isOpen && !!trigger && !position,
|
|
1369
|
-
placement
|
|
1399
|
+
placement,
|
|
1400
|
+
offset: positionOffset
|
|
1370
1401
|
});
|
|
1371
1402
|
const resolvedPlacement = positioned?.placement ?? placement;
|
|
1372
1403
|
const getArrowStyle = (arrowPlacement) => {
|
|
@@ -1622,7 +1653,10 @@ var ContextMenuRoot = (0, import_react6.forwardRef)(
|
|
|
1622
1653
|
onClick: (event) => {
|
|
1623
1654
|
if (!event.defaultPrevented) toggleMenu();
|
|
1624
1655
|
},
|
|
1625
|
-
style: {
|
|
1656
|
+
style: {
|
|
1657
|
+
display: "inline-flex",
|
|
1658
|
+
width: triggerFullWidth ? "100%" : "auto"
|
|
1659
|
+
},
|
|
1626
1660
|
children: (0, import_react6.cloneElement)(trigger, {
|
|
1627
1661
|
"aria-haspopup": "menu",
|
|
1628
1662
|
"aria-expanded": isOpen ? "true" : "false"
|
|
@@ -1644,10 +1678,13 @@ var ContextMenuRoot = (0, import_react6.forwardRef)(
|
|
|
1644
1678
|
position: "fixed",
|
|
1645
1679
|
left: position.x,
|
|
1646
1680
|
top: position.y
|
|
1647
|
-
} : trigger ? {
|
|
1648
|
-
position: "
|
|
1649
|
-
left: positioned
|
|
1650
|
-
top: positioned
|
|
1681
|
+
} : trigger ? positioned ? {
|
|
1682
|
+
position: "absolute",
|
|
1683
|
+
left: positioned.left,
|
|
1684
|
+
top: positioned.top
|
|
1685
|
+
} : {
|
|
1686
|
+
position: "absolute",
|
|
1687
|
+
visibility: "hidden"
|
|
1651
1688
|
} : void 0;
|
|
1652
1689
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ContextMenuContext.Provider, { value: contextValue, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
1653
1690
|
"div",
|