@linzjs/step-ag-grid 14.1.2 → 14.2.0

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.
@@ -8,7 +8,7 @@ import { ReactElement, ReactNode, useCallback, useContext, useEffect, useMemo, u
8
8
 
9
9
  import { ColDefT, GridBaseRow } from "../components";
10
10
  import { isNotEmpty, sanitiseFileName, wait } from "../utils/util";
11
- import { GridContext, GridFilterExternal } from "./GridContext";
11
+ import { AutoSizeColumnsProps, AutoSizeColumnsResult, GridContext, GridFilterExternal } from "./GridContext";
12
12
  import { GridUpdatingContext } from "./GridUpdatingContext";
13
13
 
14
14
  interface GridContextProps {
@@ -64,14 +64,13 @@ export const GridContextProvider = <RowType extends GridBaseRow>(props: GridCont
64
64
  */
65
65
  const ensureRowVisible = useCallback(
66
66
  (id: number | string): boolean => {
67
- return gridApiOp((gridApi) => {
68
- const node = gridApi.getRowNode(`${id}`);
69
- if (!node) return false;
70
- gridApi.ensureNodeVisible(node);
71
- return true;
72
- });
67
+ if (!gridApi) return false;
68
+ const node = gridApi?.getRowNode(`${id}`);
69
+ if (!node) return false;
70
+ defer(() => gridApi.ensureNodeVisible(node));
71
+ return true;
73
72
  },
74
- [gridApiOp],
73
+ [gridApi],
75
74
  );
76
75
 
77
76
  const getFirstRowId = useCallback((): number => {
@@ -211,17 +210,16 @@ export const GridContextProvider = <RowType extends GridBaseRow>(props: GridCont
211
210
  );
212
211
  const firstNode = rowsThatNeedSelecting[0];
213
212
  if (firstNode) {
214
- gridApi.ensureNodeVisible(firstNode);
213
+ defer(() => gridApi.ensureNodeVisible(firstNode));
215
214
  const colDefs = gridApi.getColumnDefs();
216
- if (colDefs && colDefs.length) {
215
+ if (colDefs?.length) {
217
216
  const col = colDefs[0] as ColDef; // We don't support ColGroupDef
218
217
  const rowIndex = firstNode.rowIndex;
219
218
  if (rowIndex != null && col != null) {
220
219
  const colId = col.colId;
221
220
  // We need to make sure we aren't currently editing a cell otherwise tests will fail
222
221
  // as they will start to edit the cell before this stuff has a chance to run
223
- colId != null &&
224
- defer(() => isEmpty(gridApi.getEditingCells()) && gridApi.setFocusedCell(rowIndex, colId));
222
+ colId && defer(() => isEmpty(gridApi.getEditingCells()) && gridApi.setFocusedCell(rowIndex, colId));
225
223
  }
226
224
  }
227
225
  }
@@ -337,25 +335,30 @@ export const GridContextProvider = <RowType extends GridBaseRow>(props: GridCont
337
335
  gridApiOp((gridApi) => {
338
336
  const selectedNodes = gridApi.getSelectedNodes();
339
337
  if (isEmpty(selectedNodes)) return;
340
- gridApi.ensureNodeVisible(last(selectedNodes));
338
+ defer(() => gridApi.ensureNodeVisible(last(selectedNodes)));
341
339
  });
342
340
  }, [gridApiOp]);
343
341
 
344
342
  /**
345
343
  * Resize columns to fit container
346
344
  */
347
- const autoSizeAllColumns = useCallback(
348
- ({ skipHeader }): { width: number } | null => {
349
- if (columnApi) {
350
- columnApi.autoSizeAllColumns(skipHeader);
351
- return {
352
- width: sumBy(
353
- columnApi.getColumnState().filter((col) => col.hide !== true),
354
- "width",
355
- ),
356
- };
357
- }
358
- return null;
345
+ const autoSizeColumns = useCallback(
346
+ ({ skipHeader, colIds, userSizedColIds }: AutoSizeColumnsProps = {}): AutoSizeColumnsResult => {
347
+ if (!columnApi) return null;
348
+ const colIdsSet = colIds instanceof Set ? colIds : new Set<string>(colIds ?? []);
349
+ columnApi.getColumnState().forEach((col) => {
350
+ const colId = col.colId;
351
+ if ((isEmpty(colIdsSet) || colIdsSet.has(colId)) && !userSizedColIds?.has(colId)) {
352
+ columnApi.autoSizeColumn(colId, skipHeader);
353
+ }
354
+ });
355
+
356
+ return {
357
+ width: sumBy(
358
+ columnApi.getColumnState().filter((col) => !col.hide),
359
+ "width",
360
+ ),
361
+ };
359
362
  },
360
363
  [columnApi],
361
364
  );
@@ -364,26 +367,83 @@ export const GridContextProvider = <RowType extends GridBaseRow>(props: GridCont
364
367
  * Resize columns to fit container
365
368
  */
366
369
  const sizeColumnsToFit = useCallback((): void => {
367
- if (gridApi) {
368
- gridApi.sizeColumnsToFit();
369
- }
370
+ gridApi && gridApi.sizeColumnsToFit();
370
371
  }, [gridApi]);
371
372
 
372
373
  const stopEditing = useCallback((): void => {
374
+ if (!gridApi) return;
373
375
  if (prePopupFocusedCell.current) {
374
376
  gridApi?.setFocusedCell(prePopupFocusedCell.current.rowIndex, prePopupFocusedCell.current.column);
375
377
  }
376
- gridApiOp((gridApi) => gridApi.stopEditing());
377
- }, [gridApi, gridApiOp]);
378
+ gridApi.stopEditing();
379
+ }, [gridApi]);
378
380
 
379
- const selectNextCell = useCallback(
380
- (tabDirection: -1 | 0 | 1 = 0) => {
381
- gridApiOp((gridApi) => {
382
- if (tabDirection == 1) gridApi.tabToNextCell();
383
- if (tabDirection == -1) gridApi.tabToPreviousCell();
384
- });
381
+ /**
382
+ * This differs from stopEdit in that it will also invoke cellEditingCompleteCallback
383
+ */
384
+ const cancelEdit = useCallback((): void => {
385
+ stopEditing();
386
+ cellEditingCompleteCallbackRef.current && cellEditingCompleteCallbackRef.current();
387
+ }, [stopEditing]);
388
+
389
+ const cellEditingCompleteCallbackRef = useRef<() => void>();
390
+ const setOnCellEditingComplete = useCallback((cellEditingCompleteCallback: (() => void) | undefined) => {
391
+ cellEditingCompleteCallbackRef.current = cellEditingCompleteCallback;
392
+ }, []);
393
+
394
+ /**
395
+ * Returns true if an editable cell on same row was selected, else false.
396
+ */
397
+ const selectNextEditableCell = useCallback(
398
+ (tabDirection: -1 | 1): boolean => {
399
+ // Pretend it succeeded to prevent unwanted cellEditingCompleteCallback
400
+ if (!gridApi) return true;
401
+
402
+ const focusedCellIsEditable = () => {
403
+ const focusedCell = gridApi.getFocusedCell();
404
+ const nextColumn = focusedCell?.column;
405
+ const nextColDef = nextColumn?.getColDef();
406
+ const rowNode = focusedCell && gridApi.getDisplayedRowAtIndex(focusedCell?.rowIndex);
407
+ return (
408
+ !!(rowNode && nextColumn && nextColDef) &&
409
+ nextColumn.isCellEditable(rowNode) &&
410
+ !nextColDef.cellEditorParams?.preventAutoEdit &&
411
+ !nextColDef.cellRendererParams?.editAction
412
+ );
413
+ };
414
+
415
+ let foundEditableCell = false;
416
+
417
+ // Just in case I've missed something, we don't want the loop to hang everything
418
+ let maxIterations = 50;
419
+ let preRow: CellPosition | null = null;
420
+ let postRow: CellPosition | null = null;
421
+ do {
422
+ preRow = gridApi.getFocusedCell();
423
+ tabDirection === 1 ? gridApi.tabToNextCell() : gridApi.tabToPreviousCell();
424
+ postRow = gridApi.getFocusedCell();
425
+ foundEditableCell = focusedCellIsEditable();
426
+ } while (
427
+ preRow?.rowIndex === postRow?.rowIndex &&
428
+ preRow?.column !== postRow?.column &&
429
+ !foundEditableCell &&
430
+ maxIterations-- > 0
431
+ );
432
+
433
+ if (foundEditableCell) {
434
+ prePopupOps();
435
+ const focusedCell = gridApi?.getFocusedCell();
436
+ if (focusedCell) {
437
+ gridApi.startEditingCell({
438
+ rowIndex: focusedCell.rowIndex,
439
+ colKey: focusedCell.column.getColId(),
440
+ });
441
+ return false;
442
+ }
443
+ }
444
+ return true;
385
445
  },
386
- [gridApiOp],
446
+ [gridApi, prePopupOps],
387
447
  );
388
448
 
389
449
  const updatingCells = useCallback(
@@ -398,6 +458,7 @@ export const GridContextProvider = <RowType extends GridBaseRow>(props: GridCont
398
458
  const selectedRows = props.selectedRows;
399
459
 
400
460
  let ok = false;
461
+
401
462
  await modifyUpdating(
402
463
  props.field ?? "",
403
464
  selectedRows.map((data) => data.id),
@@ -429,19 +490,20 @@ export const GridContextProvider = <RowType extends GridBaseRow>(props: GridCont
429
490
  // Only focus next cell if user hasn't already manually changed focus
430
491
  const postPopupFocusedCell = gridApi.getFocusedCell();
431
492
  if (
432
- tabDirection &&
433
493
  prePopupFocusedCell.current &&
434
494
  postPopupFocusedCell &&
435
495
  prePopupFocusedCell.current.rowIndex == postPopupFocusedCell.rowIndex &&
436
496
  prePopupFocusedCell.current.column.getColId() == postPopupFocusedCell.column.getColId()
437
497
  ) {
438
- selectNextCell(tabDirection);
498
+ if (!tabDirection || selectNextEditableCell(tabDirection)) {
499
+ cellEditingCompleteCallbackRef.current && cellEditingCompleteCallbackRef.current();
500
+ }
439
501
  }
440
502
 
441
503
  return ok;
442
504
  });
443
505
  },
444
- [gridApiOp, modifyUpdating, selectNextCell],
506
+ [gridApiOp, modifyUpdating, selectNextEditableCell],
445
507
  );
446
508
 
447
509
  const redrawRows = useCallback(
@@ -557,8 +619,9 @@ export const GridContextProvider = <RowType extends GridBaseRow>(props: GridCont
557
619
  ensureRowVisible,
558
620
  ensureSelectedRowIsVisible,
559
621
  sizeColumnsToFit,
560
- autoSizeAllColumns,
622
+ autoSizeColumns,
561
623
  stopEditing,
624
+ cancelEdit,
562
625
  updatingCells,
563
626
  redrawRows,
564
627
  externallySelectedItemsAreInSync,
@@ -569,6 +632,7 @@ export const GridContextProvider = <RowType extends GridBaseRow>(props: GridCont
569
632
  isExternalFilterPresent,
570
633
  doesExternalFilterPass,
571
634
  downloadCsv,
635
+ setOnCellEditingComplete,
572
636
  }}
573
637
  >
574
638
  {props.children}
@@ -617,6 +681,7 @@ export const downloadCsvUseValueFormattersProcessCellCallback = (params: Process
617
681
  }
618
682
 
619
683
  const result = valueFormatter({ ...params, data: params.node?.data, colDef } as ValueFormatterParams);
684
+ // type may not be string due to casting, leave the type check in
620
685
  if (params.value != null && typeof result !== "string") {
621
686
  console.error(`downloadCsv: valueFormatter is returning non string values, colDef:", colId: ${colDef.colId}`);
622
687
  }
@@ -2,6 +2,7 @@ import { createContext } from "react";
2
2
 
3
3
  export type GridUpdatingContextType = {
4
4
  checkUpdating: (fields: string | string[], id: number | string) => boolean;
5
+ isUpdating: () => boolean;
5
6
  modifyUpdating: (
6
7
  fields: string | string[],
7
8
  ids: (number | string)[],
@@ -15,6 +16,10 @@ export const GridUpdatingContext = createContext<GridUpdatingContextType>({
15
16
  console.error("Missing GridUpdatingContext");
16
17
  return false;
17
18
  },
19
+ isUpdating: () => {
20
+ console.error("Missing GridUpdatingContext");
21
+ return false;
22
+ },
18
23
  modifyUpdating: async () => {
19
24
  console.error("Missing GridUpdatingContext");
20
25
  },
@@ -1,4 +1,4 @@
1
- import { castArray, flatten, remove } from "lodash-es";
1
+ import { castArray, flatten, isEmpty, remove } from "lodash-es";
2
2
  import { ReactNode, useRef, useState } from "react";
3
3
 
4
4
  import { GridUpdatingContext } from "./GridUpdatingContext";
@@ -21,12 +21,17 @@ export const GridUpdatingContextProvider = (props: UpdatingContextProviderProps)
21
21
  const resetUpdating = () => {
22
22
  const mergedUpdatingBlocks: GridUpdatingContextStatus = {};
23
23
  for (const key in updatingBlocks.current) {
24
- mergedUpdatingBlocks[key] = flatten(updatingBlocks.current[key]);
24
+ const arr = flatten(updatingBlocks.current[key]);
25
+ if (arr.length) {
26
+ mergedUpdatingBlocks[key] = arr;
27
+ }
25
28
  }
26
29
  updating.current = mergedUpdatingBlocks;
27
30
  setUpdatedDep((updatedDep) => updatedDep + 1);
28
31
  };
29
32
 
33
+ const isUpdating = () => !isEmpty(updating.current);
34
+
30
35
  const modifyUpdating = async (
31
36
  fields: string | string[],
32
37
  ids: (number | string)[],
@@ -50,7 +55,7 @@ export const GridUpdatingContextProvider = (props: UpdatingContextProviderProps)
50
55
  castArray(fields).some((f) => updating.current[f]?.includes(id));
51
56
 
52
57
  return (
53
- <GridUpdatingContext.Provider value={{ modifyUpdating, checkUpdating, updatedDep }}>
58
+ <GridUpdatingContext.Provider value={{ modifyUpdating, checkUpdating, isUpdating, updatedDep }}>
54
59
  {props.children}
55
60
  </GridUpdatingContext.Provider>
56
61
  );
@@ -477,7 +477,27 @@ export const MenuList = ({
477
477
  top: menuPosition.y,
478
478
  }}
479
479
  >
480
- <div ref={focusRef} tabIndex={-1} style={{ position: "absolute", left: 0, top: 0 }} />
480
+ <div
481
+ ref={focusRef}
482
+ tabIndex={-1}
483
+ style={{ position: "absolute", left: 0, top: 0 }}
484
+ onKeyDown={(event) => {
485
+ if (event.key == "Tab") {
486
+ event.preventDefault();
487
+ event.stopPropagation();
488
+ safeCall(onClose, {
489
+ key: event.key,
490
+ shiftKey: event.shiftKey,
491
+ reason:
492
+ event.key === "Tab"
493
+ ? event.shiftKey
494
+ ? CloseReason.TAB_BACKWARD
495
+ : CloseReason.TAB_FORWARD
496
+ : CloseReason.CLICK,
497
+ });
498
+ }
499
+ }}
500
+ />
481
501
  {arrow && (
482
502
  <div
483
503
  className={_arrowClass}
@@ -192,7 +192,7 @@ const GridPopoutEditGenericTemplate: ComponentStory<typeof Grid> = (props: GridP
192
192
  setRowData([
193
193
  ...rowData,
194
194
  {
195
- id: lastRow.id + 1,
195
+ id: (lastRow?.id ?? 0) + 1,
196
196
  name: "?",
197
197
  nameType: "?",
198
198
  numba: "?",
@@ -212,6 +212,12 @@ const GridPopoutEditGenericTemplate: ComponentStory<typeof Grid> = (props: GridP
212
212
  columnDefs={columnDefs}
213
213
  rowData={rowData}
214
214
  domLayout={"autoHeight"}
215
+ defaultColDef={{ minWidth: 70 }}
216
+ sizeColumns={"auto"}
217
+ onCellEditingComplete={() => {
218
+ /* eslint-disable-next-line no-console */
219
+ console.log("Cell editing complete");
220
+ }}
215
221
  />
216
222
  <ActionButton icon={"ic_add"} name={"Add new row"} inProgressName={"Adding..."} onClick={addRowAction} />
217
223
  </>
@@ -155,6 +155,7 @@ const GridEditDropDownTemplate: ComponentStory<typeof Grid> = (props: GridProps)
155
155
  {
156
156
  field: "position3",
157
157
  headerName: "Filtered",
158
+ editable: false,
158
159
  },
159
160
  {
160
161
  multiEdit: true,
@@ -302,6 +303,10 @@ const GridEditDropDownTemplate: ComponentStory<typeof Grid> = (props: GridProps)
302
303
  columnDefs={columnDefs}
303
304
  rowData={rowData}
304
305
  domLayout={"autoHeight"}
306
+ onCellEditingComplete={() => {
307
+ /* eslint-disable-next-line no-console */
308
+ console.log("Cell editing complete");
309
+ }}
305
310
  />
306
311
  </GridWrapper>
307
312
  );
@@ -248,7 +248,9 @@ GridKeyboardInteractions.play = async ({ canvasElement }) => {
248
248
  await test(() => userEvent.keyboard("{Enter}"), "8", "2");
249
249
  await test(() => userEvent.tab(), "9", "2");
250
250
  userEvent.tab({ shift: true });
251
- await test(() => userEvent.tab({ shift: true }), "7", "2");
251
+ await test(() => userEvent.tab({ shift: true }), "6", "2");
252
+ userEvent.keyboard("{Esc}");
253
+ userEvent.tab();
252
254
 
253
255
  userEvent.keyboard("{Enter}");
254
256
  await wait(250);