@linzjs/step-ag-grid 2.4.3 → 2.4.4
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/index.js +105 -38
- package/dist/index.js.map +1 -1
- package/dist/src/contexts/GridContext.d.ts +2 -1
- package/dist/src/lui/ActionButton.d.ts +6 -3
- package/dist/step-ag-grid.esm.js +105 -38
- package/dist/step-ag-grid.esm.js.map +1 -1
- package/package.json +2 -1
- package/src/components/PostSortRowsHook.ts +78 -25
- package/src/contexts/GridContext.tsx +5 -1
- package/src/contexts/GridContextProvider.tsx +7 -0
- package/src/lui/ActionButton.tsx +23 -13
- package/src/stories/components/ActionButton.stories.tsx +6 -1
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": "2.4.
|
|
5
|
+
"version": "2.4.4",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"aggrid",
|
|
8
8
|
"ag-grid",
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
},
|
|
47
47
|
"scripts": {
|
|
48
48
|
"build": "run-s clean stylelint lint css bundle",
|
|
49
|
+
"yalc": "run-s clean css bundle && yalc publish",
|
|
49
50
|
"clean": "rimraf dist && mkdirp ./dist",
|
|
50
51
|
"bundle": "rollup -c",
|
|
51
52
|
"stylelint": "stylelint src/**/*.scss src/**/*.css --fix",
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import { useCallback, useRef } from "react";
|
|
1
|
+
import { useCallback, useContext, useRef } from "react";
|
|
2
2
|
import { PostSortRowsParams } from "ag-grid-community/dist/lib/entities/iCallbackParams";
|
|
3
3
|
import { ColumnState } from "ag-grid-community/dist/lib/columns/columnModel";
|
|
4
|
+
import { isEmpty } from "lodash-es";
|
|
5
|
+
import { RowNode } from "ag-grid-community";
|
|
6
|
+
import { GridContext } from "../contexts/GridContext";
|
|
4
7
|
|
|
5
8
|
interface PostSortRowsHookProps {
|
|
6
9
|
setStaleGrid: (stale: boolean) => void;
|
|
@@ -12,6 +15,8 @@ interface PostSortRowsHookProps {
|
|
|
12
15
|
* Handles stale sort, when you edit a row but don't want to re-sort.
|
|
13
16
|
*/
|
|
14
17
|
export const usePostSortRowsHook = ({ setStaleGrid }: PostSortRowsHookProps) => {
|
|
18
|
+
const { redrawRows } = useContext(GridContext);
|
|
19
|
+
|
|
15
20
|
// On first run we need to init the first backed up sort order
|
|
16
21
|
const initialised = useRef(false);
|
|
17
22
|
|
|
@@ -19,7 +24,7 @@ export const usePostSortRowsHook = ({ setStaleGrid }: PostSortRowsHookProps) =>
|
|
|
19
24
|
const lastSortOrderHash = useRef<string>("");
|
|
20
25
|
|
|
21
26
|
// Used to detect if sort order was the same, has the direction changed
|
|
22
|
-
const previousRowSortIndexRef = useRef<Record<
|
|
27
|
+
const previousRowSortIndexRef = useRef<Record<string, { index: number; hash: string } | undefined>>({});
|
|
23
28
|
|
|
24
29
|
// stale sort is when there's a sort and user edits a row
|
|
25
30
|
// this applies a class to the div wrapping the grid which in turn adds a * beside the sort arrow
|
|
@@ -33,14 +38,21 @@ export const usePostSortRowsHook = ({ setStaleGrid }: PostSortRowsHookProps) =>
|
|
|
33
38
|
|
|
34
39
|
return useCallback(
|
|
35
40
|
({ api, columnApi, nodes }: PostSortRowsParams) => {
|
|
41
|
+
// Grid is destroyed
|
|
42
|
+
if (!api || !columnApi) return;
|
|
43
|
+
|
|
36
44
|
const previousRowSortIndex = previousRowSortIndexRef.current;
|
|
37
45
|
|
|
46
|
+
const hashNode = (node: RowNode | undefined) => {
|
|
47
|
+
return node ? JSON.stringify(node.data) : "";
|
|
48
|
+
};
|
|
49
|
+
|
|
38
50
|
const copyCurrentSortSettings = (): ColumnState[] =>
|
|
39
51
|
columnApi.getColumnState().map((row) => ({ colId: row.colId, sort: row.sort, sortIndex: row.sortIndex }));
|
|
40
52
|
|
|
41
53
|
const backupSortOrder = () => {
|
|
42
54
|
for (const x in previousRowSortIndex) delete previousRowSortIndex[x];
|
|
43
|
-
nodes.forEach((
|
|
55
|
+
nodes.forEach((node, index) => (previousRowSortIndex[`${node.data.id}`] = { index, hash: hashNode(node) }));
|
|
44
56
|
};
|
|
45
57
|
|
|
46
58
|
// Check if column is the first sorted column. Note: column is preconfigured to sort its sortIndex is null not 1
|
|
@@ -51,22 +63,13 @@ export const usePostSortRowsHook = ({ setStaleGrid }: PostSortRowsHookProps) =>
|
|
|
51
63
|
|
|
52
64
|
const restorePreviousSortColumnState = () => columnApi.applyColumnState({ state: previousColSort.current });
|
|
53
65
|
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
const sortIsStale = () => {
|
|
57
|
-
// If there are new rows we want to them to be at the bottom of the grid, so we treat it as sort not stale
|
|
58
|
-
if (hasNewRows()) return false;
|
|
59
|
-
|
|
60
|
-
// Otherwise check if the stored sort index matches the new sort index
|
|
61
|
-
return nodes.some((node, index) => previousRowSortIndex[node.data.id] != index);
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
const sortNodesByPreviousSort = () =>
|
|
66
|
+
const sortNodesByPreviousSort = () => {
|
|
65
67
|
nodes.sort(
|
|
66
68
|
(a, b) =>
|
|
67
|
-
(previousRowSortIndex[a.data.id] ?? Number.MAX_SAFE_INTEGER) -
|
|
68
|
-
(previousRowSortIndex[b.data.id] ?? Number.MAX_SAFE_INTEGER),
|
|
69
|
+
(previousRowSortIndex[`${a.data.id}`]?.index ?? Number.MAX_SAFE_INTEGER) -
|
|
70
|
+
(previousRowSortIndex[`${b.data.id}`]?.index ?? Number.MAX_SAFE_INTEGER),
|
|
69
71
|
);
|
|
72
|
+
};
|
|
70
73
|
|
|
71
74
|
// On first load copy the current sort
|
|
72
75
|
if (!initialised.current) {
|
|
@@ -83,6 +86,10 @@ export const usePostSortRowsHook = ({ setStaleGrid }: PostSortRowsHookProps) =>
|
|
|
83
86
|
sortOrderChanged = true;
|
|
84
87
|
}
|
|
85
88
|
|
|
89
|
+
if (isEmpty(previousRowSortIndex)) {
|
|
90
|
+
backupSortOrder();
|
|
91
|
+
}
|
|
92
|
+
|
|
86
93
|
if (sortOrderChanged) {
|
|
87
94
|
const thisFirstCol = copyCurrentSortSettings().find(isFirstSortColumn);
|
|
88
95
|
const previousFirstCol = previousColSort.current.find(isFirstSortColumn);
|
|
@@ -115,23 +122,69 @@ export const usePostSortRowsHook = ({ setStaleGrid }: PostSortRowsHookProps) =>
|
|
|
115
122
|
lastSortOrderHash.current = newSortOrder;
|
|
116
123
|
}
|
|
117
124
|
} else {
|
|
118
|
-
|
|
125
|
+
let firstChangedNodeIndex = -1;
|
|
126
|
+
let lastNewNode: RowNode | undefined = undefined;
|
|
127
|
+
let changedRowCount = 0;
|
|
128
|
+
let newRowCount = 0;
|
|
129
|
+
let index = 0;
|
|
130
|
+
for (const node of nodes) {
|
|
131
|
+
const psr = previousRowSortIndex[`${node.data.id}`];
|
|
132
|
+
if (psr) {
|
|
133
|
+
if (psr.hash != hashNode(node)) {
|
|
134
|
+
if (firstChangedNodeIndex === -1) firstChangedNodeIndex = index;
|
|
135
|
+
changedRowCount++;
|
|
136
|
+
}
|
|
137
|
+
} else {
|
|
138
|
+
lastNewNode = node;
|
|
139
|
+
newRowCount++;
|
|
140
|
+
}
|
|
141
|
+
index++;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
let wasStale = false;
|
|
145
|
+
if (changedRowCount === 0 && newRowCount === 1) {
|
|
146
|
+
// insert new row at end
|
|
147
|
+
const newIndex = index - 1;
|
|
148
|
+
previousRowSortIndex[`${lastNewNode?.data.id}`] = { index: newIndex, hash: hashNode(lastNewNode) };
|
|
149
|
+
wasStale = true;
|
|
150
|
+
} else if (changedRowCount === 2 && newRowCount === 0) {
|
|
151
|
+
// This must be a swap rows
|
|
152
|
+
backupSortOrder();
|
|
153
|
+
wasStale = false;
|
|
154
|
+
} else if (changedRowCount > 1 && newRowCount === 1) {
|
|
155
|
+
// This must be a insert so, insert new row near the row that changed
|
|
156
|
+
previousRowSortIndex[`${lastNewNode?.data.id}`] = {
|
|
157
|
+
index: firstChangedNodeIndex + 0.5,
|
|
158
|
+
hash: hashNode(lastNewNode),
|
|
159
|
+
};
|
|
160
|
+
wasStale = true;
|
|
161
|
+
// For some reason AgGrid mis-positions the inserted row.
|
|
162
|
+
lastNewNode && redrawRows();
|
|
163
|
+
} else if (changedRowCount == 1 && newRowCount === 0) {
|
|
164
|
+
// User edited one row so, do nothing, retain sort
|
|
165
|
+
wasStale = true;
|
|
166
|
+
} else if (changedRowCount !== 0 || newRowCount != 0) {
|
|
167
|
+
// too many rows changed, resort
|
|
168
|
+
backupSortOrder();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (wasStale) {
|
|
172
|
+
// Check if the sort order the aggrid passed matches our stale sort order
|
|
173
|
+
const stillStale =
|
|
174
|
+
Object.keys(previousRowSortIndex).length != nodes.length ||
|
|
175
|
+
nodes.some((node, index) => previousRowSortIndex[`${node.data.id}`]?.index !== index);
|
|
176
|
+
|
|
119
177
|
// If we haven't already processed a stale sort then...
|
|
120
|
-
if (!sortWasStale.current) {
|
|
178
|
+
if (stillStale && !sortWasStale.current) {
|
|
121
179
|
// backup sort state, so we can restore it when sort is clicked on a stale column
|
|
122
180
|
previousColSort.current = copyCurrentSortSettings();
|
|
123
|
-
backupSortOrder();
|
|
124
181
|
sortWasStale.current = true;
|
|
125
182
|
setStaleGrid(true);
|
|
126
183
|
}
|
|
127
|
-
|
|
128
|
-
sortNodesByPreviousSort();
|
|
129
184
|
}
|
|
130
|
-
|
|
131
|
-
// which would cause two new rows to sort out of sequence
|
|
132
|
-
backupSortOrder();
|
|
185
|
+
sortNodesByPreviousSort();
|
|
133
186
|
}
|
|
134
187
|
},
|
|
135
|
-
[setStaleGrid],
|
|
188
|
+
[redrawRows, setStaleGrid],
|
|
136
189
|
);
|
|
137
190
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createContext } from "react";
|
|
2
|
-
import { GridApi } from "ag-grid-community";
|
|
2
|
+
import { GridApi, RowNode } from "ag-grid-community";
|
|
3
3
|
import { GridBaseRow } from "../components/Grid";
|
|
4
4
|
|
|
5
5
|
export interface GridContextType {
|
|
@@ -24,6 +24,7 @@ export interface GridContextType {
|
|
|
24
24
|
fnUpdate: (selectedRows: any[]) => Promise<boolean>,
|
|
25
25
|
setSaving?: (saving: boolean) => void,
|
|
26
26
|
) => Promise<boolean>;
|
|
27
|
+
redrawRows: (rowNodes?: RowNode[]) => void;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
export const GridContext = createContext<GridContextType>({
|
|
@@ -83,4 +84,7 @@ export const GridContext = createContext<GridContextType>({
|
|
|
83
84
|
console.error("no context provider for modifyUpdating");
|
|
84
85
|
return false;
|
|
85
86
|
},
|
|
87
|
+
redrawRows: () => {
|
|
88
|
+
console.error("no context provider for redrawRows");
|
|
89
|
+
},
|
|
86
90
|
});
|
|
@@ -273,6 +273,12 @@ export const GridContextProvider = (props: GridContextProps): ReactElement => {
|
|
|
273
273
|
});
|
|
274
274
|
};
|
|
275
275
|
|
|
276
|
+
const redrawRows = (rowNodes?: RowNode[]) => {
|
|
277
|
+
gridApiOp((gridApi) => {
|
|
278
|
+
gridApi.redrawRows(rowNodes ? { rowNodes } : undefined);
|
|
279
|
+
});
|
|
280
|
+
};
|
|
281
|
+
|
|
276
282
|
return (
|
|
277
283
|
<GridContext.Provider
|
|
278
284
|
value={{
|
|
@@ -293,6 +299,7 @@ export const GridContextProvider = (props: GridContextProps): ReactElement => {
|
|
|
293
299
|
sizeColumnsToFit,
|
|
294
300
|
stopEditing,
|
|
295
301
|
updatingCells,
|
|
302
|
+
redrawRows,
|
|
296
303
|
}}
|
|
297
304
|
>
|
|
298
305
|
{props.children}
|
package/src/lui/ActionButton.tsx
CHANGED
|
@@ -6,18 +6,21 @@ import { LuiButton, LuiIcon, LuiMiniSpinner } from "@linzjs/lui";
|
|
|
6
6
|
import { IconName } from "@linzjs/lui/dist/components/LuiIcon/LuiIcon";
|
|
7
7
|
import { usePrevious } from "./reactUtils";
|
|
8
8
|
import { useStateDeferred } from "./stateDeferredHook";
|
|
9
|
+
import { LuiButtonProps } from "@linzjs/lui/dist/components/LuiButton/LuiButton";
|
|
9
10
|
|
|
10
11
|
export interface ActionButtonProps {
|
|
11
12
|
icon: IconName;
|
|
12
|
-
name
|
|
13
|
+
name?: string;
|
|
14
|
+
"aria-label"?: string;
|
|
13
15
|
inProgressName?: string;
|
|
14
16
|
title?: string;
|
|
15
17
|
dataTestId?: string;
|
|
16
18
|
size?: "xs" | "sm" | "md" | "lg" | "xl" | "ns";
|
|
17
19
|
className?: string;
|
|
18
|
-
onAction: () => Promise<void
|
|
20
|
+
onAction: () => Promise<void> | void;
|
|
19
21
|
// Used for external code to get access to whether action is in progress
|
|
20
22
|
externalSetInProgress?: () => void;
|
|
23
|
+
level?: LuiButtonProps["level"];
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
// Kept this less than one second, so I don't have issues with waitFor as it defaults to 1s
|
|
@@ -33,6 +36,8 @@ export const ActionButton = ({
|
|
|
33
36
|
onAction,
|
|
34
37
|
externalSetInProgress,
|
|
35
38
|
size = "sm",
|
|
39
|
+
level = "tertiary",
|
|
40
|
+
"aria-label": ariaLabel,
|
|
36
41
|
}: ActionButtonProps): JSX.Element => {
|
|
37
42
|
const [inProgress, setInProgress] = useState(false);
|
|
38
43
|
const lastInProgress = usePrevious(inProgress ?? false);
|
|
@@ -47,17 +52,22 @@ export const ActionButton = ({
|
|
|
47
52
|
<LuiButton
|
|
48
53
|
data-testid={dataTestId}
|
|
49
54
|
type={"button"}
|
|
50
|
-
level={
|
|
51
|
-
title={title ?? name}
|
|
52
|
-
aria-label={name}
|
|
55
|
+
level={level}
|
|
56
|
+
title={title ?? ariaLabel ?? name}
|
|
57
|
+
aria-label={ariaLabel ?? name}
|
|
53
58
|
className={clsx("lui-button-icon", "ActionButton", className, localInProgress && "ActionButton-inProgress")}
|
|
54
59
|
size={"lg"}
|
|
60
|
+
style={name == null ? { padding: "8px 5px" } : {}}
|
|
55
61
|
onClick={async () => {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
62
|
+
const promise = onAction();
|
|
63
|
+
const isPromise = typeof promise !== "undefined";
|
|
64
|
+
if (isPromise) {
|
|
65
|
+
setInProgress(true);
|
|
66
|
+
externalSetInProgress && setInProgress(true);
|
|
67
|
+
if (isPromise) await promise;
|
|
68
|
+
externalSetInProgress && setInProgress(false);
|
|
69
|
+
setInProgress(false);
|
|
70
|
+
}
|
|
61
71
|
}}
|
|
62
72
|
disabled={localInProgress}
|
|
63
73
|
>
|
|
@@ -66,13 +76,13 @@ export const ActionButton = ({
|
|
|
66
76
|
size={16}
|
|
67
77
|
divProps={{
|
|
68
78
|
"data-testid": "loading-spinner",
|
|
69
|
-
style: {
|
|
79
|
+
style: { margin: 0, paddingRight: 5, paddingLeft: 3, paddingBottom: 0, paddingTop: 0 },
|
|
70
80
|
role: "status",
|
|
71
|
-
|
|
81
|
+
"aria-label": "Loading",
|
|
72
82
|
}}
|
|
73
83
|
/>
|
|
74
84
|
) : (
|
|
75
|
-
<LuiIcon name={icon} alt={name} size={size} />
|
|
85
|
+
<LuiIcon name={icon} alt={ariaLabel ?? name ?? ""} size={size} />
|
|
76
86
|
)}
|
|
77
87
|
<span className={"ActionButton-minimalArea"}>
|
|
78
88
|
<span className={"ActionButton-minimalAreaDisplay"}>{(localInProgress ? inProgressName : name) ?? name}</span>
|
|
@@ -16,7 +16,12 @@ const ActionButtonTemplate: ComponentStory<typeof ActionButton> = () => {
|
|
|
16
16
|
const performAction = useCallback(async () => {
|
|
17
17
|
await wait(1000);
|
|
18
18
|
}, []);
|
|
19
|
-
return
|
|
19
|
+
return (
|
|
20
|
+
<>
|
|
21
|
+
<ActionButton icon={"ic_add"} name={"Add new row"} inProgressName={"Adding..."} onAction={performAction} />
|
|
22
|
+
<ActionButton icon={"ic_add"} aria-label={"Add new row"} onAction={performAction} level={"primary"} />
|
|
23
|
+
</>
|
|
24
|
+
);
|
|
20
25
|
};
|
|
21
26
|
|
|
22
27
|
export const ActionButtonSimple = ActionButtonTemplate.bind({});
|