@etsoo/materialui 1.0.7 → 1.0.10

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
  });
@@ -44,7 +44,7 @@ export declare type HiSelectorProps<T extends object, D extends DataTypes.Keys<T
44
44
  /**
45
45
  * Item change callback
46
46
  */
47
- onItemChange?: (option?: T) => void;
47
+ onItemChange?: (option: T | undefined, userAction: boolean) => void;
48
48
  /**
49
49
  * Required
50
50
  */
package/lib/HiSelector.js CHANGED
@@ -31,7 +31,7 @@ export function HiSelector(props) {
31
31
  if (!userAction &&
32
32
  (option == null || option[idField] !== values.at(-1)))
33
33
  return;
34
- onItemChange(option);
34
+ onItemChange(option, userAction);
35
35
  };
36
36
  React.useEffect(() => {
37
37
  if (values.length > 0) {
@@ -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.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,30 +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, onItemChange, 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);
19
18
  const doItemChange = (options, value, userAction) => {
20
19
  if (onItemChange == null)
21
20
  return;
22
- if (value == null || value === '')
21
+ if (value == null || value === '') {
23
22
  onItemChange(undefined, userAction);
23
+ return;
24
+ }
24
25
  const option = options.find((option) => option[idField] === value);
25
26
  onItemChange(option, userAction);
26
27
  };
27
28
  const setOptionsAdd = (options) => {
28
29
  setOptions(options);
29
- if (valueState != null && valueState !== '')
30
- doItemChange(options, valueState, false);
30
+ if (localValue != null && localValue !== '')
31
+ doItemChange(options, localValue, false);
31
32
  };
32
33
  // When options change
33
34
  // [options] will cause infinite loop
34
35
  const propertyWay = loadData == null;
35
36
  React.useEffect(() => {
36
- if (propertyWay && options != null)
37
- setOptionsAdd(options);
38
- }, [JSON.stringify(options), propertyWay]);
37
+ if (options == null || !propertyWay)
38
+ return;
39
+ setOptionsAdd(options);
40
+ }, [options, propertyWay]);
39
41
  // Local value
40
42
  const valueSource = (_a = defaultValue !== null && defaultValue !== void 0 ? defaultValue : value) !== null && _a !== void 0 ? _a : '';
41
43
  let localValue;
@@ -72,14 +74,15 @@ export function SelectEx(props) {
72
74
  };
73
75
  // Set item
74
76
  const setItemValue = (id) => {
75
- var _a;
76
77
  if (id != valueState) {
77
78
  setValueState(id);
78
- const input = (_a = divRef.current) === null || _a === void 0 ? void 0 : _a.querySelector('input');
79
+ /*
80
+ const input = divRef.current?.querySelector('input');
79
81
  if (input) {
80
82
  // Different value, trigger change event
81
- ReactUtils.triggerChange(input, id, false);
83
+ ReactUtils.triggerChange(input, id as string, false);
82
84
  }
85
+ */
83
86
  }
84
87
  };
85
88
  // Get option id
@@ -135,8 +138,7 @@ export function SelectEx(props) {
135
138
  if (onChange)
136
139
  onChange(event, child);
137
140
  doItemChange(localOptions, event.target.value, true);
138
- if (multiple)
139
- handleChange(event);
141
+ handleChange(event);
140
142
  }, renderValue: (selected) => {
141
143
  // The text shows up
142
144
  return localOptions
@@ -160,8 +162,6 @@ export function SelectEx(props) {
160
162
  if (event.defaultPrevented)
161
163
  return;
162
164
  }
163
- if (!multiple)
164
- setItemValue(id);
165
165
  }, style: itemStyle == null
166
166
  ? undefined
167
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.7",
3
+ "version": "1.0.10",
4
4
  "description": "TypeScript Material-UI Implementation",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -51,15 +51,15 @@
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.89",
54
+ "@etsoo/appscript": "^1.2.90",
55
55
  "@etsoo/notificationbase": "^1.1.7",
56
- "@etsoo/react": "^1.5.87",
57
- "@etsoo/shared": "^1.1.52",
56
+ "@etsoo/react": "^1.5.89",
57
+ "@etsoo/shared": "^1.1.53",
58
58
  "@mui/icons-material": "^5.10.3",
59
59
  "@mui/material": "^5.10.4",
60
60
  "@types/pica": "^9.0.1",
61
61
  "@types/pulltorefreshjs": "^0.1.5",
