wenay-react2 1.0.39 → 1.0.40
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/lib/common/src/components/Dnd/FloatingWindow.d.ts +29 -12
- package/lib/common/src/components/Dnd/FloatingWindow.js +100 -91
- package/lib/common/src/components/Menu/RightMenu.d.ts +28 -1
- package/lib/common/src/components/Menu/RightMenu.js +62 -35
- package/lib/common/src/components/Modal/LeftModal.js +5 -4
- package/lib/common/src/components/Modal/Modal.js +8 -7
- package/lib/common/src/components/ParamsEditor.d.ts +15 -0
- package/lib/common/src/components/ParamsEditor.js +45 -18
- package/lib/common/src/components/Settings/SettingsDialog.d.ts +68 -6
- package/lib/common/src/components/Settings/SettingsDialog.js +61 -12
- package/lib/common/src/components/Toolbar/Toolbar.js +64 -19
- package/lib/common/src/components/UiSlot/UiSlot.js +5 -4
- package/lib/common/src/grid/columnState/columnState.js +8 -6
- package/lib/common/src/hooks/useKeyboard.js +4 -3
- package/lib/common/src/logs/logs.d.ts +13 -133
- package/lib/common/src/logs/logs.js +20 -39
- package/lib/common/src/logs/logsContext.d.ts +26 -0
- package/lib/common/src/logs/logsContext.js +25 -20
- package/lib/common/src/logs/logsController.d.ts +88 -0
- package/lib/common/src/logs/logsController.js +54 -0
- package/lib/common/src/logs/miniLogs.d.ts +69 -3
- package/lib/common/src/logs/miniLogs.js +70 -15
- package/lib/common/src/menu/menu.d.ts +15 -3
- package/lib/common/src/menu/menu.js +81 -17
- package/lib/common/src/menu/menuMouse.d.ts +18 -0
- package/lib/common/src/menu/menuMouse.js +43 -1
- package/lib/common/src/utils/memoryStore.d.ts +1 -10
- package/lib/common/src/utils/searchHistory.js +4 -3
- package/package.json +49 -49
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import React, { ReactNode } from "react";
|
|
2
|
+
import { type RndResizeCallback } from "react-rnd";
|
|
2
3
|
import { ObservableMap } from "../../utils/observableMap";
|
|
3
|
-
type
|
|
4
|
+
export type FloatingWindowPosition = {
|
|
4
5
|
x: number;
|
|
5
6
|
y: number;
|
|
6
7
|
};
|
|
7
|
-
type
|
|
8
|
+
export type FloatingWindowSize = {
|
|
8
9
|
height: number | string;
|
|
9
10
|
width: number | string;
|
|
10
11
|
};
|
|
11
|
-
type
|
|
12
|
-
position:
|
|
13
|
-
size:
|
|
12
|
+
export type FloatingWindowSavedGeometry = {
|
|
13
|
+
position: FloatingWindowPosition;
|
|
14
|
+
size: FloatingWindowSize;
|
|
14
15
|
};
|
|
15
16
|
export type FloatingWindowUpdate = {
|
|
16
17
|
e: MouseEvent | TouchEvent;
|
|
@@ -20,15 +21,15 @@ export type FloatingWindowUpdate = {
|
|
|
20
21
|
width: number;
|
|
21
22
|
height: number;
|
|
22
23
|
};
|
|
23
|
-
position:
|
|
24
|
+
position: FloatingWindowPosition;
|
|
24
25
|
};
|
|
25
|
-
type
|
|
26
|
+
export type FloatingWindowProps = {
|
|
26
27
|
zIndex?: number;
|
|
27
28
|
disableDragging?: () => boolean;
|
|
28
29
|
keyForSave?: string;
|
|
29
30
|
onUpdate?: (data: FloatingWindowUpdate) => void;
|
|
30
|
-
position?:
|
|
31
|
-
size?:
|
|
31
|
+
position?: FloatingWindowPosition;
|
|
32
|
+
size?: FloatingWindowSize;
|
|
32
33
|
moveOnlyHeader?: boolean;
|
|
33
34
|
onCLickClose?: () => void;
|
|
34
35
|
header?: React.ReactElement | boolean;
|
|
@@ -47,13 +48,30 @@ type tDivRndBase = {
|
|
|
47
48
|
children: React.ReactElement | ((update: number) => React.ReactElement);
|
|
48
49
|
className?: string;
|
|
49
50
|
};
|
|
50
|
-
export
|
|
51
|
+
export type FloatingWindowControllerOptions = Omit<FloatingWindowProps, "children" | "className" | "header" | "moveOnlyHeader" | "overflow" | "onCLickClose">;
|
|
52
|
+
export type FloatingWindowController = {
|
|
53
|
+
position: FloatingWindowPosition;
|
|
54
|
+
size: FloatingWindowSize;
|
|
55
|
+
update: number;
|
|
56
|
+
stackIndex: number;
|
|
57
|
+
zIndex: number;
|
|
58
|
+
overlayZIndex: number;
|
|
59
|
+
dragging: boolean;
|
|
60
|
+
headerRef: React.RefObject<HTMLDivElement | null>;
|
|
61
|
+
onHeaderTouchStart: React.TouchEventHandler<HTMLDivElement>;
|
|
62
|
+
onHeaderMouseDown: React.MouseEventHandler<HTMLDivElement>;
|
|
63
|
+
onWindowMouseDown: React.MouseEventHandler<HTMLDivElement>;
|
|
64
|
+
onResize: RndResizeCallback;
|
|
65
|
+
onResizeStop: RndResizeCallback;
|
|
66
|
+
};
|
|
67
|
+
export declare const floatingWindowMap: ObservableMap<string, FloatingWindowSavedGeometry>;
|
|
51
68
|
export declare const FloatingWindow: typeof FloatingWindowBase;
|
|
69
|
+
export declare function useFloatingWindowController({ keyForSave: ks, position, size, zIndex, onUpdate, limit, sizeByWindow }?: FloatingWindowControllerOptions): FloatingWindowController;
|
|
52
70
|
/**
|
|
53
71
|
* Wrapper component around react-rnd.
|
|
54
72
|
* Provides dragging and resizing, an optional header, and a close button.
|
|
55
73
|
*/
|
|
56
|
-
export declare function FloatingWindowBase({ children, keyForSave: ks, position, size, overflow, zIndex, onUpdate, disableDragging, className, header, moveOnlyHeader, limit, onCLickClose, sizeByWindow }:
|
|
74
|
+
export declare function FloatingWindowBase({ children, keyForSave: ks, position, size, overflow, zIndex, onUpdate, disableDragging, className, header, moveOnlyHeader, limit, onCLickClose, sizeByWindow }: FloatingWindowProps): import("react/jsx-runtime").JSX.Element;
|
|
57
75
|
export type DragBoxProps = {
|
|
58
76
|
/** Child element that should be draggable */
|
|
59
77
|
children: ReactNode;
|
|
@@ -89,4 +107,3 @@ export type DragBoxProps = {
|
|
|
89
107
|
* Returns the distance traveled when moving the child element.
|
|
90
108
|
*/
|
|
91
109
|
export declare function DragBox({ children, onX, onY, x, y, right, last, dragging, onStart, onStop }: DragBoxProps): import("react/jsx-runtime").JSX.Element;
|
|
92
|
-
export {};
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import React, { useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
3
3
|
import { Rnd } from "react-rnd";
|
|
4
|
-
import {
|
|
4
|
+
import { createUpdateApi } from "../../../updateBy";
|
|
5
5
|
import { ObservableMap } from "../../utils/observableMap";
|
|
6
6
|
// Map of all popup window sizes; observable - memoryCache marks itself dirty on its mutations
|
|
7
7
|
export const floatingWindowMap = new ObservableMap();
|
|
8
8
|
// limit={{x:{min:0}, y:{min:0}}}
|
|
9
9
|
let k = 0;
|
|
10
10
|
const openWindows = { ar: [] };
|
|
11
|
+
const openWindowsApi = createUpdateApi(openWindows);
|
|
11
12
|
// Freezes the subtree until update changes (intentionally ignores render closure changes) -
|
|
12
13
|
// the previous useMemo-in-callback semantics, but without calling a hook from an arbitrary place
|
|
13
14
|
const MemoChild = React.memo(({ update, render }) => render(update), (prev, next) => prev.update === next.update);
|
|
@@ -17,25 +18,15 @@ export const FloatingWindow = (a) => {
|
|
|
17
18
|
const ff = (update) => _jsx(MemoChild, { update: isFunc ? update : 0, render: renderChild });
|
|
18
19
|
return _jsx(FloatingWindowBase, { ...a, children: ff });
|
|
19
20
|
};
|
|
20
|
-
|
|
21
|
-
* Wrapper component around react-rnd.
|
|
22
|
-
* Provides dragging and resizing, an optional header, and a close button.
|
|
23
|
-
*/
|
|
24
|
-
export function FloatingWindowBase({ children, keyForSave: ks, position, size, overflow = true, zIndex = 9, onUpdate, disableDragging, className, header, moveOnlyHeader, limit, onCLickClose, sizeByWindow = true }) {
|
|
25
|
-
// NOTE: no implicit limit for onCLickClose windows. `limit` is parent-relative,
|
|
26
|
-
// so {y:{min:0}} pinned windows to their DOM parent's top: a window opened from a
|
|
27
|
-
// centered wrapper (ModalProvider / ModalWrapper at top:50%) could not be dragged
|
|
28
|
-
// above mid-screen. Keeping the window on screen is already handled by the
|
|
29
|
-
// viewport clamp below (header rect vs viewport in useLayoutEffect).
|
|
21
|
+
export function useFloatingWindowController({ keyForSave: ks, position, size, zIndex = 9, onUpdate, limit, sizeByWindow = true } = {}) {
|
|
30
22
|
const positionDef = { x: 0, y: 0, ...(position ?? {}) };
|
|
31
23
|
const sizeDef = { height: 0, width: 0, ...(size ?? {}) };
|
|
32
|
-
// If there is a key, store position and size data in the map
|
|
33
24
|
let map;
|
|
34
25
|
if (ks) {
|
|
35
26
|
map = floatingWindowMap.get(ks) ?? floatingWindowMap.set(ks, { size: sizeDef, position: positionDef }).get(ks);
|
|
36
27
|
}
|
|
37
|
-
|
|
38
|
-
|
|
28
|
+
const savedPosition = map?.position ?? positionDef;
|
|
29
|
+
const savedSize = map?.size ?? sizeDef;
|
|
39
30
|
const id2 = useRef({ k: k++ });
|
|
40
31
|
const id = id2.current;
|
|
41
32
|
const [zIndexX, setZIndexX] = useState(0);
|
|
@@ -43,32 +34,24 @@ export function FloatingWindowBase({ children, keyForSave: ks, position, size, o
|
|
|
43
34
|
const lastT = useRef(null);
|
|
44
35
|
const [a, setA] = useState(false);
|
|
45
36
|
const [b, setB] = useState(false);
|
|
46
|
-
const [x, setX] = useState(
|
|
47
|
-
const [y, setY] = useState(
|
|
48
|
-
const [width, setWidth] = useState(
|
|
49
|
-
const [height, setHeight] = useState(
|
|
37
|
+
const [x, setX] = useState(savedPosition.x);
|
|
38
|
+
const [y, setY] = useState(savedPosition.y);
|
|
39
|
+
const [width, setWidth] = useState(savedSize.width);
|
|
40
|
+
const [height, setHeight] = useState(savedSize.height);
|
|
50
41
|
const [update, setUpdate] = useState(0);
|
|
51
42
|
const zindex = useRef(zIndexX);
|
|
52
43
|
zindex.current = zIndexX;
|
|
53
|
-
|
|
54
|
-
updateBy(openWindows, () => {
|
|
44
|
+
openWindowsApi.use(() => {
|
|
55
45
|
const z = openWindows.ar.findIndex((v) => v.k === id.k);
|
|
56
46
|
if (z >= 0 && z !== zindex.current) {
|
|
57
47
|
setZIndexX(z);
|
|
58
48
|
}
|
|
59
49
|
});
|
|
60
|
-
// limit via ref: an inline object in props must not resubscribe document listeners on every render
|
|
61
50
|
const limitRef = useRef(limit);
|
|
62
51
|
useLayoutEffect(() => { limitRef.current = limit; });
|
|
63
|
-
/**
|
|
64
|
-
* Hook for mouse dragging (a) and touch-device dragging (b).
|
|
65
|
-
* Remove subscriptions on unmount and when dependencies change (a/b).
|
|
66
|
-
*/
|
|
67
52
|
useEffect(() => {
|
|
68
|
-
// Mouse
|
|
69
53
|
const mouseMoveHandler = (e) => {
|
|
70
54
|
e.stopPropagation();
|
|
71
|
-
// mousedown always sets lastC before this subscription exists
|
|
72
55
|
if (lastC.current == null)
|
|
73
56
|
return;
|
|
74
57
|
const data = lastC.current;
|
|
@@ -98,12 +81,9 @@ export function FloatingWindowBase({ children, keyForSave: ks, position, size, o
|
|
|
98
81
|
document.removeEventListener("mousemove", mouseMoveHandler);
|
|
99
82
|
lastC.current = null;
|
|
100
83
|
setA(false);
|
|
101
|
-
// drag end commits geometry mutated in place - invisible to the map, so announce
|
|
102
|
-
// it; a no-move click also lands here, the save-side diff turns that into a no-op
|
|
103
84
|
if (ks)
|
|
104
85
|
floatingWindowMap.touch(ks);
|
|
105
86
|
};
|
|
106
|
-
// Touch
|
|
107
87
|
const touchMoveHandler = (e) => {
|
|
108
88
|
const data = lastT.current;
|
|
109
89
|
if (!data)
|
|
@@ -150,17 +130,14 @@ export function FloatingWindowBase({ children, keyForSave: ks, position, size, o
|
|
|
150
130
|
floatingWindowMap.touch(ks);
|
|
151
131
|
}
|
|
152
132
|
};
|
|
153
|
-
// If mouse dragging mode is active
|
|
154
133
|
if (a) {
|
|
155
134
|
document.addEventListener("mousemove", mouseMoveHandler);
|
|
156
135
|
document.addEventListener("mouseup", mouseUpHandler);
|
|
157
136
|
}
|
|
158
|
-
// If touch-event dragging mode is active
|
|
159
137
|
if (b) {
|
|
160
138
|
document.addEventListener("touchmove", touchMoveHandler);
|
|
161
139
|
document.addEventListener("touchend", touchEndHandler);
|
|
162
140
|
}
|
|
163
|
-
// Return the cleanup function for unmount and a/b changes:
|
|
164
141
|
return () => {
|
|
165
142
|
document.removeEventListener("mousemove", mouseMoveHandler);
|
|
166
143
|
document.removeEventListener("mouseup", mouseUpHandler);
|
|
@@ -168,30 +145,22 @@ export function FloatingWindowBase({ children, keyForSave: ks, position, size, o
|
|
|
168
145
|
document.removeEventListener("touchend", touchEndHandler);
|
|
169
146
|
};
|
|
170
147
|
}, [a, b]);
|
|
171
|
-
// On the first render, add the window to the array; remove it on unmount
|
|
172
148
|
useEffect(() => {
|
|
173
149
|
openWindows.ar.push(id);
|
|
174
|
-
|
|
150
|
+
openWindowsApi.render();
|
|
175
151
|
return () => {
|
|
176
152
|
const z = openWindows.ar.findIndex((v) => v.k === id.k);
|
|
177
153
|
if (z >= 0) {
|
|
178
154
|
openWindows.ar.splice(z, 1);
|
|
179
|
-
|
|
155
|
+
openWindowsApi.render();
|
|
180
156
|
}
|
|
181
157
|
};
|
|
182
158
|
}, []);
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}
|
|
188
|
-
if (position) {
|
|
189
|
-
position.x = x;
|
|
190
|
-
position.y = y;
|
|
191
|
-
}
|
|
159
|
+
savedSize.height = height;
|
|
160
|
+
savedSize.width = width;
|
|
161
|
+
savedPosition.x = x;
|
|
162
|
+
savedPosition.y = y;
|
|
192
163
|
const headerRef = useRef(null);
|
|
193
|
-
// clamp to the viewport in a layout effect: an inline ref-callback ran twice per render
|
|
194
|
-
// (null + element) with a forced reflow from getBoundingClientRect on each call
|
|
195
164
|
useLayoutEffect(() => {
|
|
196
165
|
const el = headerRef.current;
|
|
197
166
|
if (!el || !sizeByWindow)
|
|
@@ -208,39 +177,88 @@ export function FloatingWindowBase({ children, keyForSave: ks, position, size, o
|
|
|
208
177
|
setHeight(window.innerHeight);
|
|
209
178
|
}
|
|
210
179
|
}, [x, y, width, height, sizeByWindow]);
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
setB(true);
|
|
221
|
-
}, onMouseDown: (e) => {
|
|
222
|
-
lastC.current = {
|
|
223
|
-
x: x - e.clientX,
|
|
224
|
-
y: y - e.clientY
|
|
180
|
+
const onHeaderTouchStart = (e) => {
|
|
181
|
+
const t = e.changedTouches[0];
|
|
182
|
+
if (t) {
|
|
183
|
+
lastT.current = {
|
|
184
|
+
x: x - t.clientX,
|
|
185
|
+
y: y - t.clientY,
|
|
186
|
+
id: t.identifier
|
|
225
187
|
};
|
|
226
|
-
|
|
227
|
-
|
|
188
|
+
}
|
|
189
|
+
setB(true);
|
|
190
|
+
};
|
|
191
|
+
const onHeaderMouseDown = (e) => {
|
|
192
|
+
lastC.current = {
|
|
193
|
+
x: x - e.clientX,
|
|
194
|
+
y: y - e.clientY
|
|
195
|
+
};
|
|
196
|
+
setA(true);
|
|
197
|
+
};
|
|
198
|
+
const onWindowMouseDown = () => {
|
|
199
|
+
const z = openWindows.ar.findIndex((v) => v === id);
|
|
200
|
+
if (z !== openWindows.ar.length - 1 || zindex.current !== z) {
|
|
201
|
+
const buf = openWindows.ar[z];
|
|
202
|
+
openWindows.ar.splice(z, 1);
|
|
203
|
+
openWindows.ar.push(buf);
|
|
204
|
+
openWindowsApi.render();
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
const onResizeStop = (e, dir, elementRef, delta, { x: nx, y: ny }) => {
|
|
208
|
+
setX(nx);
|
|
209
|
+
setY(ny);
|
|
210
|
+
setHeight(elementRef.offsetHeight);
|
|
211
|
+
setWidth(elementRef.offsetWidth);
|
|
212
|
+
setUpdate(update + 1);
|
|
213
|
+
if (ks)
|
|
214
|
+
floatingWindowMap.touch(ks);
|
|
215
|
+
};
|
|
216
|
+
const onResize = (e, dir, elementRef, delta, pos) => {
|
|
217
|
+
onUpdate?.({ e, dir, elementRef, delta, position: pos });
|
|
218
|
+
};
|
|
219
|
+
const windowZIndex = zIndexX * 2 + zIndex;
|
|
220
|
+
return {
|
|
221
|
+
position: { x, y },
|
|
222
|
+
size: { width, height },
|
|
223
|
+
update,
|
|
224
|
+
stackIndex: zIndexX,
|
|
225
|
+
zIndex: windowZIndex,
|
|
226
|
+
overlayZIndex: windowZIndex + 1,
|
|
227
|
+
dragging: a || b,
|
|
228
|
+
headerRef,
|
|
229
|
+
onHeaderTouchStart,
|
|
230
|
+
onHeaderMouseDown,
|
|
231
|
+
onWindowMouseDown,
|
|
232
|
+
onResize,
|
|
233
|
+
onResizeStop,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Wrapper component around react-rnd.
|
|
238
|
+
* Provides dragging and resizing, an optional header, and a close button.
|
|
239
|
+
*/
|
|
240
|
+
export function FloatingWindowBase({ children, keyForSave: ks, position, size, overflow = true, zIndex = 9, onUpdate, disableDragging, className, header, moveOnlyHeader, limit, onCLickClose, sizeByWindow = true }) {
|
|
241
|
+
// NOTE: no implicit limit for onCLickClose windows. `limit` is parent-relative,
|
|
242
|
+
// so {y:{min:0}} pinned windows to their DOM parent's top: a window opened from a
|
|
243
|
+
// centered wrapper (ModalProvider / ModalWrapper at top:50%) could not be dragged
|
|
244
|
+
// above mid-screen. Keeping the window on screen is already handled by the
|
|
245
|
+
// viewport clamp below (header rect vs viewport in useLayoutEffect).
|
|
246
|
+
const controller = useFloatingWindowController({
|
|
247
|
+
keyForSave: ks,
|
|
248
|
+
position,
|
|
249
|
+
size,
|
|
250
|
+
zIndex,
|
|
251
|
+
onUpdate,
|
|
252
|
+
disableDragging,
|
|
253
|
+
limit,
|
|
254
|
+
sizeByWindow,
|
|
255
|
+
});
|
|
256
|
+
const headerD = (_jsx("div", { ref: controller.headerRef, className: "wenayWndHeader", onTouchStart: controller.onHeaderTouchStart, onMouseDown: controller.onHeaderMouseDown, children: header ?? _jsx("div", { className: "wenayWndHeaderDef" }) }));
|
|
228
257
|
return (_jsx(Rnd, { disableDragging: true, style: {
|
|
229
|
-
zIndex:
|
|
230
|
-
}, className: className, onResizeStop:
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
// actual element size: `+height + delta.height` gave NaN for string sizes like "100%"
|
|
234
|
-
setHeight(elementRef.offsetHeight);
|
|
235
|
-
setWidth(elementRef.offsetWidth);
|
|
236
|
-
setUpdate(update + 1);
|
|
237
|
-
if (ks)
|
|
238
|
-
floatingWindowMap.touch(ks);
|
|
239
|
-
}, onResize: (e, dir, elementRef, delta, pos) => {
|
|
240
|
-
onUpdate?.({ e, dir, elementRef, delta, position: pos });
|
|
241
|
-
}, position: { x, y }, size: { width, height }, default: {
|
|
242
|
-
...position,
|
|
243
|
-
...size
|
|
258
|
+
zIndex: controller.zIndex
|
|
259
|
+
}, className: className, onResizeStop: controller.onResizeStop, onResize: controller.onResize, position: controller.position, size: controller.size, default: {
|
|
260
|
+
...controller.position,
|
|
261
|
+
...controller.size
|
|
244
262
|
}, children: _jsxs("div", { className: "wenayWnd", style: {
|
|
245
263
|
width: "100%",
|
|
246
264
|
height: "100%",
|
|
@@ -248,20 +266,11 @@ export function FloatingWindowBase({ children, keyForSave: ks, position, size, o
|
|
|
248
266
|
flexDirection: "column",
|
|
249
267
|
position: "relative",
|
|
250
268
|
flex: "auto"
|
|
251
|
-
}, onMouseDown: (
|
|
252
|
-
// Bring the window to the top
|
|
253
|
-
const z = openWindows.ar.findIndex((v) => v === id);
|
|
254
|
-
if (z !== openWindows.ar.length - 1 || zindex.current !== z) {
|
|
255
|
-
const buf = openWindows.ar[z];
|
|
256
|
-
openWindows.ar.splice(z, 1);
|
|
257
|
-
openWindows.ar.push(buf);
|
|
258
|
-
renderBy(openWindows);
|
|
259
|
-
}
|
|
260
|
-
}, children: [moveOnlyHeader || header ? headerD : null, _jsxs("div", { className: "maxSize", style: { overflow: overflow ? "auto" : undefined }, children: [(a || b) && (_jsx("div", { className: "maxSize", style: {
|
|
269
|
+
}, onMouseDown: controller.onWindowMouseDown, children: [moveOnlyHeader || header ? headerD : null, _jsxs("div", { className: "maxSize", style: { overflow: overflow ? "auto" : undefined }, children: [controller.dragging && (_jsx("div", { className: "maxSize", style: {
|
|
261
270
|
position: "absolute",
|
|
262
|
-
zIndex:
|
|
263
|
-
} })), typeof children === "function" ? children(update) : children] }), onCLickClose && (_jsx("div", { className: "wenayCloseBtn wenayWndClose", title: "Close", style: {
|
|
264
|
-
zIndex:
|
|
271
|
+
zIndex: controller.overlayZIndex
|
|
272
|
+
} })), typeof children === "function" ? children(controller.update) : children] }), onCLickClose && (_jsx("div", { className: "wenayCloseBtn wenayWndClose", title: "Close", style: {
|
|
273
|
+
zIndex: controller.overlayZIndex
|
|
265
274
|
}, onClick: onCLickClose, children: _jsx("svg", { width: "12", height: "12", viewBox: "0 0 12 12", "aria-hidden": "true", children: _jsx("path", { d: "M2 2 L10 10 M10 2 L2 10", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" }) }) }, "323"))] }) }));
|
|
266
275
|
}
|
|
267
276
|
/**
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { JSX } from 'react';
|
|
2
|
+
import { useDraggable } from "../../hooks/useDraggable";
|
|
2
3
|
import { type MenuRightPosition, type MenuRightVerticalPosition } from "./RightMenuStore";
|
|
3
4
|
export type MenuElement = {
|
|
4
5
|
label: string;
|
|
@@ -32,7 +33,33 @@ export type DropdownMenuProps = {
|
|
|
32
33
|
keyForSave?: string;
|
|
33
34
|
};
|
|
34
35
|
export type MenuRightRenderProps = Omit<DropdownMenuProps, 'elements'>;
|
|
35
|
-
export
|
|
36
|
+
export type UseRightMenuControllerOptions = Pick<DropdownMenuProps, 'elements' | 'style' | 'styles' | 'position' | 'verticalPosition' | 'keyForSave'>;
|
|
37
|
+
export type RightMenuController = {
|
|
38
|
+
elements: MenuElement[];
|
|
39
|
+
isOpen: boolean;
|
|
40
|
+
isFixed: boolean;
|
|
41
|
+
select: number | null;
|
|
42
|
+
position: MenuRightPosition;
|
|
43
|
+
isTop: boolean;
|
|
44
|
+
containerRef: React.RefObject<HTMLDivElement | null>;
|
|
45
|
+
dragProps: ReturnType<typeof useDraggable>['dragProps'];
|
|
46
|
+
triggerState: MenuRightTriggerState;
|
|
47
|
+
containerStyle: React.CSSProperties;
|
|
48
|
+
flyoutStyle: React.CSSProperties;
|
|
49
|
+
submenuRender: React.ReactElement;
|
|
50
|
+
hasSubmenu: boolean;
|
|
51
|
+
open(): void;
|
|
52
|
+
close(): void;
|
|
53
|
+
toggleFixed(): void;
|
|
54
|
+
setOpen(open: boolean): void;
|
|
55
|
+
selectItem(item: MenuElement, index: number): void;
|
|
56
|
+
contentMouseEnter(): void;
|
|
57
|
+
contentMouseLeave(): Promise<void>;
|
|
58
|
+
submenuMouseEnter(): void;
|
|
59
|
+
submenuMouseLeave(): Promise<void>;
|
|
60
|
+
};
|
|
61
|
+
export declare function useRightMenuController({ elements, style, styles, position: initialPosition, verticalPosition: initialVerticalPosition, keyForSave }: UseRightMenuControllerOptions): RightMenuController;
|
|
62
|
+
export declare function DropdownMenu({ elements, style, styles, classNames, trigger, position, verticalPosition, keyForSave }: DropdownMenuProps): import("react/jsx-runtime").JSX.Element;
|
|
36
63
|
export declare function createRightMenuController(): {
|
|
37
64
|
set(array: MenuElement[]): void;
|
|
38
65
|
delete(array: MenuElement[]): void;
|
|
@@ -12,7 +12,7 @@ function cx(...parts) {
|
|
|
12
12
|
function renderTrigger(trigger, state) {
|
|
13
13
|
return typeof trigger == 'function' ? trigger(state) : trigger;
|
|
14
14
|
}
|
|
15
|
-
export function
|
|
15
|
+
export function useRightMenuController({ elements, style, styles, position: initialPosition = 'right', verticalPosition: initialVerticalPosition = 'top', keyForSave }) {
|
|
16
16
|
const [initialState] = useState(() => {
|
|
17
17
|
const fallback = {
|
|
18
18
|
position: initialPosition,
|
|
@@ -31,9 +31,8 @@ export function DropdownMenu({ elements, style, styles, classNames = {}, trigger
|
|
|
31
31
|
const [isFixed, setIsFixed] = useState(false);
|
|
32
32
|
const [select, setSelect] = useState(null);
|
|
33
33
|
const data = useRef({ m1: false, m2: false });
|
|
34
|
-
// Get modal JSX functions
|
|
35
34
|
const jsx = useMemo(createModalRenderStore, []);
|
|
36
|
-
const
|
|
35
|
+
const submenuRender = useMemo(() => _jsx(jsx.Render, {}), [jsx]);
|
|
37
36
|
const [position, setPosition] = useState(initialState.position);
|
|
38
37
|
const [isTop, setIsTop] = useState(initialState.verticalPosition === 'top');
|
|
39
38
|
const positionLast = useRef({ ...initialState.offset });
|
|
@@ -82,67 +81,50 @@ export function DropdownMenu({ elements, style, styles, classNames = {}, trigger
|
|
|
82
81
|
}
|
|
83
82
|
}, [isTop, keyForSave, position]);
|
|
84
83
|
const { position: pos, dragProps } = useDraggable(0, 0, 50, handleDragEnd, () => { });
|
|
85
|
-
|
|
86
|
-
const handleClickOutside = useCallback(() => {
|
|
84
|
+
const close = useCallback(() => {
|
|
87
85
|
setIsOpen(false);
|
|
88
86
|
}, []);
|
|
89
|
-
const
|
|
87
|
+
const toggleFixed = useCallback(() => {
|
|
90
88
|
setIsFixed((prev) => !prev);
|
|
91
89
|
setIsOpen((prev) => !prev);
|
|
92
90
|
}, []);
|
|
93
|
-
const
|
|
91
|
+
const selectItem = useCallback((item, index) => {
|
|
94
92
|
jsx.set(item.subMenuContent);
|
|
95
93
|
setSelect(index);
|
|
96
94
|
}, [jsx]);
|
|
97
|
-
const
|
|
95
|
+
const contentMouseEnter = useCallback(() => {
|
|
98
96
|
data.current.m1 = true;
|
|
99
97
|
}, []);
|
|
100
|
-
const
|
|
101
|
-
data.current.m1 = false;
|
|
98
|
+
const clearSelectIfIdle = useCallback(async () => {
|
|
102
99
|
await sleepAsync(50);
|
|
103
100
|
if (!data.current.m1 && !data.current.m2) {
|
|
104
101
|
jsx.set(null);
|
|
105
102
|
setSelect(null);
|
|
106
103
|
}
|
|
107
104
|
}, [jsx]);
|
|
108
|
-
const
|
|
105
|
+
const contentMouseLeave = useCallback(async () => {
|
|
106
|
+
data.current.m1 = false;
|
|
107
|
+
await clearSelectIfIdle();
|
|
108
|
+
}, [clearSelectIfIdle]);
|
|
109
|
+
const submenuMouseEnter = useCallback(() => {
|
|
109
110
|
data.current.m2 = true;
|
|
110
111
|
}, []);
|
|
111
|
-
const
|
|
112
|
+
const submenuMouseLeave = useCallback(async () => {
|
|
112
113
|
data.current.m2 = false;
|
|
113
|
-
await
|
|
114
|
-
|
|
115
|
-
jsx.set(null);
|
|
116
|
-
setSelect(null);
|
|
117
|
-
}
|
|
118
|
-
}, [jsx]);
|
|
119
|
-
// Render dropdown menu (dop)
|
|
120
|
-
const dop = (isFixed || isOpen) && (_jsxs("div", { onMouseEnter: handleContentMouseEnter, onMouseLeave: handleContentMouseLeave, className: cx(classNames.flyout ?? 'dropdown-content2', !isTop ? (classNames.flyoutUp ?? 'dropdown-up') : undefined), style: {
|
|
121
|
-
display: 'flex',
|
|
122
|
-
[position]: 0,
|
|
123
|
-
right: position === 'left' ? 'auto' : 0,
|
|
124
|
-
flexDirection: position === 'left' ? 'row' : 'row-reverse',
|
|
125
|
-
...styles?.flyout
|
|
126
|
-
}, children: [_jsx("div", { className: classNames.list ?? "dropdown-content", style: styles?.list, onMouseEnter: handleContentMouseEnter, onMouseLeave: handleContentMouseLeave, children: elements.map((item, index) => (_jsx("div", { className: cx(classNames.item ?? 'menu-item', select === index ? (classNames.itemActive ?? 'force-hover') : undefined), style: styles?.item, onMouseEnter: () => handleSelect(item, index), onClick: () => handleSelect(item, index), children: item.label }, item.label))) }), _jsx("div", { onMouseEnter: handleSubmenuMouseEnter, onMouseLeave: handleSubmenuMouseLeave, children: jsx.JSX ? (
|
|
127
|
-
// clamp to the viewport: with hard 40vw/70vh the submenu ran
|
|
128
|
-
// off screen when the menu sat near a bottom/side edge
|
|
129
|
-
_jsx("div", { className: classNames.submenu ?? "submenu", style: { width: '40vw', minHeight: '70vh',
|
|
130
|
-
maxWidth: '90vw', maxHeight: '85vh', overflowY: 'auto', ...styles?.submenu }, children: jsxRender })) : null })] }));
|
|
131
|
-
// Calculate offsets
|
|
114
|
+
await clearSelectIfIdle();
|
|
115
|
+
}, [clearSelectIfIdle]);
|
|
132
116
|
const computedX = position === 'left'
|
|
133
117
|
? positionLast.current.x + pos.x
|
|
134
118
|
: positionLast.current.x - pos.x;
|
|
135
119
|
const computedY = isTop
|
|
136
120
|
? positionLast.current.y + pos.y
|
|
137
121
|
: positionLast.current.y - pos.y;
|
|
138
|
-
// Calculate menu container styles
|
|
139
122
|
const containerStyle = useMemo(() => {
|
|
140
123
|
const computedStyle = {
|
|
141
124
|
...style,
|
|
142
125
|
...styles?.container,
|
|
143
126
|
display: 'flex'
|
|
144
127
|
};
|
|
145
|
-
// Horizontal positioning
|
|
146
128
|
if (position === 'left') {
|
|
147
129
|
computedStyle.left = Math.max(0, Math.min(computedX, window.innerWidth - 50));
|
|
148
130
|
computedStyle.right = 'auto';
|
|
@@ -151,7 +133,6 @@ export function DropdownMenu({ elements, style, styles, classNames = {}, trigger
|
|
|
151
133
|
computedStyle.right = Math.max(0, Math.min(computedX, window.innerWidth - 50));
|
|
152
134
|
computedStyle.left = 'auto';
|
|
153
135
|
}
|
|
154
|
-
// Vertical positioning
|
|
155
136
|
if (isTop) {
|
|
156
137
|
computedStyle.top = Math.max(0, Math.min(computedY, window.innerHeight - 50));
|
|
157
138
|
computedStyle.bottom = 'auto';
|
|
@@ -162,7 +143,53 @@ export function DropdownMenu({ elements, style, styles, classNames = {}, trigger
|
|
|
162
143
|
}
|
|
163
144
|
return computedStyle;
|
|
164
145
|
}, [style, styles?.container, position, isTop, computedX, computedY]);
|
|
165
|
-
|
|
146
|
+
const flyoutStyle = useMemo(() => ({
|
|
147
|
+
display: 'flex',
|
|
148
|
+
[position]: 0,
|
|
149
|
+
right: position === 'left' ? 'auto' : 0,
|
|
150
|
+
flexDirection: position === 'left' ? 'row' : 'row-reverse',
|
|
151
|
+
...styles?.flyout
|
|
152
|
+
}), [position, styles?.flyout]);
|
|
153
|
+
return {
|
|
154
|
+
elements,
|
|
155
|
+
isOpen,
|
|
156
|
+
isFixed,
|
|
157
|
+
select,
|
|
158
|
+
position,
|
|
159
|
+
isTop,
|
|
160
|
+
containerRef,
|
|
161
|
+
dragProps,
|
|
162
|
+
triggerState: { isOpen, isFixed },
|
|
163
|
+
containerStyle,
|
|
164
|
+
flyoutStyle,
|
|
165
|
+
submenuRender,
|
|
166
|
+
hasSubmenu: !!jsx.JSX,
|
|
167
|
+
open: () => setIsOpen(true),
|
|
168
|
+
close,
|
|
169
|
+
toggleFixed,
|
|
170
|
+
setOpen: setIsOpen,
|
|
171
|
+
selectItem,
|
|
172
|
+
contentMouseEnter,
|
|
173
|
+
contentMouseLeave,
|
|
174
|
+
submenuMouseEnter,
|
|
175
|
+
submenuMouseLeave,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
export function DropdownMenu({ elements, style, styles, classNames = {}, trigger = '☰', position, verticalPosition, keyForSave }) {
|
|
179
|
+
const controller = useRightMenuController({
|
|
180
|
+
elements,
|
|
181
|
+
style,
|
|
182
|
+
styles,
|
|
183
|
+
position,
|
|
184
|
+
verticalPosition,
|
|
185
|
+
keyForSave,
|
|
186
|
+
});
|
|
187
|
+
const dop = (controller.isFixed || controller.isOpen) && (_jsxs("div", { onMouseEnter: controller.contentMouseEnter, onMouseLeave: controller.contentMouseLeave, className: cx(classNames.flyout ?? 'dropdown-content2', !controller.isTop ? (classNames.flyoutUp ?? 'dropdown-up') : undefined), style: controller.flyoutStyle, children: [_jsx("div", { className: classNames.list ?? "dropdown-content", style: styles?.list, onMouseEnter: controller.contentMouseEnter, onMouseLeave: controller.contentMouseLeave, children: elements.map((item, index) => (_jsx("div", { className: cx(classNames.item ?? 'menu-item', controller.select === index ? (classNames.itemActive ?? 'force-hover') : undefined), style: styles?.item, onMouseEnter: () => controller.selectItem(item, index), onClick: () => controller.selectItem(item, index), children: item.label }, item.label))) }), _jsx("div", { onMouseEnter: controller.submenuMouseEnter, onMouseLeave: controller.submenuMouseLeave, children: controller.hasSubmenu ? (
|
|
188
|
+
// clamp to the viewport: with hard 40vw/70vh the submenu ran
|
|
189
|
+
// off screen when the menu sat near a bottom/side edge
|
|
190
|
+
_jsx("div", { className: classNames.submenu ?? "submenu", style: { width: '40vw', minHeight: '70vh',
|
|
191
|
+
maxWidth: '90vw', maxHeight: '85vh', overflowY: 'auto', ...styles?.submenu }, children: controller.submenuRender })) : null })] }));
|
|
192
|
+
return (_jsxs(OutsideClickArea, { ref: controller.containerRef, outsideClick: controller.close, className: cx(classNames.container ?? 'menu-container', controller.isFixed ? (classNames.activeContainer ?? 'activeM') : undefined), style: controller.containerStyle, onMouseEnter: () => !controller.isFixed && controller.setOpen(true), onMouseLeave: () => !controller.isFixed && controller.setOpen(false), children: [_jsx("div", { ...controller.dragProps, className: classNames.trigger ?? "menu-button", style: styles?.trigger, onClick: controller.toggleFixed, children: renderTrigger(trigger, controller.triggerState) }), dop] }));
|
|
166
193
|
}
|
|
167
194
|
export function createRightMenuController() {
|
|
168
195
|
const elements = [];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
3
3
|
import { colorGenerator2, sleepAsync } from "wenay-common2";
|
|
4
|
-
import {
|
|
4
|
+
import { createUpdateApi } from "../../../updateBy";
|
|
5
5
|
import { createModalElementStore } from "./Modal";
|
|
6
6
|
import { DragBox } from "../Dnd/FloatingWindow";
|
|
7
7
|
function useViewport() {
|
|
@@ -206,6 +206,7 @@ export function getApiLeftMenu() {
|
|
|
206
206
|
}, children: textB })), children()] }));
|
|
207
207
|
};
|
|
208
208
|
const menuStore = new Map();
|
|
209
|
+
const menuStoreApi = createUpdateApi(menuStore);
|
|
209
210
|
const setMenu = (items, key = "base") => {
|
|
210
211
|
const colorGen = colorGenerator2({ min: 0, max: 90 });
|
|
211
212
|
// exclude this key's old items, otherwise a repeated setMenu shifts default colors
|
|
@@ -242,15 +243,15 @@ export function getApiLeftMenu() {
|
|
|
242
243
|
return;
|
|
243
244
|
lastSet.current = { menu, menuKey };
|
|
244
245
|
setMenu(menu, menuKey);
|
|
245
|
-
|
|
246
|
+
menuStoreApi.render();
|
|
246
247
|
}, [menu, menuKey]);
|
|
247
|
-
|
|
248
|
+
menuStoreApi.use();
|
|
248
249
|
return (_jsxs("div", { className: "maxSize", style: { position: "absolute", zIndex: zIndex0 }, children: [_jsx(modal.Render, {}), _jsx(LeftMenuComponent, { zIndex: zIndex, api: () => { }, menu: getAllMenuItems() })] }));
|
|
249
250
|
}
|
|
250
251
|
const modal = createModalElementStore();
|
|
251
252
|
return {
|
|
252
253
|
modal,
|
|
253
|
-
renderBy() {
|
|
254
|
+
renderBy() { menuStoreApi.render(); },
|
|
254
255
|
getMenu: () => menuStore,
|
|
255
256
|
setMenu: setMenu,
|
|
256
257
|
Modal2: Modal2
|