@etsoo/materialui 1.0.5 → 1.0.8

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.
@@ -2,6 +2,8 @@ import React from 'react';
2
2
  import { SelectEx } from '../src';
3
3
  import { findByText, fireEvent, render, screen } from '@testing-library/react';
4
4
  import '@testing-library/jest-dom/extend-expect';
5
+ import { Utils } from '@etsoo/shared';
6
+ import { act } from 'react-dom/test-utils';
5
7
 
6
8
  it('Render SelectEx', async () => {
7
9
  // Arrange
@@ -11,16 +13,41 @@ it('Render SelectEx', async () => {
11
13
  { id: 2, name: 'Name 2' }
12
14
  ];
13
15
 
16
+ Utils.addBlankItem(options, 'id', 'name');
17
+
18
+ const itemChangeCallback = jest.fn((option, userAction) => {
19
+ if (userAction) expect(option).toBeUndefined();
20
+ else expect(option.id).toBe(1);
21
+ });
22
+
14
23
  // Render component
15
24
  const { baseElement } = render(
16
- <SelectEx<T> options={options} name="test" search labelField="name" />
25
+ <SelectEx<T>
26
+ options={options}
27
+ name="test"
28
+ onItemChange={itemChangeCallback}
29
+ value={1}
30
+ search
31
+ labelField="name"
32
+ />
17
33
  );
18
34
 
35
+ expect(itemChangeCallback).toBeCalled();
36
+
19
37
  // Act, click to show the list
20
38
  const button = screen.getByRole('button');
21
39
  fireEvent.mouseDown(button); // Not click
22
40
 
23
41
  // Get list item
24
- const item = await findByText(baseElement, 'Name 2');
25
- expect(item.nodeName).toBe('SPAN');
42
+ const itemName2 = await findByText(baseElement, 'Name 2');
43
+ expect(itemName2.nodeName).toBe('SPAN');
44
+
45
+ const itemBlank = await findByText(baseElement, '---');
46
+ expect(itemBlank.nodeName).toBe('SPAN');
47
+
48
+ act(() => {
49
+ itemBlank.click();
50
+ });
51
+
52
+ expect(itemChangeCallback).toBeCalledTimes(2);
26
53
  });
@@ -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 | undefined, userAction: boolean) => 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, userAction);
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
@@ -37,6 +37,10 @@ export declare type SelectExProps<T extends object, D extends DataTypes.Keys<T>
37
37
  * Load data callback
38
38
  */
39
39
  loadData?: () => PromiseLike<T[] | null | undefined>;
40
+ /**
41
+ * Item change callback
42
+ */
43
+ onItemChange?: (option: T | undefined, userAction: boolean) => void;
40
44
  /**
41
45
  * Item click handler
42
46
  */
package/lib/SelectEx.js CHANGED
@@ -3,7 +3,6 @@ import React from 'react';
3
3
  import { MUGlobal } from './MUGlobal';
4
4
  import { ListItemRightIcon } from './ListItemRightIcon';
5
5
  import { Utils } from '@etsoo/shared';
6
- import { ReactUtils } from '@etsoo/react';
7
6
  /**
8
7
  * Extended select component
9
8
  * @param props Props
@@ -12,17 +11,33 @@ import { ReactUtils } from '@etsoo/react';
12
11
  export function SelectEx(props) {
13
12
  var _a;
14
13
  // Destruct
15
- const { defaultValue, idField = 'id', error, helperText, inputRequired, itemIconRenderer, itemStyle, label, labelField = 'label', loadData, onItemClick, onLoadData, multiple = false, name, options = [], search = false, autoAddBlankItem = search, value, onChange, fullWidth, ...rest } = props;
14
+ 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
15
  // Options state
17
- const [localOptions, setOptions] = React.useState(options);
16
+ const [localOptions, setOptions] = React.useState([]);
18
17
  const isMounted = React.useRef(true);
18
+ const doItemChange = (options, value, userAction) => {
19
+ if (onItemChange == null)
20
+ return;
21
+ if (value == null || value === '') {
22
+ onItemChange(undefined, userAction);
23
+ return;
24
+ }
25
+ const option = options.find((option) => option[idField] === value);
26
+ onItemChange(option, userAction);
27
+ };
28
+ const setOptionsAdd = (options) => {
29
+ setOptions(options);
30
+ if (localValue != null && localValue !== '')
31
+ doItemChange(options, localValue, false);
32
+ };
19
33
  // When options change
20
34
  // [options] will cause infinite loop
21
35
  const propertyWay = loadData == null;
22
36
  React.useEffect(() => {
23
- if (propertyWay && options != null)
24
- setOptions(options);
25
- }, [JSON.stringify(options), propertyWay]);
37
+ if (options == null || !propertyWay)
38
+ return;
39
+ setOptionsAdd(options);
40
+ }, [options, propertyWay]);
26
41
  // Local value
27
42
  const valueSource = (_a = defaultValue !== null && defaultValue !== void 0 ? defaultValue : value) !== null && _a !== void 0 ? _a : '';
28
43
  let localValue;
@@ -59,14 +74,15 @@ export function SelectEx(props) {
59
74
  };
60
75
  // Set item
61
76
  const setItemValue = (id) => {
62
- var _a;
63
77
  if (id != valueState) {
64
78
  setValueState(id);
65
- const input = (_a = divRef.current) === null || _a === void 0 ? void 0 : _a.querySelector('input');
79
+ /*
80
+ const input = divRef.current?.querySelector('input');
66
81
  if (input) {
67
82
  // Different value, trigger change event
68
- ReactUtils.triggerChange(input, id, false);
83
+ ReactUtils.triggerChange(input, id as string, false);
69
84
  }
85
+ */
70
86
  }
