@etsoo/react 1.5.73 → 1.5.76
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__/mu/MUGlobalTests.tsx +11 -0
- package/lib/mu/AutocompleteExtendedProps.d.ts +3 -3
- package/lib/mu/ComboBox.d.ts +4 -4
- package/lib/mu/ComboBox.js +6 -6
- package/lib/mu/MUGlobal.d.ts +8 -1
- package/lib/mu/MUGlobal.js +33 -0
- package/lib/mu/SelectBool.js +1 -1
- package/lib/mu/SelectEx.d.ts +7 -7
- package/lib/mu/SelectEx.js +3 -3
- package/lib/mu/Tiplist.d.ts +3 -4
- package/lib/mu/Tiplist.js +5 -5
- package/package.json +6 -6
- package/src/mu/AutocompleteExtendedProps.ts +6 -4
- package/src/mu/ComboBox.tsx +18 -15
- package/src/mu/MUGlobal.ts +35 -1
- package/src/mu/SelectBool.tsx +7 -1
- package/src/mu/SelectEx.tsx +17 -13
- package/src/mu/Tiplist.tsx +14 -14
|
@@ -6,6 +6,17 @@ const updateFunc = (value: number) => `${value * 8}px`;
|
|
|
6
6
|
// Arrange
|
|
7
7
|
const paddings = { sx: 2, sm: 3, key: 'a' };
|
|
8
8
|
|
|
9
|
+
test('getMenuItem tests', () => {
|
|
10
|
+
// Assert
|
|
11
|
+
expect(
|
|
12
|
+
MUGlobal.getMenuItem('/user/add', '/user/all').selected
|
|
13
|
+
).toBeTruthy();
|
|
14
|
+
expect(MUGlobal.getMenuItem('/user/add', '/user/*').selected).toBeTruthy();
|
|
15
|
+
expect(
|
|
16
|
+
MUGlobal.getMenuItem('/user/add', '/user/edit').selected
|
|
17
|
+
).toBeFalsy();
|
|
18
|
+
});
|
|
19
|
+
|
|
9
20
|
test('half tests', () => {
|
|
10
21
|
// Act
|
|
11
22
|
const result = MUGlobal.half(paddings);
|
|
@@ -4,15 +4,15 @@ import { ChangeEventHandler } from 'react';
|
|
|
4
4
|
/**
|
|
5
5
|
* Autocomplete extended props
|
|
6
6
|
*/
|
|
7
|
-
export interface AutocompleteExtendedProps<T extends
|
|
7
|
+
export interface AutocompleteExtendedProps<T extends {}, D extends DataTypes.Keys<T>> extends Omit<AutocompleteProps<T, undefined, false, false>, 'renderInput' | 'options'> {
|
|
8
8
|
/**
|
|
9
9
|
* Id field, default is id
|
|
10
10
|
*/
|
|
11
|
-
idField
|
|
11
|
+
idField: D;
|
|
12
12
|
/**
|
|
13
13
|
* Id value
|
|
14
14
|
*/
|
|
15
|
-
idValue?:
|
|
15
|
+
idValue?: T[D];
|
|
16
16
|
/**
|
|
17
17
|
* Autocomplete for the input
|
|
18
18
|
*/
|
package/lib/mu/ComboBox.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
|
-
import {
|
|
2
|
+
import { DataTypes } from '@etsoo/shared';
|
|
3
3
|
import { AutocompleteExtendedProps } from './AutocompleteExtendedProps';
|
|
4
4
|
/**
|
|
5
5
|
* ComboBox props
|
|
6
6
|
*/
|
|
7
|
-
export interface ComboBoxProps<T extends {}> extends AutocompleteExtendedProps<T> {
|
|
7
|
+
export interface ComboBoxProps<T extends {}, D extends DataTypes.Keys<T> = DataTypes.Keys<T>> extends AutocompleteExtendedProps<T, D> {
|
|
8
8
|
/**
|
|
9
9
|
* Auto add blank item
|
|
10
10
|
*/
|
|
@@ -16,7 +16,7 @@ export interface ComboBoxProps<T extends {}> extends AutocompleteExtendedProps<T
|
|
|
16
16
|
/**
|
|
17
17
|
* Label field
|
|
18
18
|
*/
|
|
19
|
-
labelField
|
|
19
|
+
labelField: D;
|
|
20
20
|
/**
|
|
21
21
|
* Load data callback
|
|
22
22
|
*/
|
|
@@ -35,4 +35,4 @@ export interface ComboBoxProps<T extends {}> extends AutocompleteExtendedProps<T
|
|
|
35
35
|
* @param props Props
|
|
36
36
|
* @returns Component
|
|
37
37
|
*/
|
|
38
|
-
export declare function ComboBox<T extends {} =
|
|
38
|
+
export declare function ComboBox<T extends {} = DataTypes.IdLabelItem, D extends DataTypes.Keys<T> = DataTypes.Keys<T>>(props: ComboBoxProps<T, D>): JSX.Element;
|
package/lib/mu/ComboBox.js
CHANGED
|
@@ -12,7 +12,7 @@ import { ReactUtils } from '../app/ReactUtils';
|
|
|
12
12
|
*/
|
|
13
13
|
export function ComboBox(props) {
|
|
14
14
|
// Destruct
|
|
15
|
-
const { search = false, autoAddBlankItem = search, idField
|
|
15
|
+
const { search = false, autoAddBlankItem = search, idField, idValue, inputError, inputHelperText, inputMargin, inputOnChange, inputRequired, inputVariant, defaultValue, label, labelField, loadData, onLoadData, name, inputAutoComplete = 'off', options, dataReadonly = true, readOnly, onChange, openOnFocus = true, value, getOptionLabel = (option) => `${option[labelField]}`, sx = { minWidth: '150px' }, ...rest } = props;
|
|
16
16
|
// Value input ref
|
|
17
17
|
const inputRef = React.createRef();
|
|
18
18
|
// Options state
|
|
@@ -27,7 +27,7 @@ export function ComboBox(props) {
|
|
|
27
27
|
}, [JSON.stringify(options), propertyWay]);
|
|
28
28
|
// Local default value
|
|
29
29
|
let localValue = idValue != null
|
|
30
|
-
? localOptions.find((o) =>
|
|
30
|
+
? localOptions.find((o) => o[idField] === idValue)
|
|
31
31
|
: defaultValue !== null && defaultValue !== void 0 ? defaultValue : value;
|
|
32
32
|
if (localValue === undefined)
|
|
33
33
|
localValue = null;
|
|
@@ -36,7 +36,7 @@ export function ComboBox(props) {
|
|
|
36
36
|
const [stateValue, setStateValue] = React.useState(null);
|
|
37
37
|
// Current id value
|
|
38
38
|
// One time calculation for input's default value (uncontrolled)
|
|
39
|
-
const localIdValue = stateValue &&
|
|
39
|
+
const localIdValue = stateValue && stateValue[idField];
|
|
40
40
|
React.useEffect(() => {
|
|
41
41
|
if (localValue != null)
|
|
42
42
|
setStateValue(localValue);
|
|
@@ -68,7 +68,7 @@ export function ComboBox(props) {
|
|
|
68
68
|
const input = inputRef.current;
|
|
69
69
|
if (input) {
|
|
70
70
|
// Update value
|
|
71
|
-
const newValue = value != null ? `${
|
|
71
|
+
const newValue = value != null ? `${value[idField]}` : '';
|
|
72
72
|
if (newValue !== input.value) {
|
|
73
73
|
// Different value, trigger change event
|
|
74
74
|
ReactUtils.triggerChange(input, newValue, false);
|
|
@@ -97,8 +97,8 @@ export function ComboBox(props) {
|
|
|
97
97
|
}, []);
|
|
98
98
|
// Layout
|
|
99
99
|
return (React.createElement("div", null,
|
|
100
|
-
React.createElement("input", { ref: inputRef, "data-reset": "true", type: "text", style: { display: 'none' }, name: name, value: localIdValue !== null && localIdValue !== void 0 ? localIdValue : ''
|
|
101
|
-
React.createElement(Autocomplete, { value: stateValue, getOptionLabel: getOptionLabel, isOptionEqualToValue: (option, value) =>
|
|
100
|
+
React.createElement("input", { ref: inputRef, "data-reset": "true", type: "text", style: { display: 'none' }, name: name, value: `${localIdValue !== null && localIdValue !== void 0 ? localIdValue : ''}`, readOnly: true, onChange: inputOnChange }),
|
|
101
|
+
React.createElement(Autocomplete, { value: stateValue, getOptionLabel: getOptionLabel, isOptionEqualToValue: (option, value) => option[idField] === value[idField], onChange: (event, value, reason, details) => {
|
|
102
102
|
// Set value
|
|
103
103
|
setInputValue(value);
|
|
104
104
|
// Custom
|
package/lib/mu/MUGlobal.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
|
-
import { Theme } from '@mui/material';
|
|
2
|
+
import { ListItemButtonProps, Theme } from '@mui/material';
|
|
3
3
|
/**
|
|
4
4
|
* Mouse event handler with data
|
|
5
5
|
*/
|
|
@@ -43,6 +43,13 @@ export declare class MUGlobal {
|
|
|
43
43
|
xs: number;
|
|
44
44
|
sm: number;
|
|
45
45
|
};
|
|
46
|
+
/**
|
|
47
|
+
* Get menu item props
|
|
48
|
+
* @param path Current path
|
|
49
|
+
* @param href Item's href
|
|
50
|
+
* @returns Props
|
|
51
|
+
*/
|
|
52
|
+
static getMenuItem(path: string, href: string): ListItemButtonProps<"div", {}>;
|
|
46
53
|
/**
|
|
47
54
|
* Update object number properties with half of it
|
|
48
55
|
* @param input Input object
|
package/lib/mu/MUGlobal.js
CHANGED
|
@@ -1,8 +1,41 @@
|
|
|
1
1
|
import { NumberUtils } from '@etsoo/shared';
|
|
2
|
+
import { RLink } from './RLink';
|
|
2
3
|
/**
|
|
3
4
|
* MUGlobal for global configurations
|
|
4
5
|
*/
|
|
5
6
|
export class MUGlobal {
|
|
7
|
+
/**
|
|
8
|
+
* Get menu item props
|
|
9
|
+
* @param path Current path
|
|
10
|
+
* @param href Item's href
|
|
11
|
+
* @returns Props
|
|
12
|
+
*/
|
|
13
|
+
static getMenuItem(path, href) {
|
|
14
|
+
let selected = false;
|
|
15
|
+
if (path === href) {
|
|
16
|
+
// Exact match, most common case
|
|
17
|
+
selected = true;
|
|
18
|
+
}
|
|
19
|
+
else if (href.endsWith('*')) {
|
|
20
|
+
href = href.slice(0, -1);
|
|
21
|
+
selected = path.startsWith(href);
|
|
22
|
+
}
|
|
23
|
+
else if (href.endsWith('/all')) {
|
|
24
|
+
selected = path.startsWith(href.slice(0, -3));
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
component: RLink,
|
|
28
|
+
selected,
|
|
29
|
+
href,
|
|
30
|
+
sx: {
|
|
31
|
+
...(selected && {
|
|
32
|
+
'.MuiListItemIcon-root': {
|
|
33
|
+
color: (theme) => theme.palette.primary.main
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
6
39
|
/**
|
|
7
40
|
* Update object number properties with half of it
|
|
8
41
|
* @param input Input object
|
package/lib/mu/SelectBool.js
CHANGED
|
@@ -18,5 +18,5 @@ export function SelectBool(props) {
|
|
|
18
18
|
if (autoAddBlankItem)
|
|
19
19
|
Utils.addBlankItem(options);
|
|
20
20
|
// Layout
|
|
21
|
-
return React.createElement(SelectEx, { options: options, search: search, ...rest });
|
|
21
|
+
return (React.createElement(SelectEx, { options: options, search: search, ...rest }));
|
|
22
22
|
}
|
package/lib/mu/SelectEx.d.ts
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
import { SelectProps } from '@mui/material';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import { DataTypes } from '@etsoo/shared';
|
|
4
4
|
/**
|
|
5
5
|
* Extended select component props
|
|
6
6
|
*/
|
|
7
|
-
export interface SelectExProps<T extends {}> extends Omit<SelectProps, 'labelId' | 'input' | 'native'> {
|
|
7
|
+
export interface SelectExProps<T extends {}, D extends DataTypes.Keys<T> = DataTypes.Keys<T>> extends Omit<SelectProps, 'labelId' | 'input' | 'native'> {
|
|
8
8
|
/**
|
|
9
9
|
* Auto add blank item
|
|
10
10
|
*/
|
|
11
11
|
autoAddBlankItem?: boolean;
|
|
12
12
|
/**
|
|
13
|
-
* Id field
|
|
13
|
+
* Id field
|
|
14
14
|
*/
|
|
15
|
-
idField
|
|
15
|
+
idField: D;
|
|
16
16
|
/**
|
|
17
17
|
* Item icon renderer
|
|
18
18
|
*/
|
|
19
19
|
itemIconRenderer?: (id: unknown) => React.ReactNode;
|
|
20
20
|
/**
|
|
21
|
-
* Label field
|
|
21
|
+
* Label field
|
|
22
22
|
*/
|
|
23
|
-
labelField
|
|
23
|
+
labelField: ((option: T) => string) | D;
|
|
24
24
|
/**
|
|
25
25
|
* Load data callback
|
|
26
26
|
*/
|
|
@@ -47,4 +47,4 @@ export interface SelectExProps<T extends {}> extends Omit<SelectProps, 'labelId'
|
|
|
47
47
|
* @param props Props
|
|
48
48
|
* @returns Component
|
|
49
49
|
*/
|
|
50
|
-
export declare function SelectEx<T extends {} =
|
|
50
|
+
export declare function SelectEx<T extends {} = DataTypes.IdLabelItem, D extends DataTypes.Keys<T> = DataTypes.Keys<T>>(props: SelectExProps<T, D>): JSX.Element;
|
package/lib/mu/SelectEx.js
CHANGED
|
@@ -12,7 +12,7 @@ import { ReactUtils } from '../app/ReactUtils';
|
|
|
12
12
|
export function SelectEx(props) {
|
|
13
13
|
var _a;
|
|
14
14
|
// Destruct
|
|
15
|
-
const { defaultValue, idField
|
|
15
|
+
const { defaultValue, idField, itemIconRenderer, label, labelField, loadData, onItemClick, onLoadData, multiple = false, name, options = [], search = false, autoAddBlankItem = search, value, onChange, fullWidth, ...rest } = props;
|
|
16
16
|
// Options state
|
|
17
17
|
const [localOptions, setOptions] = React.useState(options);
|
|
18
18
|
const isMounted = React.useRef(true);
|
|
@@ -71,13 +71,13 @@ export function SelectEx(props) {
|
|
|
71
71
|
};
|
|
72
72
|
// Get option id
|
|
73
73
|
const getId = (option) => {
|
|
74
|
-
return
|
|
74
|
+
return option[idField];
|
|
75
75
|
};
|
|
76
76
|
// Get option label
|
|
77
77
|
const getLabel = (option) => {
|
|
78
78
|
return typeof labelField === 'function'
|
|
79
79
|
? labelField(option)
|
|
80
|
-
:
|
|
80
|
+
: new String(option[labelField]);
|
|
81
81
|
};
|
|
82
82
|
// Refs
|
|
83
83
|
const divRef = React.useRef();
|
package/lib/mu/Tiplist.d.ts
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
|
-
import { IdLabelDto } from '@etsoo/appscript';
|
|
3
2
|
import { DataTypes } from '@etsoo/shared';
|
|
4
3
|
import { AutocompleteExtendedProps } from './AutocompleteExtendedProps';
|
|
5
4
|
/**
|
|
6
5
|
* Tiplist props
|
|
7
6
|
*/
|
|
8
|
-
export interface TiplistProps<T extends {}
|
|
7
|
+
export interface TiplistProps<T extends {}, D extends DataTypes.Keys<T>> extends Omit<AutocompleteExtendedProps<T, D>, 'open'> {
|
|
9
8
|
/**
|
|
10
9
|
* Load data callback
|
|
11
10
|
*/
|
|
12
|
-
loadData: (keyword?: string, id?:
|
|
11
|
+
loadData: (keyword?: string, id?: T[D]) => PromiseLike<T[] | null | undefined>;
|
|
13
12
|
}
|
|
14
13
|
/**
|
|
15
14
|
* Tiplist
|
|
16
15
|
* @param props Props
|
|
17
16
|
* @returns Component
|
|
18
17
|
*/
|
|
19
|
-
export declare function Tiplist<T extends {}
|
|
18
|
+
export declare function Tiplist<T extends {}, D extends DataTypes.Keys<T>>(props: TiplistProps<T, D>): JSX.Element;
|
package/lib/mu/Tiplist.js
CHANGED
|
@@ -12,7 +12,7 @@ import { SearchField } from './SearchField';
|
|
|
12
12
|
*/
|
|
13
13
|
export function Tiplist(props) {
|
|
14
14
|
// Destruct
|
|
15
|
-
const { search = false, idField
|
|
15
|
+
const { search = false, idField, idValue, inputAutoComplete = 'off', inputError, inputHelperText, inputMargin, inputOnChange, inputRequired, inputVariant, label, loadData, defaultValue, value, name, readOnly, onChange, openOnFocus = true, sx = { minWidth: '180px' }, ...rest } = props;
|
|
16
16
|
// Value input ref
|
|
17
17
|
const inputRef = React.createRef();
|
|
18
18
|
// Local value
|
|
@@ -20,7 +20,7 @@ export function Tiplist(props) {
|
|
|
20
20
|
if (localValue === undefined)
|
|
21
21
|
localValue = null;
|
|
22
22
|
// One time calculation for input's default value (uncontrolled)
|
|
23
|
-
const localIdValue = idValue !== null && idValue !== void 0 ? idValue : (localValue &&
|
|
23
|
+
const localIdValue = idValue !== null && idValue !== void 0 ? idValue : (localValue && localValue[idField]);
|
|
24
24
|
// Changable states
|
|
25
25
|
const [states, stateUpdate] = React.useReducer((currentState, newState) => {
|
|
26
26
|
return { ...currentState, ...newState };
|
|
@@ -31,7 +31,7 @@ export function Tiplist(props) {
|
|
|
31
31
|
value: null
|
|
32
32
|
});
|
|
33
33
|
// Input value
|
|
34
|
-
const inputValue = React.useMemo(() => states.value &&
|
|
34
|
+
const inputValue = React.useMemo(() => states.value && states.value[idField], [states.value]);
|
|
35
35
|
React.useEffect(() => {
|
|
36
36
|
if (localValue != null)
|
|
37
37
|
stateUpdate({ value: localValue });
|
|
@@ -128,7 +128,7 @@ export function Tiplist(props) {
|
|
|
128
128
|
}, []);
|
|
129
129
|
// Layout
|
|
130
130
|
return (React.createElement("div", null,
|
|
131
|
-
React.createElement("input", { ref: inputRef, "data-reset": "true", type: "text", style: { display: 'none' }, name: name, value: inputValue !== null && inputValue !== void 0 ? inputValue : ''
|
|
131
|
+
React.createElement("input", { ref: inputRef, "data-reset": "true", type: "text", style: { display: 'none' }, name: name, value: `${inputValue !== null && inputValue !== void 0 ? inputValue : ''}`, readOnly: true, onChange: inputOnChange }),
|
|
132
132
|
React.createElement(Autocomplete, { filterOptions: (options, _state) => options, value: states.value, options: states.options, onChange: (event, value, reason, details) => {
|
|
133
133
|
// Set value
|
|
134
134
|
setInputValue(value);
|
|
@@ -150,7 +150,7 @@ export function Tiplist(props) {
|
|
|
150
150
|
if (loading)
|
|
151
151
|
loadDataDirect(undefined, states.value == null
|
|
152
152
|
? undefined
|
|
153
|
-
:
|
|
153
|
+
: states.value[idField]);
|
|
154
154
|
}, onClose: () => {
|
|
155
155
|
stateUpdate({
|
|
156
156
|
open: false,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@etsoo/react",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.76",
|
|
4
4
|
"description": "TypeScript ReactJs framework",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -51,9 +51,9 @@
|
|
|
51
51
|
"@emotion/css": "^11.10.0",
|
|
52
52
|
"@emotion/react": "^11.10.0",
|
|
53
53
|
"@emotion/styled": "^11.10.0",
|
|
54
|
-
"@etsoo/appscript": "^1.2.
|
|
54
|
+
"@etsoo/appscript": "^1.2.79",
|
|
55
55
|
"@etsoo/notificationbase": "^1.1.6",
|
|
56
|
-
"@etsoo/shared": "^1.1.
|
|
56
|
+
"@etsoo/shared": "^1.1.46",
|
|
57
57
|
"@mui/icons-material": "^5.8.4",
|
|
58
58
|
"@mui/material": "^5.10.1",
|
|
59
59
|
"@types/pica": "^9.0.1",
|
|
@@ -75,14 +75,14 @@
|
|
|
75
75
|
},
|
|
76
76
|
"devDependencies": {
|
|
77
77
|
"@babel/cli": "^7.18.10",
|
|
78
|
-
"@babel/core": "^7.18.
|
|
78
|
+
"@babel/core": "^7.18.13",
|
|
79
79
|
"@babel/plugin-transform-runtime": "^7.18.10",
|
|
80
80
|
"@babel/preset-env": "^7.18.10",
|
|
81
81
|
"@babel/runtime-corejs3": "^7.18.9",
|
|
82
82
|
"@types/jest": "^28.1.7",
|
|
83
83
|
"@types/react-test-renderer": "^18.0.0",
|
|
84
|
-
"@typescript-eslint/eslint-plugin": "^5.
|
|
85
|
-
"@typescript-eslint/parser": "^5.
|
|
84
|
+
"@typescript-eslint/eslint-plugin": "^5.34.0",
|
|
85
|
+
"@typescript-eslint/parser": "^5.34.0",
|
|
86
86
|
"eslint": "^8.22.0",
|
|
87
87
|
"eslint-config-airbnb-base": "^15.0.0",
|
|
88
88
|
"eslint-plugin-import": "^2.26.0",
|
|
@@ -5,20 +5,22 @@ import { ChangeEventHandler } from 'react';
|
|
|
5
5
|
/**
|
|
6
6
|
* Autocomplete extended props
|
|
7
7
|
*/
|
|
8
|
-
export interface AutocompleteExtendedProps<
|
|
9
|
-
extends
|
|
8
|
+
export interface AutocompleteExtendedProps<
|
|
9
|
+
T extends {},
|
|
10
|
+
D extends DataTypes.Keys<T>
|
|
11
|
+
> extends Omit<
|
|
10
12
|
AutocompleteProps<T, undefined, false, false>,
|
|
11
13
|
'renderInput' | 'options'
|
|
12
14
|
> {
|
|
13
15
|
/**
|
|
14
16
|
* Id field, default is id
|
|
15
17
|
*/
|
|
16
|
-
idField
|
|
18
|
+
idField: D;
|
|
17
19
|
|
|
18
20
|
/**
|
|
19
21
|
* Id value
|
|
20
22
|
*/
|
|
21
|
-
idValue?:
|
|
23
|
+
idValue?: T[D];
|
|
22
24
|
|
|
23
25
|
/**
|
|
24
26
|
* Autocomplete for the input
|
package/src/mu/ComboBox.tsx
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Keyboard } from '@etsoo/shared';
|
|
1
|
+
import { DataTypes, Keyboard } from '@etsoo/shared';
|
|
3
2
|
import { Autocomplete, AutocompleteRenderInputParams } from '@mui/material';
|
|
4
3
|
import React from 'react';
|
|
5
4
|
import { Utils as SharedUtils } from '@etsoo/shared';
|
|
@@ -11,8 +10,10 @@ import { ReactUtils } from '../app/ReactUtils';
|
|
|
11
10
|
/**
|
|
12
11
|
* ComboBox props
|
|
13
12
|
*/
|
|
14
|
-
export interface ComboBoxProps<
|
|
15
|
-
extends
|
|
13
|
+
export interface ComboBoxProps<
|
|
14
|
+
T extends {},
|
|
15
|
+
D extends DataTypes.Keys<T> = DataTypes.Keys<T>
|
|
16
|
+
> extends AutocompleteExtendedProps<T, D> {
|
|
16
17
|
/**
|
|
17
18
|
* Auto add blank item
|
|
18
19
|
*/
|
|
@@ -26,7 +27,7 @@ export interface ComboBoxProps<T extends {}>
|
|
|
26
27
|
/**
|
|
27
28
|
* Label field
|
|
28
29
|
*/
|
|
29
|
-
labelField
|
|
30
|
+
labelField: D;
|
|
30
31
|
|
|
31
32
|
/**
|
|
32
33
|
* Load data callback
|
|
@@ -49,12 +50,15 @@ export interface ComboBoxProps<T extends {}>
|
|
|
49
50
|
* @param props Props
|
|
50
51
|
* @returns Component
|
|
51
52
|
*/
|
|
52
|
-
export function ComboBox<
|
|
53
|
+
export function ComboBox<
|
|
54
|
+
T extends {} = DataTypes.IdLabelItem,
|
|
55
|
+
D extends DataTypes.Keys<T> = DataTypes.Keys<T>
|
|
56
|
+
>(props: ComboBoxProps<T, D>) {
|
|
53
57
|
// Destruct
|
|
54
58
|
const {
|
|
55
59
|
search = false,
|
|
56
60
|
autoAddBlankItem = search,
|
|
57
|
-
idField
|
|
61
|
+
idField,
|
|
58
62
|
idValue,
|
|
59
63
|
inputError,
|
|
60
64
|
inputHelperText,
|
|
@@ -64,7 +68,7 @@ export function ComboBox<T extends {} = IdLabelDto>(props: ComboBoxProps<T>) {
|
|
|
64
68
|
inputVariant,
|
|
65
69
|
defaultValue,
|
|
66
70
|
label,
|
|
67
|
-
labelField
|
|
71
|
+
labelField,
|
|
68
72
|
loadData,
|
|
69
73
|
onLoadData,
|
|
70
74
|
name,
|
|
@@ -75,7 +79,7 @@ export function ComboBox<T extends {} = IdLabelDto>(props: ComboBoxProps<T>) {
|
|
|
75
79
|
onChange,
|
|
76
80
|
openOnFocus = true,
|
|
77
81
|
value,
|
|
78
|
-
getOptionLabel = (option: T) =>
|
|
82
|
+
getOptionLabel = (option: T) => `${option[labelField]}`,
|
|
79
83
|
sx = { minWidth: '150px' },
|
|
80
84
|
...rest
|
|
81
85
|
} = props;
|
|
@@ -97,7 +101,7 @@ export function ComboBox<T extends {} = IdLabelDto>(props: ComboBoxProps<T>) {
|
|
|
97
101
|
// Local default value
|
|
98
102
|
let localValue =
|
|
99
103
|
idValue != null
|
|
100
|
-
? localOptions.find((o) =>
|
|
104
|
+
? localOptions.find((o) => o[idField] === idValue)
|
|
101
105
|
: defaultValue ?? value;
|
|
102
106
|
|
|
103
107
|
if (localValue === undefined) localValue = null;
|
|
@@ -108,7 +112,7 @@ export function ComboBox<T extends {} = IdLabelDto>(props: ComboBoxProps<T>) {
|
|
|
108
112
|
|
|
109
113
|
// Current id value
|
|
110
114
|
// One time calculation for input's default value (uncontrolled)
|
|
111
|
-
const localIdValue = stateValue &&
|
|
115
|
+
const localIdValue = stateValue && stateValue[idField];
|
|
112
116
|
|
|
113
117
|
React.useEffect(() => {
|
|
114
118
|
if (localValue != null) setStateValue(localValue);
|
|
@@ -147,8 +151,7 @@ export function ComboBox<T extends {} = IdLabelDto>(props: ComboBoxProps<T>) {
|
|
|
147
151
|
const input = inputRef.current;
|
|
148
152
|
if (input) {
|
|
149
153
|
// Update value
|
|
150
|
-
const newValue =
|
|
151
|
-
value != null ? `${Reflect.get(value, idField)}` : '';
|
|
154
|
+
const newValue = value != null ? `${value[idField]}` : '';
|
|
152
155
|
|
|
153
156
|
if (newValue !== input.value) {
|
|
154
157
|
// Different value, trigger change event
|
|
@@ -186,7 +189,7 @@ export function ComboBox<T extends {} = IdLabelDto>(props: ComboBoxProps<T>) {
|
|
|
186
189
|
type="text"
|
|
187
190
|
style={{ display: 'none' }}
|
|
188
191
|
name={name}
|
|
189
|
-
value={localIdValue ?? ''}
|
|
192
|
+
value={`${localIdValue ?? ''}`}
|
|
190
193
|
readOnly
|
|
191
194
|
onChange={inputOnChange}
|
|
192
195
|
/>
|
|
@@ -195,7 +198,7 @@ export function ComboBox<T extends {} = IdLabelDto>(props: ComboBoxProps<T>) {
|
|
|
195
198
|
value={stateValue}
|
|
196
199
|
getOptionLabel={getOptionLabel}
|
|
197
200
|
isOptionEqualToValue={(option: T, value: T) =>
|
|
198
|
-
|
|
201
|
+
option[idField] === value[idField]
|
|
199
202
|
}
|
|
200
203
|
onChange={(event, value, reason, details) => {
|
|
201
204
|
// Set value
|
package/src/mu/MUGlobal.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { NumberUtils } from '@etsoo/shared';
|
|
2
|
-
import { Breakpoint, Theme } from '@mui/material';
|
|
2
|
+
import { Breakpoint, ListItemButtonProps, Theme } from '@mui/material';
|
|
3
|
+
import { RLink } from './RLink';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Mouse event handler with data
|
|
@@ -53,6 +54,39 @@ export class MUGlobal {
|
|
|
53
54
|
*/
|
|
54
55
|
static pagePaddings = { xs: 2, sm: 3 };
|
|
55
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Get menu item props
|
|
59
|
+
* @param path Current path
|
|
60
|
+
* @param href Item's href
|
|
61
|
+
* @returns Props
|
|
62
|
+
*/
|
|
63
|
+
static getMenuItem(path: string, href: string) {
|
|
64
|
+
let selected = false;
|
|
65
|
+
|
|
66
|
+
if (path === href) {
|
|
67
|
+
// Exact match, most common case
|
|
68
|
+
selected = true;
|
|
69
|
+
} else if (href.endsWith('*')) {
|
|
70
|
+
href = href.slice(0, -1);
|
|
71
|
+
selected = path.startsWith(href);
|
|
72
|
+
} else if (href.endsWith('/all')) {
|
|
73
|
+
selected = path.startsWith(href.slice(0, -3));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
component: RLink,
|
|
78
|
+
selected,
|
|
79
|
+
href,
|
|
80
|
+
sx: {
|
|
81
|
+
...(selected && {
|
|
82
|
+
'.MuiListItemIcon-root': {
|
|
83
|
+
color: (theme) => theme.palette.primary.main
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
} as ListItemButtonProps;
|
|
88
|
+
}
|
|
89
|
+
|
|
56
90
|
/**
|
|
57
91
|
* Update object number properties with half of it
|
|
58
92
|
* @param input Input object
|
package/src/mu/SelectBool.tsx
CHANGED
|
@@ -28,5 +28,11 @@ export function SelectBool(props: SelectBoolProps) {
|
|
|
28
28
|
if (autoAddBlankItem) Utils.addBlankItem(options);
|
|
29
29
|
|
|
30
30
|
// Layout
|
|
31
|
-
return
|
|
31
|
+
return (
|
|
32
|
+
<SelectEx<IdLabelDto<string>>
|
|
33
|
+
options={options}
|
|
34
|
+
search={search}
|
|
35
|
+
{...rest}
|
|
36
|
+
/>
|
|
37
|
+
);
|
|
32
38
|
}
|
package/src/mu/SelectEx.tsx
CHANGED
|
@@ -11,25 +11,26 @@ import {
|
|
|
11
11
|
} from '@mui/material';
|
|
12
12
|
import React from 'react';
|
|
13
13
|
import { MUGlobal } from './MUGlobal';
|
|
14
|
-
import { IdLabelDto } from '@etsoo/appscript';
|
|
15
14
|
import { ListItemRightIcon } from './ListItemRightIcon';
|
|
16
|
-
import { Utils } from '@etsoo/shared';
|
|
15
|
+
import { DataTypes, Utils } from '@etsoo/shared';
|
|
17
16
|
import { ReactUtils } from '../app/ReactUtils';
|
|
18
17
|
|
|
19
18
|
/**
|
|
20
19
|
* Extended select component props
|
|
21
20
|
*/
|
|
22
|
-
export interface SelectExProps<
|
|
23
|
-
extends
|
|
21
|
+
export interface SelectExProps<
|
|
22
|
+
T extends {},
|
|
23
|
+
D extends DataTypes.Keys<T> = DataTypes.Keys<T>
|
|
24
|
+
> extends Omit<SelectProps, 'labelId' | 'input' | 'native'> {
|
|
24
25
|
/**
|
|
25
26
|
* Auto add blank item
|
|
26
27
|
*/
|
|
27
28
|
autoAddBlankItem?: boolean;
|
|
28
29
|
|
|
29
30
|
/**
|
|
30
|
-
* Id field
|
|
31
|
+
* Id field
|
|
31
32
|
*/
|
|
32
|
-
idField
|
|
33
|
+
idField: D;
|
|
33
34
|
|
|
34
35
|
/**
|
|
35
36
|
* Item icon renderer
|
|
@@ -37,9 +38,9 @@ export interface SelectExProps<T extends {}>
|
|
|
37
38
|
itemIconRenderer?: (id: unknown) => React.ReactNode;
|
|
38
39
|
|
|
39
40
|
/**
|
|
40
|
-
* Label field
|
|
41
|
+
* Label field
|
|
41
42
|
*/
|
|
42
|
-
labelField
|
|
43
|
+
labelField: ((option: T) => string) | D;
|
|
43
44
|
|
|
44
45
|
/**
|
|
45
46
|
* Load data callback
|
|
@@ -72,14 +73,17 @@ export interface SelectExProps<T extends {}>
|
|
|
72
73
|
* @param props Props
|
|
73
74
|
* @returns Component
|
|
74
75
|
*/
|
|
75
|
-
export function SelectEx<
|
|
76
|
+
export function SelectEx<
|
|
77
|
+
T extends {} = DataTypes.IdLabelItem,
|
|
78
|
+
D extends DataTypes.Keys<T> = DataTypes.Keys<T>
|
|
79
|
+
>(props: SelectExProps<T, D>) {
|
|
76
80
|
// Destruct
|
|
77
81
|
const {
|
|
78
82
|
defaultValue,
|
|
79
|
-
idField
|
|
83
|
+
idField,
|
|
80
84
|
itemIconRenderer,
|
|
81
85
|
label,
|
|
82
|
-
labelField
|
|
86
|
+
labelField,
|
|
83
87
|
loadData,
|
|
84
88
|
onItemClick,
|
|
85
89
|
onLoadData,
|
|
@@ -153,14 +157,14 @@ export function SelectEx<T extends {} = IdLabelDto>(props: SelectExProps<T>) {
|
|
|
153
157
|
|
|
154
158
|
// Get option id
|
|
155
159
|
const getId = (option: T) => {
|
|
156
|
-
return
|
|
160
|
+
return option[idField] as unknown as React.Key;
|
|
157
161
|
};
|
|
158
162
|
|
|
159
163
|
// Get option label
|
|
160
164
|
const getLabel = (option: T) => {
|
|
161
165
|
return typeof labelField === 'function'
|
|
162
166
|
? labelField(option)
|
|
163
|
-
:
|
|
167
|
+
: new String(option[labelField]);
|
|
164
168
|
};
|
|
165
169
|
|
|
166
170
|
// Refs
|
package/src/mu/Tiplist.tsx
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { IdLabelDto } from '@etsoo/appscript';
|
|
2
1
|
import { DataTypes } from '@etsoo/shared';
|
|
3
2
|
import { Autocomplete, AutocompleteRenderInputParams } from '@mui/material';
|
|
4
3
|
import React from 'react';
|
|
@@ -11,14 +10,14 @@ import { SearchField } from './SearchField';
|
|
|
11
10
|
/**
|
|
12
11
|
* Tiplist props
|
|
13
12
|
*/
|
|
14
|
-
export interface TiplistProps<T extends {}
|
|
15
|
-
extends Omit<AutocompleteExtendedProps<T>, 'open'> {
|
|
13
|
+
export interface TiplistProps<T extends {}, D extends DataTypes.Keys<T>>
|
|
14
|
+
extends Omit<AutocompleteExtendedProps<T, D>, 'open'> {
|
|
16
15
|
/**
|
|
17
16
|
* Load data callback
|
|
18
17
|
*/
|
|
19
18
|
loadData: (
|
|
20
19
|
keyword?: string,
|
|
21
|
-
id?:
|
|
20
|
+
id?: T[D]
|
|
22
21
|
) => PromiseLike<T[] | null | undefined>;
|
|
23
22
|
}
|
|
24
23
|
|
|
@@ -35,11 +34,13 @@ interface States<T extends {}> {
|
|
|
35
34
|
* @param props Props
|
|
36
35
|
* @returns Component
|
|
37
36
|
*/
|
|
38
|
-
export function Tiplist<T extends {}
|
|
37
|
+
export function Tiplist<T extends {}, D extends DataTypes.Keys<T>>(
|
|
38
|
+
props: TiplistProps<T, D>
|
|
39
|
+
) {
|
|
39
40
|
// Destruct
|
|
40
41
|
const {
|
|
41
42
|
search = false,
|
|
42
|
-
idField
|
|
43
|
+
idField,
|
|
43
44
|
idValue,
|
|
44
45
|
inputAutoComplete = 'off',
|
|
45
46
|
inputError,
|
|
@@ -68,8 +69,7 @@ export function Tiplist<T extends {} = IdLabelDto>(props: TiplistProps<T>) {
|
|
|
68
69
|
if (localValue === undefined) localValue = null;
|
|
69
70
|
|
|
70
71
|
// One time calculation for input's default value (uncontrolled)
|
|
71
|
-
const localIdValue =
|
|
72
|
-
idValue ?? (localValue && Reflect.get(localValue, idField));
|
|
72
|
+
const localIdValue = idValue ?? (localValue && localValue[idField]);
|
|
73
73
|
|
|
74
74
|
// Changable states
|
|
75
75
|
const [states, stateUpdate] = React.useReducer(
|
|
@@ -86,7 +86,7 @@ export function Tiplist<T extends {} = IdLabelDto>(props: TiplistProps<T>) {
|
|
|
86
86
|
|
|
87
87
|
// Input value
|
|
88
88
|
const inputValue = React.useMemo(
|
|
89
|
-
() => states.value &&
|
|
89
|
+
() => states.value && states.value[idField],
|
|
90
90
|
[states.value]
|
|
91
91
|
);
|
|
92
92
|
|
|
@@ -130,7 +130,7 @@ export function Tiplist<T extends {} = IdLabelDto>(props: TiplistProps<T>) {
|
|
|
130
130
|
};
|
|
131
131
|
|
|
132
132
|
// Directly load data
|
|
133
|
-
const loadDataDirect = (keyword?: string, id?:
|
|
133
|
+
const loadDataDirect = (keyword?: string, id?: T[D]) => {
|
|
134
134
|
// Reset options
|
|
135
135
|
// setOptions([]);
|
|
136
136
|
|
|
@@ -181,7 +181,7 @@ export function Tiplist<T extends {} = IdLabelDto>(props: TiplistProps<T>) {
|
|
|
181
181
|
}
|
|
182
182
|
};
|
|
183
183
|
|
|
184
|
-
if (localIdValue != null && localIdValue !== '') {
|
|
184
|
+
if (localIdValue != null && (localIdValue as any) !== '') {
|
|
185
185
|
if (state.idLoaded) {
|
|
186
186
|
// Set default
|
|
187
187
|
if (!state.idSet && states.options.length == 1) {
|
|
@@ -211,7 +211,7 @@ export function Tiplist<T extends {} = IdLabelDto>(props: TiplistProps<T>) {
|
|
|
211
211
|
type="text"
|
|
212
212
|
style={{ display: 'none' }}
|
|
213
213
|
name={name}
|
|
214
|
-
value={inputValue ?? ''}
|
|
214
|
+
value={`${inputValue ?? ''}`}
|
|
215
215
|
readOnly
|
|
216
216
|
onChange={inputOnChange}
|
|
217
217
|
/>
|
|
@@ -250,7 +250,7 @@ export function Tiplist<T extends {} = IdLabelDto>(props: TiplistProps<T>) {
|
|
|
250
250
|
undefined,
|
|
251
251
|
states.value == null
|
|
252
252
|
? undefined
|
|
253
|
-
:
|
|
253
|
+
: states.value[idField]
|
|
254
254
|
);
|
|
255
255
|
}}
|
|
256
256
|
onClose={() => {
|
|
@@ -291,7 +291,7 @@ export function Tiplist<T extends {} = IdLabelDto>(props: TiplistProps<T>) {
|
|
|
291
291
|
/>
|
|
292
292
|
)
|
|
293
293
|
}
|
|
294
|
-
isOptionEqualToValue={(option:
|
|
294
|
+
isOptionEqualToValue={(option: T, value: T) =>
|
|
295
295
|
option[idField] === value[idField]
|
|
296
296
|
}
|
|
297
297
|
{...rest}
|