@etsoo/materialui 1.1.25 → 1.1.27

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/Tiplist.tsx CHANGED
@@ -1,33 +1,34 @@
1
- import { ReactUtils, useDelayedExecutor } from '@etsoo/react';
2
- import { DataTypes, IdDefaultType, ListType } from '@etsoo/shared';
3
- import { Autocomplete, AutocompleteRenderInputParams } from '@mui/material';
4
- import React from 'react';
5
- import { AutocompleteExtendedProps } from './AutocompleteExtendedProps';
6
- import { InputField } from './InputField';
7
- import { SearchField } from './SearchField';
1
+ import { ReactUtils, useDelayedExecutor } from "@etsoo/react";
2
+ import { DataTypes, IdDefaultType, ListType } from "@etsoo/shared";
3
+ import { Autocomplete, AutocompleteRenderInputParams } from "@mui/material";
4
+ import React from "react";
5
+ import { globalApp } from "./app/ReactApp";
6
+ import { AutocompleteExtendedProps } from "./AutocompleteExtendedProps";
7
+ import { InputField } from "./InputField";
8
+ import { SearchField } from "./SearchField";
8
9
 
9
10
  /**
10
11
  * Tiplist props
11
12
  */
12
13
  export type TiplistProps<T extends object, D extends DataTypes.Keys<T>> = Omit<
13
- AutocompleteExtendedProps<T, D, undefined>,
14
- 'open' | 'multiple'
14
+ AutocompleteExtendedProps<T, D, undefined>,
15
+ "open" | "multiple"
15
16
  > & {
16
- /**
17
- * Load data callback
18
- */
19
- loadData: (
20
- keyword?: string,
21
- id?: T[D]
22
- ) => PromiseLike<T[] | null | undefined>;
17
+ /**
18
+ * Load data callback
19
+ */
20
+ loadData: (
21
+ keyword?: string,
22
+ id?: T[D]
23
+ ) => PromiseLike<T[] | null | undefined>;
23
24
  };
24
25
 
25
26
  // Multiple states
26
27
  interface States<T extends object> {
27
- open: boolean;
28
- options: T[];
29
- value?: T | null;
30
- loading?: boolean;
28
+ open: boolean;
29
+ options: T[];
30
+ value?: T | null;
31
+ loading?: boolean;
31
32
  }
32
33
 
33
34
  /**
@@ -36,268 +37,270 @@ interface States<T extends object> {
36
37
  * @returns Component
37
38
  */
38
39
  export function Tiplist<
