@etsoo/materialui 1.2.0 → 1.2.2

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.
@@ -6,7 +6,6 @@ export function ComboBoxPro(props) {
6
6
  var _a;
7
7
  // Labels
8
8
  const { noOptions, loading: loadingLabel, open: openDefault } = (_a = globalApp === null || globalApp === void 0 ? void 0 : globalApp.getLabels("noOptions", "loading", "open")) !== null && _a !== void 0 ? _a : {};
9
- const getLabel = (item) => "label" in item ? item.label : "name" in item ? item.name : "";
10
9
  // Destruct
11
10
  const { noOptionsText = noOptions, loadingText = loadingLabel, openText = openDefault, options, openOnFocus = true, label, inputProps, name, value, idValue, onChange, ...rest } = props;
12
11
  const [open, setOpen] = React.useState(false);
@@ -47,7 +46,11 @@ export function ComboBoxPro(props) {
47
46
  }, options: localOptions, loading: loading, openOnFocus: openOnFocus, renderInput: (params) => (React.createElement(InputField, { ...inputProps, ...params, label: label, name: name, onBlur: (event) => {
48
47
  if (localValue == null && onChange)
49
48
  onChange(event, event.target.value, "blur", undefined);
50
- } })), getOptionLabel: (item) => typeof item === "object" ? getLabel(item) : item, isOptionEqualToValue: (option, value) => option.id === value.id, noOptionsText: noOptionsText, loadingText: loadingText, openText: openText, onChange: (event, value, reason, details) => {
49
+ } })), getOptionLabel: (item) => typeof item === "object"
50
+ ? "label" in item
51
+ ? item.label
52
+ : item.name
53
+ : item, isOptionEqualToValue: (option, value) => option.id === value.id, noOptionsText: noOptionsText, loadingText: loadingText, openText: openText, onChange: (event, value, reason, details) => {
51
54
  setValue(value);
52
55
  if (onChange)
53
56
  onChange(event, value, reason, details);
package/lib/FabBox.d.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  /// <reference types="react" />
2
- import { BoxProps } from "@mui/material";
2
+ import { BoxProps, PaperProps } from "@mui/material";
3
+ type SharedProps = keyof BoxProps & keyof PaperProps;
3
4
  /**
4
5
  * Fabs container box props
5
6
  */
6
- export type FabBoxProps = BoxProps & {
7
+ export type FabBoxProps = Pick<BoxProps, SharedProps> & Pick<PaperProps, SharedProps> & {
7
8
  /**
8
9
  * Item gap
9
10
  */
@@ -23,3 +24,4 @@ export type FabBoxProps = BoxProps & {
23
24
  * @returns Component
24
25
  */
25
26
  export declare function FabBox(props: FabBoxProps): JSX.Element;
27
+ export {};
package/lib/FabBox.js CHANGED
@@ -7,7 +7,7 @@ import React from "react";
7
7
  */
8
8
  export function FabBox(props) {
9
9
  // Destruct
10
- const { columnDirection, fabPanel = columnDirection === false ? true : false, itemGap = 1, sx, ...rest } = props;
10
+ const { columnDirection, fabPanel = columnDirection, itemGap = 1, sx, ...rest } = props;
11
11
  // Theme
12
12
  const theme = useTheme();
13
13
  const spaceGap = theme.spacing(itemGap);
@@ -17,7 +17,15 @@ export function FabBox(props) {
17
17
  const margin = columnDirection
18
18
  ? { marginTop: spaceGap }
19
19
  : { marginLeft: spaceGap };
20
- const box = (React.createElement(Box, { sx: {
20
+ return fabPanel ? (React.createElement(Paper, { sx: {
21
+ position: "fixed",
22
+ display: "flex",
23
+ alignItems: "center",
24
+ padding: spaceGap,
25
+ flexDirection: columnDirection ? "column" : "row",
26
+ "& > :not(style) + :not(style)": margin,
27
+ ...sx
28
+ }, ...rest })) : (React.createElement(Box, { sx: {
21
29
  position: "fixed",
22
30
  display: "flex",
23
31
  alignItems: "center",
@@ -25,5 +33,4 @@ export function FabBox(props) {
25
33
  "& > :not(style) + :not(style)": margin,
26
34
  ...sx
27
35
  }, ...rest }));
28
- return fabPanel ? React.createElement(Paper, { sx: { padding: spaceGap } }, box) : box;
29
36
  }
@@ -0,0 +1,47 @@
1
+ import { ListType2 } from "@etsoo/shared";
2
+ import { AutocompleteProps } from "@mui/material";
3
+ import { ChangeEventHandler } from "react";
4
+ import { InputFieldProps } from "./InputField";
5
+ /**
6
+ * TiplistPro props
7
+ */
8
+ export type TiplistProProps<T extends ListType2 = ListType2> = Omit<AutocompleteProps<T, false, false, true>, "open" | "multiple" | "options" | "renderInput"> & {
9
+ /**
10
+ * Load data callback
11
+ */
12
+ loadData: (keyword: string | undefined, id: T["id"] | undefined, maxItems: number) => PromiseLike<T[] | null | undefined>;
13
+ /**
14
+ * Max items to read and display
15
+ */
16
+ maxItems?: number;
17
+ /**
18
+ * Width
19
+ */
20
+ width?: number;
21
+ /**
22
+ * Label
23
+ */
24
+ label?: string;
25
+ /**
26
+ * Field name
27
+ */
28
+ name?: string;
29
+ /**
30
+ * Id value
31
+ */
32
+ idValue?: T["id"] | null;
33
+ /**
34
+ * Input onChange hanlder
35
+ */
36
+ inputOnChange?: ChangeEventHandler<HTMLInputElement> | undefined;
37
+ /**
38
+ * Input props
39
+ */
40
+ inputProps?: Omit<InputFieldProps, "onChange">;
41
+ };
42
+ /**
43
+ * TiplistPro
44
+ * @param props Props
45
+ * @returns Component
46
+ */
47
+ export declare function TiplistPro<T extends ListType2 = ListType2>(props: TiplistProProps<T>): JSX.Element;
@@ -0,0 +1,173 @@
1
+ import { ReactUtils, useDelayedExecutor } from "@etsoo/react";
2
+ import { Autocomplete } from "@mui/material";
3
+ import React from "react";
4
+ import { InputField } from "./InputField";
5
+ import { globalApp } from "./app/ReactApp";
6
+ /**
7
+ * TiplistPro
8
+ * @param props Props
9
+ * @returns Component
10
+ */
11
+ export function TiplistPro(props) {
12
+ var _a;
13
+ // Labels
14
+ const { noOptions, loading, more, open: openDefault } = (_a = globalApp === null || globalApp === void 0 ? void 0 : globalApp.getLabels("noOptions", "loading", "more", "open")) !== null && _a !== void 0 ? _a : {};
15
+ // Destruct
16
+ const { label, loadData, defaultValue, value, idValue, maxItems = 16, width, name, inputOnChange, inputProps, sx, openOnFocus = true, noOptionsText = noOptions, loadingText = loading, openText = openDefault, getOptionDisabled, getOptionLabel, onChange, ...rest } = props;
17
+ if (width && sx)
18
+ Object.assign(sx, { width: `${width}px` });
19
+ // Value input ref
20
+ const inputRef = React.createRef();
21
+ // Local value
22
+ let localValue = value !== null && value !== void 0 ? value : defaultValue;
23
+ // One time calculation for input's default value (uncontrolled)
24
+ const localIdValue = idValue !== null && idValue !== void 0 ? idValue : (localValue != null && typeof localValue === "object"
25
+ ? localValue.id
26
+ : null);
27
+ // Changable states
28
+ const [states, stateUpdate] = React.useReducer((currentState, newState) => {
29
+ return { ...currentState, ...newState };
30
+ }, {
31
+ // Loading unknown
32
+ open: false,
33
+ options: [],
34
+ value: null
35
+ });
36
+ React.useEffect(() => {
37
+ if (localValue != value)
38
+ stateUpdate({ value: localValue });
39
+ }, [localValue]);
40
+ // Input value
41
+ const inputValue = React.useMemo(() => states.value && typeof states.value === "object"
42
+ ? states.value.id
43
+ : undefined, [states.value]);
44
+ // State
45
+ const [state] = React.useState({});
46
+ const isMounted = React.useRef(true);
47
+ // Change handler
48
+ const changeHandle = (event) => {
49
+ // Stop processing with auto trigger event
50
+ if (event.nativeEvent.cancelable && !event.nativeEvent.composed) {
51
+ stateUpdate({ options: [] });
52
+ return;
53
+ }
54
+ // Stop bubble
55
+ event.stopPropagation();
56
+ // Call with delay
57
+ delayed.call(undefined, event.currentTarget.value);
58
+ };
59
+ // Directly load data
60
+ const loadDataDirect = (keyword, id) => {
61
+ // Reset options
62
+ // setOptions([]);
63
+ if (id == null) {
64
+ // Reset real value
65
+ const input = inputRef.current;
66
+ if (input && input.value !== "") {
67
+ // Different value, trigger change event
68
+ ReactUtils.triggerChange(input, "", false);
69
+ }
70
+ if (states.options.length > 0) {
71
+ // Reset options
72
+ stateUpdate({ options: [] });
73
+ }
74
+ }
75
+ // Loading indicator
76
+ if (!states.loading)
77
+ stateUpdate({ loading: true });
78
+ // Load list
79
+ loadData(keyword, id, maxItems).then((options) => {
80
+ if (!isMounted.current)
81
+ return;
82
+ if (options != null && options.length >= maxItems) {
83
+ options.push({ id: -1, name: "n/a" });
84
+ }
85
+ // Indicates loading completed
86
+ stateUpdate({
87
+ loading: false,
88
+ ...(options != null && { options })
89
+ });
90
+ });
91
+ };
92
+ const delayed = useDelayedExecutor(loadDataDirect, 480);
93
+ const setInputValue = (value) => {
94
+ var _a;
95
+ stateUpdate({ value });
96
+ // Input value
97
+ const input = inputRef.current;
98
+ if (input) {
99
+ // Update value
100
+ const newValue = (_a = value === null || value === void 0 ? void 0 : value.id.toString()) !== null && _a !== void 0 ? _a : "";
101
+ if (newValue !== input.value) {
102
+ // Different value, trigger change event
103
+ ReactUtils.triggerChange(input, newValue, false);
104
+ }
105
+ }
106
+ };
107
+ if (localIdValue != null && localIdValue !== "") {
108
+ if (state.idLoaded) {
109
+ // Set default
110
+ if (!state.idSet && states.options.length == 1) {
111
+ stateUpdate({ value: states.options[0] });
112
+ state.idSet = true;
113
+ }
114
+ }
115
+ else {
116
+ // Load id data
117
+ loadDataDirect(undefined, localIdValue);
118
+ state.idLoaded = true;
119
+ }
120
+ }
121
+ React.useEffect(() => {
122
+ return () => {
123
+ isMounted.current = false;
124
+ delayed.clear();
125
+ };
126
+ }, []);
127
+ // Layout
128
+ return (React.createElement("div", null,
129
+ React.createElement("input", { ref: inputRef, "data-reset": "true", type: "text", style: { display: "none" }, name: name, value: inputValue !== null && inputValue !== void 0 ? inputValue : "", readOnly: true, onChange: inputOnChange }),
130
+ React.createElement(Autocomplete, { filterOptions: (options, _state) => options, value: states.value, options: states.options, freeSolo: true, clearOnBlur: false, onChange: (event, value, reason, details) => {
131
+ if (typeof value === "object") {
132
+ // Set value
133
+ setInputValue(value);
134
+ }
135
+ // Custom
136
+ if (onChange != null)
137
+ onChange(event, value, reason, details);
138
+ // For clear case
139
+ if (reason === "clear") {
140
+ stateUpdate({ options: [] });
141
+ loadDataDirect();
142
+ }
143
+ }, open: states.open, openOnFocus: openOnFocus, onOpen: () => {
144
+ // Should load
145
+ const loading = states.loading ? true : states.options.length === 0;
146
+ stateUpdate({ open: true, loading });
147
+ // If not loading
148
+ if (loading)
149
+ loadDataDirect(undefined, states.value && typeof states.value === "object"
150
+ ? states.value.id
151
+ : undefined);
152
+ }, onClose: () => {
153
+ stateUpdate({
154
+ open: false,
155
+ ...(!states.value && { options: [] })
156
+ });
157
+ }, loading: states.loading, renderInput: (params) => (React.createElement(InputField, { ...inputProps, ...params, onChange: changeHandle, label: label, name: name + "Input", onBlur: (event) => {
158
+ if (states.value == null && onChange)
159
+ onChange(event, event.target.value, "blur", undefined);
160
+ } })), isOptionEqualToValue: (option, value) => option.id === value.id, sx: sx, noOptionsText: noOptionsText, loadingText: loadingText, openText: openText, getOptionDisabled: (item) => {
161
+ if (item.id === -1)
162
+ return true;
163
+ return getOptionDisabled ? getOptionDisabled(item) : false;
164
+ }, getOptionLabel: (item) => {
165
+ if (typeof item === "string")
166
+ return item;
167
+ if (item["id"] === -1)
168
+ return (more !== null && more !== void 0 ? more : "More") + "...";
169
+ if (getOptionLabel == null)
170
+ return "label" in item ? item.label : item.name;
171
+ return getOptionLabel(item);
172
+ }, ...rest })));
173
+ }
package/lib/index.d.ts CHANGED
@@ -89,6 +89,7 @@ export * from "./TabBox";
89
89
  export * from "./TableEx";
90
90
  export * from "./TextFieldEx";
91
91
  export * from "./Tiplist";
92
+ export * from "./TiplistPro";
92
93
  export * from "./TagList";
93
94
  export * from "./TagListPro";
94
95
  export * from "./TwoFieldInput";
package/lib/index.js CHANGED
@@ -89,6 +89,7 @@ export * from "./TabBox";
89
89
  export * from "./TableEx";
90
90
  export * from "./TextFieldEx";
91
91
  export * from "./Tiplist";
92
+ export * from "./TiplistPro";
92
93
  export * from "./TagList";
93
94
  export * from "./TagListPro";
94
95
  export * from "./TwoFieldInput";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@etsoo/materialui",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "TypeScript Material-UI Implementation",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -50,16 +50,16 @@
50
50
  "@emotion/css": "^11.10.6",
51
51
  "@emotion/react": "^11.10.6",
52
52
  "@emotion/styled": "^11.10.6",
53
- "@etsoo/appscript": "^1.3.89",
53
+ "@etsoo/appscript": "^1.3.91",
54
54
  "@etsoo/notificationbase": "^1.1.24",
55
- "@etsoo/react": "^1.6.65",
56
- "@etsoo/shared": "^1.1.99",
55
+ "@etsoo/react": "^1.6.67",
56
+ "@etsoo/shared": "^1.2.0",
57
57
  "@mui/icons-material": "^5.11.16",
58
- "@mui/material": "^5.11.16",
59
- "@mui/x-data-grid": "^6.0.4",
58
+ "@mui/material": "^5.12.0",
59
+ "@mui/x-data-grid": "^6.2.0",
60
60
  "@types/pica": "^9.0.1",
61
61
  "@types/pulltorefreshjs": "^0.1.5",
62
- "@types/react": "^18.0.33",
62
+ "@types/react": "^18.0.35",
63
63
  "@types/react-avatar-editor": "^13.0.0",
64
64
  "@types/react-dom": "^18.0.11",
65
65
  "@types/react-input-mask": "^3.0.2",
@@ -70,7 +70,7 @@
70
70
  "react-avatar-editor": "^13.0.0",
71
71
  "react-dom": "^18.2.0",
72
72
  "react-draggable": "^4.4.5",
73
- "react-imask": "^6.5.0",
73
+ "react-imask": "^6.5.1",
74
74
  "react-router-dom": "^6.10.0",
75
75
  "react-window": "^1.8.8"
76
76
  },
@@ -85,8 +85,8 @@
85
85
  "@testing-library/jest-dom": "^5.16.5",
86
86
  "@testing-library/react": "^14.0.0",
87
87
  "@types/jest": "^29.5.0",
88
- "@typescript-eslint/eslint-plugin": "^5.57.1",
89
- "@typescript-eslint/parser": "^5.57.1",
88
+ "@typescript-eslint/eslint-plugin": "^5.58.0",
89
+ "@typescript-eslint/parser": "^5.58.0",
90
90
  "jest": "^29.5.0",
91
91
  "jest-environment-jsdom": "^29.5.0",
92
92
  "typescript": "^5.0.4"
@@ -44,9 +44,6 @@ export function ComboBoxPro<D extends ListType2 = ListType2>(
44
44
  open: openDefault
45
45
  } = globalApp?.getLabels("noOptions", "loading", "open") ?? {};
46
46
 
47
- const getLabel = (item: D) =>
48
- "label" in item ? item.label : "name" in item ? item.name : "";
49
-
50
47
  // Destruct
51
48
  const {
52
49
  noOptionsText = noOptions,
@@ -121,7 +118,11 @@ export function ComboBoxPro<D extends ListType2 = ListType2>(
121
118
  />
122
119
  )}
123
120
  getOptionLabel={(item) =>
124
- typeof item === "object" ? getLabel(item) : item
121
+ typeof item === "object"
122
+ ? "label" in item
123
+ ? item.label
124
+ : item.name
125
+ : item
125
126
  }
126
127
  isOptionEqualToValue={(option, value) => option.id === value.id}
127
128
  noOptionsText={noOptionsText}
package/src/FabBox.tsx CHANGED
@@ -1,25 +1,28 @@
1
- import { Box, BoxProps, Paper, useTheme } from "@mui/material";
1
+ import { Box, BoxProps, Paper, PaperProps, useTheme } from "@mui/material";
2
2
  import React from "react";
3
3
 
4
+ type SharedProps = keyof BoxProps & keyof PaperProps;
5
+
4
6
  /**
5
7
  * Fabs container box props
6
8
  */
7
- export type FabBoxProps = BoxProps & {
8
- /**
9
- * Item gap
10
- */
11
- itemGap?: number;
9
+ export type FabBoxProps = Pick<BoxProps, SharedProps> &
10
+ Pick<PaperProps, SharedProps> & {
11
+ /**
12
+ * Item gap
13
+ */
14
+ itemGap?: number;
12
15
 
13
- /**
14
- * Flex direction, row or column
15
- */
16
- columnDirection?: boolean;
16
+ /**
17
+ * Flex direction, row or column
18
+ */
19
+ columnDirection?: boolean;
17
20
 
18
- /**
19
- * Add panel to the Fab
20
- */
21
- fabPanel?: boolean;
22
- };
21
+ /**
22
+ * Add panel to the Fab
23
+ */
24
+ fabPanel?: boolean;
25
+ };
23
26
 
24
27
  /**
25
28
  * Fabs container box
@@ -30,7 +33,7 @@ export function FabBox(props: FabBoxProps) {
30
33
  // Destruct
31
34
  const {
32
35
  columnDirection,
33
- fabPanel = columnDirection === false ? true : false,
36
+ fabPanel = columnDirection,
34
37
  itemGap = 1,
35
38
  sx,
36
39
  ...rest
@@ -47,7 +50,20 @@ export function FabBox(props: FabBoxProps) {
47
50
  ? { marginTop: spaceGap }
48
51
  : { marginLeft: spaceGap };
49
52
 
50
- const box = (
53
+ return fabPanel ? (
54
+ <Paper
55
+ sx={{
56
+ position: "fixed",
57
+ display: "flex",
58
+ alignItems: "center",
59
+ padding: spaceGap,
60
+ flexDirection: columnDirection ? "column" : "row",
61
+ "& > :not(style) + :not(style)": margin,
62
+ ...sx
63
+ }}
64
+ {...rest}
65
+ />
66
+ ) : (
51
67
  <Box
52
68
  sx={{
53
69
  position: "fixed",
@@ -60,6 +76,4 @@ export function FabBox(props: FabBoxProps) {
60
76
  {...rest}
61
77
  />
62
78
  );
63
-
64
- return fabPanel ? <Paper sx={{ padding: spaceGap }}>{box}</Paper> : box;
65
79
  }
package/src/Tiplist.tsx CHANGED
@@ -1,7 +1,6 @@
1
1
  import { ReactUtils, useDelayedExecutor } from "@etsoo/react";
2
2
  import { DataTypes, IdDefaultType, ListType } from "@etsoo/shared";
3
3
  import { Autocomplete, AutocompleteRenderInputParams } from "@mui/material";
4
- import { width } from "@mui/system";
5
4
  import React from "react";
6
5
  import { globalApp } from "./app/ReactApp";
7
6
  import { AutocompleteExtendedProps } from "./AutocompleteExtendedProps";
@@ -0,0 +1,339 @@
1
+ import { ReactUtils, useDelayedExecutor } from "@etsoo/react";
2
+ import { ListType2 } from "@etsoo/shared";
3
+ import { Autocomplete, AutocompleteProps } from "@mui/material";
4
+ import React, { ChangeEventHandler } from "react";
5
+ import { InputField, InputFieldProps } from "./InputField";
6
+ import { globalApp } from "./app/ReactApp";
7
+
8
+ /**
9
+ * TiplistPro props
10
+ */
11
+ export type TiplistProProps<T extends ListType2 = ListType2> = Omit<
12
+ AutocompleteProps<T, false, false, true>,
13
+ "open" | "multiple" | "options" | "renderInput"
14
+ > & {
15
+ /**
16
+ * Load data callback
17
+ */
18
+ loadData: (
19
+ keyword: string | undefined,
20
+ id: T["id"] | undefined,
21
+ maxItems: number
22
+ ) => PromiseLike<T[] | null | undefined>;
23
+
24
+ /**
25
+ * Max items to read and display
26
+ */
27
+ maxItems?: number;
28
+
29
+ /**
30
+ * Width
31
+ */
32
+ width?: number;
33
+
34
+ /**
35
+ * Label
36
+ */
37
+ label?: string;
38
+
39
+ /**
40
+ * Field name
41
+ */
42
+ name?: string;
43
+
44
+ /**
45
+ * Id value
46
+ */
47
+ idValue?: T["id"] | null;
48
+
49
+ /**
50
+ * Input onChange hanlder
51
+ */
52
+ inputOnChange?: ChangeEventHandler<HTMLInputElement> | undefined;
53
+
54
+ /**
55
+ * Input props
56
+ */
57
+ inputProps?: Omit<InputFieldProps, "onChange">;
58
+ };
59
+
60
+ // Multiple states
61
+ interface States<T extends object> {
62
+ open: boolean;
63
+ options: T[];
64
+ value?: T | string | null | undefined;
65
+ loading?: boolean;
66
+ }
67
+
68
+ /**
69
+ * TiplistPro
70
+ * @param props Props
71
+ * @returns Component
72
+ */
73
+ export function TiplistPro<T extends ListType2 = ListType2>(
74
+ props: TiplistProProps<T>
75
+ ) {
76
+ // Labels
77
+ const {
78
+ noOptions,
79
+ loading,
80
+ more,
81
+ open: openDefault
82
+ } = globalApp?.getLabels("noOptions", "loading", "more", "open") ?? {};
83
+
84
+ // Destruct
85
+ const {
86
+ label,
87
+ loadData,
88
+ defaultValue,
89
+ value,
90
+ idValue,
91
+ maxItems = 16,
92
+ width,
93
+ name,
94
+ inputOnChange,
95
+ inputProps,
96
+ sx,
97
+ openOnFocus = true,
98
+ noOptionsText = noOptions,
99
+ loadingText = loading,
100
+ openText = openDefault,
101
+ getOptionDisabled,
102
+ getOptionLabel,
103
+ onChange,
104
+ ...rest
105
+ } = props;
106
+
107
+ if (width && sx) Object.assign(sx, { width: `${width}px` });
108
+
109
+ // Value input ref
110
+ const inputRef = React.createRef<HTMLInputElement>();
111
+
112
+ // Local value
113
+ let localValue = value ?? defaultValue;
114
+
115
+ // One time calculation for input's default value (uncontrolled)
116
+ const localIdValue =
117
+ idValue ??
118
+ (localValue != null && typeof localValue === "object"
119
+ ? localValue.id
120
+ : null);
121
+
122
+ // Changable states
123
+ const [states, stateUpdate] = React.useReducer(
124
+ (currentState: States<T>, newState: Partial<States<T>>) => {
125
+ return { ...currentState, ...newState };
126
+ },
127
+ {
128
+ // Loading unknown
129
+ open: false,
130
+ options: [],
131
+ value: null
132
+ }
133
+ );
134
+
135
+ React.useEffect(() => {
136
+ if (localValue != value) stateUpdate({ value: localValue });
137
+ }, [localValue]);
138
+
139
+ // Input value
140
+ const inputValue = React.useMemo(
141
+ () =>
142
+ states.value && typeof states.value === "object"
143
+ ? states.value.id
144
+ : undefined,
145
+ [states.value]
146
+ );
147
+
148
+ // State
149
+ const [state] = React.useState<{
150
+ idLoaded?: boolean;
151
+ idSet?: boolean;
152
+ }>({});
153
+ const isMounted = React.useRef(true);
154
+
155
+ // Change handler
156
+ const changeHandle = (event: React.ChangeEvent<HTMLInputElement>) => {
157
+ // Stop processing with auto trigger event
158
+ if (event.nativeEvent.cancelable && !event.nativeEvent.composed) {
159
+ stateUpdate({ options: [] });
160
+ return;
161
+ }
162
+
163
+ // Stop bubble
164
+ event.stopPropagation();
165
+
166
+ // Call with delay
167
+ delayed.call(undefined, event.currentTarget.value);
168
+ };
169
+
170
+ // Directly load data
171
+ const loadDataDirect = (keyword?: string, id?: T["id"]) => {
172
+ // Reset options
173
+ // setOptions([]);
174
+
175
+ if (id == null) {
176
+ // Reset real value
177
+ const input = inputRef.current;
178
+
179
+ if (input && input.value !== "") {
180
+ // Different value, trigger change event
181
+ ReactUtils.triggerChange(input, "", false);
182
+ }
183
+
184
+ if (states.options.length > 0) {
185
+ // Reset options
186
+ stateUpdate({ options: [] });
187
+ }
188
+ }
189
+
190
+ // Loading indicator
191
+ if (!states.loading) stateUpdate({ loading: true });
192
+
193
+ // Load list
194
+ loadData(keyword, id, maxItems).then((options) => {
195
+ if (!isMounted.current) return;
196
+
197
+ if (options != null && options.length >= maxItems) {
198
+ options.push({ id: -1, name: "n/a" } as T);
199
+ }
200
+
201
+ // Indicates loading completed
202
+ stateUpdate({
203
+ loading: false,
204
+ ...(options != null && { options })
205
+ });
206
+ });
207
+ };
208
+
209
+ const delayed = useDelayedExecutor(loadDataDirect, 480);
210
+
211
+ const setInputValue = (value: T | null) => {
212
+ stateUpdate({ value });
213
+
214
+ // Input value
215
+ const input = inputRef.current;
216
+ if (input) {
217
+ // Update value
218
+ const newValue = value?.id.toString() ?? "";
219
+ if (newValue !== input.value) {
220
+ // Different value, trigger change event
221
+ ReactUtils.triggerChange(input, newValue, false);
222
+ }
223
+ }
224
+ };
225
+
226
+ if (localIdValue != null && (localIdValue as any) !== "") {
227
+ if (state.idLoaded) {
228
+ // Set default
229
+ if (!state.idSet && states.options.length == 1) {
230
+ stateUpdate({ value: states.options[0] });
231
+ state.idSet = true;
232
+ }
233
+ } else {
234
+ // Load id data
235
+ loadDataDirect(undefined, localIdValue);
236
+ state.idLoaded = true;
237
+ }
238
+ }
239
+
240
+ React.useEffect(() => {
241
+ return () => {
242
+ isMounted.current = false;
243
+ delayed.clear();
244
+ };
245
+ }, []);
246
+
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={inputValue ?? ""}
257
+ readOnly
258
+ onChange={inputOnChange}
259
+ />
260
+ {/* Previous input will reset first with "disableClearable = false", next input trigger change works */}
261
+ <Autocomplete<T, false, false, true>
262
+ filterOptions={(options, _state) => options}
263
+ value={states.value}
264
+ options={states.options}
265
+ freeSolo
266
+ clearOnBlur={false}
267
+ onChange={(event, value, reason, details) => {
268
+ if (typeof value === "object") {
269
+ // Set value
270
+ setInputValue(value);
271
+ }
272
+
273
+ // Custom
274
+ if (onChange != null) onChange(event, value, reason, details);
275
+
276
+ // For clear case
277
+ if (reason === "clear") {
278
+ stateUpdate({ options: [] });
279
+ loadDataDirect();
280
+ }
281
+ }}
282
+ open={states.open}
283
+ openOnFocus={openOnFocus}
284
+ onOpen={() => {
285
+ // Should load
286
+ const loading = states.loading ? true : states.options.length === 0;
287
+
288
+ stateUpdate({ open: true, loading });
289
+
290
+ // If not loading
291
+ if (loading)
292
+ loadDataDirect(
293
+ undefined,
294
+ states.value && typeof states.value === "object"
295
+ ? states.value.id
296
+ : undefined
297
+ );
298
+ }}
299
+ onClose={() => {
300
+ stateUpdate({
301
+ open: false,
302
+ ...(!states.value && { options: [] })
303
+ });
304
+ }}
305
+ loading={states.loading}
306
+ renderInput={(params) => (
307
+ <InputField
308
+ {...inputProps}
309
+ {...params}
310
+ onChange={changeHandle}
311
+ label={label}
312
+ name={name + "Input"}
313
+ onBlur={(event) => {
314
+ if (states.value == null && onChange)
315
+ onChange(event, event.target.value, "blur", undefined);
316
+ }}
317
+ />
318
+ )}
319
+ isOptionEqualToValue={(option, value) => option.id === value.id}
320
+ sx={sx}
321
+ noOptionsText={noOptionsText}
322
+ loadingText={loadingText}
323
+ openText={openText}
324
+ getOptionDisabled={(item) => {
325
+ if (item.id === -1) return true;
326
+ return getOptionDisabled ? getOptionDisabled(item) : false;
327
+ }}
328
+ getOptionLabel={(item) => {
329
+ if (typeof item === "string") return item;
330
+ if (item["id"] === -1) return (more ?? "More") + "...";
331
+ if (getOptionLabel == null)
332
+ return "label" in item ? item.label : item.name;
333
+ return getOptionLabel(item);
334
+ }}
335
+ {...rest}
336
+ />
337
+ </div>
338
+ );
339
+ }
package/src/index.ts CHANGED
@@ -92,6 +92,7 @@ export * from "./TabBox";
92
92
  export * from "./TableEx";
93
93
  export * from "./TextFieldEx";
94
94
  export * from "./Tiplist";
95
+ export * from "./TiplistPro";
95
96
  export * from "./TagList";
96
97
  export * from "./TagListPro";
97
98
  export * from "./TwoFieldInput";