@linzjs/step-ag-grid 7.11.7 → 7.11.9

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.
@@ -1,7 +1,7 @@
1
- import { ReactElement, ReactNode, useContext, useRef, useState } from "react";
1
+ import { ReactElement, ReactNode, useCallback, useContext, useRef, useState } from "react";
2
2
  import { ColDef, GridApi, RowNode } from "ag-grid-community";
3
3
  import { GridContext } from "./GridContext";
4
- import { delay, difference, isEmpty, last, sortBy } from "lodash-es";
4
+ import { defer, delay, difference, isEmpty, last, sortBy } from "lodash-es";
5
5
  import { isNotEmpty, wait } from "../utils/util";
6
6
  import { GridUpdatingContext } from "./GridUpdatingContext";
7
7
  import { GridBaseRow } from "../components/Grid";
@@ -22,10 +22,10 @@ export const GridContextProvider = (props: GridContextProps): ReactElement => {
22
22
  const idsBeforeUpdate = useRef<number[]>([]);
23
23
  const externallySelectedItemsAreInSync = useRef(false);
24
24
 
25
- const setGridApi = (gridApi: GridApi | undefined) => {
25
+ const setGridApi = useCallback((gridApi: GridApi | undefined) => {
26
26
  _setGridApi(gridApi);
27
27
  setGridReady(!!gridApi);
28
- };
28
+ }, []);
29
29
 
30
30
  /**
31
31
  * Wraps things that require gridApi in common handling, for when gridApi not present.
@@ -33,27 +33,30 @@ export const GridContextProvider = (props: GridContextProps): ReactElement => {
33
33
  * @param hasApiFn Execute when api is ready.
34
34
  * @param noApiFn Execute if api is not ready.
35
35
  */
36
- const gridApiOp = <T extends unknown, R extends unknown>(
37
- hasApiFn: (gridApi: GridApi) => T,
38
- noApiFn?: () => R,
39
- ): T | R => {
40
- if (!noApiFn) {
41
- noApiFn = (() => {}) as () => R;
42
- }
43
- return gridApi ? hasApiFn(gridApi) : noApiFn();
44
- };
36
+ const gridApiOp = useCallback(
37
+ <T extends unknown, R extends unknown>(hasApiFn: (gridApi: GridApi) => T, noApiFn?: () => R): T | R => {
38
+ if (!noApiFn) {
39
+ noApiFn = (() => {}) as () => R;
40
+ }
41
+ return gridApi ? hasApiFn(gridApi) : noApiFn();
42
+ },
43
+ [gridApi],
44
+ );
45
45
 
46
46
  /**
47
47
  * Set the quick filter value to grid.
48
48
  */
49
- const setQuickFilter = (quickFilter: string) => {
50
- gridApiOp((gridApi) => gridApi.setQuickFilter(quickFilter));
51
- };
49
+ const setQuickFilter = useCallback(
50
+ (quickFilter: string) => {
51
+ gridApiOp((gridApi) => gridApi.setQuickFilter(quickFilter));
52
+ },
53
+ [gridApiOp],
54
+ );
52
55
 
53
56
  /**
54
57
  * Get all row id's in grid.
55
58
  */
56
- const _getAllRowIds = () => {
59
+ const _getAllRowIds = useCallback(() => {
57
60
  const result: number[] = [];
58
61
  return gridApiOp(
59
62
  (gridApi) => {
@@ -62,20 +65,20 @@ export const GridContextProvider = (props: GridContextProps): ReactElement => {
62
65
  },
63
66
  () => result,
64
67
  );
65
- };
68
+ }, [gridApiOp]);
66
69
 
67
70
  /**
68
71
  * Record all row id's before update so that we can select/flash the new rows after update.
69
72
  */
70
- const beforeUpdate = () => {
73
+ const beforeUpdate = useCallback(() => {
71
74
  idsBeforeUpdate.current = _getAllRowIds();
72
- };
75
+ }, [_getAllRowIds]);
73
76
 
74
77
  /**
75
78
  * Find new row ids
76
79
  * Uses beforeUpdate ids to find new nodes.
77
80
  */
78
- const _getNewNodes = (): RowNode[] => {
81
+ const _getNewNodes = useCallback((): RowNode[] => {
79
82
  return gridApiOp(
80
83
  (gridApi) =>
81
84
  difference(_getAllRowIds(), idsBeforeUpdate.current)
@@ -83,7 +86,7 @@ export const GridContextProvider = (props: GridContextProps): ReactElement => {
83
86
  .filter((r) => r) as RowNode[],
84
87
  () => [],
85
88
  );
86
- };
89
+ }, [_getAllRowIds, gridApiOp]);
87
90
 
88
91
  /**
89
92
  * Get grid nodes via rowIds. If rowIds has no matching node the result may be smaller than expected.
@@ -91,15 +94,18 @@ export const GridContextProvider = (props: GridContextProps): ReactElement => {
91
94
  *
92
95
  * @param rowIds Row ids to get from grid.
93
96
  */
94
- const _rowIdsToNodes = (rowIds: number[]): RowNode[] => {
95
- return gridApiOp(
96
- (gridApi) =>
97
- rowIds
98
- .map((rowId) => gridApi.getRowNode("" + rowId)) //
99
- .filter((r) => r) as RowNode[],
100
- () => [] as RowNode[],
101
- );
102
- };
97
+ const _rowIdsToNodes = useCallback(
98
+ (rowIds: number[]): RowNode[] => {
99
+ return gridApiOp(
100
+ (gridApi) =>
101
+ rowIds
102
+ .map((rowId) => gridApi.getRowNode("" + rowId)) //
103
+ .filter((r) => r) as RowNode[],
104
+ () => [] as RowNode[],
105
+ );
106
+ },
107
+ [gridApiOp],
108
+ );
103
109
 
104
110
  /**
105
111
  * Internal method for selecting and flashing rows.
@@ -109,212 +115,251 @@ export const GridContextProvider = (props: GridContextProps): ReactElement => {
109
115
  * @param flash Whether to flash rows
110
116
  * @param retryCount Table updates may not be present when this is called, so retry is needed.
111
117
  */
112
- const _selectRowsWithOptionalFlash = (
113
- rowIds: number[] | undefined,
114
- select: boolean,
115
- flash: boolean,
116
- retryCount = 15, // We retry for approximately 5x200ms=1s
117
- ) => {
118
- return gridApiOp((gridApi) => {
119
- const rowNodes = rowIds ? _rowIdsToNodes(rowIds) : _getNewNodes();
120
- const gridRowIdsNotUpdatedYet = rowIds && rowNodes.length !== rowIds.length; // rowIds are specified
121
- const gridRowIdsNotChangedYet = !rowIds && isEmpty(rowNodes); // rowIds are from beforeUpdate
122
- const gridHasNotUpdated = gridRowIdsNotUpdatedYet || gridRowIdsNotChangedYet;
123
- // After retry count expires we give-up and deselect all rows, then select any subset of rows that have updated
124
- if (gridHasNotUpdated && retryCount > 0) {
125
- delay(() => _selectRowsWithOptionalFlash(rowIds, select, flash, retryCount - 1), 250);
126
- return;
127
- }
118
+ const _selectRowsWithOptionalFlash = useCallback(
119
+ (
120
+ rowIds: number[] | undefined,
121
+ select: boolean,
122
+ flash: boolean,
123
+ retryCount = 15, // We retry for approximately 5x200ms=1s
124
+ ) => {
125
+ return gridApiOp((gridApi) => {
126
+ const rowNodes = rowIds ? _rowIdsToNodes(rowIds) : _getNewNodes();
127
+ const gridRowIdsNotUpdatedYet = rowIds && rowNodes.length !== rowIds.length; // rowIds are specified
128
+ const gridRowIdsNotChangedYet = !rowIds && isEmpty(rowNodes); // rowIds are from beforeUpdate
129
+ const gridHasNotUpdated = gridRowIdsNotUpdatedYet || gridRowIdsNotChangedYet;
130
+ // After retry count expires we give-up and deselect all rows, then select any subset of rows that have updated
131
+ if (gridHasNotUpdated && retryCount > 0) {
132
+ delay(() => _selectRowsWithOptionalFlash(rowIds, select, flash, retryCount - 1), 250);
133
+ return;
134
+ }
128
135
 
129
- const rowsThatNeedSelecting = sortBy(
130
- rowNodes.filter((node) => !node.isSelected()),
131
- (node) => node.data.id,
132
- );
133
- const firstNode = rowsThatNeedSelecting[0];
134
- if (firstNode) {
135
- gridApi.ensureNodeVisible(firstNode);
136
- const colDefs = gridApi.getColumnDefs();
137
- if (colDefs && colDefs.length) {
138
- const col = colDefs[0] as ColDef; // We don't support ColGroupDef
139
- const rowIndex = firstNode.rowIndex;
140
- if (rowIndex != null && col != null) {
141
- const colId = col.colId;
142
- colId != null && setTimeout(() => gridApi.setFocusedCell(rowIndex, colId), 100);
136
+ const rowsThatNeedSelecting = sortBy(
137
+ rowNodes.filter((node) => !node.isSelected()),
138
+ (node) => node.data.id,
139
+ );
140
+ const firstNode = rowsThatNeedSelecting[0];
141
+ if (firstNode) {
142
+ gridApi.ensureNodeVisible(firstNode);
143
+ const colDefs = gridApi.getColumnDefs();
144
+ if (colDefs && colDefs.length) {
145
+ const col = colDefs[0] as ColDef; // We don't support ColGroupDef
146
+ const rowIndex = firstNode.rowIndex;
147
+ if (rowIndex != null && col != null) {
148
+ const colId = col.colId;
149
+ // We need to make sure we aren't currently editing a cell otherwise tests will fail
150
+ // as they will start to edit the cell before this stuff has a chance to run
151
+ colId != null &&
152
+ defer(() => isEmpty(gridApi.getEditingCells()) && gridApi.setFocusedCell(rowIndex, colId));
153
+ }
143
154
  }
144
155
  }
145
- }
146
- if (select) {
147
- // Select rows that shouldn't be selected
148
- rowsThatNeedSelecting.forEach((node) => node.setSelected(true));
149
- // Unselect rows that shouldn't be selected
150
- gridApi.getSelectedNodes()?.forEach((node) => {
151
- if (node && !rowNodes.includes(node)) {
152
- node.setSelected(false);
153
- }
154
- });
155
- }
156
- if (flash) {
157
- delay(() => {
158
- try {
159
- gridApi.flashCells({ rowNodes });
160
- } catch {
161
- // ignore, flash cells sometimes throws errors as nodes have gone out of scope
162
- }
163
- }, 250);
164
- }
165
- });
166
- };
167
-
168
- const selectRowsById = (rowIds?: number[]) => _selectRowsWithOptionalFlash(rowIds, true, false);
169
- const selectRowsByIdWithFlash = (rowIds?: number[]) => _selectRowsWithOptionalFlash(rowIds, true, true);
170
- const flashRows = (rowIds?: number[]) => _selectRowsWithOptionalFlash(rowIds, false, true);
171
-
172
- const selectRowsDiff = async (fn: () => Promise<any>) => {
173
- beforeUpdate();
174
- await fn();
175
- _selectRowsWithOptionalFlash(undefined, true, false);
176
- };
177
-
178
- const selectRowsWithFlashDiff = async (fn: () => Promise<any>) => {
179
- beforeUpdate();
180
- await fn();
181
- _selectRowsWithOptionalFlash(undefined, true, true);
182
- };
183
-
184
- const flashRowsDiff = async (fn: () => Promise<any>) => {
185
- beforeUpdate();
186
- await fn();
187
- _selectRowsWithOptionalFlash(undefined, false, true);
188
- };
189
-
190
- const getSelectedRows = <T extends unknown>(): T[] => {
156
+ if (select) {
157
+ // Select rows that shouldn't be selected
158
+ rowsThatNeedSelecting.forEach((node) => node.setSelected(true));
159
+ // Unselect rows that shouldn't be selected
160
+ gridApi.getSelectedNodes()?.forEach((node) => {
161
+ if (node && !rowNodes.includes(node)) {
162
+ node.setSelected(false);
163
+ }
164
+ });
165
+ }
166
+ if (flash) {
167
+ delay(() => {
168
+ try {
169
+ gridApi.flashCells({ rowNodes });
170
+ } catch {
171
+ // ignore, flash cells sometimes throws errors as nodes have gone out of scope
172
+ }
173
+ }, 250);
174
+ }
175
+ });
176
+ },
177
+ [_getNewNodes, _rowIdsToNodes, gridApiOp],
178
+ );
179
+
180
+ const selectRowsById = useCallback(
181
+ (rowIds?: number[]) => _selectRowsWithOptionalFlash(rowIds, true, false),
182
+ [_selectRowsWithOptionalFlash],
183
+ );
184
+ const selectRowsByIdWithFlash = useCallback(
185
+ (rowIds?: number[]) => _selectRowsWithOptionalFlash(rowIds, true, true),
186
+ [_selectRowsWithOptionalFlash],
187
+ );
188
+ const flashRows = useCallback(
189
+ (rowIds?: number[]) => _selectRowsWithOptionalFlash(rowIds, false, true),
190
+ [_selectRowsWithOptionalFlash],
191
+ );
192
+
193
+ const selectRowsDiff = useCallback(
194
+ async (fn: () => Promise<any>) => {
195
+ beforeUpdate();
196
+ await fn();
197
+ _selectRowsWithOptionalFlash(undefined, true, false);
198
+ },
199
+ [_selectRowsWithOptionalFlash, beforeUpdate],
200
+ );
201
+
202
+ const selectRowsWithFlashDiff = useCallback(
203
+ async (fn: () => Promise<any>) => {
204
+ beforeUpdate();
205
+ await fn();
206
+ _selectRowsWithOptionalFlash(undefined, true, true);
207
+ },
208
+ [_selectRowsWithOptionalFlash, beforeUpdate],
209
+ );
210
+
211
+ const flashRowsDiff = useCallback(
212
+ async (fn: () => Promise<any>) => {
213
+ beforeUpdate();
214
+ await fn();
215
+ _selectRowsWithOptionalFlash(undefined, false, true);
216
+ },
217
+ [_selectRowsWithOptionalFlash, beforeUpdate],
218
+ );
219
+
220
+ const getSelectedRows = useCallback(<T extends unknown>(): T[] => {
191
221
  return gridApiOp(
192
222
  (gridApi) => gridApi.getSelectedRows(),
193
223
  () => [],
194
224
  );
195
- };
225
+ }, [gridApiOp]);
196
226
 
197
- const getSelectedRowIds = (): number[] => getSelectedRows().map((row) => (row as any).id as number);
227
+ const getSelectedRowIds = useCallback(
228
+ (): number[] => getSelectedRows().map((row) => (row as any).id as number),
229
+ [getSelectedRows],
230
+ );
198
231
 
199
- const editingCells = (): boolean => {
232
+ const editingCells = useCallback((): boolean => {
200
233
  return gridApiOp(
201
234
  (gridApi) => isNotEmpty(gridApi.getEditingCells()),
202
235
  () => false,
203
236
  );
204
- };
205
-
206
- const ensureRowVisible = (id: number | string): boolean => {
207
- return gridApiOp((gridApi) => {
208
- const node = gridApi.getRowNode(`${id}`);
209
- if (!node) return false;
210
- gridApi.ensureNodeVisible(node);
211
- return true;
212
- });
213
- };
237
+ }, [gridApiOp]);
238
+
239
+ const ensureRowVisible = useCallback(
240
+ (id: number | string): boolean => {
241
+ return gridApiOp((gridApi) => {
242
+ const node = gridApi.getRowNode(`${id}`);
243
+ if (!node) return false;
244
+ gridApi.ensureNodeVisible(node);
245
+ return true;
246
+ });
247
+ },
248
+ [gridApiOp],
249
+ );
214
250
 
215
251
  /**
216
252
  * Scroll last selected row into view on grid sort change
217
253
  */
218
- const ensureSelectedRowIsVisible = (): void => {
254
+ const ensureSelectedRowIsVisible = useCallback((): void => {
219
255
  gridApiOp((gridApi) => {
220
256
  const selectedNodes = gridApi.getSelectedNodes();
221
257
  if (isEmpty(selectedNodes)) return;
222
258
  gridApi.ensureNodeVisible(last(selectedNodes));
223
259
  });
224
- };
260
+ }, [gridApiOp]);
225
261
 
226
262
  /**
227
263
  * Resize columns to fit container
228
264
  */
229
- const sizeColumnsToFit = (): void => {
265
+ const sizeColumnsToFit = useCallback((): void => {
230
266
  gridApiOp((gridApi) => {
231
267
  // Hide size columns to fit errors in tests
232
268
  document.body.clientWidth && gridApi.sizeColumnsToFit();
233
269
  });
234
- };
235
-
236
- const stopEditing = (): void => gridApiOp((gridApi) => gridApi.stopEditing());
237
-
238
- const updatingCells = async (
239
- props: { selectedRows: GridBaseRow[]; field?: string },
240
- fnUpdate: (selectedRows: any[]) => Promise<boolean>,
241
- setSaving?: (saving: boolean) => void,
242
- tabDirection?: 1 | 0 | -1,
243
- ): Promise<boolean> => {
244
- setSaving && setSaving(true);
245
- return await gridApiOp(async (gridApi) => {
246
- const preOpCell = gridApi.getFocusedCell();
247
-
248
- const selectedRows = props.selectedRows;
249
-
250
- let ok = false;
251
- await modifyUpdating(
252
- props.field ?? "",
253
- selectedRows.map((data) => data.id),
254
- async () => {
255
- // Need to refresh to get spinners to work on all rows
256
- gridApi.refreshCells({ rowNodes: props.selectedRows as RowNode[], force: true });
257
- ok = await fnUpdate(selectedRows).catch((ex) => {
258
- console.error("Exception during modifyUpdating", ex);
259
- return false;
260
- });
261
- },
262
- );
263
-
264
- // async processes need to refresh their own rows
265
- gridApi.refreshCells({ rowNodes: selectedRows as RowNode[], force: true });
270
+ }, [gridApiOp]);
271
+
272
+ const stopEditing = useCallback((): void => gridApiOp((gridApi) => gridApi.stopEditing()), [gridApiOp]);
273
+
274
+ const selectNextCell = useCallback(
275
+ (tabDirection: -1 | 0 | 1 = 0) => {
276
+ gridApiOp((gridApi) => {
277
+ if (tabDirection == 1) gridApi.tabToNextCell();
278
+ if (tabDirection == -1) gridApi.tabToPreviousCell();
279
+ });
280
+ },
281
+ [gridApiOp],
282
+ );
266
283
 
267
- if (ok) {
268
- const cell = gridApi.getFocusedCell();
269
- if (cell && gridApi.getFocusedCell() == null) {
270
- gridApi.setFocusedCell(cell.rowIndex, cell.column);
284
+ const updatingCells = useCallback(
285
+ async (
286
+ props: { selectedRows: GridBaseRow[]; field?: string },
287
+ fnUpdate: (selectedRows: any[]) => Promise<boolean>,
288
+ setSaving?: (saving: boolean) => void,
289
+ tabDirection?: 1 | 0 | -1,
290
+ ): Promise<boolean> => {
291
+ setSaving && setSaving(true);
292
+ return await gridApiOp(async (gridApi) => {
293
+ const preOpCell = gridApi.getFocusedCell();
294
+
295
+ const selectedRows = props.selectedRows;
296
+
297
+ let ok = false;
298
+ await modifyUpdating(
299
+ props.field ?? "",
300
+ selectedRows.map((data) => data.id),
301
+ async () => {
302
+ // Need to refresh to get spinners to work on all rows
303
+ gridApi.refreshCells({ rowNodes: props.selectedRows as RowNode[], force: true });
304
+ ok = await fnUpdate(selectedRows).catch((ex) => {
305
+ console.error("Exception during modifyUpdating", ex);
306
+ return false;
307
+ });
308
+ },
309
+ );
310
+
311
+ // async processes need to refresh their own rows
312
+ gridApi.refreshCells({ rowNodes: selectedRows as RowNode[], force: true });
313
+
314
+ if (ok) {
315
+ const cell = gridApi.getFocusedCell();
316
+ if (cell && gridApi.getFocusedCell() == null) {
317
+ gridApi.setFocusedCell(cell.rowIndex, cell.column);
318
+ }
319
+ // This is needed to trigger postSortRowsHook
320
+ gridApi.refreshClientSideRowModel();
321
+ } else {
322
+ // Don't set saving if ok as the form has already closed
323
+ setSaving && setSaving(false);
271
324
  }
272
- // This is needed to trigger postSortRowsHook
273
- gridApi.refreshClientSideRowModel();
274
- } else {
275
- // Don't set saving if ok as the form has already closed
276
- setSaving && setSaving(false);
277
- }
278
325
 
279
- // Only focus next cell if user hasn't already manually changed focus
280
- const postOpCell = gridApi.getFocusedCell();
281
- if (
282
- tabDirection &&
283
- preOpCell &&
284
- postOpCell &&
285
- preOpCell.rowIndex == postOpCell.rowIndex &&
286
- preOpCell.column.getColId() == postOpCell.column.getColId()
287
- ) {
288
- selectNextCell(tabDirection);
289
- }
290
-
291
- return ok;
292
- });
293
- };
326
+ // Only focus next cell if user hasn't already manually changed focus
327
+ const postOpCell = gridApi.getFocusedCell();
328
+ if (
329
+ tabDirection &&
330
+ preOpCell &&
331
+ postOpCell &&
332
+ preOpCell.rowIndex == postOpCell.rowIndex &&
333
+ preOpCell.column.getColId() == postOpCell.column.getColId()
334
+ ) {
335
+ selectNextCell(tabDirection);
336
+ }
294
337
 
295
- const redrawRows = (rowNodes?: RowNode[]) => {
296
- gridApiOp((gridApi) => {
297
- gridApi.redrawRows(rowNodes ? { rowNodes } : undefined);
298
- });
299
- };
338
+ return ok;
339
+ });
340
+ },
341
+ [gridApiOp, modifyUpdating, selectNextCell],
342
+ );
300
343
 
301
- const selectNextCell = (tabDirection: -1 | 0 | 1 = 0) => {
302
- gridApiOp((gridApi) => {
303
- if (tabDirection == 1) gridApi.tabToNextCell();
304
- if (tabDirection == -1) gridApi.tabToPreviousCell();
305
- });
306
- };
344
+ const redrawRows = useCallback(
345
+ (rowNodes?: RowNode[]) => {
346
+ gridApiOp((gridApi) => {
347
+ gridApi.redrawRows(rowNodes ? { rowNodes } : undefined);
348
+ });
349
+ },
350
+ [gridApiOp],
351
+ );
307
352
 
308
- const setExternallySelectedItemsAreInSync = (inSync: boolean) => {
353
+ const setExternallySelectedItemsAreInSync = useCallback((inSync: boolean) => {
309
354
  externallySelectedItemsAreInSync.current = inSync;
310
- };
355
+ }, []);
311
356
 
312
- const waitForExternallySelectedItemsToBeInSync = async () => {
357
+ const waitForExternallySelectedItemsToBeInSync = useCallback(async () => {
313
358
  // Wait for up to 5 seconds
314
359
  for (let i = 0; i < 5000 / 200 && !externallySelectedItemsAreInSync.current; i++) {
315
360
  await wait(200);
316
361
  }
317
- };
362
+ }, []);
318
363
 
319
364
  return (
320
365
  <GridContext.Provider
@@ -137,8 +137,8 @@ export const ControlledMenuFr = (
137
137
  if (activeElement !== firstInputEl && activeElement !== lastInputEl) return;
138
138
 
139
139
  const isTextArea = activeElement.nodeName === "TEXTAREA";
140
- const suppressEnterAutoSave = activeElement.getAttribute("data-disableenterautosave") || isTextArea;
141
- const allowTabToSave = activeElement.getAttribute("data-allowtabtosave");
140
+ const suppressEnterAutoSave = activeElement.getAttribute("data-disableenterautosave") != "false" || isTextArea;
141
+ const allowTabToSave = activeElement.getAttribute("data-allowtabtosave") != "false";
142
142
  const invokeSave = (reason: string) => {
143
143
  if (!saveButtonRef?.current) return;
144
144
  saveButtonRef.current?.setAttribute("data-reason", reason);