@etsoo/materialui 1.1.26 → 1.1.28

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