@asup/context-menu 1.5.2 → 2.0.2
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 +117 -65
- package/dist/cjs/main.js +192 -142
- package/dist/cjs/main.js.map +1 -1
- package/dist/context-menu.d.ts +12 -33
- package/dist/context-menu.d.ts.map +1 -1
- package/dist/main.js +194 -144
- package/dist/main.js.map +1 -1
- package/package.json +31 -23
package/README.md
CHANGED
|
@@ -10,84 +10,136 @@
|
|
|
10
10
|
|
|
11
11
|
# @asup/context-menu
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
A small, highly-configurable React + TypeScript context menu component and helpers.
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
Key points:
|
|
16
|
+
|
|
17
|
+
- Works with React 19 (package is built and tested against React 19; React is a peer dependency).
|
|
18
|
+
- TypeScript types included.
|
|
19
|
+
- Lightweight and focused on accessibility and nested sub-menus.
|
|
20
|
+
|
|
21
|
+
## Storybook
|
|
22
|
+
|
|
23
|
+
Run Storybook to see interactive component examples and documentation.
|
|
24
|
+
|
|
25
|
+
```powershell
|
|
26
|
+
# install dependencies
|
|
27
|
+
npm install
|
|
16
28
|
|
|
29
|
+
# run Storybook
|
|
30
|
+
npm run storybook
|
|
31
|
+
|
|
32
|
+
# run tests
|
|
33
|
+
npm run test
|
|
34
|
+
|
|
35
|
+
# build library bundle
|
|
36
|
+
npm run build
|
|
17
37
|
```
|
|
18
|
-
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
Install from npm:
|
|
42
|
+
|
|
43
|
+
```powershell
|
|
19
44
|
npm install @asup/context-menu
|
|
20
45
|
```
|
|
21
46
|
|
|
47
|
+
Note: React and ReactDOM are peer dependencies — install a compatible React version in your application.
|
|
48
|
+
|
|
22
49
|
## Usage
|
|
23
50
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
51
|
+
### ContextMenuHandler
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
import { ContextMenuHandler, IMenuItem } from "@asup/context-menu";
|
|
55
|
+
|
|
56
|
+
const menuItems: IMenuItem[] = [
|
|
57
|
+
{ label: "Item 1", action: () => console.log("Item 1") },
|
|
58
|
+
{
|
|
59
|
+
label: "Item 2",
|
|
60
|
+
action: () => console.log("Item 2"),
|
|
61
|
+
group: [{ label: "Subitem 2.1", action: () => console.log("Subitem 2.1") }],
|
|
62
|
+
},
|
|
63
|
+
{ label: "Item 3 (disabled)", disabled: true },
|
|
64
|
+
];
|
|
27
65
|
|
|
66
|
+
<ContextMenuHandler menuItems={menuItems}>
|
|
67
|
+
<div>Right click here to open the menu</div>
|
|
68
|
+
</ContextMenuHandler>;
|
|
28
69
|
```
|
|
29
|
-
import { ContextMenuProvider, IMenuItem } from '@asup/context-menu';
|
|
30
|
-
|
|
31
|
-
<ContextMenuHandler
|
|
32
|
-
menuItems={[
|
|
33
|
-
{
|
|
34
|
-
label: 'Item 1',
|
|
35
|
-
action: () => {
|
|
36
|
-
console.log('Item 1 function run');
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
label: 'Item 2',
|
|
41
|
-
action: () => console.log('Item 2 function run'),
|
|
42
|
-
group: [
|
|
43
|
-
{ label: 'Subitem 2.1', action: () => console.log('Item 2.1 function run') },
|
|
44
|
-
],
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
label: 'Item 3',
|
|
48
|
-
action: () => console.log('Item 3 function run'),
|
|
49
|
-
disabled: true,
|
|
50
|
-
},
|
|
51
|
-
]}
|
|
52
|
-
>
|
|
53
|
-
<Chilren
|
|
54
|
-
where the context menu is applied...
|
|
55
|
-
/>
|
|
56
|
-
</ContextMenuHandler>
|
|
57
70
|
|
|
71
|
+
### AutoHeight
|
|
72
|
+
|
|
73
|
+
Use `AutoHeight` to wrap content that may expand/contract — it will manage layout height for smoother transitions.
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
import { AutoHeight } from "@asup/context-menu";
|
|
77
|
+
|
|
78
|
+
<AutoHeight>
|
|
79
|
+
<div style={{ padding: 12 }}>
|
|
80
|
+
This content can change size; AutoHeight will help the layout adjust smoothly.
|
|
81
|
+
</div>
|
|
82
|
+
</AutoHeight>;
|
|
58
83
|
```
|
|
59
84
|
|
|
85
|
+
### ClickForMenu
|
|
86
|
+
|
|
87
|
+
`ClickForMenu` attaches a click-based menu to any element (useful for toolbar buttons or inline actions). Give each instance a stable `id` so the trigger element can be referenced and cleaned up correctly.
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
import { ClickForMenu, IMenuItem } from "@asup/context-menu";
|
|
91
|
+
|
|
92
|
+
const clickItems: IMenuItem[] = [
|
|
93
|
+
{ label: "Edit", action: () => console.log("Edit") },
|
|
94
|
+
{ label: "Delete", action: () => console.log("Delete") },
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
<ClickForMenu
|
|
98
|
+
id="actions-menu"
|
|
99
|
+
menuItems={clickItems}
|
|
100
|
+
>
|
|
101
|
+
<button type="button">Actions</button>
|
|
102
|
+
</ClickForMenu>;
|
|
60
103
|
```
|
|
61
|
-
import { ContextWindowStack, ContextWindow }
|
|
62
|
-
|
|
63
|
-
// Context window stack needs to cover the application, or portion where context windows cannot clash with each other
|
|
64
|
-
<ContextWindowStack>
|
|
65
|
-
...rest of app
|
|
66
|
-
|
|
67
|
-
<ContextWindow
|
|
68
|
-
id='window-1'
|
|
69
|
-
title={'Window 1'}
|
|
70
|
-
visible={visible}
|
|
71
|
-
style={ window styling, applied to the window div}
|
|
72
|
-
onOpen={ called function on opening}
|
|
73
|
-
onClose={ called function on closing (close cross in the window)}
|
|
74
|
-
>
|
|
75
|
-
{window contents}
|
|
76
|
-
</ContextWindow>
|
|
77
|
-
|
|
78
|
-
<ContextWindow
|
|
79
|
-
id='window-2'
|
|
80
|
-
title={'Window 2'}
|
|
81
|
-
visible={visible}
|
|
82
|
-
style={ window styling, applied to the window div}
|
|
83
|
-
onOpen={ called function on opening}
|
|
84
|
-
onClose={ called function on closing (close cross in the window)}
|
|
85
|
-
>
|
|
86
|
-
{window contents}
|
|
87
|
-
</ContextWindow>
|
|
88
|
-
|
|
89
|
-
...end of app
|
|
90
|
-
|
|
91
|
-
</ContextWindowStack>
|
|
92
104
|
|
|
105
|
+
### ContextWindow
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
import { ContextWindow } from "@asup/context-menu";
|
|
109
|
+
|
|
110
|
+
<ContextWindow
|
|
111
|
+
id="window-1"
|
|
112
|
+
title="Window 1"
|
|
113
|
+
visible={true}
|
|
114
|
+
onClose={() => {}}
|
|
115
|
+
>
|
|
116
|
+
Window content
|
|
117
|
+
</ContextWindow>;
|
|
93
118
|
```
|
|
119
|
+
|
|
120
|
+
See the Storybook for interactive examples and more options.
|
|
121
|
+
|
|
122
|
+
## Development
|
|
123
|
+
|
|
124
|
+
Useful scripts (from `package.json`):
|
|
125
|
+
|
|
126
|
+
- `npm run prepare` — run Husky (prepares Git hooks).
|
|
127
|
+
- `npm run storybook` — start Storybook to view component examples.
|
|
128
|
+
- `npm run build-storybook` — build a static Storybook site.
|
|
129
|
+
- `npm run test` — run Jest and collect coverage (`jest --collectCoverage=true`).
|
|
130
|
+
- `npm run test-watch` — run Jest in watch mode with coverage (`jest --watch --collectCoverage=true --maxWorkers=4`).
|
|
131
|
+
- `npm run eslint` — run ESLint over `src` (pattern: `src/**/*.{js,jsx,ts,tsx}`).
|
|
132
|
+
- `npm run build` — build the library bundle with Parcel. This script clears the Parcel cache before building (`parcel build src/main.ts`).
|
|
133
|
+
|
|
134
|
+
## Contributing
|
|
135
|
+
|
|
136
|
+
Contributions and PRs are welcome. Please follow the repository conventions (linting, types, and tests).
|
|
137
|
+
|
|
138
|
+
1. Fork the repo and create a feature branch.
|
|
139
|
+
2. Run `npm install`.
|
|
140
|
+
3. Run and update tests: `npm run test`.
|
|
141
|
+
4. Submit a PR and describe your changes.
|
|
142
|
+
|
|
143
|
+
## License
|
|
144
|
+
|
|
145
|
+
MIT — see the `LICENCE` file for details.
|
package/dist/cjs/main.js
CHANGED
|
@@ -40,7 +40,6 @@ $parcel$export($a68bd8a6c0fd98c2$exports, "ClickForMenu", function () { return $
|
|
|
40
40
|
$parcel$export($a68bd8a6c0fd98c2$exports, "ContextMenu", function () { return $5150b66b01c99189$export$8dc6765e8be191c7; });
|
|
41
41
|
$parcel$export($a68bd8a6c0fd98c2$exports, "ContextMenuHandler", function () { return $3c568ee547c732c3$export$ed4f9641643dc7e4; });
|
|
42
42
|
$parcel$export($a68bd8a6c0fd98c2$exports, "ContextWindow", function () { return $46fb0088a1bbb6d8$export$1af8984c69ba1b24; });
|
|
43
|
-
$parcel$export($a68bd8a6c0fd98c2$exports, "ContextWindowStack", function () { return $16208d559c772441$export$9f37482ccd50dad2; });
|
|
44
43
|
|
|
45
44
|
|
|
46
45
|
|
|
@@ -57,7 +56,7 @@ $796f463330153c24$export$563bd8f955c52746 = `aiw-AutoHeight-module-pDlSVW-autoHe
|
|
|
57
56
|
|
|
58
57
|
|
|
59
58
|
function $95149596d5a7ed2b$export$77bf000da9303d1(_param) {
|
|
60
|
-
var { children: children, hide: hide, duration: duration = 300 } = _param, rest = (0, $gTuX4$swchelperscjs_object_without_propertiescjs._)(_param, [
|
|
59
|
+
var { children: children, hide: hide = false, duration: duration = 300 } = _param, rest = (0, $gTuX4$swchelperscjs_object_without_propertiescjs._)(_param, [
|
|
61
60
|
"children",
|
|
62
61
|
"hide",
|
|
63
62
|
"duration"
|
|
@@ -65,29 +64,63 @@ function $95149596d5a7ed2b$export$77bf000da9303d1(_param) {
|
|
|
65
64
|
const wrapperRef = (0, $gTuX4$react.useRef)(null);
|
|
66
65
|
const innerRef = (0, $gTuX4$react.useRef)(null);
|
|
67
66
|
const [height, setHeight] = (0, $gTuX4$react.useState)(null);
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
67
|
+
const [animationState, setAnimationState] = (0, $gTuX4$react.useState)(!hide ? "open" : "closed");
|
|
68
|
+
const rafRef = (0, $gTuX4$react.useRef)(null);
|
|
69
|
+
const timeoutRef = (0, $gTuX4$react.useRef)(null);
|
|
70
|
+
const targetChildren = animationState === "closed" || !children ? null : children;
|
|
71
|
+
const setTargetHeight = (0, $gTuX4$react.useEffectEvent)((newHeight)=>{
|
|
72
|
+
setHeight(newHeight);
|
|
73
|
+
});
|
|
74
|
+
const transitionToOpening = (0, $gTuX4$react.useEffectEvent)(()=>{
|
|
75
|
+
// Cancel any pending close timeout
|
|
76
|
+
if (timeoutRef.current !== null) {
|
|
77
|
+
clearTimeout(timeoutRef.current);
|
|
78
|
+
timeoutRef.current = null;
|
|
79
|
+
}
|
|
80
|
+
setAnimationState("opening");
|
|
81
|
+
const id = window.requestAnimationFrame(()=>{
|
|
82
|
+
rafRef.current = null;
|
|
83
|
+
setTargetHeight(1);
|
|
84
|
+
const frameId = window.requestAnimationFrame(()=>{
|
|
85
|
+
const inner = innerRef.current;
|
|
86
|
+
if (inner) {
|
|
87
|
+
setTargetHeight(inner.offsetHeight);
|
|
88
|
+
setAnimationState("open");
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
rafRef.current = frameId;
|
|
92
|
+
});
|
|
93
|
+
rafRef.current = id;
|
|
94
|
+
});
|
|
95
|
+
const transitionToClosing = (0, $gTuX4$react.useEffectEvent)(()=>{
|
|
96
|
+
// Cancel any pending RAF
|
|
97
|
+
if (rafRef.current !== null) {
|
|
98
|
+
cancelAnimationFrame(rafRef.current);
|
|
99
|
+
rafRef.current = null;
|
|
100
|
+
}
|
|
101
|
+
setAnimationState("closing");
|
|
102
|
+
setTargetHeight(1);
|
|
103
|
+
timeoutRef.current = window.setTimeout(()=>{
|
|
104
|
+
timeoutRef.current = null;
|
|
105
|
+
setAnimationState("closed");
|
|
106
|
+
}, duration);
|
|
107
|
+
});
|
|
108
|
+
(0, $gTuX4$react.useLayoutEffect)(()=>{
|
|
109
|
+
if (!hide) // Want to show: transition to open
|
|
110
|
+
{
|
|
111
|
+
if (animationState === "closed" || animationState === "closing") transitionToOpening();
|
|
112
|
+
} else // Want to hide: transition to closed
|
|
113
|
+
if (animationState === "open" || animationState === "opening") transitionToClosing();
|
|
82
114
|
}, [
|
|
83
|
-
hide
|
|
115
|
+
hide,
|
|
116
|
+
animationState
|
|
84
117
|
]);
|
|
85
|
-
//
|
|
118
|
+
// Setup ResizeObserver to track content size changes
|
|
86
119
|
(0, $gTuX4$react.useEffect)(()=>{
|
|
87
120
|
const transition = innerRef.current;
|
|
88
121
|
if (transition) {
|
|
89
122
|
const observer = new ResizeObserver(()=>{
|
|
90
|
-
setTargetHeight();
|
|
123
|
+
if (animationState === "open") setTargetHeight(transition.offsetHeight);
|
|
91
124
|
});
|
|
92
125
|
observer.observe(transition);
|
|
93
126
|
return ()=>{
|
|
@@ -95,19 +128,26 @@ function $95149596d5a7ed2b$export$77bf000da9303d1(_param) {
|
|
|
95
128
|
};
|
|
96
129
|
}
|
|
97
130
|
}, [
|
|
98
|
-
|
|
99
|
-
]);
|
|
100
|
-
// Trigger height change on children update
|
|
101
|
-
(0, $gTuX4$react.useLayoutEffect)(()=>{
|
|
102
|
-
setTargetHeight();
|
|
103
|
-
}, [
|
|
104
|
-
setTargetHeight,
|
|
105
|
-
children
|
|
131
|
+
animationState
|
|
106
132
|
]);
|
|
133
|
+
// Cleanup on unmount
|
|
134
|
+
(0, $gTuX4$react.useEffect)(()=>{
|
|
135
|
+
return ()=>{
|
|
136
|
+
if (rafRef.current !== null) {
|
|
137
|
+
cancelAnimationFrame(rafRef.current);
|
|
138
|
+
rafRef.current = null;
|
|
139
|
+
}
|
|
140
|
+
if (timeoutRef.current !== null) {
|
|
141
|
+
clearTimeout(timeoutRef.current);
|
|
142
|
+
timeoutRef.current = null;
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
}, []);
|
|
107
146
|
return /*#__PURE__*/ (0, $gTuX4$reactjsxruntime.jsx)("div", (0, $gTuX4$swchelperscjs_object_spread_propscjs._)((0, $gTuX4$swchelperscjs_object_spreadcjs._)({}, rest), {
|
|
108
147
|
className: (0, (/*@__PURE__*/$parcel$interopDefault($796f463330153c24$exports))).autoHeightWrapper,
|
|
109
148
|
ref: wrapperRef,
|
|
110
149
|
style: (0, $gTuX4$swchelperscjs_object_spread_propscjs._)((0, $gTuX4$swchelperscjs_object_spreadcjs._)({}, rest.style), {
|
|
150
|
+
display: animationState === "closed" ? "none" : undefined,
|
|
111
151
|
height: height ? `${height}px` : "auto",
|
|
112
152
|
transitionDuration: `${duration}ms`
|
|
113
153
|
}),
|
|
@@ -247,13 +287,25 @@ const $5150b66b01c99189$var$ESTIMATED_MENU_ITEM_HEIGHT = 34;
|
|
|
247
287
|
const $5150b66b01c99189$var$ESTIMATED_MENU_PADDING = 4;
|
|
248
288
|
const $5150b66b01c99189$var$ESTIMATED_MENU_WIDTH = 200;
|
|
249
289
|
const $5150b66b01c99189$export$8dc6765e8be191c7 = /*#__PURE__*/ (0, $gTuX4$react.forwardRef)(({ visible: visible, entries: entries, xPos: xPos, yPos: yPos, toClose: toClose }, ref)=>{
|
|
250
|
-
//
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
290
|
+
// Measure menu size after mount/render to avoid accessing refs during render
|
|
291
|
+
const [menuHeight, setMenuHeight] = (0, $gTuX4$react.useState)(entries.length * $5150b66b01c99189$var$ESTIMATED_MENU_ITEM_HEIGHT + $5150b66b01c99189$var$ESTIMATED_MENU_PADDING);
|
|
292
|
+
const [menuWidth, setMenuWidth] = (0, $gTuX4$react.useState)($5150b66b01c99189$var$ESTIMATED_MENU_WIDTH);
|
|
293
|
+
(0, $gTuX4$react.useLayoutEffect)(()=>{
|
|
294
|
+
// Only measure when visible; ref access inside effect is allowed
|
|
295
|
+
if (visible && ref && typeof ref !== "function" && ref.current instanceof HTMLDivElement) {
|
|
296
|
+
setMenuHeight(ref.current.offsetHeight);
|
|
297
|
+
setMenuWidth(ref.current.offsetWidth);
|
|
298
|
+
}
|
|
299
|
+
// When not visible, fall back to estimates
|
|
300
|
+
if (!visible) {
|
|
301
|
+
setMenuHeight(entries.length * $5150b66b01c99189$var$ESTIMATED_MENU_ITEM_HEIGHT + $5150b66b01c99189$var$ESTIMATED_MENU_PADDING);
|
|
302
|
+
setMenuWidth($5150b66b01c99189$var$ESTIMATED_MENU_WIDTH);
|
|
303
|
+
}
|
|
304
|
+
}, [
|
|
305
|
+
visible,
|
|
306
|
+
entries,
|
|
307
|
+
ref
|
|
308
|
+
]);
|
|
257
309
|
const adjustedYPos = yPos + menuHeight > window.innerHeight ? Math.max(window.innerHeight - menuHeight - $5150b66b01c99189$var$ESTIMATED_MENU_PADDING, 0) : yPos;
|
|
258
310
|
const adjustedXPos = xPos + menuWidth > window.innerWidth ? Math.max(window.innerWidth - menuWidth - $5150b66b01c99189$var$ESTIMATED_MENU_PADDING, 0) : xPos;
|
|
259
311
|
return /*#__PURE__*/ (0, $gTuX4$reactjsxruntime.jsx)("div", {
|
|
@@ -294,9 +346,10 @@ const $a79b75d040e03c92$export$d4ebdd58e04c6ace = (_param)=>{
|
|
|
294
346
|
// Set up outsideClick handler
|
|
295
347
|
const menuRef = (0, $gTuX4$react.useRef)(null);
|
|
296
348
|
// Handle click off the menu
|
|
297
|
-
|
|
349
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
350
|
+
const handleClick = (e)=>{
|
|
298
351
|
if (menuRef.current && (e.target instanceof Element && !menuRef.current.contains(e.target) || !(e.target instanceof Element))) setMenuInDom(false);
|
|
299
|
-
}
|
|
352
|
+
};
|
|
300
353
|
const removeController = (0, $gTuX4$react.useRef)(null);
|
|
301
354
|
const removeTimeoutRef = (0, $gTuX4$react.useRef)(null);
|
|
302
355
|
(0, $gTuX4$react.useEffect)(()=>{
|
|
@@ -531,30 +584,26 @@ const $3c568ee547c732c3$export$ed4f9641643dc7e4 = (_param)=>{
|
|
|
531
584
|
"menuItems",
|
|
532
585
|
"showLowMenu"
|
|
533
586
|
]);
|
|
534
|
-
var _divHandlderRef_current;
|
|
535
587
|
// Check for higher content menu
|
|
536
588
|
const higherContext = (0, $gTuX4$react.useContext)($3c568ee547c732c3$export$fc58dc71afe92de2);
|
|
537
|
-
const thisMenuItems =
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
higherContext,
|
|
556
|
-
menuItems
|
|
557
|
-
]);
|
|
589
|
+
const thisMenuItems = [
|
|
590
|
+
...higherContext !== null ? [
|
|
591
|
+
...higherContext.menuItems,
|
|
592
|
+
...[
|
|
593
|
+
higherContext.menuItems.length > 0 && !$3c568ee547c732c3$var$isDivider(higherContext.menuItems[higherContext.menuItems.length - 1].label) && menuItems.length > 0 && !$3c568ee547c732c3$var$isDivider(menuItems[0].label) ? {
|
|
594
|
+
label: /*#__PURE__*/ (0, $gTuX4$reactjsxruntime.jsx)("hr", {
|
|
595
|
+
style: {
|
|
596
|
+
flexGrow: 1,
|
|
597
|
+
cursor: "none",
|
|
598
|
+
margin: "0",
|
|
599
|
+
padding: "0"
|
|
600
|
+
}
|
|
601
|
+
})
|
|
602
|
+
} : null
|
|
603
|
+
].filter((item)=>item !== null)
|
|
604
|
+
] : [],
|
|
605
|
+
...menuItems
|
|
606
|
+
];
|
|
558
607
|
// Menu resources
|
|
559
608
|
const divHandlderRef = (0, $gTuX4$react.useRef)(null);
|
|
560
609
|
const menuRef = (0, $gTuX4$react.useRef)(null);
|
|
@@ -564,9 +613,34 @@ const $3c568ee547c732c3$export$ed4f9641643dc7e4 = (_param)=>{
|
|
|
564
613
|
const [menuInDom, setMenuInDom] = (0, $gTuX4$react.useState)(false);
|
|
565
614
|
const [mouseOverHandlerDiv, setMouseOverHandlerDiv] = (0, $gTuX4$react.useState)(false);
|
|
566
615
|
const [mouseOverMenu, setMouseOverMenu] = (0, $gTuX4$react.useState)(false);
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
616
|
+
// Holder position - measured in an effect to avoid reading refs during render
|
|
617
|
+
const [divHandlerPos, setDivHandlerPos] = (0, $gTuX4$react.useState)(null);
|
|
618
|
+
(0, $gTuX4$react.useLayoutEffect)(()=>{
|
|
619
|
+
function updatePos() {
|
|
620
|
+
if (divHandlderRef.current) setDivHandlerPos(divHandlderRef.current.getBoundingClientRect());
|
|
621
|
+
}
|
|
622
|
+
// When the handler is hovered or the menu is mounted, ensure we have a fresh position
|
|
623
|
+
if (mouseOverHandlerDiv || menuInDom) updatePos();
|
|
624
|
+
// Attach listeners while the menu/low-menu may be visible so the position stays correct
|
|
625
|
+
if (mouseOverHandlerDiv || menuInDom) {
|
|
626
|
+
window.addEventListener("resize", updatePos);
|
|
627
|
+
// listen on capture to catch scrolls from ancestor elements as well
|
|
628
|
+
window.addEventListener("scroll", updatePos, true);
|
|
629
|
+
let ro = null;
|
|
630
|
+
if (typeof ResizeObserver !== "undefined" && divHandlderRef.current) {
|
|
631
|
+
ro = new ResizeObserver(()=>updatePos());
|
|
632
|
+
ro.observe(divHandlderRef.current);
|
|
633
|
+
}
|
|
634
|
+
return ()=>{
|
|
635
|
+
window.removeEventListener("resize", updatePos);
|
|
636
|
+
window.removeEventListener("scroll", updatePos, true);
|
|
637
|
+
if (ro) ro.disconnect();
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
}, [
|
|
641
|
+
mouseOverHandlerDiv,
|
|
642
|
+
menuInDom
|
|
643
|
+
]);
|
|
570
644
|
// Handle click off the menu
|
|
571
645
|
const handleClick = (0, $gTuX4$react.useCallback)((e)=>{
|
|
572
646
|
var _menuRef_current;
|
|
@@ -761,17 +835,19 @@ const $46fb0088a1bbb6d8$export$1af8984c69ba1b24 = (_param)=>{
|
|
|
761
835
|
var _rest_style, _rest_style1, _rest_style2, _rest_style3;
|
|
762
836
|
const divRef = (0, $gTuX4$react.useRef)(null);
|
|
763
837
|
const windowRef = (0, $gTuX4$react.useRef)(null);
|
|
764
|
-
const [windowInDOM, setWindowInDOM] = (0, $gTuX4$react.useState)(false);
|
|
765
|
-
const [windowVisible, setWindowVisible] = (0, $gTuX4$react.useState)(false);
|
|
766
838
|
const [zIndex, setZIndex] = (0, $gTuX4$react.useState)(minZIndex);
|
|
767
839
|
const resizeListenerRef = (0, $gTuX4$react.useRef)(null);
|
|
840
|
+
// Track internal state: whether window is in DOM and whether it's been positioned
|
|
841
|
+
const [windowInDOM, setWindowInDOM] = (0, $gTuX4$react.useState)(false);
|
|
842
|
+
const [windowVisible, setWindowVisible] = (0, $gTuX4$react.useState)(false);
|
|
843
|
+
const [, startTransition] = (0, $gTuX4$react.useTransition)();
|
|
768
844
|
// Position
|
|
769
845
|
const windowPos = (0, $gTuX4$react.useRef)({
|
|
770
846
|
x: 0,
|
|
771
847
|
y: 0
|
|
772
848
|
});
|
|
773
849
|
const [moving, setMoving] = (0, $gTuX4$react.useState)(false);
|
|
774
|
-
const move = (
|
|
850
|
+
const move = (x, y)=>{
|
|
775
851
|
if (windowRef.current && windowPos.current) {
|
|
776
852
|
const window1 = windowRef.current;
|
|
777
853
|
const pos = windowPos.current;
|
|
@@ -779,24 +855,21 @@ const $46fb0088a1bbb6d8$export$1af8984c69ba1b24 = (_param)=>{
|
|
|
779
855
|
pos.y += y;
|
|
780
856
|
window1.style.transform = `translate(${pos.x}px, ${pos.y}px)`;
|
|
781
857
|
}
|
|
782
|
-
}
|
|
783
|
-
|
|
858
|
+
};
|
|
859
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
860
|
+
const checkPosition = ()=>{
|
|
784
861
|
const chkPos = (0, $c986bcdcae4b83bd$export$d81cfea7c54be196)(windowRef);
|
|
785
862
|
move(chkPos.translateX, chkPos.translateY);
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
]);
|
|
789
|
-
const mouseMove = (0, $gTuX4$react.useCallback)((e)=>{
|
|
863
|
+
};
|
|
864
|
+
const mouseMove = (e)=>{
|
|
790
865
|
e.preventDefault();
|
|
791
866
|
e.stopPropagation();
|
|
792
867
|
move(e.movementX, e.movementY);
|
|
793
|
-
}
|
|
794
|
-
move
|
|
795
|
-
]);
|
|
868
|
+
};
|
|
796
869
|
// Store stable references to mouseMove and mouseUp for cleanup
|
|
797
870
|
const mouseMoveRef = (0, $gTuX4$react.useRef)(mouseMove);
|
|
798
871
|
const mouseUpRef = (0, $gTuX4$react.useRef)(()=>{});
|
|
799
|
-
const mouseUp = (
|
|
872
|
+
const mouseUp = (e)=>{
|
|
800
873
|
e.preventDefault();
|
|
801
874
|
e.stopPropagation();
|
|
802
875
|
setMoving(false);
|
|
@@ -808,55 +881,65 @@ const $46fb0088a1bbb6d8$export$1af8984c69ba1b24 = (_param)=>{
|
|
|
808
881
|
resizeListenerRef.current = null;
|
|
809
882
|
}
|
|
810
883
|
if (e.target && (e.target instanceof HTMLElement || e.target instanceof SVGElement)) e.target.style.userSelect = "auto";
|
|
811
|
-
}
|
|
812
|
-
checkPosition
|
|
813
|
-
]);
|
|
884
|
+
};
|
|
814
885
|
// Update refs when callbacks change
|
|
815
886
|
(0, $gTuX4$react.useEffect)(()=>{
|
|
816
887
|
mouseMoveRef.current = mouseMove;
|
|
817
888
|
mouseUpRef.current = mouseUp;
|
|
818
|
-
}
|
|
819
|
-
mouseMove,
|
|
820
|
-
mouseUp
|
|
821
|
-
]);
|
|
889
|
+
});
|
|
822
890
|
// Helper function to push this window to the top
|
|
823
|
-
const pushToTop = (
|
|
891
|
+
const pushToTop = ()=>{
|
|
824
892
|
const maxZIndex = $46fb0088a1bbb6d8$var$getMaxZIndex(minZIndex);
|
|
825
893
|
setZIndex(maxZIndex + 1);
|
|
894
|
+
};
|
|
895
|
+
// Sync windowInDOM with visible prop using a layout effect to avoid ESLint warnings
|
|
896
|
+
// This effect derives state from props, which is acceptable when there's no synchronous setState
|
|
897
|
+
(0, $gTuX4$react.useEffect)(()=>{
|
|
898
|
+
if (visible && !windowInDOM) // Window should be in DOM when visible becomes true
|
|
899
|
+
startTransition(()=>{
|
|
900
|
+
setWindowInDOM(true);
|
|
901
|
+
});
|
|
902
|
+
else if (!visible && windowInDOM) // Window should leave DOM when visible becomes false
|
|
903
|
+
startTransition(()=>{
|
|
904
|
+
setWindowInDOM(false);
|
|
905
|
+
setWindowVisible(false);
|
|
906
|
+
});
|
|
826
907
|
}, [
|
|
827
|
-
|
|
908
|
+
visible,
|
|
909
|
+
windowInDOM,
|
|
910
|
+
startTransition
|
|
828
911
|
]);
|
|
829
|
-
//
|
|
912
|
+
// Position and show window after it's added to DOM
|
|
830
913
|
(0, $gTuX4$react.useEffect)(()=>{
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
windowRef.current.style.top = `${parentPos.bottom + windowHeight < window.innerHeight ? parentPos.bottom : Math.max(0, parentPos.top - windowHeight)}px`;
|
|
844
|
-
windowRef.current.style.transform = "";
|
|
845
|
-
windowPos.current = {
|
|
846
|
-
x: 0,
|
|
847
|
-
y: 0
|
|
848
|
-
};
|
|
849
|
-
}
|
|
914
|
+
if (windowInDOM && !windowVisible && visible && divRef.current && windowRef.current) {
|
|
915
|
+
// Position the window
|
|
916
|
+
const parentPos = divRef.current.getBoundingClientRect();
|
|
917
|
+
const pos = windowRef.current.getBoundingClientRect();
|
|
918
|
+
const windowHeight = pos.bottom - pos.top;
|
|
919
|
+
windowRef.current.style.left = `${parentPos.left}px`;
|
|
920
|
+
windowRef.current.style.top = `${parentPos.bottom + windowHeight < window.innerHeight ? parentPos.bottom : Math.max(0, parentPos.top - windowHeight)}px`;
|
|
921
|
+
windowRef.current.style.transform = "";
|
|
922
|
+
windowPos.current = {
|
|
923
|
+
x: 0,
|
|
924
|
+
y: 0
|
|
925
|
+
};
|
|
850
926
|
checkPosition();
|
|
851
|
-
|
|
852
|
-
|
|
927
|
+
// Update z-index and make visible - use startTransition
|
|
928
|
+
const maxZ = $46fb0088a1bbb6d8$var$getMaxZIndex(minZIndex);
|
|
929
|
+
onOpen === null || onOpen === void 0 ? void 0 : onOpen();
|
|
930
|
+
startTransition(()=>{
|
|
931
|
+
setZIndex(maxZ + 1);
|
|
932
|
+
setWindowVisible(true);
|
|
933
|
+
});
|
|
934
|
+
}
|
|
853
935
|
}, [
|
|
936
|
+
windowInDOM,
|
|
937
|
+
windowVisible,
|
|
938
|
+
visible,
|
|
854
939
|
checkPosition,
|
|
940
|
+
minZIndex,
|
|
855
941
|
onOpen,
|
|
856
|
-
|
|
857
|
-
visible,
|
|
858
|
-
windowInDOM,
|
|
859
|
-
windowVisible
|
|
942
|
+
startTransition
|
|
860
943
|
]);
|
|
861
944
|
// Cleanup effect to remove event listeners on unmount
|
|
862
945
|
(0, $gTuX4$react.useEffect)(()=>{
|
|
@@ -952,39 +1035,6 @@ $46fb0088a1bbb6d8$export$1af8984c69ba1b24.displayName = "ContextWindow";
|
|
|
952
1035
|
|
|
953
1036
|
|
|
954
1037
|
|
|
955
|
-
const $16208d559c772441$var$SESSION_KEY = "context-menu.ContextWindowStack.rendered";
|
|
956
|
-
const $16208d559c772441$export$9f37482ccd50dad2 = ({ children: children })=>{
|
|
957
|
-
(0, $gTuX4$react.useEffect)(()=>{
|
|
958
|
-
const doWarn = ()=>console.warn("ContextWindowStack is deprecated and no longer required. ContextWindow now manages z-index automatically. Please remove the ContextWindowStack wrapper from your code.");
|
|
959
|
-
try {
|
|
960
|
-
// Prefer sessionStorage so the warning lasts for the browser session.
|
|
961
|
-
if (typeof window !== "undefined" && window.sessionStorage) {
|
|
962
|
-
const already = window.sessionStorage.getItem($16208d559c772441$var$SESSION_KEY);
|
|
963
|
-
if (!already) {
|
|
964
|
-
window.sessionStorage.setItem($16208d559c772441$var$SESSION_KEY, "1");
|
|
965
|
-
doWarn();
|
|
966
|
-
}
|
|
967
|
-
return;
|
|
968
|
-
}
|
|
969
|
-
} catch (e) {
|
|
970
|
-
// sessionStorage may be unavailable (privacy mode). Fall through to global fallback.
|
|
971
|
-
}
|
|
972
|
-
// Fallback: use a global flag for environments where sessionStorage isn't available.
|
|
973
|
-
const g = globalThis;
|
|
974
|
-
if (!g.__ContextWindowStackRendered) {
|
|
975
|
-
g.__ContextWindowStackRendered = true;
|
|
976
|
-
doWarn();
|
|
977
|
-
}
|
|
978
|
-
}, []);
|
|
979
|
-
return /*#__PURE__*/ (0, $gTuX4$reactjsxruntime.jsx)((0, $gTuX4$reactjsxruntime.Fragment), {
|
|
980
|
-
children: children
|
|
981
|
-
});
|
|
982
|
-
};
|
|
983
|
-
$16208d559c772441$export$9f37482ccd50dad2.displayName = "ContextWindowStack";
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
1038
|
$parcel$exportWildcard(module.exports, $a68bd8a6c0fd98c2$exports);
|
|
989
1039
|
|
|
990
1040
|
|