@linzjs/step-ag-grid 7.10.2 → 7.11.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/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@linzjs/step-ag-grid",
3
3
  "repository": "github:linz/step-ag-grid.git",
4
4
  "license": "MIT",
5
- "version": "7.10.2",
5
+ "version": "7.11.1",
6
6
  "keywords": [
7
7
  "aggrid",
8
8
  "ag-grid",
@@ -34,6 +34,7 @@ export interface GridProps {
34
34
  animateRows?: boolean;
35
35
  rowClassRules?: GridOptions["rowClassRules"];
36
36
  rowSelection?: "single" | "multiple";
37
+ autoSelectFirstRow?: boolean;
37
38
  }
38
39
 
39
40
  /**
@@ -48,6 +49,7 @@ export const Grid = (params: GridProps): JSX.Element => {
48
49
  selectRowsById,
49
50
  ensureSelectedRowIsVisible,
50
51
  sizeColumnsToFit,
52
+ setExternallySelectedItemsAreInSync,
51
53
  } = useContext(GridContext);
52
54
  const { checkUpdating } = useContext(GridUpdatingContext);
53
55
 
@@ -56,6 +58,22 @@ export const Grid = (params: GridProps): JSX.Element => {
56
58
  const [staleGrid, setStaleGrid] = useState(false);
57
59
  const postSortRows = usePostSortRowsHook({ setStaleGrid });
58
60
 
61
+ /**
62
+ * On data load select the first row of the grid if required.
63
+ */
64
+ const hasSelectedFirstItem = useRef(false);
65
+ useEffect(() => {
66
+ if (!gridReady || !params.autoSelectFirstRow || hasSelectedFirstItem.current || !params.rowData) return;
67
+ hasSelectedFirstItem.current = true;
68
+ if (isNotEmpty(params.rowData)) {
69
+ if (params.setExternalSelectedItems) {
70
+ params.setExternalSelectedItems([params.rowData[0]]);
71
+ } else {
72
+ selectRowsById([params.rowData[0].id]);
73
+ }
74
+ }
75
+ }, [gridReady, params, params.autoSelectFirstRow, params.rowData, selectRowsById]);
76
+
59
77
  /**
60
78
  * AgGrid checkbox select does not pass clicks within cell but not on the checkbox to checkbox.
61
79
  * This passes the event to the checkbox when you click anywhere in the cell.
@@ -71,7 +89,10 @@ export const Grid = (params: GridProps): JSX.Element => {
71
89
  */
72
90
  const synchroniseExternalStateToGridSelection = useCallback(
73
91
  ({ api }: SelectionChangedEvent) => {
74
- if (!params.externalSelectedItems || !params.setExternalSelectedItems) return;
92
+ if (!params.externalSelectedItems || !params.setExternalSelectedItems) {
93
+ setExternallySelectedItemsAreInSync(true);
94
+ return;
95
+ }
75
96
 
76
97
  const selectedRows = api.getSelectedRows();
77
98
  // We don't want to update selected Items if it hasn't changed to prevent excess renders
@@ -79,10 +100,13 @@ export const Grid = (params: GridProps): JSX.Element => {
79
100
  params.externalSelectedItems.length != selectedRows.length ||
80
101
  isNotEmpty(xorBy(selectedRows, params.externalSelectedItems, (row) => row.id))
81
102
  ) {
103
+ setExternallySelectedItemsAreInSync(false);
82
104
  params.setExternalSelectedItems([...selectedRows]);
105
+ } else {
106
+ setExternallySelectedItemsAreInSync(true);
83
107
  }
84
108
  },
85
- [params],
109
+ [params, setExternallySelectedItemsAreInSync],
86
110
  );
87
111
 
88
112
  /**
@@ -90,21 +114,25 @@ export const Grid = (params: GridProps): JSX.Element => {
90
114
  * If new ids are selected scroll them into view.
91
115
  */
92
116
  const synchroniseExternallySelectedItemsToGrid = useCallback(() => {
93
- if (!gridReady()) return;
94
- if (!params.externalSelectedItems) return;
117
+ if (!gridReady) return;
118
+ if (!params.externalSelectedItems) {
119
+ setExternallySelectedItemsAreInSync(true);
120
+ return;
121
+ }
95
122
 
96
123
  const selectedIds = params.externalSelectedItems.map((row) => row.id) as number[];
97
124
  const lastNewId = last(difference(selectedIds, lastSelectedIds.current));
98
125
  if (lastNewId != null) ensureRowVisible(lastNewId);
99
126
  lastSelectedIds.current = selectedIds;
100
127
  selectRowsById(selectedIds);
101
- }, [params.externalSelectedItems, ensureRowVisible, gridReady, selectRowsById]);
128
+ setExternallySelectedItemsAreInSync(true);
129
+ }, [gridReady, params.externalSelectedItems, ensureRowVisible, selectRowsById, setExternallySelectedItemsAreInSync]);
102
130
 
103
131
  /**
104
132
  * Synchronise quick filter to grid
105
133
  */
