@etsoo/materialui 1.4.39 → 1.4.41

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.
@@ -1,6 +1,12 @@
1
1
  import React from "react";
2
2
  import { ComboBox } from "../src";
3
- import { act, fireEvent, render, screen } from "@testing-library/react";
3
+ import {
4
+ act,
5
+ fireEvent,
6
+ render,
7
+ screen,
8
+ waitFor
9
+ } from "@testing-library/react";
4
10
 
5
11
  it("Render ComboBox", async () => {
6
12
  // Arrange
@@ -21,11 +27,19 @@ it("Render ComboBox", async () => {
21
27
  );
22
28
  });
23
29
 
30
+ await vi.waitFor(
31
+ async () => {
32
+ await screen.findByRole("button");
33
+ },
34
+ {
35
+ timeout: 500, // default is 1000
36
+ interval: 20 // default is 50
37
+ }
38
+ );
39
+
24
40
  // Act, click the list
25
- act(() => {
26
- const clicked = fireEvent.click(screen.getByRole("button"));
27
- expect(clicked).toBeTruthy();
28
- });
41
+ const clicked = fireEvent.click(screen.getByRole("button"));
42
+ expect(clicked).toBeTruthy();
29
43
 
30
44
  // Get list item
31
45
  const item = screen.getByText("Name 1");
package/lib/ComboBox.js CHANGED
@@ -50,6 +50,7 @@ export function ComboBox(props) {
50
50
  }
51
51
  }, [localValue]);
52
52
  // Add readOnly
53
+ // Before AutocompleteRenderInputParams changed, impossible to remove "InputLabelProps" and "InputProps"
53
54
  const addReadOnly = (params) => {
54
55
  if (readOnly != null) {
55
56
  Object.assign(params, { readOnly });
@@ -16,6 +16,10 @@ export type InputFieldProps = TextFieldProps & {
16
16
  * Is the field read only?
17
17
  */
18
18
  readOnly?: boolean;
19
+ /**
20
+ * Minimum characters to trigger the change event
21
+ */
22
+ minChars?: number;
19
23
  };
20
24
  /**
21
25
  * Input field
@@ -35,6 +39,10 @@ export declare const InputField: React.ForwardRefExoticComponent<(Omit<import("@
35
39
  * Is the field read only?
36
40
  */
37
41
  readOnly?: boolean;
42
+ /**
43
+ * Minimum characters to trigger the change event
44
+ */
45
+ minChars?: number;
38
46
  }, "ref"> | Omit<import("@mui/material").FilledTextFieldProps & {
39
47
  /**
40
48
  * Change delay (ms) to avoid repeatly dispatch onChange
@@ -48,6 +56,10 @@ export declare const InputField: React.ForwardRefExoticComponent<(Omit<import("@
48
56
  * Is the field read only?
49
57
  */
50
58
  readOnly?: boolean;
59
+ /**
60
+ * Minimum characters to trigger the change event
61
+ */
62
+ minChars?: number;
51
63
  }, "ref"> | Omit<import("@mui/material").StandardTextFieldProps & {
52
64
  /**
53
65
  * Change delay (ms) to avoid repeatly dispatch onChange
@@ -61,4 +73,8 @@ export declare const InputField: React.ForwardRefExoticComponent<(Omit<import("@
61
73
  * Is the field read only?
62
74
  */
63
75
  readOnly?: boolean;
76
+ /**
77
+ * Minimum characters to trigger the change event
78
+ */
79
+ minChars?: number;
64
80
  }, "ref">) & React.RefAttributes<HTMLDivElement>>;
package/lib/InputField.js CHANGED
@@ -10,7 +10,7 @@ import { MUGlobal } from "./MUGlobal";
10
10
  */
