@etsoo/materialui 1.0.6 → 1.0.9

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
  });
@@ -38,9 +38,13 @@ export declare type HiSelectorProps<T extends object, D extends DataTypes.Keys<T
38
38
  */
39
39
  onChange?: (value: unknown) => void;
40
40
  /**
41
- * On item change event
41
+ * On select change event
42
42
  */
43
- onItemChange?: (e: SelectChangeEvent<unknown>) => void;
43
+ onSelectChange?: (e: SelectChangeEvent<unknown>) => void;
44
+ /**
45
+ * Item change callback
46
+ */
47
+ onItemChange?: (option: T | undefined, userAction: boolean) => void;
44
48
  /**
45
49
  * Required
46
50
  */
package/lib/HiSelector.js CHANGED
@@ -8,7 +8,7 @@ import { SelectEx } from './SelectEx';
8
8
  */
9
9
  export function HiSelector(props) {
10
10
  // Destruct
11
- const { idField = 'id', error, helperText, name, label = name, labelField = 'name', loadData, onChange, onItemChange, required, values = [] } = props;
11
+ const { idField = 'id', error, helperText, name, label = name, labelField = 'name', loadData, onChange, onSelectChange, onItemChange, required, values = [] } = props;
12
12
  const [localValues, setValues] = React.useState(values);
13
13
  const updateValue = (value) => {
14
14
  if (onChange)
@@ -22,8 +22,16 @@ export function HiSelector(props) {
22
22
  if (itemValue != null)
23
23
  newValues.push(itemValue);
24
24
  setValues(newValues);
25
- if (onItemChange)
26
- onItemChange(event);
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);
27
35
  };
28
36
  React.useEffect(() => {
29
37
  if (values.length > 0) {
@@ -37,11 +45,11 @@ export function HiSelector(props) {
37
45
  React.createElement(FormLabel, { required: required, sx: { fontSize: (theme) => theme.typography.caption } }, label),
38
46
  React.createElement("input", { type: "hidden", name: name, value: `${currentValue !== null && currentValue !== void 0 ? currentValue : ''}` })),
39
47
  React.createElement(Grid, { item: true, xs: 6, md: 4, lg: 3 },
40
- React.createElement(SelectEx, { idField: idField, labelField: labelField, name: "tab1", search: true, fullWidth: true, loadData: () => loadData(), value: values[0], onChange: (event) => doChange(event, 0), inputRequired: required, error: error, helperText: helperText })),
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 })),
41
49
  localValues[0] != null && (React.createElement(Grid, { item: true, xs: 6, md: 4, lg: 3 },
42
- 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) }))),
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 }))),
43
51
  localValues[1] != null && (React.createElement(Grid, { item: true, xs: 6, md: 4, lg: 3 },
44
- 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) }))),
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 }))),
45
53
  localValues[2] != null && (React.createElement(Grid, { item: true, xs: 6, md: 4, lg: 3 },
46
- 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) })))));
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 })))));
47
55
  }
@@ -22,7 +22,8 @@ export function ListMoreDisplay(props) {
22
22
  orderBy: defaultOrderBy,
23
23
  batchSize: 10,
24
24
  loadedItems: 0,
25
- selectedItems: []
25
+ selectedItems: [],
26
+ idCache: {}
26
27
  });
27
28
  const ref = refs.current;
28
29
  // States
@@ -59,13 +60,15 @@ export function ListMoreDisplay(props) {
59
60
  // Next page
60
61
  ref.currentPage = currentPage + 1;
61
62
  // Update rows
62
- if (states.items == null || reset)
63
+ if (states.items == null || reset) {
63
64
  setStates({ items, completed: !hasNextPage });
64
- else
65
+ }
66
+ else {
65
67
  setStates({
66
68
  items: [...states.items, ...items],
67
69
  completed: !hasNextPage
68
70
  });
71
+ }
69
72
  };
