@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.
- package/README.md +4 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +89342 -0
- package/dist/index.js.map +1 -0
- package/dist/src/components/ComponentLoadingWrapper.d.ts +9 -0
- package/dist/src/components/GenericCellClass.d.ts +2 -0
- package/dist/src/components/Grid.d.ts +24 -0
- package/dist/src/components/GridCell.d.ts +31 -0
- package/dist/src/components/GridIcon.d.ts +6 -0
- package/dist/src/components/GridLoadableCell.d.ts +6 -0
- package/dist/src/components/GridPopoutHook.d.ts +7 -0
- package/dist/src/components/GridSubComponentTextArea.d.ts +5 -0
- package/dist/src/components/PostSortRowsHook.d.ts +11 -0
- package/dist/src/components/gridForm/GridFormDropDown.d.ts +26 -0
- package/dist/src/components/gridForm/GridFormEditBearing.d.ts +9 -0
- package/dist/src/components/gridForm/GridFormMessage.d.ts +8 -0
- package/dist/src/components/gridForm/GridFormMultiSelect.d.ts +26 -0
- package/dist/src/components/gridForm/GridFormPopoutMenu.d.ts +24 -0
- package/dist/src/components/gridForm/GridFormTextArea.d.ts +11 -0
- package/dist/src/components/gridForm/GridFormTextInput.d.ts +11 -0
- package/dist/src/components/gridHeader/GridHeaderSelect.d.ts +8 -0
- package/dist/src/components/gridPopoverEdit/GridPopoutEditMultiSelect.d.ts +4 -0
- package/dist/src/components/gridPopoverEdit/GridPopoutMenu.d.ts +10 -0
- package/dist/src/components/gridPopoverEdit/GridPopoverEditBearing.d.ts +4 -0
- package/dist/src/components/gridPopoverEdit/GridPopoverEditDropDown.d.ts +4 -0
- package/dist/src/components/gridPopoverEdit/GridPopoverMessage.d.ts +4 -0
- package/dist/src/components/gridRender/GridRenderGenericCell.d.ts +15 -0
- package/dist/src/components/gridRender/GridRenderPopoutMenuCell.d.ts +3 -0
- package/dist/src/contexts/GridContext.d.ts +25 -0
- package/dist/src/contexts/GridContextProvider.d.ts +11 -0
- package/dist/src/contexts/UpdatingContext.d.ts +6 -0
- package/dist/src/contexts/UpdatingContextProvider.d.ts +7 -0
- package/dist/src/lui/TextAreaInput.d.ts +11 -0
- package/dist/src/lui/TextInputFormatted.d.ts +14 -0
- package/dist/src/setupTests.d.ts +1 -0
- package/dist/src/stories/components/FormTest.d.ts +12 -0
- package/dist/src/stories/components/GridPopoutBearing.stories.d.ts +9 -0
- package/dist/src/stories/components/GridPopoutEditDropDown.stories.d.ts +9 -0
- package/dist/src/stories/components/GridPopoutEditGeneric.stories.d.ts +9 -0
- package/dist/src/stories/components/GridPopoutEditGenericTextArea.stories.d.ts +9 -0
- package/dist/src/stories/components/GridPopoutEditMultiSelect.stories.d.ts +9 -0
- package/dist/src/stories/components/GridReadOnly.stories.d.ts +9 -0
- package/dist/src/utils/bearing.d.ts +5 -0
- package/dist/src/utils/util.d.ts +4 -0
- package/dist/step-ag-grid.esm.js +89297 -0
- package/dist/step-ag-grid.esm.js.map +1 -0
- package/package.json +126 -0
- package/src/components/ComponentLoadingWrapper.tsx +31 -0
- package/src/components/GenericCellClass.tsx +15 -0
- package/src/components/Grid.scss +38 -0
- package/src/components/Grid.tsx +262 -0
- package/src/components/GridCell.tsx +101 -0
- package/src/components/GridIcon.tsx +12 -0
- package/src/components/GridLoadableCell.tsx +22 -0
- package/src/components/GridPopoutHook.tsx +118 -0
- package/src/components/GridSubComponentTextArea.tsx +26 -0
- package/src/components/GridTheme.scss +94 -0
- package/src/components/PostSortRowsHook.ts +131 -0
- package/src/components/gridForm/GridFormDropDown.tsx +192 -0
- package/src/components/gridForm/GridFormEditBearing.scss +3 -0
- package/src/components/gridForm/GridFormEditBearing.tsx +62 -0
- package/src/components/gridForm/GridFormMessage.tsx +34 -0
- package/src/components/gridForm/GridFormMultiSelect.tsx +180 -0
- package/src/components/gridForm/GridFormPopoutMenu.tsx +90 -0
- package/src/components/gridForm/GridFormTextArea.tsx +61 -0
- package/src/components/gridForm/GridFormTextInput.tsx +66 -0
- package/src/components/gridHeader/GridHeaderSelect.tsx +54 -0
- package/src/components/gridPopoverEdit/GridPopoutEditMultiSelect.ts +22 -0
- package/src/components/gridPopoverEdit/GridPopoutMenu.scss +9 -0
- package/src/components/gridPopoverEdit/GridPopoutMenu.tsx +35 -0
- package/src/components/gridPopoverEdit/GridPopoverEditBearing.ts +24 -0
- package/src/components/gridPopoverEdit/GridPopoverEditDropDown.ts +22 -0
- package/src/components/gridPopoverEdit/GridPopoverMessage.ts +22 -0
- package/src/components/gridRender/GridRenderGenericCell.scss +15 -0
- package/src/components/gridRender/GridRenderGenericCell.tsx +49 -0
- package/src/components/gridRender/GridRenderPopoutMenuCell.tsx +22 -0
- package/src/contexts/GridContext.tsx +82 -0
- package/src/contexts/GridContextProvider.tsx +292 -0
- package/src/contexts/UpdatingContext.tsx +16 -0
- package/src/contexts/UpdatingContextProvider.tsx +43 -0
- package/src/lui/TextAreaInput.tsx +50 -0
- package/src/lui/TextInputFormatted.scss +11 -0
- package/src/lui/TextInputFormatted.tsx +60 -0
- package/src/lui-overrides.scss +163 -0
- package/src/react-app-env.d.ts +1 -0
- package/src/setupTests.ts +5 -0
- package/src/stories/components/FormTest.scss +10 -0
- package/src/stories/components/FormTest.tsx +51 -0
- package/src/stories/components/GridPopoutBearing.stories.tsx +100 -0
- package/src/stories/components/GridPopoutEditDropDown.stories.tsx +174 -0
- package/src/stories/components/GridPopoutEditGeneric.stories.tsx +76 -0
- package/src/stories/components/GridPopoutEditGenericTextArea.stories.tsx +106 -0
- package/src/stories/components/GridPopoutEditMultiSelect.stories.tsx +111 -0
- package/src/stories/components/GridReadOnly.stories.tsx +151 -0
- package/src/utils/bearing.ts +72 -0
- package/src/utils/util.ts +26 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ICellRendererParams } from "ag-grid-community";
|
|
2
|
+
import { useContext } from "react";
|
|
3
|
+
import { UpdatingContext } from "../../contexts/UpdatingContext";
|
|
4
|
+
import { GridLoadableCell } from "../GridLoadableCell";
|
|
5
|
+
import { LuiIcon } from "@linzjs/lui";
|
|
6
|
+
|
|
7
|
+
export const GridRenderPopoutMenuCell = (props: ICellRendererParams) => {
|
|
8
|
+
const { checkUpdating } = useContext(UpdatingContext);
|
|
9
|
+
const isLoading = checkUpdating(props.colDef?.field ?? "", props.data.id);
|
|
10
|
+
const disabled = !props.colDef?.editable;
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<GridLoadableCell isLoading={isLoading}>
|
|
14
|
+
<LuiIcon
|
|
15
|
+
name={"ic_more_vert"}
|
|
16
|
+
alt={"More actions"}
|
|
17
|
+
size={"md"}
|
|
18
|
+
className={disabled ? `GridPopoutMenu-burgerDisabled` : `GridPopoutMenu-burger`}
|
|
19
|
+
/>
|
|
20
|
+
</GridLoadableCell>
|
|
21
|
+
);
|
|
22
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { createContext } from "react";
|
|
2
|
+
import { GridApi } from "ag-grid-community";
|
|
3
|
+
import { GridBaseRow } from "../components/Grid";
|
|
4
|
+
|
|
5
|
+
export interface GridContextType {
|
|
6
|
+
gridReady: () => boolean;
|
|
7
|
+
setGridApi: (gridApi: GridApi | undefined) => void;
|
|
8
|
+
setQuickFilter: (quickFilter: string) => void;
|
|
9
|
+
editingCells: () => boolean;
|
|
10
|
+
getSelectedRows: <T extends unknown>() => T[];
|
|
11
|
+
getSelectedRowIds: () => number[];
|
|
12
|
+
selectRowsDiff: (updateFn: () => Promise<any>) => Promise<void>;
|
|
13
|
+
selectRowsWithFlashDiff: (updateFn: () => Promise<any>) => Promise<void>;
|
|
14
|
+
selectRowsById: (rowIds?: number[]) => void;
|
|
15
|
+
selectRowsByIdWithFlash: (rowIds?: number[]) => void;
|
|
16
|
+
flashRows: (rowIds?: number[]) => void;
|
|
17
|
+
flashRowsDiff: (updateFn: () => Promise<any>) => Promise<void>;
|
|
18
|
+
ensureRowVisible: (id: number) => void;
|
|
19
|
+
ensureSelectedRowIsVisible: () => void;
|
|
20
|
+
stopEditing: () => void;
|
|
21
|
+
updatingCells: (
|
|
22
|
+
props: { selectedRows: GridBaseRow[]; field?: string },
|
|
23
|
+
fnUpdate: (selectedRows: any[]) => Promise<boolean>,
|
|
24
|
+
setSaving?: (saving: boolean) => void,
|
|
25
|
+
) => Promise<boolean>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const GridContext = createContext<GridContextType>({
|
|
29
|
+
gridReady: () => {
|
|
30
|
+
console.error("no context provider for gridReady");
|
|
31
|
+
return false;
|
|
32
|
+
},
|
|
33
|
+
setGridApi: () => {
|
|
34
|
+
console.error("no context provider for setGridApi");
|
|
35
|
+
},
|
|
36
|
+
setQuickFilter: () => {
|
|
37
|
+
console.error("no context provider for setQuickFilter");
|
|
38
|
+
},
|
|
39
|
+
selectRowsById: () => {
|
|
40
|
+
console.error("no context provider for selectRows");
|
|
41
|
+
},
|
|
42
|
+
getSelectedRows: <T extends unknown>(): T[] => {
|
|
43
|
+
console.error("no context provider for getSelectedRows");
|
|
44
|
+
return [];
|
|
45
|
+
},
|
|
46
|
+
getSelectedRowIds: () => {
|
|
47
|
+
console.error("no context provider for getSelectedRowIds");
|
|
48
|
+
return [];
|
|
49
|
+
},
|
|
50
|
+
selectRowsDiff: async () => {
|
|
51
|
+
console.error("no context provider for selectRowsDiff");
|
|
52
|
+
},
|
|
53
|
+
selectRowsByIdWithFlash: () => {
|
|
54
|
+
console.error("no context provider for selectRowsWithFlash");
|
|
55
|
+
},
|
|
56
|
+
selectRowsWithFlashDiff: async () => {
|
|
57
|
+
console.error("no context provider for selectRowsWithFlashDiff");
|
|
58
|
+
},
|
|
59
|
+
flashRows: () => {
|
|
60
|
+
console.error("no context provider for flashRows");
|
|
61
|
+
},
|
|
62
|
+
flashRowsDiff: async () => {
|
|
63
|
+
console.error("no context provider for flashRows");
|
|
64
|
+
},
|
|
65
|
+
ensureRowVisible: () => {
|
|
66
|
+
console.error("no context provider for ensureRowVisible");
|
|
67
|
+
},
|
|
68
|
+
ensureSelectedRowIsVisible: () => {
|
|
69
|
+
console.error("no context provider for ensureSelectedRowIsVisible");
|
|
70
|
+
},
|
|
71
|
+
editingCells: () => {
|
|
72
|
+
console.error("no context provider for editingCells");
|
|
73
|
+
return false;
|
|
74
|
+
},
|
|
75
|
+
stopEditing: () => {
|
|
76
|
+
console.error("no context provider for stopEditing");
|
|
77
|
+
},
|
|
78
|
+
updatingCells: async () => {
|
|
79
|
+
console.error("no context provider for modifyUpdating");
|
|
80
|
+
return false;
|
|
81
|
+
},
|
|
82
|
+
});
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { ReactElement, ReactNode, useContext, useRef } from "react";
|
|
2
|
+
import { GridApi, RowNode } from "ag-grid-community";
|
|
3
|
+
import { GridContext } from "./GridContext";
|
|
4
|
+
import { delay, difference, isEmpty, last, sortBy } from "lodash-es";
|
|
5
|
+
import { isNotEmpty } from "../utils/util";
|
|
6
|
+
import { UpdatingContext } from "./UpdatingContext";
|
|
7
|
+
import { GridBaseRow } from "../components/Grid";
|
|
8
|
+
|
|
9
|
+
interface GridContextProps {
|
|
10
|
+
children: ReactNode;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Context for AgGrid operations.
|
|
15
|
+
* Make sure you wrap AgGrid in this.
|
|
16
|
+
* Also, make sure the provider is created in a separate component, otherwise it won't be found.
|
|
17
|
+
*/
|
|
18
|
+
export const GridContextProvider = (props: GridContextProps): ReactElement => {
|
|
19
|
+
const { modifyUpdating } = useContext(UpdatingContext);
|
|
20
|
+
const gridApiRef = useRef<GridApi>();
|
|
21
|
+
const idsBeforeUpdate = useRef<number[]>([]);
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Has Grid api has been set?
|
|
25
|
+
* Some ops in components may occur before onGridReady has been called,
|
|
26
|
+
* and thus will need to check grid is ready before calling.
|
|
27
|
+
*/
|
|
28
|
+
const gridReady = () => gridApiRef.current != null;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Set current ref to grid api.
|
|
32
|
+
*/
|
|
33
|
+
const setGridApi = (_gridApi: GridApi | undefined) => {
|
|
34
|
+
gridApiRef.current = _gridApi;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Wraps things that require gridApi in common handling, for when gridApi not present.
|
|
39
|
+
*
|
|
40
|
+
* @param hasApiFn Execute when api is ready.
|
|
41
|
+
* @param noApiFn Execute if api is not ready.
|
|
42
|
+
*/
|
|
43
|
+
const gridApiOp = <T extends unknown, R extends unknown>(
|
|
44
|
+
hasApiFn: (gridApi: GridApi) => T,
|
|
45
|
+
noApiFn?: () => R,
|
|
46
|
+
): T | R => {
|
|
47
|
+
if (!noApiFn) {
|
|
48
|
+
noApiFn = (() => {}) as () => R;
|
|
49
|
+
}
|
|
50
|
+
const gridApi = gridApiRef.current;
|
|
51
|
+
if (gridApi) return hasApiFn(gridApi);
|
|
52
|
+
console.error("GridApi not ready");
|
|
53
|
+
return noApiFn();
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Set the quick filter value to grid.
|
|
58
|
+
*/
|
|
59
|
+
const setQuickFilter = (quickFilter: string) => {
|
|
60
|
+
gridApiOp((gridApi) => gridApi.setQuickFilter(quickFilter));
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get all row id's in grid.
|
|
65
|
+
*/
|
|
66
|
+
const _getAllRowIds = () => {
|
|
67
|
+
const result: number[] = [];
|
|
68
|
+
return gridApiOp(
|
|
69
|
+
(gridApi) => {
|
|
70
|
+
gridApi.forEachNode((node) => result.push(node.data.id));
|
|
71
|
+
return result;
|
|
72
|
+
},
|
|
73
|
+
() => result,
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Record all row id's before update so that we can select/flash the new rows after update.
|
|
79
|
+
*/
|
|
80
|
+
const beforeUpdate = () => {
|
|
81
|
+
idsBeforeUpdate.current = _getAllRowIds();
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Find new row ids
|
|
86
|
+
* Uses beforeUpdate ids to find new nodes.
|
|
87
|
+
*/
|
|
88
|
+
const _getNewNodes = (): RowNode[] => {
|
|
89
|
+
return gridApiOp(
|
|
90
|
+
(gridApi) =>
|
|
91
|
+
difference(_getAllRowIds(), idsBeforeUpdate.current)
|
|
92
|
+
.map((rowId) => gridApi.getRowNode("" + rowId)) //
|
|
93
|
+
.filter((r) => r) as RowNode[],
|
|
94
|
+
() => [],
|
|
95
|
+
);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get grid nodes via rowIds. If rowIds has no matching node the result may be smaller than expected.
|
|
100
|
+
* This can happen if the grid has not re-rendered yet.
|
|
101
|
+
*
|
|
102
|
+
* @param rowIds Row ids to get from grid.
|
|
103
|
+
*/
|
|
104
|
+
const _rowIdsToNodes = (rowIds: number[]): RowNode[] => {
|
|
105
|
+
return gridApiOp(
|
|
106
|
+
(gridApi) =>
|
|
107
|
+
rowIds
|
|
108
|
+
.map((rowId) => gridApi.getRowNode("" + rowId)) //
|
|
109
|
+
.filter((r) => r) as RowNode[],
|
|
110
|
+
() => [] as RowNode[],
|
|
111
|
+
);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Internal method for selecting and flashing rows.
|
|
116
|
+
*
|
|
117
|
+
* @param rowIds RowIds to select and flash. If undefined a diff is done with beforeUpdate and current state to select
|
|
118
|
+
* @param select Whether to select rows
|
|
119
|
+
* @param flash Whether to flash rows
|
|
120
|
+
* @param retryCount Table updates may not be present when this is called, so retry is needed.
|
|
121
|
+
*/
|
|
122
|
+
const _selectRowsWithOptionalFlash = (
|
|
123
|
+
rowIds: number[] | undefined,
|
|
124
|
+
select: boolean,
|
|
125
|
+
flash: boolean,
|
|
126
|
+
retryCount = 5, // We retry for approximately 5x200ms=1s
|
|
127
|
+
) => {
|
|
128
|
+
return gridApiOp((gridApi) => {
|
|
129
|
+
const rowNodes = rowIds ? _rowIdsToNodes(rowIds) : _getNewNodes();
|
|
130
|
+
const gridRowIdsNotUpdatedYet = rowIds && rowNodes.length !== rowIds.length; // rowIds are specified
|
|
131
|
+
const gridRowIdsNotChangedYet = !rowIds && isEmpty(rowNodes); // rowIds are from beforeUpdate
|
|
132
|
+
const gridHasNotUpdated = gridRowIdsNotUpdatedYet || gridRowIdsNotChangedYet;
|
|
133
|
+
// After retry count expires we give-up and deselect all rows, then select any subset of rows that have updated
|
|
134
|
+
if (gridHasNotUpdated && retryCount > 0) {
|
|
135
|
+
delay(() => _selectRowsWithOptionalFlash(rowIds, select, flash, retryCount - 1), 250);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const rowsThatNeedSelecting = sortBy(
|
|
140
|
+
rowNodes.filter((node) => !node.isSelected()),
|
|
141
|
+
(node) => node.data.id,
|
|
142
|
+
);
|
|
143
|
+
const firstNode = rowsThatNeedSelecting[0];
|
|
144
|
+
firstNode && gridApi.ensureNodeVisible(firstNode);
|
|
145
|
+
if (select) {
|
|
146
|
+
// Select rows that shouldn't be selected
|
|
147
|
+
rowsThatNeedSelecting.forEach((node) => node.setSelected(true));
|
|
148
|
+
// Unselect rows that shouldn't be selected
|
|
149
|
+
gridApi.getSelectedNodes()?.forEach((node) => {
|
|
150
|
+
if (node && !rowNodes.includes(node)) {
|
|
151
|
+
node.setSelected(false);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
if (flash) {
|
|
156
|
+
delay(() => {
|
|
157
|
+
try {
|
|
158
|
+
gridApi.flashCells({ rowNodes });
|
|
159
|
+
} catch {
|
|
160
|
+
// ignore, flash cells sometimes throws errors as nodes have gone out of scope
|
|
161
|
+
}
|
|
162
|
+
}, 250);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const selectRowsById = (rowIds?: number[]) => _selectRowsWithOptionalFlash(rowIds, true, false);
|
|
168
|
+
const selectRowsByIdWithFlash = (rowIds?: number[]) => _selectRowsWithOptionalFlash(rowIds, true, true);
|
|
169
|
+
const flashRows = (rowIds?: number[]) => _selectRowsWithOptionalFlash(rowIds, false, true);
|
|
170
|
+
|
|
171
|
+
const selectRowsDiff = async (fn: () => Promise<any>) => {
|
|
172
|
+
beforeUpdate();
|
|
173
|
+
await fn();
|
|
174
|
+
_selectRowsWithOptionalFlash(undefined, true, false);
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const selectRowsWithFlashDiff = async (fn: () => Promise<any>) => {
|
|
178
|
+
beforeUpdate();
|
|
179
|
+
await fn();
|
|
180
|
+
_selectRowsWithOptionalFlash(undefined, true, true);
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const flashRowsDiff = async (fn: () => Promise<any>) => {
|
|
184
|
+
beforeUpdate();
|
|
185
|
+
await fn();
|
|
186
|
+
_selectRowsWithOptionalFlash(undefined, false, true);
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const getSelectedRows = <T extends unknown>(): T[] => {
|
|
190
|
+
return gridApiOp(
|
|
191
|
+
(gridApi) => gridApi.getSelectedRows(),
|
|
192
|
+
() => [],
|
|
193
|
+
);
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const getSelectedRowIds = (): number[] => getSelectedRows().map((row) => (row as any).id as number);
|
|
197
|
+
|
|
198
|
+
const editingCells = (): boolean => {
|
|
199
|
+
return gridApiOp(
|
|
200
|
+
(gridApi) => isNotEmpty(gridApi.getEditingCells()),
|
|
201
|
+
() => false,
|
|
202
|
+
);
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const ensureRowVisible = (id: number): void => {
|
|
206
|
+
gridApiOp((gridApi) => {
|
|
207
|
+
const node = gridApi.getRowNode(`${id}`);
|
|
208
|
+
node && gridApi.ensureNodeVisible(node);
|
|
209
|
+
});
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Scroll last selected row into view on grid sort change
|
|
214
|
+
*/
|
|
215
|
+
const ensureSelectedRowIsVisible = (): void => {
|
|
216
|
+
gridApiOp((gridApi) => {
|
|
217
|
+
const selectedNodes = gridApi.getSelectedNodes();
|
|
218
|
+
if (isEmpty(selectedNodes)) return;
|
|
219
|
+
gridApi.ensureNodeVisible(last(selectedNodes));
|
|
220
|
+
});
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const stopEditing = (): void => gridApiOp((gridApi) => gridApi.stopEditing());
|
|
224
|
+
|
|
225
|
+
const updatingCells = async (
|
|
226
|
+
props: { selectedRows: GridBaseRow[]; field?: string },
|
|
227
|
+
fnUpdate: (selectedRows: any[]) => Promise<boolean>,
|
|
228
|
+
setSaving?: (saving: boolean) => void,
|
|
229
|
+
): Promise<boolean> => {
|
|
230
|
+
setSaving && setSaving(true);
|
|
231
|
+
|
|
232
|
+
return await gridApiOp(async (gridApi) => {
|
|
233
|
+
const selectedRows = props.selectedRows;
|
|
234
|
+
//TODO do I need to do this for all rows?
|
|
235
|
+
/*if (!props.multiEdit) {
|
|
236
|
+
// You can't use data as it could be an orphaned reference due to updates
|
|
237
|
+
selectedRows = selectedRows.filter((row) => row.id === props.data.id);
|
|
238
|
+
}
|
|
239
|
+
*/
|
|
240
|
+
let ok = false;
|
|
241
|
+
await modifyUpdating(
|
|
242
|
+
props.field ?? "",
|
|
243
|
+
selectedRows.map((data) => data.id),
|
|
244
|
+
async () => {
|
|
245
|
+
// Need to refresh to get spinners to work on all rows
|
|
246
|
+
gridApi.refreshCells({ rowNodes: props.selectedRows as RowNode[], force: true });
|
|
247
|
+
ok = await fnUpdate(selectedRows);
|
|
248
|
+
},
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
// async processes need to refresh their own rows
|
|
252
|
+
gridApi.refreshCells({ rowNodes: selectedRows as RowNode[], force: true });
|
|
253
|
+
|
|
254
|
+
if (ok) {
|
|
255
|
+
const cell = gridApi.getFocusedCell();
|
|
256
|
+
if (cell && gridApi.getFocusedCell() == null) {
|
|
257
|
+
gridApi.setFocusedCell(cell.rowIndex, cell.column);
|
|
258
|
+
}
|
|
259
|
+
// This is needed to trigger postSortRowsHook
|
|
260
|
+
gridApi.refreshClientSideRowModel();
|
|
261
|
+
stopEditing();
|
|
262
|
+
}
|
|
263
|
+
setSaving && setSaving(false);
|
|
264
|
+
return ok;
|
|
265
|
+
});
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
return (
|
|
269
|
+
<GridContext.Provider
|
|
270
|
+
value={{
|
|
271
|
+
gridReady,
|
|
272
|
+
setGridApi,
|
|
273
|
+
setQuickFilter,
|
|
274
|
+
selectRowsById,
|
|
275
|
+
selectRowsDiff,
|
|
276
|
+
selectRowsByIdWithFlash,
|
|
277
|
+
selectRowsWithFlashDiff,
|
|
278
|
+
flashRows,
|
|
279
|
+
flashRowsDiff,
|
|
280
|
+
getSelectedRows,
|
|
281
|
+
getSelectedRowIds,
|
|
282
|
+
editingCells,
|
|
283
|
+
ensureRowVisible,
|
|
284
|
+
ensureSelectedRowIsVisible,
|
|
285
|
+
stopEditing,
|
|
286
|
+
updatingCells,
|
|
287
|
+
}}
|
|
288
|
+
>
|
|
289
|
+
{props.children}
|
|
290
|
+
</GridContext.Provider>
|
|
291
|
+
);
|
|
292
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createContext } from "react";
|
|
2
|
+
|
|
3
|
+
export type UpdatingContextType = {
|
|
4
|
+
checkUpdating: (fields: string | string[], id: number | string) => boolean;
|
|
5
|
+
modifyUpdating: (field: string, ids: (number | string)[], fn: () => void | Promise<void>) => Promise<void>;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const UpdatingContext = createContext<UpdatingContextType>({
|
|
9
|
+
checkUpdating: () => {
|
|
10
|
+
console.error("Missing UpdatingContext");
|
|
11
|
+
return false;
|
|
12
|
+
},
|
|
13
|
+
modifyUpdating: async () => {
|
|
14
|
+
console.error("Missing UpdatingContext");
|
|
15
|
+
},
|
|
16
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { ReactNode, useRef } from "react";
|
|
2
|
+
import { castArray, flatten, remove } from "lodash-es";
|
|
3
|
+
import { UpdatingContext } from "./UpdatingContext";
|
|
4
|
+
|
|
5
|
+
interface UpdatingContextProviderProps {
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type UpdatingContextStatus = Record<string, (number | string)[] | undefined>;
|
|
10
|
+
|
|
11
|
+
type FieldName = string;
|
|
12
|
+
type IdList = (number | string)[];
|
|
13
|
+
type UpdatingBlock = Record<FieldName, IdList[]>;
|
|
14
|
+
|
|
15
|
+
export const UpdatingContextProvider = (props: UpdatingContextProviderProps) => {
|
|
16
|
+
const updatingBlocks = useRef<UpdatingBlock>({});
|
|
17
|
+
const updating = useRef<UpdatingContextStatus>({});
|
|
18
|
+
|
|
19
|
+
const resetUpdating = () => {
|
|
20
|
+
const mergedUpdatingBlocks: UpdatingContextStatus = {};
|
|
21
|
+
for (const key in updatingBlocks.current) {
|
|
22
|
+
mergedUpdatingBlocks[key] = flatten(updatingBlocks.current[key]);
|
|
23
|
+
}
|
|
24
|
+
updating.current = mergedUpdatingBlocks;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const modifyUpdating = async (field: string, ids: (number | string)[], fn: () => void | Promise<void>) => {
|
|
28
|
+
const fieldUpdatingIds = updatingBlocks.current[field] ?? (updatingBlocks.current[field] = []);
|
|
29
|
+
const idRef = [...ids];
|
|
30
|
+
fieldUpdatingIds.push(idRef);
|
|
31
|
+
resetUpdating();
|
|
32
|
+
await fn();
|
|
33
|
+
remove(fieldUpdatingIds, (idList) => idList === idRef);
|
|
34
|
+
resetUpdating();
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const checkUpdating = (fields: string | string[], id: number | string): boolean =>
|
|
38
|
+
castArray(fields).some((f) => updating.current[f]?.includes(id));
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<UpdatingContext.Provider value={{ modifyUpdating, checkUpdating }}>{props.children}</UpdatingContext.Provider>
|
|
42
|
+
);
|
|
43
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { ChangeEventHandler, InputHTMLAttributes, useState } from "react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
import { LuiIcon } from "@linzjs/lui";
|
|
4
|
+
import { v4 as uuidv4 } from "uuid";
|
|
5
|
+
|
|
6
|
+
export const useGenerateOrDefaultId = (idFromProps?: string) => {
|
|
7
|
+
const [id] = useState(idFromProps ? idFromProps : uuidv4());
|
|
8
|
+
return id;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export interface LuiTextAreaInputProps {
|
|
12
|
+
label?: JSX.Element | string;
|
|
13
|
+
mandatory?: boolean;
|
|
14
|
+
inputProps?: InputHTMLAttributes<HTMLTextAreaElement>;
|
|
15
|
+
onChange?: ChangeEventHandler<HTMLTextAreaElement>;
|
|
16
|
+
value: string;
|
|
17
|
+
error?: string | boolean | null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const TextAreaInput = (props: LuiTextAreaInputProps) => {
|
|
21
|
+
const id = useGenerateOrDefaultId(props.inputProps?.id);
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div
|
|
25
|
+
className={clsx(
|
|
26
|
+
"LuiTextAreaInput Grid-popoverContainer",
|
|
27
|
+
props.inputProps?.disabled ? "isDisabled" : "",
|
|
28
|
+
props?.error ? "hasError" : "",
|
|
29
|
+
)}
|
|
30
|
+
>
|
|
31
|
+
<label htmlFor={id}>
|
|
32
|
+
{props.mandatory && <span className="LuiTextAreaInput-mandatory">*</span>}
|
|
33
|
+
{props.label && <span className="LuiTextAreaInput-label">{props.label}</span>}
|
|
34
|
+
<div className="LuiTextAreaInput-wrapper">
|
|
35
|
+
{" "}
|
|
36
|
+
{/* wrapper div used for error styling */}
|
|
37
|
+
<textarea id={id} value={props.value} onChange={props.onChange} rows={5} {...props.inputProps} />
|
|
38
|
+
</div>
|
|
39
|
+
</label>
|
|
40
|
+
|
|
41
|
+
{/* Error message */}
|
|
42
|
+
{props.error && (
|
|
43
|
+
<span className="LuiTextAreaInput-error">
|
|
44
|
+
<LuiIcon alt="error" name="ic_error" className="LuiTextAreaInput-error-icon" size="sm" status="error" />
|
|
45
|
+
{props.error}
|
|
46
|
+
</span>
|
|
47
|
+
)}
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import "./TextInputFormatted.scss";
|
|
2
|
+
|
|
3
|
+
import { ChangeEventHandler, DetailedHTMLProps, InputHTMLAttributes } from "react";
|
|
4
|
+
|
|
5
|
+
import clsx from "clsx";
|
|
6
|
+
import { LuiIcon } from "@linzjs/lui";
|
|
7
|
+
|
|
8
|
+
export interface LuiTextInputProps {
|
|
9
|
+
onChange?: ChangeEventHandler<HTMLInputElement>;
|
|
10
|
+
inputProps?: DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>;
|
|
11
|
+
error?: string | boolean | null;
|
|
12
|
+
warning?: string | boolean | null;
|
|
13
|
+
|
|
14
|
+
className?: string;
|
|
15
|
+
value: string;
|
|
16
|
+
|
|
17
|
+
icon?: JSX.Element;
|
|
18
|
+
placeholder?: string;
|
|
19
|
+
formatted: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const TextInputFormatted = (props: LuiTextInputProps): JSX.Element => {
|
|
23
|
+
return (
|
|
24
|
+
<div
|
|
25
|
+
className={clsx(
|
|
26
|
+
"LuiTextInput Grid-popoverContainer",
|
|
27
|
+
props.error && "hasError",
|
|
28
|
+
props.warning && "hasWarning",
|
|
29
|
+
props.className,
|
|
30
|
+
)}
|
|
31
|
+
>
|
|
32
|
+
<span className="LuiTextInput-inputWrapper">
|
|
33
|
+
<input
|
|
34
|
+
type={"text"}
|
|
35
|
+
className={"LuiTextInput-input"}
|
|
36
|
+
min="0"
|
|
37
|
+
value={props.value}
|
|
38
|
+
onChange={props.onChange}
|
|
39
|
+
{...props.inputProps}
|
|
40
|
+
/>
|
|
41
|
+
<span className={"LuiTextInput-formatted"}>{props.formatted}</span>
|
|
42
|
+
{props.icon}
|
|
43
|
+
</span>
|
|
44
|
+
|
|
45
|
+
{props.error && (
|
|
46
|
+
<span className="LuiTextInput-error">
|
|
47
|
+
<LuiIcon alt="error" name="ic_error" className="LuiTextInput-error-icon" size="sm" status="error" />
|
|
48
|
+
{props.error}
|
|
49
|
+
</span>
|
|
50
|
+
)}
|
|
51
|
+
|
|
52
|
+
{props.warning && (
|
|
53
|
+
<span className="LuiTextInput-warning">
|
|
54
|
+
<LuiIcon alt="warning" name="ic_warning" className="LuiTextInput-warning-icon" size="sm" status="warning" />
|
|
55
|
+
{props.warning}
|
|
56
|
+
</span>
|
|
57
|
+
)}
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
};
|