@linzjs/step-ag-grid 14.9.4 → 15.0.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/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": "14.9.4",
5
+ "version": "15.0.0",
6
6
  "keywords": [
7
7
  "aggrid",
8
8
  "ag-grid",
@@ -12,7 +12,7 @@
12
12
  "react",
13
13
  "react-component"
14
14
  ],
15
- "main": "dist/step-ag-grid.esm.js",
15
+ "main": "dist/step-ag-grid.cjs.js",
16
16
  "type": "module",
17
17
  "typings": "dist/src/index.d.ts",
18
18
  "module": "dist/step-ag-grid.esm.js",
@@ -0,0 +1,11 @@
1
+ import { ColDef } from "ag-grid-community";
2
+
3
+ export const GridCellFillerColId = "gridCellFiller";
4
+
5
+ export const isGridCellFiller = (col: ColDef) => col.colId === GridCellFillerColId;
6
+
7
+ export const GridCellFiller = (): ColDef => ({
8
+ colId: GridCellFillerColId,
9
+ headerName: "",
10
+ flex: 1,
11
+ });
@@ -1,4 +1,4 @@
1
- import { compact, isEmpty } from "lodash-es";
1
+ import { isEmpty } from "lodash-es";
2
2
  import { useCallback, useContext, useEffect, useMemo, useState } from "react";
3
3
 
4
4
  import { LuiCheckboxInput, LuiIcon } from "@linzjs/lui";
