@etsoo/materialui 1.0.56 → 1.0.57

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.
@@ -51,3 +51,50 @@ 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 itemName3 = await findByText(baseElement, 'Name 3');
90
+ expect(itemName3.nodeName).toBe('SPAN');
91
+
92
+ // Checkbox
93
+ const checkbox = itemName3.closest('li')?.querySelector('input');
94
+
95
+ act(() => {
96
+ checkbox?.click();
97
+ });
98
+
99
+ expect(itemChangeCallback).toBeCalledTimes(2);
100
+ });
@@ -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(true);
18
+ const isMounted = React.useRef(false);
20
19
  const doItemChange = (options, value, userAction) => {
21
20
  if (onItemChange == null)
22
21
  return;
23
- if (value == null || value === '') {
24
- onItemChange(undefined, userAction);
25
- return;
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 (localValue != null && localValue !== '')
33
- doItemChange(options, localValue, false);
36
+ if (valueSource != null)
37
+ doItemChange(options, valueSource, false);
34
38
  };
35
39
  // When options change
36
40
  // [options] will cause infinite loop
@@ -41,28 +45,25 @@ export function SelectEx(props) {
41
45
  setOptionsAdd(options);
42
46
  }, [options, propertyWay]);
43
47
  // Local value
44
- const valueSource = (_a = defaultValue !== null && defaultValue !== void 0 ? defaultValue : value) !== null && _a !== void 0 ? _a : '';
45
- let localValue;
46
- if (multiple) {
47
- if (Array.isArray(valueSource))
48
- localValue = valueSource;
49
- else
50
- localValue = [valueSource];
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 (localValue != null)
64
- setValueState(localValue);
65
- }, [localValue]);
64
+ if (valueSource != null)
65
+ setValueState(valueSource);
66
+ }, [valueSource]);
66
67
  // Label id
67
68
  const labelId = `selectex-label-${name}`;
68
69
  // Item checked or not
@@ -71,27 +72,23 @@ export function SelectEx(props) {
71
72
  return valueState.indexOf(id) !== -1;
72
73
  return valueState === id;
73
74
  };
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
75
  // Set item
83
76
  const setItemValue = (id) => {
84
77
  var _a;
85
78
  if (id != valueRef.current) {
79
+ // Difference
80
+ const diff = multiple
81
+ ? Utils.arrayDifferences(id, valueRef.current)
82
+ : id;
86
83
  setValueState(id);
87
84
  const input = (_a = divRef.current) === null || _a === void 0 ? void 0 : _a.querySelector('input');
88
85
  if (input) {
89
86
  // Different value, trigger change event
90
87
  ReactUtils.triggerChange(input, id, false);
91
88
  }
92
- return true;
89
+ return diff;
93
90
  }
94
- return false;
91
+ return undefined;
95
92
  };
96
93
  // Get option id
97
94
  const getId = (option) => {
@@ -123,7 +120,7 @@ export function SelectEx(props) {
123
120
  // When value change
124
121
  React.useEffect(() => {
125
122
  refreshData();
126
- }, [localValue]);
123
+ }, [valueSource]);
127
124
  // When layout ready
