wenay-react2 1.0.19 → 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 +10 -7
- 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 -107
- package/lib/common/src/components/Dnd/Resizable.js +3 -3
- package/lib/common/src/components/Input.js +3 -3
- package/lib/common/src/components/Menu/RightMenu.js +17 -18
- package/lib/common/src/components/Menu/StickerMenu.d.ts +1 -3
- 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 +34 -52
- 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 +6 -5
- package/lib/common/src/components/Parameters.js +8 -7
- package/lib/common/src/components/ParametersEngine.js +36 -59
- 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 -9
- 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 +77 -26
- package/lib/common/src/logs/logs.d.ts +5 -2
- package/lib/common/src/logs/logs.js +18 -34
- package/lib/common/src/logs/logs3.d.ts +9 -9
- package/lib/common/src/logs/logs3.js +48 -39
- 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 +49 -25
- package/lib/common/src/menu/menuMouse.d.ts +2 -1
- package/lib/common/src/menu/menuMouse.js +5 -4
- package/lib/common/src/menu/menuR.d.ts +4 -3
- package/lib/common/src/menu/menuR.js +42 -38
- 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 +189 -118
- 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 +25 -19
- 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 +32 -24
- 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 +15 -15
- package/lib/common/updateBy.d.ts +21 -2
- package/lib/common/updateBy.js +66 -131
- 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);
|
|
@@ -6,13 +6,13 @@ export function EditParams2({ onSave, params: paramsDef }) {
|
|
|
6
6
|
paramsDef().then(e => {
|
|
7
7
|
setParamsD(e);
|
|
8
8
|
});
|
|
9
|
-
}, [
|
|
9
|
+
}, []);
|
|
10
10
|
const [paramsD, setParamsD] = useState(null);
|
|
11
11
|
const params = useRef(null);
|
|
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 }) {
|
|
@@ -20,12 +20,13 @@ export function EditParams3({ onSave, params: paramsDef }) {
|
|
|
20
20
|
paramsDef().then(e => {
|
|
21
21
|
setParams(e);
|
|
22
22
|
});
|
|
23
|
-
}, [
|
|
23
|
+
}, []);
|
|
24
24
|
const [params, setParams] = useState(null);
|
|
25
25
|
return _jsxs("div", { className: "maxSize", children: [params && params.map((z, i) => _jsx(ParametersReact, { params: z, onChange: e => {
|
|
26
|
-
params[i] =
|
|
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); //
|
|
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
|
|
10
|
+
const [hovered, setHovered] = React.useState(false); // Tracks mouse hover state
|
|
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
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
3
3
|
import { deepCloneMutable, isDate, TF, timeLocalToStr_yyyymmdd, timeLocalToStr_yyyymmdd_hhmm, timeLocalToStr_yyyymmdd_hhmmss, timeLocalToStr_yyyymmdd_hhmmss_ms } from "wenay-common2";
|
|
4
|
-
import { setResizeableElement } from "./MyResizeObserver";
|
|
4
|
+
import { setResizeableElement, removeResizeableElement } from "./MyResizeObserver";
|
|
5
5
|
import { CParameter, FNameButton } from "./Parameters";
|
|
6
6
|
import { SetAutoStepForElement } from "../utils";
|
|
7
7
|
function timeToStr(time, step) {
|
|
@@ -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,29 +57,23 @@ 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) {
|
|
74
65
|
setResizeableElement(ref);
|
|
66
|
+
return () => removeResizeableElement(ref);
|
|
75
67
|
}
|
|
76
68
|
} }), _jsx("div", { children: _jsx("input", { type: step % TF.D1.msec == 0 ? "date" : "datetime-local", style: { width: "calc(100% - 13px)", marginTop: 5 }, onInput: (e) => {
|
|
77
69
|
set(e.currentTarget.value);
|
|
78
|
-
}, min: toInputStr(min), max: toInputStr(max), step: step % TF.D1.msec == 0 ? step / TF.D1.msec : step / TF.S1.msec, value: toInputStr(normalizedValue),
|
|
79
|
-
//onMouseOver={()=>{ console.log("over"); _ref?.dispatchEvent(new MouseEvent("mouseenter", { 'bubbles': true })); _ref?.dispatchEvent(new MouseEvent("mouseover", { 'bubbles': true }))}}
|
|
80
|
-
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: () => {
|
|
81
71
|
_ref?.focus();
|
|
82
72
|
}, onMouseLeave: () => {
|
|
83
73
|
_ref?.blur();
|
|
84
|
-
} }) }), _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) => {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
},
|
|
88
|
-
//onMouseEnter={()=>console.log("!!!", deepClone(_ref?.classList))}
|
|
89
|
-
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) => {
|
|
90
77
|
if (ref) {
|
|
91
78
|
_ref = ref;
|
|
92
79
|
}
|
|
@@ -108,6 +95,7 @@ function InputList(set, value, range, rangeLabels) {
|
|
|
108
95
|
}, ref: (ref) => {
|
|
109
96
|
if (ref) {
|
|
110
97
|
setResizeableElement(ref);
|
|
98
|
+
return () => removeResizeableElement(ref);
|
|
111
99
|
}
|
|
112
100
|
}, children: range.map((a, i) => {
|
|
113
101
|
return _jsx("option", { value: toString(a), label: rangeLabels?.at(i) ?? toString(a), children: toString(a) }, toString(a));
|
|
@@ -115,27 +103,24 @@ function InputList(set, value, range, rangeLabels) {
|
|
|
115
103
|
}
|
|
116
104
|
const _inputNumStrMap = new WeakMap();
|
|
117
105
|
function InputNumber(set, value, range) {
|
|
118
|
-
//if (1) return null;
|
|
119
106
|
const data = _inputNumStrMap.get(range);
|
|
120
|
-
//let val= value;
|
|
121
107
|
let { min, max, step, val } = (data?.value == value)
|
|
122
108
|
? data
|
|
123
109
|
: { min: range.min, max: range.max, step: range.defaultStep ?? range.step, val: value };
|
|
124
110
|
_inputNumStrMap.delete(range);
|
|
125
111
|
let _ref;
|
|
126
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) => {
|
|
127
|
-
|
|
128
|
-
set(Number(current.value));
|
|
113
|
+
set(Number(e.currentTarget.value));
|
|
129
114
|
if (_ref)
|
|
130
|
-
_ref.step =
|
|
115
|
+
_ref.step = e.currentTarget.step;
|
|
131
116
|
}, ref: (ref) => {
|
|
132
117
|
if (ref) {
|
|
133
118
|
setResizeableElement(ref);
|
|
119
|
+
return () => removeResizeableElement(ref);
|
|
134
120
|
}
|
|
135
121
|
} }), _jsx("input", { required: true, className: "toNumberInput inputCan", type: "number", min: min, max: max, step: step, value: val, onInput: (e) => {
|
|
136
122
|
const target = e.currentTarget;
|
|
137
123
|
let value2 = target.value != "" ? Number(target.value) : value;
|
|
138
|
-
//if (target.value=="") console.log(value, range);
|
|
139
124
|
_inputNumStrMap.set(range, {
|
|
140
125
|
val: target.value,
|
|
141
126
|
min: target.min,
|
|
@@ -147,11 +132,8 @@ function InputNumber(set, value, range) {
|
|
|
147
132
|
set(Number(target.value));
|
|
148
133
|
else {
|
|
149
134
|
target.reportValidity();
|
|
150
|
-
// this.Refresh();
|
|
151
135
|
}
|
|
152
|
-
},
|
|
153
|
-
//onBlur={ (a)=>{ console.log("blur"); if (! a.currentTarget.checkValidity()) set(Number(a.currentTarget.value)); } }
|
|
154
|
-
ref: (ref) => {
|
|
136
|
+
}, ref: (ref) => {
|
|
155
137
|
if (ref) {
|
|
156
138
|
_ref = ref;
|
|
157
139
|
ref.step = step + "";
|
|
@@ -175,16 +157,19 @@ function InputListArray(set, values, range, rangeLabels) {
|
|
|
175
157
|
}, multiple: true, ref: (ref) => {
|
|
176
158
|
if (ref) {
|
|
177
159
|
setResizeableElement(ref);
|
|
160
|
+
return () => removeResizeableElement(ref);
|
|
178
161
|
}
|
|
179
162
|
}, children: range.map((a, i) => {
|
|
180
163
|
return _jsx("option", { value: String(a), label: rangeLabels?.at(i) ?? String(a), children: a }, String(a));
|
|
181
164
|
}) });
|
|
182
165
|
}
|
|
183
166
|
export function ParametersReact(data) {
|
|
184
|
-
|
|
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 };
|
|
185
171
|
const result = useMemo(() => {
|
|
186
|
-
params.current
|
|
187
|
-
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 });
|
|
188
173
|
}, [data.params]);
|
|
189
174
|
return result;
|
|
190
175
|
}
|
|
@@ -194,18 +179,19 @@ function ParametersBaseReact(data) {
|
|
|
194
179
|
const styleColorDisable = "rgb(146,146,146)";
|
|
195
180
|
const p = useMemo(() => deepCloneMutable(data.params), [data.params]);
|
|
196
181
|
const myParams = useRef(p);
|
|
197
|
-
|
|
182
|
+
// sync during render: the effect+Refresh variant committed a frame of stale params first
|
|
183
|
+
if (myParams.current !== p)
|
|
198
184
|
myParams.current = p;
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
185
|
+
const timeoutId = useRef(null);
|
|
186
|
+
useEffect(() => () => { if (timeoutId.current != null)
|
|
187
|
+
clearTimeout(timeoutId.current); }, []);
|
|
202
188
|
function refreshIndicator() {
|
|
203
189
|
data.onChange(myParams.current);
|
|
204
190
|
}
|
|
205
191
|
function refreshIndicatorDelayed() {
|
|
206
|
-
if (timeoutId != null)
|
|
207
|
-
clearTimeout(timeoutId);
|
|
208
|
-
timeoutId = setTimeout(() => {
|
|
192
|
+
if (timeoutId.current != null)
|
|
193
|
+
clearTimeout(timeoutId.current);
|
|
194
|
+
timeoutId.current = setTimeout(() => {
|
|
209
195
|
refreshIndicator();
|
|
210
196
|
}, 200);
|
|
211
197
|
}
|
|
@@ -227,24 +213,23 @@ function ParametersBaseReact(data) {
|
|
|
227
213
|
Refresh();
|
|
228
214
|
if (enabled)
|
|
229
215
|
if (typeof value == "number" || value instanceof Date || (value instanceof Array && (typeof value[0] == "number" || value[0] instanceof Date)))
|
|
230
|
-
refreshIndicatorDelayed(); //
|
|
216
|
+
refreshIndicatorDelayed(); // process with delay
|
|
231
217
|
else
|
|
232
|
-
refreshIndicator(); //
|
|
233
|
-
//SessionSave();
|
|
218
|
+
refreshIndicator(); // process without delay
|
|
234
219
|
}
|
|
235
220
|
function onExpandA(param, flag) {
|
|
236
221
|
data.onExpand?.(myParams.current);
|
|
237
222
|
}
|
|
238
223
|
function ListParams(obj, parentEnabled = true, expandLevel = 0) {
|
|
239
224
|
return Object.entries(obj).map(([key, param]) => {
|
|
240
|
-
|
|
225
|
+
// Indicator parameter changes
|
|
241
226
|
const onSetValue = (value, currentEnabled = true) => {
|
|
242
227
|
onSetValueA(param, value, parentEnabled && currentEnabled);
|
|
243
228
|
};
|
|
244
229
|
if (!Array.isArray(obj))
|
|
245
230
|
if (typeof (param) == "boolean" || typeof (param) == "string") {
|
|
246
231
|
const set = (data) => {
|
|
247
|
-
obj[key] = data;
|
|
232
|
+
obj[key] = data; // this branch only renders boolean/string params
|
|
248
233
|
onSetValue(data);
|
|
249
234
|
};
|
|
250
235
|
return _jsx(CParameter, { name: key, enabled: parentEnabled, children: Param(set, param) }, key + "#" + typeof (param));
|
|
@@ -259,7 +244,6 @@ function ParametersBaseReact(data) {
|
|
|
259
244
|
const nameButton = (type) => FNameButton(type, name);
|
|
260
245
|
const nameT = _jsx("p", { className: "toPTextIndicator", children: name });
|
|
261
246
|
const set = (a) => {
|
|
262
|
-
//if (typeof a=="boolean") {a=!value;}
|
|
263
247
|
const aa = param.value instanceof Date ? new Date(a) : a;
|
|
264
248
|
param.value = aa;
|
|
265
249
|
onSetValue(aa, param.enabled ?? true);
|
|
@@ -292,7 +276,7 @@ function ParametersBaseReact(data) {
|
|
|
292
276
|
onSetValue(value);
|
|
293
277
|
}, children: param.enabled ? "ON " : "OFF" })
|
|
294
278
|
: null;
|
|
295
|
-
//
|
|
279
|
+
// If there are nested parameters, recurse with an offset.
|
|
296
280
|
if (!Array.isArray(value)) {
|
|
297
281
|
const enabledD = enabled != false && parentEnabled != false;
|
|
298
282
|
const list = ListParams(value, enabledD, expandLevel - 1);
|
|
@@ -303,7 +287,7 @@ function ParametersBaseReact(data) {
|
|
|
303
287
|
}
|
|
304
288
|
if (Array.isArray(value)) {
|
|
305
289
|
let arr = value;
|
|
306
|
-
if (Array.isArray(range)) { //
|
|
290
|
+
if (Array.isArray(range)) { // render a dropdown list
|
|
307
291
|
if (typeof arr[0] == "boolean")
|
|
308
292
|
throw "boolean range is not supported!";
|
|
309
293
|
let arr2 = arr;
|
|
@@ -319,11 +303,10 @@ function ParametersBaseReact(data) {
|
|
|
319
303
|
type = itemType;
|
|
320
304
|
}
|
|
321
305
|
const elements = arr.map((itemVal, index, array) => {
|
|
322
|
-
|
|
306
|
+
// Render and add array elements.
|
|
323
307
|
const name_ = name + " #" + String(index + 1);
|
|
324
308
|
const nameElement = _jsx("div", { children: name_ }, name_ + "#$3");
|
|
325
309
|
const enabledElements = Array.isArray(param.elementsEnabled) ? param.elementsEnabled : undefined;
|
|
326
|
-
//const onHoverElement = <div style={{position: "absolute", left: -30, top: 0, display: "flex"}}>
|
|
327
310
|
const onHoverElement = _jsxs("div", { style: { marginLeft: -18, marginTop: -7 }, children: [_jsx("div", { className: "toButtonEasy", style: { width: "15px", fontSize: "medium", marginRight: 3 }, onClick: () => {
|
|
328
311
|
array.splice(index, 0, itemVal);
|
|
329
312
|
enabledElements?.splice(index, 0, enabledElements[index]);
|
|
@@ -347,11 +330,7 @@ function ParametersBaseReact(data) {
|
|
|
347
330
|
: undefined] });
|
|
348
331
|
return _jsx(DivHover, { children: (hover) => paramL(hover) }, name_ + "#$");
|
|
349
332
|
});
|
|
350
|
-
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: {
|
|
351
|
-
//marginLeft: widthStr,
|
|
352
|
-
//width: "calc(100%-" + widthStr + ")",
|
|
353
|
-
color: "inherit"
|
|
354
|
-
}, 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 &&
|
|
355
334
|
_jsx("div", { className: "toButtonEasy", style: { paddingLeft: 10, textAlign: "left" /*width: "255px"*/ }, onClick: () => {
|
|
356
335
|
let element = arr.at(-1);
|
|
357
336
|
element ??= Array.isArray(param.range) ? param.range[0] : param.range ? param.range.min :
|
|
@@ -361,9 +340,7 @@ function ParametersBaseReact(data) {
|
|
|
361
340
|
}, children: " ......add new element" })] }) }) }, key + "#$");
|
|
362
341
|
}
|
|
363
342
|
}
|
|
364
|
-
|
|
365
|
-
return simpleParameter(Param(set, value, undefined, range, param.labels));
|
|
366
|
-
}
|
|
343
|
+
return simpleParameter(Param(set, value, undefined, range, param.labels));
|
|
367
344
|
}
|
|
368
345
|
return null;
|
|
369
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;
|