@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/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: string, index: number) => (
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
- if (result.length >= maxItems) {
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
@@ -321,7 +321,7 @@ export function Tiplist<
321
321
  />
322
322
  )
323
323
  }
324
- isOptionEqualToValue={(option: T, value: T) =>
324
+ isOptionEqualToValue={(option, value) =>
325
325
  option[idField] === value[idField]
326
326
  }
327
327
  sx={sx}
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";