@linzjs/step-ag-grid 14.1.1 → 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,20 +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 { width: sumBy(columnApi.getColumnState(), "width") };
352
- }
353
- 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
+ };
354
362
  },
355
363
  [columnApi],
356
364
  );
@@ -359,26 +367,83 @@ export const GridContextProvider = <RowType extends GridBaseRow>(props: GridCont
359
367
  * Resize columns to fit container
360
368
  */
361
369
  const sizeColumnsToFit = useCallback((): void => {
362
- if (gridApi) {
363
- gridApi.sizeColumnsToFit();
364
- }
370
+ gridApi && gridApi.sizeColumnsToFit();
365
371
  }, [gridApi]);
366
372
 
367
373
  const stopEditing = useCallback((): void => {
374
+ if (!gridApi) return;
368
375
  if (prePopupFocusedCell.current) {
369
376
  gridApi?.setFocusedCell(prePopupFocusedCell.current.rowIndex, prePopupFocusedCell.current.column);
370
377
  }
371
- gridApiOp((gridApi) => gridApi.stopEditing());
372
- }, [gridApi, gridApiOp]);
378
+ gridApi.stopEditing();
379
+ }, [gridApi]);
373
380
 
374
- const selectNextCell = useCallback(
375
- (tabDirection: -1 | 0 | 1 = 0) => {
376
- gridApiOp((gridApi) => {
377
- if (tabDirection == 1) gridApi.tabToNextCell();
378
- if (tabDirection == -1) gridApi.tabToPreviousCell();
379
- });
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;
380
445
  },
381
- [gridApiOp],
446
+ [gridApi, prePopupOps],
382
447
  );
383
448
 
384
449
  const updatingCells = useCallback(
@@ -393,6 +458,7 @@ export const GridContextProvider = <RowType extends GridBaseRow>(props: GridCont
393
458
  const selectedRows = props.selectedRows;
394
459
 
395
460
  let ok = false;
461
+
396
462
  await modifyUpdating(
397
463
  props.field ?? "",
398
464
  selectedRows.map((data) => data.id),
@@ -424,19 +490,20 @@ export const GridContextProvider = <RowType extends GridBaseRow>(props: GridCont
424
490
  // Only focus next cell if user hasn't already manually changed focus
425
491
  const postPopupFocusedCell = gridApi.getFocusedCell();
426
492
  if (
427
- tabDirection &&
428
493
  prePopupFocusedCell.current &&
429
494
  postPopupFocusedCell &&
430
495
  prePopupFocusedCell.current.rowIndex == postPopupFocusedCell.rowIndex &&
431
496
  prePopupFocusedCell.current.column.getColId() == postPopupFocusedCell.column.getColId()
432
497
  ) {
433
- selectNextCell(tabDirection);
498
+ if (!tabDirection || selectNextEditableCell(tabDirection)) {
499
+ cellEditingCompleteCallbackRef.current && cellEditingCompleteCallbackRef.current();
500
+ }
434
501
  }
435
502
 
436
503
  return ok;
437
504
  });
438
505
  },
439
- [gridApiOp, modifyUpdating, selectNextCell],
506
+ [gridApiOp, modifyUpdating, selectNextEditableCell],
440
507
  );
441
508
 
442
509
  const redrawRows = useCallback(
@@ -552,8 +619,9 @@ export const GridContextProvider = <RowType extends GridBaseRow>(props: GridCont
552
619
  ensureRowVisible,
553
620
  ensureSelectedRowIsVisible,
554
621
  sizeColumnsToFit,
555
- autoSizeAllColumns,
622
+ autoSizeColumns,
556
623
  stopEditing,
624
+ cancelEdit,
557
625
  updatingCells,
558
626
  redrawRows,
559
627
  externallySelectedItemsAreInSync,
@@ -564,6 +632,7 @@ export const GridContextProvider = <RowType extends GridBaseRow>(props: GridCont
564
632
  isExternalFilterPresent,
565
633
  doesExternalFilterPass,
566
634
  downloadCsv,
635
+ setOnCellEditingComplete,
567
636
  }}
568
637
  >
569
638
  {props.children}
@@ -612,6 +681,7 @@ export const downloadCsvUseValueFormattersProcessCellCallback = (params: Process
612
681
  }
613
682
 
614
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
615
685
  if (params.value != null && typeof result !== "string") {
616
686
  console.error(`downloadCsv: valueFormatter is returning non string values, colDef:", colId: ${colDef.colId}`);
617
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);