wenay-react2 1.0.27 → 1.0.29
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/api.d.ts +4 -1
- package/lib/common/api.js +4 -1
- package/lib/common/src/components/Dnd/RNDFunc3.js +9 -17
- package/lib/common/src/components/Menu/RightMenu.d.ts +9 -9
- package/lib/common/src/components/Menu/RightMenu.js +68 -38
- package/lib/common/src/components/Menu/RightMenuStore.d.ts +11 -0
- package/lib/common/src/components/Menu/RightMenuStore.js +1 -0
- package/lib/common/src/components/Menu/index.d.ts +1 -0
- package/lib/common/src/components/Menu/index.js +1 -0
- package/lib/common/src/components/Settings/SettingsDialog.d.ts +22 -0
- package/lib/common/src/components/Settings/SettingsDialog.js +55 -0
- package/lib/common/src/components/Settings/index.d.ts +1 -0
- package/lib/common/src/components/Settings/index.js +1 -0
- package/lib/common/src/components/UiSlot/UiSlot.d.ts +24 -0
- package/lib/common/src/components/UiSlot/UiSlot.js +33 -0
- package/lib/common/src/components/UiSlot/index.d.ts +1 -0
- package/lib/common/src/components/UiSlot/index.js +1 -0
- package/lib/common/src/styles/tokens.d.ts +39 -0
- package/lib/common/src/styles/tokens.js +39 -0
- package/lib/common/src/utils/cache.d.ts +4 -0
- package/lib/common/src/utils/cache.js +52 -3
- package/lib/common/src/utils/callbackHub.d.ts +10 -0
- package/lib/common/src/utils/callbackHub.js +23 -0
- package/lib/common/src/utils/index.d.ts +1 -0
- package/lib/common/src/utils/index.js +1 -0
- package/lib/common/src/utils/mapMemory.d.ts +3 -0
- package/lib/common/src/utils/mapMemory.js +3 -0
- package/lib/style/style.css +121 -11
- package/lib/style/tokens.css +43 -0
- package/package.json +1 -1
package/lib/common/api.d.ts
CHANGED
|
@@ -25,7 +25,10 @@ export * from "./src/components/ParametersEngine";
|
|
|
25
25
|
export * from "./src/components/Input";
|
|
26
26
|
export * from "./src/components/Modal";
|
|
27
27
|
export * from "./src/components/Menu";
|
|
28
|
-
export * from "./src/
|
|
28
|
+
export * from "./src/components/Settings";
|
|
29
|
+
export * from "./src/components/UiSlot";
|
|
30
|
+
export { MenuBase, TimeNum, MenuElement as LegacyMenuElement } from "./src/menu/menu";
|
|
31
|
+
export type { tMenuReact, tMenuReactStrictly } from "./src/menu/menu";
|
|
29
32
|
export * from "./src/menu/menuMouse";
|
|
30
33
|
export * from "./src/menu/menuR";
|
|
31
34
|
export * from "./src/logs/logs";
|
package/lib/common/api.js
CHANGED
|
@@ -37,8 +37,11 @@ export * from "./src/components/Input";
|
|
|
37
37
|
export * from "./src/components/Modal";
|
|
38
38
|
// 11. Menu - depends on hooks, Dnd, and Modal
|
|
39
39
|
export * from "./src/components/Menu";
|
|
40
|
+
// 11b. Settings dialog + section registry; UI slot with configurable placement
|
|
41
|
+
export * from "./src/components/Settings";
|
|
42
|
+
export * from "./src/components/UiSlot";
|
|
40
43
|
// 12. MENU - depends on components
|
|
41
|
-
export
|
|
44
|
+
export { MenuBase, TimeNum, MenuElement as LegacyMenuElement } from "./src/menu/menu";
|
|
42
45
|
export * from "./src/menu/menuMouse";
|
|
43
46
|
export * from "./src/menu/menuR";
|
|
44
47
|
// 13. LOGS - depend on utils/mapMemory, menu, and components/ParametersEngine
|
|
@@ -21,9 +21,11 @@ export const DivRnd3 = (a) => {
|
|
|
21
21
|
* Provides dragging and resizing, an optional header, and a close button.
|
|
22
22
|
*/
|
|
23
23
|
export function DivRndBase3({ children, keyForSave: ks, position, size, overflow = true, zIndex = 9, onUpdate, disableDragging, className, header, moveOnlyHeader, limit, onCLickClose, sizeByWindow = true }) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
// NOTE: no implicit limit for onCLickClose windows. `limit` is parent-relative,
|
|
25
|
+
// so {y:{min:0}} pinned windows to their DOM parent's top: a window opened from a
|
|
26
|
+
// centered wrapper (ModalProvider / ModalWrapper at top:50%) could not be dragged
|
|
27
|
+
// above mid-screen. Keeping the window on screen is already handled by the
|
|
28
|
+
// viewport clamp below (header rect vs viewport in useLayoutEffect).
|
|
27
29
|
const positionDef = { x: 0, y: 0, ...(position ?? {}) };
|
|
28
30
|
const sizeDef = { height: 0, width: 0, ...(size ?? {}) };
|
|
29
31
|
// If there is a key, store position and size data in the map
|
|
@@ -199,7 +201,7 @@ export function DivRndBase3({ children, keyForSave: ks, position, size, overflow
|
|
|
199
201
|
setHeight(window.innerHeight);
|
|
200
202
|
}
|
|
201
203
|
}, [x, y, width, height, sizeByWindow]);
|
|
202
|
-
const headerD = (_jsx("div", { ref: headerRef, onTouchStart: (e) => {
|
|
204
|
+
const headerD = (_jsx("div", { ref: headerRef, className: "wenayWndHeader", onTouchStart: (e) => {
|
|
203
205
|
const t = e.changedTouches[0];
|
|
204
206
|
if (t) {
|
|
205
207
|
lastT.current = {
|
|
@@ -215,14 +217,7 @@ export function DivRndBase3({ children, keyForSave: ks, position, size, overflow
|
|
|
215
217
|
y: y - e.clientY
|
|
216
218
|
};
|
|
217
219
|
setA(true);
|
|
218
|
-
},
|
|
219
|
-
userSelect: "none",
|
|
220
|
-
cursor: "grabbing"
|
|
221
|
-
}, children: header ?? (_jsx("div", { style: {
|
|
222
|
-
height: 20,
|
|
223
|
-
width: "100%",
|
|
224
|
-
backgroundImage: "repeating-linear-gradient(139deg, hsla(0,0%,100%,.1), hsla(0,0%,100%,.1) 15px, transparent 0, transparent 30px)"
|
|
225
|
-
} })) }));
|
|
220
|
+
}, children: header ?? _jsx("div", { className: "wenayWndHeaderDef" }) }));
|
|
226
221
|
return (_jsx(Rnd, { disableDragging: true, style: {
|
|
227
222
|
zIndex: zIndexX * 2 + zIndex
|
|
228
223
|
}, className: className, onResizeStop: (e, dir, elementRef, delta, { x: nx, y: ny }) => {
|
|
@@ -237,7 +232,7 @@ export function DivRndBase3({ children, keyForSave: ks, position, size, overflow
|
|
|
237
232
|
}, position: { x, y }, size: { width, height }, default: {
|
|
238
233
|
...position,
|
|
239
234
|
...size
|
|
240
|
-
}, children: _jsxs("div", { style: {
|
|
235
|
+
}, children: _jsxs("div", { className: "wenayWnd", style: {
|
|
241
236
|
width: "100%",
|
|
242
237
|
height: "100%",
|
|
243
238
|
display: "flex",
|
|
@@ -256,10 +251,7 @@ export function DivRndBase3({ children, keyForSave: ks, position, size, overflow
|
|
|
256
251
|
}, children: [moveOnlyHeader || header ? headerD : null, _jsxs("div", { className: "maxSize", style: { overflow: overflow ? "auto" : undefined }, children: [(a || b) && (_jsx("div", { className: "maxSize", style: {
|
|
257
252
|
position: "absolute",
|
|
258
253
|
zIndex: zIndexX * 2 + zIndex + 1
|
|
259
|
-
} })), typeof children === "function" ? children(update) : children] }), onCLickClose && (_jsx("div", { className: "wenayCloseBtn", title: "Close", style: {
|
|
260
|
-
position: "absolute",
|
|
261
|
-
right: -12,
|
|
262
|
-
top: -12,
|
|
254
|
+
} })), typeof children === "function" ? children(update) : children] }), onCLickClose && (_jsx("div", { className: "wenayCloseBtn wenayWndClose", title: "Close", style: {
|
|
263
255
|
zIndex: zIndexX * 2 + zIndex + 1
|
|
264
256
|
}, 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"))] }) }));
|
|
265
257
|
}
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
import React, { JSX } from 'react';
|
|
2
|
-
type
|
|
2
|
+
import { type MenuRightPosition, type MenuRightPosition2 } from "./RightMenuStore";
|
|
3
|
+
export type MenuElement = {
|
|
3
4
|
label: string;
|
|
4
5
|
subMenuContent: () => JSX.Element;
|
|
5
6
|
};
|
|
6
|
-
type DropdownMenuProps = {
|
|
7
|
+
export type DropdownMenuProps = {
|
|
7
8
|
elements: MenuElement[];
|
|
8
9
|
style?: React.CSSProperties;
|
|
9
|
-
position?:
|
|
10
|
-
position2?:
|
|
10
|
+
position?: MenuRightPosition;
|
|
11
|
+
position2?: MenuRightPosition2;
|
|
12
|
+
keyForSave?: string;
|
|
11
13
|
};
|
|
12
|
-
export
|
|
14
|
+
export type MenuRightRenderProps = Omit<DropdownMenuProps, 'elements'>;
|
|
15
|
+
export declare function DropdownMenu({ elements, style, position: initialPosition, position2: initialPos2, keyForSave }: DropdownMenuProps): import("react/jsx-runtime").JSX.Element;
|
|
13
16
|
export declare function MenuRightApi(): {
|
|
14
17
|
set(array: MenuElement[]): void;
|
|
15
18
|
delete(array: MenuElement[]): void;
|
|
16
19
|
get(): MenuElement[];
|
|
17
|
-
Render(
|
|
18
|
-
style?: React.CSSProperties;
|
|
19
|
-
}): import("react/jsx-runtime").JSX.Element;
|
|
20
|
+
Render(props?: MenuRightRenderProps): import("react/jsx-runtime").JSX.Element;
|
|
20
21
|
};
|
|
21
22
|
export declare function DropdownMenuTest(): import("react/jsx-runtime").JSX.Element;
|
|
22
|
-
export {};
|
|
@@ -5,7 +5,22 @@ import { useDraggable } from "../../hooks/useDraggable";
|
|
|
5
5
|
import { DivOutsideClick } from "../../hooks/useOutside";
|
|
6
6
|
import { DraggableOutlineDiv } from "../Dnd/DraggableOutlineDiv";
|
|
7
7
|
import { GetModalFuncJSX } from "../Modal/Modal";
|
|
8
|
-
|
|
8
|
+
import { mapRightMenu } from "./RightMenuStore";
|
|
9
|
+
export function DropdownMenu({ elements, style, position: initialPosition = 'right', position2: initialPos2 = 'top', keyForSave }) {
|
|
10
|
+
const [initialState] = useState(() => {
|
|
11
|
+
const fallback = {
|
|
12
|
+
position: initialPosition,
|
|
13
|
+
position2: initialPos2,
|
|
14
|
+
offset: { x: 0, y: 0 }
|
|
15
|
+
};
|
|
16
|
+
if (!keyForSave)
|
|
17
|
+
return fallback;
|
|
18
|
+
const saved = mapRightMenu.get(keyForSave);
|
|
19
|
+
if (saved)
|
|
20
|
+
return saved;
|
|
21
|
+
mapRightMenu.set(keyForSave, fallback);
|
|
22
|
+
return fallback;
|
|
23
|
+
});
|
|
9
24
|
const [isOpen, setIsOpen] = useState(false);
|
|
10
25
|
const [isFixed, setIsFixed] = useState(false);
|
|
11
26
|
const [select, setSelect] = useState(null);
|
|
@@ -13,41 +28,52 @@ export function DropdownMenu({ elements, style, position: initialPosition = 'rig
|
|
|
13
28
|
// Get modal JSX functions
|
|
14
29
|
const jsx = useMemo(GetModalFuncJSX, []);
|
|
15
30
|
const jsxRender = useMemo(() => _jsx(jsx.Render, {}), [jsx]);
|
|
16
|
-
const [position, setPosition] = useState(
|
|
17
|
-
const [isTop, setIsTop] = useState(
|
|
18
|
-
const positionLast = useRef({
|
|
31
|
+
const [position, setPosition] = useState(initialState.position);
|
|
32
|
+
const [isTop, setIsTop] = useState(initialState.position2 === 'top');
|
|
33
|
+
const positionLast = useRef({ ...initialState.offset });
|
|
34
|
+
const containerRef = useRef(null);
|
|
19
35
|
const handleDragEnd = useCallback((finalPosition) => {
|
|
20
|
-
//
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
positionLast.current.y
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
36
|
+
// Re-anchor from the computed dropped rect. useDraggable resets its delta
|
|
37
|
+
// before React necessarily paints, so measuring the element position here
|
|
38
|
+
// can read the pre-drop corner and cause a sudden relocation.
|
|
39
|
+
const el = containerRef.current;
|
|
40
|
+
if (!el)
|
|
41
|
+
return;
|
|
42
|
+
const parent = el.offsetParent;
|
|
43
|
+
// fixed positioning -> offsetParent is null -> viewport is the box
|
|
44
|
+
const bounds = parent?.getBoundingClientRect()
|
|
45
|
+
?? new DOMRect(0, 0, window.innerWidth, document.documentElement.clientHeight);
|
|
46
|
+
const measured = el.getBoundingClientRect();
|
|
47
|
+
const width = measured.width;
|
|
48
|
+
const height = measured.height;
|
|
49
|
+
const baseLeft = position === 'left'
|
|
50
|
+
? bounds.left + positionLast.current.x
|
|
51
|
+
: bounds.right - positionLast.current.x - width;
|
|
52
|
+
const baseTop = isTop
|
|
53
|
+
? bounds.top + positionLast.current.y
|
|
54
|
+
: bounds.bottom - positionLast.current.y - height;
|
|
55
|
+
const left = baseLeft + finalPosition.x;
|
|
56
|
+
const top = baseTop + finalPosition.y;
|
|
57
|
+
const right = left + width;
|
|
58
|
+
const bottom = top + height;
|
|
59
|
+
const toLeft = left + width / 2 < bounds.left + bounds.width / 2;
|
|
60
|
+
const toTop = top + height / 2 < bounds.top + bounds.height / 2;
|
|
61
|
+
const nextPosition = toLeft ? 'left' : 'right';
|
|
62
|
+
const nextOffset = {
|
|
63
|
+
x: Math.max(0, toLeft ? left - bounds.left : bounds.right - right),
|
|
64
|
+
y: Math.max(0, toTop ? top - bounds.top : bounds.bottom - bottom)
|
|
65
|
+
};
|
|
66
|
+
positionLast.current = nextOffset;
|
|
67
|
+
setPosition(nextPosition);
|
|
68
|
+
setIsTop(toTop);
|
|
69
|
+
if (keyForSave) {
|
|
70
|
+
mapRightMenu.set(keyForSave, {
|
|
71
|
+
position: nextPosition,
|
|
72
|
+
position2: toTop ? 'top' : 'bottom',
|
|
73
|
+
offset: { ...nextOffset }
|
|
74
|
+
});
|
|
49
75
|
}
|
|
50
|
-
}, [
|
|
76
|
+
}, [isTop, keyForSave, position]);
|
|
51
77
|
const { position: pos, dragProps } = useDraggable(0, 0, 50, handleDragEnd, () => { });
|
|
52
78
|
// Click and hover handlers
|
|
53
79
|
const handleClickOutside = useCallback(() => {
|
|
@@ -89,7 +115,11 @@ export function DropdownMenu({ elements, style, position: initialPosition = 'rig
|
|
|
89
115
|
[position]: 0,
|
|
90
116
|
right: position === 'left' ? 'auto' : 0,
|
|
91
117
|
flexDirection: position === 'left' ? 'row' : 'row-reverse'
|
|
92
|
-
}, children: [_jsx("div", { className: "dropdown-content", onMouseEnter: handleContentMouseEnter, onMouseLeave: handleContentMouseLeave, children: elements.map((item, index) => (_jsx("div", { className: `menu-item ${select === index ? 'force-hover' : ''}`, onMouseEnter: () => handleSelect(item, index), onClick: () => handleSelect(item, index), children: item.label }, item.label))) }), _jsx("div", { onMouseEnter: handleSubmenuMouseEnter, onMouseLeave: handleSubmenuMouseLeave, children: jsx.JSX ? (
|
|
118
|
+
}, children: [_jsx("div", { className: "dropdown-content", onMouseEnter: handleContentMouseEnter, onMouseLeave: handleContentMouseLeave, children: elements.map((item, index) => (_jsx("div", { className: `menu-item ${select === index ? 'force-hover' : ''}`, onMouseEnter: () => handleSelect(item, index), onClick: () => handleSelect(item, index), children: item.label }, item.label))) }), _jsx("div", { onMouseEnter: handleSubmenuMouseEnter, onMouseLeave: handleSubmenuMouseLeave, children: jsx.JSX ? (
|
|
119
|
+
// clamp to the viewport: with hard 40vw/70vh the submenu ran
|
|
120
|
+
// off screen when the menu sat near a bottom/side edge
|
|
121
|
+
_jsx("div", { className: "submenu", style: { width: '40vw', minHeight: '70vh',
|
|
122
|
+
maxWidth: '90vw', maxHeight: '85vh', overflowY: 'auto' }, children: jsxRender })) : null })] }));
|
|
93
123
|
// Calculate offsets
|
|
94
124
|
const computedX = position === 'left'
|
|
95
125
|
? positionLast.current.x + pos.x
|
|
@@ -123,7 +153,7 @@ export function DropdownMenu({ elements, style, position: initialPosition = 'rig
|
|
|
123
153
|
}
|
|
124
154
|
return computedStyle;
|
|
125
155
|
}, [style, position, isTop, computedX, computedY]);
|
|
126
|
-
return (_jsxs(DivOutsideClick, { outsideClick: handleClickOutside, className: `menu-container ${isFixed ? 'activeM' : ''}`, style: containerStyle, onMouseEnter: () => !isFixed && setIsOpen(true), onMouseLeave: () => !isFixed && setIsOpen(false), children: [_jsx("div", { ...dragProps, className: "menu-button", onClick: handleToggle, children: "\u2630" }), dop] }));
|
|
156
|
+
return (_jsxs(DivOutsideClick, { ref: containerRef, outsideClick: handleClickOutside, className: `menu-container ${isFixed ? 'activeM' : ''}`, style: containerStyle, onMouseEnter: () => !isFixed && setIsOpen(true), onMouseLeave: () => !isFixed && setIsOpen(false), children: [_jsx("div", { ...dragProps, className: "menu-button", onClick: handleToggle, children: "\u2630" }), dop] }));
|
|
127
157
|
}
|
|
128
158
|
export function MenuRightApi() {
|
|
129
159
|
const elements = [];
|
|
@@ -147,7 +177,7 @@ export function MenuRightApi() {
|
|
|
147
177
|
get() {
|
|
148
178
|
return elements;
|
|
149
179
|
},
|
|
150
|
-
Render(
|
|
180
|
+
Render(props = {}) {
|
|
151
181
|
const [el, setEl] = useState([...elements]);
|
|
152
182
|
useEffect(() => {
|
|
153
183
|
render = setEl;
|
|
@@ -155,7 +185,7 @@ export function MenuRightApi() {
|
|
|
155
185
|
render = null;
|
|
156
186
|
};
|
|
157
187
|
}, []);
|
|
158
|
-
return _jsx(DropdownMenu, { elements: el
|
|
188
|
+
return _jsx(DropdownMenu, { ...props, elements: el });
|
|
159
189
|
}
|
|
160
190
|
};
|
|
161
191
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type MenuRightPosition = 'left' | 'right';
|
|
2
|
+
export type MenuRightPosition2 = 'top' | 'bottom';
|
|
3
|
+
export type MenuRightSavedState = {
|
|
4
|
+
position: MenuRightPosition;
|
|
5
|
+
position2: MenuRightPosition2;
|
|
6
|
+
offset: {
|
|
7
|
+
x: number;
|
|
8
|
+
y: number;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
export declare const mapRightMenu: Map<string, MenuRightSavedState>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const mapRightMenu = new Map();
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export type tSettingsSection = {
|
|
3
|
+
key: string;
|
|
4
|
+
name: string;
|
|
5
|
+
render: () => React.ReactNode;
|
|
6
|
+
};
|
|
7
|
+
/** Register an external section. Re-register with the same key replaces the previous one.
|
|
8
|
+
* The returned function removes exactly this registration (a no-op if it was replaced). */
|
|
9
|
+
export declare function registerSettingsSection(s: tSettingsSection): () => void;
|
|
10
|
+
/** Current external sections (static props sections are not included). */
|
|
11
|
+
export declare function getSettingsSections(): readonly tSettingsSection[];
|
|
12
|
+
/** Centered settings dialog: sections column on the left, active section content on the right.
|
|
13
|
+
* Sections = props.sections (first) + everything from registerSettingsSection.
|
|
14
|
+
* Look is themed via --dlg-* CSS variables (dark defaults), same contract as --wnd-*. */
|
|
15
|
+
export declare function SettingsDialog(props: {
|
|
16
|
+
trigger: React.ReactNode;
|
|
17
|
+
sections?: tSettingsSection[];
|
|
18
|
+
defaultSection?: string;
|
|
19
|
+
/** Section buttons: apps pass their own .chip / .chipActive; defaults are minimal library styles */
|
|
20
|
+
sectionClassName?: string;
|
|
21
|
+
sectionActiveClassName?: string;
|
|
22
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { createPortal } from "react-dom";
|
|
4
|
+
import { renderBy, updateBy } from "../../../updateBy";
|
|
5
|
+
// Module singleton (closure + updateBy subscription, no React context): any module
|
|
6
|
+
// registers a section on mount and removes it on unmount; the dialog re-renders on changes.
|
|
7
|
+
const registry = { list: [] };
|
|
8
|
+
/** Register an external section. Re-register with the same key replaces the previous one.
|
|
9
|
+
* The returned function removes exactly this registration (a no-op if it was replaced). */
|
|
10
|
+
export function registerSettingsSection(s) {
|
|
11
|
+
const i = registry.list.findIndex(e => e.key == s.key);
|
|
12
|
+
if (i == -1)
|
|
13
|
+
registry.list.push(s);
|
|
14
|
+
else
|
|
15
|
+
registry.list.splice(i, 1, s);
|
|
16
|
+
renderBy(registry);
|
|
17
|
+
return () => {
|
|
18
|
+
const j = registry.list.indexOf(s);
|
|
19
|
+
if (j != -1) {
|
|
20
|
+
registry.list.splice(j, 1);
|
|
21
|
+
renderBy(registry);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/** Current external sections (static props sections are not included). */
|
|
26
|
+
export function getSettingsSections() {
|
|
27
|
+
return registry.list;
|
|
28
|
+
}
|
|
29
|
+
/** Centered settings dialog: sections column on the left, active section content on the right.
|
|
30
|
+
* Sections = props.sections (first) + everything from registerSettingsSection.
|
|
31
|
+
* Look is themed via --dlg-* CSS variables (dark defaults), same contract as --wnd-*. */
|
|
32
|
+
export function SettingsDialog(props) {
|
|
33
|
+
const [open, setOpen] = useState(false);
|
|
34
|
+
const [active, setActive] = useState(props.defaultSection);
|
|
35
|
+
updateBy(registry);
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (!open)
|
|
38
|
+
return;
|
|
39
|
+
const onKeyDown = (e) => {
|
|
40
|
+
if (e.key == 'Escape')
|
|
41
|
+
setOpen(false);
|
|
42
|
+
};
|
|
43
|
+
document.addEventListener('keydown', onKeyDown);
|
|
44
|
+
return () => document.removeEventListener('keydown', onKeyDown);
|
|
45
|
+
}, [open]);
|
|
46
|
+
const sections = [...(props.sections ?? []), ...registry.list];
|
|
47
|
+
// fall back to the first section when active was never set or its section unmounted
|
|
48
|
+
const current = sections.find(s => s.key == active) ?? sections[0];
|
|
49
|
+
const base = props.sectionClassName ?? "wenayDlgSection";
|
|
50
|
+
const activeCls = props.sectionActiveClassName ?? "wenayDlgSectionActive";
|
|
51
|
+
return _jsxs(_Fragment, { children: [_jsx("span", { onClick: () => setOpen(true), style: { display: "inline-block", cursor: "pointer" }, children: props.trigger }), open && createPortal(_jsx("div", { className: "wenayDlgScrim", onClick: e => {
|
|
52
|
+
if (e.target == e.currentTarget)
|
|
53
|
+
setOpen(false);
|
|
54
|
+
}, children: _jsxs("div", { className: "wenayDlg", children: [_jsx("div", { className: "wenayDlgNav", children: sections.map(s => (_jsx("div", { className: s.key == current?.key ? `${base} ${activeCls}` : base, onClick: () => setActive(s.key), children: s.name }, s.key))) }), _jsx("div", { className: "wenayDlgContent", children: current?.render() }), _jsx("div", { className: "wenayCloseBtn wenayDlgClose", title: "Close", onClick: () => setOpen(false), 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" }) }) })] }) }), document.body)] });
|
|
55
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './SettingsDialog';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './SettingsDialog';
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
/** One UI block shown in exactly one of several mount points; the point is a persisted setting.
|
|
3
|
+
* Persistence rides the existing staticProps -> Cash mechanics: the app calls Cash.load()/save()
|
|
4
|
+
* the same way it does for window state (ExRNDMap3 etc.).
|
|
5
|
+
* Mount points render <Slot place="..."> themselves and decide nothing: the slot compares
|
|
6
|
+
* the persisted place with its own and renders children only on a match. */
|
|
7
|
+
export declare function createUiSlot<Places extends string>(opts: {
|
|
8
|
+
key: string;
|
|
9
|
+
places: {
|
|
10
|
+
[P in Places]: string;
|
|
11
|
+
};
|
|
12
|
+
def: NoInfer<Places>;
|
|
13
|
+
}): {
|
|
14
|
+
Slot: (p: {
|
|
15
|
+
place: Places;
|
|
16
|
+
children: React.ReactNode;
|
|
17
|
+
}) => React.JSX.Element | null;
|
|
18
|
+
PlacementSetting: (p?: {
|
|
19
|
+
className?: string;
|
|
20
|
+
activeClassName?: string;
|
|
21
|
+
}) => import("react/jsx-runtime").JSX.Element;
|
|
22
|
+
getPlace: () => Places;
|
|
23
|
+
setPlace: (p: Places) => void;
|
|
24
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { renderBy, updateBy } from "../../../updateBy";
|
|
3
|
+
import { staticGetAdd } from "../../utils/mapMemory";
|
|
4
|
+
/** One UI block shown in exactly one of several mount points; the point is a persisted setting.
|
|
5
|
+
* Persistence rides the existing staticProps -> Cash mechanics: the app calls Cash.load()/save()
|
|
6
|
+
* the same way it does for window state (ExRNDMap3 etc.).
|
|
7
|
+
* Mount points render <Slot place="..."> themselves and decide nothing: the slot compares
|
|
8
|
+
* the persisted place with its own and renders children only on a match. */
|
|
9
|
+
export function createUiSlot(opts) {
|
|
10
|
+
const st = staticGetAdd(opts.key, { place: opts.def });
|
|
11
|
+
// a stored place may no longer exist after an app update - fall back to def
|
|
12
|
+
const isPlace = (p) => typeof p == "string" && p in opts.places;
|
|
13
|
+
const getPlace = () => isPlace(st.place) ? st.place : opts.def;
|
|
14
|
+
const setPlace = (p) => {
|
|
15
|
+
if (st.place == p)
|
|
16
|
+
return;
|
|
17
|
+
st.place = p;
|
|
18
|
+
renderBy(st);
|
|
19
|
+
};
|
|
20
|
+
function Slot(p) {
|
|
21
|
+
updateBy(st);
|
|
22
|
+
return getPlace() == p.place ? _jsx(_Fragment, { children: p.children }) : null;
|
|
23
|
+
}
|
|
24
|
+
/** Segmented row over opts.places; apps pass their own .chip / .chipActive classes */
|
|
25
|
+
function PlacementSetting(p = {}) {
|
|
26
|
+
updateBy(st);
|
|
27
|
+
const cur = getPlace();
|
|
28
|
+
const base = p.className ?? "wenaySegBtn";
|
|
29
|
+
const activeCls = p.activeClassName ?? "wenaySegBtnActive";
|
|
30
|
+
return _jsx("div", { style: { display: "inline-flex", gap: 4, flexWrap: "wrap" }, children: Object.keys(opts.places).map(key => (_jsx("div", { className: key == cur ? `${base} ${activeCls}` : base, onClick: () => setPlace(key), children: opts.places[key] }, key))) });
|
|
31
|
+
}
|
|
32
|
+
return { Slot, PlacementSetting, getPlace, setPlace };
|
|
33
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './UiSlot';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './UiSlot';
|
|
@@ -17,6 +17,45 @@ export declare const tokens: {
|
|
|
17
17
|
readonly itemColor: "#fff";
|
|
18
18
|
readonly itemHoverColor: "#101010";
|
|
19
19
|
readonly itemHoverBgColor: "#fff";
|
|
20
|
+
readonly shadow: "0 0 20px 14px rgba(34, 60, 80, 0.2)";
|
|
21
|
+
};
|
|
22
|
+
/** DivRnd3 window chrome (--wnd-*). Defaults = legacy look; apps re-skin via :root[data-theme].
|
|
23
|
+
* --wnd-header-height/color/font-size/letter-spacing/transform are intentionally undeclared
|
|
24
|
+
* (inherit/auto) until a theme sets them. */
|
|
25
|
+
readonly wnd: {
|
|
26
|
+
readonly bg: "transparent";
|
|
27
|
+
readonly border: "none";
|
|
28
|
+
readonly radius: "0";
|
|
29
|
+
readonly shadow: "none";
|
|
30
|
+
readonly headerBg: "transparent";
|
|
31
|
+
readonly headerStripes: "repeating-linear-gradient(139deg, hsla(0, 0%, 100%, 0.1), hsla(0, 0%, 100%, 0.1) 15px, transparent 0, transparent 30px)";
|
|
32
|
+
readonly headerBorder: "none";
|
|
33
|
+
readonly headerPadding: "0";
|
|
34
|
+
readonly closeSize: "28px";
|
|
35
|
+
readonly closeTop: "-12px";
|
|
36
|
+
readonly closeRight: "-12px";
|
|
37
|
+
/** var(--color-bg-light) in CSS */
|
|
38
|
+
readonly closeBg: "#17202e";
|
|
39
|
+
/** var(--color-border-common) in CSS */
|
|
40
|
+
readonly closeBorder: "1px solid rgb(50, 62, 71)";
|
|
41
|
+
readonly closeShadow: "0 2px 6px rgba(0, 0, 0, 0.35)";
|
|
42
|
+
/** var(--color-text-base) in CSS */
|
|
43
|
+
readonly closeColor: "#c4c4c4";
|
|
44
|
+
readonly closeHover: "#fff";
|
|
45
|
+
readonly closeHoverBg: "#e5484d";
|
|
46
|
+
};
|
|
47
|
+
/** SettingsDialog chrome (--dlg-*). Dark defaults; apps re-skin via :root[data-theme]. */
|
|
48
|
+
readonly dlg: {
|
|
49
|
+
readonly scrim: "rgba(0, 0, 0, 0.5)";
|
|
50
|
+
/** var(--color-bg-dark) in CSS */
|
|
51
|
+
readonly bg: "#131821";
|
|
52
|
+
/** var(--color-border-common) in CSS */
|
|
53
|
+
readonly border: "1px solid rgb(50, 62, 71)";
|
|
54
|
+
readonly radius: "10px";
|
|
55
|
+
readonly shadow: "0 12px 40px rgba(0, 0, 0, 0.5)";
|
|
56
|
+
/** var(--color-bg-light) in CSS */
|
|
57
|
+
readonly navBg: "#17202e";
|
|
58
|
+
readonly navWidth: "168px";
|
|
20
59
|
};
|
|
21
60
|
readonly font: {
|
|
22
61
|
readonly family: "Roboto";
|
|
@@ -19,6 +19,45 @@ export const tokens = {
|
|
|
19
19
|
itemColor: '#fff',
|
|
20
20
|
itemHoverColor: '#101010',
|
|
21
21
|
itemHoverBgColor: '#fff',
|
|
22
|
+
shadow: '0 0 20px 14px rgba(34, 60, 80, 0.2)',
|
|
23
|
+
},
|
|
24
|
+
/** DivRnd3 window chrome (--wnd-*). Defaults = legacy look; apps re-skin via :root[data-theme].
|
|
25
|
+
* --wnd-header-height/color/font-size/letter-spacing/transform are intentionally undeclared
|
|
26
|
+
* (inherit/auto) until a theme sets them. */
|
|
27
|
+
wnd: {
|
|
28
|
+
bg: 'transparent',
|
|
29
|
+
border: 'none',
|
|
30
|
+
radius: '0',
|
|
31
|
+
shadow: 'none',
|
|
32
|
+
headerBg: 'transparent',
|
|
33
|
+
headerStripes: 'repeating-linear-gradient(139deg, hsla(0, 0%, 100%, 0.1), hsla(0, 0%, 100%, 0.1) 15px, transparent 0, transparent 30px)',
|
|
34
|
+
headerBorder: 'none',
|
|
35
|
+
headerPadding: '0',
|
|
36
|
+
closeSize: '28px',
|
|
37
|
+
closeTop: '-12px',
|
|
38
|
+
closeRight: '-12px',
|
|
39
|
+
/** var(--color-bg-light) in CSS */
|
|
40
|
+
closeBg: '#17202e',
|
|
41
|
+
/** var(--color-border-common) in CSS */
|
|
42
|
+
closeBorder: '1px solid rgb(50, 62, 71)',
|
|
43
|
+
closeShadow: '0 2px 6px rgba(0, 0, 0, 0.35)',
|
|
44
|
+
/** var(--color-text-base) in CSS */
|
|
45
|
+
closeColor: '#c4c4c4',
|
|
46
|
+
closeHover: '#fff',
|
|
47
|
+
closeHoverBg: '#e5484d',
|
|
48
|
+
},
|
|
49
|
+
/** SettingsDialog chrome (--dlg-*). Dark defaults; apps re-skin via :root[data-theme]. */
|
|
50
|
+
dlg: {
|
|
51
|
+
scrim: 'rgba(0, 0, 0, 0.5)',
|
|
52
|
+
/** var(--color-bg-dark) in CSS */
|
|
53
|
+
bg: '#131821',
|
|
54
|
+
/** var(--color-border-common) in CSS */
|
|
55
|
+
border: '1px solid rgb(50, 62, 71)',
|
|
56
|
+
radius: '10px',
|
|
57
|
+
shadow: '0 12px 40px rgba(0, 0, 0, 0.5)',
|
|
58
|
+
/** var(--color-bg-light) in CSS */
|
|
59
|
+
navBg: '#17202e',
|
|
60
|
+
navWidth: '168px',
|
|
22
61
|
},
|
|
23
62
|
font: {
|
|
24
63
|
family: 'Roboto',
|
|
@@ -20,12 +20,16 @@ export declare const ObjectStringToDate: (obj: unknown) => void;
|
|
|
20
20
|
export declare function CacheFuncMapBase(arr: [k: string, v: Map<string, unknown>][], Save: IServerSaveBasePromise): {
|
|
21
21
|
load(): Promise<void>;
|
|
22
22
|
save(): Promise<void>;
|
|
23
|
+
saveDebounced(delay?: number): void;
|
|
24
|
+
flush(): Promise<void>;
|
|
23
25
|
clear(): Promise<void>;
|
|
24
26
|
getArr: [k: string, v: Map<string, unknown>][];
|
|
25
27
|
};
|
|
26
28
|
export declare function CacheFuncMap(arr: [k: string, v: Map<string, unknown>][]): {
|
|
27
29
|
load(): Promise<void>;
|
|
28
30
|
save(): Promise<void>;
|
|
31
|
+
saveDebounced(delay?: number): void;
|
|
32
|
+
flush(): Promise<void>;
|
|
29
33
|
clear(): Promise<void>;
|
|
30
34
|
getArr: [k: string, v: Map<string, unknown>][];
|
|
31
35
|
};
|
|
@@ -99,6 +99,43 @@ function isDate(_date) {
|
|
|
99
99
|
return ISO_DATE_RE.test(_date);
|
|
100
100
|
}
|
|
101
101
|
export function CacheFuncMapBase(arr, Save) {
|
|
102
|
+
const savedPayloadByKey = new Map();
|
|
103
|
+
let saveTimer = null;
|
|
104
|
+
let runningSave = null;
|
|
105
|
+
const getPayloads = () => arr.map(([key, map]) => [key, JSON.stringify([...map.entries()])]);
|
|
106
|
+
const rememberCurrentPayloads = () => {
|
|
107
|
+
savedPayloadByKey.clear();
|
|
108
|
+
for (const [key, payload] of getPayloads())
|
|
109
|
+
savedPayloadByKey.set(key, payload);
|
|
110
|
+
};
|
|
111
|
+
const cancelDebouncedSave = () => {
|
|
112
|
+
if (saveTimer === null)
|
|
113
|
+
return;
|
|
114
|
+
clearTimeout(saveTimer);
|
|
115
|
+
saveTimer = null;
|
|
116
|
+
};
|
|
117
|
+
const saveChangedPayloads = async () => {
|
|
118
|
+
for (const [key, payload] of getPayloads()) {
|
|
119
|
+
if (savedPayloadByKey.get(key) === payload)
|
|
120
|
+
continue;
|
|
121
|
+
if (await Save.set(key, JSON.parse(payload))) {
|
|
122
|
+
savedPayloadByKey.set(key, payload);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
const trackSave = (savePromise) => {
|
|
127
|
+
const trackedPromise = savePromise.catch(() => undefined).finally(() => {
|
|
128
|
+
if (runningSave === trackedPromise)
|
|
129
|
+
runningSave = null;
|
|
130
|
+
});
|
|
131
|
+
runningSave = trackedPromise;
|
|
132
|
+
return savePromise;
|
|
133
|
+
};
|
|
134
|
+
const queueSave = () => {
|
|
135
|
+
if (runningSave === null)
|
|
136
|
+
return trackSave(saveChangedPayloads());
|
|
137
|
+
return trackSave(runningSave.then(saveChangedPayloads, saveChangedPayloads));
|
|
138
|
+
};
|
|
102
139
|
return {
|
|
103
140
|
async load() {
|
|
104
141
|
for (let [k, v] of arr) {
|
|
@@ -108,13 +145,25 @@ export function CacheFuncMapBase(arr, Save) {
|
|
|
108
145
|
ObjectStringToDate(t);
|
|
109
146
|
addDataToMap(t, v);
|
|
110
147
|
}
|
|
148
|
+
rememberCurrentPayloads();
|
|
111
149
|
},
|
|
112
150
|
async save() {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
151
|
+
await queueSave();
|
|
152
|
+
},
|
|
153
|
+
saveDebounced(delay = 800) {
|
|
154
|
+
cancelDebouncedSave();
|
|
155
|
+
saveTimer = setTimeout(() => {
|
|
156
|
+
saveTimer = null;
|
|
157
|
+
void queueSave();
|
|
158
|
+
}, delay);
|
|
159
|
+
},
|
|
160
|
+
async flush() {
|
|
161
|
+
cancelDebouncedSave();
|
|
162
|
+
await queueSave();
|
|
116
163
|
},
|
|
117
164
|
async clear() {
|
|
165
|
+
cancelDebouncedSave();
|
|
166
|
+
savedPayloadByKey.clear();
|
|
118
167
|
for (let [k, v] of arr) {
|
|
119
168
|
await Save.delete(k);
|
|
120
169
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Multiplexer for a single-slot callback API (onX(cb | null)): many subscribers share
|
|
2
|
+
* one slot instead of silently overwriting each other.
|
|
3
|
+
* bind(emit) is called lazily, ONCE, on the first on() - not at creation time, because
|
|
4
|
+
* the slot may still be taken by app initialization. */
|
|
5
|
+
export declare function createCallbackHub<Args extends any[]>(bind: (emit: (...a: Args) => void) => void): {
|
|
6
|
+
/** Subscribe; the returned function unsubscribes without touching other subscribers.
|
|
7
|
+
* The callback is registered BEFORE bind runs, so it also catches synchronous emits. */
|
|
8
|
+
on(cb: (...a: Args) => void): () => void;
|
|
9
|
+
count: () => number;
|
|
10
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Multiplexer for a single-slot callback API (onX(cb | null)): many subscribers share
|
|
2
|
+
* one slot instead of silently overwriting each other.
|
|
3
|
+
* bind(emit) is called lazily, ONCE, on the first on() - not at creation time, because
|
|
4
|
+
* the slot may still be taken by app initialization. */
|
|
5
|
+
export function createCallbackHub(bind) {
|
|
6
|
+
const subs = new Set();
|
|
7
|
+
let bound = false;
|
|
8
|
+
// copy before iterating: an unsubscribe from inside a callback must not skip the rest
|
|
9
|
+
const emit = (...a) => { [...subs].forEach(cb => cb(...a)); };
|
|
10
|
+
return {
|
|
11
|
+
/** Subscribe; the returned function unsubscribes without touching other subscribers.
|
|
12
|
+
* The callback is registered BEFORE bind runs, so it also catches synchronous emits. */
|
|
13
|
+
on(cb) {
|
|
14
|
+
subs.add(cb);
|
|
15
|
+
if (!bound) {
|
|
16
|
+
bound = true;
|
|
17
|
+
bind(emit);
|
|
18
|
+
}
|
|
19
|
+
return () => { subs.delete(cb); };
|
|
20
|
+
},
|
|
21
|
+
count: () => subs.size,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -10,6 +10,8 @@ export declare function staticGetById<T extends object>(key: any, def: T, id: st
|
|
|
10
10
|
export declare const Cash: {
|
|
11
11
|
load(): Promise<void>;
|
|
12
12
|
save(): Promise<void>;
|
|
13
|
+
saveDebounced(delay?: number): void;
|
|
14
|
+
flush(): Promise<void>;
|
|
13
15
|
clear(): Promise<void>;
|
|
14
16
|
getArr: [k: string, v: Map<string, unknown>][];
|
|
15
17
|
};
|
|
@@ -28,5 +30,6 @@ export declare const MemoryMap: {
|
|
|
28
30
|
height?: number | string;
|
|
29
31
|
width?: number | string;
|
|
30
32
|
}>;
|
|
33
|
+
rightMenu: Map<string, import("../components/Menu/RightMenuStore").MenuRightSavedState>;
|
|
31
34
|
other: Map<string, object>;
|
|
32
35
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { deepClone } from "wenay-common2";
|
|
2
2
|
import { ExRNDMap3 } from "../components/Dnd/RNDFunc3";
|
|
3
3
|
import { mapResiReact } from "../components/Dnd/Resizable";
|
|
4
|
+
import { mapRightMenu } from "../components/Menu/RightMenuStore";
|
|
4
5
|
import { CacheFuncMap } from "./cache";
|
|
5
6
|
const staticProps = new Map();
|
|
6
7
|
export function staticSet(key, data) {
|
|
@@ -63,10 +64,12 @@ export function staticGetById(key, def, id) {
|
|
|
63
64
|
export const Cash = CacheFuncMap([
|
|
64
65
|
["mapResiReact", mapResiReact],
|
|
65
66
|
["ExRNDMap3", ExRNDMap3],
|
|
67
|
+
["mapRightMenu", mapRightMenu],
|
|
66
68
|
["staticProps", staticProps]
|
|
67
69
|
]);
|
|
68
70
|
export const MemoryMap = {
|
|
69
71
|
rnd: ExRNDMap3,
|
|
70
72
|
resize: mapResiReact,
|
|
73
|
+
rightMenu: mapRightMenu,
|
|
71
74
|
other: staticProps
|
|
72
75
|
};
|
package/lib/style/style.css
CHANGED
|
@@ -136,11 +136,11 @@ input:invalid {
|
|
|
136
136
|
|
|
137
137
|
/* Menu */
|
|
138
138
|
.MenuR {
|
|
139
|
-
box-shadow:
|
|
139
|
+
box-shadow: var(--menu-shadow);
|
|
140
140
|
margin-top: 4px;
|
|
141
141
|
min-width: 80px;
|
|
142
142
|
width: max-content;
|
|
143
|
-
background:
|
|
143
|
+
background: var(--menu-bg-color);
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
.maxSize {
|
|
@@ -319,28 +319,138 @@ body {
|
|
|
319
319
|
margin-left: -1px;
|
|
320
320
|
}
|
|
321
321
|
|
|
322
|
+
/* DivRnd3 window chrome. All visuals go through --wnd-* tokens (tokens.css);
|
|
323
|
+
defaults reproduce the legacy look, apps re-skin via :root[data-theme] token overrides. */
|
|
324
|
+
.wenayWnd {
|
|
325
|
+
background: var(--wnd-bg);
|
|
326
|
+
border: var(--wnd-border);
|
|
327
|
+
border-radius: var(--wnd-radius);
|
|
328
|
+
box-shadow: var(--wnd-shadow);
|
|
329
|
+
}
|
|
330
|
+
/* Header drag zone. Color/font tokens are undeclared by default: an unresolved var()
|
|
331
|
+
makes the property fall back to its inherited value, i.e. exactly the legacy look. */
|
|
332
|
+
.wenayWndHeader {
|
|
333
|
+
user-select: none;
|
|
334
|
+
cursor: grabbing;
|
|
335
|
+
height: var(--wnd-header-height, auto);
|
|
336
|
+
padding: var(--wnd-header-padding);
|
|
337
|
+
background: var(--wnd-header-bg);
|
|
338
|
+
border-bottom: var(--wnd-header-border);
|
|
339
|
+
border-top-left-radius: var(--wnd-radius);
|
|
340
|
+
border-top-right-radius: var(--wnd-radius);
|
|
341
|
+
color: var(--wnd-header-color, inherit);
|
|
342
|
+
font-size: var(--wnd-header-font-size, inherit);
|
|
343
|
+
letter-spacing: var(--wnd-header-letter-spacing, inherit);
|
|
344
|
+
text-transform: var(--wnd-header-transform, inherit);
|
|
345
|
+
}
|
|
346
|
+
/* Default header content when no header element is passed (striped drag strip) */
|
|
347
|
+
.wenayWndHeaderDef {
|
|
348
|
+
height: var(--wnd-header-height, 20px);
|
|
349
|
+
width: 100%;
|
|
350
|
+
background: var(--wnd-header-stripes);
|
|
351
|
+
}
|
|
352
|
+
|
|
322
353
|
/* Window close button (DivRnd3 and others) */
|
|
323
354
|
.wenayCloseBtn {
|
|
324
|
-
width: 28px;
|
|
325
|
-
height: 28px;
|
|
355
|
+
width: var(--wnd-close-size, 28px);
|
|
356
|
+
height: var(--wnd-close-size, 28px);
|
|
357
|
+
/* keep the clickable zone >= 24x24 whatever the theme sets */
|
|
358
|
+
min-width: 24px;
|
|
359
|
+
min-height: 24px;
|
|
326
360
|
display: flex;
|
|
327
361
|
align-items: center;
|
|
328
362
|
justify-content: center;
|
|
329
363
|
border-radius: 50%;
|
|
330
|
-
background: var(--color-bg-light);
|
|
331
|
-
border: 1px solid var(--color-border-common);
|
|
332
|
-
color: var(--color-text-base);
|
|
364
|
+
background: var(--wnd-close-bg, var(--color-bg-light));
|
|
365
|
+
border: var(--wnd-close-border, 1px solid var(--color-border-common));
|
|
366
|
+
color: var(--wnd-close-color, var(--color-text-base));
|
|
333
367
|
cursor: pointer;
|
|
334
368
|
user-select: none;
|
|
335
|
-
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.35);
|
|
369
|
+
box-shadow: var(--wnd-close-shadow, 0 2px 6px rgba(0, 0, 0, 0.35));
|
|
336
370
|
transition: background 0.15s, color 0.15s, transform 0.15s;
|
|
337
371
|
}
|
|
338
372
|
.wenayCloseBtn:hover {
|
|
339
|
-
background: #e5484d;
|
|
340
|
-
border-color: #e5484d;
|
|
341
|
-
color: #fff;
|
|
373
|
+
background: var(--wnd-close-hover-bg, #e5484d);
|
|
374
|
+
border-color: var(--wnd-close-hover-bg, #e5484d);
|
|
375
|
+
color: var(--wnd-close-hover, #fff);
|
|
342
376
|
transform: scale(1.08);
|
|
343
377
|
}
|
|
344
378
|
.wenayCloseBtn:active {
|
|
345
379
|
transform: scale(0.95);
|
|
346
380
|
}
|
|
381
|
+
/* Positioning of the close button inside DivRnd3. Kept separate from .wenayCloseBtn,
|
|
382
|
+
which may be used outside windows and must not become position:absolute there. */
|
|
383
|
+
.wenayWndClose {
|
|
384
|
+
position: absolute;
|
|
385
|
+
top: var(--wnd-close-top, -12px);
|
|
386
|
+
right: var(--wnd-close-right, -12px);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/* Settings dialog (SettingsDialog). Themed via --dlg-* (tokens.css), dark defaults. */
|
|
390
|
+
.wenayDlgScrim {
|
|
391
|
+
position: fixed;
|
|
392
|
+
inset: 0;
|
|
393
|
+
z-index: var(--wenay-z-modal, 9999);
|
|
394
|
+
background: var(--dlg-scrim, rgba(0, 0, 0, 0.5));
|
|
395
|
+
display: flex;
|
|
396
|
+
align-items: center;
|
|
397
|
+
justify-content: center;
|
|
398
|
+
}
|
|
399
|
+
.wenayDlg {
|
|
400
|
+
position: relative;
|
|
401
|
+
display: flex;
|
|
402
|
+
width: 640px;
|
|
403
|
+
height: 420px;
|
|
404
|
+
max-width: 92vw;
|
|
405
|
+
max-height: 82vh;
|
|
406
|
+
background: var(--dlg-bg, var(--color-bg-dark));
|
|
407
|
+
border: var(--dlg-border, 1px solid var(--color-border-common));
|
|
408
|
+
border-radius: var(--dlg-radius, 10px);
|
|
409
|
+
box-shadow: var(--dlg-shadow, 0 12px 40px rgba(0, 0, 0, 0.5));
|
|
410
|
+
color: var(--color-text-base);
|
|
411
|
+
overflow: hidden;
|
|
412
|
+
}
|
|
413
|
+
.wenayDlgNav {
|
|
414
|
+
width: var(--dlg-nav-width, 168px);
|
|
415
|
+
flex: none;
|
|
416
|
+
background: var(--dlg-nav-bg, var(--color-bg-light));
|
|
417
|
+
padding: 10px 8px;
|
|
418
|
+
display: flex;
|
|
419
|
+
flex-direction: column;
|
|
420
|
+
gap: 4px;
|
|
421
|
+
overflow-y: auto;
|
|
422
|
+
}
|
|
423
|
+
.wenayDlgContent {
|
|
424
|
+
position: relative;
|
|
425
|
+
flex: 1;
|
|
426
|
+
min-width: 0;
|
|
427
|
+
padding: 14px 40px 14px 16px;
|
|
428
|
+
overflow: auto;
|
|
429
|
+
}
|
|
430
|
+
/* Positioning of the close button in the dialog's top-right corner (over the content column).
|
|
431
|
+
A direct child of .wenayDlg, NOT of the scrollable content, so it does not scroll away. */
|
|
432
|
+
.wenayDlgClose {
|
|
433
|
+
position: absolute;
|
|
434
|
+
top: 8px;
|
|
435
|
+
right: 8px;
|
|
436
|
+
}
|
|
437
|
+
/* Minimal default section/segment buttons; apps override via sectionClassName /
|
|
438
|
+
sectionActiveClassName (SettingsDialog) and className / activeClassName (PlacementSetting) */
|
|
439
|
+
.wenayDlgSection,
|
|
440
|
+
.wenaySegBtn {
|
|
441
|
+
padding: 6px 10px;
|
|
442
|
+
border-radius: 6px;
|
|
443
|
+
cursor: pointer;
|
|
444
|
+
user-select: none;
|
|
445
|
+
text-align: left;
|
|
446
|
+
color: inherit;
|
|
447
|
+
}
|
|
448
|
+
.wenayDlgSection:hover,
|
|
449
|
+
.wenaySegBtn:hover {
|
|
450
|
+
background: rgba(255, 255, 255, 0.08);
|
|
451
|
+
}
|
|
452
|
+
.wenayDlgSectionActive,
|
|
453
|
+
.wenaySegBtnActive {
|
|
454
|
+
background: rgba(255, 255, 255, 0.16);
|
|
455
|
+
color: #fff;
|
|
456
|
+
}
|
package/lib/style/tokens.css
CHANGED
|
@@ -29,6 +29,49 @@
|
|
|
29
29
|
--menu-item-hover-color: #101010;
|
|
30
30
|
--menu-item-hover-bg-color: var(--base-color-hover);
|
|
31
31
|
|
|
32
|
+
/* Menu shadow (was a literal in .MenuR) */
|
|
33
|
+
--menu-shadow: 0 0 20px 14px rgba(34, 60, 80, 0.2);
|
|
34
|
+
|
|
35
|
+
/* Window (DivRnd3) chrome. Defaults = legacy look, so apps without themes do not change.
|
|
36
|
+
Apps re-skin via :root[data-theme='...'] overrides, same contract as --color-* / --menu-*.
|
|
37
|
+
Intentionally NOT declared here (fall back to inherit/auto until a theme declares them):
|
|
38
|
+
--wnd-header-height, --wnd-header-color, --wnd-header-font-size,
|
|
39
|
+
--wnd-header-letter-spacing, --wnd-header-transform */
|
|
40
|
+
--wnd-bg: transparent;
|
|
41
|
+
--wnd-border: none;
|
|
42
|
+
--wnd-radius: 0;
|
|
43
|
+
--wnd-shadow: none;
|
|
44
|
+
|
|
45
|
+
/* Header drag zone: wrapper background (solid in themes) vs default striped strip.
|
|
46
|
+
Themes typically set --wnd-header-bg to a panel color and --wnd-header-stripes to none. */
|
|
47
|
+
--wnd-header-bg: transparent;
|
|
48
|
+
--wnd-header-stripes: repeating-linear-gradient(139deg, hsla(0, 0%, 100%, 0.1), hsla(0, 0%, 100%, 0.1) 15px, transparent 0, transparent 30px);
|
|
49
|
+
--wnd-header-border: none;
|
|
50
|
+
--wnd-header-padding: 0;
|
|
51
|
+
|
|
52
|
+
/* Close button. Legacy: floating circle at the window's top-right corner.
|
|
53
|
+
Themes move it into the header (top/right offsets) and strip the circle chrome.
|
|
54
|
+
Clickable zone is clamped to >= 24x24 in CSS regardless of --wnd-close-size. */
|
|
55
|
+
--wnd-close-size: 28px;
|
|
56
|
+
--wnd-close-top: -12px;
|
|
57
|
+
--wnd-close-right: -12px;
|
|
58
|
+
--wnd-close-bg: var(--color-bg-light);
|
|
59
|
+
--wnd-close-border: 1px solid var(--color-border-common);
|
|
60
|
+
--wnd-close-shadow: 0 2px 6px rgba(0, 0, 0, 0.35);
|
|
61
|
+
--wnd-close-color: var(--color-text-base);
|
|
62
|
+
--wnd-close-hover: #fff;
|
|
63
|
+
--wnd-close-hover-bg: #e5484d;
|
|
64
|
+
|
|
65
|
+
/* Settings dialog (SettingsDialog). Dark defaults close to current apps;
|
|
66
|
+
apps re-skin via :root[data-theme='...'] overrides, same contract as --wnd-*. */
|
|
67
|
+
--dlg-scrim: rgba(0, 0, 0, 0.5);
|
|
68
|
+
--dlg-bg: var(--color-bg-dark);
|
|
69
|
+
--dlg-border: 1px solid var(--color-border-common);
|
|
70
|
+
--dlg-radius: 10px;
|
|
71
|
+
--dlg-shadow: 0 12px 40px rgba(0, 0, 0, 0.5);
|
|
72
|
+
--dlg-nav-bg: var(--color-bg-light);
|
|
73
|
+
--dlg-nav-width: 168px;
|
|
74
|
+
|
|
32
75
|
/* Layers */
|
|
33
76
|
--wenay-z-modal: 9999;
|
|
34
77
|
}
|