react-graph-grid 0.0.0 → 0.0.1

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 CHANGED
@@ -1,16 +1,3 @@
1
- # React + Vite
1
+ # react-graph-grid
2
2
 
3
- This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4
-
5
- Currently, two official plugins are available:
6
-
7
- - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
8
- - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9
-
10
- ## React Compiler
11
-
12
- The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
13
-
14
- ## Expanding the ESLint configuration
15
-
16
- If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.
3
+ A React package containing a grid that can communicate with other grids through a connection graph
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "react-graph-grid",
3
3
  "author": "Mikhail Razumtsev",
4
- "description": "a React package containing a grid that can communicate with other grids through a connection graph",
4
+ "description": "A React package containing a grid that can communicate with other grids through a connection graph",
5
5
  "private": false,
6
- "version": "0.0.0",
6
+ "version": "0.0.1",
7
7
  "type": "module",
8
8
  "scripts": {
9
9
  "dev": "vite --port 4000",
package/src/Dropdown.jsx CHANGED
@@ -170,7 +170,7 @@ export class DropdownClass extends ModalClass {
170
170
  style={{ listStyleType: 'none', display: 'flex', flexWrap: 'nowrap' }}
171
171
  onClick={(e) => dd.onItemClick(e, 'append')}
172
172
  >
173
- ${dd.translate('more...')}
173
+ ${dd.translate('more') + '...'}
174
174
  </li>
175
175
  </ul>
176
176
  : <></>
@@ -0,0 +1,357 @@
1
+ /* eslint-disable no-mixed-operators */
2
+ import { useState, useEffect, useRef, useLayoutEffect } from 'react';
3
+ import { BaseComponent } from './Base';
4
+ import { Modal } from './Modal';
5
+ import { Images } from './Themes/Images';
6
+ import { GridFE } from './GridFE';
7
+ export function FieldEdit(props) {
8
+ let fe = null;
9
+
10
+ const [feState, setState] = useState({ fe: fe, ind: 0 });
11
+
12
+ fe = feState.fe;
13
+ if (!fe) {
14
+ if (props.findFieldEdit) {
15
+ fe = props.findFieldEdit();
16
+ }
17
+ fe = fe || new FieldEditClass(props);
18
+ }
19
+
20
+ fe.textareaRef = useRef(null);
21
+
22
+ fe.column = props.column;
23
+
24
+ if (fe.value == null) {
25
+ fe.value = props.value || '';
26
+ fe.text = props.text || '';
27
+ }
28
+
29
+ fe.getFilters = props.getFilters;
30
+
31
+ fe.multi = props.multi;
32
+
33
+ fe.id = props.keyPref || FieldEditClass._seq++;
34
+
35
+ fe.disabled = props.disabled;
36
+
37
+ BaseComponent.theme = BaseComponent.theme || {};
38
+
39
+ fe.buttonClass = props.buttonClass || BaseComponent.theme.filterButtonClass || '';
40
+ fe.inputClass = props.inputClass || BaseComponent.theme.inputClass || '';
41
+ fe.inputClassLG = props.inputClassLG || BaseComponent.theme.inputClassLG || '';
42
+ fe.clearButtonClass = props.clearButtonClass || BaseComponent.theme.clearButtonClass || '';
43
+ fe.selectClass = props.selectClass || BaseComponent.theme.selectClass || '';
44
+ fe.divContainerClass = props.divContainerClass || '';
45
+
46
+ fe.datePickerDateFormat = props.datePickerDateFormat || 'dd.MM.yyyy';
47
+
48
+ fe.w = props.w;
49
+ fe.maxW = props.maxW;
50
+ fe.h = fe.h || props.h || '1.6em';
51
+ fe.textareaH = props.textareaH || '2.1em';
52
+
53
+ if (props.init) {
54
+ props.init(fe);
55
+ }
56
+
57
+ fe.refreshState = function () {
58
+ setState({ fe: fe, ind: fe.stateind++ });
59
+ }
60
+
61
+ useEffect(() => {
62
+ return () => {
63
+ if (fe._resizeObserver && fe._resizeObservedObj) {
64
+ fe._resizeObserver.unobserve(fe._resizeObservedObj);
65
+ }
66
+ };
67
+ }, [fe])
68
+
69
+ useLayoutEffect(() => {
70
+ if (fe.textareaRef.current && fe._refocus) {
71
+ fe.textareaRef.current.focus();
72
+ if (fe.selectionStart > 0) {
73
+ fe.textareaRef.current.setSelectionRange(fe.selectionStart, fe.selectionStart);
74
+ }
75
+ fe._refocus = false;
76
+ }
77
+
78
+ const handleResize = () => {
79
+ //console.log('Textarea resized. New height:', fe.textareaRef.current.offsetHeight);
80
+ if (fe.textareaRef.current) {
81
+ fe.h = fe.textareaRef.current.offsetHeight;
82
+ }
83
+ };
84
+
85
+ if (fe.textareaRef.current && fe._resizeObserved !== fe.textareaRef.current) {
86
+ fe._resizeObserver = new ResizeObserver(handleResize);
87
+
88
+ if (fe._resizeObservedObj) {
89
+ fe._resizeObserver.unobserve(fe._resizeObservedObj);
90
+ }
91
+
92
+ fe._resizeObservedObj = fe.textareaRef.current;
93
+
94
+ fe._resizeObserver.observe(fe.textareaRef.current);
95
+ }
96
+ });
97
+
98
+ return (fe.render());
99
+ }
100
+
101
+ // ==================================================================================================================================================================
102
+ export class FieldEditClass extends BaseComponent {
103
+
104
+ constructor(props) {
105
+ super(props);
106
+
107
+ const fe = this;
108
+
109
+ fe.stateind = 0;
110
+ fe.id = props.keyPref || FieldEditClass._seq++;
111
+
112
+ fe.onChange = props.onChange || (() => { });
113
+
114
+ fe.selectionStart = 0;
115
+
116
+ fe.large = props.large;
117
+
118
+ // просто разметка 'span 2' etc.
119
+ fe.gridColumn = props.gridColumn;
120
+ }
121
+ // -------------------------------------------------------------------------------------------------------------------------------------------------------------
122
+ static _seq = 0;
123
+ static _autoFocusColumn;
124
+ // -------------------------------------------------------------------------------------------------------------------------------------------------------------
125
+ render() {
126
+ const fe = this;
127
+
128
+ const isLookup = fe.column.type === 'lookup';
129
+ const isReadonly = fe.column.readonly;
130
+ const hasValue = fe.value != null && fe.value !== '';
131
+ const noClear = fe.column.required || fe.column.readonly || !fe.multi && !hasValue;
132
+
133
+ const needFocus = FieldEditClass._autoFocusColumn === fe.column;
134
+ if (needFocus) {
135
+ //FieldEditClass._autoFocusColumn = null;
136
+ }
137
+
138
+ return (
139
+ <>
140
+ <div
141
+ key={`fieldEditDiv_${fe.id}_${fe.column.id}_`}
142
+ className={fe.divContainerClass ? fe.divContainerClass : fe.large ? 'field-edit' : isLookup && !isReadonly ? 'grid-cell-lookup' : 'grid-cell-edit'}
143
+ style={{
144
+ border: 'none',
145
+ height: !fe.inputClass ? fe.h : '',
146
+ width: '100%',
147
+ display: 'grid',
148
+ gridColumn: fe.gridColumn || '',
149
+ gridTemplateColumns: fe.large ? 'calc(100% - 4.6em) 2.2em 2.2em' : 'calc(100% - 2.8em) 1.4em 1.4em',
150
+ maxWidth: fe.maxW ? fe.maxW : '',
151
+ minHeight: fe.large ? '2.5em' : '',
152
+ columnGap: fe.large ? '0.2em' : '',
153
+ alignItems: 'center',
154
+ justifyItems: 'center',
155
+ }}
156
+ >
157
+ {
158
+ isLookup && !isReadonly ?
159
+ <>
160
+ <input
161
+ key={`fieldLookupTitle_${fe.id}_${fe.column.id}_`}
162
+ style={{
163
+ width: '100%',
164
+ gridColumn: noClear ? !fe.comboboxValues ? 'span 2' : 'span 3' : 'span 1',
165
+ overflowX: 'hidden',
166
+ height: !fe.large ? '1.7em' : '2.2em',
167
+ minHeight: !fe.inputClass ? fe.textareaH : fe.h,
168
+ boxSizing: 'border-box',
169
+ }}
170
+ disabled={true}
171
+ className={fe.large ? fe.inputClassLG : fe.inputClass || ''}
172
+ value={!hasValue ? '' : fe.text != null && fe.text !== '' ? fe.text : fe.value}
173
+ >
174
+ </input>
175
+ <button
176
+ key={`fieldLookupButton_${fe.id}_${fe.column.id}_`}
177
+ className={`${fe.large ? 'graph-filter-button' : 'grid-cell-button'} ${fe.clearButtonClass}`}
178
+ style={{ width: !fe.large ? '1.6em' : '', height: !fe.large ? '1.6em' : '' }}
179
+ onClick={(e) => {
180
+ fe.openLookupField(e);
181
+ }}
182
+ disabled={fe.disabled}
183
+ >
184
+ {!fe.large ? '...' : Images.images.filterSelect()}
185
+ </button>
186
+ </>
187
+ : //autoFocus={needFocus}
188
+ <textarea
189
+ key={`fieldTextarea_${fe.id}_${fe.column.id}_`}
190
+ ref={fe.textareaRef}
191
+ className={`${fe.large ? fe.inputClassLG : fe.inputClass}`}
192
+ value={isLookup ? fe.text : fe.value || ''}
193
+ style={{
194
+ width: noClear ? 'calc(100% - 2px)' : '100%',
195
+ minHeight: !fe.inputClass ? fe.textareaH : fe.minH,
196
+ height: fe.h ? fe.h : !fe.large ? '1.7em' : '2.2em',
197
+ padding: '0',
198
+ boxSizing: 'border-box',
199
+ gridColumn: noClear ? 'span 3' : 'span 2',
200
+ resize: 'vertical',
201
+ overflowX: 'hidden',
202
+ }}
203
+ onChange={(e) => {
204
+ e.value = e.text = e.target.value;
205
+ fe.value = fe.text = e.target.value;
206
+ e.fe = fe;
207
+ FieldEditClass._autoFocusColumn = fe.column;
208
+
209
+ fe.selectionStart = e.target.selectionStart;
210
+ fe._refocus = true;
211
+
212
+ fe.onChange(e);
213
+ fe.refreshState();
214
+ }}
215
+ disabled={fe.disabled || fe.column.readonly}
216
+ >
217
+ </textarea>
218
+ }
219
+ {
220
+ noClear || fe.column.readonly ? <></>
221
+ :
222
+ <button
223
+ key={`fieldClearButton_${fe.id}_${fe.column.id}_`}
224
+ className={`${fe.large ? 'graph-filter-clear' : 'grid-cell-button'} ${fe.clearButtonClass || ''}`}
225
+ style={{ width: !fe.large ? '1.6em' : '', height: !fe.large ? '1.6em' : '' }}
226
+ onClick={(e) => {
227
+ e.value = e.text = '';
228
+ fe.value = fe.text = '';
229
+ e.fe = fe;
230
+ fe.onChange(e);
231
+ fe.refreshState();
232
+ }}
233
+ disabled={fe.disabled}
234
+ >
235
+ {!fe.large ? '×' : Images.images.filterClear()}
236
+ </button>
237
+
238
+ }
239
+ </div >
240
+ {
241
+ fe.lookupIsShowing ?
242
+ <Modal
243
+ title={fe.column.title}
244
+ renderContent={(wnd) => { return fe.renderLookupGrid(wnd); }}
245
+ pos={fe.popupPos}
246
+ onClose={(e) => {
247
+ if (fe.grid) {
248
+ delete fe.grid.value;
249
+ }
250
+ fe.closeLookupField();
251
+ fe.refreshState();
252
+ }}
253
+ >
254
+ </Modal>
255
+ :
256
+ <></>
257
+ }
258
+ </>
259
+ )
260
+ }
261
+ // -------------------------------------------------------------------------------------------------------------------------------------------------------------
262
+ renderLookupGrid(wnd) {
263
+ const fe = this;
264
+
265
+ return (
266
+ <GridFE
267
+ getRows={fe.column.getRows}
268
+ keyField={fe.column.refKeyField}
269
+ nameField={fe.column.refNameField}
270
+ activeRow={fe.value}
271
+ multi={fe.multi}
272
+ level={fe.level + 1}
273
+ findGrid={() => { return fe.grid; }}
274
+ onSelectValue={(e) => {
275
+ fe._selectedOptions = e.values || [];
276
+
277
+ fe.value = e.value;
278
+ fe.text = e.text;
279
+
280
+ e.fe = fe;
281
+
282
+ fe.closeLookupField();
283
+ fe.onChange(e);
284
+ fe.refreshState();
285
+ }}
286
+ init={(lookupGrid) => {
287
+ if (!lookupGrid.value || fe.value && fe.value !== lookupGrid.value) {
288
+ fe.onLookupGridInit(lookupGrid);
289
+
290
+ delete lookupGrid._selectedRows;
291
+ if (fe.value) {
292
+ lookupGrid._selectedRowsDict = {};
293
+ for (let opt of fe._selectedOptions) {
294
+ let fakeRow = {};
295
+ fakeRow[fe.column.refKeyField] = opt.value;
296
+ fakeRow[fe.column.refNameField] = opt.label;
297
+ lookupGrid._selectedRowsDict[opt.value] = fakeRow;
298
+ }
299
+ }
300
+ }
301
+
302
+ lookupGrid.closeSelfWnd = () => {
303
+ fe.closeLookupField();
304
+ fe.refreshState();
305
+ };
306
+ }}
307
+ onClose={() => {
308
+ fe.closeLookupField();
309
+ }}
310
+ >
311
+ </GridFE>
312
+ );
313
+ }
314
+ // -------------------------------------------------------------------------------------------------------------------------------------------------------------
315
+ closeLookupField() {
316
+ const fe = this;
317
+ fe.lookupIsShowing = false;
318
+ if (fe.ownerGrid) {
319
+ fe.ownerGrid._clicksDisabled = false;
320
+ }
321
+ }
322
+ // -------------------------------------------------------------------------------------------------------------------------------------------------------------
323
+ openLookupField(e) {
324
+ const fe = this;
325
+
326
+ const shift = (fe.level + 1) * 20;
327
+
328
+ fe.popupPos = fe.popupPos || { x: 100 + shift, y: 100 + shift, w: 1700, h: 900 };
329
+
330
+ fe.lookupIsShowing = true;
331
+ if (fe.ownerGrid) {
332
+ fe.ownerGrid._clicksDisabled = true;
333
+ }
334
+
335
+ fe.refreshState();
336
+ }
337
+ // -------------------------------------------------------------------------------------------------------------------------------------------------------------
338
+ onLookupGridInit(grid) {
339
+ const fe = this;
340
+ fe.grid = grid;
341
+ grid.value = fe.value;
342
+ grid.getSelectedRowIndex();
343
+
344
+ if (fe.getFilters) {
345
+ grid.collectFilters = fe.getFilters;
346
+ }
347
+
348
+ if (grid._lookupPrepared) return;
349
+
350
+ grid._lookupPrepared = true;
351
+
352
+ grid.visible = true;
353
+ grid.title = fe.column.title;
354
+ grid.isSelecting = true;
355
+ };
356
+ // -------------------------------------------------------------------------------------------------------------------------------------------------------------
357
+ }
package/src/Graph.jsx CHANGED
@@ -128,9 +128,6 @@ export class GraphClass {
128
128
 
129
129
  const graph = this;
130
130
 
131
- // выставляем у графа признак "пущена волна"
132
- graph._isMakingWave = true;
133
-
134
131
  if (e.prepared == null) {
135
132
  if (e.waveType == null) {
136
133
  e.waveType = WaveType.value;
@@ -146,6 +143,9 @@ export class GraphClass {
146
143
  e.prepared = true;
147
144
  }
148
145
 
146
+ // выставляем у графа признак "пущена волна"
147
+ graph._isMakingWave = true;
148
+
149
149
  // если запущена новая однотипная волна, то нет смысла продолжать текущую
150
150
  if (graph.lastWaveInds[e.waveType] != null && e.waveInd < graph.lastWaveInds[e.waveType]) return;
151
151
 
@@ -163,13 +163,20 @@ export class GraphClass {
163
163
 
164
164
  if (graph.lastWaveInds[e.waveType] == e.waveInd && e.nodes.length <= 0) {
165
165
  graph._isMakingWave = false;
166
+
167
+ if (e.afterAllVisited) {
168
+ e.afterAllVisited();
169
+ }
166
170
  return;
167
171
  }
168
172
 
169
173
  let i = 0;
170
174
  while (i < e.nodes.length) {
171
175
  let node = e.nodes[i];
172
- node._lastWaveInd = e.waveInd;
176
+ if (node._lastWaveInd == e.waveInd) {
177
+ i++;
178
+ continue;
179
+ }
173
180
 
174
181
  // если текущий узел не должен посещаться текущей волной
175
182
  if (node.skipOnWaveVisit && node.skipOnWaveVisit(e)) {
@@ -187,19 +194,41 @@ export class GraphClass {
187
194
  node.visited = true;
188
195
  }
189
196
 
190
- e.nodes.splice(i, 1);
197
+ node._lastWaveInd = e.waveInd;
191
198
 
192
199
  if (node.visitByWave) {
200
+ i++;
193
201
  node.visitByWave(e).then(() => {
194
202
  graph.addChildrenToWave([node], e);
195
203
 
204
+ const index = e.nodes.indexOf(node);
205
+
206
+ if (index > -1) {
207
+ e.nodes.splice(index, 1);
208
+ }
209
+
196
210
  graph.triggerWave(e);
197
211
 
198
212
  if (graph.lastWaveInds[e.waveType] == e.waveInd && e.nodes.length <= 0) {
199
213
  graph._isMakingWave = false;
214
+
215
+ if (e.afterAllVisited) {
216
+ e.afterAllVisited();
217
+ }
200
218
  }
201
219
  });
202
220
  }
221
+ else {
222
+ e.nodes.splice(i, 1);
223
+
224
+ if (graph.lastWaveInds[e.waveType] == e.waveInd && e.nodes.length <= 0) {
225
+ graph._isMakingWave = false;
226
+
227
+ if (e.afterAllVisited) {
228
+ e.afterAllVisited();
229
+ }
230
+ }
231
+ }
203
232
  }
204
233
  }
205
234
  // -------------------------------------------------------------------------------------------------------------------------------------------------------------
package/src/Grid.jsx CHANGED
@@ -85,7 +85,6 @@ export class GridClass extends BaseComponent {
85
85
  grid.keyField = props.keyField;
86
86
  grid.nameField = props.nameField;
87
87
 
88
- //grid._selectedRowsDict = {};
89
88
  if (props.renderCell) {
90
89
  grid.defaultRenderCell = grid.renderCell;
91
90
  grid.renderCell = props.renderCell;
@@ -164,6 +163,7 @@ export class GridClass extends BaseComponent {
164
163
  rows => {
165
164
  grid.rows = rows;
166
165
  grid.afterGetRows();
166
+ grid._waitingRows = false;
167
167
  grid.refreshState();
168
168
  }
169
169
  ).finally(() => {
@@ -420,11 +420,19 @@ export class GridClass extends BaseComponent {
420
420
  renderCell(grid, col, row) {
421
421
  let val = row[col.name];
422
422
 
423
- if (col.type === 'date' && val) {
424
- val = grid.formatDate(val, grid.dateFormat);
423
+ if (col.type === 'date' && val != null) {
424
+ try {
425
+ val = grid.formatDate(val, grid.dateFormat);
426
+ }
427
+ catch {
428
+ }
425
429
  }
426
- else if (col.type === 'datetime' && val) {
427
- val = grid.formatDate(val, grid.dateTimeFormat);
430
+ else if (col.type === 'datetime' && val != null) {
431
+ try {
432
+ val = grid.formatDate(val, grid.dateTimeFormat);
433
+ }
434
+ catch {
435
+ }
428
436
  }
429
437
 
430
438
  if (col.allowVerticalResize) {
package/src/GridDB.jsx CHANGED
@@ -690,7 +690,7 @@ export class GridDBClass extends GridPKClass {
690
690
  // -------------------------------------------------------------------------------------------------------------------------------------------------------------
691
691
  setPocketImage() {
692
692
  const grid = this;
693
- if (!grid.multi) return;
693
+ if (!grid.multi || !grid.pagerButtonsDict) return;
694
694
 
695
695
  const pocket = grid.pagerButtonsDict['pocket'];
696
696
  if (!pocket) return;