71
87
  };
72
88
  // Get option id
@@ -92,7 +108,7 @@ export function SelectEx(props) {
92
108
  if (autoAddBlankItem) {
93
109
  Utils.addBlankItem(result, idField, labelField);
94
110
  }
95
- setOptions(result);
111
+ setOptionsAdd(result);
96
112
  });
97
113
  }
98
114
  }, [localValue]);
@@ -121,8 +137,8 @@ export function SelectEx(props) {
121
137
  : '', input: React.createElement(OutlinedInput, { notched: true, label: label, required: inputRequired }), labelId: labelId, name: name, multiple: multiple, onChange: (event, child) => {
122
138
  if (onChange)
123
139
  onChange(event, child);
124
- if (multiple)
125
- handleChange(event);
140
+ doItemChange(localOptions, event.target.value, true);
141
+ handleChange(event);
126
142
  }, renderValue: (selected) => {
127
143
  // The text shows up
128
144
  return localOptions
@@ -146,8 +162,6 @@ export function SelectEx(props) {
146
162
  if (event.defaultPrevented)
147
163
  return;
148
164
  }
149
- if (!multiple)
150
- setItemValue(id);
151
165
  }, style: itemStyle == null
152
166
  ? undefined
153
167
  : itemStyle(option) },
package/lib/index.d.ts CHANGED
@@ -38,6 +38,7 @@ export * from './EmailInput';
38
38
  export * from './FabBox';
39
39
  export * from './FlexBox';
40
40
  export * from './GridDataFormat';
41
+ export * from './HiSelector';
41
42
  export * from './IconButtonLink';
42
43
  export * from './InputField';
43
44
  export * from './ItemList';
package/lib/index.js CHANGED
@@ -38,6 +38,7 @@ export * from './EmailInput';
38
38
  export * from './FabBox';
39
39
  export * from './FlexBox';
40
40
  export * from './GridDataFormat';
41
+ export * from './HiSelector';
41
42
  export * from './IconButtonLink';
42
43
  export * from './InputField';
43
44
  export * from './ItemList';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@etsoo/materialui",
3
- "version": "1.0.5",
3
+ "version": "1.0.8",
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.88",
54
+ "@etsoo/appscript": "^1.2.89",
55
55
  "@etsoo/notificationbase": "^1.1.7",
56
- "@etsoo/react": "^1.5.86",
57
- "@etsoo/shared": "^1.1.51",
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.3",
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.18.13",
79
+ "@babel/core": "^7.19.0",
80
80
  "@babel/plugin-transform-runtime": "^7.18.10",
81
- "@babel/preset-env": "^7.18.10",
82
- "@babel/runtime-corejs3": "^7.18.9",
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.3.0",
84
+ "@testing-library/react": "^13.4.0",
85
85
  "@types/jest": "^29.0.0",
86
- "@typescript-eslint/eslint-plugin": "^5.36.1",
87
- "@typescript-eslint/parser": "^5.36.1",
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.1",
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 | undefined, userAction: boolean) => 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, userAction);
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
@@ -20,7 +20,6 @@ import {
20
20
  ListType,
21
21
  Utils
22
22
  } from '@etsoo/shared';