106
134
  const updateQuickFilter = useCallback(() => {
107
- if (!gridReady()) return;
135
+ if (!gridReady) return;
108
136
  if (params.quickFilter) {
109
137
  setQuickFilter(internalQuickFilter);
110
138
  return;
@@ -257,7 +285,12 @@ export const Grid = (params: GridProps): JSX.Element => {
257
285
  return (
258
286
  <div
259
287
  data-testid={params["data-testid"]}
260
- className={clsx("Grid-container", "ag-theme-alpine", staleGrid && "Grid-sortIsStale")}
288
+ className={clsx(
289
+ "Grid-container",
290
+ "ag-theme-alpine",
291
+ staleGrid && "Grid-sortIsStale",
292
+ gridReady && params.rowData && "Grid-ready",
293
+ )}
261
294
  >
262
295
  {params.quickFilter && (
263
296
  <div className="Grid-quickFilter">
@@ -3,7 +3,7 @@ import { GridApi, RowNode } from "ag-grid-community";
3
3
  import { GridBaseRow } from "../components/Grid";
4
4
 
5
5
  export interface GridContextType {
6
- gridReady: () => boolean;
6
+ gridReady: boolean;
7
7
  setGridApi: (gridApi: GridApi | undefined) => void;
8
8
  setQuickFilter: (quickFilter: string) => void;
9
9
  editingCells: () => boolean;
@@ -26,13 +26,12 @@ export interface GridContextType {
26
26
  tabDirection?: 1 | 0 | -1,
27
27
  ) => Promise<boolean>;
28
28
  redrawRows: (rowNodes?: RowNode[]) => void;
29
+ setExternallySelectedItemsAreInSync: (inSync: boolean) => void;
30
+ waitForExternallySelectedItemsToBeInSync: () => Promise<void>;
29
31
  }
30
32
 
31
33
  export const GridContext = createContext<GridContextType>({
32
- gridReady: () => {
33
- console.error("no context provider for gridReady");
34
- return false;
35
- },
34
+ gridReady: false,
36
35
  setGridApi: () => {
37
36
  console.error("no context provider for setGridApi");
38
37
  },
@@ -89,4 +88,10 @@ export const GridContext = createContext<GridContextType>({
89
88
  redrawRows: () => {
90
89
  console.error("no context provider for redrawRows");
91
90
  },
91
+ setExternallySelectedItemsAreInSync: () => {
92
+ console.error("no context provider for setExternallySelectedItemsAreInSync");
93
+ },
94
+ waitForExternallySelectedItemsToBeInSync: async () => {
95
+ console.error("no context provider for waitForExternallySelectedItemsToBeInSync");
96
+ },
92
97
  });
@@ -1,8 +1,8 @@
1
- import { ReactElement, ReactNode, useContext, useRef } from "react";
1
+ import { ReactElement, ReactNode, useContext, useRef, useState } from "react";
2
2
  import { GridApi, RowNode } from "ag-grid-community";
3
3
  import { GridContext } from "./GridContext";
4
4
  import { delay, difference, isEmpty, last, sortBy } from "lodash-es";
5
- import { isNotEmpty } from "../utils/util";
5
+ import { isNotEmpty, wait } from "../utils/util";
6
6
  import { GridUpdatingContext } from "./GridUpdatingContext";
7
7
  import { GridBaseRow } from "../components/Grid";
8
8
 
@@ -17,21 +17,14 @@ interface GridContextProps {
17
17
  */
18
18
  export const GridContextProvider = (props: GridContextProps): ReactElement => {
19
19
  const { modifyUpdating } = useContext(GridUpdatingContext);
20
- const gridApiRef = useRef<GridApi>();
20
+ const [gridApi, _setGridApi] = useState<GridApi>();
21
+ const [gridReady, setGridReady] = useState(false);
21
22
  const idsBeforeUpdate = useRef<number[]>([]);
23
+ const externallySelectedItemsAreInSync = useRef(false);
22
24
 
23
- /**
24
- * Has Grid api has been set?
25
- * Some ops in components may occur before onGridReady has been called,
26
- * and thus will need to check grid is ready before calling.
27
- */
28
- const gridReady = () => gridApiRef.current != null;
29
-
30
- /**
31
- * Set current ref to grid api.
32
- */
33
- const setGridApi = (_gridApi: GridApi | undefined) => {
34
- gridApiRef.current = _gridApi;
25
+ const setGridApi = (gridApi: GridApi | undefined) => {
26
+ _setGridApi(gridApi);
27
+ setGridReady(!!gridApi);
35
28
  };
36
29
 
37
30
  /**
@@ -47,7 +40,6 @@ export const GridContextProvider = (props: GridContextProps): ReactElement => {
47
40
  if (!noApiFn) {
48
41
  noApiFn = (() => {}) as () => R;
49
42
  }
50
- const gridApi = gridApiRef.current;
51
43
  return gridApi ? hasApiFn(gridApi) : noApiFn();
52
44
  };
53
45
 
@@ -121,7 +113,7 @@ export const GridContextProvider = (props: GridContextProps): ReactElement => {
121
113
  rowIds: number[] | undefined,
122
114
  select: boolean,
123
115
  flash: boolean,
124
- retryCount = 5, // We retry for approximately 5x200ms=1s
116
+ retryCount = 15, // We retry for approximately 5x200ms=1s
125
117
  ) => {
126
118
  return gridApiOp((gridApi) => {
127
119
  const rowNodes = rowIds ? _rowIdsToNodes(rowIds) : _getNewNodes();
@@ -302,6 +294,17 @@ export const GridContextProvider = (props: GridContextProps): ReactElement => {
302
294
  });
303
295
  };
304
296
 
297
+ const setExternallySelectedItemsAreInSync = (inSync: boolean) => {
298
+ externallySelectedItemsAreInSync.current = inSync;
299
+ };
300
+
301
+ const waitForExternallySelectedItemsToBeInSync = async () => {
302
+ // Wait for up to 5 seconds
303
+ for (let i = 0; i < 5000 / 200 && !externallySelectedItemsAreInSync.current; i++) {
304
+ await wait(200);
305
+ }
306
+ };
307
+
305
308
  return (
306
309
  <GridContext.Provider
307
310
  value={{
@@ -323,6 +326,8 @@ export const GridContextProvider = (props: GridContextProps): ReactElement => {
323
326
  stopEditing,
324
327
  updatingCells,
325
328
  redrawRows,
329
+ setExternallySelectedItemsAreInSync,
330
+ waitForExternallySelectedItemsToBeInSync,
326
331
  }}
327
332
  >
328
333
  {props.children}
@@ -205,6 +205,7 @@ const GridReadOnlyTemplate: ComponentStory<typeof Grid> = (props: GridProps) =>
205
205
  columnDefs={columnDefs}
206
206
  rowData={rowData}
207
207
  domLayout={"autoHeight"}
208
+ autoSelectFirstRow={true}
208
209
  />
209
210
  );
210
211
  };
@@ -1,7 +1,6 @@
1
1
  import { act, waitFor, within } from "@testing-library/react";
2
2
  import userEvent from "@testing-library/user-event";
3
3
  import { findQuick, getAllQuick, getMatcher, getQuick, queryQuick } from "./testQuick";
4
- import { wait } from "./util";
5
4
 
6
5
  export const countRows = async (within?: HTMLElement): Promise<number> => {
7
6
  return getAllQuick({ tagName: `div[row-id]:not(:empty)` }, within).length;
@@ -22,9 +21,15 @@ const _selectRow = async (
22
21
  ): Promise<void> => {
23
22
  await act(async () => {
24
23
  const row = await findRow(rowId, within);
25
- const isSelected = !row.className.includes("ag-row-selected");
24
+ const isSelected = row.className.includes("ag-row-selected");
26
25
  if (select === "toggle" || (select === "select" && !isSelected) || (select === "deselect" && isSelected)) {
27
- userEvent.click(row);
26
+ const cell = await findCell(rowId, "selection", within);
27
+ userEvent.click(cell);
28
+ await waitFor(async () => {
29
+ const row = await findRow(rowId, within);
30
+ const nowSelected = row.className.includes("ag-row-selected");
31
+ if (nowSelected == isSelected) throw `Row ${rowId} won't select`;
32
+ });
28
33
  }
29
34
  });
30
35
  };
@@ -63,11 +68,12 @@ export const selectCell = async (rowId: string | number, colId: string, within?:
63
68
  };
64
69
 
65
70
  export const editCell = async (rowId: number | string, colId: string, within?: HTMLElement): Promise<void> => {
71
+ await selectRow(rowId, within);
66
72
  await act(async () => {
67
73
  const cell = await findCell(rowId, colId, within);
68
74
  userEvent.dblClick(cell);
69
75
  });
70
- await findOpenMenu();
76
+ await waitFor(findOpenMenu);
71
77
  };
72
78
 
73
79
  const findOpenMenu = async (): Promise<HTMLElement> => findQuick({ classes: ".szh-menu--state-open" });
@@ -89,14 +95,14 @@ export const findMenuOption = async (menuOptionText: string | RegExp): Promise<H
89
95
  }
90
96
  return menuOption;
91
97
  },
92
- { timeout: 10000 },
98
+ { timeout: 5000 },
93
99
  );
94
100
  };
95
101
 
96
102
  export const clickMenuOption = async (menuOptionText: string | RegExp): Promise<void> => {
103
+ const menuOption = await findMenuOption(menuOptionText);
97
104
  await act(async () => {
98
- const menuOption = await findMenuOption(menuOptionText);
99
- menuOption && userEvent.click(menuOption);
105
+ userEvent.click(menuOption);
100
106
  });
101
107
  };
102
108
 
@@ -107,7 +113,6 @@ export const openAndClickMenuOption = async (
107
113
  within?: HTMLElement,
108
114
  ): Promise<void> => {
109
115
  await editCell(rowId, colId, within);
110
- await wait(100);
111
116
  await clickMenuOption(menuOptionText);
112
117
  };
113
118