wenay-react2 1.0.20 → 1.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +383 -382
- package/lib/common/api.d.ts +28 -0
- package/lib/common/api.js +41 -16
- package/lib/common/src/components/Buttons/MiniButton.d.ts +1 -0
- package/lib/common/src/components/Buttons/MiniButton.js +2 -2
- package/lib/common/src/components/Dnd/DraggableOutlineDiv.d.ts +1 -0
- package/lib/common/src/components/Dnd/DraggableOutlineDiv.js +7 -6
- package/lib/common/src/components/Dnd/RNDFunc.js +28 -17
- package/lib/common/src/components/Dnd/RNDFunc3.d.ts +27 -17
- package/lib/common/src/components/Dnd/RNDFunc3.js +107 -105
- package/lib/common/src/components/Dnd/Resizable.js +3 -3
- package/lib/common/src/components/Menu/RightMenu.js +11 -17
- package/lib/common/src/components/Menu/StickerMenu.js +42 -28
- package/lib/common/src/components/Menu/index.d.ts +1 -1
- package/lib/common/src/components/Menu/index.js +1 -1
- package/lib/common/src/components/Modal/LeftModal.d.ts +14 -12
- package/lib/common/src/components/Modal/LeftModal.js +30 -16
- package/lib/common/src/components/Modal/Modal.d.ts +26 -12
- package/lib/common/src/components/Modal/Modal.js +66 -89
- package/lib/common/src/components/Modal/ModalContextProvider.d.ts +20 -3
- package/lib/common/src/components/Modal/ModalContextProvider.js +35 -8
- package/lib/common/src/components/MyResizeObserver.js +15 -18
- package/lib/common/src/components/Other.js +3 -2
- package/lib/common/src/components/Parameters.js +7 -6
- package/lib/common/src/components/ParametersEngine.js +25 -54
- package/lib/common/src/grid/agGrid4/agGrid4.d.ts +53 -0
- package/lib/common/src/grid/agGrid4/agGrid4.js +132 -0
- package/lib/common/src/grid/agGrid4/core.d.ts +56 -0
- package/lib/common/src/grid/agGrid4/core.js +127 -0
- package/lib/common/src/grid/agGrid4/index.d.ts +6 -0
- package/lib/common/src/grid/agGrid4/index.js +3 -0
- package/lib/common/src/grid/agGrid4/theme.d.ts +7 -0
- package/lib/common/src/grid/agGrid4/theme.js +18 -0
- package/lib/common/src/hooks/useAddDownAnyKey.d.ts +21 -2
- package/lib/common/src/hooks/useAddDownAnyKey.js +46 -8
- package/lib/common/src/hooks/useDraggable.d.ts +20 -2
- package/lib/common/src/hooks/useDraggable.js +146 -115
- package/lib/common/src/hooks/useOutside.d.ts +24 -4
- package/lib/common/src/hooks/useOutside.js +76 -27
- package/lib/common/src/logs/logs.d.ts +5 -2
- package/lib/common/src/logs/logs.js +15 -31
- package/lib/common/src/logs/logs3.d.ts +9 -9
- package/lib/common/src/logs/logs3.js +47 -38
- package/lib/common/src/logs/miniLogs.d.ts +3 -3
- package/lib/common/src/logs/miniLogs.js +28 -36
- package/lib/common/src/menu/menu.d.ts +18 -18
- package/lib/common/src/menu/menu.js +48 -24
- package/lib/common/src/menu/menuMouse.js +4 -1
- package/lib/common/src/menu/menuR.d.ts +2 -2
- package/lib/common/src/menu/menuR.js +34 -34
- package/lib/common/src/myChart/1/myChart.d.ts +5 -5
- package/lib/common/src/myChart/1/myChart.js +58 -47
- package/lib/common/src/myChart/1/myChartTest.js +8 -4
- package/lib/common/src/myChart/chartEngine/chartEngineReact.d.ts +24 -27
- package/lib/common/src/myChart/chartEngine/chartEngineReact.js +166 -117
- package/lib/common/src/styles/styleGrid.d.ts +2 -1
- package/lib/common/src/styles/styleGrid.js +10 -12
- package/lib/common/src/styles/tokens.d.ts +39 -0
- package/lib/common/src/styles/tokens.js +40 -0
- package/lib/common/src/utils/applyTransactionAsyncUpdate.d.ts +7 -0
- package/lib/common/src/utils/applyTransactionAsyncUpdate.js +19 -12
- package/lib/common/src/utils/arrayPromise.d.ts +6 -0
- package/lib/common/src/utils/arrayPromise.js +7 -1
- package/lib/common/src/utils/cache.js +24 -21
- package/lib/common/src/utils/inputAutoStep.d.ts +1 -1
- package/lib/common/src/utils/inputAutoStep.js +30 -19
- package/lib/common/src/utils/mapMemory.js +9 -10
- package/lib/common/updateBy.d.ts +21 -2
- package/lib/common/updateBy.js +66 -31
- package/lib/index.js +0 -2
- package/lib/style/menuRight.css +13 -26
- package/lib/style/style.css +51 -39
- package/lib/style/tokens.css +34 -0
- package/package.json +2 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Class for tracking element size changes
|
|
2
2
|
export class CResizeObserver {
|
|
3
3
|
#idMap = new WeakMap();
|
|
4
4
|
#funcMap = new WeakMap();
|
|
@@ -19,9 +19,7 @@ export class CResizeObserver {
|
|
|
19
19
|
this.#observer?.observe(element);
|
|
20
20
|
}
|
|
21
21
|
functions.push(onResize);
|
|
22
|
-
|
|
23
|
-
[Symbol.species] = this; //{} as ObserveID
|
|
24
|
-
}();
|
|
22
|
+
const id = {}; // unique WeakMap key; the branded type only exists at compile time
|
|
25
23
|
this.#idMap.set(id, { element, func: onResize });
|
|
26
24
|
return id;
|
|
27
25
|
}
|
|
@@ -43,13 +41,13 @@ export class CResizeObserver {
|
|
|
43
41
|
}
|
|
44
42
|
const global_resizeObserver = new CResizeObserver();
|
|
45
43
|
const resizeableElementMap = new WeakMap();
|
|
46
|
-
//
|
|
44
|
+
// Set automatic element resizing based on the parent element size
|
|
47
45
|
//
|
|
48
46
|
export function setResizeableElement(el) {
|
|
49
47
|
const parent = el.parentElement;
|
|
50
48
|
if (!parent)
|
|
51
49
|
return;
|
|
52
|
-
const parentParent = parent.parentElement; //
|
|
50
|
+
const parentParent = parent.parentElement; // one level higher
|
|
53
51
|
if (!parentParent)
|
|
54
52
|
return;
|
|
55
53
|
const lastEl = parent.lastElementChild;
|
|
@@ -60,26 +58,25 @@ export function setResizeableElement(el) {
|
|
|
60
58
|
if (existing)
|
|
61
59
|
global_resizeObserver.delete(existing);
|
|
62
60
|
const observerId = global_resizeObserver.add(parentParent, () => {
|
|
61
|
+
// Jump directly by the mismatch amount (previously 1px with reflow on each step);
|
|
62
|
+
// a few iterations are only for final adjustment, the upper bound prevents an infinite loop
|
|
63
63
|
let lastRangeDelta = 0;
|
|
64
|
-
let i = 0;
|
|
65
|
-
|
|
66
|
-
for (let width = el.clientWidth;;) { //} width>=20; width--) {
|
|
67
|
-
let rangeDelta = Math.floor(lastEl.getBoundingClientRect().right - parentParent.getBoundingClientRect().right - 0);
|
|
64
|
+
for (let width = el.clientWidth, i = 0; i < 8; i++) {
|
|
65
|
+
const rangeDelta = Math.floor(lastEl.getBoundingClientRect().right - parentParent.getBoundingClientRect().right);
|
|
68
66
|
if (rangeDelta == 0)
|
|
69
67
|
break;
|
|
70
68
|
if (lastRangeDelta && rangeDelta * lastRangeDelta < 0)
|
|
71
69
|
break;
|
|
72
70
|
lastRangeDelta = rangeDelta;
|
|
73
|
-
if (rangeDelta > 0)
|
|
74
|
-
width--;
|
|
75
|
-
else
|
|
76
|
-
width++;
|
|
77
71
|
defaultWidth ??= el.clientWidth;
|
|
78
|
-
|
|
79
|
-
|
|
72
|
+
width -= rangeDelta;
|
|
73
|
+
if (width < 10)
|
|
74
|
+
width = 10;
|
|
75
|
+
if (width > defaultWidth)
|
|
76
|
+
width = defaultWidth;
|
|
80
77
|
el.style.width = width + "px";
|
|
81
|
-
|
|
82
|
-
|
|
78
|
+
if (width == 10 || width == defaultWidth)
|
|
79
|
+
break;
|
|
83
80
|
}
|
|
84
81
|
});
|
|
85
82
|
resizeableElementMap.set(el, observerId);
|
|
@@ -12,7 +12,7 @@ export function EditParams2({ onSave, params: paramsDef }) {
|
|
|
12
12
|
return _jsxs("div", { className: "maxSize", children: [paramsD && _jsx(ParametersReact, { params: paramsD, onChange: e => params.current = e }), onSave && _jsx("div", { className: "msTradeActive msTradeAlt", onClick: async () => {
|
|
13
13
|
const t = params.current || paramsD;
|
|
14
14
|
if (t)
|
|
15
|
-
onSave
|
|
15
|
+
onSave(t);
|
|
16
16
|
}, children: "save" })] });
|
|
17
17
|
}
|
|
18
18
|
export function EditParams3({ onSave, params: paramsDef }) {
|
|
@@ -26,6 +26,7 @@ export function EditParams3({ onSave, params: paramsDef }) {
|
|
|
26
26
|
params[i] = e;
|
|
27
27
|
setParams(params);
|
|
28
28
|
} }, i)), onSave && _jsx("div", { className: "msTradeActive msTradeAlt", onClick: async () => {
|
|
29
|
-
|
|
29
|
+
if (params)
|
|
30
|
+
onSave(params);
|
|
30
31
|
}, children: "save" })] });
|
|
31
32
|
}
|
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import React from "react";
|
|
3
|
-
|
|
3
|
+
// Button view
|
|
4
4
|
export function FButton(name) {
|
|
5
5
|
return _jsxs("div", { className: "", style: { width: "100%" }, children: [" ", name] });
|
|
6
6
|
}
|
|
7
|
-
|
|
7
|
+
// Arrow for the button
|
|
8
8
|
export function FNameButton(type, name) { return FButton(_jsx("p", { className: "toPTextIndicator", children: (type ? "▼ " : "▶ ") + name })); }
|
|
9
9
|
export function CParameter(props) {
|
|
10
|
-
const [hovered, setHovered] = React.useState(false); //
|
|
10
|
+
const [hovered, setHovered] = React.useState(false); // Tracks mouse hover state
|
|
11
11
|
return (_jsxs("div", { className: "toLine LeftMenuParameters toIndicatorMenuButton", style: { position: "relative" }, children: [_jsx("div", { className: "toLine", style: { width: "auto", ...props.style }, onMouseEnter: () => setHovered(true), onMouseLeave: () => setHovered(false), children: props.name }), _jsx("div", { className: "toLine toRight", style: props.enabled === false ? { opacity: 0.5 } : {}, children: props.children }), hovered && props.commentary && props.commentary.length > 0 && (_jsx("div", { className: "commentary", style: {
|
|
12
12
|
marginTop: "5px",
|
|
13
13
|
fontSize: "12px",
|
|
14
14
|
color: "gray",
|
|
15
|
-
position: "absolute", //
|
|
16
|
-
bottom: "-20px", //
|
|
15
|
+
position: "absolute", // Can be absolutely positioned
|
|
16
|
+
bottom: "-20px", // Shift down to avoid covering the main content
|
|
17
17
|
left: "0",
|
|
18
|
-
backgroundColor: "white", //
|
|
18
|
+
backgroundColor: "white", // Make it stand out from the background
|
|
19
19
|
padding: "2px 4px",
|
|
20
20
|
border: "1px solid lightgray",
|
|
21
21
|
borderRadius: "4px",
|
|
22
22
|
zIndex: 10,
|
|
23
|
+
whiteSpace: "pre-line", // otherwise "\n" collapses into spaces
|
|
23
24
|
}, children: props.commentary.join("\n") }))] }));
|
|
24
25
|
}
|
|
@@ -20,7 +20,7 @@ function CButton({ name, className, status: statusDef, header, onExpand, childre
|
|
|
20
20
|
onExpand?.(status2);
|
|
21
21
|
}, children: name(status) }), header] }), status && children] });
|
|
22
22
|
}
|
|
23
|
-
|
|
23
|
+
// Hover wrapper: when the wrapper around children is hovered, focus state is exposed.
|
|
24
24
|
function DivHover({ children, className, style }) {
|
|
25
25
|
const [hover, setHover] = useState(false);
|
|
26
26
|
return _jsx("div", { className: className, style: style, onMouseLeave: () => { setHover(false); }, onMouseEnter: () => { setHover(true); }, children: children(hover) });
|
|
@@ -36,14 +36,7 @@ function InputString(set, val, style) {
|
|
|
36
36
|
} });
|
|
37
37
|
}
|
|
38
38
|
function InputTime(set, value, range) {
|
|
39
|
-
//const data= this._inputNumStrMap.get(range);
|
|
40
|
-
//let val= value;
|
|
41
|
-
//if (1) return null;
|
|
42
39
|
let { min, max } = range ?? {};
|
|
43
|
-
// (data?.value==value)
|
|
44
|
-
// ? data
|
|
45
|
-
// : { min: range.min, max: range.max, step: range.defaultStep??range.step, val: value };
|
|
46
|
-
//this._inputNumStrMap.delete(range);
|
|
47
40
|
const timeSplits = typeof value == "string" ? value.split(":").length : 2;
|
|
48
41
|
const hasDot = typeof value == "string" ? value.includes(".") : false;
|
|
49
42
|
const step = range?.step ?? (timeSplits <= 1 ? TF.D1.msec : timeSplits == 2 ? TF.M1.msec : hasDot ? 1 : TF.S1.msec);
|
|
@@ -64,10 +57,8 @@ function InputTime(set, value, range) {
|
|
|
64
57
|
step % TF.S1.msec == 0 ? timeLocalToStr_yyyymmdd_hhmmss(new Date(val_)) : timeLocalToStr_yyyymmdd_hhmmss_ms(new Date(val_));
|
|
65
58
|
return timeStr.replace(" ", "T");
|
|
66
59
|
}
|
|
67
|
-
//function toInputVal(val: string | const_Date | undefined) { return val ? new Date(val).valueOf() : undefined; }
|
|
68
|
-
//const inputType : React.InputHTMLAttributes.type = ;
|
|
69
60
|
let _ref = null;
|
|
70
|
-
return _jsxs(_Fragment, { children: [_jsx("input", { type: "range", min: toNum(range?.defaultMin ?? range?.min ?? "2015
|
|
61
|
+
return _jsxs(_Fragment, { children: [_jsx("input", { type: "range", min: toNum(range?.defaultMin ?? range?.min ?? "2015-01-01"), max: toNum(range?.defaultMax ?? range?.max ?? new Date()), step: range?.defaultStep ?? step, value: toNum(value) ?? "", onInput: (e) => {
|
|
71
62
|
setVal(Number(e.currentTarget.value));
|
|
72
63
|
}, ref: (ref) => {
|
|
73
64
|
if (ref) {
|
|
@@ -76,18 +67,13 @@ function InputTime(set, value, range) {
|
|
|
76
67
|
}
|
|
77
68
|
} }), _jsx("div", { children: _jsx("input", { type: step % TF.D1.msec == 0 ? "date" : "datetime-local", style: { width: "calc(100% - 13px)", marginTop: 5 }, onInput: (e) => {
|
|
78
69
|
set(e.currentTarget.value);
|
|
79
|
-
}, min: toInputStr(min), max: toInputStr(max), step: step % TF.D1.msec == 0 ? step / TF.D1.msec : step / TF.S1.msec, value: toInputStr(normalizedValue),
|
|
80
|
-
//onMouseOver={()=>{ console.log("over"); _ref?.dispatchEvent(new MouseEvent("mouseenter", { 'bubbles': true })); _ref?.dispatchEvent(new MouseEvent("mouseover", { 'bubbles': true }))}}
|
|
81
|
-
onMouseEnter: () => {
|
|
70
|
+
}, min: toInputStr(min), max: toInputStr(max), step: step % TF.D1.msec == 0 ? step / TF.D1.msec : step / TF.S1.msec, value: toInputStr(normalizedValue), onMouseEnter: () => {
|
|
82
71
|
_ref?.focus();
|
|
83
72
|
}, onMouseLeave: () => {
|
|
84
73
|
_ref?.blur();
|
|
85
|
-
} }) }), _jsx("input", { required: true, className: "toNumberInput inputCan", type: "number", style: { width: 18, marginLeft: -13, marginRight: 0 }, min: toNum(min), max: toNum(max), step: step, value: toNum(value), onInput: (e) => {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
},
|
|
89
|
-
//onMouseEnter={()=>console.log("!!!", deepClone(_ref?.classList))}
|
|
90
|
-
ref: (ref) => {
|
|
74
|
+
} }) }), _jsx("input", { required: true, className: "toNumberInput inputCan", type: "number", style: { width: 18, marginLeft: -13, marginRight: 0 }, min: toNum(min), max: toNum(max), step: step, value: toNum(value) ?? "", onInput: (e) => {
|
|
75
|
+
setVal(Number(e.currentTarget.value));
|
|
76
|
+
}, ref: (ref) => {
|
|
91
77
|
if (ref) {
|
|
92
78
|
_ref = ref;
|
|
93
79
|
}
|
|
@@ -117,19 +103,16 @@ function InputList(set, value, range, rangeLabels) {
|
|
|
117
103
|
}
|
|
118
104
|
const _inputNumStrMap = new WeakMap();
|
|
119
105
|
function InputNumber(set, value, range) {
|
|
120
|
-
//if (1) return null;
|
|
121
106
|
const data = _inputNumStrMap.get(range);
|
|
122
|
-
//let val= value;
|
|
123
107
|
let { min, max, step, val } = (data?.value == value)
|
|
124
108
|
? data
|
|
125
109
|
: { min: range.min, max: range.max, step: range.defaultStep ?? range.step, val: value };
|
|
126
110
|
_inputNumStrMap.delete(range);
|
|
127
111
|
let _ref;
|
|
128
112
|
return _jsxs(_Fragment, { children: [_jsx("input", { type: "range", min: range.defaultMin ?? range.min, max: range.defaultMax ?? range.max, step: range.defaultStep ?? range.step, value: String(value), onInput: (e) => {
|
|
129
|
-
|
|
130
|
-
set(Number(current.value));
|
|
113
|
+
set(Number(e.currentTarget.value));
|
|
131
114
|
if (_ref)
|
|
132
|
-
_ref.step =
|
|
115
|
+
_ref.step = e.currentTarget.step;
|
|
133
116
|
}, ref: (ref) => {
|
|
134
117
|
if (ref) {
|
|
135
118
|
setResizeableElement(ref);
|
|
@@ -138,7 +121,6 @@ function InputNumber(set, value, range) {
|
|
|
138
121
|
} }), _jsx("input", { required: true, className: "toNumberInput inputCan", type: "number", min: min, max: max, step: step, value: val, onInput: (e) => {
|
|
139
122
|
const target = e.currentTarget;
|
|
140
123
|
let value2 = target.value != "" ? Number(target.value) : value;
|
|
141
|
-
//if (target.value=="") console.log(value, range);
|
|
142
124
|
_inputNumStrMap.set(range, {
|
|
143
125
|
val: target.value,
|
|
144
126
|
min: target.min,
|
|
@@ -150,11 +132,8 @@ function InputNumber(set, value, range) {
|
|
|
150
132
|
set(Number(target.value));
|
|
151
133
|
else {
|
|
152
134
|
target.reportValidity();
|
|
153
|
-
// this.Refresh();
|
|
154
135
|
}
|
|
155
|
-
},
|
|
156
|
-
//onBlur={ (a)=>{ console.log("blur"); if (! a.currentTarget.checkValidity()) set(Number(a.currentTarget.value)); } }
|
|
157
|
-
ref: (ref) => {
|
|
136
|
+
}, ref: (ref) => {
|
|
158
137
|
if (ref) {
|
|
159
138
|
_ref = ref;
|
|
160
139
|
ref.step = step + "";
|
|
@@ -185,10 +164,12 @@ function InputListArray(set, values, range, rangeLabels) {
|
|
|
185
164
|
}) });
|
|
186
165
|
}
|
|
187
166
|
export function ParametersReact(data) {
|
|
188
|
-
|
|
167
|
+
// callbacks live in a ref: useMemo below intentionally freezes the element until
|
|
168
|
+
// params identity changes, and would otherwise freeze a stale onChange/onExpand with it
|
|
169
|
+
const callbacks = useRef({ onChange: data.onChange, onExpand: data.onExpand });
|
|
170
|
+
callbacks.current = { onChange: data.onChange, onExpand: data.onExpand };
|
|
189
171
|
const result = useMemo(() => {
|
|
190
|
-
params.current
|
|
191
|
-
return _jsx(ParametersBaseReact, { params: data.params, onChange: data.onChange, onExpand: data.onExpand, expandStatus: data.expandStatus, expandStatusLvl: data.expandStatusLvl });
|
|
172
|
+
return _jsx(ParametersBaseReact, { params: data.params, onChange: (p) => callbacks.current.onChange(p), onExpand: (p) => callbacks.current.onExpand?.(p), expandStatus: data.expandStatus, expandStatusLvl: data.expandStatusLvl });
|
|
192
173
|
}, [data.params]);
|
|
193
174
|
return result;
|
|
194
175
|
}
|
|
@@ -198,10 +179,9 @@ function ParametersBaseReact(data) {
|
|
|
198
179
|
const styleColorDisable = "rgb(146,146,146)";
|
|
199
180
|
const p = useMemo(() => deepCloneMutable(data.params), [data.params]);
|
|
200
181
|
const myParams = useRef(p);
|
|
201
|
-
|
|
182
|
+
// sync during render: the effect+Refresh variant committed a frame of stale params first
|
|
183
|
+
if (myParams.current !== p)
|
|
202
184
|
myParams.current = p;
|
|
203
|
-
Refresh();
|
|
204
|
-
}, [p]);
|
|
205
185
|
const timeoutId = useRef(null);
|
|
206
186
|
useEffect(() => () => { if (timeoutId.current != null)
|
|
207
187
|
clearTimeout(timeoutId.current); }, []);
|
|
@@ -233,24 +213,23 @@ function ParametersBaseReact(data) {
|
|
|
233
213
|
Refresh();
|
|
234
214
|
if (enabled)
|
|
235
215
|
if (typeof value == "number" || value instanceof Date || (value instanceof Array && (typeof value[0] == "number" || value[0] instanceof Date)))
|
|
236
|
-
refreshIndicatorDelayed(); //
|
|
216
|
+
refreshIndicatorDelayed(); // process with delay
|
|
237
217
|
else
|
|
238
|
-
refreshIndicator(); //
|
|
239
|
-
//SessionSave();
|
|
218
|
+
refreshIndicator(); // process without delay
|
|
240
219
|
}
|
|
241
220
|
function onExpandA(param, flag) {
|
|
242
221
|
data.onExpand?.(myParams.current);
|
|
243
222
|
}
|
|
244
223
|
function ListParams(obj, parentEnabled = true, expandLevel = 0) {
|
|
245
224
|
return Object.entries(obj).map(([key, param]) => {
|
|
246
|
-
|
|
225
|
+
// Indicator parameter changes
|
|
247
226
|
const onSetValue = (value, currentEnabled = true) => {
|
|
248
227
|
onSetValueA(param, value, parentEnabled && currentEnabled);
|
|
249
228
|
};
|
|
250
229
|
if (!Array.isArray(obj))
|
|
251
230
|
if (typeof (param) == "boolean" || typeof (param) == "string") {
|
|
252
231
|
const set = (data) => {
|
|
253
|
-
obj[key] = data;
|
|
232
|
+
obj[key] = data; // this branch only renders boolean/string params
|
|
254
233
|
onSetValue(data);
|
|
255
234
|
};
|
|
256
235
|
return _jsx(CParameter, { name: key, enabled: parentEnabled, children: Param(set, param) }, key + "#" + typeof (param));
|
|
@@ -265,7 +244,6 @@ function ParametersBaseReact(data) {
|
|
|
265
244
|
const nameButton = (type) => FNameButton(type, name);
|
|
266
245
|
const nameT = _jsx("p", { className: "toPTextIndicator", children: name });
|
|
267
246
|
const set = (a) => {
|
|
268
|
-
//if (typeof a=="boolean") {a=!value;}
|
|
269
247
|
const aa = param.value instanceof Date ? new Date(a) : a;
|
|
270
248
|
param.value = aa;
|
|
271
249
|
onSetValue(aa, param.enabled ?? true);
|
|
@@ -298,7 +276,7 @@ function ParametersBaseReact(data) {
|
|
|
298
276
|
onSetValue(value);
|
|
299
277
|
}, children: param.enabled ? "ON " : "OFF" })
|
|
300
278
|
: null;
|
|
301
|
-
//
|
|
279
|
+
// If there are nested parameters, recurse with an offset.
|
|
302
280
|
if (!Array.isArray(value)) {
|
|
303
281
|
const enabledD = enabled != false && parentEnabled != false;
|
|
304
282
|
const list = ListParams(value, enabledD, expandLevel - 1);
|
|
@@ -309,7 +287,7 @@ function ParametersBaseReact(data) {
|
|
|
309
287
|
}
|
|
310
288
|
if (Array.isArray(value)) {
|
|
311
289
|
let arr = value;
|
|
312
|
-
if (Array.isArray(range)) { //
|
|
290
|
+
if (Array.isArray(range)) { // render a dropdown list
|
|
313
291
|
if (typeof arr[0] == "boolean")
|
|
314
292
|
throw "boolean range is not supported!";
|
|
315
293
|
let arr2 = arr;
|
|
@@ -325,11 +303,10 @@ function ParametersBaseReact(data) {
|
|
|
325
303
|
type = itemType;
|
|
326
304
|
}
|
|
327
305
|
const elements = arr.map((itemVal, index, array) => {
|
|
328
|
-
|
|
306
|
+
// Render and add array elements.
|
|
329
307
|
const name_ = name + " #" + String(index + 1);
|
|
330
308
|
const nameElement = _jsx("div", { children: name_ }, name_ + "#$3");
|
|
331
309
|
const enabledElements = Array.isArray(param.elementsEnabled) ? param.elementsEnabled : undefined;
|
|
332
|
-
//const onHoverElement = <div style={{position: "absolute", left: -30, top: 0, display: "flex"}}>
|
|
333
310
|
const onHoverElement = _jsxs("div", { style: { marginLeft: -18, marginTop: -7 }, children: [_jsx("div", { className: "toButtonEasy", style: { width: "15px", fontSize: "medium", marginRight: 3 }, onClick: () => {
|
|
334
311
|
array.splice(index, 0, itemVal);
|
|
335
312
|
enabledElements?.splice(index, 0, enabledElements[index]);
|
|
@@ -353,11 +330,7 @@ function ParametersBaseReact(data) {
|
|
|
353
330
|
: undefined] });
|
|
354
331
|
return _jsx(DivHover, { children: (hover) => paramL(hover) }, name_ + "#$");
|
|
355
332
|
});
|
|
356
|
-
return _jsx(CButton, { className: "toIndicatorMenuButton", name: nameButton, status: expanded, header: enableHTML, onExpand: onExpand, children: _jsx("div", { className: "", style: { overflow: "hidden", paddingLeft: nestedMarginLeft }, children: _jsxs("div", { style: { animation: "0.1s ease 0s 1 normal none running moveDown" }, children: [_jsx("div", { className: "", style: {
|
|
357
|
-
//marginLeft: widthStr,
|
|
358
|
-
//width: "calc(100%-" + widthStr + ")",
|
|
359
|
-
color: "inherit"
|
|
360
|
-
}, children: elements }), !param.constLength &&
|
|
333
|
+
return _jsx(CButton, { className: "toIndicatorMenuButton", name: nameButton, status: expanded, header: enableHTML, onExpand: onExpand, children: _jsx("div", { className: "", style: { overflow: "hidden", paddingLeft: nestedMarginLeft }, children: _jsxs("div", { style: { animation: "0.1s ease 0s 1 normal none running moveDown" }, children: [_jsx("div", { className: "", style: { color: "inherit" }, children: elements }), !param.constLength &&
|
|
361
334
|
_jsx("div", { className: "toButtonEasy", style: { paddingLeft: 10, textAlign: "left" /*width: "255px"*/ }, onClick: () => {
|
|
362
335
|
let element = arr.at(-1);
|
|
363
336
|
element ??= Array.isArray(param.range) ? param.range[0] : param.range ? param.range.min :
|
|
@@ -367,9 +340,7 @@ function ParametersBaseReact(data) {
|
|
|
367
340
|
}, children: " ......add new element" })] }) }) }, key + "#$");
|
|
368
341
|
}
|
|
369
342
|
}
|
|
370
|
-
|
|
371
|
-
return simpleParameter(Param(set, value, undefined, range, param.labels));
|
|
372
|
-
}
|
|
343
|
+
return simpleParameter(Param(set, value, undefined, range, param.labels));
|
|
373
344
|
}
|
|
374
345
|
return null;
|
|
375
346
|
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { AgGridReactProps } from 'ag-grid-react';
|
|
3
|
+
import type { GridApi, GetRowIdParams, GridPreDestroyedEvent, GridReadyEvent } from 'ag-grid-community';
|
|
4
|
+
import { type BufferTable, type GetId } from './core';
|
|
5
|
+
export type UseAgGridOptions<T> = {
|
|
6
|
+
/** How to get a row id. Defaults to the `id` field. Captured once (first render). */
|
|
7
|
+
getId?: GetId<T>;
|
|
8
|
+
/** External buffer: a plain object above the component (like datum.tableArr) that survives route remounts. */
|
|
9
|
+
externalBuffer?: BufferTable<T>;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Headless table hook. All race handling lives in the core; the hook only binds it
|
|
13
|
+
* to the React and grid lifecycle:
|
|
14
|
+
*
|
|
15
|
+
* const grid = useAgGrid<Row>({ getId })
|
|
16
|
+
* <AgGridMy<Row> controller={grid} columnDefs={cols} />
|
|
17
|
+
* grid.update({ newData })
|
|
18
|
+
*/
|
|
19
|
+
export declare function useAgGrid<T>(options?: UseAgGridOptions<T>): {
|
|
20
|
+
update: (args: import("./core").UpdateArgs<T>) => void;
|
|
21
|
+
remove(rows: Partial<T>[]): void;
|
|
22
|
+
fit: () => boolean;
|
|
23
|
+
updateData: (args: import("./core").UpdateArgs<T>) => void;
|
|
24
|
+
sync: () => void;
|
|
25
|
+
getId: GetId<T>;
|
|
26
|
+
buffer: BufferTable<T>;
|
|
27
|
+
apiRef: React.RefObject<GridApi<T> | null>;
|
|
28
|
+
props: {
|
|
29
|
+
getRowId(params: GetRowIdParams<T>): string;
|
|
30
|
+
onGridReady(event: GridReadyEvent<T>): void;
|
|
31
|
+
onGridPreDestroyed(_event: GridPreDestroyedEvent<T>): void;
|
|
32
|
+
};
|
|
33
|
+
gridProps: {
|
|
34
|
+
getRowId(params: GetRowIdParams<T>): string;
|
|
35
|
+
onGridReady(event: GridReadyEvent<T>): void;
|
|
36
|
+
onGridPreDestroyed(_event: GridPreDestroyedEvent<T>): void;
|
|
37
|
+
};
|
|
38
|
+
getApi(): GridApi<T> | null;
|
|
39
|
+
withApi<R>(fn: (api: GridApi<T>) => R): R | undefined;
|
|
40
|
+
sizeColumnsToFit: () => boolean;
|
|
41
|
+
};
|
|
42
|
+
export type AgGridController<T> = ReturnType<typeof useAgGrid<T>>;
|
|
43
|
+
export type AgGridMyProps<T> = AgGridReactProps<T> & {
|
|
44
|
+
/** Controller from useAgGrid: all wiring (getRowId, ready/destroy, sync) is inside. */
|
|
45
|
+
controller?: AgGridController<T>;
|
|
46
|
+
/** Declarative row upsert for simple cases without a controller. */
|
|
47
|
+
data?: Partial<T>[];
|
|
48
|
+
/** Auto-fit columns to the container. Defaults to true. */
|
|
49
|
+
autoSizeColumns?: boolean;
|
|
50
|
+
};
|
|
51
|
+
declare function AgGridMyInner<T>(props: AgGridMyProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
52
|
+
export declare const AgGridMy: typeof AgGridMyInner;
|
|
53
|
+
export {};
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
// Layer 2 (React): headless hook over the core + custom <AgGridMy> component.
|
|
3
|
+
// The hook does NOT render; it returns a controller. AgGridMy is our AgGridReact with
|
|
4
|
+
// project defaults (theme, memo, auto sizing, selection, lifecycle). Wiring is the
|
|
5
|
+
// controller prop; bare <AgGridReact> can still spread gridProps (as in agGrid3).
|
|
6
|
+
import { memo, useEffect, useMemo, useRef, useState } from 'react';
|
|
7
|
+
import { AgGridReact } from 'ag-grid-react';
|
|
8
|
+
import { AllCommunityModule, ModuleRegistry } from 'ag-grid-community';
|
|
9
|
+
import { createGridBuffer } from './core';
|
|
10
|
+
import { useAgGridTheme } from './theme';
|
|
11
|
+
// ag-grid v35 requires module registration. Do it lazily and once, on the first hook
|
|
12
|
+
// use, not when the package is imported (the entry stays side-effect-free).
|
|
13
|
+
// Re-registration by a consumer that already calls GridStyleDefault() is harmless.
|
|
14
|
+
let modulesRegistered = false;
|
|
15
|
+
function ensureAgGridModules() {
|
|
16
|
+
if (modulesRegistered)
|
|
17
|
+
return;
|
|
18
|
+
modulesRegistered = true;
|
|
19
|
+
ModuleRegistry.registerModules([AllCommunityModule]);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Headless table hook. All race handling lives in the core; the hook only binds it
|
|
23
|
+
* to the React and grid lifecycle:
|
|
24
|
+
*
|
|
25
|
+
* const grid = useAgGrid<Row>({ getId })
|
|
26
|
+
* <AgGridMy<Row> controller={grid} columnDefs={cols} />
|
|
27
|
+
* grid.update({ newData })
|
|
28
|
+
*/
|
|
29
|
+
export function useAgGrid(options) {
|
|
30
|
+
ensureAgGridModules();
|
|
31
|
+
const apiRef = useRef(null);
|
|
32
|
+
// The core is created once; dependencies live in its closure, with no useCallback chains.
|
|
33
|
+
const [core] = useState(function createCore() {
|
|
34
|
+
const getId = options?.getId ?? (data => String(data?.id));
|
|
35
|
+
return createGridBuffer({ getId, externalBuffer: options?.externalBuffer });
|
|
36
|
+
});
|
|
37
|
+
const gridProps = useMemo(() => ({
|
|
38
|
+
getRowId(params) {
|
|
39
|
+
return core.api.getId(params.data);
|
|
40
|
+
},
|
|
41
|
+
onGridReady(event) {
|
|
42
|
+
apiRef.current = event.api;
|
|
43
|
+
core.control.attach(event.api); // attach runs sync and catches up with the race
|
|
44
|
+
},
|
|
45
|
+
onGridPreDestroyed(_event) {
|
|
46
|
+
apiRef.current = null;
|
|
47
|
+
core.control.detach();
|
|
48
|
+
},
|
|
49
|
+
}), [core]);
|
|
50
|
+
return useMemo(() => {
|
|
51
|
+
const fit = () => {
|
|
52
|
+
const api = apiRef.current;
|
|
53
|
+
if (!api)
|
|
54
|
+
return false;
|
|
55
|
+
api.sizeColumnsToFit();
|
|
56
|
+
return true;
|
|
57
|
+
};
|
|
58
|
+
return {
|
|
59
|
+
update: core.api.updateData,
|
|
60
|
+
remove(rows) {
|
|
61
|
+
core.api.updateData({ removeData: rows });
|
|
62
|
+
},
|
|
63
|
+
fit,
|
|
64
|
+
updateData: core.api.updateData,
|
|
65
|
+
sync: core.api.sync,
|
|
66
|
+
getId: core.api.getId,
|
|
67
|
+
buffer: core.api.buffer,
|
|
68
|
+
apiRef,
|
|
69
|
+
props: gridProps,
|
|
70
|
+
gridProps,
|
|
71
|
+
getApi() {
|
|
72
|
+
return apiRef.current;
|
|
73
|
+
},
|
|
74
|
+
withApi(fn) {
|
|
75
|
+
const api = apiRef.current;
|
|
76
|
+
return api ? fn(api) : undefined;
|
|
77
|
+
},
|
|
78
|
+
sizeColumnsToFit: fit,
|
|
79
|
+
};
|
|
80
|
+
}, [core, gridProps]);
|
|
81
|
+
}
|
|
82
|
+
// --- AgGridMy: our grid with project defaults --------------------------------
|
|
83
|
+
// v35: the canonical row-selection form is an object, not the legacy "multiple" string.
|
|
84
|
+
// Stable module-level reference so the grid does not re-evaluate the option on each render.
|
|
85
|
+
const ROW_SELECTION = { mode: 'multiRow' };
|
|
86
|
+
function AgGridMyInner(props) {
|
|
87
|
+
const { controller, data, autoSizeColumns = true, defaultColDef, theme, getRowId, onGridReady, onGridPreDestroyed, ...rest } = props;
|
|
88
|
+
// Own controller for declarative mode (without an external controller); the hook is unconditional.
|
|
89
|
+
const own = useAgGrid();
|
|
90
|
+
const grid = controller ?? own;
|
|
91
|
+
const defaultTheme = useAgGridTheme();
|
|
92
|
+
const containerRef = useRef(null);
|
|
93
|
+
useEffect(function pushData() {
|
|
94
|
+
if (data)
|
|
95
|
+
grid.update({ newData: data });
|
|
96
|
+
}, [data, grid]);
|
|
97
|
+
useEffect(function observeResize() {
|
|
98
|
+
if (!containerRef.current || !autoSizeColumns)
|
|
99
|
+
return;
|
|
100
|
+
let rafId = 0;
|
|
101
|
+
const observer = new ResizeObserver(function fitColumns(entries) {
|
|
102
|
+
// one observed element: the last entry is the latest size; one RAF per batch,
|
|
103
|
+
// cancelled on cleanup so fit() cannot hit a grid mid-destroy
|
|
104
|
+
const entry = entries[entries.length - 1];
|
|
105
|
+
if (entry.contentRect.width < 400)
|
|
106
|
+
return;
|
|
107
|
+
cancelAnimationFrame(rafId);
|
|
108
|
+
rafId = requestAnimationFrame(() => grid.fit());
|
|
109
|
+
});
|
|
110
|
+
observer.observe(containerRef.current);
|
|
111
|
+
return () => {
|
|
112
|
+
cancelAnimationFrame(rafId);
|
|
113
|
+
observer.disconnect();
|
|
114
|
+
};
|
|
115
|
+
}, [autoSizeColumns, grid]);
|
|
116
|
+
const mergedColDef = useMemo(() => ({ sortable: true, resizable: true, filter: true, ...defaultColDef }), [defaultColDef]);
|
|
117
|
+
return (_jsx("div", { ref: containerRef, style: { height: '100%', width: '100%', overflow: 'hidden' }, children: _jsx(AgGridReact, { theme: theme ?? defaultTheme, asyncTransactionWaitMillis: 50, suppressCellFocus: true, rowSelection: ROW_SELECTION, ...rest, defaultColDef: mergedColDef,
|
|
118
|
+
// getRowId after the spread: undefined from rest must not erase the wiring,
|
|
119
|
+
// otherwise the grid falls back to index-based row identity.
|
|
120
|
+
getRowId: getRowId ?? grid.props.getRowId, onGridReady: function ready(event) {
|
|
121
|
+
grid.props.onGridReady(event);
|
|
122
|
+
if (autoSizeColumns)
|
|
123
|
+
event.api.sizeColumnsToFit();
|
|
124
|
+
onGridReady?.(event);
|
|
125
|
+
}, onGridPreDestroyed: function destroyed(event) {
|
|
126
|
+
grid.props.onGridPreDestroyed(event);
|
|
127
|
+
onGridPreDestroyed?.(event);
|
|
128
|
+
} }) }));
|
|
129
|
+
}
|
|
130
|
+
// memo: the grid does not re-render from unrelated page state; data updates bypass
|
|
131
|
+
// render (controller.updateData). The cast is required because memo() erases the component generic.
|
|
132
|
+
export const AgGridMy = memo(AgGridMyInner);
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export type RowId = string;
|
|
2
|
+
/** Gets a stable row id. Works with partial data too. */
|
|
3
|
+
export type GetId<T> = (data: Partial<T>) => RowId;
|
|
4
|
+
export type BufferTable<T> = Record<RowId, Partial<T>>;
|
|
5
|
+
export type GridTransaction<T> = {
|
|
6
|
+
add?: T[];
|
|
7
|
+
update?: T[];
|
|
8
|
+
remove?: T[];
|
|
9
|
+
};
|
|
10
|
+
/** Minimal grid api contract (the real GridApi is structurally compatible). */
|
|
11
|
+
export type GridApiLike<T> = {
|
|
12
|
+
forEachNode(callback: (node: {
|
|
13
|
+
data?: T;
|
|
14
|
+
}) => void): void;
|
|
15
|
+
applyTransaction(tx: GridTransaction<T>): unknown;
|
|
16
|
+
applyTransactionAsync(tx: GridTransaction<T>): void;
|
|
17
|
+
};
|
|
18
|
+
export type PushOptions = {
|
|
19
|
+
add?: boolean;
|
|
20
|
+
update?: boolean;
|
|
21
|
+
/** add+update in one SYNCHRONOUS transaction. Default: add is sync, update is async (batched). */
|
|
22
|
+
sync?: boolean;
|
|
23
|
+
};
|
|
24
|
+
export type UpdateArgs<T> = {
|
|
25
|
+
newData?: Partial<T>[];
|
|
26
|
+
removeData?: Partial<T>[];
|
|
27
|
+
/** Only update the buffer; do not touch the grid. */
|
|
28
|
+
onlyMemo?: boolean;
|
|
29
|
+
option?: PushOptions;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Table core: buffer + delivery to the grid. Lifecycle:
|
|
33
|
+
* 1. updateData at any time: the buffer is always updated, the grid only if attached;
|
|
34
|
+
* 2. attach(api) on onGridReady: sync catches up with everything that arrived earlier;
|
|
35
|
+
* 3. detach() on grid destroy: the core accumulates in the buffer again until the next attach.
|
|
36
|
+
*/
|
|
37
|
+
export declare function createGridBuffer<T>(deps: {
|
|
38
|
+
getId: GetId<T>;
|
|
39
|
+
/** External buffer (object above the component) survives route remounts. Otherwise use an internal one. */
|
|
40
|
+
externalBuffer?: BufferTable<T>;
|
|
41
|
+
}): {
|
|
42
|
+
control: {
|
|
43
|
+
attach: (gridApi: GridApiLike<T>) => void;
|
|
44
|
+
detach: () => void;
|
|
45
|
+
};
|
|
46
|
+
api: {
|
|
47
|
+
updateData: (args: UpdateArgs<T>) => void;
|
|
48
|
+
sync: () => void;
|
|
49
|
+
getId: GetId<T>;
|
|
50
|
+
buffer: BufferTable<T>;
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
export type GridBufferCore<T> = ReturnType<typeof createGridBuffer<T>>;
|
|
54
|
+
export type NumberPair = [a: number, b: number];
|
|
55
|
+
/** Column comparator: numbers as numbers, empty/NaN values last (respecting inversion). */
|
|
56
|
+
export declare function numericComparator<T = any>(map?: (a: any, b: any) => NumberPair): (a1: any, b1: any, _nodeA?: T, _nodeB?: T, inv?: boolean) => number;
|