@etsoo/materialui 1.6.2 → 1.6.4

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.
@@ -13,14 +13,25 @@ const InputField_1 = require("./InputField");
13
13
  const Box_1 = __importDefault(require("@mui/material/Box"));
14
14
  const InputAdornment_1 = __importDefault(require("@mui/material/InputAdornment"));
15
15
  const IconButton_1 = __importDefault(require("@mui/material/IconButton"));
16
+ const react_2 = require("@etsoo/react");
16
17
  /**
17
18
  * Integer input field (controlled)
18
19
  */
19
20
  function IntInputField(props) {
20
21
  // Destruct
21
- const { min = 0, step = 1, max = 9999999, inputStyle = { textAlign: "right" }, boxProps, buttons, endSymbol, symbol, value, changeDelay = [600], onChangeDelay, onChange, onFocus = (event) => event.currentTarget.select(), onValueChange, required, ...rest } = props;
22
+ const { min = 0, step = 1, max = 9999999, inputRef, inputStyle = { textAlign: "right" }, boxProps, buttons, endSymbol, symbol, value, changeDelay = [600], onChangeDelay, onChange, onFocus = (event) => event.currentTarget.select(), onValueChange, required, ...rest } = props;
23
+ const isControlled = value !== undefined;
22
24
  // State
23
- const [localValue, setLocalValue] = react_1.default.useState();
25
+ const [localValue, setInnerLocalValue] = react_1.default.useState();
26
+ const localRef = react_1.default.useRef(null);
27
+ function setLocalValue(value) {
28
+ if (isControlled) {
29
+ setInnerLocalValue(value);
30
+ }
31
+ else if (localRef.current) {
32
+ localRef.current.value = value?.toString() ?? "";
33
+ }
34
+ }
24
35
  const setValue = (value, source, init = false) => {
25
36
  if (onValueChange) {
26
37
  const newValue = onValueChange(value, source, init);
@@ -40,10 +51,17 @@ function IntInputField(props) {
40
51
  }
41
52
  };
42
53
  react_1.default.useEffect(() => {
43
- setValue(value, undefined, true);
44
- }, [value]);
54
+ if (isControlled)
55
+ setValue(value, undefined, true);
56
+ }, [value, isControlled]);
45
57
  // Layout
