@linzjs/step-ag-grid 7.6.1 → 7.7.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/GridTheme.scss +4 -4
- package/dist/index.js +43 -14
- package/dist/index.js.map +1 -1
- package/dist/src/utils/textValidator.d.ts +7 -0
- package/dist/src/utils/textValidator.test.d.ts +1 -0
- package/dist/step-ag-grid.esm.js +43 -14
- package/dist/step-ag-grid.esm.js.map +1 -1
- package/package.json +7 -3
- package/src/components/GridCell.tsx +1 -1
- package/src/contexts/GridContextProvider.tsx +4 -1
- package/src/contexts/GridPopoverContextProvider.tsx +1 -3
- package/src/styles/GridTheme.scss +4 -4
- package/src/utils/deferredPromise.ts +8 -1
- package/src/utils/textValidator.test.ts +95 -0
- package/src/utils/textValidator.ts +37 -7
- package/src/utils/util.ts +1 -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": "7.
|
|
5
|
+
"version": "7.7.0",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"aggrid",
|
|
8
8
|
"ag-grid",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"react-dom": ">=17"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@linzjs/lui": "
|
|
37
|
+
"@linzjs/lui": ">=17",
|
|
38
38
|
"ag-grid-community": ">=27",
|
|
39
39
|
"ag-grid-react": ">=27",
|
|
40
40
|
"debounce-promise": "^3.1.2",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"bundle": "rollup -c",
|
|
53
53
|
"stylelint": "stylelint src/**/*.scss src/**/*.css --fix",
|
|
54
54
|
"css": "sass ./src/styles/index.scss:dist/index.css --no-source-map",
|
|
55
|
-
"test": "
|
|
55
|
+
"test": "jest",
|
|
56
56
|
"eject": "react-scripts eject",
|
|
57
57
|
"lint": "eslint ./src --ext .js,.ts,.tsx --fix --cache --ignore-path .gitignore",
|
|
58
58
|
"storybook": "start-storybook -p 6006",
|
|
@@ -120,12 +120,16 @@
|
|
|
120
120
|
"eslint-plugin-react": "^7.31.10",
|
|
121
121
|
"eslint-plugin-react-hooks": "^4.6.0",
|
|
122
122
|
"eslint-plugin-testing-library": "^5.9.1",
|
|
123
|
+
"jest": "^29.3.1",
|
|
124
|
+
"jest-environment-jsdom": "^29.3.1",
|
|
125
|
+
"jest-expect-message": "^1.1.3",
|
|
123
126
|
"mkdirp": "^1.0.4",
|
|
124
127
|
"npm-run-all": "^4.1.5",
|
|
125
128
|
"postcss": "^8.4.18",
|
|
126
129
|
"postcss-loader": "^7.0.1",
|
|
127
130
|
"postcss-scss": "^4.0.5",
|
|
128
131
|
"prettier": "^2.7.1",
|
|
132
|
+
"react-app-polyfill": "^3.0.0",
|
|
129
133
|
"react-scripts": "^5.0.1",
|
|
130
134
|
"rollup": "^3.2.5",
|
|
131
135
|
"rollup-plugin-copy": "^3.4.0",
|
|
@@ -68,7 +68,7 @@ export const GridCell = <RowType extends GridBaseRow, Props extends CellEditorCo
|
|
|
68
68
|
sortable: !!(props?.field || props?.valueGetter),
|
|
69
69
|
resizable: true,
|
|
70
70
|
...(custom?.editor && {
|
|
71
|
-
cellClassRules: GridCellMultiSelectClassRules,
|
|
71
|
+
cellClassRules: custom.multiEdit ? GridCellMultiSelectClassRules : undefined,
|
|
72
72
|
editable: props.editable ?? true,
|
|
73
73
|
cellEditor: GenericCellEditorComponentWrapper(custom),
|
|
74
74
|
}),
|
|
@@ -251,7 +251,10 @@ export const GridContextProvider = (props: GridContextProps): ReactElement => {
|
|
|
251
251
|
async () => {
|
|
252
252
|
// Need to refresh to get spinners to work on all rows
|
|
253
253
|
gridApi.refreshCells({ rowNodes: props.selectedRows as RowNode[], force: true });
|
|
254
|
-
ok = await fnUpdate(selectedRows)
|
|
254
|
+
ok = await fnUpdate(selectedRows).catch((ex) => {
|
|
255
|
+
console.error("Exception during modifyUpdating", ex);
|
|
256
|
+
return false;
|
|
257
|
+
});
|
|
255
258
|
},
|
|
256
259
|
);
|
|
257
260
|
|
|
@@ -31,9 +31,7 @@ export const GridPopoverContextProvider = ({ props, children }: GridPopoverConte
|
|
|
31
31
|
async (saveFn: (selectedRows: any[]) => Promise<boolean>, tabDirection: 1 | 0 | -1): Promise<boolean> => {
|
|
32
32
|
if (hasSaved.current) return true;
|
|
33
33
|
hasSaved.current = true;
|
|
34
|
-
|
|
35
|
-
//if (r) stopEditing();
|
|
36
|
-
return r;
|
|
34
|
+
return saving ? false : await updatingCells({ selectedRows, field }, saveFn, setSaving, tabDirection);
|
|
37
35
|
},
|
|
38
36
|
[field, saving, selectedRows, updatingCells],
|
|
39
37
|
);
|
|
@@ -75,8 +75,6 @@
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
.ag-cell.ag-cell-inline-editing {
|
|
78
|
-
padding-left: 9px;
|
|
79
|
-
padding-right: 9px;
|
|
80
78
|
bottom: 0;
|
|
81
79
|
}
|
|
82
80
|
|
|
@@ -84,9 +82,11 @@
|
|
|
84
82
|
word-break: break-word;
|
|
85
83
|
}
|
|
86
84
|
|
|
87
|
-
.ag-cell-popup-editing,
|
|
88
|
-
.ag-selected-for-edit,
|
|
85
|
+
.ag-cell.ag-cell-popup-editing,
|
|
86
|
+
.ag-cell.ag-selected-for-edit,
|
|
89
87
|
.ag-cell-inline-editing {
|
|
88
|
+
padding-left: 9px;
|
|
89
|
+
padding-right: 9px;
|
|
90
90
|
background: rgb(72 160 244 / 20%);
|
|
91
91
|
// These are important, it needs to override ag-grid to work
|
|
92
92
|
border: 3px solid #48a0f4 !important;
|
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
import { useRef } from "react";
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
2
|
|
|
3
3
|
export const useDeferredPromise = <T>() => {
|
|
4
4
|
const promiseResolve = useRef<((value: T | PromiseLike<T>) => void) | undefined>();
|
|
5
5
|
const promiseReject = useRef<() => void>();
|
|
6
6
|
|
|
7
|
+
// End promise on unload
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
return () => {
|
|
10
|
+
promiseReject.current && promiseReject.current();
|
|
11
|
+
};
|
|
12
|
+
}, []);
|
|
13
|
+
|
|
7
14
|
return {
|
|
8
15
|
invoke: () =>
|
|
9
16
|
new Promise<T>((resolve, reject) => {
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { TextInputValidator, TextInputValidatorProps } from "./textValidator";
|
|
2
|
+
import { GridBaseRow } from "../components/Grid";
|
|
3
|
+
|
|
4
|
+
describe("TextInputValidator", () => {
|
|
5
|
+
test("number format", () => {
|
|
6
|
+
const validate = [
|
|
7
|
+
{
|
|
8
|
+
numberFormat: {},
|
|
9
|
+
tests: [
|
|
10
|
+
["x", "Must be a valid number"],
|
|
11
|
+
["1.2e6", "Must be a valid number"],
|
|
12
|
+
["", null],
|
|
13
|
+
["1", null],
|
|
14
|
+
["1.2", null],
|
|
15
|
+
[".2", null],
|
|
16
|
+
["-1.2", null],
|
|
17
|
+
["-.2", null],
|
|
18
|
+
["-.2", null],
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
numberFormat: { geMin: 0, leMax: 10 },
|
|
23
|
+
tests: [
|
|
24
|
+
["x", "Must be a valid number"],
|
|
25
|
+
["", null],
|
|
26
|
+
["1", null],
|
|
27
|
+
["0", null],
|
|
28
|
+
["-1", "Must not be less than 0"],
|
|
29
|
+
["10", null],
|
|
30
|
+
["10.1", "Must not be greater than 10"],
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
numberFormat: { gtMin: 0, ltMax: 10 },
|
|
35
|
+
tests: [
|
|
36
|
+
["x", "Must be a valid number"],
|
|
37
|
+
["", null],
|
|
38
|
+
["1", null],
|
|
39
|
+
["0", "Must be greater than 0"],
|
|
40
|
+
["10", "Must be less than 10"],
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
numberFormat: { precision: 2 },
|
|
45
|
+
tests: [
|
|
46
|
+
["x", "Must be a valid number"],
|
|
47
|
+
["", null],
|
|
48
|
+
["1.22", null],
|
|
49
|
+
["1.123", "Must have no more than 2 decimal places"],
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
numberFormat: { precision: 0 },
|
|
54
|
+
tests: [
|
|
55
|
+
["1", null],
|
|
56
|
+
["1.1", "Must be a whole number"],
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
required: true,
|
|
61
|
+
tests: [
|
|
62
|
+
["xx", null],
|
|
63
|
+
["", "Must not be empty"],
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
] as (TextInputValidatorProps<GridBaseRow> & { tests: [string, string | undefined][] })[];
|
|
67
|
+
|
|
68
|
+
validate.forEach((v, i) => {
|
|
69
|
+
for (const test of v.tests) {
|
|
70
|
+
expect(
|
|
71
|
+
TextInputValidator(v, test[0], { id: 0 }, {}),
|
|
72
|
+
`Test ${i}: "${test[0]}" should return "${test[1]}"`,
|
|
73
|
+
).toBe(test[1]);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("validator is called", () => {
|
|
79
|
+
const fn = jest.fn();
|
|
80
|
+
TextInputValidator({ invalid: fn }, "", { id: 0 }, {});
|
|
81
|
+
expect(fn).toHaveBeenCalled();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("maxLength", () => {
|
|
85
|
+
expect(TextInputValidator({ maxLength: 2 }, "", { id: 0 }, {})).toBeNull();
|
|
86
|
+
expect(TextInputValidator({ maxLength: 2 }, "aa", { id: 0 }, {})).toBeNull();
|
|
87
|
+
expect(TextInputValidator({ maxLength: 2 }, "aaa", { id: 0 }, {})).toBe("Must be no longer than 2 characters");
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("maxBytes", () => {
|
|
91
|
+
expect(TextInputValidator({ maxBytes: 2 }, "", { id: 0 }, {})).toBeNull();
|
|
92
|
+
expect(TextInputValidator({ maxBytes: 2 }, "aa", { id: 0 }, {})).toBeNull();
|
|
93
|
+
expect(TextInputValidator({ maxBytes: 2 }, "a–", { id: 0 }, {})).toBe("Must be no longer than 2 bytes");
|
|
94
|
+
});
|
|
95
|
+
});
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import { GridBaseRow } from "../components/Grid";
|
|
2
|
-
import { stringByteLengthIsInvalid } from "./util";
|
|
2
|
+
import { isFloat, stringByteLengthIsInvalid } from "./util";
|
|
3
3
|
|
|
4
4
|
export interface TextInputValidatorProps<RowType extends GridBaseRow> {
|
|
5
5
|
required?: boolean;
|
|
6
6
|
maxLength?: number;
|
|
7
7
|
maxBytes?: number;
|
|
8
8
|
invalid?: (value: string, data: RowType, context: any) => JSX.Element | string | null;
|
|
9
|
+
numberFormat?: {
|
|
10
|
+
precision?: number;
|
|
11
|
+
gtMin?: number;
|
|
12
|
+
geMin?: number;
|
|
13
|
+
ltMax?: number;
|
|
14
|
+
leMax?: number;
|
|
15
|
+
};
|
|
9
16
|
}
|
|
10
17
|
|
|
11
18
|
export const TextInputValidator = <RowType extends GridBaseRow>(
|
|
@@ -18,17 +25,40 @@ export const TextInputValidator = <RowType extends GridBaseRow>(
|
|
|
18
25
|
|
|
19
26
|
// This can happen because subcomponent is invoked without type safety
|
|
20
27
|
if (typeof value !== "string") {
|
|
21
|
-
|
|
22
|
-
return null;
|
|
28
|
+
return "Value is not a string";
|
|
23
29
|
}
|
|
24
|
-
if (props.required && value
|
|
25
|
-
return
|
|
30
|
+
if (props.required && value == "") {
|
|
31
|
+
return "Must not be empty";
|
|
26
32
|
}
|
|
27
33
|
if (props.maxLength && value.length > props.maxLength) {
|
|
28
|
-
return `
|
|
34
|
+
return `Must be no longer than ${props.maxLength} characters`;
|
|
29
35
|
}
|
|
30
36
|
if (props.maxBytes && stringByteLengthIsInvalid(value, props.maxBytes)) {
|
|
31
|
-
return `
|
|
37
|
+
return `Must be no longer than ${props.maxBytes} bytes`;
|
|
38
|
+
}
|
|
39
|
+
const nf = props.numberFormat;
|
|
40
|
+
if (nf) {
|
|
41
|
+
if (value != "" && !isFloat(value)) {
|
|
42
|
+
return `Must be a valid number`;
|
|
43
|
+
}
|
|
44
|
+
const number = parseFloat(value);
|
|
45
|
+
if (nf.gtMin != null && number <= nf.gtMin) {
|
|
46
|
+
return `Must be greater than ${nf.gtMin}`;
|
|
47
|
+
}
|
|
48
|
+
if (nf.geMin != null && number < nf.geMin) {
|
|
49
|
+
return `Must not be less than ${nf.geMin}`;
|
|
50
|
+
}
|
|
51
|
+
if (nf.ltMax != null && number >= nf.ltMax) {
|
|
52
|
+
return `Must be less than ${nf.ltMax}`;
|
|
53
|
+
}
|
|
54
|
+
if (nf.leMax != null && number > nf.leMax) {
|
|
55
|
+
return `Must not be greater than ${nf.leMax}`;
|
|
56
|
+
}
|
|
57
|
+
if (nf.precision != null && value != "") {
|
|
58
|
+
if (parseFloat(number.toPrecision(nf.precision + 1)) != number) {
|
|
59
|
+
return nf.precision == 0 ? `Must be a whole number` : `Must have no more than ${nf.precision} decimal places`;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
32
62
|
}
|
|
33
63
|
if (props.invalid) {
|
|
34
64
|
return props.invalid(value, data, context);
|