62
- "@types/react": "^18.0.18",
62
+ "@types/react": "^18.0.19",
63
63
  "@types/react-avatar-editor": "^13.0.0",
64
64
  "@types/react-dom": "^18.0.6",
65
65
  "@types/react-input-mask": "^3.0.1",
@@ -88,10 +88,10 @@
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.7",
92
- "jest": "^28.1.3",
93
- "jest-environment-jsdom": "^28.1.3",
94
- "ts-jest": "^28.0.8",
95
- "typescript": "^4.8.2"
91
+ "eslint-plugin-react": "^7.31.8",
92
+ "jest": "^29.0.3",
93
+ "jest-environment-jsdom": "^29.0.3",
94
+ "ts-jest": "^29.0.0",
95
+ "typescript": "^4.8.3"
96
96
  }
97
97
  }
@@ -59,7 +59,7 @@ export type HiSelectorProps<
59
59
  /**
60
60
  * Item change callback
61
61
  */
62
- onItemChange?: (option?: T) => void;
62
+ onItemChange?: (option: T | undefined, userAction: boolean) => void;
63
63
 
64
64
  /**
65
65
  * Required
@@ -125,7 +125,7 @@ export function HiSelector<
125
125
  (option == null || option[idField] !== values.at(-1))
126
126
  )
127
127
  return;
128
- onItemChange(option);
128
+ onItemChange(option, userAction);
129
129
  };
130
130
 
131
131
  React.useEffect(() => {
@@ -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
@@ -123,7 +122,7 @@ export function SelectEx<
123
122
  onLoadData,
124
123
  multiple = false,
125
124
  name,
126
- options = [],
125
+ options,
127
126
  search = false,
128
127
  autoAddBlankItem = search,
129
128
  value,
@@ -133,7 +132,7 @@ export function SelectEx<
133
132
  } = props;
134
133
 
135
134
  // Options state
136
- const [localOptions, setOptions] = React.useState(options);
135
+ const [localOptions, setOptions] = React.useState<readonly T[]>([]);
137
136
  const isMounted = React.useRef(true);
138
137
 
139
138
  const doItemChange = (
@@ -142,23 +141,27 @@ export function SelectEx<
142
141
  userAction: boolean
143
142
  ) => {
144
143
  if (onItemChange == null) return;
145
- if (value == null || value === '') onItemChange(undefined, userAction);
144
+ if (value == null || value === '') {
145
+ onItemChange(undefined, userAction);
146
+ return;
147
+ }
146
148
  const option = options.find((option) => option[idField] === value);
147
149
  onItemChange(option, userAction);
148
150
  };
149
151
 
150
152
  const setOptionsAdd = (options: readonly T[]) => {
151
153
  setOptions(options);
152
- if (valueState != null && valueState !== '')
153
- doItemChange(options, valueState, false);
154
+ if (localValue != null && localValue !== '')
155
+ doItemChange(options, localValue, false);
154
156
  };
155
157
 
156
158
  // When options change
157
159
  // [options] will cause infinite loop
158
160
  const propertyWay = loadData == null;
159
161
  React.useEffect(() => {
160
- if (propertyWay && options != null) setOptionsAdd(options);
161
- }, [JSON.stringify(options), propertyWay]);
162
+ if (options == null || !propertyWay) return;
163
+ setOptionsAdd(options);
164
+ }, [options, propertyWay]);
162
165
 
163
166
  // Local value
164
167
  const valueSource = defaultValue ?? value ?? '';
@@ -198,11 +201,13 @@ export function SelectEx<
198
201
  if (id != valueState) {
199
202
  setValueState(id);
200
203
 
204
+ /*
201
205
  const input = divRef.current?.querySelector('input');
202
206
  if (input) {
203
207
  // Different value, trigger change event
204
208
  ReactUtils.triggerChange(input, id as string, false);
205
209
  }
210
+ */
206
211
  }
207
212
  };
208
213
 
@@ -287,7 +292,7 @@ export function SelectEx<
287
292
  onChange={(event, child) => {
288
293
  if (onChange) onChange(event, child);
289
294
  doItemChange(localOptions, event.target.value, true);
290
- if (multiple) handleChange(event);
295
+ handleChange(event);
291
296
  }}
292
297
  renderValue={(selected) => {
293
298
  // The text shows up
@@ -322,7 +327,6 @@ export function SelectEx<
322
327
  onItemClick(event, option);
323
328
  if (event.defaultPrevented) return;
324
329
  }
325
- if (!multiple) setItemValue(id);
326
330
  }}
327
331
  style={
328
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