39
- T extends object = ListType,
40
- D extends DataTypes.Keys<T> = IdDefaultType<T>
40
+ T extends object = ListType,
41
+ D extends DataTypes.Keys<T> = IdDefaultType<T>
41
42
  >(props: TiplistProps<T, D>) {
42
- // Destruct
43
- const {
44
- search = false,
45
- idField = 'id' as D,
46
- idValue,
47
- inputAutoComplete = 'new-password',
48
- inputError,
49
- inputHelperText,
50
- inputMargin,
51
- inputOnChange,
52
- inputRequired,
53
- inputVariant,
54
- label,
55
- loadData,
56
- defaultValue,
57
- value,
58
- name,
59
- readOnly,
60
- onChange,
61
- openOnFocus = true,
62
- sx = { minWidth: '180px' },
63
- ...rest
64
- } = props;
65
-
66
- // Value input ref
67
- const inputRef = React.createRef<HTMLInputElement>();
68
-
69
- // Local value
70
- let localValue = value ?? defaultValue;
71
-
72
- // One time calculation for input's default value (uncontrolled)
73
- const localIdValue =
74
- idValue ?? DataTypes.getValue(localValue, idField as any);
75
-
76
- // Changable states
77
- const [states, stateUpdate] = React.useReducer(
78
- (currentState: States<T>, newState: Partial<States<T>>) => {
79
- return { ...currentState, ...newState };
80
- },
81
- {
82
- // Loading unknown
83
- open: false,
84
- options: [],
85
- value: null
86
- }
87
- );
88
-
89
- // Input value
90
- const inputValue = React.useMemo(
91
- () => states.value && states.value[idField],
92
- [states.value]
93
- );
94
-
95
- React.useEffect(() => {
96
- if (localValue != null) stateUpdate({ value: localValue });
97
- }, [localValue]);
98
-
99
- // State
100
- const [state] = React.useState<{
101
- idLoaded?: boolean;
102
- idSet?: boolean;
103
- }>({});
104
- const isMounted = React.useRef(true);
105
-
106
- // Add readOnly
107
- const addReadOnly = (params: AutocompleteRenderInputParams) => {
108
- if (readOnly != null) {
109
- Object.assign(params, { readOnly });
110
- }
111
-
112
- // https://stackoverflow.com/questions/15738259/disabling-chrome-autofill
113
- // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html
114
- Object.assign(params.inputProps, { autoComplete: inputAutoComplete });
43
+ // Labels
44
+ const labels = globalApp?.getLabels("noOptions", "loading");
45
+
46
+ // Destruct
47
+ const {
48
+ search = false,
49
+ idField = "id" as D,
50
+ idValue,
51
+ inputAutoComplete = "new-password",
52
+ inputError,
53
+ inputHelperText,
54
+ inputMargin,
55
+ inputOnChange,
56
+ inputRequired,
57
+ inputVariant,
58
+ label,
59
+ loadData,
60
+ defaultValue,
61
+ value,
62
+ name,
63
+ readOnly,
64
+ onChange,
65
+ openOnFocus = true,
66
+ sx = { minWidth: "180px" },
67
+ noOptionsText = labels?.noOptions,
68
+ loadingText = labels?.loading,
69
+ ...rest
70
+ } = props;
71
+
72
+ // Value input ref
73
+ const inputRef = React.createRef<HTMLInputElement>();
74
+
75
+ // Local value
76
+ let localValue = value ?? defaultValue;
77
+
78
+ // One time calculation for input's default value (uncontrolled)
79
+ const localIdValue =
80
+ idValue ?? DataTypes.getValue(localValue, idField as any);
81
+
82
+ // Changable states
83
+ const [states, stateUpdate] = React.useReducer(
84
+ (currentState: States<T>, newState: Partial<States<T>>) => {
85
+ return { ...currentState, ...newState };
86
+ },
87
+ {
88
+ // Loading unknown
89
+ open: false,
90
+ options: [],
91
+ value: null
92
+ }
93
+ );
94
+
95
+ // Input value
96
+ const inputValue = React.useMemo(
97
+ () => states.value && states.value[idField],
98
+ [states.value]
99
+ );
100
+
101
+ React.useEffect(() => {
102
+ if (localValue != null) stateUpdate({ value: localValue });
103
+ }, [localValue]);
104
+
105
+ // State
106
+ const [state] = React.useState<{
107
+ idLoaded?: boolean;
108
+ idSet?: boolean;
109
+ }>({});
110
+ const isMounted = React.useRef(true);
111
+
112
+ // Add readOnly
113
+ const addReadOnly = (params: AutocompleteRenderInputParams) => {
114
+ if (readOnly != null) {
115
+ Object.assign(params, { readOnly });
116
+ }
115
117
 
116
- return params;
117
- };
118
+ // https://stackoverflow.com/questions/15738259/disabling-chrome-autofill
119
+ // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html
120
+ Object.assign(params.inputProps, { autoComplete: inputAutoComplete });
118
121
 
119
- // Change handler
120
- const changeHandle = (event: React.ChangeEvent<HTMLInputElement>) => {
121
- // Stop processing with auto trigger event
122
- if (event.nativeEvent.cancelable && !event.nativeEvent.composed) {
123
- stateUpdate({ options: [] });
124
- return;
125
- }
122
+ return params;
123
+ };
126
124
 
127
- // Stop bubble
128
- event.stopPropagation();
125
+ // Change handler
126
+ const changeHandle = (event: React.ChangeEvent<HTMLInputElement>) => {
127
+ // Stop processing with auto trigger event
128
+ if (event.nativeEvent.cancelable && !event.nativeEvent.composed) {
129
+ stateUpdate({ options: [] });
130
+ return;
131
+ }
129
132
 
130
- // Call with delay
131
- delayed.call(undefined, event.currentTarget.value);
132
- };
133
+ // Stop bubble
134
+ event.stopPropagation();
133
135
 
134
- // Directly load data
135
- const loadDataDirect = (keyword?: string, id?: T[D]) => {
136
- // Reset options
137
- // setOptions([]);
136
+ // Call with delay
137
+ delayed.call(undefined, event.currentTarget.value);
138
+ };
138
139
 
139
- if (id == null) {
140
- // Reset real value
141
- const input = inputRef.current;
140
+ // Directly load data
141
+ const loadDataDirect = (keyword?: string, id?: T[D]) => {
142
+ // Reset options
143
+ // setOptions([]);
142
144
 
143
- if (input && input.value !== '') {
144
- // Different value, trigger change event
145
- ReactUtils.triggerChange(input, '', false);
146
- }
145
+ if (id == null) {
146
+ // Reset real value
147
+ const input = inputRef.current;
147
148
 
148
- if (states.options.length > 0) {
149
- // Reset options
150
- stateUpdate({ options: [] });
151
- }
152
- }
149
+ if (input && input.value !== "") {
150
+ // Different value, trigger change event
151
+ ReactUtils.triggerChange(input, "", false);
152
+ }
153
153
 
154
- // Loading indicator
155
- if (!states.loading) stateUpdate({ loading: true });
154
+ if (states.options.length > 0) {
155
+ // Reset options
156
+ stateUpdate({ options: [] });
157
+ }
158
+ }
156
159
 
157
- // Load list
158
- loadData(keyword, id).then((options) => {
159
- if (!isMounted.current) return;
160
+ // Loading indicator
161
+ if (!states.loading) stateUpdate({ loading: true });
160
162
 
161
- // Indicates loading completed
162
- stateUpdate({
163
- loading: false,
164
- ...(options != null && { options })
165
- });
166
- });
167
- };
163
+ // Load list
164
+ loadData(keyword, id).then((options) => {
165
+ if (!isMounted.current) return;
168
166
 
169
- const delayed = useDelayedExecutor(loadDataDirect, 480);
167
+ // Indicates loading completed
168
+ stateUpdate({
169
+ loading: false,
170
+ ...(options != null && { options })
171
+ });
172
+ });
173
+ };
170
174
 
171
- const setInputValue = (value: T | null) => {
172
- stateUpdate({ value });
175
+ const delayed = useDelayedExecutor(loadDataDirect, 480);
173
176
 
174
- // Input value
175
- const input = inputRef.current;
176
- if (input) {
177
- // Update value
178
- const newValue = DataTypes.getStringValue(value, idField) ?? '';
179
- if (newValue !== input.value) {
180
- // Different value, trigger change event
181
- ReactUtils.triggerChange(input, newValue, false);
182
- }
183
- }
184
- };
177
+ const setInputValue = (value: T | null) => {
178
+ stateUpdate({ value });
185
179
 
186
- if (localIdValue != null && (localIdValue as any) !== '') {
187
- if (state.idLoaded) {
188
- // Set default
189
- if (!state.idSet && states.options.length == 1) {
190
- stateUpdate({ value: states.options[0] });
191
- state.idSet = true;
192
- }
193
- } else {
194
- // Load id data
195
- loadDataDirect(undefined, localIdValue);
196
- state.idLoaded = true;
197
- }
180
+ // Input value
181
+ const input = inputRef.current;
182
+ if (input) {
183
+ // Update value
184
+ const newValue = DataTypes.getStringValue(value, idField) ?? "";
185
+ if (newValue !== input.value) {
186
+ // Different value, trigger change event
187
+ ReactUtils.triggerChange(input, newValue, false);
188
+ }
189
+ }
190
+ };
191
+
192
+ if (localIdValue != null && (localIdValue as any) !== "") {
193
+ if (state.idLoaded) {
194
+ // Set default
195
+ if (!state.idSet && states.options.length == 1) {
196
+ stateUpdate({ value: states.options[0] });
197
+ state.idSet = true;
198
+ }
199
+ } else {
200
+ // Load id data
201
+ loadDataDirect(undefined, localIdValue);
202
+ state.idLoaded = true;
198
203
  }
204
+ }
199
205
 
200
- React.useEffect(() => {
201
- return () => {
202
- isMounted.current = false;
203
- delayed.clear();
204
- };
205
- }, []);
206
-
207
- // Layout
208
- return (
209
- <div>
210
- <input
211
- ref={inputRef}
212
- data-reset="true"
213
- type="text"
214
- style={{ display: 'none' }}
215
- name={name}
216
- value={`${inputValue ?? ''}`}
217
- readOnly
218
- onChange={inputOnChange}
206
+ React.useEffect(() => {
207
+ return () => {
208
+ isMounted.current = false;
209
+ delayed.clear();
210
+ };
211
+ }, []);
212
+
213
+ // Layout
214
+ return (
215
+ <div>
216
+ <input
217
+ ref={inputRef}
218
+ data-reset="true"
219
+ type="text"
220
+ style={{ display: "none" }}
221
+ name={name}
222
+ value={`${inputValue ?? ""}`}
223
+ readOnly
224
+ onChange={inputOnChange}
225
+ />
226
+ {/* Previous input will reset first with "disableClearable = false", next input trigger change works */}
227
+ <Autocomplete<T, undefined, false, false>
228
+ filterOptions={(options, _state) => options}
229
+ value={states.value}
230
+ options={states.options}
231
+ onChange={(event, value, reason, details) => {
232
+ // Set value
233
+ setInputValue(value);
234
+
235
+ // Custom
236
+ if (onChange != null) onChange(event, value, reason, details);
237
+
238
+ // For clear case
239
+ if (reason === "clear") {
240
+ stateUpdate({ options: [] });
241
+ loadDataDirect();
242
+ }
243
+ }}
244
+ open={states.open}
245
+ openOnFocus={openOnFocus}
246
+ onOpen={() => {
247
+ // Should load
248
+ const loading = states.loading ? true : states.options.length === 0;
249
+
250
+ stateUpdate({ open: true, loading });
251
+
252
+ // If not loading
253
+ if (loading)
254
+ loadDataDirect(
255
+ undefined,
256
+ states.value == null ? undefined : states.value[idField]
257
+ );
258
+ }}
259
+ onClose={() => {
260
+ stateUpdate({
261
+ open: false,
262
+ ...(!states.value && { options: [] })
263
+ });
264
+ }}
265
+ loading={states.loading}
266
+ sx={sx}
267
+ renderInput={(params) =>
268
+ search ? (
269
+ <SearchField
270
+ onChange={changeHandle}
271
+ {...addReadOnly(params)}
272
+ readOnly={readOnly}
273
+ label={label}
274
+ name={name + "Input"}
275
+ margin={inputMargin}
276
+ variant={inputVariant}
277
+ required={inputRequired}
278
+ autoComplete={inputAutoComplete}
279
+ error={inputError}
280
+ helperText={inputHelperText}
219
281
  />
220
- {/* Previous input will reset first with "disableClearable = false", next input trigger change works */}
221
- <Autocomplete<T, undefined, false, false>
222
- filterOptions={(options, _state) => options}
223
- value={states.value}
224
- options={states.options}
225
- onChange={(event, value, reason, details) => {
226
- // Set value
227
- setInputValue(value);
228
-
229
- // Custom
230
- if (onChange != null)
231
- onChange(event, value, reason, details);
232
-
233
- // For clear case
234
- if (reason === 'clear') {
235
- stateUpdate({ options: [] });
236
- loadDataDirect();
237
- }
238
- }}
239
- open={states.open}
240
- openOnFocus={openOnFocus}
241
- onOpen={() => {
242
- // Should load
243
- const loading = states.loading
244
- ? true
245
- : states.options.length === 0;
246
-
247
- stateUpdate({ open: true, loading });
248
-
249
- // If not loading
250
- if (loading)
251
- loadDataDirect(
252
- undefined,
253
- states.value == null
254
- ? undefined
255
- : states.value[idField]
256
- );
257
- }}
258
- onClose={() => {
259
- stateUpdate({
260
- open: false,
261
- ...(!states.value && { options: [] })
262
- });
263
- }}
264
- loading={states.loading}
265
- sx={sx}
266
- renderInput={(params) =>
267
- search ? (
268
- <SearchField
269
- onChange={changeHandle}
270
- {...addReadOnly(params)}
271
- readOnly={readOnly}
272
- label={label}
273
- name={name + 'Input'}
274
- margin={inputMargin}
275
- variant={inputVariant}
276
- required={inputRequired}
277
- autoComplete={inputAutoComplete}
278
- error={inputError}
279
- helperText={inputHelperText}
280
- />
281
- ) : (
282
- <InputField
283
- onChange={changeHandle}
284
- {...addReadOnly(params)}
285
- label={label}
286
- name={name + 'Input'}
287
- margin={inputMargin}
288
- variant={inputVariant}
289
- required={inputRequired}
290
- autoComplete={inputAutoComplete}
291
- error={inputError}
292
- helperText={inputHelperText}
293
- />
294
- )
295
- }
296
- isOptionEqualToValue={(option: T, value: T) =>
297
- option[idField] === value[idField]
298
- }
299
- {...rest}
282
+ ) : (
283
+ <InputField
284
+ onChange={changeHandle}
285
+ {...addReadOnly(params)}
286
+ label={label}
287
+ name={name + "Input"}
288
+ margin={inputMargin}
289
+ variant={inputVariant}
290
+ required={inputRequired}
291
+ autoComplete={inputAutoComplete}
292
+ error={inputError}
293
+ helperText={inputHelperText}
300
294
  />
301
- </div>
302
- );
295
+ )
296
+ }
297
+ isOptionEqualToValue={(option: T, value: T) =>
298
+ option[idField] === value[idField]
299
+ }
300
+ noOptionsText={noOptionsText}
301
+ loadingText={loadingText}
302
+ {...rest}
303
+ />
304
+ </div>
305
+ );
303
306
  }