128
125
  React.useEffect(() => {
129
126
  var _a;
@@ -134,6 +131,7 @@ export function SelectEx(props) {
134
131
  setValueState(multiple ? [] : '');
135
132
  };
136
133
  input === null || input === void 0 ? void 0 : input.addEventListener('change', inputChange);
134
+ isMounted.current = true;
137
135
  return () => {
138
136
  isMounted.current = false;
139
137
  input === null || input === void 0 ? void 0 : input.removeEventListener('change', inputChange);
@@ -145,17 +143,21 @@ export function SelectEx(props) {
145
143
  React.createElement(InputLabel, { id: labelId, shrink: search
146
144
  ? MUGlobal.searchFieldShrink
147
145
  : MUGlobal.inputFieldShrink }, label),
148
- React.createElement(Select, { ref: divRef, value: localOptions.some((option) => itemChecked(getId(option)))
149
- ? valueState !== null && valueState !== void 0 ? valueState : ''
150
- : '', input: React.createElement(OutlinedInput, { notched: true, label: label, required: inputRequired }), labelId: labelId, name: name, multiple: multiple, onChange: (event, child) => {
146
+ React.createElement(Select, { ref: divRef, value: multiple
147
+ ? valueState
148
+ : localOptions.some((o) => o[idField] === valueState)
149
+ ? valueState
150
+ : '', input: React.createElement(OutlinedInput, { notched: true, label: label, required: inputRequired }), labelId: labelId, name: name, multiple: multiple, onChange: (event, child) => {
151
151
  if (onChange) {
152
152
  onChange(event, child);
153
153
  // event.preventDefault() will block executing
154
154
  if (event.defaultPrevented)
155
155
  return;
156
156
  }
157
- if (handleChange(event)) {
158
- doItemChange(localOptions, event.target.value, true);
157
+ // Set item value
158
+ const diff = setItemValue(event.target.value);
159
+ if (diff != null) {
160
+ doItemChange(localOptions, diff, true);
159
161
  }
160
162
  }, renderValue: (selected) => {
161
163
  // The text shows up
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@etsoo/materialui",
3
- "version": "1.0.56",
3
+ "version": "1.0.57",
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.70",
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",
@@ -10,7 +10,7 @@ export type AutocompleteExtendedProps<
10
10
  D extends DataTypes.Keys<T>
11
11
  > = Omit<
12
12
  AutocompleteProps<T, undefined, false, false>,
13
- 'renderInput' | 'options'
13
+ 'renderInput' | 'options' | 'multiple'
14
14
  > & {
15
15
  /**
16
16
  * Id field
package/src/SelectEx.tsx CHANGED
@@ -143,7 +143,7 @@ export function SelectEx<
143
143
 
144
144
  // Options state
145
145
  const [localOptions, setOptions] = React.useState<readonly T[]>([]);
146
- const isMounted = React.useRef(true);
146
+ const isMounted = React.useRef(false);
147
147
 
148
148
  const doItemChange = (
149
149
  options: readonly T[],
@@ -151,18 +151,21 @@ export function SelectEx<
151
151
  userAction: boolean
152
152
  ) => {
153
153
  if (onItemChange == null) return;
154
- if (value == null || value === '') {
155
- onItemChange(undefined, userAction);
156
- return;
154
+
155
+ let option: T | undefined;
156
+ if (multiple && Array.isArray(value)) {
157
+ option = options.find((option) => value.includes(option[idField]));
158
+ } else if (value == null || value === '') {
159
+ option = undefined;
160
+ } else {
161
+ option = options.find((option) => option[idField] === value);
157
162
  }
158
- const option = options.find((option) => option[idField] === value);
159
163
  onItemChange(option, userAction);
160
164
  };
161
165
 
162
166
  const setOptionsAdd = (options: readonly T[]) => {
163
167
  setOptions(options);
164
- if (localValue != null && localValue !== '')
165
- doItemChange(options, localValue, false);
168
+ if (valueSource != null) doItemChange(options, valueSource, false);
166
169
  };
167
170
 
168
171
  // When options change
@@ -174,17 +177,18 @@ export function SelectEx<
174
177
  }, [options, propertyWay]);
175
178
 
176
179
  // Local value
177
- const valueSource = defaultValue ?? value ?? '';
178
- let localValue: unknown | unknown[];
179
- if (multiple) {
180
- if (Array.isArray(valueSource)) localValue = valueSource;
181
- else localValue = [valueSource];
182
- } else {
183
- localValue = valueSource;
184
- }
180
+ const v = defaultValue ?? value;
181
+ const valueSource = multiple
182
+ ? v
183
+ ? Array.isArray(v)
184
+ ? v
185
+ : [v]
186
+ : []
187
+ : v ?? '';
185
188
 
186
189
  // Value state
187
- const [valueState, setValueStateBase] = React.useState<unknown>();
190
+ const [valueState, setValueStateBase] =
191
+ React.useState<unknown>(valueSource);
188
192
  const valueRef = React.useRef<unknown>();
189
193
  const setValueState = (newValue: unknown) => {
190
194
  valueRef.current = newValue;
@@ -192,8 +196,8 @@ export function SelectEx<
192
196
  };
193
197
 
194
198
  React.useEffect(() => {
195
- if (localValue != null) setValueState(localValue);
196
- }, [localValue]);
199
+ if (valueSource != null) setValueState(valueSource);
200
+ }, [valueSource]);
197
201
 
198
202
  // Label id
199
203
  const labelId = `selectex-label-${name}`;
@@ -204,16 +208,17 @@ export function SelectEx<
204
208
  return valueState === id;
205
209
  };
206
210
 
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
211
  // Set item
215
212
  const setItemValue = (id: unknown) => {
216
213
  if (id != valueRef.current) {
214
+ // Difference
215
+ const diff = multiple
216
+ ? Utils.arrayDifferences(
217
+ id as T[D][],
218
+ valueRef.current as T[D][]
219
+ )
220
+ : id;
221
+
217
222
  setValueState(id);
218
223
 
219
224
  const input = divRef.current?.querySelector('input');
@@ -221,9 +226,9 @@ export function SelectEx<
221
226
  // Different value, trigger change event
222
227
  ReactUtils.triggerChange(input, id as string, false);
223
228
  }
224
- return true;
229
+ return diff;
225
230
  }
226
- return false;
231
+ return undefined;
227
232
  };
228
233
 
229
234
  // Get option id
@@ -257,7 +262,7 @@ export function SelectEx<
257
262
  // When value change
258
263
  React.useEffect(() => {
259
264
  refreshData();
260
- }, [localValue]);
265
+ }, [valueSource]);
261
266
 
262
267
  // When layout ready
263
268
  React.useEffect(() => {
@@ -268,6 +273,8 @@ export function SelectEx<
268
273
  };
269
274
  input?.addEventListener('change', inputChange);
270
275
 
276
+ isMounted.current = true;
277
+
271
278
  return () => {
272
279
  isMounted.current = false;
273
280
  input?.removeEventListener('change', inputChange);
@@ -297,10 +304,12 @@ export function SelectEx<
297
304
  <Select
298
305
  ref={divRef}
299
306
  value={
300
- localOptions.some((option) =>
301
- itemChecked(getId(option))
302
- )
303
- ? valueState ?? ''
307
+ multiple
308
+ ? valueState
309
+ : localOptions.some(
310
+ (o) => o[idField] === valueState
311
+ )
312
+ ? valueState
304
313
  : ''
305
314
  }
306
315
  input={
@@ -321,12 +330,10 @@ export function SelectEx<
321
330
  if (event.defaultPrevented) return;
322
331
  }
323
332
 
324
- if (handleChange(event)) {
325
- doItemChange(
326
- localOptions,
327
- event.target.value,
328
- true
329
- );
333
+ // Set item value
334
+ const diff = setItemValue(event.target.value);
335
+ if (diff != null) {
336
+ doItemChange(localOptions, diff, true);
330
337
  }
331
338
  }}
332
339
  renderValue={(selected) => {