11
11
  export const InputField = React.forwardRef((props, ref) => {
12
12
  // Destruct
13
- const { changeDelay, InputLabelProps = {}, InputProps = {}, onChange, onChangeDelay, readOnly, size = MUGlobal.inputFieldSize, variant = MUGlobal.inputFieldVariant, ...rest } = props;
13
+ const { changeDelay, InputLabelProps = {}, InputProps = {}, onChange, onChangeDelay, readOnly, size = MUGlobal.inputFieldSize, variant = MUGlobal.inputFieldVariant, minChars = 0, ...rest } = props;
14
14
  // Shrink
15
15
  InputLabelProps.shrink ??= MUGlobal.searchFieldShrink;
16
16
  // Read only
@@ -27,6 +27,10 @@ export const InputField = React.forwardRef((props, ref) => {
27
27
  };
28
28
  const delayed = createDelayed();
29
29
  const onChangeEx = (event) => {
30
+ // Min characters check
31
+ const len = event.target.value.length;
32
+ if (len > 0 && len < minChars)
33
+ return;
30
34
  if (onChange && (delayed == null || onChangeDelay != null))
31
35
  onChange(event);
32
36
  delayed?.call(undefined, event);
package/lib/MUUtils.js CHANGED
@@ -45,6 +45,9 @@ export var MUUtils;
45
45
  const keysets = orderBy.map((o) => Reflect.get(lastItem, o.field));
46
46
  data.queryPaging.keysets = keysets;
47
47
  }
48
+ else {
49
+ data.queryPaging.keysets = undefined;
50
+ }
48
51
  }
49
52
  return data;
50
53
  }
package/lib/SearchBar.js CHANGED
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Button, Drawer, IconButton, Stack } from "@mui/material";
3
3
  import React from "react";
4
4
  import MoreHorizIcon from "@mui/icons-material/MoreHoriz";
5
- import { DomUtils } from "@etsoo/shared";
5
+ import { DomUtils, NumberUtils } from "@etsoo/shared";
6
6
  import { ReactUtils, useDelayedExecutor, useDimensions } from "@etsoo/react";
7
7
  import { Labels } from "./app/Labels";
8
8
  // Cached width attribute name
