@etsoo/materialui 1.0.4 → 1.0.7
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/lib/HiSelector.d.ts +62 -0
- package/lib/HiSelector.js +55 -0
- package/lib/SelectEx.d.ts +12 -0
- package/lib/SelectEx.js +22 -7
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/package.json +12 -12
- package/src/HiSelector.tsx +221 -0
- package/src/SelectEx.tsx +51 -3
- package/src/index.ts +1 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { DataTypes, IdDefaultType, LabelDefaultType } from '@etsoo/shared';
|
|
2
|
+
import { SelectChangeEvent } from '@mui/material';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
/**
|
|
5
|
+
* Hierarchy selector props
|
|
6
|
+
*/
|
|
7
|
+
export declare type HiSelectorProps<T extends object, D extends DataTypes.Keys<T> = IdDefaultType<T>, L extends DataTypes.Keys<T, string> = LabelDefaultType<T>> = {
|
|
8
|
+
/**
|
|
9
|
+
* Id field
|
|
10
|
+
*/
|
|
11
|
+
idField?: D;
|
|
12
|
+
/**
|
|
13
|
+
* Error
|
|
14
|
+
*/
|
|
15
|
+
error?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* The helper text content.
|
|
18
|
+
*/
|
|
19
|
+
helperText?: React.ReactNode;
|
|
20
|
+
/**
|
|
21
|
+
* Name, also hidden input field name
|
|
22
|
+
*/
|
|
23
|
+
name: string;
|
|
24
|
+
/**
|
|
25
|
+
* Label
|
|
26
|
+
*/
|
|
27
|
+
label?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Label field
|
|
30
|
+
*/
|
|
31
|
+
labelField?: L;
|
|
32
|
+
/**
|
|
33
|
+
* Load data callback
|
|
34
|
+
*/
|
|
35
|
+
loadData: (parent?: T[D]) => PromiseLike<T[] | null | undefined>;
|
|
36
|
+
/**
|
|
37
|
+
* On value change event
|
|
38
|
+
*/
|
|
39
|
+
onChange?: (value: unknown) => void;
|
|
40
|
+
/**
|
|
41
|
+
* On select change event
|
|
42
|
+
*/
|
|
43
|
+
onSelectChange?: (e: SelectChangeEvent<unknown>) => void;
|
|
44
|
+
/**
|
|
45
|
+
* Item change callback
|
|
46
|
+
*/
|
|
47
|
+
onItemChange?: (option?: T) => void;
|
|
48
|
+
/**
|
|
49
|
+
* Required
|
|
50
|
+
*/
|
|
51
|
+
required?: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Values
|
|
54
|
+
*/
|
|
55
|
+
values?: T[D][];
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Hierarchy selector
|
|
59
|
+
* @param props Prop
|
|
60
|
+
* @returns Component
|
|
61
|
+
*/
|
|
62
|
+
export declare function HiSelector<T extends object, D extends DataTypes.Keys<T> = IdDefaultType<T>, L extends DataTypes.Keys<T, string> = LabelDefaultType<T>>(props: HiSelectorProps<T, D, L>): JSX.Element;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { FormLabel, Grid } from '@mui/material';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { SelectEx } from './SelectEx';
|
|
4
|
+
/**
|
|
5
|
+
* Hierarchy selector
|
|
6
|
+
* @param props Prop
|
|
7
|
+
* @returns Component
|
|
8
|
+
*/
|
|
9
|
+
export function HiSelector(props) {
|
|
10
|
+
// Destruct
|
|
11
|
+
const { idField = 'id', error, helperText, name, label = name, labelField = 'name', loadData, onChange, onSelectChange, onItemChange, required, values = [] } = props;
|
|
12
|
+
const [localValues, setValues] = React.useState(values);
|
|
13
|
+
const updateValue = (value) => {
|
|
14
|
+
if (onChange)
|
|
15
|
+
onChange(value);
|
|
16
|
+
};
|
|
17
|
+
const doChange = (event, index) => {
|
|
18
|
+
const value = event.target.value;
|
|
19
|
+
const itemValue = value === '' ? undefined : value;
|
|
20
|
+
updateValue(itemValue);
|
|
21
|
+
const newValues = [...localValues.slice(0, index)];
|
|
22
|
+
if (itemValue != null)
|
|
23
|
+
newValues.push(itemValue);
|
|
24
|
+
setValues(newValues);
|
|
25
|
+
if (onSelectChange)
|
|
26
|
+
onSelectChange(event);
|
|
27
|
+
};
|
|
28
|
+
const doItemChange = (option, userAction) => {
|
|
29
|
+
if (onItemChange == null)
|
|
30
|
+
return;
|
|
31
|
+
if (!userAction &&
|
|
32
|
+
(option == null || option[idField] !== values.at(-1)))
|
|
33
|
+
return;
|
|
34
|
+
onItemChange(option);
|
|
35
|
+
};
|
|
36
|
+
React.useEffect(() => {
|
|
37
|
+
if (values.length > 0) {
|
|
38
|
+
setValues(values);
|
|
39
|
+
updateValue(values.at(-1));
|
|
40
|
+
}
|
|
41
|
+
}, [values]);
|
|
42
|
+
const currentValue = localValues.at(-1);
|
|
43
|
+
return (React.createElement(React.Fragment, null,
|
|
44
|
+
React.createElement(Grid, { item: true, xs: 12 },
|
|
45
|
+
React.createElement(FormLabel, { required: required, sx: { fontSize: (theme) => theme.typography.caption } }, label),
|
|
46
|
+
React.createElement("input", { type: "hidden", name: name, value: `${currentValue !== null && currentValue !== void 0 ? currentValue : ''}` })),
|
|
47
|
+
React.createElement(Grid, { item: true, xs: 6, md: 4, lg: 3 },
|
|
48
|
+
React.createElement(SelectEx, { idField: idField, labelField: labelField, name: "tab1", search: true, fullWidth: true, loadData: () => loadData(), value: values[0], onChange: (event) => doChange(event, 0), onItemChange: doItemChange, inputRequired: required, error: error, helperText: helperText })),
|
|
49
|
+
localValues[0] != null && (React.createElement(Grid, { item: true, xs: 6, md: 4, lg: 3 },
|
|
50
|
+
React.createElement(SelectEx, { key: `${localValues[0]}`, idField: idField, labelField: labelField, name: "tab2", search: true, fullWidth: true, loadData: () => loadData(localValues[0]), value: values[1], onChange: (event) => doChange(event, 1), onItemChange: doItemChange }))),
|
|
51
|
+
localValues[1] != null && (React.createElement(Grid, { item: true, xs: 6, md: 4, lg: 3 },
|
|
52
|
+
React.createElement(SelectEx, { key: `${localValues[1]}`, idField: idField, labelField: labelField, name: "tab3", search: true, fullWidth: true, loadData: () => loadData(localValues[1]), value: values[2], onChange: (event) => doChange(event, 2), onItemChange: doItemChange }))),
|
|
53
|
+
localValues[2] != null && (React.createElement(Grid, { item: true, xs: 6, md: 4, lg: 3 },
|
|
54
|
+
React.createElement(SelectEx, { key: `${localValues[2]}`, idField: idField, labelField: labelField, name: "tab4", search: true, fullWidth: true, loadData: () => loadData(localValues[2]), value: values[3], onChange: (event) => doChange(event, 3), onItemChange: doItemChange })))));
|
|
55
|
+
}
|
package/lib/SelectEx.d.ts
CHANGED
|
@@ -9,6 +9,14 @@ export declare type SelectExProps<T extends object, D extends DataTypes.Keys<T>
|
|
|
9
9
|
* Auto add blank item
|
|
10
10
|
*/
|
|
11
11
|
autoAddBlankItem?: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* The helper text content.
|
|
14
|
+
*/
|
|
15
|
+
helperText?: React.ReactNode;
|
|
16
|
+
/**
|
|
17
|
+
* Input required
|
|
18
|
+
*/
|
|
19
|
+
inputRequired?: boolean;
|
|
12
20
|
/**
|
|
13
21
|
* Id field
|
|
14
22
|
*/
|
|
@@ -29,6 +37,10 @@ export declare type SelectExProps<T extends object, D extends DataTypes.Keys<T>
|
|
|
29
37
|
* Load data callback
|
|
30
38
|
*/
|
|
31
39
|
loadData?: () => PromiseLike<T[] | null | undefined>;
|
|
40
|
+
/**
|
|
41
|
+
* Item change callback
|
|
42
|
+
*/
|
|
43
|
+
onItemChange?: (option: T | undefined, userAction: boolean) => void;
|
|
32
44
|
/**
|
|
33
45
|
* Item click handler
|
|
34
46
|
*/
|
package/lib/SelectEx.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Checkbox, FormControl, InputLabel, ListItemText, MenuItem, OutlinedInput, Select } from '@mui/material';
|
|
1
|
+
import { Checkbox, FormControl, FormHelperText, InputLabel, ListItemText, MenuItem, OutlinedInput, Select } from '@mui/material';
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { MUGlobal } from './MUGlobal';
|
|
4
4
|
import { ListItemRightIcon } from './ListItemRightIcon';
|
|
@@ -12,16 +12,29 @@ import { ReactUtils } from '@etsoo/react';
|
|
|
12
12
|
export function SelectEx(props) {
|
|
13
13
|
var _a;
|
|
14
14
|
// Destruct
|
|
15
|
-
const { defaultValue, idField = 'id', itemIconRenderer, itemStyle, label, labelField = 'label', loadData, onItemClick, onLoadData, multiple = false, name, options = [], search = false, autoAddBlankItem = search, value, onChange, fullWidth, ...rest } = props;
|
|
15
|
+
const { defaultValue, idField = 'id', error, helperText, inputRequired, itemIconRenderer, itemStyle, label, labelField = 'label', loadData, onItemChange, onItemClick, onLoadData, multiple = false, name, options = [], search = false, autoAddBlankItem = search, value, onChange, fullWidth, ...rest } = props;
|
|
16
16
|
// Options state
|
|
17
17
|
const [localOptions, setOptions] = React.useState(options);
|
|
18
18
|
const isMounted = React.useRef(true);
|
|
19
|
+
const doItemChange = (options, value, userAction) => {
|
|
20
|
+
if (onItemChange == null)
|
|
21
|
+
return;
|
|
22
|
+
if (value == null || value === '')
|
|
23
|
+
onItemChange(undefined, userAction);
|
|
24
|
+
const option = options.find((option) => option[idField] === value);
|
|
25
|
+
onItemChange(option, userAction);
|
|
26
|
+
};
|
|
27
|
+
const setOptionsAdd = (options) => {
|
|
28
|
+
setOptions(options);
|
|
29
|
+
if (valueState != null && valueState !== '')
|
|
30
|
+
doItemChange(options, valueState, false);
|
|
31
|
+
};
|
|
19
32
|
// When options change
|
|
20
33
|
// [options] will cause infinite loop
|
|
21
34
|
const propertyWay = loadData == null;
|
|
22
35
|
React.useEffect(() => {
|
|
23
36
|
if (propertyWay && options != null)
|
|
24
|
-
|
|
37
|
+
setOptionsAdd(options);
|
|
25
38
|
}, [JSON.stringify(options), propertyWay]);
|
|
26
39
|
// Local value
|
|
27
40
|
const valueSource = (_a = defaultValue !== null && defaultValue !== void 0 ? defaultValue : value) !== null && _a !== void 0 ? _a : '';
|
|
@@ -92,7 +105,7 @@ export function SelectEx(props) {
|
|
|
92
105
|
if (autoAddBlankItem) {
|
|
93
106
|
Utils.addBlankItem(result, idField, labelField);
|
|
94
107
|
}
|
|
95
|
-
|
|
108
|
+
setOptionsAdd(result);
|
|
96
109
|
});
|
|
97
110
|
}
|
|
98
111
|
}, [localValue]);
|
|
@@ -112,15 +125,16 @@ export function SelectEx(props) {
|
|
|
112
125
|
};
|
|
113
126
|
}, []);
|
|
114
127
|
// Layout
|
|
115
|
-
return (React.createElement(FormControl, { size: search ? MUGlobal.searchFieldSize : MUGlobal.inputFieldSize, fullWidth: fullWidth },
|
|
128
|
+
return (React.createElement(FormControl, { size: search ? MUGlobal.searchFieldSize : MUGlobal.inputFieldSize, fullWidth: fullWidth, error: error },
|
|
116
129
|
React.createElement(InputLabel, { id: labelId, shrink: search
|
|
117
130
|
? MUGlobal.searchFieldShrink
|
|
118
131
|
: MUGlobal.inputFieldShrink }, label),
|
|
119
132
|
React.createElement(Select, { ref: divRef, value: localOptions.some((option) => itemChecked(getId(option)))
|
|
120
133
|
? valueState !== null && valueState !== void 0 ? valueState : ''
|
|
121
|
-
: '', input: React.createElement(OutlinedInput, { notched: true, label: label }), labelId: labelId, name: name, multiple: multiple, onChange: (event, child) => {
|
|
134
|
+
: '', input: React.createElement(OutlinedInput, { notched: true, label: label, required: inputRequired }), labelId: labelId, name: name, multiple: multiple, onChange: (event, child) => {
|
|
122
135
|
if (onChange)
|
|
123
136
|
onChange(event, child);
|
|
137
|
+
doItemChange(localOptions, event.target.value, true);
|
|
124
138
|
if (multiple)
|
|
125
139
|
handleChange(event);
|
|
126
140
|
}, renderValue: (selected) => {
|
|
@@ -154,5 +168,6 @@ export function SelectEx(props) {
|
|
|
154
168
|
multiple && React.createElement(Checkbox, { checked: itemChecked(id) }),
|
|
155
169
|
React.createElement(ListItemText, { primary: label }),
|
|
156
170
|
itemIconRenderer && (React.createElement(ListItemRightIcon, null, itemIconRenderer(option[idField])))));
|
|
157
|
-
}))
|
|
171
|
+
})),
|
|
172
|
+
helperText != null && (React.createElement(FormHelperText, null, helperText))));
|
|
158
173
|
}
|
package/lib/index.d.ts
CHANGED
package/lib/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@etsoo/materialui",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "TypeScript Material-UI Implementation",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -51,12 +51,12 @@
|
|
|
51
51
|
"@emotion/css": "^11.10.0",
|
|
52
52
|
"@emotion/react": "^11.10.4",
|
|
53
53
|
"@emotion/styled": "^11.10.4",
|
|
54
|
-
"@etsoo/appscript": "^1.2.
|
|
54
|
+
"@etsoo/appscript": "^1.2.89",
|
|
55
55
|
"@etsoo/notificationbase": "^1.1.7",
|
|
56
|
-
"@etsoo/react": "^1.5.
|
|
57
|
-
"@etsoo/shared": "^1.1.
|
|
56
|
+
"@etsoo/react": "^1.5.87",
|
|
57
|
+
"@etsoo/shared": "^1.1.52",
|
|
58
58
|
"@mui/icons-material": "^5.10.3",
|
|
59
|
-
"@mui/material": "^5.10.
|
|
59
|
+
"@mui/material": "^5.10.4",
|
|
60
60
|
"@types/pica": "^9.0.1",
|
|
61
61
|
"@types/pulltorefreshjs": "^0.1.5",
|
|
62
62
|
"@types/react": "^18.0.18",
|
|
@@ -76,19 +76,19 @@
|
|
|
76
76
|
},
|
|
77
77
|
"devDependencies": {
|
|
78
78
|
"@babel/cli": "^7.18.10",
|
|
79
|
-
"@babel/core": "^7.
|
|
79
|
+
"@babel/core": "^7.19.0",
|
|
80
80
|
"@babel/plugin-transform-runtime": "^7.18.10",
|
|
81
|
-
"@babel/preset-env": "^7.
|
|
82
|
-
"@babel/runtime-corejs3": "^7.
|
|
81
|
+
"@babel/preset-env": "^7.19.0",
|
|
82
|
+
"@babel/runtime-corejs3": "^7.19.0",
|
|
83
83
|
"@testing-library/jest-dom": "^5.16.5",
|
|
84
|
-
"@testing-library/react": "^13.
|
|
84
|
+
"@testing-library/react": "^13.4.0",
|
|
85
85
|
"@types/jest": "^29.0.0",
|
|
86
|
-
"@typescript-eslint/eslint-plugin": "^5.36.
|
|
87
|
-
"@typescript-eslint/parser": "^5.36.
|
|
86
|
+
"@typescript-eslint/eslint-plugin": "^5.36.2",
|
|
87
|
+
"@typescript-eslint/parser": "^5.36.2",
|
|
88
88
|
"eslint": "^8.23.0",
|
|
89
89
|
"eslint-config-airbnb-base": "^15.0.0",
|
|
90
90
|
"eslint-plugin-import": "^2.26.0",
|
|
91
|
-
"eslint-plugin-react": "^7.31.
|
|
91
|
+
"eslint-plugin-react": "^7.31.7",
|
|
92
92
|
"jest": "^28.1.3",
|
|
93
93
|
"jest-environment-jsdom": "^28.1.3",
|
|
94
94
|
"ts-jest": "^28.0.8",
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { DataTypes, IdDefaultType, LabelDefaultType } from '@etsoo/shared';
|
|
2
|
+
import { FormLabel, Grid, SelectChangeEvent } from '@mui/material';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { SelectEx } from './SelectEx';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Hierarchy selector props
|
|
8
|
+
*/
|
|
9
|
+
export type HiSelectorProps<
|
|
10
|
+
T extends object,
|
|
11
|
+
D extends DataTypes.Keys<T> = IdDefaultType<T>,
|
|
12
|
+
L extends DataTypes.Keys<T, string> = LabelDefaultType<T>
|
|
13
|
+
> = {
|
|
14
|
+
/**
|
|
15
|
+
* Id field
|
|
16
|
+
*/
|
|
17
|
+
idField?: D;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Error
|
|
21
|
+
*/
|
|
22
|
+
error?: boolean;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The helper text content.
|
|
26
|
+
*/
|
|
27
|
+
helperText?: React.ReactNode;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Name, also hidden input field name
|
|
31
|
+
*/
|
|
32
|
+
name: string;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Label
|
|
36
|
+
*/
|
|
37
|
+
label?: string;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Label field
|
|
41
|
+
*/
|
|
42
|
+
labelField?: L;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Load data callback
|
|
46
|
+
*/
|
|
47
|
+
loadData: (parent?: T[D]) => PromiseLike<T[] | null | undefined>;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* On value change event
|
|
51
|
+
*/
|
|
52
|
+
onChange?: (value: unknown) => void;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* On select change event
|
|
56
|
+
*/
|
|
57
|
+
onSelectChange?: (e: SelectChangeEvent<unknown>) => void;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Item change callback
|
|
61
|
+
*/
|
|
62
|
+
onItemChange?: (option?: T) => void;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Required
|
|
66
|
+
*/
|
|
67
|
+
required?: boolean;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Values
|
|
71
|
+
*/
|
|
72
|
+
values?: T[D][];
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Hierarchy selector
|
|
77
|
+
* @param props Prop
|
|
78
|
+
* @returns Component
|
|
79
|
+
*/
|
|
80
|
+
export function HiSelector<
|
|
81
|
+
T extends object,
|
|
82
|
+
D extends DataTypes.Keys<T> = IdDefaultType<T>,
|
|
83
|
+
L extends DataTypes.Keys<T, string> = LabelDefaultType<T>
|
|
84
|
+
>(props: HiSelectorProps<T, D, L>) {
|
|
85
|
+
// Destruct
|
|
86
|
+
const {
|
|
87
|
+
idField = 'id' as D,
|
|
88
|
+
error,
|
|
89
|
+
helperText,
|
|
90
|
+
name,
|
|
91
|
+
label = name,
|
|
92
|
+
labelField = 'name' as L,
|
|
93
|
+
loadData,
|
|
94
|
+
onChange,
|
|
95
|
+
onSelectChange,
|
|
96
|
+
onItemChange,
|
|
97
|
+
required,
|
|
98
|
+
values = []
|
|
99
|
+
} = props;
|
|
100
|
+
|
|
101
|
+
// Value type
|
|
102
|
+
type ValueType = T[D];
|
|
103
|
+
const [localValues, setValues] = React.useState<ValueType[]>(values);
|
|
104
|
+
|
|
105
|
+
const updateValue = (value?: T[D]) => {
|
|
106
|
+
if (onChange) onChange(value);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const doChange = (event: SelectChangeEvent<unknown>, index: number) => {
|
|
110
|
+
const value = event.target.value;
|
|
111
|
+
const itemValue = value === '' ? undefined : (value as T[D]);
|
|
112
|
+
updateValue(itemValue);
|
|
113
|
+
|
|
114
|
+
const newValues = [...localValues.slice(0, index)];
|
|
115
|
+
if (itemValue != null) newValues.push(itemValue);
|
|
116
|
+
setValues(newValues);
|
|
117
|
+
|
|
118
|
+
if (onSelectChange) onSelectChange(event);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const doItemChange = (option: T | undefined, userAction: boolean) => {
|
|
122
|
+
if (onItemChange == null) return;
|
|
123
|
+
if (
|
|
124
|
+
!userAction &&
|
|
125
|
+
(option == null || option[idField] !== values.at(-1))
|
|
126
|
+
)
|
|
127
|
+
return;
|
|
128
|
+
onItemChange(option);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
React.useEffect(() => {
|
|
132
|
+
if (values.length > 0) {
|
|
133
|
+
setValues(values);
|
|
134
|
+
updateValue(values.at(-1));
|
|
135
|
+
}
|
|
136
|
+
}, [values]);
|
|
137
|
+
|
|
138
|
+
const currentValue = localValues.at(-1);
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<React.Fragment>
|
|
142
|
+
<Grid item xs={12}>
|
|
143
|
+
<FormLabel
|
|
144
|
+
required={required}
|
|
145
|
+
sx={{ fontSize: (theme) => theme.typography.caption }}
|
|
146
|
+
>
|
|
147
|
+
{label}
|
|
148
|
+
</FormLabel>
|
|
149
|
+
<input
|
|
150
|
+
type="hidden"
|
|
151
|
+
name={name}
|
|
152
|
+
value={`${currentValue ?? ''}`}
|
|
153
|
+
/>
|
|
154
|
+
</Grid>
|
|
155
|
+
<Grid item xs={6} md={4} lg={3}>
|
|
156
|
+
<SelectEx<T, D, L>
|
|
157
|
+
idField={idField}
|
|
158
|
+
labelField={labelField}
|
|
159
|
+
name="tab1"
|
|
160
|
+
search
|
|
161
|
+
fullWidth
|
|
162
|
+
loadData={() => loadData()}
|
|
163
|
+
value={values[0]}
|
|
164
|
+
onChange={(event) => doChange(event, 0)}
|
|
165
|
+
onItemChange={doItemChange}
|
|
166
|
+
inputRequired={required}
|
|
167
|
+
error={error}
|
|
168
|
+
helperText={helperText}
|
|
169
|
+
/>
|
|
170
|
+
</Grid>
|
|
171
|
+
{localValues[0] != null && (
|
|
172
|
+
<Grid item xs={6} md={4} lg={3}>
|
|
173
|
+
<SelectEx<T, D, L>
|
|
174
|
+
key={`${localValues[0]}`}
|
|
175
|
+
idField={idField}
|
|
176
|
+
labelField={labelField}
|
|
177
|
+
name="tab2"
|
|
178
|
+
search
|
|
179
|
+
fullWidth
|
|
180
|
+
loadData={() => loadData(localValues[0])}
|
|
181
|
+
value={values[1]}
|
|
182
|
+
onChange={(event) => doChange(event, 1)}
|
|
183
|
+
onItemChange={doItemChange}
|
|
184
|
+
/>
|
|
185
|
+
</Grid>
|
|
186
|
+
)}
|
|
187
|
+
{localValues[1] != null && (
|
|
188
|
+
<Grid item xs={6} md={4} lg={3}>
|
|
189
|
+
<SelectEx<T, D, L>
|
|
190
|
+
key={`${localValues[1]}`}
|
|
191
|
+
idField={idField}
|
|
192
|
+
labelField={labelField}
|
|
193
|
+
name="tab3"
|
|
194
|
+
search
|
|
195
|
+
fullWidth
|
|
196
|
+
loadData={() => loadData(localValues[1])}
|
|
197
|
+
value={values[2]}
|
|
198
|
+
onChange={(event) => doChange(event, 2)}
|
|
199
|
+
onItemChange={doItemChange}
|
|
200
|
+
/>
|
|
201
|
+
</Grid>
|
|
202
|
+
)}
|
|
203
|
+
{localValues[2] != null && (
|
|
204
|
+
<Grid item xs={6} md={4} lg={3}>
|
|
205
|
+
<SelectEx<T, D, L>
|
|
206
|
+
key={`${localValues[2]}`}
|
|
207
|
+
idField={idField}
|
|
208
|
+
labelField={labelField}
|
|
209
|
+
name="tab4"
|
|
210
|
+
search
|
|
211
|
+
fullWidth
|
|
212
|
+
loadData={() => loadData(localValues[2])}
|
|
213
|
+
value={values[3]}
|
|
214
|
+
onChange={(event) => doChange(event, 3)}
|
|
215
|
+
onItemChange={doItemChange}
|
|
216
|
+
/>
|
|
217
|
+
</Grid>
|
|
218
|
+
)}
|
|
219
|
+
</React.Fragment>
|
|
220
|
+
);
|
|
221
|
+
}
|
package/src/SelectEx.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Checkbox,
|
|
3
3
|
FormControl,
|
|
4
|
+
FormHelperText,
|
|
4
5
|
InputLabel,
|
|
5
6
|
ListItemText,
|
|
6
7
|
MenuItem,
|
|
@@ -34,6 +35,16 @@ export type SelectExProps<
|
|
|
34
35
|
*/
|
|
35
36
|
autoAddBlankItem?: boolean;
|
|
36
37
|
|
|
38
|
+
/**
|
|
39
|
+
* The helper text content.
|
|
40
|
+
*/
|
|
41
|
+
helperText?: React.ReactNode;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Input required
|
|
45
|
+
*/
|
|
46
|
+
inputRequired?: boolean;
|
|
47
|
+
|
|
37
48
|
/**
|
|
38
49
|
* Id field
|
|
39
50
|
*/
|
|
@@ -59,6 +70,11 @@ export type SelectExProps<
|
|
|
59
70
|
*/
|
|
60
71
|
loadData?: () => PromiseLike<T[] | null | undefined>;
|
|
61
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Item change callback
|
|
75
|
+
*/
|
|
76
|
+
onItemChange?: (option: T | undefined, userAction: boolean) => void;
|
|
77
|
+
|
|
62
78
|
/**
|
|
63
79
|
* Item click handler
|
|
64
80
|
*/
|
|
@@ -94,11 +110,15 @@ export function SelectEx<
|
|
|
94
110
|
const {
|
|
95
111
|
defaultValue,
|
|
96
112
|
idField = 'id' as D,
|
|
113
|
+
error,
|
|
114
|
+
helperText,
|
|
115
|
+
inputRequired,
|
|
97
116
|
itemIconRenderer,
|
|
98
117
|
itemStyle,
|
|
99
118
|
label,
|
|
100
119
|
labelField = 'label' as L,
|
|
101
120
|
loadData,
|
|
121
|
+
onItemChange,
|
|
102
122
|
onItemClick,
|
|
103
123
|
onLoadData,
|
|
104
124
|
multiple = false,
|
|
@@ -116,11 +136,28 @@ export function SelectEx<
|
|
|
116
136
|
const [localOptions, setOptions] = React.useState(options);
|
|
117
137
|
const isMounted = React.useRef(true);
|
|
118
138
|
|
|
139
|
+
const doItemChange = (
|
|
140
|
+
options: readonly T[],
|
|
141
|
+
value: unknown,
|
|
142
|
+
userAction: boolean
|
|
143
|
+
) => {
|
|
144
|
+
if (onItemChange == null) return;
|
|
145
|
+
if (value == null || value === '') onItemChange(undefined, userAction);
|
|
146
|
+
const option = options.find((option) => option[idField] === value);
|
|
147
|
+
onItemChange(option, userAction);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const setOptionsAdd = (options: readonly T[]) => {
|
|
151
|
+
setOptions(options);
|
|
152
|
+
if (valueState != null && valueState !== '')
|
|
153
|
+
doItemChange(options, valueState, false);
|
|
154
|
+
};
|
|
155
|
+
|
|
119
156
|
// When options change
|
|
120
157
|
// [options] will cause infinite loop
|
|
121
158
|
const propertyWay = loadData == null;
|
|
122
159
|
React.useEffect(() => {
|
|
123
|
-
if (propertyWay && options != null)
|
|
160
|
+
if (propertyWay && options != null) setOptionsAdd(options);
|
|
124
161
|
}, [JSON.stringify(options), propertyWay]);
|
|
125
162
|
|
|
126
163
|
// Local value
|
|
@@ -193,7 +230,7 @@ export function SelectEx<
|
|
|
193
230
|
if (autoAddBlankItem) {
|
|
194
231
|
Utils.addBlankItem(result, idField, labelField);
|
|
195
232
|
}
|
|
196
|
-
|
|
233
|
+
setOptionsAdd(result);
|
|
197
234
|
});
|
|
198
235
|
}
|
|
199
236
|
}, [localValue]);
|
|
@@ -218,6 +255,7 @@ export function SelectEx<
|
|
|
218
255
|
<FormControl
|
|
219
256
|
size={search ? MUGlobal.searchFieldSize : MUGlobal.inputFieldSize}
|
|
220
257
|
fullWidth={fullWidth}
|
|
258
|
+
error={error}
|
|
221
259
|
>
|
|
222
260
|
<InputLabel
|
|
223
261
|
id={labelId}
|
|
@@ -236,12 +274,19 @@ export function SelectEx<
|
|
|
236
274
|
? valueState ?? ''
|
|
237
275
|
: ''
|
|
238
276
|
}
|
|
239
|
-
input={
|
|
277
|
+
input={
|
|
278
|
+
<OutlinedInput
|
|
279
|
+
notched
|
|
280
|
+
label={label}
|
|
281
|
+
required={inputRequired}
|
|
282
|
+
/>
|
|
283
|
+
}
|
|
240
284
|
labelId={labelId}
|
|
241
285
|
name={name}
|
|
242
286
|
multiple={multiple}
|
|
243
287
|
onChange={(event, child) => {
|
|
244
288
|
if (onChange) onChange(event, child);
|
|
289
|
+
doItemChange(localOptions, event.target.value, true);
|
|
245
290
|
if (multiple) handleChange(event);
|
|
246
291
|
}}
|
|
247
292
|
renderValue={(selected) => {
|
|
@@ -296,6 +341,9 @@ export function SelectEx<
|
|
|
296
341
|
);
|
|
297
342
|
})}
|
|
298
343
|
</Select>
|
|
344
|
+
{helperText != null && (
|
|
345
|
+
<FormHelperText>{helperText}</FormHelperText>
|
|
346
|
+
)}
|
|
299
347
|
</FormControl>
|
|
300
348
|
);
|
|
301
349
|
}
|
package/src/index.ts
CHANGED