@etsoo/materialui 1.1.42 → 1.1.44
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/AddresSelector.d.ts +81 -0
- package/lib/AddresSelector.js +131 -0
- package/lib/ComboBox.d.ts +1 -9
- package/lib/ComboBox.js +8 -31
- package/lib/ComboBoxMultiple.d.ts +42 -0
- package/lib/ComboBoxMultiple.js +127 -0
- package/lib/HiSelector.js +4 -4
- package/lib/HiSelectorTL.js +3 -3
- package/lib/SwitchField.js +1 -1
- package/lib/index.d.ts +2 -0
- package/lib/index.js +2 -0
- package/package.json +2 -2
- package/src/AddresSelector.tsx +307 -0
- package/src/ComboBox.tsx +12 -63
- package/src/ComboBoxMultiple.tsx +299 -0
- package/src/HiSelector.tsx +12 -10
- package/src/HiSelectorTL.tsx +11 -9
- package/src/SwitchField.tsx +2 -2
- package/src/index.ts +2 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DataTypes,
|
|
3
|
+
IdDefaultType,
|
|
4
|
+
Keyboard,
|
|
5
|
+
LabelDefaultType,
|
|
6
|
+
ListType
|
|
7
|
+
} from "@etsoo/shared";
|
|
8
|
+
import {
|
|
9
|
+
Autocomplete,
|
|
10
|
+
AutocompleteRenderInputParams,
|
|
11
|
+
Checkbox
|
|
12
|
+
} from "@mui/material";
|
|
13
|
+
import React from "react";
|
|
14
|
+
import { Utils as SharedUtils } from "@etsoo/shared";
|
|
15
|
+
import { ReactUtils } from "@etsoo/react";
|
|
16
|
+
|
|
17
|
+
import CheckBoxOutlineBlankIcon from "@mui/icons-material/CheckBoxOutlineBlank";
|
|
18
|
+
import CheckBoxIcon from "@mui/icons-material/CheckBox";
|
|
19
|
+
import { AutocompleteExtendedProps } from "./AutocompleteExtendedProps";
|
|
20
|
+
import { SearchField } from "./SearchField";
|
|
21
|
+
import { InputField } from "./InputField";
|
|
22
|
+
import { globalApp } from "./app/ReactApp";
|
|
23
|
+
|
|
24
|
+
const icon = <CheckBoxOutlineBlankIcon fontSize="small" />;
|
|
25
|
+
const checkedIcon = <CheckBoxIcon fontSize="small" />;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* ComboBox multiple props
|
|
29
|
+
*/
|
|
30
|
+
export type ComboBoxMultipleProps<
|
|
31
|
+
T extends object = ListType,
|
|
32
|
+
D extends DataTypes.Keys<T> = IdDefaultType<T>,
|
|
33
|
+
L extends DataTypes.Keys<T, string> = LabelDefaultType<T>
|
|
34
|
+
> = AutocompleteExtendedProps<T, D, true> & {
|
|
35
|
+
/**
|
|
36
|
+
* Auto add blank item
|
|
37
|
+
*/
|
|
38
|
+
autoAddBlankItem?: boolean;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Data readonly
|
|
42
|
+
*/
|
|
43
|
+
dataReadonly?: boolean;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Label field
|
|
47
|
+
*/
|
|
48
|
+
labelField?: L;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Load data callback
|
|
52
|
+
*/
|
|
53
|
+
loadData?: () => PromiseLike<T[] | null | undefined>;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* On load data handler
|
|
57
|
+
*/
|
|
58
|
+
onLoadData?: (options: T[]) => void;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Array of options.
|
|
62
|
+
*/
|
|
63
|
+
options?: ReadonlyArray<T>;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Id values
|
|
67
|
+
*/
|
|
68
|
+
idValues?: T[D][];
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* ComboBox multiple
|
|
73
|
+
* @param props Props
|
|
74
|
+
* @returns Component
|
|
75
|
+
*/
|
|
76
|
+
export function ComboBoxMultiple<
|
|
77
|
+
T extends object = ListType,
|
|
78
|
+
D extends DataTypes.Keys<T> = IdDefaultType<T>,
|
|
79
|
+
L extends DataTypes.Keys<T, string> = LabelDefaultType<T>
|
|
80
|
+
>(props: ComboBoxMultipleProps<T, D, L>) {
|
|
81
|
+
// Labels
|
|
82
|
+
const labels = globalApp?.getLabels("noOptions", "loading");
|
|
83
|
+
|
|
84
|
+
// Destruct
|
|
85
|
+
const {
|
|
86
|
+
search = false,
|
|
87
|
+
autoAddBlankItem = search,
|
|
88
|
+
idField = "id" as D,
|
|
89
|
+
idValue,
|
|
90
|
+
idValues,
|
|
91
|
+
inputError,
|
|
92
|
+
inputHelperText,
|
|
93
|
+
inputMargin,
|
|
94
|
+
inputOnChange,
|
|
95
|
+
inputRequired,
|
|
96
|
+
inputVariant,
|
|
97
|
+
defaultValue,
|
|
98
|
+
label,
|
|
99
|
+
labelField = "label" as L,
|
|
100
|
+
loadData,
|
|
101
|
+
onLoadData,
|
|
102
|
+
name,
|
|
103
|
+
inputAutoComplete = "new-password", // disable autocomplete and autofill, 'off' does not work
|
|
104
|
+
options,
|
|
105
|
+
dataReadonly = true,
|
|
106
|
+
readOnly,
|
|
107
|
+
onChange,
|
|
108
|
+
openOnFocus = true,
|
|
109
|
+
value,
|
|
110
|
+
disableCloseOnSelect = true,
|
|
111
|
+
renderOption = (props, option, { selected }) => (
|
|
112
|
+
<li {...props}>
|
|
113
|
+
<>
|
|
114
|
+
<Checkbox
|
|
115
|
+
icon={icon}
|
|
116
|
+
checkedIcon={checkedIcon}
|
|
117
|
+
style={{ marginRight: 8 }}
|
|
118
|
+
checked={selected}
|
|
119
|
+
/>
|
|
120
|
+
{option[labelField]}
|
|
121
|
+
</>
|
|
122
|
+
</li>
|
|
123
|
+
),
|
|
124
|
+
getOptionLabel = (option: T) => `${option[labelField]}`,
|
|
125
|
+
sx = { minWidth: "150px" },
|
|
126
|
+
noOptionsText = labels?.noOptions,
|
|
127
|
+
loadingText = labels?.loading,
|
|
128
|
+
...rest
|
|
129
|
+
} = props;
|
|
130
|
+
|
|
131
|
+
// Value input ref
|
|
132
|
+
const inputRef = React.createRef<HTMLInputElement>();
|
|
133
|
+
|
|
134
|
+
// Options state
|
|
135
|
+
const [localOptions, setOptions] = React.useState(options ?? []);
|
|
136
|
+
const isMounted = React.useRef(true);
|
|
137
|
+
|
|
138
|
+
// When options change
|
|
139
|
+
// [options] will cause infinite loop
|
|
140
|
+
const propertyWay = loadData == null;
|
|
141
|
+
React.useEffect(() => {
|
|
142
|
+
if (propertyWay && options != null) setOptions(options);
|
|
143
|
+
}, [options, propertyWay]);
|
|
144
|
+
|
|
145
|
+
// Local default value
|
|
146
|
+
const localValue: T | T[] | null | undefined =
|
|
147
|
+
idValue != null
|
|
148
|
+
? localOptions.filter((o) => o[idField] === idValue)
|
|
149
|
+
: idValues != null
|
|
150
|
+
? localOptions.filter((o) => idValues?.includes(o[idField]))
|
|
151
|
+
: defaultValue ?? value;
|
|
152
|
+
|
|
153
|
+
// State
|
|
154
|
+
// null for controlled
|
|
155
|
+
const [stateValue, setStateValue] = React.useState<T | T[] | null>(null);
|
|
156
|
+
|
|
157
|
+
React.useEffect(() => {
|
|
158
|
+
if (localValue != null && localValue != stateValue) {
|
|
159
|
+
setStateValue(localValue);
|
|
160
|
+
}
|
|
161
|
+
}, [localValue]);
|
|
162
|
+
|
|
163
|
+
// Add readOnly
|
|
164
|
+
const addReadOnly = (params: AutocompleteRenderInputParams) => {
|
|
165
|
+
if (readOnly != null) {
|
|
166
|
+
Object.assign(params, { readOnly });
|
|
167
|
+
|
|
168
|
+
if (readOnly) {
|
|
169
|
+
Object.assign(params.inputProps, { "data-reset": true });
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (dataReadonly) {
|
|
174
|
+
params.inputProps.onKeyDown = (event) => {
|
|
175
|
+
if (Keyboard.isTypingContent(event.key)) {
|
|
176
|
+
event.preventDefault();
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// https://stackoverflow.com/questions/15738259/disabling-chrome-autofill
|
|
182
|
+
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html
|
|
183
|
+
Object.assign(params.inputProps, { autoComplete: inputAutoComplete });
|
|
184
|
+
|
|
185
|
+
return params;
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const getValue = (value: T | T[] | null): string => {
|
|
189
|
+
if (value == null) return "";
|
|
190
|
+
if (Array.isArray(value))
|
|
191
|
+
return value.map((item) => item[idField]).join(",");
|
|
192
|
+
return `${value[idField]}`;
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const setInputValue = (value: T | T[] | null) => {
|
|
196
|
+
// Set state
|
|
197
|
+
setStateValue(value);
|
|
198
|
+
|
|
199
|
+
// Input value
|
|
200
|
+
const input = inputRef.current;
|
|
201
|
+
if (input) {
|
|
202
|
+
// Update value
|
|
203
|
+
const newValue = getValue(value);
|
|
204
|
+
|
|
205
|
+
if (newValue !== input.value) {
|
|
206
|
+
// Different value, trigger change event
|
|
207
|
+
ReactUtils.triggerChange(input, newValue, false);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
React.useEffect(() => {
|
|
213
|
+
if (propertyWay || loadData == null) return;
|
|
214
|
+
loadData().then((result) => {
|
|
215
|
+
if (result == null || !isMounted.current) return;
|
|
216
|
+
if (onLoadData) onLoadData(result);
|
|
217
|
+
if (autoAddBlankItem) {
|
|
218
|
+
SharedUtils.addBlankItem(result, idField, labelField);
|
|
219
|
+
}
|
|
220
|
+
setOptions(result);
|
|
221
|
+
});
|
|
222
|
+
}, [propertyWay, autoAddBlankItem, idField, labelField]);
|
|
223
|
+
|
|
224
|
+
React.useEffect(() => {
|
|
225
|
+
return () => {
|
|
226
|
+
isMounted.current = false;
|
|
227
|
+
};
|
|
228
|
+
}, []);
|
|
229
|
+
|
|
230
|
+
// Layout
|
|
231
|
+
return (
|
|
232
|
+
<div>
|
|
233
|
+
<input
|
|
234
|
+
ref={inputRef}
|
|
235
|
+
data-reset="true"
|
|
236
|
+
type="text"
|
|
237
|
+
style={{ display: "none" }}
|
|
238
|
+
name={name}
|
|
239
|
+
value={getValue(stateValue)}
|
|
240
|
+
readOnly
|
|
241
|
+
onChange={inputOnChange}
|
|
242
|
+
/>
|
|
243
|
+
{/* Previous input will reset first with "disableClearable = false", next input trigger change works */}
|
|
244
|
+
<Autocomplete<T, true, false, false>
|
|
245
|
+
value={
|
|
246
|
+
stateValue == null
|
|
247
|
+
? undefined
|
|
248
|
+
: Array.isArray(stateValue)
|
|
249
|
+
? stateValue
|
|
250
|
+
: [stateValue]
|
|
251
|
+
}
|
|
252
|
+
disableCloseOnSelect={disableCloseOnSelect}
|
|
253
|
+
getOptionLabel={getOptionLabel}
|
|
254
|
+
isOptionEqualToValue={(option: T, value: T) =>
|
|
255
|
+
option[idField] === value[idField]
|
|
256
|
+
}
|
|
257
|
+
onChange={(event, value, reason, details) => {
|
|
258
|
+
// Set value
|
|
259
|
+
setInputValue(value);
|
|
260
|
+
|
|
261
|
+
// Custom
|
|
262
|
+
if (onChange != null) onChange(event, value, reason, details);
|
|
263
|
+
}}
|
|
264
|
+
openOnFocus={openOnFocus}
|
|
265
|
+
sx={sx}
|
|
266
|
+
renderInput={(params) =>
|
|
267
|
+
search ? (
|
|
268
|
+
<SearchField
|
|
269
|
+
{...addReadOnly(params)}
|
|
270
|
+
label={label}
|
|
271
|
+
name={name + "Input"}
|
|
272
|
+
margin={inputMargin}
|
|
273
|
+
variant={inputVariant}
|
|
274
|
+
required={inputRequired}
|
|
275
|
+
error={inputError}
|
|
276
|
+
helperText={inputHelperText}
|
|
277
|
+
/>
|
|
278
|
+
) : (
|
|
279
|
+
<InputField
|
|
280
|
+
{...addReadOnly(params)}
|
|
281
|
+
label={label}
|
|
282
|
+
name={name + "Input"}
|
|
283
|
+
margin={inputMargin}
|
|
284
|
+
variant={inputVariant}
|
|
285
|
+
required={inputRequired}
|
|
286
|
+
error={inputError}
|
|
287
|
+
helperText={inputHelperText}
|
|
288
|
+
/>
|
|
289
|
+
)
|
|
290
|
+
}
|
|
291
|
+
options={localOptions}
|
|
292
|
+
renderOption={renderOption}
|
|
293
|
+
noOptionsText={noOptionsText}
|
|
294
|
+
loadingText={loadingText}
|
|
295
|
+
{...rest}
|
|
296
|
+
/>
|
|
297
|
+
</div>
|
|
298
|
+
);
|
|
299
|
+
}
|
package/src/HiSelector.tsx
CHANGED
|
@@ -93,7 +93,7 @@ export function HiSelector<
|
|
|
93
93
|
error,
|
|
94
94
|
helperText,
|
|
95
95
|
name,
|
|
96
|
-
label
|
|
96
|
+
label,
|
|
97
97
|
labelField = "name" as L,
|
|
98
98
|
loadData,
|
|
99
99
|
onChange,
|
|
@@ -142,15 +142,17 @@ export function HiSelector<
|
|
|
142
142
|
|
|
143
143
|
return (
|
|
144
144
|
<React.Fragment>
|
|
145
|
-
|
|
146
|
-
<
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
145
|
+
{label && (
|
|
146
|
+
<Grid item xs={12}>
|
|
147
|
+
<FormLabel
|
|
148
|
+
required={required}
|
|
149
|
+
sx={{ fontSize: (theme) => theme.typography.caption }}
|
|
150
|
+
>
|
|
151
|
+
{label}
|
|
152
|
+
</FormLabel>
|
|
153
|
+
</Grid>
|
|
154
|
+
)}
|
|
155
|
+
<input type="hidden" name={name} value={`${currentValue ?? ""}`} />
|
|
154
156
|
<Grid item xs={6} md={4} lg={3}>
|
|
155
157
|
<SelectEx<T, D, L>
|
|
156
158
|
idField={idField}
|
package/src/HiSelectorTL.tsx
CHANGED
|
@@ -142,15 +142,17 @@ export function HiSelectorTL<
|
|
|
142
142
|
|
|
143
143
|
return (
|
|
144
144
|
<React.Fragment>
|
|
145
|
-
|
|
146
|
-
<
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
145
|
+
{label && (
|
|
146
|
+
<Grid item xs={12}>
|
|
147
|
+
<FormLabel
|
|
148
|
+
required={required}
|
|
149
|
+
sx={{ fontSize: (theme) => theme.typography.caption }}
|
|
150
|
+
>
|
|
151
|
+
{label}
|
|
152
|
+
</FormLabel>
|
|
153
|
+
</Grid>
|
|
154
|
+
)}
|
|
155
|
+
<input type="hidden" name={name} value={`${currentValue ?? ""}`} />
|
|
154
156
|
<Grid item xs={6} md={4} lg={3}>
|
|
155
157
|
<Tiplist<T, D>
|
|
156
158
|
idField={idField}
|
package/src/SwitchField.tsx
CHANGED
|
@@ -120,8 +120,8 @@ export function SwitchField(props: SwitchFieldProps) {
|
|
|
120
120
|
<SwitchAnt
|
|
121
121
|
activeColor={activeColor}
|
|
122
122
|
name={name}
|
|
123
|
-
startLabel={startLabel
|
|
124
|
-
endLabel={endLabel
|
|
123
|
+
startLabel={startLabel}
|
|
124
|
+
endLabel={endLabel}
|
|
125
125
|
value={value}
|
|
126
126
|
checked={checked}
|
|
127
127
|
/>
|
package/src/index.ts
CHANGED
|
@@ -29,12 +29,14 @@ export * from "./texts/DateText";
|
|
|
29
29
|
export * from "./texts/MoneyText";
|
|
30
30
|
export * from "./texts/NumberText";
|
|
31
31
|
|
|
32
|
+
export * from "./AddresSelector";
|
|
32
33
|
export * from "./AuditDisplay";
|
|
33
34
|
export * from "./AutocompleteExtendedProps";
|
|
34
35
|
export * from "./BackButton";
|
|
35
36
|
export * from "./BridgeCloseButton";
|
|
36
37
|
export * from "./ButtonLink";
|
|
37
38
|
export * from "./ComboBox";
|
|
39
|
+
export * from "./ComboBoxMultiple";
|
|
38
40
|
export * from "./CountdownButton";
|
|
39
41
|
export * from "./CountryList";
|
|
40
42
|
export * from "./CustomFabProps";
|