@linzjs/step-ag-grid 1.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.
Files changed (96) hide show
  1. package/README.md +4 -0
  2. package/dist/index.d.ts +32 -0
  3. package/dist/index.js +89342 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/src/components/ComponentLoadingWrapper.d.ts +9 -0
  6. package/dist/src/components/GenericCellClass.d.ts +2 -0
  7. package/dist/src/components/Grid.d.ts +24 -0
  8. package/dist/src/components/GridCell.d.ts +31 -0
  9. package/dist/src/components/GridIcon.d.ts +6 -0
  10. package/dist/src/components/GridLoadableCell.d.ts +6 -0
  11. package/dist/src/components/GridPopoutHook.d.ts +7 -0
  12. package/dist/src/components/GridSubComponentTextArea.d.ts +5 -0
  13. package/dist/src/components/PostSortRowsHook.d.ts +11 -0
  14. package/dist/src/components/gridForm/GridFormDropDown.d.ts +26 -0
  15. package/dist/src/components/gridForm/GridFormEditBearing.d.ts +9 -0
  16. package/dist/src/components/gridForm/GridFormMessage.d.ts +8 -0
  17. package/dist/src/components/gridForm/GridFormMultiSelect.d.ts +26 -0
  18. package/dist/src/components/gridForm/GridFormPopoutMenu.d.ts +24 -0
  19. package/dist/src/components/gridForm/GridFormTextArea.d.ts +11 -0
  20. package/dist/src/components/gridForm/GridFormTextInput.d.ts +11 -0
  21. package/dist/src/components/gridHeader/GridHeaderSelect.d.ts +8 -0
  22. package/dist/src/components/gridPopoverEdit/GridPopoutEditMultiSelect.d.ts +4 -0
  23. package/dist/src/components/gridPopoverEdit/GridPopoutMenu.d.ts +10 -0
  24. package/dist/src/components/gridPopoverEdit/GridPopoverEditBearing.d.ts +4 -0
  25. package/dist/src/components/gridPopoverEdit/GridPopoverEditDropDown.d.ts +4 -0
  26. package/dist/src/components/gridPopoverEdit/GridPopoverMessage.d.ts +4 -0
  27. package/dist/src/components/gridRender/GridRenderGenericCell.d.ts +15 -0
  28. package/dist/src/components/gridRender/GridRenderPopoutMenuCell.d.ts +3 -0
  29. package/dist/src/contexts/GridContext.d.ts +25 -0
  30. package/dist/src/contexts/GridContextProvider.d.ts +11 -0
  31. package/dist/src/contexts/UpdatingContext.d.ts +6 -0
  32. package/dist/src/contexts/UpdatingContextProvider.d.ts +7 -0
  33. package/dist/src/lui/TextAreaInput.d.ts +11 -0
  34. package/dist/src/lui/TextInputFormatted.d.ts +14 -0
  35. package/dist/src/setupTests.d.ts +1 -0
  36. package/dist/src/stories/components/FormTest.d.ts +12 -0
  37. package/dist/src/stories/components/GridPopoutBearing.stories.d.ts +9 -0
  38. package/dist/src/stories/components/GridPopoutEditDropDown.stories.d.ts +9 -0
  39. package/dist/src/stories/components/GridPopoutEditGeneric.stories.d.ts +9 -0
  40. package/dist/src/stories/components/GridPopoutEditGenericTextArea.stories.d.ts +9 -0
  41. package/dist/src/stories/components/GridPopoutEditMultiSelect.stories.d.ts +9 -0
  42. package/dist/src/stories/components/GridReadOnly.stories.d.ts +9 -0
  43. package/dist/src/utils/bearing.d.ts +5 -0
  44. package/dist/src/utils/util.d.ts +4 -0
  45. package/dist/step-ag-grid.esm.js +89297 -0
  46. package/dist/step-ag-grid.esm.js.map +1 -0
  47. package/package.json +126 -0
  48. package/src/components/ComponentLoadingWrapper.tsx +31 -0
  49. package/src/components/GenericCellClass.tsx +15 -0
  50. package/src/components/Grid.scss +38 -0
  51. package/src/components/Grid.tsx +262 -0
  52. package/src/components/GridCell.tsx +101 -0
  53. package/src/components/GridIcon.tsx +12 -0
  54. package/src/components/GridLoadableCell.tsx +22 -0
  55. package/src/components/GridPopoutHook.tsx +118 -0
  56. package/src/components/GridSubComponentTextArea.tsx +26 -0
  57. package/src/components/GridTheme.scss +94 -0
  58. package/src/components/PostSortRowsHook.ts +131 -0
  59. package/src/components/gridForm/GridFormDropDown.tsx +192 -0
  60. package/src/components/gridForm/GridFormEditBearing.scss +3 -0
  61. package/src/components/gridForm/GridFormEditBearing.tsx +62 -0
  62. package/src/components/gridForm/GridFormMessage.tsx +34 -0
  63. package/src/components/gridForm/GridFormMultiSelect.tsx +180 -0
  64. package/src/components/gridForm/GridFormPopoutMenu.tsx +90 -0
  65. package/src/components/gridForm/GridFormTextArea.tsx +61 -0
  66. package/src/components/gridForm/GridFormTextInput.tsx +66 -0
  67. package/src/components/gridHeader/GridHeaderSelect.tsx +54 -0
  68. package/src/components/gridPopoverEdit/GridPopoutEditMultiSelect.ts +22 -0
  69. package/src/components/gridPopoverEdit/GridPopoutMenu.scss +9 -0
  70. package/src/components/gridPopoverEdit/GridPopoutMenu.tsx +35 -0
  71. package/src/components/gridPopoverEdit/GridPopoverEditBearing.ts +24 -0
  72. package/src/components/gridPopoverEdit/GridPopoverEditDropDown.ts +22 -0
  73. package/src/components/gridPopoverEdit/GridPopoverMessage.ts +22 -0
  74. package/src/components/gridRender/GridRenderGenericCell.scss +15 -0
  75. package/src/components/gridRender/GridRenderGenericCell.tsx +49 -0
  76. package/src/components/gridRender/GridRenderPopoutMenuCell.tsx +22 -0
  77. package/src/contexts/GridContext.tsx +82 -0
  78. package/src/contexts/GridContextProvider.tsx +292 -0
  79. package/src/contexts/UpdatingContext.tsx +16 -0
  80. package/src/contexts/UpdatingContextProvider.tsx +43 -0
  81. package/src/lui/TextAreaInput.tsx +50 -0
  82. package/src/lui/TextInputFormatted.scss +11 -0
  83. package/src/lui/TextInputFormatted.tsx +60 -0
  84. package/src/lui-overrides.scss +163 -0
  85. package/src/react-app-env.d.ts +1 -0
  86. package/src/setupTests.ts +5 -0
  87. package/src/stories/components/FormTest.scss +10 -0
  88. package/src/stories/components/FormTest.tsx +51 -0
  89. package/src/stories/components/GridPopoutBearing.stories.tsx +100 -0
  90. package/src/stories/components/GridPopoutEditDropDown.stories.tsx +174 -0
  91. package/src/stories/components/GridPopoutEditGeneric.stories.tsx +76 -0
  92. package/src/stories/components/GridPopoutEditGenericTextArea.stories.tsx +106 -0
  93. package/src/stories/components/GridPopoutEditMultiSelect.stories.tsx +111 -0
  94. package/src/stories/components/GridReadOnly.stories.tsx +151 -0
  95. package/src/utils/bearing.ts +72 -0
  96. package/src/utils/util.ts +26 -0