@@ -186,6 +186,16 @@ export function SearchBar(props) {
186
186
  const moreFormChange = (event) => {
187
187
  if (event.nativeEvent.cancelable && !event.nativeEvent.composed)
188
188
  return;
189
+ if (event.target instanceof HTMLInputElement ||
190
+ event.target instanceof HTMLTextAreaElement) {
191
+ const minChars = NumberUtils.parse(event.target.dataset.minChars);
192
+ if (minChars != null && minChars > 0) {
193
+ const len = event.target.value.length;
194
+ if (len > 0 && len < minChars) {
195
+ return;
196
+ }
197
+ }
198
+ }
189
199
  if (state.moreForm == null)
190
200
  state.moreForm = event.currentTarget;
191
201
  delayed.call();
@@ -11,6 +11,10 @@ export type SearchFieldProps = TextFieldProps & {
11
11
  * Is the field read only?
12
12
  */
13
13
  readOnly?: boolean;
14
+ /**
15
+ * Minimum characters to trigger the change event
16
+ */
17
+ minChars?: number;
14
18
  };
15
19
  /**
16
20
  * Search field
@@ -10,7 +10,7 @@ import { MUGlobal } from "./MUGlobal";
10
10
  */
11
11
  export function SearchField(props) {
12
12
  // Destruct
13
- const { changeDelay, InputLabelProps = {}, InputProps = {}, onChange, readOnly, size = MUGlobal.searchFieldSize, variant = MUGlobal.searchFieldVariant, ...rest } = props;
13
+ const { changeDelay, InputLabelProps = {}, InputProps = {}, onChange, readOnly, size = MUGlobal.searchFieldSize, variant = MUGlobal.searchFieldVariant, minChars = 0, ...rest } = props;
14
14
  // Shrink
15
15
  InputLabelProps.shrink ??= MUGlobal.searchFieldShrink;
16
16
  // Read only
@@ -23,6 +23,10 @@ export function SearchField(props) {
23
23
  const onChangeEx = (event) => {
24
24
  if (onChange == null)
25
25
  return;
26
+ // Min characters check
27
+ const len = event.target.value.length;
28
+ if (len > 0 && len < minChars)
29
+ return;
26
30
  if (changeDelay == null || changeDelay < 1) {
27
31
  onChange(event);
28
32
  return;
package/lib/Tiplist.d.ts CHANGED
@@ -12,6 +12,10 @@ export type TiplistProps<T extends object, D extends DataTypes.Keys<T>> = Omit<A
12
12
  * Max items to read and display
13
13
  */
14
14
  maxItems?: number;
15
+ /**
16
+ * Minimum characters to trigger the change event
17
+ */
18
+ minChars?: number;
15
19
  /**
16
20
  * Width
17
21
  */
package/lib/Tiplist.js CHANGED
@@ -15,7 +15,7 @@ export function Tiplist(props) {
15
15
  // Labels
16
16
  const { noOptions, loading, more, open: openDefault } = globalApp?.getLabels("noOptions", "loading", "more", "open") ?? {};
17
17
  // Destruct
18
- const { search = false, idField = "id", idValue, inputAutoComplete = "off", inputError, inputHelperText, inputMargin, inputOnChange, inputRequired, inputReset, inputVariant, label, loadData, defaultValue, value, maxItems = 16, width = search ? 160 : undefined, name, readOnly, onChange, onValueChange, openOnFocus = true, noOptionsText = noOptions, loadingText = loading, openText = openDefault, getOptionLabel, getOptionDisabled, sx = {}, ...rest } = props;
18
+ const { search = false, idField = "id", idValue, inputAutoComplete = "off", inputError, inputHelperText, inputMargin, inputOnChange, inputRequired, inputReset, inputVariant, label, loadData, defaultValue, value, maxItems = 16, width = search ? 160 : undefined, name, readOnly, onChange, onValueChange, openOnFocus = true, noOptionsText = noOptions, loadingText = loading, openText = openDefault, getOptionLabel, getOptionDisabled, sx = {}, minChars, ...rest } = props;
19
19
  if (width && sx)
20
20
  Object.assign(sx, { width: `${width}px` });
21
21
  // Value input ref
@@ -189,7 +189,7 @@ export function Tiplist(props) {
189
189
  open: false,
190
190
  ...(!states.value && { options: [] })
191
191
  });
192
- }, loading: states.loading, renderInput: (params) => search ? (_jsx(SearchField, { onChange: changeHandle, ...addReadOnly(params), readOnly: readOnly, label: label, name: name + "Input", margin: inputMargin, variant: inputVariant, required: inputRequired, autoComplete: inputAutoComplete, error: inputError, helperText: inputHelperText })) : (_jsx(InputField, { onChange: changeHandle, ...addReadOnly(params), label: label, name: name + "Input", margin: inputMargin, variant: inputVariant, required: inputRequired, autoComplete: inputAutoComplete, error: inputError, helperText: inputHelperText })), isOptionEqualToValue: (option, value) => option[idField] === value[idField], sx: sx, noOptionsText: noOptionsText, loadingText: loadingText, openText: openText, getOptionDisabled: (item) => {
192
+ }, loading: states.loading, renderInput: (params) => search ? (_jsx(SearchField, { onChange: changeHandle, ...addReadOnly(params), readOnly: readOnly, label: label, name: name + "Input", margin: inputMargin, minChars: minChars, variant: inputVariant, required: inputRequired, autoComplete: inputAutoComplete, error: inputError, helperText: inputHelperText })) : (_jsx(InputField, { onChange: changeHandle, ...addReadOnly(params), label: label, name: name + "Input", margin: inputMargin, minChars: minChars, variant: inputVariant, required: inputRequired, autoComplete: inputAutoComplete, error: inputError, helperText: inputHelperText })), isOptionEqualToValue: (option, value) => option[idField] === value[idField], sx: sx, noOptionsText: noOptionsText, loadingText: loadingText, openText: openText, getOptionDisabled: (item) => {
193
193
  if (item[idField] === "n/a")
194
194
  return true;
195
195
  return getOptionDisabled ? getOptionDisabled(item) : false;
@@ -47,6 +47,10 @@ export type TiplistProProps<T extends ListType2 = ListType2> = Omit<Autocomplete
47
47
  * @param value New value
48
48
  */
49
49
  onValueChange?: (value: T | null) => void;
50
+ /**
51
+ * Minimum characters to trigger the change event
52
+ */
53
+ minChars?: number;
50
54
  };
51
55
  /**
52
56
  * TiplistPro
package/lib/TiplistPro.js CHANGED
@@ -14,7 +14,7 @@ export function TiplistPro(props) {
14
14
  // Labels
15
15
  const { noOptions, loading, more, open: openDefault } = globalApp?.getLabels("noOptions", "loading", "more", "open") ?? {};
16
16
  // Destruct
17
- const { label, loadData, defaultValue, value, idValue, maxItems = 16, width, name, inputOnChange, inputProps, inputReset, sx, openOnFocus = true, noOptionsText = noOptions, loadingText = loading, openText = openDefault, getOptionDisabled, getOptionLabel, onChange, onValueChange, ...rest } = props;
17
+ const { label, loadData, defaultValue, value, idValue, maxItems = 16, width, name, inputOnChange, inputProps, inputReset, sx, openOnFocus = true, noOptionsText = noOptions, loadingText = loading, openText = openDefault, getOptionDisabled, getOptionLabel, onChange, onValueChange, minChars, ...rest } = props;
18
18
  if (width && sx)
19
19
  Object.assign(sx, { width: `${width}px` });
20
20
  // Value input ref
@@ -181,7 +181,7 @@ export function TiplistPro(props) {
181
181
  open: false,
182
182
  ...(!states.value && { options: [] })
183
183
  });
184
- }, loading: states.loading, renderInput: (params) => (_jsx(InputField, { ...inputProps, ...params, onChange: changeHandle, label: label, name: name + "Input", onBlur: (event) => {
184
+ }, loading: states.loading, renderInput: (params) => (_jsx(InputField, { minChars: minChars, ...inputProps, ...params, onChange: changeHandle, label: label, name: name + "Input", onBlur: (event) => {
185
185
  if (states.value == null && onChange)
186
186
  onChange(event, event.target.value, "blur", undefined);
187
187
  }, "data-reset": inputReset })), isOptionEqualToValue: (option, value) => option.id === value.id, sx: sx, noOptionsText: noOptionsText, loadingText: loadingText, openText: openText, getOptionDisabled: (item) => {
@@ -39,8 +39,8 @@ export function FixedListPage(props) {
39
39
  states.data = data;
40
40
  reset();
41
41
  };
42
- const localLoadData = (props) => {
43
- return loadData(GridUtils.createLoader(props, fieldTemplate, cacheKey));
42
+ const localLoadData = (props, lastItem) => {
43
+ return loadData(GridUtils.createLoader(props, fieldTemplate, cacheKey), lastItem);
44
44
  };
45
45
  // Search data
46
46
  const searchData = GridUtils.getSearchData(cacheKey);
@@ -37,8 +37,8 @@ export function ListPage(props) {
37
37
  states.data = data;
38
38
  reset();
39
39
  };
40
- const localLoadData = (props) => {
41
- return loadData(GridUtils.createLoader(props, fieldTemplate, cacheKey));
40
+ const localLoadData = (props, lastItem) => {
41
+ return loadData(GridUtils.createLoader(props, fieldTemplate, cacheKey), lastItem);
42
42
  };
43
43
  // Search data
44
44
  const searchData = GridUtils.getSearchData(cacheKey);
@@ -36,8 +36,8 @@ export function TablePage(props) {
36
36
  states.data = data;
37
37
  reset();
38
38
  };
39
- const localLoadData = (props) => {
40
- return loadData(GridUtils.createLoader(props, fieldTemplate, cacheKey));
39
+ const localLoadData = (props, lastItem) => {
40
+ return loadData(GridUtils.createLoader(props, fieldTemplate, cacheKey), lastItem);
41
41
  };
42
42
  // Search data
43
43
  const searchData = GridUtils.getSearchData(cacheKey);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@etsoo/materialui",
3
- "version": "1.4.39",
3
+ "version": "1.4.41",
4
4
  "description": "TypeScript Material-UI Implementation",
5
5
  "main": "lib/index.js",
6
6
  "type": "module",
package/src/ComboBox.tsx CHANGED
@@ -160,6 +160,7 @@ export function ComboBox<
160
160
  }, [localValue]);
161
161
 
162
162
  // Add readOnly
163
+ // Before AutocompleteRenderInputParams changed, impossible to remove "InputLabelProps" and "InputProps"
163
164
  const addReadOnly = (params: AutocompleteRenderInputParams) => {
164
165
  if (readOnly != null) {
165
166
  Object.assign(params, { readOnly });
@@ -23,6 +23,11 @@ export type InputFieldProps = TextFieldProps & {
23
23
  * Is the field read only?
24
24
  */
25
25
  readOnly?: boolean;
26
+
27
+ /**
28
+ * Minimum characters to trigger the change event
29
+ */
30
+ minChars?: number;
26
31
  };
27
32
 
28
33
  /**
@@ -42,6 +47,7 @@ export const InputField = React.forwardRef<HTMLDivElement, InputFieldProps>(
42
47
  readOnly,
43
48
  size = MUGlobal.inputFieldSize,
44
49
  variant = MUGlobal.inputFieldVariant,
50
+ minChars = 0,
45
51
  ...rest
46
52
  } = props;
47
53
 
@@ -65,6 +71,10 @@ export const InputField = React.forwardRef<HTMLDivElement, InputFieldProps>(
65
71
  const onChangeEx = (
66
72
  event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
67
73
  ) => {
74
+ // Min characters check
75
+ const len = event.target.value.length;
76
+ if (len > 0 && len < minChars) return;
77
+
68
78
  if (onChange && (delayed == null || onChangeDelay != null))
69
79
  onChange(event);
70
80
  delayed?.call(undefined, event);
package/src/MUUtils.ts CHANGED
@@ -57,6 +57,8 @@ export namespace MUUtils {
57
57
  if (lastItem) {
58
58
  const keysets = orderBy.map((o) => Reflect.get(lastItem, o.field));
59
59
  data.queryPaging.keysets = keysets;
60
+ } else {
61
+ data.queryPaging.keysets = undefined;
60
62
  }
61
63
  }
62
64
 
package/src/SearchBar.tsx CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Button, Drawer, IconButton, Stack } from "@mui/material";
2
2
  import React from "react";
3
3
  import MoreHorizIcon from "@mui/icons-material/MoreHoriz";
4
- import { DomUtils } from "@etsoo/shared";
4
+ import { DomUtils, NumberUtils } from "@etsoo/shared";
5
5
  import { ReactUtils, useDelayedExecutor, useDimensions } from "@etsoo/react";
6
6
  import { Labels } from "./app/Labels";
7
7
 
@@ -259,6 +259,19 @@ export function SearchBar(props: SearchBarProps) {
259
259
  const moreFormChange = (event: React.FormEvent<HTMLFormElement>) => {
260
260
  if (event.nativeEvent.cancelable && !event.nativeEvent.composed) return;
261
261
 
262
+ if (
263
+ event.target instanceof HTMLInputElement ||
264
+ event.target instanceof HTMLTextAreaElement
265
+ ) {
266
+ const minChars = NumberUtils.parse(event.target.dataset.minChars);
267
+ if (minChars != null && minChars > 0) {
268
+ const len = event.target.value.length;
269
+ if (len > 0 && len < minChars) {
270
+ return;
271
+ }
272
+ }
273
+ }
274
+
262
275
  if (state.moreForm == null) state.moreForm = event.currentTarget;
263
276
 
264
277
  delayed.call();
@@ -16,6 +16,11 @@ export type SearchFieldProps = TextFieldProps & {
16
16
  * Is the field read only?
17
17
  */
18
18
  readOnly?: boolean;
19
+
20
+ /**
21
+ * Minimum characters to trigger the change event
22
+ */
23
+ minChars?: number;
19
24
  };
20
25
 
21
26
  /**
@@ -33,6 +38,7 @@ export function SearchField(props: SearchFieldProps) {
33
38
  readOnly,
34
39
  size = MUGlobal.searchFieldSize,
35
40
  variant = MUGlobal.searchFieldVariant,
41
+ minChars = 0,
36
42
  ...rest
37
43
  } = props;
38
44
 
@@ -53,6 +59,10 @@ export function SearchField(props: SearchFieldProps) {
53
59
  ) => {
54
60
  if (onChange == null) return;
55
61
 
62
+ // Min characters check
63
+ const len = event.target.value.length;
64
+ if (len > 0 && len < minChars) return;
65
+
56
66
  if (changeDelay == null || changeDelay < 1) {
57
67
  onChange(event);
58
68
  return;
package/src/Tiplist.tsx CHANGED
@@ -28,6 +28,11 @@ export type TiplistProps<T extends object, D extends DataTypes.Keys<T>> = Omit<
28
28
  */
29
29
  maxItems?: number;
30
30
 
31
+ /**
32
+ * Minimum characters to trigger the change event
33
+ */
34
+ minChars?: number;
35
+
31
36
  /**
32
37
  * Width
33
38
  */
@@ -89,6 +94,7 @@ export function Tiplist<
89
94
  getOptionLabel,
90
95
  getOptionDisabled,
91
96
  sx = {},
97
+ minChars,
92
98
  ...rest
93
99
  } = props;
94
100
 
@@ -336,6 +342,7 @@ export function Tiplist<
336
342
  label={label}
337
343
  name={name + "Input"}
338
344
  margin={inputMargin}
345
+ minChars={minChars}
339
346
  variant={inputVariant}
340
347
  required={inputRequired}
341
348
  autoComplete={inputAutoComplete}
@@ -349,6 +356,7 @@ export function Tiplist<
349
356
  label={label}
350
357
  name={name + "Input"}
351
358
  margin={inputMargin}
359
+ minChars={minChars}
352
360
  variant={inputVariant}
353
361
  required={inputRequired}
354
362
  autoComplete={inputAutoComplete}
@@ -66,6 +66,11 @@ export type TiplistProProps<T extends ListType2 = ListType2> = Omit<
66
66
  * @param value New value
67
67
  */
68
68
  onValueChange?: (value: T | null) => void;
69
+
70
+ /**
71
+ * Minimum characters to trigger the change event
72
+ */
73
+ minChars?: number;
69
74
  };
70
75
 
71
76
  // Multiple states
@@ -114,6 +119,7 @@ export function TiplistPro<T extends ListType2 = ListType2>(
114
119
  getOptionLabel,
115
120
  onChange,
116
121
  onValueChange,
122
+ minChars,
117
123
  ...rest
118
124
  } = props;
119
125
 
@@ -345,6 +351,7 @@ export function TiplistPro<T extends ListType2 = ListType2>(
345
351
  loading={states.loading}
346
352
  renderInput={(params) => (
347
353
  <InputField
354
+ minChars={minChars}
348
355
  {...inputProps}
349
356
  {...params}
350
357
  onChange={changeHandle}
@@ -88,8 +88,11 @@ export function FixedListPage<
88
88
  reset();
89
89
  };
90
90
 
91
- const localLoadData = (props: GridLoadDataProps) => {
92
- return loadData(GridUtils.createLoader<F>(props, fieldTemplate, cacheKey));
91
+ const localLoadData = (props: GridLoadDataProps, lastItem?: T) => {
92
+ return loadData(
93
+ GridUtils.createLoader<F>(props, fieldTemplate, cacheKey),
94
+ lastItem
95
+ );
93
96
  };
94
97
 
95
98
  // Search data
@@ -76,8 +76,11 @@ export function ListPage<
76
76
  reset();
77
77
  };
78
78
 
79
- const localLoadData = (props: GridLoadDataProps) => {
80
- return loadData(GridUtils.createLoader<F>(props, fieldTemplate, cacheKey));
79
+ const localLoadData = (props: GridLoadDataProps, lastItem?: T) => {
80
+ return loadData(
81
+ GridUtils.createLoader<F>(props, fieldTemplate, cacheKey),
82
+ lastItem
83
+ );
81
84
  };
82
85
 
83
86
  // Search data
@@ -83,8 +83,11 @@ export function TablePage<
83
83
  reset();
84
84
  };
85
85
 
86
- const localLoadData = (props: GridLoadDataProps) => {
87
- return loadData(GridUtils.createLoader<F>(props, fieldTemplate, cacheKey));
86
+ const localLoadData = (props: GridLoadDataProps, lastItem?: T) => {
87
+ return loadData(
88
+ GridUtils.createLoader<F>(props, fieldTemplate, cacheKey),
89
+ lastItem
90
+ );
88
91
  };
89
92
 
90
93
  // Search data