70
73
  const reset = (data) => {
71
74
  // Update the form data
@@ -34,7 +34,7 @@ export declare type ScrollerListExItemSize = ((index: number) => [number, number
34
34
  /**
35
35
  * Extended ScrollerList Props
36
36
  */
37
- export declare type ScrollerListExProps<T extends object, D extends DataTypes.Keys<T>> = Omit<ScrollerListProps<T>, 'itemRenderer' | 'itemSize'> & {
37
+ export declare type ScrollerListExProps<T extends object, D extends DataTypes.Keys<T>> = Omit<ScrollerListProps<T, D>, 'itemRenderer' | 'itemSize'> & {
38
38
  /**
39
39
  * Alternating colors for odd/even rows
40
40
  */
@@ -47,11 +47,6 @@ export declare type ScrollerListExProps<T extends object, D extends DataTypes.Ke
47
47
  * Item renderer
48
48
  */
49
49
  itemRenderer?: (props: ListChildComponentProps<T>) => React.ReactElement;
50
- /**
51
- * Id field
52
- * Failed: D extends { id: DataTypes.IdType } ? { idField?: D } : { idField: D }
53
- */
54
- idField?: D;
55
50
  /**
56
51
  * Item size, a function indicates its a variable size list
57
52
  */
@@ -1,6 +1,6 @@
1
1
  import { css } from '@emotion/css';
2
2
  import { ScrollerList } from '@etsoo/react';
3
- import { DataTypes, Utils } from '@etsoo/shared';
3
+ import { Utils } from '@etsoo/shared';
4
4
  import { useTheme } from '@mui/material';
5
5
  import React from 'react';
6
6
  import { MUGlobal } from './MUGlobal';
@@ -112,7 +112,7 @@ export function ScrollerListEx(props) {
112
112
  return selected;
113
113
  };
114
114
  // Destruct
115
- const { alternatingColors = [undefined, undefined], className, idField = 'id', innerItemRenderer, itemSize, itemKey = (index, data) => { var _a; return (_a = DataTypes.getIdValue1(data, idField)) !== null && _a !== void 0 ? _a : index; }, itemRenderer = (itemProps) => {
115
+ const { alternatingColors = [undefined, undefined], className, idField = 'id', innerItemRenderer, itemSize, itemRenderer = (itemProps) => {
116
116
  const [itemHeight, space, margins] = calculateItemSize(itemProps.index);
117
117
  return defaultItemRenderer({
118
118
  itemHeight,
@@ -163,5 +163,5 @@ export function ScrollerListEx(props) {
163
163
  return size + space;
164
164
  };
165
165
  // Layout
166
- return (React.createElement(ScrollerList, { className: Utils.mergeClasses('ScrollerListEx-Body', className, createGridStyle(alternatingColors, selectedColor)), itemKey: itemKey, itemRenderer: itemRenderer, itemSize: itemSizeLocal, ...rest }));
166
+ return (React.createElement(ScrollerList, { className: Utils.mergeClasses('ScrollerListEx-Body', className, createGridStyle(alternatingColors, selectedColor)), idField: idField, itemRenderer: itemRenderer, itemSize: itemSizeLocal, ...rest }));
167
167
  }
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/TableEx.js CHANGED
@@ -51,7 +51,8 @@ export function TableEx(props) {
51
51
  ? (_a = columns.find((column) => column.field === defaultOrderBy)) === null || _a === void 0 ? void 0 : _a.sortAsc
52
52
  : undefined,
53
53
  batchSize: rowsPerPageLocal,
54
- selectedItems: []
54
+ selectedItems: [],
55
+ idCache: {}
55
56
  });
56
57
  const state = stateRefs.current;
57
58
  // Reset the state and load again
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@etsoo/materialui",
3
- "version": "1.0.6",
3
+ "version": "1.0.9",
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",
@@ -52,9 +52,14 @@ export type HiSelectorProps<
52
52
  onChange?: (value: unknown) => void;
53
53
 
54
54
  /**
55
- * On item change event
55
+ * On select change event
56
56
  */
57
- onItemChange?: (e: SelectChangeEvent<unknown>) => void;
57
+ onSelectChange?: (e: SelectChangeEvent<unknown>) => void;
58
+
59
+ /**
60
+ * Item change callback
61
+ */
62
+ onItemChange?: (option: T | undefined, userAction: boolean) => void;
58
63
 
59
64
  /**
60
65
  * Required
@@ -87,6 +92,7 @@ export function HiSelector<
87
92
  labelField = 'name' as L,
88
93
  loadData,
89
94
  onChange,
95
+ onSelectChange,
90
96
  onItemChange,
91
97
  required,
92
98
  values = []
@@ -109,7 +115,17 @@ export function HiSelector<
109
115
  if (itemValue != null) newValues.push(itemValue);
110
116
  setValues(newValues);
111
117
 
112
- if (onItemChange) onItemChange(event);
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);
113
129
  };
114
130
 
115
131
  React.useEffect(() => {
@@ -146,6 +162,7 @@ export function HiSelector<
146
162
  loadData={() => loadData()}
147
163
  value={values[0]}
148
164
  onChange={(event) => doChange(event, 0)}
165
+ onItemChange={doItemChange}
149
166
  inputRequired={required}
150
167
  error={error}
151
168
  helperText={helperText}
@@ -163,6 +180,7 @@ export function HiSelector<
163
180
  loadData={() => loadData(localValues[0])}
164
181
  value={values[1]}
165
182
  onChange={(event) => doChange(event, 1)}
183
+ onItemChange={doItemChange}
166
184
  />
167
185
  </Grid>
168
186
  )}
@@ -178,6 +196,7 @@ export function HiSelector<
178
196
  loadData={() => loadData(localValues[1])}
179
197
  value={values[2]}
180
198
  onChange={(event) => doChange(event, 2)}
199
+ onItemChange={doItemChange}
181
200
  />
182
201
  </Grid>
183
202
  )}
@@ -193,6 +212,7 @@ export function HiSelector<
193
212
  loadData={() => loadData(localValues[2])}
194
213
  value={values[3]}
195
214
  onChange={(event) => doChange(event, 3)}
215
+ onItemChange={doItemChange}
196
216
  />
197
217
  </Grid>
198
218
  )}
@@ -92,7 +92,8 @@ export function ListMoreDisplay<
92
92
  orderBy: defaultOrderBy,
93
93
  batchSize: 10,
94
94
  loadedItems: 0,
95
- selectedItems: []
95
+ selectedItems: [],
96
+ idCache: {}
96
97
  });
97
98
  const ref = refs.current;
98
99
 
@@ -141,13 +142,14 @@ export function ListMoreDisplay<
141
142
  ref.currentPage = currentPage + 1;
142
143
 
143
144
  // Update rows
144
- if (states.items == null || reset)
145
+ if (states.items == null || reset) {
145
146
  setStates({ items, completed: !hasNextPage });
146
- else
147
+ } else {
147
148
  setStates({
148
149
  items: [...states.items, ...items],
149
150
  completed: !hasNextPage
150
151
  });
152
+ }
151
153
  };
152
154
 
153
155
  const reset = (data?: GridData) => {
@@ -120,7 +120,7 @@ export type ScrollerListExItemSize =
120
120
  export type ScrollerListExProps<
121
121
  T extends object,
122
122
  D extends DataTypes.Keys<T>
123
- > = Omit<ScrollerListProps<T>, 'itemRenderer' | 'itemSize'> & {
123
+ > = Omit<ScrollerListProps<T, D>, 'itemRenderer' | 'itemSize'> & {
124
124
  /**
125
125
  * Alternating colors for odd/even rows
126
126
  */
@@ -138,12 +138,6 @@ export type ScrollerListExProps<
138
138
  */
139
139
  itemRenderer?: (props: ListChildComponentProps<T>) => React.ReactElement;
140
140
 
141
- /**
142
- * Id field
143
- * Failed: D extends { id: DataTypes.IdType } ? { idField?: D } : { idField: D }
144
- */
145
- idField?: D;
146
-
147
141
  /**
148
142
  * Item size, a function indicates its a variable size list
149
143
  */
@@ -302,8 +296,6 @@ export function ScrollerListEx<
302
296
  idField = 'id' as D,
303
297
  innerItemRenderer,
304
298
  itemSize,
305
- itemKey = (index: number, data: T) =>
306
- DataTypes.getIdValue1(data, idField) ?? index,
307
299
  itemRenderer = (itemProps) => {
308
300
  const [itemHeight, space, margins] = calculateItemSize(
309
301
  itemProps.index
@@ -372,13 +364,13 @@ export function ScrollerListEx<
372
364
 
373
365
  // Layout
374
366
  return (
375
- <ScrollerList<T>
367
+ <ScrollerList<T, D>
376
368
  className={Utils.mergeClasses(
377
369
  'ScrollerListEx-Body',
378
370
  className,
379
371
  createGridStyle(alternatingColors, selectedColor)
380
372
  )}
381
- itemKey={itemKey}
373
+ idField={idField}
382
374
  itemRenderer={itemRenderer}
383
375
  itemSize={itemSizeLocal}
384
376
  {...rest}
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/TableEx.tsx CHANGED
@@ -163,7 +163,8 @@ export function TableEx<
163
163
  ? columns.find((column) => column.field === defaultOrderBy)?.sortAsc
164
164
  : undefined,
165
165
  batchSize: rowsPerPageLocal,
166
- selectedItems: []
166
+ selectedItems: [],
167
+ idCache: {}
167
168
  });
168
169
  const state = stateRefs.current;
169
170