@@ -16,17 +16,20 @@ export interface GridFilterColumnsToggleProps {
16
16
 
17
17
  export const GridFilterColumnsToggle = ({ saveState = true }: GridFilterColumnsToggleProps): JSX.Element => {
18
18
  const [loaded, setLoaded] = useState(false);
19
- const { getColumns, invisibleColumnIds, setInvisibleColumnIds } = useContext(GridContext);
19
+ const { getColumns, getColumnIds, invisibleColumnIds, setInvisibleColumnIds } = useContext(GridContext);
20
20
 
21
21
  const columnStorageKey = useMemo(
22
22
  () =>
23
- isEmpty(getColumns())
23
+ isEmpty(getColumnIds())
24
24
  ? null // Grid hasn't been initialised yet
25
- : "stepAgGrid_invisibleColumnIds_" +
26
- getColumns()
27
- .map((col) => col.colId || "")
28
- .join("_"),
29
- [getColumns],
25
+ : "stepAgGrid_invisibleColumnIds_" + getColumnIds().join("_"),
26
+ [getColumnIds],
27
+ );
28
+
29
+ // infer the invisible ids from colDefs
30
+ const resetColumns = useCallback(
31
+ () => setInvisibleColumnIds(getColumnIds("initialHide")),
32
+ [getColumnIds, setInvisibleColumnIds],
30
33
  );
31
34
 
32
35
  // Load state on start
@@ -36,14 +39,7 @@ export const GridFilterColumnsToggle = ({ saveState = true }: GridFilterColumnsT
36
39
  try {
37
40
  const stored = window.localStorage.getItem(columnStorageKey);
38
41
  if (!stored) {
39
- // infer the invisible ids from colDefs
40
- setInvisibleColumnIds(
41
- compact(
42
- getColumns()
43
- .filter((col) => col.initialHide)
44
- .map((col) => col.colId),
45
- ),
46
- );
42
+ resetColumns();
47
43
  } else {
48
44
  const invisibleIds = JSON.parse(stored ?? "[]");
49
45
  if (!Array.isArray(invisibleIds)) {
@@ -59,7 +55,7 @@ export const GridFilterColumnsToggle = ({ saveState = true }: GridFilterColumnsT
59
55
  }
60
56
  setLoaded(true);
61
57
  }
62
- }, [columnStorageKey, getColumns, loaded, saveState, setInvisibleColumnIds]);
58
+ }, [columnStorageKey, getColumns, loaded, resetColumns, saveState, setInvisibleColumnIds]);
63
59
 
64
60
  // Save state on column visibility change
65
61
  useEffect(() => {
@@ -81,10 +77,6 @@ export const GridFilterColumnsToggle = ({ saveState = true }: GridFilterColumnsT
81
77
  [invisibleColumnIds, setInvisibleColumnIds],
82
78
  );
83
79
 
84
- const resetColumns = () => {
85
- setInvisibleColumnIds([]);
86
- };
87
-
88
80
  const numericRegExp = /^\d+$/;
89
81
  const isNonManageableColumn = (col: ColDefT<GridBaseRow>) => {
90
82
  return col.lockVisible || col.colId == null || numericRegExp.test(col.colId);
@@ -98,40 +90,38 @@ export const GridFilterColumnsToggle = ({ saveState = true }: GridFilterColumnsT
98
90
  unmountOnClose={true}
99
91
  >
100
92
  <div className={"GridFilterColumnsToggle-container"}>
101
- {getColumns()
102
- .filter((col) => !!col.headerName)
103
- .map((col) => (
104
- <MenuItem
105
- key={col.colId}
106
- disabled={isNonManageableColumn(col)}
107
- onClick={(e: ClickEvent) => {
108
- // Global react-menu MenuItem handler handles tabs
109
- if (e.key !== "Tab") {
110
- e.keepOpen = true;
111
- if (e.key !== "Enter") {
112
- toggleColumn(col.colId);
113
- }
93
+ {getColumns("headerName").map((col) => (
94
+ <MenuItem
95
+ key={col.colId}
96
+ disabled={isNonManageableColumn(col)}
97
+ onClick={(e: ClickEvent) => {
98
+ // Global react-menu MenuItem handler handles tabs
99
+ if (e.key !== "Tab") {
100
+ e.keepOpen = true;
101
+ if (e.key !== "Enter") {
102
+ toggleColumn(col.colId);
114
103
  }
104
+ }
105
+ }}
106
+ >
107
+ <LuiCheckboxInput
108
+ isChecked={!!invisibleColumnIds && !invisibleColumnIds.includes(col.colId ?? "")}
109
+ value={`${col.colId}`}
110
+ label={col.headerName ?? ""}
111
+ isDisabled={isNonManageableColumn(col)}
112
+ inputProps={{
113
+ onClick: (e) => {
114
+ // Click is handled by MenuItem onClick so keyboard events work
115
+ e.preventDefault();
116
+ e.stopPropagation();
117
+ },
118
+ }}
119
+ onChange={() => {
120
+ /*Do nothing, change handled by menuItem*/
115
121
  }}
116
- >
117
- <LuiCheckboxInput
118
- isChecked={!!invisibleColumnIds && !invisibleColumnIds.includes(col.colId ?? "")}
119
- value={`${col.colId}`}
120
- label={col.headerName ?? ""}
121
- isDisabled={isNonManageableColumn(col)}
122
- inputProps={{
123
- onClick: (e) => {
124
- // Click is handled by MenuItem onClick so keyboard events work
125
- e.preventDefault();
126
- e.stopPropagation();
127
- },
128
- }}
129
- onChange={() => {
130
- /*Do nothing, change handled by menuItem*/
131
- }}
132
- />
133
- </MenuItem>
134
- ))}
122
+ />
123
+ </MenuItem>
124
+ ))}
135
125
  </div>
136
126
  <MenuDivider key={`$$divider_reset_columns`} />
137
127
  <MenuItem
@@ -0,0 +1,6 @@
1
+ import { ColDef } from "ag-grid-community";
2
+
3
+ import { isGridCellFiller } from "./GridCellFiller";
4
+
5
+ export const getColId = (colDef: ColDef): string => colDef.colId ?? "";
6
+ export const isFlexColumn = (colDef: ColDef): boolean => !!colDef.flex && !isGridCellFiller(colDef);
@@ -1,6 +1,7 @@
1
1
  export * from "./ComponentLoadingWrapper";
2
2
  export * from "./Grid";
3
3
  export * from "./GridCell";
4
+ export * from "./GridCellFiller";
4
5
  export * from "./GridCellMultiEditor";
5
6
  export * from "./GridCellMultiSelectClassRules";
6
7
  export * from "./gridFilter";
@@ -17,7 +17,10 @@ export type AutoSizeColumnsResult = { width: number } | null;
17
17
  export interface GridContextType<RowType extends GridBaseRow> {
18
18
  gridReady: boolean;
19
19
  getColDef: (colId?: string) => ColDef | undefined;
20
- getColumns: () => ColDefT<RowType>[];
20
+ getColumns: (
21
+ filter?: keyof ColDef | ((r: ColDef) => boolean | undefined | null | number | string),
22
+ ) => ColDefT<RowType>[];
23
+ getColumnIds: (filter?: keyof ColDef | ((r: ColDef) => boolean | undefined | null | number | string)) => string[];
21
24
  setApis: (gridApi: GridApi | undefined, columnApi: ColumnApi | undefined, dataTestId?: string) => void;
22
25
  prePopupOps: () => void;
23
26
  setQuickFilter: (quickFilter: string) => void;
@@ -71,6 +74,10 @@ export const GridContext = createContext<GridContextType<any>>({
71
74
  console.error("no context provider for getColumns");
72
75
  return [];
73
76
  },
77
+ getColumnIds: () => {
78
+ console.error("no context provider for getColumnIds");
79
+ return [];
80
+ },
74
81
  invisibleColumnIds: undefined,
75
82
  setInvisibleColumnIds: () => {
76
83
  console.error("no context provider for setInvisibleColumnIds");
@@ -3,10 +3,12 @@ import { CellPosition } from "ag-grid-community/dist/lib/entities/cellPosition";
3
3
  import { ValueFormatterParams } from "ag-grid-community/dist/lib/entities/colDef";
4
4
  import { CsvExportParams, ProcessCellForExportParams } from "ag-grid-community/dist/lib/interfaces/exportParams";
5
5
  import debounce from "debounce-promise";
6
- import { compact, defer, delay, difference, isEmpty, last, remove, sortBy, sumBy } from "lodash-es";
6
+ import { compact, defer, delay, difference, filter, isEmpty, last, pull, remove, sortBy, sumBy } from "lodash-es";
7
7
  import { ReactElement, ReactNode, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
8
8
 
9
9
  import { ColDefT, GridBaseRow } from "../components";
10
+ import { GridCellFillerColId, isGridCellFiller } from "../components/GridCellFiller";
11
+ import { getColId, isFlexColumn } from "../components/gridUtil";
10
12
  import { isNotEmpty, sanitiseFileName, wait } from "../utils/util";
11
13
  import { AutoSizeColumnsProps, AutoSizeColumnsResult, GridContext, GridFilterExternal } from "./GridContext";
12
14
  import { GridUpdatingContext } from "./GridUpdatingContext";
@@ -26,13 +28,19 @@ export const GridContextProvider = <RowType extends GridBaseRow>(props: GridCont
26
28
  const [columnApi, setColumnApi] = useState<ColumnApi>();
27
29
  const [gridReady, setGridReady] = useState(false);
28
30
  const [quickFilter, setQuickFilter] = useState("");
29
- const [invisibleColumnIds, setInvisibleColumnIds] = useState<string[]>();
31
+ const [invisibleColumnIds, _setInvisibleColumnIds] = useState<string[]>();
30
32
  const testId = useRef<string | undefined>();
31
33
  const idsBeforeUpdate = useRef<number[]>([]);
32
34
  const prePopupFocusedCell = useRef<CellPosition>();
33
35
  const [externallySelectedItemsAreInSync, setExternallySelectedItemsAreInSync] = useState(false);
34
36
  const externalFilters = useRef<GridFilterExternal<RowType>[]>([]);
35
37
 
38
+ /**
39
+ * Make extra sure the GridCellFillerColId never gets added to invisibleColumnIds as it's dynamically determined
40
+ */
41
+ const setInvisibleColumnIds = (invisibleColumnIds: string[]) =>
42
+ _setInvisibleColumnIds(pull(invisibleColumnIds, GridCellFillerColId));
43
+
36
44
  /**
37
45
  * Set quick filter directly on grid, based on previously save quickFilter state.
38
46
  */
@@ -152,9 +160,7 @@ export const GridContextProvider = <RowType extends GridBaseRow>(props: GridCont
152
160
  const _getNewNodes = useCallback((): RowNode[] => {
153
161
  return gridApiOp(
154
162
  (gridApi) =>
155
- difference(_getAllRowIds(), idsBeforeUpdate.current)
156
- .map((rowId) => gridApi.getRowNode("" + rowId)) //
157
- .filter((r) => r) as RowNode[],
163
+ compact(difference(_getAllRowIds(), idsBeforeUpdate.current).map((rowId) => gridApi.getRowNode("" + rowId))),
158
164
  () => [],
159
165
  );
160
166
  }, [_getAllRowIds, gridApiOp]);
@@ -168,10 +174,7 @@ export const GridContextProvider = <RowType extends GridBaseRow>(props: GridCont
168
174
  const _rowIdsToNodes = useCallback(
169
175
  (rowIds: number[]): RowNode[] => {
170
176
  return gridApiOp(
171
- (gridApi) =>
172
- rowIds
173
- .map((rowId) => gridApi.getRowNode("" + rowId)) //
174
- .filter((r) => r) as RowNode[],
177
+ (gridApi) => compact(rowIds.map((rowId) => gridApi.getRowNode("" + rowId))),
175
178
  () => [] as RowNode[],
176
179
  );
177
180
  },
@@ -181,11 +184,20 @@ export const GridContextProvider = <RowType extends GridBaseRow>(props: GridCont
181
184
  /**
182
185
  * Get ColDefs, with flattened ColGroupDefs
183
186
  */
184
- const getColumns: () => ColDefT<RowType>[] = useCallback(
185
- () => columnApi?.getAllColumns()?.map((col) => col.getColDef()) ?? [],
187
+ const getColumns = useCallback(
188
+ (
189
+ filterDef: keyof ColDef | ((r: ColDef) => boolean | undefined | null | number | string) = () => true,
190
+ ): ColDefT<RowType>[] =>
191
+ filter(columnApi?.getAllColumns()?.map((col) => col.getColDef()) ?? [], filterDef) as ColDefT<RowType>[],
186
192
  [columnApi],
187
193
  );
188
194
 
195
+ const getColumnIds = useCallback(
196
+ (filterDef: keyof ColDef | ((r: ColDef) => boolean | undefined | null | number | string) = () => true): string[] =>
197
+ compact(getColumns(filterDef).map(getColId)),
198
+ [getColumns],
199
+ );
200
+
189
201
  /**
190
202
  * Internal method for selecting and flashing rows.
191
203
  *
@@ -544,9 +556,7 @@ export const GridContextProvider = <RowType extends GridBaseRow>(props: GridCont
544
556
 
545
557
  const redrawRows = useCallback(
546
558
  (rowNodes?: RowNode[]) => {
547
- gridApiOp((gridApi) => {
548
- gridApi.redrawRows(rowNodes ? { rowNodes } : undefined);
549
- });
559
+ gridApiOp((gridApi) => gridApi.redrawRows(rowNodes ? { rowNodes } : undefined));
550
560
  },
551
561
  [gridApiOp],
552
562
  );
@@ -582,31 +592,39 @@ export const GridContextProvider = <RowType extends GridBaseRow>(props: GridCont
582
592
  onFilterChanged().then();
583
593
  };
584
594
 
585
- const isExternalFilterPresent = (): boolean => externalFilters.current.length > 0;
595
+ const isExternalFilterPresent = (): boolean => !isEmpty(externalFilters.current);
586
596
 
587
- const doesExternalFilterPass = (node: RowNode): boolean => {
588
- return externalFilters.current.every((filter) => filter(node.data, node));
589
- };
597
+ const doesExternalFilterPass = (node: RowNode): boolean =>
598
+ externalFilters.current.every((filter) => filter(node.data, node));
590
599
 
591
600
  const getColDef = useCallback(
592
601
  (colId?: string): ColDef | undefined => (!!colId && gridApi?.getColumnDef(colId)) || undefined,
593
602
  [gridApi],
594
603
  );
595
604
 
605
+ /**
606
+ * Apply column visibility
607
+ */
596
608
  useEffect(() => {
597
- if (columnApi && invisibleColumnIds) {
598
- // show all columns that aren't invisible
599
- columnApi.setColumnsVisible(
600
- compact(
601
- getColumns()
602
- .filter((col) => !col.lockVisible && col.colId && !invisibleColumnIds.includes(col.colId))
603
- .map((col) => col.colId),
604
- ),
605
- true,
606
- );
607
- // hide all invisible columns
608
- columnApi.setColumnsVisible(invisibleColumnIds, false);
609
+ if (!columnApi || !invisibleColumnIds) return;
610
+
611
+ // show all columns that aren't invisible
612
+ const newVisibleColumns = getColumns(
613
+ (col) => !col.lockVisible && col.colId && !invisibleColumnIds.includes(col.colId) && !isGridCellFiller(col),
614
+ );
615
+ // If there's no flex column showing add the filler column if defined
616
+ const visibleColumnsContainsAFlex = newVisibleColumns.some(isFlexColumn);
617
+ if (!visibleColumnsContainsAFlex) {
618
+ const fillerColumn = getColumns(isGridCellFiller)[0];
619
+ fillerColumn && newVisibleColumns.push(fillerColumn);
609
620
  }
621
+ columnApi.setColumnsVisible(compact(newVisibleColumns.map(getColId)), true);
622
+
623
+ // Hide the filler column if there's already a flex column
624
+ const invisibleColumnIdsWithOptionalFiller = visibleColumnsContainsAFlex
625
+ ? [...invisibleColumnIds, GridCellFillerColId]
626
+ : invisibleColumnIds;
627
+ columnApi.setColumnsVisible(invisibleColumnIdsWithOptionalFiller, false);
610
628
  }, [invisibleColumnIds, columnApi, getColumns]);
611
629
 
612
630
  /**
@@ -620,7 +638,10 @@ export const GridContextProvider = <RowType extends GridBaseRow>(props: GridCont
620
638
 
621
639
  const columnKeys = columnApi
622
640
  ?.getColumnState()
623
- .filter((cs) => !cs.hide && gridApi.getColumnDef(cs.colId)?.headerComponentParams?.exportable !== false)
641
+ .filter((cs) => {
642
+ const colDef = gridApi.getColumnDef(cs.colId);
643
+ return !cs.hide && colDef && !isGridCellFiller(colDef) && colDef.headerComponentParams?.exportable !== false;
644
+ })
624
645
  .map((cs) => cs.colId);
625
646
  gridApi.exportDataAsCsv({
626
647
  columnKeys,
@@ -637,6 +658,7 @@ export const GridContextProvider = <RowType extends GridBaseRow>(props: GridCont
637
658
  value={{
638
659
  getColDef,
639
660
  getColumns,
661
+ getColumnIds,
640
662
  invisibleColumnIds,
641
663
  setInvisibleColumnIds,
642
664
  gridReady,
@@ -25,6 +25,7 @@ import {
25
25
  } from "../..";
26
26
  import { GridFilterColumnsToggle } from "../../components";
27
27
  import { GridFilterDownloadCsvButton } from "../../components";
28
+ import { GridCellFiller } from "../../components/GridCellFiller";
28
29
  import "../../styles/GridTheme.scss";
29
30
  import "../../styles/index.scss";
30
31
 
@@ -97,6 +98,7 @@ const GridReadOnlyTemplate: ComponentStory<typeof Grid> = (props: GridProps) =>
97
98
  flex: 1,
98
99
  initialHide: true,
99
100
  }),
101
+ GridCellFiller(),
100
102
  GridPopoverMessage(
101
103
  {
102
104
  headerName: "Popout message",