@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.
Files changed (33) hide show
  1. package/README.md +36 -1
  2. package/dist/GridTheme.scss +2 -2
  3. package/dist/index.css +26 -2
  4. package/dist/src/components/Grid.d.ts +1 -0
  5. package/dist/src/components/GridCell.d.ts +1 -1
  6. package/dist/src/components/gridFilter/GridFilterColumnsToggle.d.ts +5 -0
  7. package/dist/src/components/gridFilter/GridFilterHeaderIconButton.d.ts +11 -0
  8. package/dist/src/components/gridFilter/index.d.ts +3 -0
  9. package/dist/src/components/{GridFilter.d.ts → gridFilter/useGridFilter.d.ts} +2 -2
  10. package/dist/src/components/index.d.ts +0 -1
  11. package/dist/src/contexts/GridContext.d.ts +8 -3
  12. package/dist/src/contexts/GridContextProvider.d.ts +1 -1
  13. package/dist/step-ag-grid.esm.js +395 -228
  14. package/dist/step-ag-grid.esm.js.map +1 -1
  15. package/package.json +1 -1
  16. package/src/components/Grid.tsx +13 -6
  17. package/src/components/GridCell.tsx +1 -1
  18. package/src/components/gridFilter/GridFilterButtons.tsx +2 -2
  19. package/src/components/gridFilter/GridFilterColumnsToggle.tsx +141 -0
  20. package/src/components/gridFilter/GridFilterHeaderIconButton.tsx +33 -0
  21. package/src/components/gridFilter/GridFilterQuick.tsx +2 -2
  22. package/src/components/gridFilter/index.ts +3 -0
  23. package/src/components/{GridFilter.ts → gridFilter/useGridFilter.ts} +2 -2
  24. package/src/components/index.ts +0 -1
  25. package/src/contexts/GridContext.tsx +26 -5
  26. package/src/contexts/GridContextProvider.tsx +94 -29
  27. package/src/contexts/GridPopoverContextProvider.tsx +4 -3
  28. package/src/stories/grid/GridReadOnly.stories.tsx +13 -6
  29. package/src/styles/Grid.scss +18 -2
  30. package/src/styles/GridFilterColumnsToggle.scss +4 -0
  31. package/src/styles/GridFilterHeaderIconButton.scss +3 -0
  32. package/src/styles/GridTheme.scss +2 -2
  33. package/src/styles/index.scss +3 -0
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": "13.0.0",
5
+ "version": "13.1.0",
6
6
  "keywords": [
7
7
  "aggrid",
8
8
  "ag-grid",
@@ -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/GridHeaderSelect";
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
- setGridApi,
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
- setGridApi(event.api);
211
+ setApis(event.api, event.columnApi, params["data-testid"]);
211
212
  synchroniseExternallySelectedItemsToGrid();
212
213
  },
213
- [setGridApi, synchroniseExternallySelectedItemsToGrid],
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>{!hasData ? "There are currently no rows" : !hasFilteredData ? "All rows have been filtered" : ""}</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) => {
@@ -14,7 +14,7 @@ import {
14
14
  GenericCellRendererParams,
15
15
  RowValueFormatterParams,
16
16
  RowValueGetterParams,
17
- } from "./gridRender/GridRenderGenericCell";
17
+ } from "./gridRender";
18
18
 
19
19
  export interface GenericCellEditorProps<E> {
20
20
  multiEdit?: boolean;
@@ -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 "../GridFilter";
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("lui-margin-top-xxs lui-margin-bottom-xxs", className)}>
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="lui-margin-top-xxs lui-margin-bottom-xxs Grid-quickFilterBox"
21
+ className="Grid-quickFilterBox"
22
22
  type="text"
23
23
  placeholder={quickFilterPlaceholder ?? "Search..."}
24
24
  value={quickFilterValue}
25
- onChange={(event): void => {
25
+ onChange={(event) => {
26
26
  setQuickFilterValue(event.target.value);
27
27
  }}
28
28
  />
@@ -1,3 +1,6 @@
1
+ export * from "./GridFilterHeaderIconButton";
2
+ export * from "./GridFilterColumnsToggle";
1
3
  export * from "./GridFilterButtons";
2
4
  export * from "./GridFilterQuick";
3
5
  export * from "./GridFilters";
6
+ export * from "./useGridFilter";
@@ -1,7 +1,7 @@
1
1
  import { useContext, useEffect } from "react";
2
2
 
3
- import { GridContext, GridFilterExternal } from "../contexts/GridContext";
4
- import { GridBaseRow } from "./Grid";
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);
@@ -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/Grid";
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
- setGridApi: (gridApi: GridApi | undefined) => void;
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
- setGridApi: () => {
50
- console.error("no context provider for setGridApi");
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/Grid";
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, _setGridApi] = useState<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
- setGridApi,
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 { getSelectedRows, updatingCells } = useContext(GridContext);
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
- () => (multiEdit ? sortBy(getSelectedRows(), (row) => row.id !== props.data.id) : [props.data as GridBaseRow]),
27
- [getSelectedRows, multiEdit, props.data],
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
- <div>
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 <input type={"text"} defaultValue={value} onChange={(e) => updateValue(e.target.value)} />;
265
+ return (
266
+ <div className={"flex-row-center"}>
267
+ <div style={{ whiteSpace: "nowrap" }}>{props.text}</div>
268
+ &#160;
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({});