@linzjs/step-ag-grid 14.0.3 → 14.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/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.0.3",
5
+ "version": "14.1.0",
6
6
  "keywords": [
7
7
  "aggrid",
8
8
  "ag-grid",
@@ -84,7 +84,7 @@
84
84
  "devDependencies": {
85
85
  "@rollup/plugin-commonjs": "^25.0.0",
86
86
  "@rollup/plugin-json": "^6.0.0",
87
- "@rollup/plugin-node-resolve": "^15.0.2",
87
+ "@rollup/plugin-node-resolve": "^15.1.0",
88
88
  "@semantic-release/changelog": "^6.0.3",
89
89
  "@semantic-release/git": "^10.0.1",
90
90
  "@storybook/addon-essentials": "^6.5.16",
@@ -102,16 +102,16 @@
102
102
  "@testing-library/user-event": "^13.5.0",
103
103
  "@trivago/prettier-plugin-sort-imports": "^4.1.1",
104
104
  "@types/debounce-promise": "^3.1.6",
105
- "@types/jest": "^29.5.1",
105
+ "@types/jest": "^29.5.2",
106
106
  "@types/lodash-es": "^4.17.7",
107
- "@types/node": "^20.2.3",
108
- "@types/react": "^17.0.59",
107
+ "@types/node": "^20.2.5",
108
+ "@types/react": "^17.0.60",
109
109
  "@types/react-dom": "^17.0.20",
110
110
  "@types/uuid": "^9.0.1",
111
- "@typescript-eslint/parser": "^5.59.7",
111
+ "@typescript-eslint/parser": "^5.59.8",
112
112
  "babel-jest": "^29.5.0",
113
113
  "babel-preset-react-app": "^10.0.1",
114
- "chromatic": "^6.17.4",
114
+ "chromatic": "^6.18.0",
115
115
  "conventional-changelog-conventionalcommits": "^5.0.0",
116
116
  "eslint": "^8.41.0",
117
117
  "eslint-config-prettier": "^8.8.0",
@@ -130,8 +130,8 @@
130
130
  "jest-expect-message": "^1.1.3",
131
131
  "mkdirp": "^3.0.1",
132
132
  "npm-run-all": "^4.1.5",
133
- "postcss": "^8.4.23",
134
- "postcss-loader": "^7.3.0",
133
+ "postcss": "^8.4.24",
134
+ "postcss-loader": "^7.3.2",
135
135
  "postcss-scss": "^4.0.6",
136
136
  "prettier": "^2.8.8",
137
137
  "react-app-polyfill": "^3.0.0",
@@ -142,11 +142,11 @@
142
142
  "rollup-plugin-postcss": "^4.0.2",
143
143
  "rollup-plugin-typescript2": "^0.34.1",
144
144
  "sass": "^1.62.1",
145
- "sass-loader": "^13.3.0",
145
+ "sass-loader": "^13.3.1",
146
146
  "semantic-release": "^19.0.5",
147
147
  "style-loader": "^3.3.3",
148
148
  "stylelint": "^14.16.1",
149
- "stylelint-config-prettier": "^9.0.4",
149
+ "stylelint-config-prettier": "^9.0.5",
150
150
  "stylelint-config-recommended-scss": "^8.0.0",
151
151
  "stylelint-config-standard": "^29.0.0",
152
152
  "stylelint-prettier": "3.0.0",
@@ -1,13 +1,7 @@
1
1
  import { CellClickedEvent, ColDef, ModelUpdatedEvent } from "ag-grid-community";
2
2
  import { CellClassParams, EditableCallback, EditableCallbackParams } from "ag-grid-community/dist/lib/entities/colDef";
3
3
  import { GridOptions } from "ag-grid-community/dist/lib/entities/gridOptions";
4
- import {
5
- CellEvent,
6
- FirstDataRenderedEvent,
7
- GridReadyEvent,
8
- GridSizeChangedEvent,
9
- SelectionChangedEvent,
10
- } from "ag-grid-community/dist/lib/events";
4
+ import { CellEvent, GridReadyEvent, SelectionChangedEvent } from "ag-grid-community/dist/lib/events";
11
5
  import { AgGridReact } from "ag-grid-react";
12
6
  import clsx from "clsx";
13
7
  import { difference, isEmpty, last, xorBy } from "lodash-es";
@@ -43,8 +37,6 @@ export interface GridProps {
43
37
  autoSelectFirstRow?: boolean;
44
38
  onColumnMoved?: GridOptions["onColumnMoved"];
45
39
  alwaysShowVerticalScroll?: boolean;
46
- onGridSizeChanged?: GridOptions["onGridSizeChanged"];
47
- onFirstDataRendered?: GridOptions["onFirstDataRendered"];
48
40
  suppressColumnVirtualization?: GridOptions["suppressColumnVirtualisation"];
49
41
  /**
50
42
  * When the grid is rendered using sizeColumns=="auto" this is called initially with the required container size to fit all content.
@@ -57,8 +49,8 @@ export interface GridProps {
57
49
  * <li>"fit" will adjust columns to fit within panel via min/max/initial sizing.
58
50
  * <b>Note:</b> This is only really needed if you have auto-height columns which prevents "auto" from working.
59
51
  * </li>
60
- * <li>"auto" (default) will size columns based on their content but still obeying min/max sizing.</li>
61
- * <li>"auto-skip-headers" same as auto but does not take headers into account.</li>
52
+ * <li>"auto" will size columns based on their content but still obeying min/max sizing.</li>
53
+ * <li>"auto-skip-headers" (default) same as auto but does not take headers into account.</li>
62
54
  * </ul>
63
55
  *
64
56
  * If you want to stretch to container width if width is greater than the container add a flex column.
@@ -82,6 +74,7 @@ export const Grid = ({
82
74
  setApis,
83
75
  prePopupOps,
84
76
  ensureRowVisible,
77
+ getFirstRowId,
85
78
  selectRowsById,
86
79
  focusByRowById,
87
80
  ensureSelectedRowIsVisible,
@@ -111,13 +104,9 @@ export const Grid = ({
111
104
  }
112
105
  }, [autoSizeAllColumns, params, sizeColumns, sizeColumnsToFit]);
113
106
 
114
- const onFirstDataRendered = useCallback(
115
- (event: FirstDataRenderedEvent) => {
116
- params.onFirstDataRendered && params.onFirstDataRendered(event);
117
- setInitialContentSize();
118
- },
119
- [params, setInitialContentSize],
120
- );
107
+ useEffect(() => {
108
+ gridReady && setInitialContentSize();
109
+ }, [gridReady, setInitialContentSize]);
121
110
 
122
111
  /**
123
112
  * On data load select the first row of the grid if required.
@@ -127,7 +116,7 @@ export const Grid = ({
127
116
  if (!gridReady || hasSelectedFirstItem.current || !params.rowData || !externallySelectedItemsAreInSync) return;
128
117
  hasSelectedFirstItem.current = true;
129
118
  if (isNotEmpty(params.rowData) && isEmpty(params.externalSelectedItems)) {
130
- const firstRowId = params.rowData[0].id;
119
+ const firstRowId = getFirstRowId();
131
120
  if (params.autoSelectFirstRow) {
132
121
  selectRowsById([firstRowId]);
133
122
  } else {
@@ -142,6 +131,7 @@ export const Grid = ({
142
131
  params.autoSelectFirstRow,
143
132
  params.rowData,
144
133
  selectRowsById,
134
+ getFirstRowId,
145
135
  ]);
146
136
 
147
137
  /**
@@ -360,13 +350,9 @@ export const Grid = ({
360
350
  [startCellEditing],
361
351
  );
362
352
 
363
- const onGridSizeChanged = useCallback(
364
- (event: GridSizeChangedEvent) => {
365
- params.onGridSizeChanged && params.onGridSizeChanged(event);
366
- sizeColumns !== "none" && sizeColumnsToFit();
367
- },
368
- [params, sizeColumns, sizeColumnsToFit],
369
- );
353
+ const onGridSizeChanged = useCallback(() => {
354
+ sizeColumns !== "none" && sizeColumnsToFit();
355
+ }, [sizeColumns, sizeColumnsToFit]);
370
356
 
371
357
  /**
372
358
  * Once the grid has auto-sized we want to run fit to fit the grid in its container,
@@ -399,7 +385,6 @@ export const Grid = ({
399
385
  suppressRowClickSelection={true}
400
386
  rowSelection={rowSelection}
401
387
  suppressBrowserResizeObserver={true}
402
- onFirstDataRendered={onFirstDataRendered}
403
388
  onGridSizeChanged={onGridSizeChanged}
404
389
  suppressColumnVirtualisation={suppressColumnVirtualization}
405
390
  suppressClickEdit={true}
@@ -418,9 +403,6 @@ export const Grid = ({
418
403
  postSortRows={params.postSortRows ?? postSortRows}
419
404
  onSelectionChanged={synchroniseExternalStateToGridSelection}
420
405
  onColumnMoved={params.onColumnMoved}
421
- onColumnResized={() => {
422
- sizeColumns !== "none" && sizeColumnsToFit();
423
- }}
424
406
  alwaysShowVerticalScroll={params.alwaysShowVerticalScroll}
425
407
  isExternalFilterPresent={isExternalFilterPresent}
426
408
  doesExternalFilterPass={doesExternalFilterPass}
@@ -25,6 +25,7 @@ export interface GridContextType<RowType extends GridBaseRow> {
25
25
  focusByRowById: (rowId: number) => void;
26
26
  ensureRowVisible: (id: number | string) => boolean;
27
27
  ensureSelectedRowIsVisible: () => void;
28
+ getFirstRowId: () => number;
28
29
  autoSizeAllColumns: (props?: { skipHeader?: boolean }) => { width: number } | null;
29
30
  sizeColumnsToFit: () => void;
30
31
  stopEditing: () => void;
@@ -112,6 +113,10 @@ export const GridContext = createContext<GridContextType<any>>({
112
113
  ensureSelectedRowIsVisible: () => {
113
114
  console.error("no context provider for ensureSelectedRowIsVisible");
114
115
  },
116
+ getFirstRowId: () => {
117
+ console.error("no context provider for getFirstRowId");
118
+ return -1;
119
+ },
115
120
  autoSizeAllColumns: () => {
116
121
  console.error("no context provider for autoSizeAllColumns");
117
122
  return null;
@@ -74,6 +74,20 @@ export const GridContextProvider = <RowType extends GridBaseRow>(props: GridCont
74
74
  [gridApiOp],
75
75
  );
76
76
 
77
+ const getFirstRowId = useCallback((): number => {
78
+ let id = 0;
79
+ try {
80
+ gridApi?.forEachNodeAfterFilterAndSort((rowNode) => {
81
+ id = parseInt(rowNode.id ?? "0");
82
+ // this is the only way to get out of the loop
83
+ throw "expected exception - exit_loop";
84
+ });
85
+ } catch (ex) {
86
+ // ignore
87
+ }
88
+ return id;
89
+ }, [gridApi]);
90
+
77
91
  /**
78
92
  * Set the grid api when the grid is ready.
79
93
  */
@@ -330,13 +344,16 @@ export const GridContextProvider = <RowType extends GridBaseRow>(props: GridCont
330
344
  /**
331
345
  * Resize columns to fit container
332
346
  */
333
- const autoSizeAllColumns = useCallback((): { width: number } | null => {
334
- if (columnApi) {
335
- columnApi.autoSizeAllColumns();
336
- return { width: sumBy(columnApi.getColumnState(), "width") };
337
- }
338
- return null;
339
- }, [columnApi]);
347
+ const autoSizeAllColumns = useCallback(
348
+ ({ skipHeader }): { width: number } | null => {
349
+ if (columnApi) {
350
+ columnApi.autoSizeAllColumns(skipHeader);
351
+ return { width: sumBy(columnApi.getColumnState(), "width") };
352
+ }
353
+ return null;
354
+ },
355
+ [columnApi],
356
+ );
340
357
 
341
358
  /**
342
359
  * Resize columns to fit container
@@ -530,6 +547,7 @@ export const GridContextProvider = <RowType extends GridBaseRow>(props: GridCont
530
547
  getFilteredSelectedRows,
531
548
  getSelectedRowIds,
532
549
  getFilteredSelectedRowIds,
550
+ getFirstRowId,
533
551
  editingCells,
534
552
  ensureRowVisible,
535
553
  ensureSelectedRowIsVisible,
@@ -134,18 +134,8 @@ export const ControlledMenuFr = (
134
134
  saveButtonRef?.current?.click();
135
135
  };
136
136
 
137
+ // data-allowtabtosave is used such that list filter inputs can use tab to save
137
138
  const allowTabToSave = activeElement.getAttribute("data-allowtabtosave") == "true";
138
- if (allowTabToSave && ev.key === "Tab") {
139
- if (isDown) {
140
- ev.preventDefault();
141
- ev.stopPropagation();
142
- lastTabDownEl.current = activeElement;
143
- } else {
144
- lastTabDownEl.current == activeElement &&
145
- invokeSave(ev.shiftKey ? CloseReason.TAB_BACKWARD : CloseReason.TAB_FORWARD);
146
- }
147
- return;
148
- }
149
139
 
150
140
  const inputElsIterator = thisDocument.querySelectorAll<HTMLElement>(".szh-menu--state-open input,textarea");
151
141
  let inputEls: HTMLElement[] = [];
@@ -154,48 +144,37 @@ export const ControlledMenuFr = (
154
144
  if (inputEls.length === 0) return;
155
145
  const firstInputEl = inputEls[0];
156
146
  const lastInputEl = inputEls[inputEls.length - 1];
157
- if (activeElement !== firstInputEl && activeElement !== lastInputEl) return;
158
147
 
159
148
  const isTextArea = activeElement.nodeName === "TEXTAREA";
160
149
  const suppressEnterAutoSave = activeElement.getAttribute("data-disableenterautosave") == "true" || isTextArea;
161
150
 
162
- switch (activeElement.nodeName) {
163
- case "TEXTAREA":
164
- case "INPUT": {
165
- if ((activeElement === lastInputEl && activeElement === firstInputEl) || allowTabToSave) {
166
- if (ev.key === "Tab") {
167
- // Can't forward/backwards tab out of popup
168
- ev.preventDefault();
169
- ev.stopPropagation();
170
- if (isDown) {
171
- lastTabDownEl.current = activeElement;
172
- } else {
173
- lastTabDownEl.current == activeElement &&
174
- invokeSave(ev.shiftKey ? CloseReason.TAB_BACKWARD : CloseReason.TAB_FORWARD);
175
- }
176
- }
177
- if (ev.key === "Enter" && !suppressEnterAutoSave) {
178
- ev.preventDefault();
179
- ev.stopPropagation();
180
- if (isDown) {
181
- lastEnterDownEl.current = activeElement;
182
- } else {
183
- lastEnterDownEl.current == activeElement && invokeSave(CloseReason.CLICK);
184
- }
185
- }
186
- } else if (activeElement === lastInputEl) {
187
- if (ev.key === "Tab" && !ev.shiftKey) {
188
- // Can't backward tab out of popup
189
- ev.preventDefault();
190
- ev.stopPropagation();
151
+ if (ev.key === "Tab") {
152
+ const tabDirection = ev.shiftKey ? CloseReason.TAB_BACKWARD : CloseReason.TAB_FORWARD;
153
+ if (
154
+ (activeElement === lastInputEl && !ev.shiftKey) ||
155
+ (activeElement === firstInputEl && ev.shiftKey) ||
156
+ allowTabToSave
157
+ ) {
158
+ ev.preventDefault();
159
+ ev.stopPropagation();
191
160
 
192
- if (isDown) {
193
- lastTabDownEl.current = activeElement;
194
- } else {
195
- lastTabDownEl.current == activeElement && invokeSave(CloseReason.TAB_FORWARD);
196
- }
197
- }
198
- if (ev.key === "Enter" && !suppressEnterAutoSave) {
161
+ if (isDown) {
162
+ lastTabDownEl.current = activeElement;
163
+ } else {
164
+ lastTabDownEl.current == activeElement && invokeSave(tabDirection);
165
+ }
166
+ }
167
+ }
168
+
169
+ const isTextInput =
170
+ "type" in activeElement &&
171
+ (activeElement.type === "text" || activeElement.type == null || activeElement.type === "textarea");
172
+
173
+ switch (activeElement.nodeName) {
174
+ case "INPUT":
175
+ {
176
+ // If there's only one input element, we support tab and enter
177
+ if (isTextInput && ev.key === "Enter" && !suppressEnterAutoSave) {
199
178
  ev.preventDefault();
200
179
  ev.stopPropagation();
201
180
  if (isDown) {
@@ -204,21 +183,8 @@ export const ControlledMenuFr = (
204
183
  lastEnterDownEl.current == activeElement && invokeSave(CloseReason.CLICK);
205
184
  }
206
185
  }
207
- } else if (activeElement === firstInputEl) {
208
- if (ev.key === "Tab" && ev.shiftKey) {
209
- // Can't backward tab out of popup
210
- ev.preventDefault();
211
- ev.stopPropagation();
212
-
213
- if (isDown) {
214
- lastTabDownEl.current = activeElement;
215
- } else {
216
- lastTabDownEl.current == activeElement && invokeSave(CloseReason.TAB_BACKWARD);
217
- }
218
- }
219
186
  }
220
187
  break;
221
- }
222
188
  }
223
189
  },
224
190
  [anchorRef, saveButtonRef],
@@ -91,15 +91,15 @@ export const indexOfNode = (nodeList: NodeListOf<Node>, node: Node) => findIndex
91
91
  export const focusFirstInput = (container: any) => {
92
92
  // We can't use instanceof Element in portals, so I use querySelectorAll as a proxy here
93
93
  if (!container || !("querySelectorAll" in container)) return false;
94
- const inputs = container.querySelectorAll("input[type='text'],textarea");
94
+ const inputs = container.querySelectorAll("input[type='text'],input:not([type]),textarea");
95
95
  const input = inputs[0];
96
96
  // Using focus as proxy for HTMLElement
97
97
  if (!input || !("focus" in input)) return false;
98
98
  input.focus();
99
99
  // Text areas should start at end
100
100
  // this is a proxy for instanceof HTMLTextAreaElement
101
- if (input.type === "textarea") {
102
- input.selectionStart = input.value.length;
101
+ if (["textarea", "text"].includes(input.type)) {
102
+ input.setSelectionRange(0, input.value.length);
103
103
  }
104
104
  return true;
105
105
  };
@@ -213,6 +213,9 @@ const GridKeyboardInteractionsTemplate: ComponentStory<typeof Grid> = (props: Gr
213
213
 
214
214
  export const GridKeyboardInteractions = GridKeyboardInteractionsTemplate.bind({});
215
215
  GridKeyboardInteractions.play = async ({ canvasElement }) => {
216
+ multiEditAction.mockReset();
217
+ eAction.mockReset();
218
+
216
219
  // Ensure first row/cell is selected on render
217
220
  await waitFor(async () => {
218
221
  const activeCell = canvasElement.ownerDocument.activeElement;