@etsoo/materialui 1.0.56 → 1.0.58
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/__tests__/SelectEx.tsx +54 -0
- package/lib/AutocompleteExtendedProps.d.ts +1 -1
- package/lib/SelectEx.js +44 -46
- package/package.json +2 -2
- package/src/AutocompleteExtendedProps.ts +1 -1
- package/src/SelectEx.tsx +52 -46
package/__tests__/SelectEx.tsx
CHANGED
|
@@ -51,3 +51,57 @@ it('Render SelectEx', async () => {
|
|
|
51
51
|
|
|
52
52
|
expect(itemChangeCallback).toBeCalledTimes(2);
|
|
53
53
|
});
|
|
54
|
+
|
|
55
|
+
it('Render multiple SelectEx', async () => {
|
|
56
|
+
// Arrange
|
|
57
|
+
type T = { id: number; name: string };
|
|
58
|
+
const options: T[] = [
|
|
59
|
+
{ id: 1, name: 'Name 1' },
|
|
60
|
+
{ id: 2, name: 'Name 2' },
|
|
61
|
+
{ id: 3, name: 'Name 3' }
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
const itemChangeCallback = jest.fn((option, userAction) => {
|
|
65
|
+
if (userAction) expect(option.id).toBe(3);
|
|
66
|
+
else expect(option.id).toBe(1);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Render component
|
|
70
|
+
const { baseElement } = render(
|
|
71
|
+
<SelectEx<T>
|
|
72
|
+
options={options}
|
|
73
|
+
name="test"
|
|
74
|
+
onItemChange={itemChangeCallback}
|
|
75
|
+
value={[1, 2]}
|
|
76
|
+
multiple
|
|
77
|
+
search
|
|
78
|
+
labelField="name"
|
|
79
|
+
/>
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
expect(itemChangeCallback).toBeCalled();
|
|
83
|
+
|
|
84
|
+
// Act, click to show the list
|
|
85
|
+
const button = screen.getByRole('button');
|
|
86
|
+
fireEvent.mouseDown(button); // Not click
|
|
87
|
+
|
|
88
|
+
// Get list item
|
|
89
|
+
const itemName1 = await findByText(baseElement, 'Name 1');
|
|
90
|
+
const checkbox1 = itemName1.closest('li')?.querySelector('input');
|
|
91
|
+
|
|
92
|
+
expect(checkbox1?.checked).toBeTruthy();
|
|
93
|
+
|
|
94
|
+
const itemName3 = await findByText(baseElement, 'Name 3');
|
|
95
|
+
expect(itemName3.nodeName).toBe('SPAN');
|
|
96
|
+
|
|
97
|
+
// Checkbox
|
|
98
|
+
const checkbox3 = itemName3.closest('li')?.querySelector('input');
|
|
99
|
+
|
|
100
|
+
act(() => {
|
|
101
|
+
checkbox3?.click();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
expect(checkbox3?.checked).toBeTruthy();
|
|
105
|
+
|
|
106
|
+
expect(itemChangeCallback).toBeCalledTimes(2);
|
|
107
|
+
});
|
|
@@ -4,7 +4,7 @@ import { ChangeEventHandler } from 'react';
|
|
|
4
4
|
/**
|
|
5
5
|
* Autocomplete extended props
|
|
6
6
|
*/
|
|
7
|
-
export declare type AutocompleteExtendedProps<T extends object, D extends DataTypes.Keys<T>> = Omit<AutocompleteProps<T, undefined, false, false>, 'renderInput' | 'options'> & {
|
|
7
|
+
export declare type AutocompleteExtendedProps<T extends object, D extends DataTypes.Keys<T>> = Omit<AutocompleteProps<T, undefined, false, false>, 'renderInput' | 'options' | 'multiple'> & {
|
|
8
8
|
/**
|
|
9
9
|
* Id field
|
|
10
10
|
*/
|
package/lib/SelectEx.js
CHANGED
|
@@ -11,26 +11,30 @@ import { ReactUtils } from '@etsoo/react';
|
|
|
11
11
|
* @returns Component
|
|
12
12
|
*/
|
|
13
13
|
export function SelectEx(props) {
|
|
14
|
-
var _a;
|
|
15
14
|
// Destruct
|
|
16
15
|
const { defaultValue, idField = 'id', error, helperText, inputRequired, itemIconRenderer, itemStyle, label, labelField = 'label', loadData, onItemChange, onItemClick, onLoadData, multiple = false, name, options, refresh, search = false, autoAddBlankItem = search, value, onChange, fullWidth, ...rest } = props;
|
|
17
16
|
// Options state
|
|
18
17
|
const [localOptions, setOptions] = React.useState([]);
|
|
19
|
-
const isMounted = React.useRef(
|
|
18
|
+
const isMounted = React.useRef(false);
|
|
20
19
|
const doItemChange = (options, value, userAction) => {
|
|
21
20
|
if (onItemChange == null)
|
|
22
21
|
return;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
let option;
|
|
23
|
+
if (multiple && Array.isArray(value)) {
|
|
24
|
+
option = options.find((option) => value.includes(option[idField]));
|
|
25
|
+
}
|
|
26
|
+
else if (value == null || value === '') {
|
|
27
|
+
option = undefined;
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
option = options.find((option) => option[idField] === value);
|
|
26
31
|
}
|
|
27
|
-
const option = options.find((option) => option[idField] === value);
|
|
28
32
|
onItemChange(option, userAction);
|
|
29
33
|
};
|
|
30
34
|
const setOptionsAdd = (options) => {
|
|
31
35
|
setOptions(options);
|
|
32
|
-
if (
|
|
33
|
-
doItemChange(options,
|
|
36
|
+
if (valueSource != null)
|
|
37
|
+
doItemChange(options, valueSource, false);
|
|
34
38
|
};
|
|
35
39
|
// When options change
|
|
36
40
|
// [options] will cause infinite loop
|
|
@@ -41,57 +45,44 @@ export function SelectEx(props) {
|
|
|
41
45
|
setOptionsAdd(options);
|
|
42
46
|
}, [options, propertyWay]);
|
|
43
47
|
// Local value
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
else {
|
|
53
|
-
localValue = valueSource;
|
|
54
|
-
}
|
|
48
|
+
const v = defaultValue !== null && defaultValue !== void 0 ? defaultValue : value;
|
|
49
|
+
const valueSource = multiple
|
|
50
|
+
? v
|
|
51
|
+
? Array.isArray(v)
|
|
52
|
+
? v
|
|
53
|
+
: [v]
|
|
54
|
+
: []
|
|
55
|
+
: v !== null && v !== void 0 ? v : '';
|
|
55
56
|
// Value state
|
|
56
|
-
const [valueState, setValueStateBase] = React.useState();
|
|
57
|
+
const [valueState, setValueStateBase] = React.useState(valueSource);
|
|
57
58
|
const valueRef = React.useRef();
|
|
58
59
|
const setValueState = (newValue) => {
|
|
59
60
|
valueRef.current = newValue;
|
|
60
61
|
setValueStateBase(newValue);
|
|
61
62
|
};
|
|
62
63
|
React.useEffect(() => {
|
|
63
|
-
if (
|
|
64
|
-
setValueState(
|
|
65
|
-
}, [
|
|
64
|
+
if (valueSource != null)
|
|
65
|
+
setValueState(valueSource);
|
|
66
|
+
}, [valueSource]);
|
|
66
67
|
// Label id
|
|
67
68
|
const labelId = `selectex-label-${name}`;
|
|
68
|
-
// Item checked or not
|
|
69
|
-
const itemChecked = (id) => {
|
|
70
|
-
if (Array.isArray(valueState))
|
|
71
|
-
return valueState.indexOf(id) !== -1;
|
|
72
|
-
return valueState === id;
|
|
73
|
-
};
|
|
74
|
-
// Change handler
|
|
75
|
-
const handleChange = (event) => {
|
|
76
|
-
const value = event.target.value;
|
|
77
|
-
if (multiple && !Array.isArray(value))
|
|
78
|
-
return setItemValue([value]);
|
|
79
|
-
else
|
|
80
|
-
return setItemValue(value);
|
|
81
|
-
};
|
|
82
69
|
// Set item
|
|
83
70
|
const setItemValue = (id) => {
|
|
84
71
|
var _a;
|
|
85
72
|
if (id != valueRef.current) {
|
|
73
|
+
// Difference
|
|
74
|
+
const diff = multiple
|
|
75
|
+
? Utils.arrayDifferences(id, valueRef.current)
|
|
76
|
+
: id;
|
|
86
77
|
setValueState(id);
|
|
87
78
|
const input = (_a = divRef.current) === null || _a === void 0 ? void 0 : _a.querySelector('input');
|
|
88
79
|
if (input) {
|
|
89
80
|
// Different value, trigger change event
|
|
90
81
|
ReactUtils.triggerChange(input, id, false);
|
|
91
82
|
}
|
|
92
|
-
return
|
|
83
|
+
return diff;
|
|
93
84
|
}
|
|
94
|
-
return
|
|
85
|
+
return undefined;
|
|
95
86
|
};
|
|
96
87
|
// Get option id
|
|
97
88
|
const getId = (option) => {
|
|
@@ -123,7 +114,7 @@ export function SelectEx(props) {
|
|
|
123
114
|
// When value change
|
|
124
115
|
React.useEffect(() => {
|
|
125
116
|
refreshData();
|
|
126
|
-
}, [
|
|
117
|
+
}, [valueSource]);
|
|
127
118
|
// When layout ready
|
|
128
119
|
React.useEffect(() => {
|
|
129
120
|
var _a;
|
|
@@ -134,6 +125,7 @@ export function SelectEx(props) {
|
|
|
134
125
|
setValueState(multiple ? [] : '');
|
|
135
126
|
};
|
|
136
127
|
input === null || input === void 0 ? void 0 : input.addEventListener('change', inputChange);
|
|
128
|
+
isMounted.current = true;
|
|
137
129
|
return () => {
|
|
138
130
|
isMounted.current = false;
|
|
139
131
|
input === null || input === void 0 ? void 0 : input.removeEventListener('change', inputChange);
|
|
@@ -145,17 +137,21 @@ export function SelectEx(props) {
|
|
|
145
137
|
React.createElement(InputLabel, { id: labelId, shrink: search
|
|
146
138
|
? MUGlobal.searchFieldShrink
|
|
147
139
|
: MUGlobal.inputFieldShrink }, label),
|
|
148
|
-
React.createElement(Select, { ref: divRef, value:
|
|
149
|
-
? valueState
|
|
150
|
-
:
|
|
140
|
+
React.createElement(Select, { ref: divRef, value: multiple
|
|
141
|
+
? valueState
|
|
142
|
+
: localOptions.some((o) => o[idField] === valueState)
|
|
143
|
+
? valueState
|
|
144
|
+
: '', input: React.createElement(OutlinedInput, { notched: true, label: label, required: inputRequired }), labelId: labelId, name: name, multiple: multiple, onChange: (event, child) => {
|
|
151
145
|
if (onChange) {
|
|
152
146
|
onChange(event, child);
|
|
153
147
|
// event.preventDefault() will block executing
|
|
154
148
|
if (event.defaultPrevented)
|
|
155
149
|
return;
|
|
156
150
|
}
|
|
157
|
-
|
|
158
|
-
|
|
151
|
+
// Set item value
|
|
152
|
+
const diff = setItemValue(event.target.value);
|
|
153
|
+
if (diff != null) {
|
|
154
|
+
doItemChange(localOptions, diff, true);
|
|
159
155
|
}
|
|
160
156
|
}, renderValue: (selected) => {
|
|
161
157
|
// The text shows up
|
|
@@ -181,7 +177,9 @@ export function SelectEx(props) {
|
|
|
181
177
|
}, style: itemStyle == null
|
|
182
178
|
? undefined
|
|
183
179
|
: itemStyle(option) },
|
|
184
|
-
multiple && (React.createElement(Checkbox, { checked:
|
|
180
|
+
multiple && (React.createElement(Checkbox, { checked: Array.isArray(valueState)
|
|
181
|
+
? valueState.includes(id)
|
|
182
|
+
: valueState === id })),
|
|
185
183
|
React.createElement(ListItemText, { primary: label }),
|
|
186
184
|
itemIconRenderer && (React.createElement(ListItemRightIcon, null, itemIconRenderer(option[idField])))));
|
|
187
185
|
})),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@etsoo/materialui",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.58",
|
|
4
4
|
"description": "TypeScript Material-UI Implementation",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"@etsoo/appscript": "^1.3.28",
|
|
55
55
|
"@etsoo/notificationbase": "^1.1.13",
|
|
56
56
|
"@etsoo/react": "^1.6.23",
|
|
57
|
-
"@etsoo/shared": "^1.1.
|
|
57
|
+
"@etsoo/shared": "^1.1.71",
|
|
58
58
|
"@mui/icons-material": "^5.10.9",
|
|
59
59
|
"@mui/material": "^5.10.12",
|
|
60
60
|
"@types/pica": "^9.0.1",
|
package/src/SelectEx.tsx
CHANGED
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
MenuItem,
|
|
9
9
|
OutlinedInput,
|
|
10
10
|
Select,
|
|
11
|
-
SelectChangeEvent,
|
|
12
11
|
SelectProps,
|
|
13
12
|
Stack
|
|
14
13
|
} from '@mui/material';
|
|
@@ -143,7 +142,7 @@ export function SelectEx<
|
|
|
143
142
|
|
|
144
143
|
// Options state
|
|
145
144
|
const [localOptions, setOptions] = React.useState<readonly T[]>([]);
|
|
146
|
-
const isMounted = React.useRef(
|
|
145
|
+
const isMounted = React.useRef(false);
|
|
147
146
|
|
|
148
147
|
const doItemChange = (
|
|
149
148
|
options: readonly T[],
|
|
@@ -151,18 +150,21 @@ export function SelectEx<
|
|
|
151
150
|
userAction: boolean
|
|
152
151
|
) => {
|
|
153
152
|
if (onItemChange == null) return;
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
153
|
+
|
|
154
|
+
let option: T | undefined;
|
|
155
|
+
if (multiple && Array.isArray(value)) {
|
|
156
|
+
option = options.find((option) => value.includes(option[idField]));
|
|
157
|
+
} else if (value == null || value === '') {
|
|
158
|
+
option = undefined;
|
|
159
|
+
} else {
|
|
160
|
+
option = options.find((option) => option[idField] === value);
|
|
157
161
|
}
|
|
158
|
-
const option = options.find((option) => option[idField] === value);
|
|
159
162
|
onItemChange(option, userAction);
|
|
160
163
|
};
|
|
161
164
|
|
|
162
165
|
const setOptionsAdd = (options: readonly T[]) => {
|
|
163
166
|
setOptions(options);
|
|
164
|
-
if (
|
|
165
|
-
doItemChange(options, localValue, false);
|
|
167
|
+
if (valueSource != null) doItemChange(options, valueSource, false);
|
|
166
168
|
};
|
|
167
169
|
|
|
168
170
|
// When options change
|
|
@@ -174,17 +176,18 @@ export function SelectEx<
|
|
|
174
176
|
}, [options, propertyWay]);
|
|
175
177
|
|
|
176
178
|
// Local value
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
179
|
+
const v = defaultValue ?? value;
|
|
180
|
+
const valueSource = multiple
|
|
181
|
+
? v
|
|
182
|
+
? Array.isArray(v)
|
|
183
|
+
? v
|
|
184
|
+
: [v]
|
|
185
|
+
: []
|
|
186
|
+
: v ?? '';
|
|
185
187
|
|
|
186
188
|
// Value state
|
|
187
|
-
const [valueState, setValueStateBase] =
|
|
189
|
+
const [valueState, setValueStateBase] =
|
|
190
|
+
React.useState<unknown>(valueSource);
|
|
188
191
|
const valueRef = React.useRef<unknown>();
|
|
189
192
|
const setValueState = (newValue: unknown) => {
|
|
190
193
|
valueRef.current = newValue;
|
|
@@ -192,28 +195,23 @@ export function SelectEx<
|
|
|
192
195
|
};
|
|
193
196
|
|
|
194
197
|
React.useEffect(() => {
|
|
195
|
-
if (
|
|
196
|
-
}, [
|
|
198
|
+
if (valueSource != null) setValueState(valueSource);
|
|
199
|
+
}, [valueSource]);
|
|
197
200
|
|
|
198
201
|
// Label id
|
|
199
202
|
const labelId = `selectex-label-${name}`;
|
|
200
203
|
|
|
201
|
-
// Item checked or not
|
|
202
|
-
const itemChecked = (id: unknown) => {
|
|
203
|
-
if (Array.isArray(valueState)) return valueState.indexOf(id) !== -1;
|
|
204
|
-
return valueState === id;
|
|
205
|
-
};
|
|
206
|
-
|
|
207
|
-
// Change handler
|
|
208
|
-
const handleChange = (event: SelectChangeEvent<unknown>) => {
|
|
209
|
-
const value = event.target.value;
|
|
210
|
-
if (multiple && !Array.isArray(value)) return setItemValue([value]);
|
|
211
|
-
else return setItemValue(value);
|
|
212
|
-
};
|
|
213
|
-
|
|
214
204
|
// Set item
|
|
215
205
|
const setItemValue = (id: unknown) => {
|
|
216
206
|
if (id != valueRef.current) {
|
|
207
|
+
// Difference
|
|
208
|
+
const diff = multiple
|
|
209
|
+
? Utils.arrayDifferences(
|
|
210
|
+
id as T[D][],
|
|
211
|
+
valueRef.current as T[D][]
|
|
212
|
+
)
|
|
213
|
+
: id;
|
|
214
|
+
|
|
217
215
|
setValueState(id);
|
|
218
216
|
|
|
219
217
|
const input = divRef.current?.querySelector('input');
|
|
@@ -221,9 +219,9 @@ export function SelectEx<
|
|
|
221
219
|
// Different value, trigger change event
|
|
222
220
|
ReactUtils.triggerChange(input, id as string, false);
|
|
223
221
|
}
|
|
224
|
-
return
|
|
222
|
+
return diff;
|
|
225
223
|
}
|
|
226
|
-
return
|
|
224
|
+
return undefined;
|
|
227
225
|
};
|
|
228
226
|
|
|
229
227
|
// Get option id
|
|
@@ -257,7 +255,7 @@ export function SelectEx<
|
|
|
257
255
|
// When value change
|
|
258
256
|
React.useEffect(() => {
|
|
259
257
|
refreshData();
|
|
260
|
-
}, [
|
|
258
|
+
}, [valueSource]);
|
|
261
259
|
|
|
262
260
|
// When layout ready
|
|
263
261
|
React.useEffect(() => {
|
|
@@ -268,6 +266,8 @@ export function SelectEx<
|
|
|
268
266
|
};
|
|
269
267
|
input?.addEventListener('change', inputChange);
|
|
270
268
|
|
|
269
|
+
isMounted.current = true;
|
|
270
|
+
|
|
271
271
|
return () => {
|
|
272
272
|
isMounted.current = false;
|
|
273
273
|
input?.removeEventListener('change', inputChange);
|
|
@@ -297,10 +297,12 @@ export function SelectEx<
|
|
|
297
297
|
<Select
|
|
298
298
|
ref={divRef}
|
|
299
299
|
value={
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
300
|
+
multiple
|
|
301
|
+
? valueState
|
|
302
|
+
: localOptions.some(
|
|
303
|
+
(o) => o[idField] === valueState
|
|
304
|
+
)
|
|
305
|
+
? valueState
|
|
304
306
|
: ''
|
|
305
307
|
}
|
|
306
308
|
input={
|
|
@@ -321,12 +323,10 @@ export function SelectEx<
|
|
|
321
323
|
if (event.defaultPrevented) return;
|
|
322
324
|
}
|
|
323
325
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
true
|
|
329
|
-
);
|
|
326
|
+
// Set item value
|
|
327
|
+
const diff = setItemValue(event.target.value);
|
|
328
|
+
if (diff != null) {
|
|
329
|
+
doItemChange(localOptions, diff, true);
|
|
330
330
|
}
|
|
331
331
|
}}
|
|
332
332
|
renderValue={(selected) => {
|
|
@@ -369,7 +369,13 @@ export function SelectEx<
|
|
|
369
369
|
}
|
|
370
370
|
>
|
|
371
371
|
{multiple && (
|
|
372
|
-
<Checkbox
|
|
372
|
+
<Checkbox
|
|
373
|
+
checked={
|
|
374
|
+
Array.isArray(valueState)
|
|
375
|
+
? valueState.includes(id)
|
|
376
|
+
: valueState === id
|
|
377
|
+
}
|
|
378
|
+
/>
|
|
373
379
|
)}
|
|
374
380
|
<ListItemText primary={label} />
|
|
375
381
|
{itemIconRenderer && (
|