@linzjs/step-ag-grid 13.0.0 → 13.1.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.
- package/README.md +36 -1
- package/dist/GridTheme.scss +2 -2
- package/dist/index.css +26 -2
- package/dist/src/components/Grid.d.ts +1 -0
- 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 +395 -228
- package/dist/step-ag-grid.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/components/Grid.tsx +13 -6
- 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"];
|
|
@@ -42,7 +43,7 @@ export interface GridProps {
|
|
|
42
43
|
export const Grid = (params: GridProps): JSX.Element => {
|
|
43
44
|
const {
|
|
44
45
|
gridReady,
|
|
45
|
-
|
|
46
|
+
setApis,
|
|
46
47
|
prePopupOps,
|
|
47
48
|
ensureRowVisible,
|
|
48
49
|
selectRowsById,
|
|
@@ -207,10 +208,10 @@ export const Grid = (params: GridProps): JSX.Element => {
|
|
|
207
208
|
|
|
208
209
|
const onGridReady = useCallback(
|
|
209
210
|
(event: GridReadyEvent) => {
|
|
210
|
-
|
|
211
|
+
setApis(event.api, event.columnApi, params["data-testid"]);
|
|
211
212
|
synchroniseExternallySelectedItemsToGrid();
|
|
212
213
|
},
|
|
213
|
-
[
|
|
214
|
+
[setApis, synchroniseExternallySelectedItemsToGrid],
|
|
214
215
|
);
|
|
215
216
|
|
|
216
217
|
const noRowsOverlayComponent = useCallback(
|
|
@@ -218,10 +219,16 @@ export const Grid = (params: GridProps): JSX.Element => {
|
|
|
218
219
|
const hasData = (params.rowData?.length ?? 0) > 0;
|
|
219
220
|
const hasFilteredData = event.api.getDisplayedRowCount() > 0;
|
|
220
221
|
return (
|
|
221
|
-
<span>
|
|
222
|
+
<span>
|
|
223
|
+
{!hasData
|
|
224
|
+
? params.noRowsOverlayText ?? "There are currently no rows"
|
|
225
|
+
: !hasFilteredData
|
|
226
|
+
? "All rows have been filtered"
|
|
227
|
+
: ""}
|
|
228
|
+
</span>
|
|
222
229
|
);
|
|
223
230
|
},
|
|
224
|
-
[params.rowData?.length],
|
|
231
|
+
[params.noRowsOverlayText, params.rowData?.length],
|
|
225
232
|
);
|
|
226
233
|
|
|
227
234
|
const onModelUpdated = useCallback((event: ModelUpdatedEvent) => {
|
|
@@ -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
|
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
useGridFilter,
|
|
24
24
|
wait,
|
|
25
25
|
} from "../..";
|
|
26
|
+
import { GridFilterColumnsToggle } from "../../components";
|
|
26
27
|
import "../../styles/GridTheme.scss";
|
|
27
28
|
import "../../styles/index.scss";
|
|
28
29
|
|
|
@@ -66,6 +67,7 @@ const GridReadOnlyTemplate: ComponentStory<typeof Grid> = (props: GridProps) =>
|
|
|
66
67
|
headerName: "Id",
|
|
67
68
|
initialWidth: 65,
|
|
68
69
|
maxWidth: 85,
|
|
70
|
+
lockVisible: true,
|
|
69
71
|
}),
|
|
70
72
|
GridCell({
|
|
71
73
|
field: "position",
|
|
@@ -213,10 +215,7 @@ const GridReadOnlyTemplate: ComponentStory<typeof Grid> = (props: GridProps) =>
|
|
|
213
215
|
<GridWrapper maxHeight={300}>
|
|
214
216
|
<GridFilters>
|
|
215
217
|
<GridFilterQuick quickFilterPlaceholder={"Custom placeholder..."} />
|
|
216
|
-
<
|
|
217
|
-
Custom filter: Age less than:
|
|
218
|
-
<GridFilterLessThan field={"age"} />
|
|
219
|
-
</div>
|
|
218
|
+
<GridFilterLessThan text="Age <" field={"age"} />
|
|
220
219
|
<GridFilterButtons<ITestRow>
|
|
221
220
|
luiButtonProps={{ style: { whiteSpace: "nowrap" } }}
|
|
222
221
|
options={[
|
|
@@ -229,8 +228,10 @@ const GridReadOnlyTemplate: ComponentStory<typeof Grid> = (props: GridProps) =>
|
|
|
229
228
|
},
|
|
230
229
|
]}
|
|
231
230
|
/>
|
|
231
|
+
<GridFilterColumnsToggle />
|
|
232
232
|
</GridFilters>
|
|
233
233
|
<Grid
|
|
234
|
+
data-testid={"readonly"}
|
|
234
235
|
{...props}
|
|
235
236
|
selectable={true}
|
|
236
237
|
autoSelectFirstRow={true}
|
|
@@ -243,7 +244,7 @@ const GridReadOnlyTemplate: ComponentStory<typeof Grid> = (props: GridProps) =>
|
|
|
243
244
|
);
|
|
244
245
|
};
|
|
245
246
|
|
|
246
|
-
const GridFilterLessThan = (props: { field: keyof ITestRow }): JSX.Element => {
|
|
247
|
+
const GridFilterLessThan = (props: { field: keyof ITestRow; text: string }): JSX.Element => {
|
|
247
248
|
const [value, setValue] = useState<number>();
|
|
248
249
|
|
|
249
250
|
const filter = useCallback(
|
|
@@ -261,7 +262,13 @@ const GridFilterLessThan = (props: { field: keyof ITestRow }): JSX.Element => {
|
|
|
261
262
|
}
|
|
262
263
|
};
|
|
263
264
|
|
|
264
|
-
return
|
|
265
|
+
return (
|
|
266
|
+
<div className={"flex-row-center"}>
|
|
267
|
+
<div style={{ whiteSpace: "nowrap" }}>{props.text}</div>
|
|
268
|
+
 
|
|
269
|
+
<input type={"text"} defaultValue={value} onChange={(e) => updateValue(e.target.value)} style={{ width: 64 }} />
|
|
270
|
+
</div>
|
|
271
|
+
);
|
|
265
272
|
};
|
|
266
273
|
|
|
267
274
|
export const ReadOnlySingleSelection = GridReadOnlyTemplate.bind({});
|