@etsoo/materialui 1.1.71 → 1.1.73
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/lib/ComboBoxPro.d.ts +34 -0
- package/lib/ComboBoxPro.js +53 -0
- package/lib/SelectEx.d.ts +4 -4
- package/lib/SelectEx.js +24 -28
- package/lib/TagList.js +19 -4
- package/lib/TagListPro.d.ts +30 -0
- package/lib/TagListPro.js +63 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +2 -0
- package/package.json +4 -4
- package/src/ComboBoxPro.tsx +139 -0
- package/src/SelectEx.tsx +359 -382
- package/src/TagList.tsx +23 -2
- package/src/TagListPro.tsx +175 -0
- package/src/Tiplist.tsx +1 -1
- package/src/index.ts +2 -0
package/src/TagList.tsx
CHANGED
|
@@ -58,7 +58,7 @@ export function TagList(props: TagListProps) {
|
|
|
58
58
|
</li>
|
|
59
59
|
),
|
|
60
60
|
renderTags = (value: readonly string[], getTagProps) =>
|
|
61
|
-
value.map((option
|
|
61
|
+
value.map((option, index) => (
|
|
62
62
|
<Chip variant="outlined" label={option} {...getTagProps({ index })} />
|
|
63
63
|
)),
|
|
64
64
|
noOptionsText = noOptions,
|
|
@@ -70,6 +70,8 @@ export function TagList(props: TagListProps) {
|
|
|
70
70
|
openOnFocus = true,
|
|
71
71
|
label,
|
|
72
72
|
inputProps,
|
|
73
|
+
onChange,
|
|
74
|
+
value,
|
|
73
75
|
...rest
|
|
74
76
|
} = props;
|
|
75
77
|
|
|
@@ -77,11 +79,24 @@ export function TagList(props: TagListProps) {
|
|
|
77
79
|
const [options, setOptions] = React.useState<readonly string[]>([]);
|
|
78
80
|
const [loading, setLoading] = React.useState(false);
|
|
79
81
|
|
|
82
|
+
const currentValue = React.useRef<readonly string[]>([]);
|
|
83
|
+
currentValue.current = value ?? [];
|
|
84
|
+
|
|
80
85
|
const loadDataLocal = async (keyword?: string) => {
|
|
81
86
|
setLoading(true);
|
|
82
87
|
const result = (await loadData(keyword, maxItems)) ?? [];
|
|
83
|
-
|
|
88
|
+
|
|
89
|
+
const len = result.length;
|
|
90
|
+
|
|
91
|
+
currentValue.current.forEach((item) => {
|
|
92
|
+
if (!result.includes(item)) result.push(item);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
if (len >= maxItems) {
|
|
84
96
|
result.push(moreLabel);
|
|
97
|
+
} else if (len === 0) {
|
|
98
|
+
// When no result, hide the popup
|
|
99
|
+
setOpen(false);
|
|
85
100
|
}
|
|
86
101
|
setOptions(result);
|
|
87
102
|
setLoading(false);
|
|
@@ -105,6 +120,7 @@ export function TagList(props: TagListProps) {
|
|
|
105
120
|
options={options}
|
|
106
121
|
loading={loading}
|
|
107
122
|
disableCloseOnSelect={disableCloseOnSelect}
|
|
123
|
+
clearOnBlur
|
|
108
124
|
openOnFocus={openOnFocus}
|
|
109
125
|
renderOption={renderOption}
|
|
110
126
|
renderTags={renderTags}
|
|
@@ -129,6 +145,11 @@ export function TagList(props: TagListProps) {
|
|
|
129
145
|
noOptionsText={noOptionsText}
|
|
130
146
|
loadingText={loadingText}
|
|
131
147
|
openText={openText}
|
|
148
|
+
value={value}
|
|
149
|
+
onChange={(event, value, reason, details) => {
|
|
150
|
+
currentValue.current = value;
|
|
151
|
+
if (onChange) onChange(event, value, reason, details);
|
|
152
|
+
}}
|
|
132
153
|
{...rest}
|
|
133
154
|
/>
|
|
134
155
|
);
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { Autocomplete, AutocompleteProps, Checkbox, Chip } from "@mui/material";
|
|
2
|
+
import CheckBoxOutlineBlankIcon from "@mui/icons-material/CheckBoxOutlineBlank";
|
|
3
|
+
import CheckBoxIcon from "@mui/icons-material/CheckBox";
|
|
4
|
+
import React from "react";
|
|
5
|
+
import { InputField, InputFieldProps } from "./InputField";
|
|
6
|
+
import { globalApp } from "./app/ReactApp";
|
|
7
|
+
|
|
8
|
+
type DataType = {
|
|
9
|
+
id: number | string;
|
|
10
|
+
} & ({ label: string } | { name: string });
|
|
11
|
+
|
|
12
|
+
export type TagListProProps<D extends DataType = DataType> = Omit<
|
|
13
|
+
AutocompleteProps<D, true, false, false>,
|
|
14
|
+
"open" | "multiple" | "options" | "renderInput"
|
|
15
|
+
> & {
|
|
16
|
+
/**
|
|
17
|
+
* Label
|
|
18
|
+
*/
|
|
19
|
+
label?: string;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Load data callback
|
|
23
|
+
*/
|
|
24
|
+
loadData: (
|
|
25
|
+
keyword: string | undefined,
|
|
26
|
+
items: number
|
|
27
|
+
) => PromiseLike<D[] | null | undefined>;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Input props
|
|
31
|
+
*/
|
|
32
|
+
inputProps?: Omit<InputFieldProps, "onChange">;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Max items
|
|
36
|
+
*/
|
|
37
|
+
maxItems?: number;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export function TagListPro<D extends DataType = DataType>(
|
|
41
|
+
props: TagListProProps<D>
|
|
42
|
+
) {
|
|
43
|
+
// Labels
|
|
44
|
+
const {
|
|
45
|
+
noOptions,
|
|
46
|
+
loading: loadingLabel,
|
|
47
|
+
more = "More",
|
|
48
|
+
open: openDefault
|
|
49
|
+
} = globalApp?.getLabels("noOptions", "loading", "more", "open") ?? {};
|
|
50
|
+
|
|
51
|
+
const moreLabel = more + "...";
|
|
52
|
+
|
|
53
|
+
const getLabel = (item: D) =>
|
|
54
|
+
"label" in item ? item.label : "name" in item ? item.name : "";
|
|
55
|
+
|
|
56
|
+
// Destruct
|
|
57
|
+
const {
|
|
58
|
+
renderOption = (props, option, { selected }) => (
|
|
59
|
+
<li {...props}>
|
|
60
|
+
<>
|
|
61
|
+
<Checkbox
|
|
62
|
+
icon={<CheckBoxOutlineBlankIcon fontSize="small" />}
|
|
63
|
+
checkedIcon={<CheckBoxIcon fontSize="small" />}
|
|
64
|
+
style={{ marginRight: 8 }}
|
|
65
|
+
checked={selected}
|
|
66
|
+
/>
|
|
67
|
+
{getLabel(option)}
|
|
68
|
+
</>
|
|
69
|
+
</li>
|
|
70
|
+
),
|
|
71
|
+
renderTags = (value: readonly D[], getTagProps) =>
|
|
72
|
+
value.map((option, index) => (
|
|
73
|
+
<Chip
|
|
74
|
+
variant="outlined"
|
|
75
|
+
label={getLabel(option)}
|
|
76
|
+
{...getTagProps({ index })}
|
|
77
|
+
/>
|
|
78
|
+
)),
|
|
79
|
+
noOptionsText = noOptions,
|
|
80
|
+
loadingText = loadingLabel,
|
|
81
|
+
openText = openDefault,
|
|
82
|
+
loadData,
|
|
83
|
+
maxItems = 16,
|
|
84
|
+
disableCloseOnSelect = true,
|
|
85
|
+
openOnFocus = true,
|
|
86
|
+
label,
|
|
87
|
+
inputProps,
|
|
88
|
+
onChange,
|
|
89
|
+
value,
|
|
90
|
+
...rest
|
|
91
|
+
} = props;
|
|
92
|
+
|
|
93
|
+
const [open, setOpen] = React.useState(false);
|
|
94
|
+
const [options, setOptions] = React.useState<readonly D[]>([]);
|
|
95
|
+
const [loading, setLoading] = React.useState(false);
|
|
96
|
+
|
|
97
|
+
const currentValue = React.useRef<readonly D[]>([]);
|
|
98
|
+
currentValue.current = value ?? [];
|
|
99
|
+
|
|
100
|
+
const loadDataLocal = async (keyword?: string) => {
|
|
101
|
+
setLoading(true);
|
|
102
|
+
const result = (await loadData(keyword, maxItems)) ?? [];
|
|
103
|
+
const len = result.length;
|
|
104
|
+
|
|
105
|
+
currentValue.current.forEach((item) => {
|
|
106
|
+
if (!result.some((r) => r.id === item.id)) result.push(item);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
if (len >= maxItems) {
|
|
110
|
+
result.push({ id: -1, name: moreLabel } as D);
|
|
111
|
+
} else if (len === 0) {
|
|
112
|
+
// When no result, hide the popup
|
|
113
|
+
setOpen(false);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
setOptions(result);
|
|
117
|
+
setLoading(false);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<Autocomplete<D, true, false, false>
|
|
122
|
+
multiple
|
|
123
|
+
filterOptions={(options, _state) => options}
|
|
124
|
+
open={open}
|
|
125
|
+
onOpen={() => {
|
|
126
|
+
setOpen(true);
|
|
127
|
+
if (options.length === 0) {
|
|
128
|
+
loadDataLocal();
|
|
129
|
+
}
|
|
130
|
+
}}
|
|
131
|
+
onClose={() => {
|
|
132
|
+
setOpen(false);
|
|
133
|
+
}}
|
|
134
|
+
options={options}
|
|
135
|
+
loading={loading}
|
|
136
|
+
disableCloseOnSelect={disableCloseOnSelect}
|
|
137
|
+
openOnFocus={openOnFocus}
|
|
138
|
+
renderOption={renderOption}
|
|
139
|
+
renderTags={renderTags}
|
|
140
|
+
renderInput={(params) => (
|
|
141
|
+
<InputField
|
|
142
|
+
label={label}
|
|
143
|
+
changeDelay={480}
|
|
144
|
+
onChange={async (event) => {
|
|
145
|
+
// Stop bubble
|
|
146
|
+
event.preventDefault();
|
|
147
|
+
event.stopPropagation();
|
|
148
|
+
|
|
149
|
+
await loadDataLocal(event.target.value);
|
|
150
|
+
}}
|
|
151
|
+
{...inputProps}
|
|
152
|
+
{...params}
|
|
153
|
+
/>
|
|
154
|
+
)}
|
|
155
|
+
getOptionDisabled={(item) => {
|
|
156
|
+
return (
|
|
157
|
+
typeof item.id === "number" &&
|
|
158
|
+
item.id < 0 &&
|
|
159
|
+
getLabel(item) === moreLabel
|
|
160
|
+
);
|
|
161
|
+
}}
|
|
162
|
+
getOptionLabel={(item) => getLabel(item)}
|
|
163
|
+
isOptionEqualToValue={(option, value) => option.id === value.id}
|
|
164
|
+
noOptionsText={noOptionsText}
|
|
165
|
+
loadingText={loadingText}
|
|
166
|
+
openText={openText}
|
|
167
|
+
value={value}
|
|
168
|
+
onChange={(event, value, reason, details) => {
|
|
169
|
+
currentValue.current = value;
|
|
170
|
+
if (onChange) onChange(event, value, reason, details);
|
|
171
|
+
}}
|
|
172
|
+
{...rest}
|
|
173
|
+
/>
|
|
174
|
+
);
|
|
175
|
+
}
|
package/src/Tiplist.tsx
CHANGED
package/src/index.ts
CHANGED
|
@@ -37,6 +37,7 @@ export * from "./BridgeCloseButton";
|
|
|
37
37
|
export * from "./ButtonLink";
|
|
38
38
|
export * from "./ComboBox";
|
|
39
39
|
export * from "./ComboBoxMultiple";
|
|
40
|
+
export * from "./ComboBoxPro";
|
|
40
41
|
export * from "./CountdownButton";
|
|
41
42
|
export * from "./CountryList";
|
|
42
43
|
export * from "./CustomFabProps";
|
|
@@ -90,6 +91,7 @@ export * from "./TableEx";
|
|
|
90
91
|
export * from "./TextFieldEx";
|
|
91
92
|
export * from "./Tiplist";
|
|
92
93
|
export * from "./TagList";
|
|
94
|
+
export * from "./TagListPro";
|
|
93
95
|
export * from "./TwoFieldInput";
|
|
94
96
|
export * from "./TooltipClick";
|
|
95
97
|
export * from "./UserAvatar";
|