46
- const layout = ((0, jsx_runtime_1.jsx)(InputField_1.InputField, { type: "number", value: localValue == null ? (required ? min : "") : localValue, slotProps: {
58
+ const layout = ((0, jsx_runtime_1.jsx)(InputField_1.InputField, { type: "number", inputRef: (0, react_2.useCombinedRefs)(inputRef, localRef), value: isControlled
59
+ ? localValue == null
60
+ ? required
61
+ ? min
62
+ : ""
63
+ : localValue
64
+ : undefined, slotProps: {
47
65
  input: {
48
66
  startAdornment: symbol ? ((0, jsx_runtime_1.jsx)(react_1.default.Fragment, { children: (0, jsx_runtime_1.jsx)(InputAdornment_1.default, { position: "start", children: symbol }) })) : undefined,
49
67
  endAdornment: endSymbol ? ((0, jsx_runtime_1.jsx)(InputAdornment_1.default, { position: "end", children: endSymbol })) : undefined
@@ -53,6 +53,7 @@ function TagList(props) {
53
53
  loadDataLocal();
54
54
  }
55
55
  }, onClose: () => {
56
+ setOptions([]);
56
57
  setOpen(false);
57
58
  }, options: options, loading: loading, disableCloseOnSelect: disableCloseOnSelect, clearOnBlur: true, openOnFocus: openOnFocus, renderOption: renderOption, renderValue: renderValue, renderInput: (params) => ((0, jsx_runtime_1.jsx)(InputField_1.InputField, { label: label, onChangeDelay: async (event) => {
58
59
  // Stop bubble
@@ -10,6 +10,10 @@ export type TagListProProps<D extends ListType2 = ListType2> = Omit<Autocomplete
10
10
  * Load data callback
11
11
  */
12
12
  loadData: (keyword: string | undefined, items: number) => PromiseLike<D[] | null | undefined>;
13
+ /**
14
+ * Load value from ids
15
+ */
16
+ loadIdValue?: () => PromiseLike<D[] | null | undefined>;
13
17
  /**
14
18
  * Input props
15
19
  */
@@ -25,12 +25,13 @@ function TagListPro(props) {
25
25
  const { getOptionKey = (option) => typeof option === "string" ? option : option.id, renderOption = ({ key, ...props }, option, { selected }) => ((0, jsx_runtime_1.jsx)("li", { ...props, children: (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(Checkbox_1.default, { icon: (0, jsx_runtime_1.jsx)(CheckBoxOutlineBlank_1.default, { fontSize: "small" }), checkedIcon: (0, jsx_runtime_1.jsx)(CheckBox_1.default, { fontSize: "small" }), style: { marginRight: 8 }, checked: selected }), getLabel(option)] }) }, key)), renderValue = (value, getTagProps) => value.map((option, index) => {
26
26
  const { key, ...rest } = getTagProps({ index });
27
27
  return ((0, jsx_runtime_1.jsx)(Chip_1.default, { variant: "outlined", label: getLabel(option), ...rest }, key));
28
- }), noOptionsText = noOptions, loadingText = loadingLabel, openText = openDefault, loadData, maxItems = 16, disableCloseOnSelect = true, openOnFocus = true, label, inputProps, onChange, value, ...rest } = props;
28
+ }), noOptionsText = noOptions, loadingText = loadingLabel, openText = openDefault, loadData, loadIdValue, maxItems = 16, disableCloseOnSelect = true, openOnFocus = true, label, inputProps, onChange, value, ...rest } = props;
29
29
  const [open, setOpen] = react_1.default.useState(false);
30
30
  const [options, setOptions] = react_1.default.useState([]);
31
31
  const [loading, setLoading] = react_1.default.useState(false);
32
+ const [valueState, setValueState] = react_1.default.useState(value ?? []);
32
33
  const currentValue = react_1.default.useRef([]);
33
- currentValue.current = value ?? [];
34
+ currentValue.current = valueState;
34
35
  const loadDataLocal = async (keyword) => {
35
36
  setLoading(true);
36
37
  const result = (await loadData(keyword, maxItems)) ?? [];
@@ -49,12 +50,22 @@ function TagListPro(props) {
49
50
  setOptions(result);
50
51
  setLoading(false);
51
52
  };
53
+ react_1.default.useEffect(() => {
54
+ if (loadIdValue) {
55
+ loadIdValue().then((result) => {
56
+ if (result == null)
57
+ return;
58
+ setValueState(result);
59
+ });
60
+ }
61
+ }, [loadIdValue]);
52
62
  return ((0, jsx_runtime_1.jsx)(Autocomplete_1.default, { multiple: true, filterOptions: (options, _state) => options, open: open, onOpen: () => {
53
63
  setOpen(true);
54
64
  if (options.length === 0) {
55
65
  loadDataLocal();
56
66
  }
57
67
  }, onClose: () => {
68
+ setOptions([]);
58
69
  setOpen(false);
59
70
  }, options: options, loading: loading, disableCloseOnSelect: disableCloseOnSelect, openOnFocus: openOnFocus, renderOption: renderOption, renderValue: renderValue, renderInput: (params) => ((0, jsx_runtime_1.jsx)(InputField_1.InputField, { label: label, onChangeDelay: async (event) => {
60
71
  // Stop bubble
@@ -65,7 +76,7 @@ function TagListPro(props) {
65
76
  return (typeof item.id === "number" &&
66
77
  item.id < 0 &&
67
78
  getLabel(item) === moreLabel);
68
- }, getOptionLabel: (item) => getLabel(item), getOptionKey: getOptionKey, isOptionEqualToValue: (option, value) => option.id === value.id, noOptionsText: noOptionsText, loadingText: loadingText, openText: openText, value: value, onChange: (event, value, reason, details) => {
79
+ }, getOptionLabel: (item) => getLabel(item), getOptionKey: getOptionKey, isOptionEqualToValue: (option, value) => option.id === value.id, noOptionsText: noOptionsText, loadingText: loadingText, openText: openText, value: valueState, onChange: (event, value, reason, details) => {
69
80
  currentValue.current = value;
70
81
  if (onChange)
71
82
  onChange(event, value, reason, details);
@@ -7,14 +7,25 @@ import { InputField } from "./InputField";
7
7
  import Box from "@mui/material/Box";
8
8
  import InputAdornment from "@mui/material/InputAdornment";
9
9
  import IconButton from "@mui/material/IconButton";
10
+ import { useCombinedRefs } from "@etsoo/react";
10
11
  /**
11
12
  * Integer input field (controlled)
12
13
  */
13
14
  export function IntInputField(props) {
14
15
  // Destruct
15
- const { min = 0, step = 1, max = 9999999, inputStyle = { textAlign: "right" }, boxProps, buttons, endSymbol, symbol, value, changeDelay = [600], onChangeDelay, onChange, onFocus = (event) => event.currentTarget.select(), onValueChange, required, ...rest } = props;
16
+ const { min = 0, step = 1, max = 9999999, inputRef, inputStyle = { textAlign: "right" }, boxProps, buttons, endSymbol, symbol, value, changeDelay = [600], onChangeDelay, onChange, onFocus = (event) => event.currentTarget.select(), onValueChange, required, ...rest } = props;
17
+ const isControlled = value !== undefined;
16
18
  // State
17
- const [localValue, setLocalValue] = React.useState();
19
+ const [localValue, setInnerLocalValue] = React.useState();
20
+ const localRef = React.useRef(null);
21
+ function setLocalValue(value) {
22
+ if (isControlled) {
23
+ setInnerLocalValue(value);
24
+ }
25
+ else if (localRef.current) {
26
+ localRef.current.value = value?.toString() ?? "";
27
+ }
28
+ }
18
29
  const setValue = (value, source, init = false) => {
19
30
  if (onValueChange) {
20
31
  const newValue = onValueChange(value, source, init);
@@ -34,10 +45,17 @@ export function IntInputField(props) {
34
45
  }
35
46
  };
36
47
  React.useEffect(() => {
37
- setValue(value, undefined, true);
38
- }, [value]);
48
+ if (isControlled)
49
+ setValue(value, undefined, true);
50
+ }, [value, isControlled]);
39
51
  // Layout
40
- const layout = (_jsx(InputField, { type: "number", value: localValue == null ? (required ? min : "") : localValue, slotProps: {
52
+ const layout = (_jsx(InputField, { type: "number", inputRef: useCombinedRefs(inputRef, localRef), value: isControlled
53
+ ? localValue == null
54
+ ? required
55
+ ? min
56
+ : ""
57
+ : localValue
58
+ : undefined, slotProps: {
41
59
  input: {
42
60
  startAdornment: symbol ? (_jsx(React.Fragment, { children: _jsx(InputAdornment, { position: "start", children: symbol }) })) : undefined,
43
61
  endAdornment: endSymbol ? (_jsx(InputAdornment, { position: "end", children: endSymbol })) : undefined
@@ -47,6 +47,7 @@ export function TagList(props) {
47
47
  loadDataLocal();
48
48
  }
49
49
  }, onClose: () => {
50
+ setOptions([]);
50
51
  setOpen(false);
51
52
  }, options: options, loading: loading, disableCloseOnSelect: disableCloseOnSelect, clearOnBlur: true, openOnFocus: openOnFocus, renderOption: renderOption, renderValue: renderValue, renderInput: (params) => (_jsx(InputField, { label: label, onChangeDelay: async (event) => {
52
53
  // Stop bubble
@@ -10,6 +10,10 @@ export type TagListProProps<D extends ListType2 = ListType2> = Omit<Autocomplete
10
10
  * Load data callback
11
11
  */
12
12
  loadData: (keyword: string | undefined, items: number) => PromiseLike<D[] | null | undefined>;
13
+ /**
14
+ * Load value from ids
15
+ */
16
+ loadIdValue?: () => PromiseLike<D[] | null | undefined>;
13
17
  /**
14
18
  * Input props
15
19
  */
@@ -19,12 +19,13 @@ export function TagListPro(props) {
19
19
  const { getOptionKey = (option) => typeof option === "string" ? option : option.id, renderOption = ({ key, ...props }, option, { selected }) => (_jsx("li", { ...props, children: _jsxs(_Fragment, { children: [_jsx(Checkbox, { icon: _jsx(CheckBoxOutlineBlankIcon, { fontSize: "small" }), checkedIcon: _jsx(CheckBoxIcon, { fontSize: "small" }), style: { marginRight: 8 }, checked: selected }), getLabel(option)] }) }, key)), renderValue = (value, getTagProps) => value.map((option, index) => {
20
20
  const { key, ...rest } = getTagProps({ index });
21
21
  return (_jsx(Chip, { variant: "outlined", label: getLabel(option), ...rest }, key));
22
- }), noOptionsText = noOptions, loadingText = loadingLabel, openText = openDefault, loadData, maxItems = 16, disableCloseOnSelect = true, openOnFocus = true, label, inputProps, onChange, value, ...rest } = props;
22
+ }), noOptionsText = noOptions, loadingText = loadingLabel, openText = openDefault, loadData, loadIdValue, maxItems = 16, disableCloseOnSelect = true, openOnFocus = true, label, inputProps, onChange, value, ...rest } = props;
23
23
  const [open, setOpen] = React.useState(false);
24
24
  const [options, setOptions] = React.useState([]);
25
25
  const [loading, setLoading] = React.useState(false);
26
+ const [valueState, setValueState] = React.useState(value ?? []);
26
27
  const currentValue = React.useRef([]);
27
- currentValue.current = value ?? [];
28
+ currentValue.current = valueState;
28
29
  const loadDataLocal = async (keyword) => {
29
30
  setLoading(true);
30
31
  const result = (await loadData(keyword, maxItems)) ?? [];
@@ -43,12 +44,22 @@ export function TagListPro(props) {
43
44
  setOptions(result);
44
45
  setLoading(false);
45
46
  };
47
+ React.useEffect(() => {
48
+ if (loadIdValue) {
49
+ loadIdValue().then((result) => {
50
+ if (result == null)
51
+ return;
52
+ setValueState(result);
53
+ });
54
+ }
55
+ }, [loadIdValue]);
46
56
  return (_jsx(Autocomplete, { multiple: true, filterOptions: (options, _state) => options, open: open, onOpen: () => {
47
57
  setOpen(true);
48
58
  if (options.length === 0) {
49
59
  loadDataLocal();
50
60
  }
51
61
  }, onClose: () => {
62
+ setOptions([]);
52
63
  setOpen(false);
53
64
  }, options: options, loading: loading, disableCloseOnSelect: disableCloseOnSelect, openOnFocus: openOnFocus, renderOption: renderOption, renderValue: renderValue, renderInput: (params) => (_jsx(InputField, { label: label, onChangeDelay: async (event) => {
54
65
  // Stop bubble
@@ -59,7 +70,7 @@ export function TagListPro(props) {
59
70
  return (typeof item.id === "number" &&
60
71
  item.id < 0 &&
61
72
  getLabel(item) === moreLabel);
62
- }, getOptionLabel: (item) => getLabel(item), getOptionKey: getOptionKey, isOptionEqualToValue: (option, value) => option.id === value.id, noOptionsText: noOptionsText, loadingText: loadingText, openText: openText, value: value, onChange: (event, value, reason, details) => {
73
+ }, getOptionLabel: (item) => getLabel(item), getOptionKey: getOptionKey, isOptionEqualToValue: (option, value) => option.id === value.id, noOptionsText: noOptionsText, loadingText: loadingText, openText: openText, value: valueState, onChange: (event, value, reason, details) => {
63
74
  currentValue.current = value;
64
75
  if (onChange)
65
76
  onChange(event, value, reason, details);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@etsoo/materialui",
3
- "version": "1.6.2",
3
+ "version": "1.6.4",
4
4
  "description": "TypeScript Material-UI Implementation",
5
5
  "main": "lib/cjs/index.js",
6
6
  "module": "lib/mjs/index.js",
@@ -40,13 +40,13 @@
40
40
  "@dnd-kit/sortable": "^10.0.0",
41
41
  "@emotion/react": "^11.14.0",
42
42
  "@emotion/styled": "^11.14.1",
43
- "@etsoo/appscript": "^1.6.52",
43
+ "@etsoo/appscript": "^1.6.53",
44
44
  "@etsoo/notificationbase": "^1.1.66",
45
- "@etsoo/react": "^1.8.69",
45
+ "@etsoo/react": "^1.8.71",
46
46
  "@etsoo/shared": "^1.2.80",
47
47
  "@mui/icons-material": "^7.3.7",
48
48
  "@mui/material": "^7.3.7",
49
- "@mui/x-data-grid": "^8.24.0",
49
+ "@mui/x-data-grid": "^8.25.0",
50
50
  "chart.js": "^4.5.1",
51
51
  "chartjs-plugin-datalabels": "^2.2.0",
52
52
  "dompurify": "^3.3.1",
@@ -65,23 +65,23 @@
65
65
  "react-dom": "$react-dom"
66
66
  },
67
67
  "devDependencies": {
68
- "@babel/cli": "^7.28.3",
69
- "@babel/core": "^7.28.5",
68
+ "@babel/cli": "^7.28.6",
69
+ "@babel/core": "^7.28.6",
70
70
  "@babel/plugin-transform-runtime": "^7.28.5",
71
- "@babel/preset-env": "^7.28.5",
71
+ "@babel/preset-env": "^7.28.6",
72
72
  "@babel/preset-react": "^7.28.5",
73
73
  "@babel/preset-typescript": "^7.28.5",
74
- "@babel/runtime-corejs3": "^7.28.4",
74
+ "@babel/runtime-corejs3": "^7.28.6",
75
75
  "@testing-library/react": "^16.3.1",
76
76
  "@types/pica": "^9.0.5",
77
77
  "@types/pulltorefreshjs": "^0.1.7",
78
- "@types/react": "^19.2.7",
78
+ "@types/react": "^19.2.8",
79
79
  "@types/react-avatar-editor": "^13.0.4",
80
80
  "@types/react-dom": "^19.2.3",
81
81
  "@types/react-input-mask": "^3.0.6",
82
82
  "@vitejs/plugin-react": "^5.1.2",
83
83
  "jsdom": "^27.4.0",
84
84
  "typescript": "^5.9.3",
85
- "vitest": "^4.0.16"
85
+ "vitest": "^4.0.17"
86
86
  }
87
87
  }
@@ -6,6 +6,7 @@ import { InputField, InputFieldProps } from "./InputField";
6
6
  import Box, { BoxProps } from "@mui/material/Box";
7
7
  import InputAdornment from "@mui/material/InputAdornment";
8
8
  import IconButton from "@mui/material/IconButton";
9
+ import { useCombinedRefs } from "@etsoo/react";
9
10
 
10
11
  /**
11
12
  * Integer input field props
@@ -81,6 +82,7 @@ export function IntInputField(props: IntInputFieldProps) {
81
82
  min = 0,
82
83
  step = 1,
83
84
  max = 9999999,
85
+ inputRef,
84
86
  inputStyle = { textAlign: "right" },
85
87
  boxProps,
86
88
  buttons,
@@ -96,8 +98,20 @@ export function IntInputField(props: IntInputFieldProps) {
96
98
  ...rest
97
99
  } = props;
98
100
 
101
+ const isControlled = value !== undefined;
102
+
99
103
  // State
100
- const [localValue, setLocalValue] = React.useState<number | string>();
104
+ const [localValue, setInnerLocalValue] = React.useState<number | string>();
105
+
106
+ const localRef = React.useRef<HTMLInputElement>(null);
107
+
108
+ function setLocalValue(value: number | string | undefined) {
109
+ if (isControlled) {
110
+ setInnerLocalValue(value);
111
+ } else if (localRef.current) {
112
+ localRef.current.value = value?.toString() ?? "";
113
+ }
114
+ }
101
115
 
102
116
  const setValue = (
103
117
  value: number | undefined,
@@ -121,14 +135,23 @@ export function IntInputField(props: IntInputFieldProps) {
121
135
  };
122
136
 
123
137
  React.useEffect(() => {
124
- setValue(value, undefined, true);
125
- }, [value]);
138
+ if (isControlled) setValue(value, undefined, true);
139
+ }, [value, isControlled]);
126
140
 
127
141
  // Layout
128
142
  const layout = (
129
143
  <InputField
130
144
  type="number"
131
- value={localValue == null ? (required ? min : "") : localValue}
145
+ inputRef={useCombinedRefs(inputRef, localRef)}
146
+ value={
147
+ isControlled
148
+ ? localValue == null
149
+ ? required
150
+ ? min
151
+ : ""
152
+ : localValue
153
+ : undefined
154
+ }
132
155
  slotProps={{
133
156
  input: {
134
157
  startAdornment: symbol ? (
package/src/TagList.tsx CHANGED
@@ -121,6 +121,7 @@ export function TagList(props: TagListProps) {
121
121
  }
122
122
  }}
123
123
  onClose={() => {
124
+ setOptions([]);
124
125
  setOpen(false);
125
126
  }}
126
127
  options={options}
@@ -25,6 +25,11 @@ export type TagListProProps<D extends ListType2 = ListType2> = Omit<
25
25
  items: number
26
26
  ) => PromiseLike<D[] | null | undefined>;
27
27
 
28
+ /**
29
+ * Load value from ids
30
+ */
31
+ loadIdValue?: () => PromiseLike<D[] | null | undefined>;
32
+
28
33
  /**
29
34
  * Input props
30
35
  */
@@ -87,6 +92,7 @@ export function TagListPro<D extends ListType2 = ListType2>(
87
92
  loadingText = loadingLabel,
88
93
  openText = openDefault,
89
94
  loadData,
95
+ loadIdValue,
90
96
  maxItems = 16,
91
97
  disableCloseOnSelect = true,
92
98
  openOnFocus = true,
@@ -100,9 +106,10 @@ export function TagListPro<D extends ListType2 = ListType2>(
100
106
  const [open, setOpen] = React.useState(false);
101
107
  const [options, setOptions] = React.useState<readonly D[]>([]);
102
108
  const [loading, setLoading] = React.useState(false);
109
+ const [valueState, setValueState] = React.useState<D[]>(value ?? []);
103
110
 
104
111
  const currentValue = React.useRef<readonly D[]>([]);
105
- currentValue.current = value ?? [];
112
+ currentValue.current = valueState;
106
113
 
107
114
  const loadDataLocal = async (keyword?: string) => {
108
115
  setLoading(true);
@@ -124,6 +131,15 @@ export function TagListPro<D extends ListType2 = ListType2>(
124
131
  setLoading(false);
125
132
  };
126
133
 
134
+ React.useEffect(() => {
135
+ if (loadIdValue) {
136
+ loadIdValue().then((result) => {
137
+ if (result == null) return;
138
+ setValueState(result);
139
+ });
140
+ }
141
+ }, [loadIdValue]);
142
+
127
143
  return (
128
144
  <Autocomplete<D, true, false, false>
129
145
  multiple
@@ -136,6 +152,7 @@ export function TagListPro<D extends ListType2 = ListType2>(
136
152
  }
137
153
  }}
138
154
  onClose={() => {
155
+ setOptions([]);
139
156
  setOpen(false);
140
157
  }}
141
158
  options={options}
@@ -171,7 +188,7 @@ export function TagListPro<D extends ListType2 = ListType2>(
171
188
  noOptionsText={noOptionsText}
172
189
  loadingText={loadingText}
173
190
  openText={openText}
174
- value={value}
191
+ value={valueState}
175
192
  onChange={(event, value, reason, details) => {
176
193
  currentValue.current = value;
177
194
  if (onChange) onChange(event, value, reason, details);