@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/dist/src/components/Grid.d.ts +2 -4
- package/dist/src/contexts/GridContext.d.ts +1 -0
- package/dist/step-ag-grid.esm.js +1897 -1953
- package/dist/step-ag-grid.esm.js.map +1 -1
- package/package.json +11 -11
- package/src/components/Grid.tsx +12 -30
- package/src/contexts/GridContext.tsx +5 -0
- package/src/contexts/GridContextProvider.tsx +25 -7
- package/src/react-menu3/components/ControlledMenu.tsx +27 -61
- package/src/react-menu3/utils/utils.ts +3 -3
- package/src/stories/grid/interactions/GridKeyboardInteractions.stories.tsx +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": "14.0
|
|
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
|
|
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.
|
|
105
|
+
"@types/jest": "^29.5.2",
|
|
106
106
|
"@types/lodash-es": "^4.17.7",
|
|
107
|
-
"@types/node": "^20.2.
|
|
108
|
-
"@types/react": "^17.0.
|
|
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.
|
|
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.
|
|
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.
|
|
134
|
-
"postcss-loader": "^7.3.
|
|
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.
|
|
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.
|
|
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",
|
package/src/components/Grid.tsx
CHANGED
|
@@ -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"
|
|
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
|
-
|
|
115
|
-
(
|
|
116
|
-
|
|
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 =
|
|
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
|
-
|
|
365
|
-
|
|
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(
|
|
334
|
-
|
|
335
|
-
columnApi
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
|
102
|
-
input.
|
|
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;
|