@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/README.md +48 -1
- package/dist/index.js +131 -64
- package/dist/index.js.map +1 -1
- package/dist/src/components/Grid.d.ts +1 -0
- package/dist/src/contexts/GridContext.d.ts +3 -1
- package/dist/step-ag-grid.esm.js +131 -64
- package/dist/step-ag-grid.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/components/Grid.tsx +40 -7
- package/src/contexts/GridContext.tsx +10 -5
- package/src/contexts/GridContextProvider.tsx +22 -17
- package/src/stories/grid/GridReadOnly.stories.tsx +1 -0
- package/src/utils/testUtil.ts +13 -8
package/package.json
CHANGED
package/src/components/Grid.tsx
CHANGED
|
@@ -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)
|
|
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
|
|
94
|
-
if (!params.externalSelectedItems)
|
|
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
|
-
|
|
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
|
|
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(
|
|
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:
|
|
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
|
|
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
|
-
|
|
25
|
-
|
|
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 =
|
|
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}
|
package/src/utils/testUtil.ts
CHANGED
|
@@ -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 =
|
|
24
|
+
const isSelected = row.className.includes("ag-row-selected");
|
|
26
25
|
if (select === "toggle" || (select === "select" && !isSelected) || (select === "deselect" && isSelected)) {
|
|
27
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
|