@linzjs/step-ag-grid 13.0.0 → 13.1.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 +36 -1
- package/dist/GridTheme.scss +2 -2
- package/dist/index.css +26 -2
- package/dist/src/components/Grid.d.ts +2 -1
- package/dist/src/components/GridCell.d.ts +1 -1
- package/dist/src/components/gridFilter/GridFilterColumnsToggle.d.ts +5 -0
- package/dist/src/components/gridFilter/GridFilterHeaderIconButton.d.ts +11 -0
- package/dist/src/components/gridFilter/index.d.ts +3 -0
- package/dist/src/components/{GridFilter.d.ts → gridFilter/useGridFilter.d.ts} +2 -2
- package/dist/src/components/index.d.ts +0 -1
- package/dist/src/contexts/GridContext.d.ts +8 -3
- package/dist/src/contexts/GridContextProvider.d.ts +1 -1
- package/dist/step-ag-grid.esm.js +405 -238
- package/dist/step-ag-grid.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/components/Grid.tsx +20 -14
- package/src/components/GridCell.tsx +1 -1
- package/src/components/gridFilter/GridFilterButtons.tsx +2 -2
- package/src/components/gridFilter/GridFilterColumnsToggle.tsx +141 -0
- package/src/components/gridFilter/GridFilterHeaderIconButton.tsx +33 -0
- package/src/components/gridFilter/GridFilterQuick.tsx +2 -2
- package/src/components/gridFilter/index.ts +3 -0
- package/src/components/{GridFilter.ts → gridFilter/useGridFilter.ts} +2 -2
- package/src/components/index.ts +0 -1
- package/src/contexts/GridContext.tsx +26 -5
- package/src/contexts/GridContextProvider.tsx +94 -29
- package/src/contexts/GridPopoverContextProvider.tsx +4 -3
- package/src/stories/grid/GridReadOnly.stories.tsx +13 -6
- package/src/styles/Grid.scss +18 -2
- package/src/styles/GridFilterColumnsToggle.scss +4 -0
- package/src/styles/GridFilterHeaderIconButton.scss +3 -0
- package/src/styles/GridTheme.scss +2 -2
- package/src/styles/index.scss +3 -0
package/package.json
CHANGED
package/src/components/Grid.tsx
CHANGED
|
@@ -11,7 +11,7 @@ import { GridContext } from "../contexts/GridContext";
|
|
|
11
11
|
import { GridUpdatingContext } from "../contexts/GridUpdatingContext";
|
|
12
12
|
import { fnOrVar, isNotEmpty } from "../utils/util";
|
|
13
13
|
import { usePostSortRowsHook } from "./PostSortRowsHook";
|
|
14
|
-
import { GridHeaderSelect } from "./gridHeader
|
|
14
|
+
import { GridHeaderSelect } from "./gridHeader";
|
|
15
15
|
|
|
16
16
|
export interface GridBaseRow {
|
|
17
17
|
id: string | number;
|
|
@@ -27,6 +27,7 @@ export interface GridProps {
|
|
|
27
27
|
defaultColDef?: GridOptions["defaultColDef"];
|
|
28
28
|
columnDefs: ColDef[];
|
|
29
29
|
rowData: GridOptions["rowData"];
|
|
30
|
+
noRowsOverlayText?: string;
|
|
30
31
|
postSortRows?: GridOptions["postSortRows"];
|
|
31
32
|
animateRows?: boolean;
|
|
32
33
|
rowClassRules?: GridOptions["rowClassRules"];
|
|
@@ -39,10 +40,10 @@ export interface GridProps {
|
|
|
39
40
|
/**
|
|
40
41
|
* Wrapper for AgGrid to add commonly used functionality.
|
|
41
42
|
*/
|
|
42
|
-
export const Grid = (params: GridProps): JSX.Element => {
|
|
43
|
+
export const Grid = ({ rowSelection = "multiple", "data-testid": dataTestId, ...params }: GridProps): JSX.Element => {
|
|
43
44
|
const {
|
|
44
45
|
gridReady,
|
|
45
|
-
|
|
46
|
+
setApis,
|
|
46
47
|
prePopupOps,
|
|
47
48
|
ensureRowVisible,
|
|
48
49
|
selectRowsById,
|
|
@@ -79,7 +80,7 @@ export const Grid = (params: GridProps): JSX.Element => {
|
|
|
79
80
|
externallySelectedItemsAreInSync,
|
|
80
81
|
focusByRowById,
|
|
81
82
|
gridReady,
|
|
82
|
-
params,
|
|
83
|
+
params.externalSelectedItems,
|
|
83
84
|
params.autoSelectFirstRow,
|
|
84
85
|
params.rowData,
|
|
85
86
|
selectRowsById,
|
|
@@ -178,11 +179,10 @@ export const Grid = (params: GridProps): JSX.Element => {
|
|
|
178
179
|
maxWidth: 42,
|
|
179
180
|
suppressSizeToFit: true,
|
|
180
181
|
checkboxSelection: true,
|
|
181
|
-
headerComponent:
|
|
182
|
+
headerComponent: rowSelection === "multiple" ? GridHeaderSelect : null,
|
|
182
183
|
suppressHeaderKeyboardEvent: (e) => {
|
|
183
184
|
if ((e.event.key === "Enter" || e.event.key === " ") && !e.event.repeat) {
|
|
184
|
-
|
|
185
|
-
if (selectedNodeCount == 0) {
|
|
185
|
+
if (isEmpty(e.api.getSelectedRows())) {
|
|
186
186
|
e.api.selectAllFiltered();
|
|
187
187
|
} else {
|
|
188
188
|
e.api.deselectAll();
|
|
@@ -199,18 +199,18 @@ export const Grid = (params: GridProps): JSX.Element => {
|
|
|
199
199
|
}, [
|
|
200
200
|
params.columnDefs,
|
|
201
201
|
params.selectable,
|
|
202
|
-
params.rowSelection,
|
|
203
202
|
params.readOnly,
|
|
204
203
|
params.defaultColDef?.editable,
|
|
204
|
+
rowSelection,
|
|
205
205
|
clickSelectorCheckboxWhenContainingCellClicked,
|
|
206
206
|
]);
|
|
207
207
|
|
|
208
208
|
const onGridReady = useCallback(
|
|
209
209
|
(event: GridReadyEvent) => {
|
|
210
|
-
|
|
210
|
+
setApis(event.api, event.columnApi, dataTestId);
|
|
211
211
|
synchroniseExternallySelectedItemsToGrid();
|
|
212
212
|
},
|
|
213
|
-
[
|
|
213
|
+
[dataTestId, setApis, synchroniseExternallySelectedItemsToGrid],
|
|
214
214
|
);
|
|
215
215
|
|
|
216
216
|
const noRowsOverlayComponent = useCallback(
|
|
@@ -218,10 +218,16 @@ export const Grid = (params: GridProps): JSX.Element => {
|
|
|
218
218
|
const hasData = (params.rowData?.length ?? 0) > 0;
|
|
219
219
|
const hasFilteredData = event.api.getDisplayedRowCount() > 0;
|
|
220
220
|
return (
|
|
221
|
-
<span>
|
|
221
|
+
<span>
|
|
222
|
+
{!hasData
|
|
223
|
+
? params.noRowsOverlayText ?? "There are currently no rows"
|
|
224
|
+
: !hasFilteredData
|
|
225
|
+
? "All rows have been filtered"
|
|
226
|
+
: ""}
|
|
227
|
+
</span>
|
|
222
228
|
);
|
|
223
229
|
},
|
|
224
|
-
[params.rowData?.length],
|
|
230
|
+
[params.noRowsOverlayText, params.rowData?.length],
|
|
225
231
|
);
|
|
226
232
|
|
|
227
233
|
const onModelUpdated = useCallback((event: ModelUpdatedEvent) => {
|
|
@@ -305,7 +311,7 @@ export const Grid = (params: GridProps): JSX.Element => {
|
|
|
305
311
|
|
|
306
312
|
return (
|
|
307
313
|
<div
|
|
308
|
-
data-testid={
|
|
314
|
+
data-testid={dataTestId}
|
|
309
315
|
className={clsx(
|
|
310
316
|
"Grid-container",
|
|
311
317
|
"ag-theme-alpine",
|
|
@@ -319,7 +325,7 @@ export const Grid = (params: GridProps): JSX.Element => {
|
|
|
319
325
|
rowClassRules={params.rowClassRules}
|
|
320
326
|
getRowId={(params) => `${params.data.id}`}
|
|
321
327
|
suppressRowClickSelection={true}
|
|
322
|
-
rowSelection={
|
|
328
|
+
rowSelection={rowSelection}
|
|
323
329
|
suppressBrowserResizeObserver={true}
|
|
324
330
|
colResizeDefault={"shift"}
|
|
325
331
|
onFirstDataRendered={sizeColumnsToFit}
|
|
@@ -6,7 +6,7 @@ import { LuiButtonProps } from "@linzjs/lui/dist/components/LuiButton/LuiButton"
|
|
|
6
6
|
|
|
7
7
|
import { GridFilterExternal } from "../../contexts/GridContext";
|
|
8
8
|
import { GridBaseRow } from "../Grid";
|
|
9
|
-
import { useGridFilter } from "
|
|
9
|
+
import { useGridFilter } from "./useGridFilter";
|
|
10
10
|
|
|
11
11
|
export interface GridFilterButtonsOption<RowType extends GridBaseRow> {
|
|
12
12
|
defaultSelected?: boolean;
|
|
@@ -32,7 +32,7 @@ export const GridFilterButtons = <RowType extends GridBaseRow>({
|
|
|
32
32
|
useGridFilter(filter);
|
|
33
33
|
|
|
34
34
|
return (
|
|
35
|
-
<div className={clsx("
|
|
35
|
+
<div className={clsx(className, "flex-col-center")}>
|
|
36
36
|
<LuiButtonGroup>
|
|
37
37
|
{options.map((option, index) => (
|
|
38
38
|
<LuiButton
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { isEmpty } from "lodash-es";
|
|
2
|
+
import { useCallback, useContext, useEffect, useMemo, useState } from "react";
|
|
3
|
+
|
|
4
|
+
import { LuiCheckboxInput, LuiIcon } from "@linzjs/lui";
|
|
5
|
+
|
|
6
|
+
import { GridContext } from "../../contexts/GridContext";
|
|
7
|
+
import { Menu, MenuDivider, MenuItem } from "../../react-menu3";
|
|
8
|
+
import { ClickEvent } from "../../react-menu3/types";
|
|
9
|
+
import { GridBaseRow } from "../Grid";
|
|
10
|
+
import { ColDefT } from "../GridCell";
|
|
11
|
+
import { GridFilterHeaderIconButton } from "./GridFilterHeaderIconButton";
|
|
12
|
+
|
|
13
|
+
export interface GridFilterColumnsToggleProps {
|
|
14
|
+
saveState?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const GridFilterColumnsToggle = ({ saveState = true }: GridFilterColumnsToggleProps): JSX.Element => {
|
|
18
|
+
const [loaded, setLoaded] = useState(false);
|
|
19
|
+
const { getColumns, invisibleColumnIds, setInvisibleColumnIds } = useContext(GridContext);
|
|
20
|
+
|
|
21
|
+
const columnStorageKey = useMemo(
|
|
22
|
+
() =>
|
|
23
|
+
isEmpty(getColumns())
|
|
24
|
+
? null // Grid hasn't been initialised yet
|
|
25
|
+
: "stepAgGrid_invisibleColumnIds_" +
|
|
26
|
+
getColumns()
|
|
27
|
+
.map((col) => col.colId || "")
|
|
28
|
+
.join("_"),
|
|
29
|
+
[getColumns],
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
// Load state on start
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (!columnStorageKey || loaded) return;
|
|
35
|
+
if (saveState) {
|
|
36
|
+
try {
|
|
37
|
+
const stored = window.localStorage.getItem(columnStorageKey);
|
|
38
|
+
const invisibleIds = JSON.parse(stored ?? "[]");
|
|
39
|
+
if (!Array.isArray(invisibleIds)) {
|
|
40
|
+
console.error(`stored invisible ids not an array: ${stored}`);
|
|
41
|
+
} else if (!invisibleIds.every((id) => typeof id === "string")) {
|
|
42
|
+
console.error(`stored invisible ids not strings: ${stored}`);
|
|
43
|
+
} else {
|
|
44
|
+
invisibleIds && setInvisibleColumnIds(invisibleIds);
|
|
45
|
+
}
|
|
46
|
+
} catch (ex) {
|
|
47
|
+
console.error(ex);
|
|
48
|
+
}
|
|
49
|
+
setLoaded(true);
|
|
50
|
+
}
|
|
51
|
+
}, [columnStorageKey, loaded, saveState, setInvisibleColumnIds]);
|
|
52
|
+
|
|
53
|
+
// Save state on column visibility change
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
loaded &&
|
|
56
|
+
columnStorageKey &&
|
|
57
|
+
saveState &&
|
|
58
|
+
window.localStorage.setItem(columnStorageKey, JSON.stringify(invisibleColumnIds));
|
|
59
|
+
}, [columnStorageKey, invisibleColumnIds, loaded, saveState]);
|
|
60
|
+
|
|
61
|
+
const toggleColumn = useCallback(
|
|
62
|
+
(colId?: string) => {
|
|
63
|
+
if (!colId) return;
|
|
64
|
+
setInvisibleColumnIds(
|
|
65
|
+
invisibleColumnIds.includes(colId)
|
|
66
|
+
? invisibleColumnIds.filter((id) => id !== colId)
|
|
67
|
+
: [...invisibleColumnIds, colId],
|
|
68
|
+
);
|
|
69
|
+
},
|
|
70
|
+
[invisibleColumnIds, setInvisibleColumnIds],
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const resetColumns = () => {
|
|
74
|
+
setInvisibleColumnIds([]);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const numericRegExp = /^\d+$/;
|
|
78
|
+
const isNonManageableColumn = (col: ColDefT<GridBaseRow>) => {
|
|
79
|
+
return col.lockVisible || col.colId == null || numericRegExp.test(col.colId);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<Menu
|
|
84
|
+
menuButton={<GridFilterHeaderIconButton icon={"ic_columns"} title={"Column visibility"} />}
|
|
85
|
+
menuClassName={"step-ag-grid-react-menu"}
|
|
86
|
+
portal={true}
|
|
87
|
+
unmountOnClose={true}
|
|
88
|
+
>
|
|
89
|
+
<div className={"GridFilterColumnsToggle-container"}>
|
|
90
|
+
{getColumns()
|
|
91
|
+
.filter((col) => !!col.headerName)
|
|
92
|
+
.map((col) => (
|
|
93
|
+
<MenuItem
|
|
94
|
+
key={col.colId}
|
|
95
|
+
disabled={col.lockVisible}
|
|
96
|
+
onClick={(e: ClickEvent) => {
|
|
97
|
+
// Global react-menu MenuItem handler handles tabs
|
|
98
|
+
if (e.key !== "Tab") {
|
|
99
|
+
e.keepOpen = true;
|
|
100
|
+
if (e.key !== "Enter") {
|
|
101
|
+
toggleColumn(col.colId);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}}
|
|
105
|
+
>
|
|
106
|
+
<LuiCheckboxInput
|
|
107
|
+
isChecked={!invisibleColumnIds.includes(col.colId ?? "")}
|
|
108
|
+
value={`${col.colId}`}
|
|
109
|
+
label={col.headerName ?? ""}
|
|
110
|
+
isDisabled={isNonManageableColumn(col)}
|
|
111
|
+
inputProps={{
|
|
112
|
+
onClick: (e) => {
|
|
113
|
+
// Click is handled by MenuItem onClick so keyboard events work
|
|
114
|
+
e.preventDefault();
|
|
115
|
+
e.stopPropagation();
|
|
116
|
+
},
|
|
117
|
+
}}
|
|
118
|
+
onChange={() => {
|
|
119
|
+
/*Do nothing, change handled by menuItem*/
|
|
120
|
+
}}
|
|
121
|
+
/>
|
|
122
|
+
</MenuItem>
|
|
123
|
+
))}
|
|
124
|
+
</div>
|
|
125
|
+
<MenuDivider key={`$$divider_reset_columns`} />
|
|
126
|
+
<MenuItem
|
|
127
|
+
key={"$$reset_columns"}
|
|
128
|
+
onClick={(e: ClickEvent) => {
|
|
129
|
+
// Global react-menu MenuItem handler handles tabs
|
|
130
|
+
if (e.key !== "Tab") {
|
|
131
|
+
e.keepOpen = e.key !== "Enter";
|
|
132
|
+
resetColumns();
|
|
133
|
+
}
|
|
134
|
+
}}
|
|
135
|
+
>
|
|
136
|
+
<LuiIcon name={"ic_regenerate"} alt={"Reset columns"} size={"md"} className={"MenuItem-icon"} />
|
|
137
|
+
Reset columns
|
|
138
|
+
</MenuItem>
|
|
139
|
+
</Menu>
|
|
140
|
+
);
|
|
141
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ButtonHTMLAttributes, MouseEvent, forwardRef } from "react";
|
|
2
|
+
|
|
3
|
+
import { LuiButton, LuiIcon } from "@linzjs/lui";
|
|
4
|
+
import { IconName, IconSize } from "@linzjs/lui/dist/components/LuiIcon/LuiIcon";
|
|
5
|
+
|
|
6
|
+
export interface GridFilterHeaderIconButtonProps {
|
|
7
|
+
icon: IconName;
|
|
8
|
+
onClick?: (e: MouseEvent) => void;
|
|
9
|
+
buttonProps?: Partial<ButtonHTMLAttributes<HTMLButtonElement>>;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
size?: IconSize;
|
|
12
|
+
title: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const GridFilterHeaderIconButton = forwardRef<HTMLButtonElement, GridFilterHeaderIconButtonProps>(
|
|
16
|
+
function columnsButton({ icon, title, onClick, buttonProps, disabled = false, size = "md" }, ref) {
|
|
17
|
+
return (
|
|
18
|
+
<LuiButton
|
|
19
|
+
{...buttonProps}
|
|
20
|
+
type={"button"}
|
|
21
|
+
level={"tertiary"}
|
|
22
|
+
className={"GridFilterHeaderIconButton lui-button-icon-only"}
|
|
23
|
+
ref={ref}
|
|
24
|
+
aria-label={title}
|
|
25
|
+
title={title}
|
|
26
|
+
onClick={onClick}
|
|
27
|
+
disabled={disabled}
|
|
28
|
+
>
|
|
29
|
+
<LuiIcon name={icon} alt={"Menu"} size={size} />
|
|
30
|
+
</LuiButton>
|
|
31
|
+
);
|
|
32
|
+
},
|
|
33
|
+
);
|
|
@@ -18,11 +18,11 @@ export const GridFilterQuick = ({ quickFilterPlaceholder, defaultValue }: GridFi
|
|
|
18
18
|
return (
|
|
19
19
|
<input
|
|
20
20
|
aria-label="Search"
|
|
21
|
-
className="
|
|
21
|
+
className="Grid-quickFilterBox"
|
|
22
22
|
type="text"
|
|
23
23
|
placeholder={quickFilterPlaceholder ?? "Search..."}
|
|
24
24
|
value={quickFilterValue}
|
|
25
|
-
onChange={(event)
|
|
25
|
+
onChange={(event) => {
|
|
26
26
|
setQuickFilterValue(event.target.value);
|
|
27
27
|
}}
|
|
28
28
|
/>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useContext, useEffect } from "react";
|
|
2
2
|
|
|
3
|
-
import { GridContext, GridFilterExternal } from "
|
|
4
|
-
import { GridBaseRow } from "
|
|
3
|
+
import { GridContext, GridFilterExternal } from "../../contexts/GridContext";
|
|
4
|
+
import { GridBaseRow } from "../Grid";
|
|
5
5
|
|
|
6
6
|
export const useGridFilter = <RowType extends GridBaseRow>(filter: GridFilterExternal<RowType> | undefined) => {
|
|
7
7
|
const { addExternalFilter, removeExternalFilter } = useContext(GridContext);
|
package/src/components/index.ts
CHANGED
|
@@ -4,7 +4,6 @@ export * from "./GridCell";
|
|
|
4
4
|
export * from "./GridCellMultiEditor";
|
|
5
5
|
export * from "./GridCellMultiSelectClassRules";
|
|
6
6
|
export * from "./gridFilter";
|
|
7
|
-
export * from "./GridFilter";
|
|
8
7
|
export * from "./gridForm";
|
|
9
8
|
export * from "./gridHeader";
|
|
10
9
|
export * from "./GridIcon";
|
|
@@ -1,18 +1,20 @@
|
|
|
1
|
-
import { GridApi, RowNode } from "ag-grid-community";
|
|
1
|
+
import { ColumnApi, GridApi, RowNode } from "ag-grid-community";
|
|
2
2
|
import { createContext, useContext } from "react";
|
|
3
3
|
|
|
4
|
-
import { GridBaseRow } from "../components
|
|
4
|
+
import { ColDefT, GridBaseRow } from "../components";
|
|
5
5
|
|
|
6
6
|
export type GridFilterExternal<RowType extends GridBaseRow> = (data: RowType, rowNode: RowNode) => boolean;
|
|
7
7
|
|
|
8
8
|
export interface GridContextType<RowType extends GridBaseRow> {
|
|
9
9
|
gridReady: boolean;
|
|
10
|
-
|
|
10
|
+
setApis: (gridApi: GridApi | undefined, columnApi: ColumnApi | undefined, dataTestId?: string) => void;
|
|
11
11
|
prePopupOps: () => void;
|
|
12
12
|
setQuickFilter: (quickFilter: string) => void;
|
|
13
13
|
editingCells: () => boolean;
|
|
14
14
|
getSelectedRows: <T extends GridBaseRow>() => T[];
|
|
15
|
+
getFilteredSelectedRows: <T extends GridBaseRow>() => T[];
|
|
15
16
|
getSelectedRowIds: () => number[];
|
|
17
|
+
getFilteredSelectedRowIds: () => number[];
|
|
16
18
|
selectRowsDiff: (updateFn: () => Promise<any>) => Promise<void>;
|
|
17
19
|
selectRowsWithFlashDiff: (updateFn: () => Promise<any>) => Promise<void>;
|
|
18
20
|
selectRowsById: (rowIds?: number[]) => void;
|
|
@@ -38,16 +40,27 @@ export interface GridContextType<RowType extends GridBaseRow> {
|
|
|
38
40
|
removeExternalFilter: (filter: GridFilterExternal<RowType>) => void;
|
|
39
41
|
isExternalFilterPresent: () => boolean;
|
|
40
42
|
doesExternalFilterPass: (node: RowNode) => boolean;
|
|
43
|
+
getColumns: () => ColDefT<RowType>[];
|
|
44
|
+
invisibleColumnIds: string[];
|
|
45
|
+
setInvisibleColumnIds: (colIds: string[]) => void;
|
|
41
46
|
}
|
|
42
47
|
|
|
43
48
|
export const GridContext = createContext<GridContextType<any>>({
|
|
44
49
|
gridReady: false,
|
|
50
|
+
getColumns: () => {
|
|
51
|
+
console.error("no context provider for getColumns");
|
|
52
|
+
return [];
|
|
53
|
+
},
|
|
54
|
+
invisibleColumnIds: [],
|
|
55
|
+
setInvisibleColumnIds: () => {
|
|
56
|
+
console.error("no context provider for setInvisibleColumnIds");
|
|
57
|
+
},
|
|
45
58
|
prePopupOps: () => {
|
|
46
59
|
console.error("no context provider for prePopupOps");
|
|
47
60
|
},
|
|
48
61
|
externallySelectedItemsAreInSync: false,
|
|
49
|
-
|
|
50
|
-
console.error("no context provider for
|
|
62
|
+
setApis: () => {
|
|
63
|
+
console.error("no context provider for setApis");
|
|
51
64
|
},
|
|
52
65
|
setQuickFilter: () => {
|
|
53
66
|
console.error("no context provider for setQuickFilter");
|
|
@@ -59,10 +72,18 @@ export const GridContext = createContext<GridContextType<any>>({
|
|
|
59
72
|
console.error("no context provider for getSelectedRows");
|
|
60
73
|
return [];
|
|
61
74
|
},
|
|
75
|
+
getFilteredSelectedRows: <T extends unknown>(): T[] => {
|
|
76
|
+
console.error("no context provider for getFilteredSelectedRows");
|
|
77
|
+
return [];
|
|
78
|
+
},
|
|
62
79
|
getSelectedRowIds: () => {
|
|
63
80
|
console.error("no context provider for getSelectedRowIds");
|
|
64
81
|
return [];
|
|
65
82
|
},
|
|
83
|
+
getFilteredSelectedRowIds: () => {
|
|
84
|
+
console.error("no context provider for getFilteredSelectedRowIds");
|
|
85
|
+
return [];
|
|
86
|
+
},
|
|
66
87
|
selectRowsDiff: async () => {
|
|
67
88
|
console.error("no context provider for selectRowsDiff");
|
|
68
89
|
},
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { ColDef, GridApi, RowNode } from "ag-grid-community";
|
|
1
|
+
import { ColDef, ColumnApi, GridApi, RowNode } from "ag-grid-community";
|
|
2
2
|
import { CellPosition } from "ag-grid-community/dist/lib/entities/cellPosition";
|
|
3
|
-
import { debounce, defer, delay, difference, isEmpty, last, remove, sortBy } from "lodash-es";
|
|
3
|
+
import { compact, debounce, defer, delay, difference, isEmpty, last, remove, sortBy } from "lodash-es";
|
|
4
4
|
import { ReactElement, ReactNode, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
5
5
|
|
|
6
|
-
import { GridBaseRow } from "../components
|
|
6
|
+
import { ColDefT, GridBaseRow } from "../components";
|
|
7
7
|
import { isNotEmpty, wait } from "../utils/util";
|
|
8
8
|
import { GridContext, GridFilterExternal } from "./GridContext";
|
|
9
9
|
import { GridUpdatingContext } from "./GridUpdatingContext";
|
|
@@ -19,9 +19,12 @@ interface GridContextProps {
|
|
|
19
19
|
*/
|
|
20
20
|
export const GridContextProvider = <RowType extends GridBaseRow>(props: GridContextProps): ReactElement => {
|
|
21
21
|
const { modifyUpdating } = useContext(GridUpdatingContext);
|
|
22
|
-
const [gridApi,
|
|
22
|
+
const [gridApi, setGridApi] = useState<GridApi>();
|
|
23
|
+
const [columnApi, setColumnApi] = useState<ColumnApi>();
|
|
23
24
|
const [gridReady, setGridReady] = useState(false);
|
|
24
25
|
const [quickFilter, setQuickFilter] = useState("");
|
|
26
|
+
const [invisibleColumnIds, setInvisibleColumnIds] = useState<string[]>([]);
|
|
27
|
+
const testId = useRef<string | undefined>();
|
|
25
28
|
const idsBeforeUpdate = useRef<number[]>([]);
|
|
26
29
|
const prePopupFocusedCell = useRef<CellPosition>();
|
|
27
30
|
const [externallySelectedItemsAreInSync, setExternallySelectedItemsAreInSync] = useState(false);
|
|
@@ -34,18 +37,6 @@ export const GridContextProvider = <RowType extends GridBaseRow>(props: GridCont
|
|
|
34
37
|
gridApi?.setQuickFilter(quickFilter);
|
|
35
38
|
}, [gridApi, quickFilter]);
|
|
36
39
|
|
|
37
|
-
/**
|
|
38
|
-
* Set the grid api when the grid is ready.
|
|
39
|
-
*/
|
|
40
|
-
const setGridApi = useCallback(
|
|
41
|
-
(gridApi: GridApi | undefined) => {
|
|
42
|
-
_setGridApi(gridApi);
|
|
43
|
-
gridApi?.setQuickFilter(quickFilter);
|
|
44
|
-
setGridReady(!!gridApi);
|
|
45
|
-
},
|
|
46
|
-
[quickFilter],
|
|
47
|
-
);
|
|
48
|
-
|
|
49
40
|
/**
|
|
50
41
|
* Wraps things that require gridApi in common handling, for when gridApi not present.
|
|
51
42
|
*
|
|
@@ -62,6 +53,54 @@ export const GridContextProvider = <RowType extends GridBaseRow>(props: GridCont
|
|
|
62
53
|
[gridApi],
|
|
63
54
|
);
|
|
64
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Scroll row into view by Id.
|
|
58
|
+
*
|
|
59
|
+
* @param id Id to scroll into view
|
|
60
|
+
* @return true if row is found, else false
|
|
61
|
+
*/
|
|
62
|
+
const ensureRowVisible = useCallback(
|
|
63
|
+
(id: number | string): boolean => {
|
|
64
|
+
return gridApiOp((gridApi) => {
|
|
65
|
+
const node = gridApi.getRowNode(`${id}`);
|
|
66
|
+
if (!node) return false;
|
|
67
|
+
gridApi.ensureNodeVisible(node);
|
|
68
|
+
return true;
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
[gridApiOp],
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Set the grid api when the grid is ready.
|
|
76
|
+
*/
|
|
77
|
+
const setApis = useCallback(
|
|
78
|
+
(gridApi: GridApi | undefined, columnApi: ColumnApi | undefined, dataTestId?: string) => {
|
|
79
|
+
testId.current = dataTestId;
|
|
80
|
+
setGridApi(gridApi);
|
|
81
|
+
setColumnApi(columnApi);
|
|
82
|
+
gridApi?.setQuickFilter(quickFilter);
|
|
83
|
+
setGridReady(!!gridApi);
|
|
84
|
+
},
|
|
85
|
+
[quickFilter],
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Expose scrollRowIntoView for playwright tests.
|
|
90
|
+
*/
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
const globalSupport = (window as any).__stepAgGrid || ((window as any).__stepAgGrid = { grids: {} });
|
|
93
|
+
if (testId.current) {
|
|
94
|
+
globalSupport.grids[testId.current] = {
|
|
95
|
+
scrollRowIntoViewById: (rowId: number | string) => {
|
|
96
|
+
if (!ensureRowVisible(rowId)) {
|
|
97
|
+
throw `scrollRowIntoView failed on grid '${testId.current}' as row with id: '${rowId}' was not found`;
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}, [ensureRowVisible]);
|
|
103
|
+
|
|
65
104
|
/**
|
|
66
105
|
* Before a popup record the currently focused cell.
|
|
67
106
|
*/
|
|
@@ -247,11 +286,26 @@ export const GridContextProvider = <RowType extends GridBaseRow>(props: GridCont
|
|
|
247
286
|
);
|
|
248
287
|
}, [gridApiOp]);
|
|
249
288
|
|
|
289
|
+
const getFilteredSelectedRows = useCallback(<T extends unknown>(): T[] => {
|
|
290
|
+
return gridApiOp((gridApi) => {
|
|
291
|
+
const rowData: T[] = [];
|
|
292
|
+
gridApi.forEachNodeAfterFilter((node) => {
|
|
293
|
+
if (node.isSelected()) rowData.push(node.data);
|
|
294
|
+
});
|
|
295
|
+
return rowData;
|
|
296
|
+
});
|
|
297
|
+
}, [gridApiOp]);
|
|
298
|
+
|
|
250
299
|
const getSelectedRowIds = useCallback(
|
|
251
300
|
(): number[] => getSelectedRows().map((row) => (row as any).id as number),
|
|
252
301
|
[getSelectedRows],
|
|
253
302
|
);
|
|
254
303
|
|
|
304
|
+
const getFilteredSelectedRowIds = useCallback(
|
|
305
|
+
(): number[] => getFilteredSelectedRows().map((row) => (row as any).id as number),
|
|
306
|
+
[getFilteredSelectedRows],
|
|
307
|
+
);
|
|
308
|
+
|
|
255
309
|
const editingCells = useCallback((): boolean => {
|
|
256
310
|
return gridApiOp(
|
|
257
311
|
(gridApi) => isNotEmpty(gridApi.getEditingCells()),
|
|
@@ -259,18 +313,6 @@ export const GridContextProvider = <RowType extends GridBaseRow>(props: GridCont
|
|
|
259
313
|
);
|
|
260
314
|
}, [gridApiOp]);
|
|
261
315
|
|
|
262
|
-
const ensureRowVisible = useCallback(
|
|
263
|
-
(id: number | string): boolean => {
|
|
264
|
-
return gridApiOp((gridApi) => {
|
|
265
|
-
const node = gridApi.getRowNode(`${id}`);
|
|
266
|
-
if (!node) return false;
|
|
267
|
-
gridApi.ensureNodeVisible(node);
|
|
268
|
-
return true;
|
|
269
|
-
});
|
|
270
|
-
},
|
|
271
|
-
[gridApiOp],
|
|
272
|
-
);
|
|
273
|
-
|
|
274
316
|
/**
|
|
275
317
|
* Scroll last selected row into view on grid sort change
|
|
276
318
|
*/
|
|
@@ -407,12 +449,33 @@ export const GridContextProvider = <RowType extends GridBaseRow>(props: GridCont
|
|
|
407
449
|
return externalFilters.current.every((filter) => filter(node.data, node));
|
|
408
450
|
};
|
|
409
451
|
|
|
452
|
+
const getColumns: () => ColDefT<RowType>[] = useCallback(() => gridApi?.getColumnDefs() ?? [], [gridApi]);
|
|
453
|
+
|
|
454
|
+
useEffect(() => {
|
|
455
|
+
if (columnApi) {
|
|
456
|
+
// show all columns that aren't invisible
|
|
457
|
+
columnApi.setColumnsVisible(
|
|
458
|
+
compact(
|
|
459
|
+
getColumns()
|
|
460
|
+
.filter((col) => !col.lockVisible && col.colId && !invisibleColumnIds.includes(col.colId))
|
|
461
|
+
.map((col) => col.colId),
|
|
462
|
+
),
|
|
463
|
+
true,
|
|
464
|
+
);
|
|
465
|
+
// hide all invisible columns
|
|
466
|
+
columnApi.setColumnsVisible(invisibleColumnIds, false);
|
|
467
|
+
}
|
|
468
|
+
}, [invisibleColumnIds, columnApi, getColumns]);
|
|
469
|
+
|
|
410
470
|
return (
|
|
411
471
|
<GridContext.Provider
|
|
412
472
|
value={{
|
|
473
|
+
getColumns,
|
|
474
|
+
invisibleColumnIds,
|
|
475
|
+
setInvisibleColumnIds,
|
|
413
476
|
gridReady,
|
|
414
477
|
prePopupOps,
|
|
415
|
-
|
|
478
|
+
setApis,
|
|
416
479
|
setQuickFilter,
|
|
417
480
|
selectRowsById,
|
|
418
481
|
selectRowsDiff,
|
|
@@ -422,7 +485,9 @@ export const GridContextProvider = <RowType extends GridBaseRow>(props: GridCont
|
|
|
422
485
|
flashRowsDiff,
|
|
423
486
|
focusByRowById,
|
|
424
487
|
getSelectedRows,
|
|
488
|
+
getFilteredSelectedRows,
|
|
425
489
|
getSelectedRowIds,
|
|
490
|
+
getFilteredSelectedRowIds,
|
|
426
491
|
editingCells,
|
|
427
492
|
ensureRowVisible,
|
|
428
493
|
ensureSelectedRowIsVisible,
|
|
@@ -12,7 +12,7 @@ interface GridPopoverContextProps {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export const GridPopoverContextProvider = ({ props, children }: GridPopoverContextProps) => {
|
|
15
|
-
const {
|
|
15
|
+
const { getFilteredSelectedRows, updatingCells } = useContext(GridContext);
|
|
16
16
|
const anchorRef = useRef<Element>(props.eGridCell);
|
|
17
17
|
|
|
18
18
|
const hasSaved = useRef(false);
|
|
@@ -23,8 +23,9 @@ export const GridPopoverContextProvider = ({ props, children }: GridPopoverConte
|
|
|
23
23
|
const multiEdit = cellEditorParams?.multiEdit ?? false;
|
|
24
24
|
// Then item that is clicked on will always be first in the list
|
|
25
25
|
const selectedRows = useMemo(
|
|
26
|
-
() =>
|
|
27
|
-
|
|
26
|
+
() =>
|
|
27
|
+
multiEdit ? sortBy(getFilteredSelectedRows(), (row) => row.id !== props.data.id) : [props.data as GridBaseRow],
|
|
28
|
+
[getFilteredSelectedRows, multiEdit, props.data],
|
|
28
29
|
);
|
|
29
30
|
const field = props.colDef?.field ?? "";
|
|
30
31
|
|