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.
Files changed (77) hide show
  1. package/README.md +383 -382
  2. package/lib/common/api.d.ts +28 -0
  3. package/lib/common/api.js +41 -16
  4. package/lib/common/src/components/Buttons/MiniButton.d.ts +1 -0
  5. package/lib/common/src/components/Buttons/MiniButton.js +2 -2
  6. package/lib/common/src/components/Dnd/DraggableOutlineDiv.d.ts +1 -0
  7. package/lib/common/src/components/Dnd/DraggableOutlineDiv.js +10 -7
  8. package/lib/common/src/components/Dnd/RNDFunc.js +28 -17
  9. package/lib/common/src/components/Dnd/RNDFunc3.d.ts +27 -17
  10. package/lib/common/src/components/Dnd/RNDFunc3.js +107 -107
  11. package/lib/common/src/components/Dnd/Resizable.js +3 -3
  12. package/lib/common/src/components/Input.js +3 -3
  13. package/lib/common/src/components/Menu/RightMenu.js +17 -18
  14. package/lib/common/src/components/Menu/StickerMenu.d.ts +1 -3
  15. package/lib/common/src/components/Menu/StickerMenu.js +42 -28
  16. package/lib/common/src/components/Menu/index.d.ts +1 -1
  17. package/lib/common/src/components/Menu/index.js +1 -1
  18. package/lib/common/src/components/Modal/LeftModal.d.ts +14 -12
  19. package/lib/common/src/components/Modal/LeftModal.js +34 -52
  20. package/lib/common/src/components/Modal/Modal.d.ts +26 -12
  21. package/lib/common/src/components/Modal/Modal.js +66 -89
  22. package/lib/common/src/components/Modal/ModalContextProvider.d.ts +20 -3
  23. package/lib/common/src/components/Modal/ModalContextProvider.js +35 -8
  24. package/lib/common/src/components/MyResizeObserver.js +15 -18
  25. package/lib/common/src/components/Other.js +6 -5
  26. package/lib/common/src/components/Parameters.js +8 -7
  27. package/lib/common/src/components/ParametersEngine.js +36 -59
  28. package/lib/common/src/grid/agGrid4/agGrid4.d.ts +53 -0
  29. package/lib/common/src/grid/agGrid4/agGrid4.js +132 -0
  30. package/lib/common/src/grid/agGrid4/core.d.ts +56 -0
  31. package/lib/common/src/grid/agGrid4/core.js +127 -0
  32. package/lib/common/src/grid/agGrid4/index.d.ts +6 -0
  33. package/lib/common/src/grid/agGrid4/index.js +3 -0
  34. package/lib/common/src/grid/agGrid4/theme.d.ts +7 -0
  35. package/lib/common/src/grid/agGrid4/theme.js +18 -0
  36. package/lib/common/src/hooks/useAddDownAnyKey.d.ts +21 -2
  37. package/lib/common/src/hooks/useAddDownAnyKey.js +46 -9
  38. package/lib/common/src/hooks/useDraggable.d.ts +20 -2
  39. package/lib/common/src/hooks/useDraggable.js +146 -115
  40. package/lib/common/src/hooks/useOutside.d.ts +24 -4
  41. package/lib/common/src/hooks/useOutside.js +77 -26
  42. package/lib/common/src/logs/logs.d.ts +5 -2
  43. package/lib/common/src/logs/logs.js +18 -34
  44. package/lib/common/src/logs/logs3.d.ts +9 -9
  45. package/lib/common/src/logs/logs3.js +48 -39
  46. package/lib/common/src/logs/miniLogs.d.ts +3 -3
  47. package/lib/common/src/logs/miniLogs.js +28 -36
  48. package/lib/common/src/menu/menu.d.ts +18 -18
  49. package/lib/common/src/menu/menu.js +49 -25
  50. package/lib/common/src/menu/menuMouse.d.ts +2 -1
  51. package/lib/common/src/menu/menuMouse.js +5 -4
  52. package/lib/common/src/menu/menuR.d.ts +4 -3
  53. package/lib/common/src/menu/menuR.js +42 -38
  54. package/lib/common/src/myChart/1/myChart.d.ts +5 -5
  55. package/lib/common/src/myChart/1/myChart.js +58 -47
  56. package/lib/common/src/myChart/1/myChartTest.js +8 -4
  57. package/lib/common/src/myChart/chartEngine/chartEngineReact.d.ts +24 -27
  58. package/lib/common/src/myChart/chartEngine/chartEngineReact.js +189 -118
  59. package/lib/common/src/styles/styleGrid.d.ts +2 -1
  60. package/lib/common/src/styles/styleGrid.js +10 -12
  61. package/lib/common/src/styles/tokens.d.ts +39 -0
  62. package/lib/common/src/styles/tokens.js +40 -0
  63. package/lib/common/src/utils/applyTransactionAsyncUpdate.d.ts +7 -0
  64. package/lib/common/src/utils/applyTransactionAsyncUpdate.js +25 -19
  65. package/lib/common/src/utils/arrayPromise.d.ts +6 -0
  66. package/lib/common/src/utils/arrayPromise.js +7 -1
  67. package/lib/common/src/utils/cache.js +32 -24
  68. package/lib/common/src/utils/inputAutoStep.d.ts +1 -1
  69. package/lib/common/src/utils/inputAutoStep.js +30 -19
  70. package/lib/common/src/utils/mapMemory.js +15 -15
  71. package/lib/common/updateBy.d.ts +21 -2
  72. package/lib/common/updateBy.js +66 -131
  73. package/lib/index.js +0 -2
  74. package/lib/style/menuRight.css +13 -26
  75. package/lib/style/style.css +51 -39
  76. package/lib/style/tokens.css +34 -0
  77. 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
- let id = new class {
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
- //console.log("###",el.style.width, el.clientWidth);
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
- if (width < 10 || width > defaultWidth)
79
- break;
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
- //console.log(i+": "+numEl.getBoundingClientRect().right,"<", parentParent.getBoundingClientRect().right, rangeEl.clientWidth); //numEl.clientLeft, numEl.clientWidth);
82
- i++;
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
- }, [true]);
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?.(t);
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
- }, [true]);
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] = z;
26
+ params[i] = e;
27
27
  setParams(params);
28
28
  } }, i)), onSave && _jsx("div", { className: "msTradeActive msTradeAlt", onClick: async () => {
29
- params && onSave?.(params);
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?.length && (_jsx("div", { className: "commentary", style: {
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
- //класс наведение на объект, если на вели на оболочку над children то появиться onFocusUp/focusDw
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.01.01"), max: toNum(range?.defaultMax ?? range?.max ?? new Date().toString()), step: range?.defaultStep ?? step, value: toNum(value), onInput: (e) => {
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
- const target = e.currentTarget;
86
- setVal(Number(target.value));
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
- const current = e.currentTarget;
128
- set(Number(current.value));
113
+ set(Number(e.currentTarget.value));
129
114
  if (_ref)
130
- _ref.step = current.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
- const params = useRef(null);
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 = data.params;
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
- useEffect(() => {
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
- Refresh();
200
- }, [p]);
201
- let timeoutId = null;
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;