wenay-react2 1.0.27 → 1.0.28

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.
@@ -25,7 +25,8 @@ 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/menu/menu";
28
+ export { MenuBase, TimeNum, MenuElement as LegacyMenuElement } from "./src/menu/menu";
29
+ export type { tMenuReact, tMenuReactStrictly } from "./src/menu/menu";
29
30
  export * from "./src/menu/menuMouse";
30
31
  export * from "./src/menu/menuR";
31
32
  export * from "./src/logs/logs";
package/lib/common/api.js CHANGED
@@ -38,7 +38,7 @@ export * from "./src/components/Modal";
38
38
  // 11. Menu - depends on hooks, Dnd, and Modal
39
39
  export * from "./src/components/Menu";
40
40
  // 12. MENU - depends on components
41
- export * from "./src/menu/menu";
41
+ export { MenuBase, TimeNum, MenuElement as LegacyMenuElement } from "./src/menu/menu";
42
42
  export * from "./src/menu/menuMouse";
43
43
  export * from "./src/menu/menuR";
44
44
  // 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
- if (onCLickClose && !limit) {
25
- limit = { y: { min: 0 } };
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
- }, style: {
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 MenuElement = {
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?: 'left' | 'right';
10
- position2?: 'top' | 'bottom';
10
+ position?: MenuRightPosition;
11
+ position2?: MenuRightPosition2;
12
+ keyForSave?: string;
11
13
  };
12
- export declare function DropdownMenu({ elements, style, position: initialPosition, position2: initialPos2 }: DropdownMenuProps): import("react/jsx-runtime").JSX.Element;
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({ style }: {
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
- export function DropdownMenu({ elements, style, position: initialPosition = 'right', position2: initialPos2 = 'top' }) {
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(initialPosition);
17
- const [isTop, setIsTop] = useState(initialPos2 === 'top');
18
- const positionLast = useRef({ x: 0, y: 0 });
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
- // Handle horizontal offset
21
- if (position === 'left') {
22
- positionLast.current.x += finalPosition.x;
23
- if (positionLast.current.x > window.innerWidth * 0.6) {
24
- positionLast.current.x = window.innerWidth - positionLast.current.x;
25
- setPosition('right');
26
- }
27
- }
28
- else if (position === 'right') {
29
- positionLast.current.x -= finalPosition.x;
30
- if (positionLast.current.x > window.innerWidth * 0.6) {
31
- positionLast.current.x = window.innerWidth - positionLast.current.x;
32
- setPosition('left');
33
- }
34
- }
35
- // Handle vertical offset
36
- if (isTop) {
37
- positionLast.current.y += finalPosition.y;
38
- if (positionLast.current.y > document.documentElement.clientHeight * 0.6) {
39
- positionLast.current.y = document.documentElement.clientHeight - positionLast.current.y;
40
- setIsTop(false);
41
- }
42
- }
43
- else {
44
- positionLast.current.y -= finalPosition.y;
45
- if (positionLast.current.y > document.documentElement.clientHeight * 0.6) {
46
- positionLast.current.y = document.documentElement.clientHeight - positionLast.current.y;
47
- setIsTop(true);
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
- }, [position, isTop]);
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 ? (_jsx("div", { className: "submenu", style: { width: '40vw', minHeight: '70vh' }, children: jsxRender })) : null })] }));
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({ style }) {
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, style: style, position: "left" });
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();
@@ -1,2 +1,3 @@
1
1
  export { default as StickerMenu } from './StickerMenu';
2
2
  export * from './RightMenu';
3
+ export * from './RightMenuStore';
@@ -1,2 +1,3 @@
1
1
  export { default as StickerMenu } from './StickerMenu';
2
2
  export * from './RightMenu';
3
+ export * from './RightMenuStore';
@@ -17,6 +17,32 @@ 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";
20
46
  };
21
47
  readonly font: {
22
48
  readonly family: "Roboto";
@@ -19,6 +19,32 @@ 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',
22
48
  },
23
49
  font: {
24
50
  family: 'Roboto',
@@ -28,5 +28,6 @@ export declare const MemoryMap: {
28
28
  height?: number | string;
29
29
  width?: number | string;
30
30
  }>;
31
+ rightMenu: Map<string, import("../components/Menu/RightMenuStore").MenuRightSavedState>;
31
32
  other: Map<string, object>;
32
33
  };
@@ -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
  };
@@ -136,11 +136,11 @@ input:invalid {
136
136
 
137
137
  /* Menu */
138
138
  .MenuR {
139
- box-shadow: 0 0 20px 14px rgba(34, 60, 80, 0.2);
139
+ box-shadow: var(--menu-shadow);
140
140
  margin-top: 4px;
141
141
  min-width: 80px;
142
142
  width: max-content;
143
- background: rgba(12, 12, 12, 0.91);
143
+ background: var(--menu-bg-color);
144
144
  }
145
145
 
146
146
  .maxSize {
@@ -319,28 +319,69 @@ 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
+ }
@@ -29,6 +29,39 @@
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
+
32
65
  /* Layers */
33
66
  --wenay-z-modal: 9999;
34
67
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wenay-react2",
3
- "version": "1.0.27",
3
+ "version": "1.0.28",
4
4
  "description": "Common react",
5
5
  "strict": true,
6
6
  "main": "dist/index.js",