@xsolla/xui-context-menu 0.172.2 → 0.173.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/README.md +1 -0
- package/native/index.d.mts +79 -75
- package/native/index.d.ts +79 -75
- package/native/index.js +796 -478
- package/native/index.js.map +1 -1
- package/native/index.mjs +807 -481
- package/native/index.mjs.map +1 -1
- package/package.json +10 -10
- package/web/index.d.mts +79 -75
- package/web/index.d.ts +79 -75
- package/web/index.js +896 -501
- package/web/index.js.map +1 -1
- package/web/index.mjs +889 -490
- package/web/index.mjs.map +1 -1
package/web/index.js
CHANGED
|
@@ -33,6 +33,7 @@ __export(index_exports, {
|
|
|
33
33
|
ContextMenu: () => ContextMenu,
|
|
34
34
|
ContextMenuContext: () => ContextMenuContext,
|
|
35
35
|
ContextMenuItem: () => ContextMenuItem,
|
|
36
|
+
ContextMenuSubmenu: () => ContextMenuSubmenu,
|
|
36
37
|
useContextMenu: () => useContextMenu,
|
|
37
38
|
useContextMenuPosition: () => useContextMenuPosition,
|
|
38
39
|
useContextMenuRequired: () => useContextMenuRequired,
|
|
@@ -41,20 +42,272 @@ __export(index_exports, {
|
|
|
41
42
|
module.exports = __toCommonJS(index_exports);
|
|
42
43
|
|
|
43
44
|
// src/ContextMenu.tsx
|
|
44
|
-
var
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
var import_react8 = require("react");
|
|
46
|
+
|
|
47
|
+
// ../../foundation/primitives-web/src/Box.tsx
|
|
48
|
+
var import_react2 = __toESM(require("react"));
|
|
49
|
+
var import_styled_components = __toESM(require("styled-components"));
|
|
50
|
+
|
|
51
|
+
// ../../foundation/primitives-web/src/filterDOMProps.ts
|
|
52
|
+
var import_react = __toESM(require("react"));
|
|
53
|
+
|
|
54
|
+
// ../../../node_modules/@emotion/memoize/dist/memoize.esm.js
|
|
55
|
+
function memoize(fn) {
|
|
56
|
+
var cache = {};
|
|
57
|
+
return function(arg) {
|
|
58
|
+
if (cache[arg] === void 0) cache[arg] = fn(arg);
|
|
59
|
+
return cache[arg];
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
var memoize_esm_default = memoize;
|
|
63
|
+
|
|
64
|
+
// ../../../node_modules/@emotion/is-prop-valid/dist/is-prop-valid.esm.js
|
|
65
|
+
var reactPropsRegex = /^((children|dangerouslySetInnerHTML|key|ref|autoFocus|defaultValue|defaultChecked|innerHTML|suppressContentEditableWarning|suppressHydrationWarning|valueLink|accept|acceptCharset|accessKey|action|allow|allowUserMedia|allowPaymentRequest|allowFullScreen|allowTransparency|alt|async|autoComplete|autoPlay|capture|cellPadding|cellSpacing|challenge|charSet|checked|cite|classID|className|cols|colSpan|content|contentEditable|contextMenu|controls|controlsList|coords|crossOrigin|data|dateTime|decoding|default|defer|dir|disabled|disablePictureInPicture|download|draggable|encType|form|formAction|formEncType|formMethod|formNoValidate|formTarget|frameBorder|headers|height|hidden|high|href|hrefLang|htmlFor|httpEquiv|id|inputMode|integrity|is|keyParams|keyType|kind|label|lang|list|loading|loop|low|marginHeight|marginWidth|max|maxLength|media|mediaGroup|method|min|minLength|multiple|muted|name|nonce|noValidate|open|optimum|pattern|placeholder|playsInline|poster|preload|profile|radioGroup|readOnly|referrerPolicy|rel|required|reversed|role|rows|rowSpan|sandbox|scope|scoped|scrolling|seamless|selected|shape|size|sizes|slot|span|spellCheck|src|srcDoc|srcLang|srcSet|start|step|style|summary|tabIndex|target|title|type|useMap|value|width|wmode|wrap|about|datatype|inlist|prefix|property|resource|typeof|vocab|autoCapitalize|autoCorrect|autoSave|color|inert|itemProp|itemScope|itemType|itemID|itemRef|on|results|security|unselectable|accentHeight|accumulate|additive|alignmentBaseline|allowReorder|alphabetic|amplitude|arabicForm|ascent|attributeName|attributeType|autoReverse|azimuth|baseFrequency|baselineShift|baseProfile|bbox|begin|bias|by|calcMode|capHeight|clip|clipPathUnits|clipPath|clipRule|colorInterpolation|colorInterpolationFilters|colorProfile|colorRendering|contentScriptType|contentStyleType|cursor|cx|cy|d|decelerate|descent|diffuseConstant|direction|display|divisor|dominantBaseline|dur|dx|dy|edgeMode|elevation|enableBackground|end|exponent|externalResourcesRequired|fill|fillOpacity|fillRule|filter|filterRes|filterUnits|floodColor|floodOpacity|focusable|fontFamily|fontSize|fontSizeAdjust|fontStretch|fontStyle|fontVariant|fontWeight|format|from|fr|fx|fy|g1|g2|glyphName|glyphOrientationHorizontal|glyphOrientationVertical|glyphRef|gradientTransform|gradientUnits|hanging|horizAdvX|horizOriginX|ideographic|imageRendering|in|in2|intercept|k|k1|k2|k3|k4|kernelMatrix|kernelUnitLength|kerning|keyPoints|keySplines|keyTimes|lengthAdjust|letterSpacing|lightingColor|limitingConeAngle|local|markerEnd|markerMid|markerStart|markerHeight|markerUnits|markerWidth|mask|maskContentUnits|maskUnits|mathematical|mode|numOctaves|offset|opacity|operator|order|orient|orientation|origin|overflow|overlinePosition|overlineThickness|panose1|paintOrder|pathLength|patternContentUnits|patternTransform|patternUnits|pointerEvents|points|pointsAtX|pointsAtY|pointsAtZ|preserveAlpha|preserveAspectRatio|primitiveUnits|r|radius|refX|refY|renderingIntent|repeatCount|repeatDur|requiredExtensions|requiredFeatures|restart|result|rotate|rx|ry|scale|seed|shapeRendering|slope|spacing|specularConstant|specularExponent|speed|spreadMethod|startOffset|stdDeviation|stemh|stemv|stitchTiles|stopColor|stopOpacity|strikethroughPosition|strikethroughThickness|string|stroke|strokeDasharray|strokeDashoffset|strokeLinecap|strokeLinejoin|strokeMiterlimit|strokeOpacity|strokeWidth|surfaceScale|systemLanguage|tableValues|targetX|targetY|textAnchor|textDecoration|textRendering|textLength|to|transform|u1|u2|underlinePosition|underlineThickness|unicode|unicodeBidi|unicodeRange|unitsPerEm|vAlphabetic|vHanging|vIdeographic|vMathematical|values|vectorEffect|version|vertAdvY|vertOriginX|vertOriginY|viewBox|viewTarget|visibility|widths|wordSpacing|writingMode|x|xHeight|x1|x2|xChannelSelector|xlinkActuate|xlinkArcrole|xlinkHref|xlinkRole|xlinkShow|xlinkTitle|xlinkType|xmlBase|xmlns|xmlnsXlink|xmlLang|xmlSpace|y|y1|y2|yChannelSelector|z|zoomAndPan|for|class|autofocus)|(([Dd][Aa][Tt][Aa]|[Aa][Rr][Ii][Aa]|x)-.*))$/;
|
|
66
|
+
var index = memoize_esm_default(
|
|
67
|
+
function(prop) {
|
|
68
|
+
return reactPropsRegex.test(prop) || prop.charCodeAt(0) === 111 && prop.charCodeAt(1) === 110 && prop.charCodeAt(2) < 91;
|
|
69
|
+
}
|
|
70
|
+
/* Z+1 */
|
|
71
|
+
);
|
|
72
|
+
var is_prop_valid_esm_default = index;
|
|
73
|
+
|
|
74
|
+
// ../../foundation/primitives-web/src/filterDOMProps.ts
|
|
75
|
+
var ADDITIONAL_BLOCKED_PROPS = /* @__PURE__ */ new Set([
|
|
76
|
+
// RN-only event handlers (pass isPropValid's on* pattern)
|
|
77
|
+
"onPress",
|
|
78
|
+
"onChangeText",
|
|
79
|
+
"onLayout",
|
|
80
|
+
"onMoveShouldSetResponder",
|
|
81
|
+
"onResponderGrant",
|
|
82
|
+
"onResponderMove",
|
|
83
|
+
"onResponderRelease",
|
|
84
|
+
"onResponderTerminate",
|
|
85
|
+
// SVG attributes that pass isPropValid
|
|
86
|
+
"strokeWidth",
|
|
87
|
+
// CSS properties that pass isPropValid but are used as component props
|
|
88
|
+
"overflow",
|
|
89
|
+
"cursor",
|
|
90
|
+
"fontSize",
|
|
91
|
+
"fontWeight",
|
|
92
|
+
"fontFamily",
|
|
93
|
+
"textDecoration"
|
|
94
|
+
]);
|
|
95
|
+
function shouldForwardProp(key) {
|
|
96
|
+
if (ADDITIONAL_BLOCKED_PROPS.has(key)) return false;
|
|
97
|
+
return is_prop_valid_esm_default(key);
|
|
98
|
+
}
|
|
99
|
+
function createFilteredElement(defaultTag) {
|
|
100
|
+
const Component = import_react.default.forwardRef(
|
|
101
|
+
({ children, elementType, ...props }, ref) => {
|
|
102
|
+
const Tag = elementType || defaultTag;
|
|
103
|
+
const htmlProps = {};
|
|
104
|
+
for (const key of Object.keys(props)) {
|
|
105
|
+
if (shouldForwardProp(key)) {
|
|
106
|
+
htmlProps[key] = props[key];
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return import_react.default.createElement(
|
|
110
|
+
Tag,
|
|
111
|
+
{ ref, ...htmlProps },
|
|
112
|
+
children
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
);
|
|
116
|
+
Component.displayName = `Filtered(${defaultTag})`;
|
|
117
|
+
return Component;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ../../foundation/primitives-web/src/Box.tsx
|
|
121
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
122
|
+
var FilteredDiv = createFilteredElement("div");
|
|
123
|
+
var StyledBox = (0, import_styled_components.default)(FilteredDiv)`
|
|
124
|
+
display: flex;
|
|
125
|
+
box-sizing: border-box;
|
|
126
|
+
background-color: ${(props) => props.backgroundColor || "transparent"};
|
|
127
|
+
border-color: ${(props) => props.borderColor || "transparent"};
|
|
128
|
+
border-width: ${(props) => typeof props.borderWidth === "number" ? `${props.borderWidth}px` : props.borderWidth || 0};
|
|
129
|
+
|
|
130
|
+
${(props) => props.borderBottomWidth !== void 0 && `
|
|
131
|
+
border-bottom-width: ${typeof props.borderBottomWidth === "number" ? `${props.borderBottomWidth}px` : props.borderBottomWidth};
|
|
132
|
+
border-bottom-color: ${props.borderBottomColor || props.borderColor || "transparent"};
|
|
133
|
+
border-bottom-style: solid;
|
|
134
|
+
`}
|
|
135
|
+
${(props) => props.borderTopWidth !== void 0 && `
|
|
136
|
+
border-top-width: ${typeof props.borderTopWidth === "number" ? `${props.borderTopWidth}px` : props.borderTopWidth};
|
|
137
|
+
border-top-color: ${props.borderTopColor || props.borderColor || "transparent"};
|
|
138
|
+
border-top-style: solid;
|
|
139
|
+
`}
|
|
140
|
+
${(props) => props.borderLeftWidth !== void 0 && `
|
|
141
|
+
border-left-width: ${typeof props.borderLeftWidth === "number" ? `${props.borderLeftWidth}px` : props.borderLeftWidth};
|
|
142
|
+
border-left-color: ${props.borderLeftColor || props.borderColor || "transparent"};
|
|
143
|
+
border-left-style: solid;
|
|
144
|
+
`}
|
|
145
|
+
${(props) => props.borderRightWidth !== void 0 && `
|
|
146
|
+
border-right-width: ${typeof props.borderRightWidth === "number" ? `${props.borderRightWidth}px` : props.borderRightWidth};
|
|
147
|
+
border-right-color: ${props.borderRightColor || props.borderColor || "transparent"};
|
|
148
|
+
border-right-style: solid;
|
|
149
|
+
`}
|
|
150
|
+
|
|
151
|
+
border-style: ${(props) => props.borderStyle || (props.borderWidth || props.borderBottomWidth || props.borderTopWidth || props.borderLeftWidth || props.borderRightWidth ? "solid" : "none")};
|
|
152
|
+
border-radius: ${(props) => typeof props.borderRadius === "number" ? `${props.borderRadius}px` : props.borderRadius || 0};
|
|
153
|
+
height: ${(props) => typeof props.height === "number" ? `${props.height}px` : props.height || "auto"};
|
|
154
|
+
width: ${(props) => typeof props.width === "number" ? `${props.width}px` : props.width || "auto"};
|
|
155
|
+
min-width: ${(props) => typeof props.minWidth === "number" ? `${props.minWidth}px` : props.minWidth || "auto"};
|
|
156
|
+
min-height: ${(props) => typeof props.minHeight === "number" ? `${props.minHeight}px` : props.minHeight || "auto"};
|
|
157
|
+
max-width: ${(props) => typeof props.maxWidth === "number" ? `${props.maxWidth}px` : props.maxWidth || "none"};
|
|
158
|
+
max-height: ${(props) => typeof props.maxHeight === "number" ? `${props.maxHeight}px` : props.maxHeight || "none"};
|
|
159
|
+
|
|
160
|
+
padding: ${(props) => typeof props.padding === "number" ? `${props.padding}px` : props.padding || 0};
|
|
161
|
+
${(props) => props.paddingHorizontal && `
|
|
162
|
+
padding-left: ${typeof props.paddingHorizontal === "number" ? `${props.paddingHorizontal}px` : props.paddingHorizontal};
|
|
163
|
+
padding-right: ${typeof props.paddingHorizontal === "number" ? `${props.paddingHorizontal}px` : props.paddingHorizontal};
|
|
164
|
+
`}
|
|
165
|
+
${(props) => props.paddingVertical && `
|
|
166
|
+
padding-top: ${typeof props.paddingVertical === "number" ? `${props.paddingVertical}px` : props.paddingVertical};
|
|
167
|
+
padding-bottom: ${typeof props.paddingVertical === "number" ? `${props.paddingVertical}px` : props.paddingVertical};
|
|
168
|
+
`}
|
|
169
|
+
${(props) => props.paddingTop !== void 0 && `padding-top: ${typeof props.paddingTop === "number" ? `${props.paddingTop}px` : props.paddingTop};`}
|
|
170
|
+
${(props) => props.paddingBottom !== void 0 && `padding-bottom: ${typeof props.paddingBottom === "number" ? `${props.paddingBottom}px` : props.paddingBottom};`}
|
|
171
|
+
${(props) => props.paddingLeft !== void 0 && `padding-left: ${typeof props.paddingLeft === "number" ? `${props.paddingLeft}px` : props.paddingLeft};`}
|
|
172
|
+
${(props) => props.paddingRight !== void 0 && `padding-right: ${typeof props.paddingRight === "number" ? `${props.paddingRight}px` : props.paddingRight};`}
|
|
173
|
+
|
|
174
|
+
margin: ${(props) => typeof props.margin === "number" ? `${props.margin}px` : props.margin || 0};
|
|
175
|
+
${(props) => props.marginTop !== void 0 && `margin-top: ${typeof props.marginTop === "number" ? `${props.marginTop}px` : props.marginTop};`}
|
|
176
|
+
${(props) => props.marginBottom !== void 0 && `margin-bottom: ${typeof props.marginBottom === "number" ? `${props.marginBottom}px` : props.marginBottom};`}
|
|
177
|
+
${(props) => props.marginLeft !== void 0 && `margin-left: ${typeof props.marginLeft === "number" ? `${props.marginLeft}px` : props.marginLeft};`}
|
|
178
|
+
${(props) => props.marginRight !== void 0 && `margin-right: ${typeof props.marginRight === "number" ? `${props.marginRight}px` : props.marginRight};`}
|
|
179
|
+
|
|
180
|
+
flex-direction: ${(props) => props.flexDirection || "column"};
|
|
181
|
+
flex-wrap: ${(props) => props.flexWrap || "nowrap"};
|
|
182
|
+
align-items: ${(props) => props.alignItems || "stretch"};
|
|
183
|
+
justify-content: ${(props) => props.justifyContent || "flex-start"};
|
|
184
|
+
cursor: ${(props) => props.cursor ? props.cursor : props.onClick || props.onPress ? "pointer" : "inherit"};
|
|
185
|
+
position: ${(props) => props.position || "static"};
|
|
186
|
+
top: ${(props) => typeof props.top === "number" ? `${props.top}px` : props.top};
|
|
187
|
+
bottom: ${(props) => typeof props.bottom === "number" ? `${props.bottom}px` : props.bottom};
|
|
188
|
+
left: ${(props) => typeof props.left === "number" ? `${props.left}px` : props.left};
|
|
189
|
+
right: ${(props) => typeof props.right === "number" ? `${props.right}px` : props.right};
|
|
190
|
+
flex: ${(props) => props.flex};
|
|
191
|
+
flex-shrink: ${(props) => props.flexShrink ?? 1};
|
|
192
|
+
gap: ${(props) => typeof props.gap === "number" ? `${props.gap}px` : props.gap || 0};
|
|
193
|
+
align-self: ${(props) => props.alignSelf || "auto"};
|
|
194
|
+
overflow: ${(props) => props.overflow || "visible"};
|
|
195
|
+
overflow-x: ${(props) => props.overflowX || "visible"};
|
|
196
|
+
overflow-y: ${(props) => props.overflowY || "visible"};
|
|
197
|
+
z-index: ${(props) => props.zIndex};
|
|
198
|
+
opacity: ${(props) => props.disabled ? 0.5 : 1};
|
|
199
|
+
pointer-events: ${(props) => props.disabled ? "none" : "auto"};
|
|
200
|
+
|
|
201
|
+
&:hover {
|
|
202
|
+
${(props) => props.hoverStyle?.backgroundColor && `background-color: ${props.hoverStyle.backgroundColor};`}
|
|
203
|
+
${(props) => props.hoverStyle?.borderColor && `border-color: ${props.hoverStyle.borderColor};`}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
&:active {
|
|
207
|
+
${(props) => props.pressStyle?.backgroundColor && `background-color: ${props.pressStyle.backgroundColor};`}
|
|
208
|
+
}
|
|
209
|
+
`;
|
|
210
|
+
var Box = import_react2.default.forwardRef(
|
|
211
|
+
({
|
|
212
|
+
children,
|
|
213
|
+
onPress,
|
|
214
|
+
onKeyDown,
|
|
215
|
+
onKeyUp,
|
|
216
|
+
role,
|
|
217
|
+
"aria-label": ariaLabel,
|
|
218
|
+
"aria-labelledby": ariaLabelledBy,
|
|
219
|
+
"aria-current": ariaCurrent,
|
|
220
|
+
"aria-disabled": ariaDisabled,
|
|
221
|
+
"aria-live": ariaLive,
|
|
222
|
+
"aria-busy": ariaBusy,
|
|
223
|
+
"aria-describedby": ariaDescribedBy,
|
|
224
|
+
"aria-expanded": ariaExpanded,
|
|
225
|
+
"aria-haspopup": ariaHasPopup,
|
|
226
|
+
"aria-pressed": ariaPressed,
|
|
227
|
+
"aria-controls": ariaControls,
|
|
228
|
+
tabIndex,
|
|
229
|
+
as,
|
|
230
|
+
src,
|
|
231
|
+
alt,
|
|
232
|
+
onError,
|
|
233
|
+
onLoad,
|
|
234
|
+
type,
|
|
235
|
+
disabled,
|
|
236
|
+
id,
|
|
237
|
+
testID,
|
|
238
|
+
"data-testid": dataTestId,
|
|
239
|
+
...props
|
|
240
|
+
}, ref) => {
|
|
241
|
+
if (as === "img" && src) {
|
|
242
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
243
|
+
"img",
|
|
244
|
+
{
|
|
245
|
+
src,
|
|
246
|
+
alt: alt || "",
|
|
247
|
+
onError,
|
|
248
|
+
onLoad,
|
|
249
|
+
style: {
|
|
250
|
+
display: "block",
|
|
251
|
+
objectFit: "cover",
|
|
252
|
+
width: typeof props.width === "number" ? `${props.width}px` : props.width,
|
|
253
|
+
height: typeof props.height === "number" ? `${props.height}px` : props.height,
|
|
254
|
+
borderRadius: typeof props.borderRadius === "number" ? `${props.borderRadius}px` : props.borderRadius,
|
|
255
|
+
position: props.position,
|
|
256
|
+
top: typeof props.top === "number" ? `${props.top}px` : props.top,
|
|
257
|
+
left: typeof props.left === "number" ? `${props.left}px` : props.left,
|
|
258
|
+
right: typeof props.right === "number" ? `${props.right}px` : props.right,
|
|
259
|
+
bottom: typeof props.bottom === "number" ? `${props.bottom}px` : props.bottom,
|
|
260
|
+
...props.style
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
266
|
+
StyledBox,
|
|
267
|
+
{
|
|
268
|
+
ref,
|
|
269
|
+
elementType: as,
|
|
270
|
+
id,
|
|
271
|
+
type: as === "button" ? type || "button" : void 0,
|
|
272
|
+
disabled: as === "button" ? disabled : void 0,
|
|
273
|
+
onClick: onPress,
|
|
274
|
+
onKeyDown,
|
|
275
|
+
onKeyUp,
|
|
276
|
+
role,
|
|
277
|
+
"aria-label": ariaLabel,
|
|
278
|
+
"aria-labelledby": ariaLabelledBy,
|
|
279
|
+
"aria-current": ariaCurrent,
|
|
280
|
+
"aria-disabled": ariaDisabled,
|
|
281
|
+
"aria-busy": ariaBusy,
|
|
282
|
+
"aria-describedby": ariaDescribedBy,
|
|
283
|
+
"aria-expanded": ariaExpanded,
|
|
284
|
+
"aria-haspopup": ariaHasPopup,
|
|
285
|
+
"aria-pressed": ariaPressed,
|
|
286
|
+
"aria-controls": ariaControls,
|
|
287
|
+
"aria-live": ariaLive,
|
|
288
|
+
tabIndex: tabIndex !== void 0 ? tabIndex : void 0,
|
|
289
|
+
"data-testid": dataTestId || testID,
|
|
290
|
+
...props,
|
|
291
|
+
children
|
|
292
|
+
}
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
);
|
|
296
|
+
Box.displayName = "Box";
|
|
297
|
+
|
|
298
|
+
// src/ContextMenu.tsx
|
|
299
|
+
var import_xui_core3 = require("@xsolla/xui-core");
|
|
47
300
|
var import_xui_spinner = require("@xsolla/xui-spinner");
|
|
48
301
|
|
|
49
302
|
// src/ContextMenuContext.tsx
|
|
50
|
-
var
|
|
51
|
-
var ContextMenuContext = (0,
|
|
303
|
+
var import_react3 = require("react");
|
|
304
|
+
var ContextMenuContext = (0, import_react3.createContext)(void 0);
|
|
52
305
|
var useContextMenu = () => {
|
|
53
|
-
const context = (0,
|
|
306
|
+
const context = (0, import_react3.useContext)(ContextMenuContext);
|
|
54
307
|
return context;
|
|
55
308
|
};
|
|
56
309
|
var useContextMenuRequired = () => {
|
|
57
|
-
const context = (0,
|
|
310
|
+
const context = (0, import_react3.useContext)(ContextMenuContext);
|
|
58
311
|
if (!context) {
|
|
59
312
|
throw new Error(
|
|
60
313
|
"useContextMenuRequired must be used within a ContextMenu component"
|
|
@@ -64,14 +317,14 @@ var useContextMenuRequired = () => {
|
|
|
64
317
|
};
|
|
65
318
|
|
|
66
319
|
// src/ContextMenuItem.tsx
|
|
67
|
-
var
|
|
320
|
+
var import_react4 = __toESM(require("react"));
|
|
68
321
|
var import_react_dom = require("react-dom");
|
|
69
322
|
var import_xui_core = require("@xsolla/xui-core");
|
|
70
323
|
var import_xui_typography = require("@xsolla/xui-typography");
|
|
71
324
|
var import_xui_checkbox = require("@xsolla/xui-checkbox");
|
|
72
325
|
var import_xui_radio = require("@xsolla/xui-radio");
|
|
73
326
|
var import_xui_icons_base = require("@xsolla/xui-icons-base");
|
|
74
|
-
var
|
|
327
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
75
328
|
var sizeToVariants = {
|
|
76
329
|
xl: { label: "bodyLg", description: "bodyLg", headingAccent: "bodyLgAccent" },
|
|
77
330
|
lg: { label: "bodyLg", description: "bodyMd", headingAccent: "bodyMdAccent" },
|
|
@@ -81,18 +334,51 @@ var sizeToVariants = {
|
|
|
81
334
|
var sizeLabelOverride = {
|
|
82
335
|
xl: { fontSize: 20, lineHeight: "26px" }
|
|
83
336
|
};
|
|
337
|
+
var SUBMENU_GAP = 8;
|
|
338
|
+
var SUBMENU_VIEWPORT_PADDING = 8;
|
|
339
|
+
var clipsOverflow = (style) => {
|
|
340
|
+
return /(auto|scroll|hidden|clip)/.test(
|
|
341
|
+
`${style.overflow}${style.overflowX}${style.overflowY}`
|
|
342
|
+
);
|
|
343
|
+
};
|
|
344
|
+
var getSubmenuBoundary = (node) => {
|
|
345
|
+
const viewport = {
|
|
346
|
+
top: SUBMENU_VIEWPORT_PADDING,
|
|
347
|
+
right: window.innerWidth - SUBMENU_VIEWPORT_PADDING,
|
|
348
|
+
bottom: window.innerHeight - SUBMENU_VIEWPORT_PADDING,
|
|
349
|
+
left: SUBMENU_VIEWPORT_PADDING
|
|
350
|
+
};
|
|
351
|
+
let current = node.parentElement;
|
|
352
|
+
while (current && current !== document.body) {
|
|
353
|
+
const style = window.getComputedStyle(current);
|
|
354
|
+
if (clipsOverflow(style) && current.getAttribute("role") !== "menu") {
|
|
355
|
+
const rect = current.getBoundingClientRect();
|
|
356
|
+
return {
|
|
357
|
+
top: Math.max(viewport.top, rect.top + SUBMENU_VIEWPORT_PADDING),
|
|
358
|
+
right: Math.min(viewport.right, rect.right - SUBMENU_VIEWPORT_PADDING),
|
|
359
|
+
bottom: Math.min(
|
|
360
|
+
viewport.bottom,
|
|
361
|
+
rect.bottom - SUBMENU_VIEWPORT_PADDING
|
|
362
|
+
),
|
|
363
|
+
left: Math.max(viewport.left, rect.left + SUBMENU_VIEWPORT_PADDING)
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
current = current.parentElement;
|
|
367
|
+
}
|
|
368
|
+
return viewport;
|
|
369
|
+
};
|
|
84
370
|
var ContextMenuItem = (props) => {
|
|
85
|
-
if (props.type === "option") return /* @__PURE__ */ (0,
|
|
86
|
-
if (props.type === "heading") return /* @__PURE__ */ (0,
|
|
87
|
-
if (props.type === "divider") return /* @__PURE__ */ (0,
|
|
88
|
-
if (props.type === "search") return /* @__PURE__ */ (0,
|
|
371
|
+
if (props.type === "option") return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(OptionCell, { ...props });
|
|
372
|
+
if (props.type === "heading") return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(HeadingCell, { ...props });
|
|
373
|
+
if (props.type === "divider") return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(DividerCell, { ...props });
|
|
374
|
+
if (props.type === "search") return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SearchCell, { ...props });
|
|
89
375
|
return null;
|
|
90
376
|
};
|
|
91
377
|
ContextMenuItem.displayName = "ContextMenuItem";
|
|
92
378
|
var SubmenuChevron = ({
|
|
93
379
|
color,
|
|
94
380
|
size
|
|
95
|
-
}) => /* @__PURE__ */ (0,
|
|
381
|
+
}) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
96
382
|
"span",
|
|
97
383
|
{
|
|
98
384
|
"data-testid": "ctxmenu-submenu-chevron",
|
|
@@ -104,7 +390,7 @@ var SubmenuChevron = ({
|
|
|
104
390
|
width: size,
|
|
105
391
|
height: size
|
|
106
392
|
},
|
|
107
|
-
children: /* @__PURE__ */ (0,
|
|
393
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
108
394
|
"svg",
|
|
109
395
|
{
|
|
110
396
|
width: size,
|
|
@@ -112,7 +398,7 @@ var SubmenuChevron = ({
|
|
|
112
398
|
viewBox: "0 0 24 24",
|
|
113
399
|
fill: "none",
|
|
114
400
|
xmlns: "http://www.w3.org/2000/svg",
|
|
115
|
-
children: /* @__PURE__ */ (0,
|
|
401
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
116
402
|
"path",
|
|
117
403
|
{
|
|
118
404
|
d: "M17.0605 11.6464C17.2558 11.8417 17.2558 12.1583 17.0605 12.3536L9.70703 19.707L8.29297 18.293L14.5859 12L8.29297 5.70703L9.70703 4.29297L17.0605 11.6464Z",
|
|
@@ -134,6 +420,7 @@ var OptionCell = ({
|
|
|
134
420
|
leadingIcon,
|
|
135
421
|
status,
|
|
136
422
|
iconWrapper,
|
|
423
|
+
slot,
|
|
137
424
|
slotContent,
|
|
138
425
|
value,
|
|
139
426
|
hint,
|
|
@@ -142,12 +429,17 @@ var OptionCell = ({
|
|
|
142
429
|
hasSubmenu,
|
|
143
430
|
submenu,
|
|
144
431
|
onSelect,
|
|
432
|
+
onCheckedChange,
|
|
145
433
|
testID,
|
|
146
434
|
themeMode,
|
|
147
435
|
themeProductContext,
|
|
148
436
|
"data-testid": testId
|
|
149
437
|
}) => {
|
|
150
|
-
const { theme } = (0, import_xui_core.useResolvedTheme)({
|
|
438
|
+
const { theme: rawTheme } = (0, import_xui_core.useResolvedTheme)({
|
|
439
|
+
themeMode,
|
|
440
|
+
themeProductContext
|
|
441
|
+
});
|
|
442
|
+
const theme = rawTheme;
|
|
151
443
|
const ctx = useContextMenu();
|
|
152
444
|
const size = propSize ?? ctx?.size ?? "md";
|
|
153
445
|
const sizing = theme.sizing.contextMenu(size);
|
|
@@ -156,12 +448,12 @@ var OptionCell = ({
|
|
|
156
448
|
const registerCell = ctx?.registerCell;
|
|
157
449
|
const unregisterCell = ctx?.unregisterCell;
|
|
158
450
|
const getCellIndex = ctx?.getCellIndex;
|
|
159
|
-
const [isHovered, setIsHovered] = (0,
|
|
160
|
-
const [submenuOpen, setSubmenuOpen] = (0,
|
|
161
|
-
const [submenuPos, setSubmenuPos] = (0,
|
|
162
|
-
const optionRef =
|
|
163
|
-
const submenuWrapperRef =
|
|
164
|
-
const closeTimerRef =
|
|
451
|
+
const [isHovered, setIsHovered] = (0, import_react4.useState)(false);
|
|
452
|
+
const [submenuOpen, setSubmenuOpen] = (0, import_react4.useState)(false);
|
|
453
|
+
const [submenuPos, setSubmenuPos] = (0, import_react4.useState)(null);
|
|
454
|
+
const optionRef = import_react4.default.useRef(null);
|
|
455
|
+
const submenuWrapperRef = import_react4.default.useRef(null);
|
|
456
|
+
const closeTimerRef = import_react4.default.useRef(
|
|
165
457
|
null
|
|
166
458
|
);
|
|
167
459
|
const cancelClose = () => {
|
|
@@ -174,8 +466,8 @@ var OptionCell = ({
|
|
|
174
466
|
cancelClose();
|
|
175
467
|
closeTimerRef.current = setTimeout(() => setSubmenuOpen(false), 120);
|
|
176
468
|
};
|
|
177
|
-
(0,
|
|
178
|
-
(0,
|
|
469
|
+
(0, import_react4.useEffect)(() => () => cancelClose(), []);
|
|
470
|
+
(0, import_react4.useEffect)(() => {
|
|
179
471
|
if (!hasSubmenu || !submenuOpen) return;
|
|
180
472
|
const onMouseDown = (e) => {
|
|
181
473
|
const target = e.target;
|
|
@@ -195,7 +487,7 @@ var OptionCell = ({
|
|
|
195
487
|
document.addEventListener("mousedown", onMouseDown);
|
|
196
488
|
return () => document.removeEventListener("mousedown", onMouseDown);
|
|
197
489
|
}, [hasSubmenu, submenuOpen]);
|
|
198
|
-
(0,
|
|
490
|
+
(0, import_react4.useLayoutEffect)(() => {
|
|
199
491
|
if (!hasSubmenu || !submenuOpen) {
|
|
200
492
|
setSubmenuPos(null);
|
|
201
493
|
return;
|
|
@@ -204,7 +496,32 @@ var OptionCell = ({
|
|
|
204
496
|
const node = optionRef.current;
|
|
205
497
|
if (!node) return;
|
|
206
498
|
const rect = node.getBoundingClientRect();
|
|
207
|
-
|
|
499
|
+
const submenuRect = submenuWrapperRef.current?.getBoundingClientRect();
|
|
500
|
+
const submenuWidth = submenuRect?.width ?? 0;
|
|
501
|
+
const submenuHeight = submenuRect?.height ?? 0;
|
|
502
|
+
const boundary = getSubmenuBoundary(node);
|
|
503
|
+
const rightSideLeft = rect.right + SUBMENU_GAP;
|
|
504
|
+
const leftSideLeft = rect.left - SUBMENU_GAP - submenuWidth;
|
|
505
|
+
const opensLeft = submenuWidth > 0 && rightSideLeft + submenuWidth > boundary.right;
|
|
506
|
+
let left = opensLeft ? leftSideLeft : rightSideLeft;
|
|
507
|
+
if (submenuWidth > 0) {
|
|
508
|
+
if (opensLeft) {
|
|
509
|
+
left = Math.max(SUBMENU_VIEWPORT_PADDING, left);
|
|
510
|
+
} else {
|
|
511
|
+
left = Math.min(
|
|
512
|
+
Math.max(boundary.left, left),
|
|
513
|
+
Math.max(boundary.left, boundary.right - submenuWidth)
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
let top = rect.top;
|
|
518
|
+
if (submenuHeight > 0 && top + submenuHeight > boundary.bottom) {
|
|
519
|
+
top = boundary.bottom - submenuHeight;
|
|
520
|
+
}
|
|
521
|
+
top = Math.max(boundary.top, top);
|
|
522
|
+
setSubmenuPos(
|
|
523
|
+
(prev) => prev?.top === top && prev.left === left ? prev : { top, left }
|
|
524
|
+
);
|
|
208
525
|
};
|
|
209
526
|
update();
|
|
210
527
|
window.addEventListener("scroll", update, true);
|
|
@@ -213,10 +530,10 @@ var OptionCell = ({
|
|
|
213
530
|
window.removeEventListener("scroll", update, true);
|
|
214
531
|
window.removeEventListener("resize", update);
|
|
215
532
|
};
|
|
216
|
-
}
|
|
217
|
-
const onSelectRef =
|
|
533
|
+
});
|
|
534
|
+
const onSelectRef = import_react4.default.useRef(onSelect);
|
|
218
535
|
onSelectRef.current = onSelect;
|
|
219
|
-
(0,
|
|
536
|
+
(0, import_react4.useEffect)(() => {
|
|
220
537
|
if (!registerCell || !unregisterCell) return;
|
|
221
538
|
registerCell(id, {
|
|
222
539
|
type: "option",
|
|
@@ -224,7 +541,7 @@ var OptionCell = ({
|
|
|
224
541
|
});
|
|
225
542
|
return () => unregisterCell(id);
|
|
226
543
|
}, [registerCell, unregisterCell, id]);
|
|
227
|
-
(0,
|
|
544
|
+
(0, import_react4.useEffect)(() => {
|
|
228
545
|
if (!registerCell) return;
|
|
229
546
|
registerCell(id, {
|
|
230
547
|
type: "option",
|
|
@@ -232,12 +549,12 @@ var OptionCell = ({
|
|
|
232
549
|
onSelect: () => onSelectRef.current?.()
|
|
233
550
|
});
|
|
234
551
|
}, [registerCell, id, disabled]);
|
|
235
|
-
const
|
|
236
|
-
const isActive = ctx ?
|
|
552
|
+
const index2 = getCellIndex ? getCellIndex(id) : -1;
|
|
553
|
+
const isActive = ctx ? index2 >= 0 && ctx.activeIndex === index2 : false;
|
|
237
554
|
const inHoverState = isActive || !ctx && isHovered || hasSubmenu && submenuOpen;
|
|
238
555
|
const handleEnter = () => {
|
|
239
556
|
if (disabled) return;
|
|
240
|
-
if (ctx &&
|
|
557
|
+
if (ctx && index2 >= 0) ctx.setActiveIndex(index2);
|
|
241
558
|
if (!ctx) setIsHovered(true);
|
|
242
559
|
if (hasSubmenu) setSubmenuOpen(true);
|
|
243
560
|
};
|
|
@@ -247,7 +564,7 @@ var OptionCell = ({
|
|
|
247
564
|
};
|
|
248
565
|
const labelColor = disabled ? theme.colors.control.input.textDisable : destructive ? theme.colors.content.alert.primary : theme.colors.content.primary;
|
|
249
566
|
const bg = inHoverState ? theme.colors.control.input.bgHover : "transparent";
|
|
250
|
-
const role = !hasSubmenu && checked !== void 0 ? "menuitemcheckbox" : "menuitem";
|
|
567
|
+
const role = !hasSubmenu && checked !== void 0 ? leadingControl === "radio" ? "menuitemradio" : "menuitemcheckbox" : "menuitem";
|
|
251
568
|
const ariaChecked = !hasSubmenu && checked !== void 0 ? checked ? "true" : "false" : void 0;
|
|
252
569
|
const handleClick = () => {
|
|
253
570
|
if (disabled) return;
|
|
@@ -255,6 +572,9 @@ var OptionCell = ({
|
|
|
255
572
|
setSubmenuOpen(true);
|
|
256
573
|
return;
|
|
257
574
|
}
|
|
575
|
+
if (checked !== void 0) {
|
|
576
|
+
onCheckedChange?.(!checked);
|
|
577
|
+
}
|
|
258
578
|
onSelect?.();
|
|
259
579
|
};
|
|
260
580
|
const closeSubmenuAndFocus = () => {
|
|
@@ -284,10 +604,13 @@ var OptionCell = ({
|
|
|
284
604
|
}
|
|
285
605
|
if (e.key === "Enter" || e.key === " ") {
|
|
286
606
|
e.preventDefault();
|
|
607
|
+
if (checked !== void 0) {
|
|
608
|
+
onCheckedChange?.(!checked);
|
|
609
|
+
}
|
|
287
610
|
onSelect?.();
|
|
288
611
|
}
|
|
289
612
|
};
|
|
290
|
-
return /* @__PURE__ */ (0,
|
|
613
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
291
614
|
"div",
|
|
292
615
|
{
|
|
293
616
|
ref: optionRef,
|
|
@@ -327,13 +650,13 @@ var OptionCell = ({
|
|
|
327
650
|
outline: "none"
|
|
328
651
|
},
|
|
329
652
|
children: [
|
|
330
|
-
leadingControl === "checkbox" && /* @__PURE__ */ (0,
|
|
653
|
+
leadingControl === "checkbox" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
331
654
|
"span",
|
|
332
655
|
{
|
|
333
656
|
"data-testid": "ctxmenu-leading-checkbox",
|
|
334
657
|
"aria-hidden": "true",
|
|
335
658
|
style: { pointerEvents: "none", display: "inline-flex" },
|
|
336
|
-
children: /* @__PURE__ */ (0,
|
|
659
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
337
660
|
import_xui_checkbox.Checkbox,
|
|
338
661
|
{
|
|
339
662
|
size,
|
|
@@ -345,13 +668,13 @@ var OptionCell = ({
|
|
|
345
668
|
)
|
|
346
669
|
}
|
|
347
670
|
),
|
|
348
|
-
leadingControl === "radio" && /* @__PURE__ */ (0,
|
|
671
|
+
leadingControl === "radio" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
349
672
|
"span",
|
|
350
673
|
{
|
|
351
674
|
"data-testid": "ctxmenu-leading-radio",
|
|
352
675
|
"aria-hidden": "true",
|
|
353
676
|
style: { pointerEvents: "none", display: "inline-flex" },
|
|
354
|
-
children: /* @__PURE__ */ (0,
|
|
677
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
355
678
|
import_xui_radio.Radio,
|
|
356
679
|
{
|
|
357
680
|
size,
|
|
@@ -366,8 +689,9 @@ var OptionCell = ({
|
|
|
366
689
|
leadingIcon,
|
|
367
690
|
status,
|
|
368
691
|
iconWrapper,
|
|
692
|
+
slot,
|
|
369
693
|
slotContent,
|
|
370
|
-
/* @__PURE__ */ (0,
|
|
694
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
371
695
|
"span",
|
|
372
696
|
{
|
|
373
697
|
style: {
|
|
@@ -378,7 +702,7 @@ var OptionCell = ({
|
|
|
378
702
|
minWidth: 0
|
|
379
703
|
},
|
|
380
704
|
children: [
|
|
381
|
-
/* @__PURE__ */ (0,
|
|
705
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
382
706
|
import_xui_typography.Typography,
|
|
383
707
|
{
|
|
384
708
|
variant: variants.label,
|
|
@@ -391,7 +715,7 @@ var OptionCell = ({
|
|
|
391
715
|
children: label
|
|
392
716
|
}
|
|
393
717
|
),
|
|
394
|
-
description !== void 0 && /* @__PURE__ */ (0,
|
|
718
|
+
description !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
395
719
|
import_xui_typography.Typography,
|
|
396
720
|
{
|
|
397
721
|
variant: variants.description,
|
|
@@ -402,7 +726,7 @@ var OptionCell = ({
|
|
|
402
726
|
]
|
|
403
727
|
}
|
|
404
728
|
),
|
|
405
|
-
(value !== void 0 || hint !== void 0) && /* @__PURE__ */ (0,
|
|
729
|
+
(value !== void 0 || hint !== void 0) && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
406
730
|
"span",
|
|
407
731
|
{
|
|
408
732
|
style: {
|
|
@@ -411,7 +735,7 @@ var OptionCell = ({
|
|
|
411
735
|
alignItems: "flex-end"
|
|
412
736
|
},
|
|
413
737
|
children: [
|
|
414
|
-
value !== void 0 && /* @__PURE__ */ (0,
|
|
738
|
+
value !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
415
739
|
import_xui_typography.Typography,
|
|
416
740
|
{
|
|
417
741
|
variant: variants.label,
|
|
@@ -420,7 +744,7 @@ var OptionCell = ({
|
|
|
420
744
|
children: value
|
|
421
745
|
}
|
|
422
746
|
),
|
|
423
|
-
hint !== void 0 && /* @__PURE__ */ (0,
|
|
747
|
+
hint !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
424
748
|
import_xui_typography.Typography,
|
|
425
749
|
{
|
|
426
750
|
variant: variants.description,
|
|
@@ -431,7 +755,7 @@ var OptionCell = ({
|
|
|
431
755
|
]
|
|
432
756
|
}
|
|
433
757
|
),
|
|
434
|
-
keyboardShortcut && /* @__PURE__ */ (0,
|
|
758
|
+
keyboardShortcut && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
435
759
|
import_xui_typography.Typography,
|
|
436
760
|
{
|
|
437
761
|
as: "kbd",
|
|
@@ -440,7 +764,7 @@ var OptionCell = ({
|
|
|
440
764
|
children: keyboardShortcut
|
|
441
765
|
}
|
|
442
766
|
),
|
|
443
|
-
hasSubmenu && /* @__PURE__ */ (0,
|
|
767
|
+
hasSubmenu && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
444
768
|
SubmenuChevron,
|
|
445
769
|
{
|
|
446
770
|
color: theme.colors.content.tertiary,
|
|
@@ -449,7 +773,7 @@ var OptionCell = ({
|
|
|
449
773
|
),
|
|
450
774
|
trailingIcon,
|
|
451
775
|
hasSubmenu && submenuOpen && submenu && submenuPos && typeof document !== "undefined" && (0, import_react_dom.createPortal)(
|
|
452
|
-
/* @__PURE__ */ (0,
|
|
776
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
453
777
|
"div",
|
|
454
778
|
{
|
|
455
779
|
ref: submenuWrapperRef,
|
|
@@ -480,7 +804,11 @@ var HeadingCell = ({
|
|
|
480
804
|
themeProductContext,
|
|
481
805
|
"data-testid": testId
|
|
482
806
|
}) => {
|
|
483
|
-
const { theme } = (0, import_xui_core.useResolvedTheme)({
|
|
807
|
+
const { theme: rawTheme } = (0, import_xui_core.useResolvedTheme)({
|
|
808
|
+
themeMode,
|
|
809
|
+
themeProductContext
|
|
810
|
+
});
|
|
811
|
+
const theme = rawTheme;
|
|
484
812
|
const ctx = useContextMenu();
|
|
485
813
|
const size = propSize ?? ctx?.size ?? "md";
|
|
486
814
|
const sizing = theme.sizing.contextMenu(size);
|
|
@@ -488,12 +816,12 @@ var HeadingCell = ({
|
|
|
488
816
|
const id = (0, import_xui_core.useId)();
|
|
489
817
|
const registerCell = ctx?.registerCell;
|
|
490
818
|
const unregisterCell = ctx?.unregisterCell;
|
|
491
|
-
(0,
|
|
819
|
+
(0, import_react4.useEffect)(() => {
|
|
492
820
|
if (!registerCell || !unregisterCell) return;
|
|
493
821
|
registerCell(id, { type: "heading" });
|
|
494
822
|
return () => unregisterCell(id);
|
|
495
823
|
}, [registerCell, unregisterCell, id]);
|
|
496
|
-
return /* @__PURE__ */ (0,
|
|
824
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
497
825
|
"div",
|
|
498
826
|
{
|
|
499
827
|
role: "presentation",
|
|
@@ -508,7 +836,7 @@ var HeadingCell = ({
|
|
|
508
836
|
paddingBottom: sizing.itemPaddingVertical
|
|
509
837
|
},
|
|
510
838
|
children: [
|
|
511
|
-
/* @__PURE__ */ (0,
|
|
839
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
512
840
|
import_xui_typography.Typography,
|
|
513
841
|
{
|
|
514
842
|
variant: variants.headingAccent,
|
|
@@ -517,7 +845,7 @@ var HeadingCell = ({
|
|
|
517
845
|
children: label
|
|
518
846
|
}
|
|
519
847
|
),
|
|
520
|
-
description !== void 0 && /* @__PURE__ */ (0,
|
|
848
|
+
description !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
521
849
|
import_xui_typography.Typography,
|
|
522
850
|
{
|
|
523
851
|
variant: variants.description,
|
|
@@ -532,6 +860,7 @@ var HeadingCell = ({
|
|
|
532
860
|
var SearchCell = ({
|
|
533
861
|
size: propSize,
|
|
534
862
|
value,
|
|
863
|
+
onChange,
|
|
535
864
|
onValueChange,
|
|
536
865
|
placeholder = "Search",
|
|
537
866
|
autoFocus,
|
|
@@ -541,19 +870,23 @@ var SearchCell = ({
|
|
|
541
870
|
themeMode,
|
|
542
871
|
themeProductContext
|
|
543
872
|
}) => {
|
|
544
|
-
const { theme } = (0, import_xui_core.useResolvedTheme)({
|
|
873
|
+
const { theme: rawTheme } = (0, import_xui_core.useResolvedTheme)({
|
|
874
|
+
themeMode,
|
|
875
|
+
themeProductContext
|
|
876
|
+
});
|
|
877
|
+
const theme = rawTheme;
|
|
545
878
|
const ctx = useContextMenu();
|
|
546
879
|
const size = propSize ?? ctx?.size ?? "md";
|
|
547
880
|
const sizing = theme.sizing.contextMenu(size);
|
|
548
881
|
const id = (0, import_xui_core.useId)();
|
|
549
882
|
const registerCell = ctx?.registerCell;
|
|
550
883
|
const unregisterCell = ctx?.unregisterCell;
|
|
551
|
-
(0,
|
|
884
|
+
(0, import_react4.useEffect)(() => {
|
|
552
885
|
if (!registerCell || !unregisterCell) return;
|
|
553
886
|
registerCell(id, { type: "search" });
|
|
554
887
|
return () => unregisterCell(id);
|
|
555
888
|
}, [registerCell, unregisterCell, id]);
|
|
556
|
-
return /* @__PURE__ */ (0,
|
|
889
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
557
890
|
"div",
|
|
558
891
|
{
|
|
559
892
|
style: {
|
|
@@ -563,7 +896,7 @@ var SearchCell = ({
|
|
|
563
896
|
paddingTop: sizing.searchPaddingVertical,
|
|
564
897
|
paddingBottom: sizing.searchPaddingVertical
|
|
565
898
|
},
|
|
566
|
-
children: /* @__PURE__ */ (0,
|
|
899
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
567
900
|
"div",
|
|
568
901
|
{
|
|
569
902
|
style: {
|
|
@@ -574,7 +907,7 @@ var SearchCell = ({
|
|
|
574
907
|
borderBottom: `1px solid ${theme.colors.border.secondary}`
|
|
575
908
|
},
|
|
576
909
|
children: [
|
|
577
|
-
/* @__PURE__ */ (0,
|
|
910
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
578
911
|
import_xui_icons_base.Search,
|
|
579
912
|
{
|
|
580
913
|
variant: "line",
|
|
@@ -583,16 +916,19 @@ var SearchCell = ({
|
|
|
583
916
|
"aria-hidden": true
|
|
584
917
|
}
|
|
585
918
|
),
|
|
586
|
-
/* @__PURE__ */ (0,
|
|
919
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
587
920
|
"input",
|
|
588
921
|
{
|
|
589
922
|
type: "search",
|
|
590
923
|
role: "searchbox",
|
|
591
924
|
"aria-label": ariaLabel,
|
|
592
925
|
placeholder,
|
|
593
|
-
value,
|
|
926
|
+
value: value ?? "",
|
|
594
927
|
autoFocus,
|
|
595
|
-
onChange: (e) =>
|
|
928
|
+
onChange: (e) => {
|
|
929
|
+
onChange?.(e);
|
|
930
|
+
onValueChange?.(e.target.value);
|
|
931
|
+
},
|
|
596
932
|
"data-testid": testId || testID,
|
|
597
933
|
style: {
|
|
598
934
|
flex: 1,
|
|
@@ -615,17 +951,21 @@ var SearchCell = ({
|
|
|
615
951
|
);
|
|
616
952
|
};
|
|
617
953
|
var DividerCell = ({ themeMode, themeProductContext, "data-testid": testId }) => {
|
|
618
|
-
const { theme } = (0, import_xui_core.useResolvedTheme)({
|
|
954
|
+
const { theme: rawTheme } = (0, import_xui_core.useResolvedTheme)({
|
|
955
|
+
themeMode,
|
|
956
|
+
themeProductContext
|
|
957
|
+
});
|
|
958
|
+
const theme = rawTheme;
|
|
619
959
|
const ctx = useContextMenu();
|
|
620
960
|
const id = (0, import_xui_core.useId)();
|
|
621
961
|
const registerCell = ctx?.registerCell;
|
|
622
962
|
const unregisterCell = ctx?.unregisterCell;
|
|
623
|
-
(0,
|
|
963
|
+
(0, import_react4.useEffect)(() => {
|
|
624
964
|
if (!registerCell || !unregisterCell) return;
|
|
625
965
|
registerCell(id, { type: "divider" });
|
|
626
966
|
return () => unregisterCell(id);
|
|
627
967
|
}, [registerCell, unregisterCell, id]);
|
|
628
|
-
return /* @__PURE__ */ (0,
|
|
968
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
629
969
|
"div",
|
|
630
970
|
{
|
|
631
971
|
role: "separator",
|
|
@@ -639,8 +979,150 @@ var DividerCell = ({ themeMode, themeProductContext, "data-testid": testId }) =>
|
|
|
639
979
|
);
|
|
640
980
|
};
|
|
641
981
|
|
|
982
|
+
// src/ContextMenuSubmenu.tsx
|
|
983
|
+
var import_react5 = require("react");
|
|
984
|
+
var import_xui_core2 = require("@xsolla/xui-core");
|
|
985
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
986
|
+
var SUBMENU_GAP2 = 4;
|
|
987
|
+
var OPEN_DELAY_MS = 200;
|
|
988
|
+
var CLOSE_GRACE_MS = 100;
|
|
989
|
+
var ContextMenuSubmenu = ({
|
|
990
|
+
label,
|
|
991
|
+
icon,
|
|
992
|
+
disabled,
|
|
993
|
+
children,
|
|
994
|
+
size: propSize,
|
|
995
|
+
"data-testid": testId = "context-menu-submenu"
|
|
996
|
+
}) => {
|
|
997
|
+
const { theme } = (0, import_xui_core2.useDesignSystem)();
|
|
998
|
+
const xuiTheme = theme;
|
|
999
|
+
const context = useContextMenu();
|
|
1000
|
+
const size = propSize || context?.size || "md";
|
|
1001
|
+
const sizeStyles = xuiTheme.sizing.contextMenu(size);
|
|
1002
|
+
const borderRadius = xuiTheme.shape?.contextMenu?.[size]?.borderRadius ?? xuiTheme.radius?.button ?? 8;
|
|
1003
|
+
const [isOpen, setIsOpen] = (0, import_react5.useState)(false);
|
|
1004
|
+
const [visible, setVisible] = (0, import_react5.useState)(false);
|
|
1005
|
+
const [openLeft, setOpenLeft] = (0, import_react5.useState)(false);
|
|
1006
|
+
const [topOffset, setTopOffset] = (0, import_react5.useState)(0);
|
|
1007
|
+
const triggerRef = (0, import_react5.useRef)(null);
|
|
1008
|
+
const submenuRef = (0, import_react5.useRef)(null);
|
|
1009
|
+
const openTimerRef = (0, import_react5.useRef)(null);
|
|
1010
|
+
const closeTimerRef = (0, import_react5.useRef)(null);
|
|
1011
|
+
const clearTimers = (0, import_react5.useCallback)(() => {
|
|
1012
|
+
if (openTimerRef.current) clearTimeout(openTimerRef.current);
|
|
1013
|
+
if (closeTimerRef.current) clearTimeout(closeTimerRef.current);
|
|
1014
|
+
}, []);
|
|
1015
|
+
const calculatePlacement = (0, import_react5.useCallback)(() => {
|
|
1016
|
+
if (!triggerRef.current) return;
|
|
1017
|
+
const triggerRect = triggerRef.current.getBoundingClientRect();
|
|
1018
|
+
const estimatedWidth = sizeStyles.minWidth + 32;
|
|
1019
|
+
const wouldOverflowRight = triggerRect.right + estimatedWidth + SUBMENU_GAP2 > window.innerWidth - 8;
|
|
1020
|
+
setOpenLeft(wouldOverflowRight);
|
|
1021
|
+
setTopOffset(0);
|
|
1022
|
+
}, [sizeStyles.minWidth]);
|
|
1023
|
+
(0, import_react5.useLayoutEffect)(() => {
|
|
1024
|
+
if (!isOpen || !submenuRef.current || !triggerRef.current) return;
|
|
1025
|
+
const submenuRect = submenuRef.current.getBoundingClientRect();
|
|
1026
|
+
const triggerRect = triggerRef.current.getBoundingClientRect();
|
|
1027
|
+
const wouldOverflowRight = triggerRect.right + submenuRect.width + SUBMENU_GAP2 > window.innerWidth - 8;
|
|
1028
|
+
setOpenLeft(wouldOverflowRight);
|
|
1029
|
+
const overflowBottom = triggerRect.top + submenuRect.height - (window.innerHeight - 8);
|
|
1030
|
+
if (overflowBottom > 0) {
|
|
1031
|
+
setTopOffset(-Math.min(overflowBottom, triggerRect.top - 8));
|
|
1032
|
+
} else {
|
|
1033
|
+
setTopOffset(0);
|
|
1034
|
+
}
|
|
1035
|
+
}, [isOpen]);
|
|
1036
|
+
(0, import_react5.useEffect)(() => {
|
|
1037
|
+
if (!isOpen) {
|
|
1038
|
+
setVisible(false);
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
const raf = requestAnimationFrame(() => setVisible(true));
|
|
1042
|
+
return () => cancelAnimationFrame(raf);
|
|
1043
|
+
}, [isOpen]);
|
|
1044
|
+
(0, import_react5.useEffect)(() => () => clearTimers(), [clearTimers]);
|
|
1045
|
+
const handleTriggerEnter = () => {
|
|
1046
|
+
if (disabled) return;
|
|
1047
|
+
clearTimers();
|
|
1048
|
+
openTimerRef.current = setTimeout(() => {
|
|
1049
|
+
calculatePlacement();
|
|
1050
|
+
setIsOpen(true);
|
|
1051
|
+
}, OPEN_DELAY_MS);
|
|
1052
|
+
};
|
|
1053
|
+
const handleTriggerLeave = () => {
|
|
1054
|
+
clearTimers();
|
|
1055
|
+
closeTimerRef.current = setTimeout(() => setIsOpen(false), CLOSE_GRACE_MS);
|
|
1056
|
+
};
|
|
1057
|
+
const handleSubmenuEnter = () => clearTimers();
|
|
1058
|
+
const handleSubmenuLeave = () => {
|
|
1059
|
+
clearTimers();
|
|
1060
|
+
closeTimerRef.current = setTimeout(() => setIsOpen(false), CLOSE_GRACE_MS);
|
|
1061
|
+
};
|
|
1062
|
+
const submenuPositionStyle = openLeft ? { right: `calc(100% + ${SUBMENU_GAP2}px)` } : { left: `calc(100% + ${SUBMENU_GAP2}px)` };
|
|
1063
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
1064
|
+
"div",
|
|
1065
|
+
{
|
|
1066
|
+
ref: triggerRef,
|
|
1067
|
+
style: { position: "relative" },
|
|
1068
|
+
onMouseEnter: handleTriggerEnter,
|
|
1069
|
+
onMouseLeave: handleTriggerLeave,
|
|
1070
|
+
"data-testid": testId,
|
|
1071
|
+
children: [
|
|
1072
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1073
|
+
ContextMenuItem,
|
|
1074
|
+
{
|
|
1075
|
+
type: "option",
|
|
1076
|
+
label,
|
|
1077
|
+
leadingIcon: icon,
|
|
1078
|
+
disabled,
|
|
1079
|
+
hasSubmenu: true,
|
|
1080
|
+
size,
|
|
1081
|
+
"data-testid": `${testId}-trigger`
|
|
1082
|
+
}
|
|
1083
|
+
),
|
|
1084
|
+
isOpen && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1085
|
+
"div",
|
|
1086
|
+
{
|
|
1087
|
+
ref: submenuRef,
|
|
1088
|
+
onMouseEnter: handleSubmenuEnter,
|
|
1089
|
+
onMouseLeave: handleSubmenuLeave,
|
|
1090
|
+
style: {
|
|
1091
|
+
position: "absolute",
|
|
1092
|
+
top: topOffset,
|
|
1093
|
+
...submenuPositionStyle,
|
|
1094
|
+
zIndex: 1001,
|
|
1095
|
+
opacity: visible ? 1 : 0,
|
|
1096
|
+
transform: visible ? "translateX(0)" : openLeft ? "translateX(4px)" : "translateX(-4px)",
|
|
1097
|
+
transition: "opacity 100ms ease, transform 100ms ease"
|
|
1098
|
+
},
|
|
1099
|
+
"data-testid": `${testId}-content`,
|
|
1100
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1101
|
+
Box,
|
|
1102
|
+
{
|
|
1103
|
+
role: "menu",
|
|
1104
|
+
backgroundColor: xuiTheme.colors.background.secondary,
|
|
1105
|
+
borderColor: xuiTheme.colors.border.secondary,
|
|
1106
|
+
borderWidth: 1,
|
|
1107
|
+
borderRadius,
|
|
1108
|
+
paddingVertical: sizeStyles.paddingVertical,
|
|
1109
|
+
style: {
|
|
1110
|
+
boxShadow: "0 4px 12px rgba(0,0,0,0.15)",
|
|
1111
|
+
minWidth: sizeStyles.minWidth
|
|
1112
|
+
},
|
|
1113
|
+
children
|
|
1114
|
+
}
|
|
1115
|
+
)
|
|
1116
|
+
}
|
|
1117
|
+
)
|
|
1118
|
+
]
|
|
1119
|
+
}
|
|
1120
|
+
);
|
|
1121
|
+
};
|
|
1122
|
+
ContextMenuSubmenu.displayName = "ContextMenuSubmenu";
|
|
1123
|
+
|
|
642
1124
|
// src/hooks/useContextMenuPosition.ts
|
|
643
|
-
var
|
|
1125
|
+
var import_react6 = require("react");
|
|
644
1126
|
var splitPlacement = (placement) => {
|
|
645
1127
|
const [vertical, horizontal] = placement.split("-");
|
|
646
1128
|
return { vertical, horizontal };
|
|
@@ -653,8 +1135,8 @@ var useContextMenuPosition = ({
|
|
|
653
1135
|
placement = "bottom-start",
|
|
654
1136
|
offset = 4
|
|
655
1137
|
}) => {
|
|
656
|
-
const [resolved, setResolved] = (0,
|
|
657
|
-
(0,
|
|
1138
|
+
const [resolved, setResolved] = (0, import_react6.useState)();
|
|
1139
|
+
(0, import_react6.useEffect)(() => {
|
|
658
1140
|
if (!isOpen) {
|
|
659
1141
|
setResolved(void 0);
|
|
660
1142
|
return;
|
|
@@ -701,30 +1183,19 @@ var useContextMenuPosition = ({
|
|
|
701
1183
|
(prev) => prev && prev.top === next.top && prev.left === next.left && prev.placement === next.placement ? prev : next
|
|
702
1184
|
);
|
|
703
1185
|
};
|
|
704
|
-
|
|
1186
|
+
const rafId = window.requestAnimationFrame(compute);
|
|
705
1187
|
const onResize = () => compute();
|
|
706
|
-
let scrollRafPending = false;
|
|
707
|
-
const onScroll = () => {
|
|
708
|
-
if (scrollRafPending) return;
|
|
709
|
-
scrollRafPending = true;
|
|
710
|
-
rafId = window.requestAnimationFrame(() => {
|
|
711
|
-
scrollRafPending = false;
|
|
712
|
-
compute();
|
|
713
|
-
});
|
|
714
|
-
};
|
|
715
1188
|
window.addEventListener("resize", onResize);
|
|
716
|
-
window.addEventListener("scroll", onScroll, true);
|
|
717
1189
|
return () => {
|
|
718
1190
|
window.cancelAnimationFrame(rafId);
|
|
719
1191
|
window.removeEventListener("resize", onResize);
|
|
720
|
-
window.removeEventListener("scroll", onScroll, true);
|
|
721
1192
|
};
|
|
722
1193
|
}, [isOpen, placement, offset, triggerRef, panelRef]);
|
|
723
1194
|
return resolved;
|
|
724
1195
|
};
|
|
725
1196
|
|
|
726
1197
|
// src/hooks/useKeyboardNavigation.ts
|
|
727
|
-
var
|
|
1198
|
+
var import_react7 = require("react");
|
|
728
1199
|
var isNavigableOption = (meta) => meta.type === "option" && !meta.disabled;
|
|
729
1200
|
var isTextInputTarget = (target) => {
|
|
730
1201
|
if (!(target instanceof HTMLElement)) return false;
|
|
@@ -739,19 +1210,19 @@ var useKeyboardNavigation = ({
|
|
|
739
1210
|
onClose,
|
|
740
1211
|
triggerRef
|
|
741
1212
|
}) => {
|
|
742
|
-
const findFirstOption = (0,
|
|
1213
|
+
const findFirstOption = (0, import_react7.useCallback)(() => {
|
|
743
1214
|
for (let i = 0; i < cells.length; i += 1) {
|
|
744
1215
|
if (isNavigableOption(cells[i].meta)) return i;
|
|
745
1216
|
}
|
|
746
1217
|
return -1;
|
|
747
1218
|
}, [cells]);
|
|
748
|
-
const findLastOption = (0,
|
|
1219
|
+
const findLastOption = (0, import_react7.useCallback)(() => {
|
|
749
1220
|
for (let i = cells.length - 1; i >= 0; i -= 1) {
|
|
750
1221
|
if (isNavigableOption(cells[i].meta)) return i;
|
|
751
1222
|
}
|
|
752
1223
|
return -1;
|
|
753
1224
|
}, [cells]);
|
|
754
|
-
const findNextOption = (0,
|
|
1225
|
+
const findNextOption = (0, import_react7.useCallback)(
|
|
755
1226
|
(from) => {
|
|
756
1227
|
const len = cells.length;
|
|
757
1228
|
if (len === 0) return -1;
|
|
@@ -763,7 +1234,7 @@ var useKeyboardNavigation = ({
|
|
|
763
1234
|
},
|
|
764
1235
|
[cells]
|
|
765
1236
|
);
|
|
766
|
-
const findPrevOption = (0,
|
|
1237
|
+
const findPrevOption = (0, import_react7.useCallback)(
|
|
767
1238
|
(from) => {
|
|
768
1239
|
const len = cells.length;
|
|
769
1240
|
if (len === 0) return -1;
|
|
@@ -775,7 +1246,7 @@ var useKeyboardNavigation = ({
|
|
|
775
1246
|
},
|
|
776
1247
|
[cells]
|
|
777
1248
|
);
|
|
778
|
-
const handleKeyDown = (0,
|
|
1249
|
+
const handleKeyDown = (0, import_react7.useCallback)(
|
|
779
1250
|
(event) => {
|
|
780
1251
|
if (!isOpen) return;
|
|
781
1252
|
switch (event.key) {
|
|
@@ -845,459 +1316,383 @@ var useKeyboardNavigation = ({
|
|
|
845
1316
|
};
|
|
846
1317
|
|
|
847
1318
|
// src/ContextMenu.tsx
|
|
848
|
-
var
|
|
1319
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
1320
|
+
var DEFAULT_EMPTY_MESSAGE = "No results";
|
|
849
1321
|
var SEARCH_DEBOUNCE_MS = 200;
|
|
850
|
-
var
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
"
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
1322
|
+
var textFromNode = (node) => {
|
|
1323
|
+
if (node === null || node === void 0 || typeof node === "boolean")
|
|
1324
|
+
return "";
|
|
1325
|
+
if (typeof node === "string" || typeof node === "number") return String(node);
|
|
1326
|
+
if (Array.isArray(node)) return node.map(textFromNode).join(" ");
|
|
1327
|
+
if ((0, import_react8.isValidElement)(node)) return textFromNode(node.props.children);
|
|
1328
|
+
return "";
|
|
1329
|
+
};
|
|
1330
|
+
var filterItems = (items, query) => {
|
|
1331
|
+
const normalized = query.trim().toLowerCase();
|
|
1332
|
+
if (!normalized) return [...items];
|
|
1333
|
+
const result = [];
|
|
1334
|
+
let pendingStructural = [];
|
|
1335
|
+
for (const item of items) {
|
|
1336
|
+
if (item.type === "option") {
|
|
1337
|
+
const label = textFromNode(item.label).toLowerCase();
|
|
1338
|
+
if (label.includes(normalized)) {
|
|
1339
|
+
result.push(...pendingStructural, item);
|
|
1340
|
+
pendingStructural = [];
|
|
1341
|
+
}
|
|
1342
|
+
continue;
|
|
1343
|
+
}
|
|
1344
|
+
if (item.type === "heading") {
|
|
1345
|
+
pendingStructural = [item];
|
|
1346
|
+
continue;
|
|
1347
|
+
}
|
|
1348
|
+
if (item.type === "divider") {
|
|
1349
|
+
if (result.length > 0) pendingStructural.push(item);
|
|
1350
|
+
continue;
|
|
1351
|
+
}
|
|
863
1352
|
}
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
1353
|
+
return result;
|
|
1354
|
+
};
|
|
1355
|
+
var isOption = (item) => item.type === "option";
|
|
1356
|
+
var ContextMenuRoot = (0, import_react8.forwardRef)(
|
|
1357
|
+
({
|
|
869
1358
|
children,
|
|
1359
|
+
type = "list",
|
|
1360
|
+
items = [],
|
|
870
1361
|
size = "md",
|
|
871
1362
|
searchable,
|
|
872
1363
|
loading,
|
|
873
|
-
|
|
1364
|
+
isLoading,
|
|
1365
|
+
emptyMessage = DEFAULT_EMPTY_MESSAGE,
|
|
874
1366
|
empty,
|
|
875
|
-
|
|
876
|
-
isOpen,
|
|
1367
|
+
isOpen: propIsOpen,
|
|
877
1368
|
onOpenChange,
|
|
878
|
-
|
|
879
|
-
width,
|
|
880
|
-
maxHeight,
|
|
1369
|
+
trigger,
|
|
881
1370
|
placement = "bottom-start",
|
|
1371
|
+
position,
|
|
1372
|
+
width,
|
|
1373
|
+
maxHeight = 300,
|
|
882
1374
|
onSelect,
|
|
1375
|
+
closeOnSelect,
|
|
883
1376
|
"aria-label": ariaLabel,
|
|
884
|
-
"data-testid": testId,
|
|
885
1377
|
testID,
|
|
1378
|
+
"data-testid": dataTestId,
|
|
886
1379
|
themeMode,
|
|
887
|
-
themeProductContext
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
(
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
const
|
|
924
|
-
|
|
1380
|
+
themeProductContext,
|
|
1381
|
+
style
|
|
1382
|
+
}, ref) => {
|
|
1383
|
+
const { theme } = (0, import_xui_core3.useDesignSystem)();
|
|
1384
|
+
const xuiTheme = theme;
|
|
1385
|
+
const menuId = (0, import_xui_core3.useId)();
|
|
1386
|
+
const [internalIsOpen, setInternalIsOpen] = (0, import_react8.useState)(false);
|
|
1387
|
+
const [activeIndex, setActiveIndex] = (0, import_react8.useState)(-1);
|
|
1388
|
+
const [cellsVersion, setCellsVersion] = (0, import_react8.useState)(0);
|
|
1389
|
+
const [query, setQuery] = (0, import_react8.useState)("");
|
|
1390
|
+
const [debouncedQuery, setDebouncedQuery] = (0, import_react8.useState)("");
|
|
1391
|
+
const containerRef = (0, import_react8.useRef)(null);
|
|
1392
|
+
const triggerRef = (0, import_react8.useRef)(null);
|
|
1393
|
+
const panelRef = (0, import_react8.useRef)(null);
|
|
1394
|
+
const cellsRef = (0, import_react8.useRef)([]);
|
|
1395
|
+
const isOpen = propIsOpen !== void 0 ? propIsOpen : internalIsOpen;
|
|
1396
|
+
const sizeStyles = xuiTheme.sizing.contextMenu(size);
|
|
1397
|
+
const borderRadius = xuiTheme.shape?.contextMenu?.[size]?.borderRadius ?? xuiTheme.radius?.button ?? 8;
|
|
1398
|
+
const shouldCloseOnSelect = closeOnSelect ?? (type === "checkbox" ? false : true);
|
|
1399
|
+
const positioned = useContextMenuPosition({
|
|
1400
|
+
triggerRef,
|
|
1401
|
+
panelRef,
|
|
1402
|
+
isOpen: isOpen && !!trigger && !position,
|
|
1403
|
+
placement
|
|
1404
|
+
});
|
|
1405
|
+
const setOpen = (0, import_react8.useCallback)(
|
|
1406
|
+
(nextOpen) => {
|
|
1407
|
+
if (propIsOpen === void 0) setInternalIsOpen(nextOpen);
|
|
1408
|
+
onOpenChange?.(nextOpen);
|
|
1409
|
+
if (!nextOpen) setActiveIndex(-1);
|
|
1410
|
+
},
|
|
1411
|
+
[propIsOpen, onOpenChange]
|
|
1412
|
+
);
|
|
1413
|
+
const closeMenu = (0, import_react8.useCallback)(() => {
|
|
1414
|
+
setOpen(false);
|
|
1415
|
+
}, [setOpen]);
|
|
1416
|
+
const toggleMenu = (0, import_react8.useCallback)(() => {
|
|
1417
|
+
setOpen(!isOpen);
|
|
1418
|
+
}, [isOpen, setOpen]);
|
|
1419
|
+
const registerCell = (0, import_react8.useCallback)((id, meta) => {
|
|
1420
|
+
const existingIndex = cellsRef.current.findIndex(
|
|
1421
|
+
(cell) => cell.id === id
|
|
1422
|
+
);
|
|
1423
|
+
if (existingIndex >= 0) {
|
|
1424
|
+
cellsRef.current[existingIndex] = { id, meta };
|
|
1425
|
+
setCellsVersion((version) => version + 1);
|
|
1426
|
+
return existingIndex;
|
|
1427
|
+
}
|
|
925
1428
|
cellsRef.current.push({ id, meta });
|
|
926
|
-
setCellsVersion((
|
|
1429
|
+
setCellsVersion((version) => version + 1);
|
|
927
1430
|
return cellsRef.current.length - 1;
|
|
928
|
-
}
|
|
929
|
-
const
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
const getCellIndex = (0, import_react5.useCallback)(
|
|
944
|
-
(id) => cellsRef.current.findIndex((c) => c.id === id),
|
|
945
|
-
[]
|
|
946
|
-
);
|
|
947
|
-
const ctx = (0, import_react5.useMemo)(
|
|
948
|
-
() => ({
|
|
949
|
-
size,
|
|
950
|
-
menuId,
|
|
951
|
-
closeMenu,
|
|
952
|
-
registerCell,
|
|
953
|
-
unregisterCell,
|
|
954
|
-
getCellIndex,
|
|
955
|
-
cellsVersion,
|
|
1431
|
+
}, []);
|
|
1432
|
+
const unregisterCell = (0, import_react8.useCallback)((id) => {
|
|
1433
|
+
const index2 = cellsRef.current.findIndex((cell) => cell.id === id);
|
|
1434
|
+
if (index2 >= 0) {
|
|
1435
|
+
cellsRef.current.splice(index2, 1);
|
|
1436
|
+
setCellsVersion((version) => version + 1);
|
|
1437
|
+
}
|
|
1438
|
+
}, []);
|
|
1439
|
+
const getCellIndex = (0, import_react8.useCallback)((id) => {
|
|
1440
|
+
return cellsRef.current.findIndex((cell) => cell.id === id);
|
|
1441
|
+
}, []);
|
|
1442
|
+
const cells = (0, import_react8.useMemo)(() => [...cellsRef.current], [cellsVersion]);
|
|
1443
|
+
const keyboard = useKeyboardNavigation({
|
|
1444
|
+
isOpen,
|
|
1445
|
+
cells,
|
|
956
1446
|
activeIndex,
|
|
957
1447
|
setActiveIndex,
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
}),
|
|
961
|
-
[
|
|
962
|
-
size,
|
|
963
|
-
menuId,
|
|
964
|
-
closeMenu,
|
|
965
|
-
registerCell,
|
|
966
|
-
unregisterCell,
|
|
967
|
-
getCellIndex,
|
|
968
|
-
cellsVersion,
|
|
969
|
-
activeIndex,
|
|
970
|
-
query
|
|
971
|
-
]
|
|
972
|
-
);
|
|
973
|
-
const triggerNode = (0, import_react5.useMemo)(() => {
|
|
974
|
-
if (!trigger) return null;
|
|
975
|
-
const inner = (0, import_react5.isValidElement)(trigger) ? (0, import_react5.cloneElement)(
|
|
976
|
-
trigger,
|
|
977
|
-
{
|
|
978
|
-
"aria-haspopup": "menu",
|
|
979
|
-
"aria-expanded": open ? "true" : "false"
|
|
980
|
-
}
|
|
981
|
-
) : trigger;
|
|
982
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
983
|
-
"span",
|
|
984
|
-
{
|
|
985
|
-
ref: (node) => {
|
|
986
|
-
if (!node) {
|
|
987
|
-
triggerRef.current = null;
|
|
988
|
-
return;
|
|
989
|
-
}
|
|
990
|
-
const focusable = node.querySelector(
|
|
991
|
-
"button, [href], input, select, textarea, [tabindex]:not([tabindex='-1'])"
|
|
992
|
-
);
|
|
993
|
-
triggerRef.current = focusable ?? node;
|
|
994
|
-
},
|
|
995
|
-
onClick: () => setOpen(!open),
|
|
996
|
-
style: { display: "inline-flex" },
|
|
997
|
-
children: inner
|
|
998
|
-
}
|
|
999
|
-
);
|
|
1000
|
-
}, [trigger, open, setOpen]);
|
|
1001
|
-
const usePortal = !!trigger && typeof document !== "undefined";
|
|
1002
|
-
const position = useContextMenuPosition({
|
|
1003
|
-
triggerRef,
|
|
1004
|
-
panelRef,
|
|
1005
|
-
isOpen: open && usePortal,
|
|
1006
|
-
placement
|
|
1007
|
-
});
|
|
1008
|
-
const cellsForNav = (0, import_react5.useMemo)(
|
|
1009
|
-
() => cellsRef.current.map((c) => ({ id: c.id, meta: c.meta })),
|
|
1010
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1011
|
-
[cellsVersion]
|
|
1012
|
-
);
|
|
1013
|
-
const { handleKeyDown } = useKeyboardNavigation({
|
|
1014
|
-
isOpen: open,
|
|
1015
|
-
cells: cellsForNav,
|
|
1016
|
-
activeIndex,
|
|
1017
|
-
setActiveIndex,
|
|
1018
|
-
onClose: closeMenu,
|
|
1019
|
-
triggerRef
|
|
1020
|
-
});
|
|
1021
|
-
const sizingFn = theme.sizing.contextMenu;
|
|
1022
|
-
const sizing = sizingFn ? sizingFn(size) : {};
|
|
1023
|
-
const radiusObj = theme.radius;
|
|
1024
|
-
const radiusVal = sizing.borderRadius ?? radiusObj?.contextMenu ?? 8;
|
|
1025
|
-
const shadowObj = theme.shadow;
|
|
1026
|
-
const shadowVal = shadowObj?.popover ?? "";
|
|
1027
|
-
const panelPaddingVertical = sizing.paddingVertical ?? 8;
|
|
1028
|
-
const glassBackground = theme.colors.layer?.float ?? theme.colors.background.primary;
|
|
1029
|
-
const panelStyle = {
|
|
1030
|
-
background: glassBackground,
|
|
1031
|
-
backdropFilter: "blur(12px)",
|
|
1032
|
-
WebkitBackdropFilter: "blur(12px)",
|
|
1033
|
-
border: `1px solid ${theme.colors.border.secondary}`,
|
|
1034
|
-
borderRadius: radiusVal,
|
|
1035
|
-
boxShadow: shadowVal,
|
|
1036
|
-
width: width ?? sizing.panelWidth,
|
|
1037
|
-
maxHeight,
|
|
1038
|
-
overflow: "hidden",
|
|
1039
|
-
display: open ? "flex" : "none",
|
|
1040
|
-
flexDirection: "column",
|
|
1041
|
-
outline: "none",
|
|
1042
|
-
fontFamily: theme.fonts.body,
|
|
1043
|
-
paddingTop: panelPaddingVertical,
|
|
1044
|
-
paddingBottom: panelPaddingVertical
|
|
1045
|
-
};
|
|
1046
|
-
if (usePortal) {
|
|
1047
|
-
panelStyle.position = "fixed";
|
|
1048
|
-
panelStyle.top = position?.top ?? 0;
|
|
1049
|
-
panelStyle.left = position?.left ?? 0;
|
|
1050
|
-
}
|
|
1051
|
-
const filteredItems = (0, import_react5.useMemo)(() => {
|
|
1052
|
-
if (!items) return void 0;
|
|
1053
|
-
if (!searchable || !debouncedQuery) return items.slice();
|
|
1054
|
-
const q = debouncedQuery.toLowerCase();
|
|
1055
|
-
const matchedFlags = items.map((item) => {
|
|
1056
|
-
if (item.type === "option") {
|
|
1057
|
-
return String(item.label ?? "").toLowerCase().includes(q);
|
|
1058
|
-
}
|
|
1059
|
-
return false;
|
|
1448
|
+
onClose: closeMenu,
|
|
1449
|
+
triggerRef
|
|
1060
1450
|
});
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
if (item.type === "heading") {
|
|
1068
|
-
pendingHeading = { item, idx: i };
|
|
1069
|
-
groupHasOption = false;
|
|
1070
|
-
} else if (item.type === "divider") {
|
|
1071
|
-
if (lastEmittedWasContent) {
|
|
1072
|
-
result.push(item);
|
|
1073
|
-
lastEmittedWasContent = false;
|
|
1074
|
-
}
|
|
1075
|
-
pendingHeading = null;
|
|
1076
|
-
groupHasOption = false;
|
|
1077
|
-
} else if (item.type === "option") {
|
|
1078
|
-
if (matchedFlags[i]) {
|
|
1079
|
-
if (pendingHeading) {
|
|
1080
|
-
result.push(pendingHeading.item);
|
|
1081
|
-
pendingHeading = null;
|
|
1082
|
-
}
|
|
1083
|
-
result.push(item);
|
|
1084
|
-
lastEmittedWasContent = true;
|
|
1085
|
-
groupHasOption = true;
|
|
1086
|
-
}
|
|
1451
|
+
(0, import_react8.useEffect)(() => {
|
|
1452
|
+
if (!isOpen) {
|
|
1453
|
+
cellsRef.current = [];
|
|
1454
|
+
setCellsVersion((version) => version + 1);
|
|
1455
|
+
setQuery("");
|
|
1456
|
+
setDebouncedQuery("");
|
|
1087
1457
|
}
|
|
1088
|
-
}
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
return result;
|
|
1094
|
-
}, [items, searchable, debouncedQuery]);
|
|
1095
|
-
const effectiveCloseOnSelect = closeOnSelect !== void 0 ? closeOnSelect : type !== "checkbox";
|
|
1096
|
-
const renderPresetItem = (item, key) => {
|
|
1097
|
-
if (item.type === "heading") {
|
|
1098
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1099
|
-
ContextMenuItem,
|
|
1100
|
-
{
|
|
1101
|
-
...item,
|
|
1102
|
-
size: item.size ?? size
|
|
1103
|
-
},
|
|
1104
|
-
`h-${key}`
|
|
1458
|
+
}, [isOpen]);
|
|
1459
|
+
(0, import_react8.useEffect)(() => {
|
|
1460
|
+
const timer = setTimeout(
|
|
1461
|
+
() => setDebouncedQuery(query),
|
|
1462
|
+
SEARCH_DEBOUNCE_MS
|
|
1105
1463
|
);
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1464
|
+
return () => clearTimeout(timer);
|
|
1465
|
+
}, [query]);
|
|
1466
|
+
(0, import_react8.useEffect)(() => {
|
|
1467
|
+
if (!isOpen || !trigger) return;
|
|
1468
|
+
const onMouseDown = (event) => {
|
|
1469
|
+
const target = event.target;
|
|
1470
|
+
if (!target || containerRef.current?.contains(target)) return;
|
|
1471
|
+
closeMenu();
|
|
1472
|
+
};
|
|
1473
|
+
const onScroll = () => closeMenu();
|
|
1474
|
+
document.addEventListener("mousedown", onMouseDown);
|
|
1475
|
+
window.addEventListener("scroll", onScroll);
|
|
1476
|
+
return () => {
|
|
1477
|
+
document.removeEventListener("mousedown", onMouseDown);
|
|
1478
|
+
window.removeEventListener("scroll", onScroll);
|
|
1479
|
+
};
|
|
1480
|
+
}, [isOpen, trigger, closeMenu]);
|
|
1481
|
+
(0, import_react8.useLayoutEffect)(() => {
|
|
1482
|
+
if (!isOpen || !panelRef.current) return;
|
|
1483
|
+
const searchbox = panelRef.current.querySelector('[role="searchbox"]');
|
|
1484
|
+
const firstOption = panelRef.current.querySelector(
|
|
1485
|
+
'[role="menuitem"],[role="menuitemcheckbox"],[role="menuitemradio"]'
|
|
1486
|
+
);
|
|
1487
|
+
(searchbox ?? firstOption)?.focus();
|
|
1488
|
+
}, [isOpen]);
|
|
1489
|
+
(0, import_react8.useLayoutEffect)(() => {
|
|
1490
|
+
if (!isOpen || !panelRef.current) return;
|
|
1491
|
+
const activeElement = document.activeElement;
|
|
1492
|
+
if (activeElement && panelRef.current.contains(activeElement)) return;
|
|
1493
|
+
panelRef.current.focus();
|
|
1494
|
+
}, [isOpen, cellsVersion]);
|
|
1495
|
+
(0, import_react8.useEffect)(() => {
|
|
1496
|
+
if (isOpen) return;
|
|
1497
|
+
triggerRef.current?.focus();
|
|
1498
|
+
}, [isOpen]);
|
|
1499
|
+
const contextValue = (0, import_react8.useMemo)(
|
|
1500
|
+
() => ({
|
|
1501
|
+
size,
|
|
1502
|
+
menuId,
|
|
1503
|
+
closeMenu,
|
|
1504
|
+
activeIndex,
|
|
1505
|
+
setActiveIndex,
|
|
1506
|
+
registerCell,
|
|
1507
|
+
unregisterCell,
|
|
1508
|
+
getCellIndex,
|
|
1509
|
+
cellsVersion,
|
|
1510
|
+
query,
|
|
1511
|
+
setQuery
|
|
1512
|
+
}),
|
|
1513
|
+
[
|
|
1514
|
+
size,
|
|
1515
|
+
menuId,
|
|
1516
|
+
closeMenu,
|
|
1517
|
+
activeIndex,
|
|
1518
|
+
registerCell,
|
|
1519
|
+
unregisterCell,
|
|
1520
|
+
getCellIndex,
|
|
1521
|
+
cellsVersion,
|
|
1522
|
+
query
|
|
1523
|
+
]
|
|
1524
|
+
);
|
|
1525
|
+
const renderPresetItem = (item, index2) => {
|
|
1526
|
+
if (!isOption(item)) {
|
|
1527
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1528
|
+
ContextMenuItem,
|
|
1529
|
+
{
|
|
1530
|
+
...item,
|
|
1531
|
+
themeMode,
|
|
1532
|
+
themeProductContext
|
|
1533
|
+
},
|
|
1534
|
+
`context-menu-item-${index2}`
|
|
1535
|
+
);
|
|
1536
|
+
}
|
|
1537
|
+
const leadingControl = item.leadingControl ?? (type === "checkbox" ? "checkbox" : type === "radio" ? "radio" : void 0);
|
|
1538
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1109
1539
|
ContextMenuItem,
|
|
1110
1540
|
{
|
|
1111
1541
|
...item,
|
|
1112
|
-
|
|
1542
|
+
leadingControl,
|
|
1543
|
+
themeMode,
|
|
1544
|
+
themeProductContext,
|
|
1545
|
+
onSelect: () => {
|
|
1546
|
+
item.onSelect?.();
|
|
1547
|
+
onSelect?.(item);
|
|
1548
|
+
if (shouldCloseOnSelect) closeMenu();
|
|
1549
|
+
}
|
|
1113
1550
|
},
|
|
1114
|
-
`
|
|
1551
|
+
`context-menu-item-${index2}`
|
|
1115
1552
|
);
|
|
1116
|
-
}
|
|
1117
|
-
const composed = composeItemForPreset(type, item);
|
|
1118
|
-
const originalSelect = composed.onSelect;
|
|
1119
|
-
const wrappedSelect = () => {
|
|
1120
|
-
originalSelect?.();
|
|
1121
|
-
onSelect?.(item);
|
|
1122
|
-
if (effectiveCloseOnSelect) closeMenu();
|
|
1123
1553
|
};
|
|
1124
|
-
|
|
1125
|
-
|
|
1554
|
+
const renderedItems = searchable ? filterItems(items, debouncedQuery) : [...items];
|
|
1555
|
+
const renderContent = () => {
|
|
1556
|
+
if (loading || isLoading || type === "loading") {
|
|
1557
|
+
const brandColor = xuiTheme.colors.control.brand.primary.bg;
|
|
1558
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1559
|
+
Box,
|
|
1560
|
+
{
|
|
1561
|
+
padding: 16,
|
|
1562
|
+
alignItems: "center",
|
|
1563
|
+
justifyContent: "center",
|
|
1564
|
+
minHeight: 60,
|
|
1565
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_xui_spinner.Spinner, { size: "md", color: brandColor })
|
|
1566
|
+
}
|
|
1567
|
+
);
|
|
1568
|
+
}
|
|
1569
|
+
if (children) return children;
|
|
1570
|
+
const content = renderedItems.map(renderPresetItem);
|
|
1571
|
+
if (searchable) {
|
|
1572
|
+
content.unshift(
|
|
1573
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1574
|
+
"div",
|
|
1575
|
+
{
|
|
1576
|
+
"data-sticky": "top",
|
|
1577
|
+
style: {
|
|
1578
|
+
position: "sticky",
|
|
1579
|
+
top: 0,
|
|
1580
|
+
zIndex: 1,
|
|
1581
|
+
backgroundColor: xuiTheme.colors.background.secondary
|
|
1582
|
+
},
|
|
1583
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1584
|
+
ContextMenuItem,
|
|
1585
|
+
{
|
|
1586
|
+
type: "search",
|
|
1587
|
+
value: query,
|
|
1588
|
+
onValueChange: setQuery,
|
|
1589
|
+
placeholder: "Search",
|
|
1590
|
+
autoFocus: true,
|
|
1591
|
+
themeMode,
|
|
1592
|
+
themeProductContext
|
|
1593
|
+
}
|
|
1594
|
+
)
|
|
1595
|
+
},
|
|
1596
|
+
"context-menu-search"
|
|
1597
|
+
)
|
|
1598
|
+
);
|
|
1599
|
+
}
|
|
1600
|
+
if (content.length > (searchable ? 1 : 0)) return content;
|
|
1601
|
+
return empty ?? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { padding: 16 }, children: emptyMessage });
|
|
1602
|
+
};
|
|
1603
|
+
const assignPanelRef = (node) => {
|
|
1604
|
+
panelRef.current = node;
|
|
1605
|
+
if (typeof ref === "function") ref(node);
|
|
1606
|
+
else if (ref) ref.current = node;
|
|
1607
|
+
};
|
|
1608
|
+
const assignTriggerRef = (node) => {
|
|
1609
|
+
triggerRef.current = node;
|
|
1610
|
+
};
|
|
1611
|
+
const triggerNode = trigger && (0, import_react8.isValidElement)(trigger) ? (0, import_react8.cloneElement)(trigger, {
|
|
1612
|
+
ref: assignTriggerRef,
|
|
1613
|
+
"aria-haspopup": "menu",
|
|
1614
|
+
"aria-expanded": isOpen ? "true" : "false",
|
|
1615
|
+
onClick: (event) => {
|
|
1616
|
+
trigger.props.onClick?.(event);
|
|
1617
|
+
if (!event.defaultPrevented) toggleMenu();
|
|
1618
|
+
}
|
|
1619
|
+
}) : trigger ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1620
|
+
"span",
|
|
1126
1621
|
{
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1622
|
+
ref: assignTriggerRef,
|
|
1623
|
+
role: "button",
|
|
1624
|
+
tabIndex: 0,
|
|
1625
|
+
"aria-haspopup": "menu",
|
|
1626
|
+
"aria-expanded": isOpen ? "true" : "false",
|
|
1627
|
+
onClick: toggleMenu,
|
|
1628
|
+
children: trigger
|
|
1629
|
+
}
|
|
1630
|
+
) : null;
|
|
1631
|
+
const positionStyle = position ? {
|
|
1632
|
+
position: "fixed",
|
|
1633
|
+
left: position.x,
|
|
1634
|
+
top: position.y
|
|
1635
|
+
} : trigger ? {
|
|
1636
|
+
position: "fixed",
|
|
1637
|
+
left: positioned?.left ?? 0,
|
|
1638
|
+
top: positioned?.top ?? 0
|
|
1639
|
+
} : void 0;
|
|
1640
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ContextMenuContext.Provider, { value: contextValue, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
1140
1641
|
"div",
|
|
1141
1642
|
{
|
|
1643
|
+
ref: containerRef,
|
|
1142
1644
|
style: {
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
justifyContent: "center",
|
|
1146
|
-
padding: 16
|
|
1645
|
+
position: trigger || position ? "relative" : void 0,
|
|
1646
|
+
display: trigger ? "inline-block" : void 0
|
|
1147
1647
|
},
|
|
1148
|
-
children:
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
bodyContent = empty ?? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(EmptyMessage, { color: theme.colors.content.tertiary, children: emptyMessage ?? "No results" });
|
|
1182
|
-
}
|
|
1183
|
-
const hasStickySearch = !!searchNode;
|
|
1184
|
-
const prevOpenRef = (0, import_react5.useRef)(false);
|
|
1185
|
-
const skipFocusRestoreRef = (0, import_react5.useRef)(false);
|
|
1186
|
-
(0, import_react5.useEffect)(() => {
|
|
1187
|
-
const wasOpen = prevOpenRef.current;
|
|
1188
|
-
prevOpenRef.current = open;
|
|
1189
|
-
if (!wasOpen && open) {
|
|
1190
|
-
const timer = setTimeout(() => {
|
|
1191
|
-
const panel2 = panelRef.current;
|
|
1192
|
-
if (!panel2) return;
|
|
1193
|
-
const search = panel2.querySelector("[role='searchbox']");
|
|
1194
|
-
if (search) {
|
|
1195
|
-
search.focus();
|
|
1196
|
-
return;
|
|
1197
|
-
}
|
|
1198
|
-
const firstOption = panel2.querySelector(
|
|
1199
|
-
"[role='menuitem'], [role='menuitemcheckbox'], [role='menuitemradio']"
|
|
1200
|
-
);
|
|
1201
|
-
if (firstOption) {
|
|
1202
|
-
firstOption.focus();
|
|
1203
|
-
setActiveIndex(-1);
|
|
1204
|
-
} else {
|
|
1205
|
-
panel2.focus();
|
|
1206
|
-
}
|
|
1207
|
-
}, 0);
|
|
1208
|
-
return () => clearTimeout(timer);
|
|
1209
|
-
}
|
|
1210
|
-
if (wasOpen && !open) {
|
|
1211
|
-
if (!skipFocusRestoreRef.current) {
|
|
1212
|
-
triggerRef.current?.focus();
|
|
1213
|
-
}
|
|
1214
|
-
skipFocusRestoreRef.current = false;
|
|
1215
|
-
}
|
|
1216
|
-
}, [open]);
|
|
1217
|
-
(0, import_react5.useEffect)(() => {
|
|
1218
|
-
if (!open || !usePortal || typeof document === "undefined") return;
|
|
1219
|
-
const handlePointerDown = (event) => {
|
|
1220
|
-
const target = event.target;
|
|
1221
|
-
if (!target) return;
|
|
1222
|
-
if (panelRef.current?.contains(target)) return;
|
|
1223
|
-
if (triggerRef.current?.contains(target)) return;
|
|
1224
|
-
if (target instanceof Element) {
|
|
1225
|
-
const portals = document.querySelectorAll(
|
|
1226
|
-
"[data-xui-context-menu-portal]"
|
|
1227
|
-
);
|
|
1228
|
-
for (let i = 0; i < portals.length; i += 1) {
|
|
1229
|
-
const portal = portals[i];
|
|
1230
|
-
if (portal.getAttribute("data-xui-context-menu-portal") === menuId && portal.contains(target)) {
|
|
1231
|
-
return;
|
|
1232
|
-
}
|
|
1233
|
-
}
|
|
1648
|
+
children: [
|
|
1649
|
+
triggerNode,
|
|
1650
|
+
isOpen && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1651
|
+
Box,
|
|
1652
|
+
{
|
|
1653
|
+
ref: assignPanelRef,
|
|
1654
|
+
role: "menu",
|
|
1655
|
+
"aria-label": ariaLabel,
|
|
1656
|
+
"data-testid": dataTestId ?? testID ?? "context-menu",
|
|
1657
|
+
"data-placement": positioned?.placement ?? placement,
|
|
1658
|
+
backgroundColor: xuiTheme.colors.background.secondary,
|
|
1659
|
+
borderColor: xuiTheme.colors.border.secondary,
|
|
1660
|
+
borderWidth: 1,
|
|
1661
|
+
borderRadius,
|
|
1662
|
+
paddingVertical: sizeStyles.paddingVertical,
|
|
1663
|
+
width,
|
|
1664
|
+
minWidth: sizeStyles.minWidth,
|
|
1665
|
+
tabIndex: -1,
|
|
1666
|
+
onKeyDown: keyboard.handleKeyDown,
|
|
1667
|
+
onMouseLeave: () => setActiveIndex(-1),
|
|
1668
|
+
style: {
|
|
1669
|
+
...positionStyle,
|
|
1670
|
+
...style,
|
|
1671
|
+
zIndex: 1e3,
|
|
1672
|
+
boxShadow: "0 4px 12px rgba(0,0,0,0.15)",
|
|
1673
|
+
maxHeight,
|
|
1674
|
+
overflowY: "auto",
|
|
1675
|
+
outline: "none"
|
|
1676
|
+
},
|
|
1677
|
+
children: renderContent()
|
|
1678
|
+
}
|
|
1679
|
+
)
|
|
1680
|
+
]
|
|
1234
1681
|
}
|
|
1235
|
-
|
|
1236
|
-
closeMenu();
|
|
1237
|
-
};
|
|
1238
|
-
document.addEventListener("mousedown", handlePointerDown);
|
|
1239
|
-
return () => document.removeEventListener("mousedown", handlePointerDown);
|
|
1240
|
-
}, [open, usePortal, closeMenu, menuId]);
|
|
1241
|
-
const resolvedPlacement = position?.placement ?? placement;
|
|
1242
|
-
const scrollContainerStyle = {
|
|
1243
|
-
overflowY: "auto",
|
|
1244
|
-
flex: 1,
|
|
1245
|
-
minHeight: 0
|
|
1246
|
-
};
|
|
1247
|
-
const stickyHeaderStyle = {
|
|
1248
|
-
position: "sticky",
|
|
1249
|
-
top: 0,
|
|
1250
|
-
zIndex: 1,
|
|
1251
|
-
// Match the glass panel so options scrolling underneath blur instead of
|
|
1252
|
-
// showing through the translucent header.
|
|
1253
|
-
background: glassBackground,
|
|
1254
|
-
backdropFilter: "blur(12px)",
|
|
1255
|
-
WebkitBackdropFilter: "blur(12px)"
|
|
1256
|
-
};
|
|
1257
|
-
const panel = open ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ContextMenuContext.Provider, { value: ctx, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
1258
|
-
"div",
|
|
1259
|
-
{
|
|
1260
|
-
ref: panelRef,
|
|
1261
|
-
role: "menu",
|
|
1262
|
-
"aria-label": ariaLabel,
|
|
1263
|
-
"data-testid": testId || testID,
|
|
1264
|
-
"data-placement": usePortal ? resolvedPlacement : void 0,
|
|
1265
|
-
tabIndex: -1,
|
|
1266
|
-
onKeyDown: handleKeyDown,
|
|
1267
|
-
onMouseLeave: () => setActiveIndex(-1),
|
|
1268
|
-
style: panelStyle,
|
|
1269
|
-
children: [
|
|
1270
|
-
hasStickySearch && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { "data-sticky": "top", style: stickyHeaderStyle, children: searchNode }),
|
|
1271
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: scrollContainerStyle, children: bodyContent })
|
|
1272
|
-
]
|
|
1273
|
-
}
|
|
1274
|
-
) }) : null;
|
|
1275
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
1276
|
-
triggerNode,
|
|
1277
|
-
usePortal ? panel && (0, import_react_dom2.createPortal)(panel, document.body) : panel
|
|
1278
|
-
] });
|
|
1279
|
-
};
|
|
1280
|
-
ContextMenu.displayName = "ContextMenu";
|
|
1281
|
-
function composeItemForPreset(type, item) {
|
|
1282
|
-
switch (type) {
|
|
1283
|
-
case "checkbox":
|
|
1284
|
-
return { ...item, leadingControl: "checkbox" };
|
|
1285
|
-
case "radio":
|
|
1286
|
-
return { ...item, leadingControl: "radio" };
|
|
1287
|
-
case "list":
|
|
1288
|
-
case "phone":
|
|
1289
|
-
case "status":
|
|
1290
|
-
case "brandLogo":
|
|
1291
|
-
case "avatar":
|
|
1292
|
-
default:
|
|
1293
|
-
return { ...item };
|
|
1682
|
+
) });
|
|
1294
1683
|
}
|
|
1295
|
-
|
|
1684
|
+
);
|
|
1685
|
+
ContextMenuRoot.displayName = "ContextMenu";
|
|
1686
|
+
var ContextMenu = Object.assign(ContextMenuRoot, {
|
|
1687
|
+
Item: ContextMenuItem,
|
|
1688
|
+
Submenu: ContextMenuSubmenu
|
|
1689
|
+
});
|
|
1296
1690
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1297
1691
|
0 && (module.exports = {
|
|
1298
1692
|
ContextMenu,
|
|
1299
1693
|
ContextMenuContext,
|
|
1300
1694
|
ContextMenuItem,
|
|
1695
|
+
ContextMenuSubmenu,
|
|
1301
1696
|
useContextMenu,
|
|
1302
1697
|
useContextMenuPosition,
|
|
1303
1698
|
useContextMenuRequired,
|