package/package.json ADDED
@@ -0,0 +1,126 @@
1
+ {
2
+ "name": "@linzjs/step-ag-grid",
3
+ "repository": "github:linz/step-ag-grid.git",
4
+ "license": "MIT",
5
+ "version": "1.1.1",
6
+ "main": "dist/index.js",
7
+ "typings": "dist/index.d.ts",
8
+ "module": "dist/step-ag-grid.esm.js",
9
+ "files": [
10
+ "dist",
11
+ "src"
12
+ ],
13
+ "publishConfig": {
14
+ "access": "public"
15
+ },
16
+ "engines": {
17
+ "node": ">=10"
18
+ },
19
+ "resolutions": {
20
+ "webpack": "5"
21
+ },
22
+ "dependencies": {
23
+ "@linzjs/lui": "^17.18.2",
24
+ "@testing-library/jest-dom": "^5.14.1",
25
+ "@testing-library/react": "^12.1.3",
26
+ "@testing-library/user-event": "^13.5.0",
27
+ "ag-grid-community": "^27.3.0",
28
+ "ag-grid-react": "^27.3.0",
29
+ "debounce-promise": "^3.1.2",
30
+ "lodash-es": "^4.17.21",
31
+ "react": "^17.0.2",
32
+ "react-dom": "^17.0.2",
33
+ "react-scripts": "^5.0.1",
34
+ "uuid": "^9.0.0",
35
+ "autoprefixer": "^10.0.2",
36
+ "postcss": "^8.1.7",
37
+ "postcss-loader": "^4.0.4"
38
+ },
39
+ "scripts": {
40
+ "build": "rollup -c && npm run",
41
+ "test": "react-app-rewired test --passWithNoTests",
42
+ "eject": "react-app-rewired eject",
43
+ "lint": "eslint ./src --ext .js,.ts,.tsx --fix --cache",
44
+ "storybook": "start-storybook -p 6006 -s public",
45
+ "build-storybook": "build-storybook -s public",
46
+ "deploy-storybook": "npx --yes -p @storybook/storybook-deployer storybook-to-ghpages",
47
+ "chromatic": "chromatic --exit-zero-on-changes",
48
+ "semantic-release": "semantic-release"
49
+ },
50
+ "eslintConfig": {
51
+ "extends": [
52
+ "react-app",
53
+ "react-app/jest"
54
+ ]
55
+ },
56
+ "browserslist": {
57
+ "production": [
58
+ ">0.2%",
59
+ "not dead",
60
+ "not op_mini all"
61
+ ],
62
+ "development": [
63
+ "last 1 chrome version",
64
+ "last 1 firefox version",
65
+ "last 1 safari version"
66
+ ]
67
+ },
68
+ "devDependencies": {
69
+ "@rollup/plugin-commonjs": "^21.0.2",
70
+ "@rollup/plugin-json": "^4.1.0",
71
+ "@rollup/plugin-node-resolve": "^11.2.1",
72
+ "@semantic-release/changelog": "^6.0.1",
73
+ "@semantic-release/git": "^10.0.1",
74
+ "@storybook/builder-webpack5": "^6.5.13",
75
+ "@storybook/cli": "^6.5.12",
76
+ "@storybook/manager-webpack5": "^6.5.13",
77
+ "@storybook/preset-scss": "^1.0.3",
78
+ "@storybook/react": "^6.5.12",
79
+ "@types/debounce-promise": "^3.1.4",
80
+ "@types/jest": "^27.5.2",
81
+ "@types/lodash-es": "^4.17.6",
82
+ "@types/node": "^16.18.0",
83
+ "@types/react": "^17.0.50",
84
+ "@types/react-dom": "^17.0.17",
85
+ "@types/uuid": "^8.3.4",
86
+ "@typescript-eslint/parser": "^5.39.0",
87
+ "babel-preset-react-app": "10.0.0",
88
+ "chromatic": "^6.10.1",
89
+ "css-loader": "^5.2.7",
90
+ "eslint": "8.22.0",
91
+ "eslint-config-prettier": "^8.5.0",
92
+ "eslint-config-react-app": "^7.0.1",
93
+ "eslint-plugin-deprecation": "^1.3.2",
94
+ "eslint-plugin-import": "^2.26.0",
95
+ "eslint-plugin-jest": "^26.1.5",
96
+ "eslint-plugin-jsx-a11y": "^6.5.1",
97
+ "eslint-plugin-prettier": "^4.0.0",
98
+ "eslint-plugin-react": "^7.29.4",
99
+ "eslint-plugin-react-hooks": "^4.6.0",
100
+ "eslint-plugin-testing-library": "^5.3.1",
101
+ "prettier": "^2.2.1",
102
+ "react-app-rewired": "^2.2.1",
103
+ "rollup": "^2.56.3",
104
+ "rollup-plugin-copy": "^3.4.0",
105
+ "rollup-plugin-peer-deps-external": "^2.2.4",
106
+ "rollup-plugin-postcss": "^4.0.2",
107
+ "rollup-plugin-typescript2": "^0.31.2",
108
+ "sass": "^1.49.9",
109
+ "sass-loader": "^10.3.1",
110
+ "semantic-release": "^19.0.5",
111
+ "style-loader": "^2.0.0",
112
+ "tsconfig-paths-webpack-plugin": "^4.0.0",
113
+ "typescript": "^4.8.4",
114
+ "webpack": "^5.74.0"
115
+ },
116
+ "babel": {
117
+ "presets": [
118
+ "react-app"
119
+ ]
120
+ },
121
+ "husky": {
122
+ "hooks": {
123
+ "pre-commit": "npm run lint"
124
+ }
125
+ }
126
+ }
@@ -0,0 +1,31 @@
1
+ import { LuiMiniSpinner } from "@linzjs/lui";
2
+
3
+ /**
4
+ * If loading is true this returns a loading spinner, otherwise it returns its children.
5
+ */
6
+ export const ComponentLoadingWrapper = (props: {
7
+ loading?: boolean;
8
+ saving?: boolean;
9
+ children: JSX.Element | undefined;
10
+ }): JSX.Element => {
11
+ return props.loading ? (
12
+ <LuiMiniSpinner size={22} divProps={{ role: "status", ["aria-label"]: "Loading", style: { padding: 16 } }} />
13
+ ) : (
14
+ <div style={{ maxHeight: 400, overflowY: "auto", pointerEvents: props.saving ? "none" : "inherit" }}>
15
+ {props.saving && (
16
+ <div
17
+ style={{
18
+ position: "absolute",
19
+ left: 0,
20
+ top: 0,
21
+ bottom: 0,
22
+ right: 0,
23
+ backgroundColor: "rgba(64,64,64,0.1)",
24
+ zIndex: 1000,
25
+ }}
26
+ />
27
+ )}
28
+ {props.children}
29
+ </div>
30
+ );
31
+ };
@@ -0,0 +1,15 @@
1
+ import { CellClassFunc } from "ag-grid-community";
2
+
3
+ export const GenericMultiEditCellClass: CellClassFunc = (props): string => {
4
+ const api = props.api;
5
+ if (api == null) return "";
6
+
7
+ const rowSelected = api
8
+ .getSelectedNodes()
9
+ .map((row) => row.id)
10
+ .includes(props.node.id);
11
+
12
+ return rowSelected && api.getEditingCells().some((cell) => cell.column.getColDef() === props.colDef)
13
+ ? "ag-selected-for-edit"
14
+ : "";
15
+ };
@@ -0,0 +1,38 @@
1
+ @use '~@linzjs/lui/dist/scss/Core' as lui;
2
+
3
+ .Grid-container {
4
+ flex: 1;
5
+ width: 100%;
6
+ }
7
+
8
+ .Grid-sortIsStale {
9
+ span.ag-icon.ag-icon-desc:after {
10
+ content: "*";
11
+ }
12
+
13
+ span.ag-icon.ag-icon-asc:after {
14
+ content: "*";
15
+ }
16
+ }
17
+
18
+ .Grid-quickFilter {
19
+ width: 100%;
20
+ margin-bottom: 16px;
21
+ }
22
+
23
+ .Grid-quickFilterBox {
24
+ width: 100%;
25
+ height: 48px;
26
+ border: 0.06rem solid lui.$silver;
27
+ border-radius: 3px;
28
+ padding-left: 0.75rem;
29
+ padding-right: 0.75rem;
30
+ }
31
+
32
+ .Grid-popoverContainer {
33
+ padding: 4px 8px;
34
+ }
35
+
36
+ .Grid-popoverContainerList {
37
+ padding: 4px 4px;
38
+ }
@@ -0,0 +1,262 @@
1
+ import "./Grid.scss";
2
+ import "./GridTheme.scss";
3
+
4
+ import clsx from "clsx";
5
+ import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
6
+ import { AgGridReact } from "ag-grid-react";
7
+ import { AgGridEvent, CellClickedEvent, ColDef } from "ag-grid-community";
8
+ import { CellEvent, GridReadyEvent, SelectionChangedEvent } from "ag-grid-community/dist/lib/events";
9
+ import { GridOptions } from "ag-grid-community/dist/lib/entities/gridOptions";
10
+ import { difference, last, xorBy } from "lodash-es";
11
+ import { GridContext } from "../contexts/GridContext";
12
+ import { usePostSortRowsHook } from "./PostSortRowsHook";
13
+ import { isNotEmpty } from "../utils/util";
14
+ import { GridHeaderSelect } from "./gridHeader/GridHeaderSelect";
15
+ import { UpdatingContext } from "../contexts/UpdatingContext";
16
+
17
+ export interface GridBaseRow {
18
+ id: string | number;
19
+ }
20
+
21
+ export interface GridProps {
22
+ dataTestId?: string;
23
+ quickFilter?: boolean;
24
+ quickFilterPlaceholder?: string;
25
+ quickFilterValue?: string;
26
+ externalSelectedItems: any[];
27
+ setExternalSelectedItems: (items: any[]) => void;
28
+ onGridReady?: GridOptions["onGridReady"];
29
+ defaultColDef: GridOptions["defaultColDef"];
30
+ columnDefs: GridOptions["columnDefs"];
31
+ rowData: GridOptions["rowData"];
32
+ noRowsOverlayText?: string;
33
+ }
34
+
35
+ /**
36
+ * Wrapper for AgGrid to add commonly used functionality.
37
+ */
38
+ export const Grid = (params: GridProps): JSX.Element => {
39
+ const { gridReady, setGridApi, setQuickFilter, ensureRowVisible, selectRowsById, ensureSelectedRowIsVisible } =
40
+ useContext(GridContext);
41
+ const { checkUpdating } = useContext(UpdatingContext);
42
+
43
+ const [internalQuickFilter, setInternalQuickFilter] = useState("");
44
+ const lastSelectedIds = useRef<number[]>([]);
45
+ const [staleGrid, setStaleGrid] = useState(false);
46
+ const postSortRows = usePostSortRowsHook({ setStaleGrid });
47
+
48
+ /**
49
+ * AgGrid checkbox select does not pass clicks within cell but not on the checkbox to checkbox.
50
+ * This passes the event to the checkbox when you click anywhere in the cell.
51
+ */
52
+ const clickSelectorCheckboxWhenContainingCellClicked = useCallback(({ event }: CellClickedEvent) => {
53
+ if (!event) return;
54
+ const input = (event.target as Element).querySelector("input");
55
+ input?.dispatchEvent(event);
56
+ }, []);
57
+
58
+ /**
59
+ * Ensure external selected items list is in sync with panel.
60
+ */
61
+ const synchroniseExternalStateToGridSelection = ({ api }: SelectionChangedEvent) => {
62
+ const selectedRows = api.getSelectedRows();
63
+ // We don't want to update selected Items if it hasn't changed to prevent excess renders
64
+ if (
65
+ params.externalSelectedItems.length != selectedRows.length ||
66
+ isNotEmpty(xorBy(selectedRows, params.externalSelectedItems, (row) => row.id))
67
+ ) {
68
+ params.setExternalSelectedItems([...selectedRows]);
69
+ }
70
+ };
71
+
72
+ /**
73
+ * Synchronise externally selected items to grid.
74
+ * If new ids are selected scroll them into view.
75
+ */
76
+ const synchroniseExternallySelectedItemsToGrid = useCallback(() => {
77
+ if (!gridReady()) return;
78
+
79
+ const selectedIds = params.externalSelectedItems.map((row) => row.id) as number[];
80
+ const lastNewId = last(difference(selectedIds, lastSelectedIds.current));
81
+ if (lastNewId != null) ensureRowVisible(lastNewId);
82
+ lastSelectedIds.current = selectedIds;
83
+ selectRowsById(selectedIds);
84
+ }, [params.externalSelectedItems, ensureRowVisible, gridReady, selectRowsById]);
85
+
86
+ /**
87
+ * Synchronise quick filter to grid
88
+ */
89
+ const updateQuickFilter = useCallback(() => {
90
+ if (!gridReady()) return;
91
+ if (params.quickFilter) {
92
+ setQuickFilter(internalQuickFilter);
93
+ return;
94
+ }
95
+ if (params.quickFilterValue == null) return;
96
+ setQuickFilter(params.quickFilterValue);
97
+ }, [gridReady, internalQuickFilter, params.quickFilter, params.quickFilterValue, setQuickFilter]);
98
+
99
+ /**
100
+ * Synchronise quick filter to grid
101
+ */
102
+ useEffect(() => {
103
+ updateQuickFilter();
104
+ }, [updateQuickFilter]);
105
+
106
+ /**
107
+ * Synchronise externally selected items to grid on externalSelectedItems change
108
+ */
109
+ useEffect(() => {
110
+ synchroniseExternallySelectedItemsToGrid();
111
+ }, [synchroniseExternallySelectedItemsToGrid]);
112
+
113
+ const columnDefs = useMemo(
114
+ (): GridOptions["columnDefs"] => [
115
+ {
116
+ colId: "selection",
117
+ editable: false,
118
+ initialWidth: 35,
119
+ minWidth: 35,
120
+ maxWidth: 35,
121
+ suppressSizeToFit: true,
122
+ checkboxSelection: true,
123
+ headerComponent: GridHeaderSelect,
124
+ onCellClicked: clickSelectorCheckboxWhenContainingCellClicked,
125
+ },
126
+ ...(params.columnDefs as ColDef[]),
127
+ ],
128
+ [clickSelectorCheckboxWhenContainingCellClicked, params.columnDefs],
129
+ );
130
+
131
+ const onGridReady = useCallback(
132
+ (event: GridReadyEvent) => {
133
+ setGridApi(event.api);
134
+ params.onGridReady && params.onGridReady(event);
135
+ synchroniseExternallySelectedItemsToGrid();
136
+ updateQuickFilter();
137
+ },
138
+ [params, setGridApi, synchroniseExternallySelectedItemsToGrid, updateQuickFilter],
139
+ );
140
+
141
+ const noRowsOverlayComponent = useCallback(
142
+ () => (
143
+ <span className="ag-overlay-no-rows-center">{params.noRowsOverlayText ?? "There are currently no rows"}</span>
144
+ ),
145
+ [params.noRowsOverlayText],
146
+ );
147
+
148
+ const sizeColumnsToFit = useCallback((event: AgGridEvent) => {
149
+ // @ts-ignore (gridBodyCtrl is private)
150
+ if (event.api.gridBodyCtrl == undefined) {
151
+ return;
152
+ // this is here because ag grid was throwing api undefined error when closing POPPED OUT panel
153
+ }
154
+ event.api.sizeColumnsToFit();
155
+ }, []);
156
+
157
+ const refreshSelectedRows = useCallback((event: CellEvent): void => {
158
+ // Force-refresh all selected rows to re-run class function, to update selection highlighting
159
+ event.api.refreshCells({
160
+ force: true,
161
+ rowNodes: event.api.getSelectedNodes(),
162
+ });
163
+ }, []);
164
+
165
+ const startCellEditing = useCallback(
166
+ (event: CellEvent) => {
167
+ if (!event.node.isSelected()) {
168
+ event.node.setSelected(true, true);
169
+ }
170
+ // Cell already being edited, so don't re-edit until finished
171
+ if (checkUpdating([event.colDef.field ?? ""], event.data.id)) {
172
+ return;
173
+ }
174
+ if (event.rowIndex !== null) {
175
+ event.api.startEditingCell({
176
+ rowIndex: event.rowIndex,
177
+ colKey: event.column.getColId(),
178
+ });
179
+ }
180
+ },
181
+ [checkUpdating],
182
+ );
183
+
184
+ const onCellDoubleClick = useCallback(
185
+ (event: CellEvent) => {
186
+ if (!event.colDef?.cellRendererParams?.singleClickEdit) {
187
+ startCellEditing(event);
188
+ }
189
+ },
190
+ [startCellEditing],
191
+ );
192
+
193
+ const onCellClicked = useCallback(
194
+ (event: CellEvent) => {
195
+ if (event.colDef?.cellRendererParams?.singleClickEdit) {
196
+ startCellEditing(event);
197
+ }
198
+ },
199
+ [startCellEditing],
200
+ );
201
+
202
+ const onCellKeyDown = useCallback(
203
+ (e: CellEvent) => {
204
+ if ((e.event as KeyboardEvent).key === "Enter") startCellEditing(e);
205
+ },
206
+ [startCellEditing],
207
+ );
208
+
209
+ const onCellEditingStopped = useCallback(
210
+ (event: CellEvent) => {
211
+ refreshSelectedRows(event);
212
+ // The grid loses cell focus after editing
213
+ const cell = event.api.getFocusedCell();
214
+ cell && event.api.setFocusedCell(cell.rowIndex, cell.column);
215
+ },
216
+ [refreshSelectedRows],
217
+ );
218
+
219
+ return (
220
+ <div
221
+ data-testid={params.dataTestId}
222
+ className={clsx("Grid-container", "ag-theme-alpine", staleGrid && "Grid-sortIsStale")}
223
+ >
224
+ {params.quickFilter && (
225
+ <div className="Grid-quickFilter">
226
+ <input
227
+ aria-label="Search"
228
+ className="lui-margin-top-xxs lui-margin-bottom-xxs Grid-quickFilterBox"
229
+ type="text"
230
+ placeholder={params.quickFilterPlaceholder ?? "Search..."}
231
+ value={internalQuickFilter}
232
+ onChange={(event): void => {
233
+ setInternalQuickFilter(event.target.value);
234
+ }}
235
+ />
236
+ </div>
237
+ )}
238
+ <AgGridReact
239
+ getRowId={(params) => `${params.data.id}`}
240
+ suppressRowClickSelection={true}
241
+ rowSelection={"multiple"}
242
+ suppressBrowserResizeObserver={true}
243
+ colResizeDefault={"shift"}
244
+ onFirstDataRendered={sizeColumnsToFit}
245
+ onGridSizeChanged={sizeColumnsToFit}
246
+ suppressClickEdit={true}
247
+ onCellKeyDown={onCellKeyDown}
248
+ onCellClicked={onCellClicked}
249
+ onCellDoubleClicked={onCellDoubleClick}
250
+ onCellEditingStarted={refreshSelectedRows}
251
+ onCellEditingStopped={onCellEditingStopped}
252
+ columnDefs={columnDefs}
253
+ rowData={params.rowData}
254
+ noRowsOverlayComponent={noRowsOverlayComponent}
255
+ onGridReady={onGridReady}
256
+ onSortChanged={ensureSelectedRowIsVisible}
257
+ postSortRows={postSortRows}
258
+ onSelectionChanged={synchroniseExternalStateToGridSelection}
259
+ />
260
+ </div>
261
+ );
262
+ };
@@ -0,0 +1,101 @@
1
+ import { useCallback, useContext, useMemo, useState } from "react";
2
+ import { GridBaseRow } from "./Grid";
3
+ import { GridContext } from "../contexts/GridContext";
4
+ import { GenericMultiEditCellClass } from "./GenericCellClass";
5
+ import { GenericCellRendererParams, GridRendererGenericCell } from "./gridRender/GridRenderGenericCell";
6
+ import { ColDef, ICellEditorParams } from "ag-grid-community";
7
+
8
+ export interface GridFormProps<RowType extends GridBaseRow> {
9
+ cellEditorParams: ICellEditorParams;
10
+ updateValue: (saveFn: (selectedRows: RowType[]) => Promise<boolean>) => Promise<boolean>;
11
+ saving: boolean;
12
+ value: any;
13
+ field: string | undefined;
14
+ selectedRows: RowType[];
15
+ formProps: Record<string, any>;
16
+ }
17
+
18
+ export interface GenericCellEditorParams<RowType extends GridBaseRow> {
19
+ multiEdit?: boolean;
20
+ form?: (props: GridFormProps<RowType>) => JSX.Element;
21
+ }
22
+
23
+ export interface GenericCellEditorColDef<
24
+ RowType extends GridBaseRow,
25
+ FormProps extends GenericCellEditorParams<RowType>,
26
+ > extends ColDef {
27
+ cellEditorParams?: FormProps;
28
+ cellRendererParams?: GenericCellRendererParams;
29
+ }
30
+
31
+ /**
32
+ * For editing a text area.
33
+ */
34
+ export const GridCell = <RowType extends GridBaseRow, FormProps extends GenericCellEditorParams<RowType>>(
35
+ props: GenericCellEditorColDef<RowType, FormProps>,
36
+ ): ColDef => {
37
+ return {
38
+ cellRenderer: props.cellRenderer ?? GridRendererGenericCell,
39
+ sortable: !!(props?.field || props?.valueGetter),
40
+ resizable: true,
41
+ ...(props.cellEditorParams && {
42
+ cellClass: props?.cellEditorParams?.multiEdit ? GenericMultiEditCellClass : undefined,
43
+ editable: true,
44
+ cellEditor: GenericCellEditorComponent,
45
+ }),
46
+ ...props,
47
+ };
48
+ };
49
+
50
+ interface GenericCellEditorICellEditorParams<RowType extends GridBaseRow, FormProps extends Record<string, any>>
51
+ extends ICellEditorParams {
52
+ data: RowType;
53
+ colDef: GenericCellEditorColDef<RowType, FormProps>;
54
+ }
55
+
56
+ export const GenericCellEditorComponent = <RowType extends GridBaseRow, FormProps extends Record<string, any>>(
57
+ props: GenericCellEditorICellEditorParams<RowType, FormProps>,
58
+ ) => {
59
+ const { updatingCells, getSelectedRows } = useContext(GridContext);
60
+
61
+ const { colDef, data } = props;
62
+ const { cellEditorParams } = props.colDef;
63
+ const multiEdit = cellEditorParams?.multiEdit ?? false;
64
+ const field = props.colDef.field ?? "";
65
+
66
+ const formProps = colDef.cellEditorParams ?? {};
67
+ const value = props.value;
68
+
69
+ const selectedRows = useMemo(
70
+ () => (multiEdit ? getSelectedRows<RowType>() : [data]),
71
+ [data, getSelectedRows, multiEdit],
72
+ );
73
+
74
+ const [saving, setSaving] = useState(false);
75
+
76
+ const updateValue = useCallback(
77
+ async (saveFn: (selectedRows: any[]) => Promise<boolean>): Promise<boolean> => {
78
+ return !saving && (await updatingCells({ selectedRows, field }, saveFn, setSaving));
79
+ },
80
+ [field, saving, selectedRows, updatingCells],
81
+ );
82
+
83
+ if (cellEditorParams == null) return <></>;
84
+
85
+ return (
86
+ <>
87
+ <div>{colDef.cellRenderer ? <colDef.cellRenderer {...props} saving={saving} /> : props.value}</div>
88
+ {cellEditorParams?.form && (
89
+ <cellEditorParams.form
90
+ cellEditorParams={props}
91
+ updateValue={updateValue}
92
+ saving={saving}
93
+ formProps={formProps}
94
+ value={value}
95
+ field={field}
96
+ selectedRows={selectedRows}
97
+ />
98
+ )}
99
+ </>
100
+ );
101
+ };
@@ -0,0 +1,12 @@
1
+ import { IconName } from "@linzjs/lui/dist/components/LuiIcon/LuiIcon";
2
+ import { LuiIcon } from "@linzjs/lui";
3
+
4
+ export const GridIcon = (props: { icon: IconName; title: string }): JSX.Element => (
5
+ <LuiIcon
6
+ name={props.icon}
7
+ title={props.title}
8
+ alt={props.title}
9
+ size={"md"}
10
+ className={`AgGridGenericCellRenderer-${props.icon}Icon`}
11
+ />
12
+ );
@@ -0,0 +1,22 @@
1
+ import { LuiMiniSpinner } from "@linzjs/lui";
2
+
3
+ export const GridLoadableCell = (props: {
4
+ isLoading: boolean;
5
+ dataTestId?: string;
6
+ children: JSX.Element | string;
7
+ }): JSX.Element => {
8
+ // console.log(`Rendering LoadableCell - loading: ${props.isLoading}`);
9
+ if (props.isLoading) {
10
+ return (
11
+ <div style={{ display: "flex", alignItems: "center" }}>
12
+ <LuiMiniSpinner size={22} divProps={{ role: "status", "aria-label": "Loading", style: { marginBottom: 4 } }} />
13
+ </div>
14
+ );
15
+ }
16
+ // only add test id into ONE of the columns in a grid. this way each row will have one unique id :)
17
+ return (
18
+ <div data-testid={props.dataTestId} style={{ display: "flex", alignItems: "center" }}>
19
+ {props.children}
20
+ </div>
21
+ );
22
+ };