23
- import { ReactUtils } from '@etsoo/react';
24
23
 
25
24
  /**
26
25
  * Extended select component props
@@ -70,6 +69,11 @@ export type SelectExProps<
70
69
  */
71
70
  loadData?: () => PromiseLike<T[] | null | undefined>;
72
71
 
72
+ /**
73
+ * Item change callback
74
+ */
75
+ onItemChange?: (option: T | undefined, userAction: boolean) => void;
76
+
73
77
  /**
74
78
  * Item click handler
75
79
  */
@@ -113,11 +117,12 @@ export function SelectEx<
113
117
  label,
114
118
  labelField = 'label' as L,
115
119
  loadData,
120
+ onItemChange,
116
121
  onItemClick,
117
122
  onLoadData,
118
123
  multiple = false,
119
124
  name,
120
- options = [],
125
+ options,
121
126
  search = false,
122
127
  autoAddBlankItem = search,
123
128
  value,
@@ -127,15 +132,36 @@ export function SelectEx<
127
132
  } = props;
128
133
 
129
134
  // Options state
130
- const [localOptions, setOptions] = React.useState(options);
135
+ const [localOptions, setOptions] = React.useState<readonly T[]>([]);
131
136
  const isMounted = React.useRef(true);
132
137
 
138
+ const doItemChange = (
139
+ options: readonly T[],
140
+ value: unknown,
141
+ userAction: boolean
142
+ ) => {
143
+ if (onItemChange == null) return;
144
+ if (value == null || value === '') {
145
+ onItemChange(undefined, userAction);
146
+ return;
147
+ }
148
+ const option = options.find((option) => option[idField] === value);
149
+ onItemChange(option, userAction);
150
+ };
151
+
152
+ const setOptionsAdd = (options: readonly T[]) => {
153
+ setOptions(options);
154
+ if (localValue != null && localValue !== '')
155
+ doItemChange(options, localValue, false);
156
+ };
157
+
133
158
  // When options change
134
159
  // [options] will cause infinite loop
135
160
  const propertyWay = loadData == null;
136
161
  React.useEffect(() => {
137
- if (propertyWay && options != null) setOptions(options);
138
- }, [JSON.stringify(options), propertyWay]);
162
+ if (options == null || !propertyWay) return;
163
+ setOptionsAdd(options);
164
+ }, [options, propertyWay]);
139
165
 
140
166
  // Local value
141
167
  const valueSource = defaultValue ?? value ?? '';
@@ -175,11 +201,13 @@ export function SelectEx<
175
201
  if (id != valueState) {
176
202
  setValueState(id);
177
203
 
204
+ /*
178
205
  const input = divRef.current?.querySelector('input');
179
206
  if (input) {
180
207
  // Different value, trigger change event
181
208
  ReactUtils.triggerChange(input, id as string, false);
182
209
  }
210
+ */
183
211
  }
184
212
  };
185
213
 
@@ -207,7 +235,7 @@ export function SelectEx<
207
235
  if (autoAddBlankItem) {
208
236
  Utils.addBlankItem(result, idField, labelField);
209
237
  }
210
- setOptions(result);
238
+ setOptionsAdd(result);
211
239
  });
212
240
  }
213
241
  }, [localValue]);
@@ -263,7 +291,8 @@ export function SelectEx<
263
291
  multiple={multiple}
264
292
  onChange={(event, child) => {
265
293
  if (onChange) onChange(event, child);
266
- if (multiple) handleChange(event);
294
+ doItemChange(localOptions, event.target.value, true);
295
+ handleChange(event);
267
296
  }}
268
297
  renderValue={(selected) => {
269
298
  // The text shows up
@@ -298,7 +327,6 @@ export function SelectEx<
298
327
  onItemClick(event, option);
299
328
  if (event.defaultPrevented) return;
300
329
  }
301
- if (!multiple) setItemValue(id);
302
330
  }}
303
331
  style={
304
332
  itemStyle == null
package/src/index.ts CHANGED
@@ -41,6 +41,7 @@ export * from './EmailInput';
41
41
  export * from './FabBox';
42
42
  export * from './FlexBox';
43
43
  export * from './GridDataFormat';
44
+ export * from './HiSelector';
44
45
  export * from './IconButtonLink';
45
46
  export * from './InputField';
46
47
  export * from './ItemList';