@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.
- package/__tests__/SelectEx.tsx +30 -3
- package/lib/HiSelector.d.ts +6 -2
- package/lib/HiSelector.js +15 -7
- package/lib/ListMoreDisplay.js +6 -3
- package/lib/ScrollerListEx.d.ts +1 -6
- package/lib/ScrollerListEx.js +3 -3
- package/lib/SelectEx.d.ts +4 -0
- package/lib/SelectEx.js +28 -14
- package/lib/TableEx.js +2 -1
- package/package.json +12 -12
- package/src/HiSelector.tsx +23 -3
- package/src/ListMoreDisplay.tsx +5 -3
- package/src/ScrollerListEx.tsx +3 -11
- package/src/SelectEx.tsx +36 -8
- package/src/TableEx.tsx +2 -1
package/__tests__/SelectEx.tsx
CHANGED
|
@@ -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>
|
|
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
|
|
25
|
-
expect(
|
|
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
|
});
|
package/lib/HiSelector.d.ts
CHANGED
|
@@ -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
|
|
41
|
+
* On select change event
|
|
42
42
|
*/
|
|
43
|
-
|
|
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 (
|
|
26
|
-
|
|
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
|
}
|
package/lib/ListMoreDisplay.js
CHANGED
|
@@ -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
|
-
|
|
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
|
package/lib/ScrollerListEx.d.ts
CHANGED
|
@@ -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
|
*/
|
package/lib/ScrollerListEx.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { css } from '@emotion/css';
|
|
2
2
|
import { ScrollerList } from '@etsoo/react';
|
|
3
|
-
import {
|
|
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,
|
|
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)),
|
|
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
|
|
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(
|
|
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 (
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
125
|
-
|
|
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.
|
|
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.
|
|
54
|
+
"@etsoo/appscript": "^1.2.89",
|
|
55
55
|
"@etsoo/notificationbase": "^1.1.7",
|
|
56
|
-
"@etsoo/react": "^1.5.
|
|
57
|
-
"@etsoo/shared": "^1.1.
|
|
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.
|
|
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.
|
|
79
|
+
"@babel/core": "^7.19.0",
|
|
80
80
|
"@babel/plugin-transform-runtime": "^7.18.10",
|
|
81
|
-
"@babel/preset-env": "^7.
|
|
82
|
-
"@babel/runtime-corejs3": "^7.
|
|
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.
|
|
84
|
+
"@testing-library/react": "^13.4.0",
|
|
85
85
|
"@types/jest": "^29.0.0",
|
|
86
|
-
"@typescript-eslint/eslint-plugin": "^5.36.
|
|
87
|
-
"@typescript-eslint/parser": "^5.36.
|
|
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.
|
|
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",
|
package/src/HiSelector.tsx
CHANGED
|
@@ -52,9 +52,14 @@ export type HiSelectorProps<
|
|
|
52
52
|
onChange?: (value: unknown) => void;
|
|
53
53
|
|
|
54
54
|
/**
|
|
55
|
-
* On
|
|
55
|
+
* On select change event
|
|
56
56
|
*/
|
|
57
|
-
|
|
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 (
|
|
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
|
)}
|
package/src/ListMoreDisplay.tsx
CHANGED
|
@@ -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) => {
|
package/src/ScrollerListEx.tsx
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
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 (
|
|
138
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|