@etsoo/materialui 1.3.65 → 1.3.67

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,5 +1,11 @@
1
1
  import React from "react";
2
- import { act, getByText, render } from "@testing-library/react";
2
+ import {
3
+ act,
4
+ fireEvent,
5
+ getByText,
6
+ render,
7
+ screen
8
+ } from "@testing-library/react";
3
9
  import { CustomFieldData } from "@etsoo/appscript";
4
10
  import { CustomFieldUtils } from "../src/custom/CustomFieldUtils";
5
11
  import { CustomFieldReactCollection } from "@etsoo/react";
@@ -91,3 +97,58 @@ it("Render FieldCheckbox", () => {
91
97
  checkboxItems[1].click();
92
98
  });
93
99
  });
100
+
101
+ it("Render FieldSelect", async () => {
102
+ // Arrange
103
+ const fields: CustomFieldData[] = [
104
+ {
105
+ type: "select",
106
+ name: "select",
107
+ options: [
108
+ { id: "a", label: "A" },
109
+ { id: "b", label: "B" },
110
+ { id: "c", label: "C" }
111
+ ]
112
+ }
113
+ ];
114
+
115
+ const collections: CustomFieldReactCollection<CustomFieldData> = {};
116
+
117
+ act(() => {
118
+ render(
119
+ <div>
120
+ {CustomFieldUtils.create(
121
+ fields,
122
+ collections,
123
+ (field) => {
124
+ switch (field.type) {
125
+ case "select":
126
+ return "c";
127
+ default:
128
+ return undefined;
129
+ }
130
+ },
131
+ (name, value) => {
132
+ expect(name).toBe("checkbox");
133
+ expect(value).toStrictEqual(["a"]);
134
+ }
135
+ )}
136
+ </div>
137
+ );
138
+ });
139
+
140
+ const button = screen.getByRole("combobox");
141
+
142
+ act(() => {
143
+ // Act, click to open the dropdown list
144
+ jest.useFakeTimers();
145
+ fireEvent.mouseDown(button);
146
+ jest.advanceTimersByTime(100);
147
+ });
148
+
149
+ const input = document.querySelector<HTMLInputElement>("input");
150
+ expect(input?.value).toBe("c");
151
+
152
+ const listItems = document.querySelectorAll("li");
153
+ expect(listItems.length).toBe(3);
154
+ });
@@ -19,7 +19,7 @@ it("Render SelectEx", async () => {
19
19
  });
20
20
 
21
21
  // Render component
22
- render(
22
+ const { findByText } = render(
23
23
  <SelectEx<T>
24
24
  options={options}
25
25
  name="test"
@@ -44,12 +44,11 @@ it("Render SelectEx", async () => {
44
44
  jest.advanceTimersByTime(100);
45
45
  });
46
46
 
47
- /*
48
47
  // Get list item
49
- const itemName2 = await findByText(baseElement, "Name 2");
48
+ const itemName2 = await findByText("Name 2");
50
49
  expect(itemName2.nodeName).toBe("SPAN");
51
50
 
52
- const itemBlank = await findByText(baseElement, "---");
51
+ const itemBlank = await findByText("---");
53
52
  expect(itemBlank.nodeName).toBe("SPAN");
54
53
 
55
54
  act(() => {
@@ -57,7 +56,6 @@ it("Render SelectEx", async () => {
57
56
  });
58
57
 
59
58
  expect(itemChangeCallback).toBeCalledTimes(2);
60
- */
61
59
  });
62
60
 
63
61
  it("Render multiple SelectEx", async () => {
@@ -10,6 +10,10 @@ import { FieldInput } from "./FieldInput";
10
10
  import { FieldLabel } from "./FieldLabel";
11
11
  import { FieldNumberInput } from "./FieldNumberInput";
12
12
  import { FieldTexarea } from "./FieldTexarea";
13
+ import { FieldJson } from "./FieldJson";
14
+ import { FieldRadio } from "./FieldRadio";
15
+ import { FieldSelect } from "./FieldSelect";
16
+ import { FieldSwitch } from "./FieldSwitch";
13
17
  /**
14
18
  * Custom field utilities
15
19
  */
@@ -25,8 +29,12 @@ export var CustomFieldUtils;
25
29
  date: FieldDateInput,
26
30
  divider: FieldDivider,
27
31
  input: FieldInput,
32
+ json: FieldJson,
28
33
  label: FieldLabel,
29
34
  number: FieldNumberInput,
35
+ radio: FieldRadio,
36
+ select: FieldSelect,
37
+ switch: FieldSwitch,
30
38
  textarea: FieldTexarea
31
39
  };
32
40
  /**
@@ -0,0 +1,55 @@
1
+ import { CustomFieldData } from "@etsoo/appscript";
2
+ import { GridProps } from "@mui/material";
3
+ import React from "react";
4
+ /**
5
+ * Custom field window props
6
+ * 自定义字段窗口属性
7
+ */
8
+ export type CustomFieldWindowProps<D extends CustomFieldData> = {
9
+ /**
10
+ * Children creation callback
11
+ * 子元素创建回调
12
+ * @param open Open callback
13
+ * @param label Label
14
+ * @param pc Property count
15
+ * @returns Component
16
+ */
17
+ children: (open: (customFields: D[], jsonData?: unknown) => void, label: string | undefined, pc: number) => React.ReactNode;
18
+ /**
19
+ * Label
20
+ * 标签
21
+ */
22
+ label?: string;
23
+ /**
24
+ * Grid props
25
+ * 网格属性
26
+ */
27
+ gridProps?: GridProps;
28
+ /**
29
+ * JSON data
30
+ * JSON 数据
31
+ */
32
+ jsonData?: unknown;
33
+ /**
34
+ * Input name
35
+ * 输入框名称
36
+ */
37
+ inputName?: string;
38
+ /**
39
+ * Input ref
40
+ * 输入框引用
41
+ */
42
+ inputRef?: React.MutableRefObject<HTMLInputElement | null>;
43
+ /**
44
+ * On update callback
45
+ * 更新回调
46
+ */
47
+ onUpdate?: (data: Record<string, unknown>) => void;
48
+ };
49
+ /**
50
+ * Custom field window
51
+ * 自定义字段窗口
52
+ * @param props Props
53
+ * @returns Component
54
+ */
55
+ export declare function CustomFieldWindow<D extends CustomFieldData = CustomFieldData>(props: CustomFieldWindowProps<D>): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,89 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Utils } from "@etsoo/shared";
3
+ import { Grid, Stack } from "@mui/material";
4
+ import React from "react";
5
+ import { globalApp } from "../app/ReactApp";
6
+ import { MUGlobal } from "../MUGlobal";
7
+ import { CustomFieldUtils } from "./CustomFieldUtils";
8
+ function calculateKeys(data) {
9
+ let count = 0;
10
+ for (const key in data) {
11
+ const item = data[key];
12
+ if (typeof item === "object" && item !== null && !Array.isArray(item)) {
13
+ count += calculateKeys(item);
14
+ }
15
+ else {
16
+ count++;
17
+ }
18
+ }
19
+ return count;
20
+ }
21
+ function parseJsonData(jsonData) {
22
+ let data = {};
23
+ if (jsonData) {
24
+ try {
25
+ data =
26
+ typeof jsonData === "object"
27
+ ? jsonData
28
+ : typeof jsonData === "string"
29
+ ? JSON.parse(jsonData)
30
+ : {};
31
+ }
32
+ catch { }
33
+ }
34
+ return [data, calculateKeys(data)];
35
+ }
36
+ /**
37
+ * Custom field window
38
+ * 自定义字段窗口
39
+ * @param props Props
40
+ * @returns Component
41
+ */
42
+ export function CustomFieldWindow(props) {
43
+ // Validate app
44
+ const app = globalApp;
45
+ if (app == null) {
46
+ throw new Error("No globalApp");
47
+ }
48
+ const { children, label = app.get("jsonData"), gridProps, jsonData, inputName = "jsonData", inputRef, onUpdate } = props;
49
+ const spacing = MUGlobal.half(MUGlobal.pagePaddings);
50
+ const [count, setCount] = React.useState(0);
51
+ const [initData, propertyCount] = parseJsonData(jsonData);
52
+ React.useEffect(() => setCount(propertyCount), [propertyCount]);
53
+ return (_jsxs(React.Fragment, { children: [_jsx("input", { type: "hidden", name: inputName, ref: inputRef }), children((customFields, jsonData) => {
54
+ const collections = {};
55
+ let data = {};
56
+ jsonData ?? (jsonData = inputRef?.current?.value);
57
+ if (jsonData) {
58
+ const [d, pc] = parseJsonData(jsonData);
59
+ data = d;
60
+ setCount(pc);
61
+ }
62
+ else {
63
+ data = initData;
64
+ }
65
+ app.notifier.confirm(label, undefined, (value) => {
66
+ if (value) {
67
+ if (inputRef?.current) {
68
+ inputRef.current.value = JSON.stringify(data);
69
+ }
70
+ setCount(calculateKeys(data));
71
+ if (onUpdate)
72
+ onUpdate(data);
73
+ }
74
+ }, {
75
+ fullScreen: app.smDown,
76
+ inputs: (_jsx(Stack, { marginTop: 1.5, children: _jsx(Grid, { container: true, justifyContent: "left", spacing: spacing, sx: {
77
+ ".MuiTypography-subtitle2": {
78
+ fontWeight: "bold"
79
+ }
80
+ }, ...gridProps, children: CustomFieldUtils.create(customFields, collections, (field) => {
81
+ if (field.name == null)
82
+ return;
83
+ return Utils.getNestedValue(data, field.name);
84
+ }, (name, value) => {
85
+ Utils.setNestedValue(data, name, value);
86
+ }) }) }))
87
+ });
88
+ }, label, count)] }));
89
+ }
@@ -37,7 +37,7 @@ export const FieldSelect = ({ field, mref, onChange, defaultValue }) => {
37
37
  if (!name) {
38
38
  return (_jsxs(Typography, { children: ["No name for FieldSelect ", JSON.stringify(field)] }));
39
39
  }
40
- return (_jsx(SelectEx, { label: field.label ?? "", helperText: field.helperText, name: name, options: field.options, fullWidth: true, onChange: (event) => {
40
+ return (_jsx(SelectEx, { label: field.label ?? "", helperText: field.helperText, name: name, options: field.options, fullWidth: true, value: value, onChange: (event) => {
41
41
  const value = event.target.value;
42
42
  const newValue = typeof value === "string" || typeof value === "number"
43
43
  ? value
package/lib/index.d.ts CHANGED
@@ -9,6 +9,7 @@ export * from "./app/Labels";
9
9
  export * from "./app/ReactApp";
10
10
  export * from "./app/ServiceApp";
11
11
  export * from "./custom/CustomFieldUtils";
12
+ export * from "./custom/CustomFieldWindow";
12
13
  export * from "./messages/MessageUtils";
13
14
  export * from "./messages/OperationMessageContainer";
14
15
  export * from "./messages/OperationMessageDto";
package/lib/index.js CHANGED
@@ -9,6 +9,7 @@ export * from "./app/Labels";
9
9
  export * from "./app/ReactApp";
10
10
  export * from "./app/ServiceApp";
11
11
  export * from "./custom/CustomFieldUtils";
12
+ export * from "./custom/CustomFieldWindow";
12
13
  export * from "./messages/MessageUtils";
13
14
  export * from "./messages/OperationMessageContainer";
14
15
  export * from "./messages/OperationMessageDto";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@etsoo/materialui",
3
- "version": "1.3.65",
3
+ "version": "1.3.67",
4
4
  "description": "TypeScript Material-UI Implementation",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -50,13 +50,13 @@
50
50
  "@emotion/css": "^11.11.2",
51
51
  "@emotion/react": "^11.11.4",
52
52
  "@emotion/styled": "^11.11.5",
53
- "@etsoo/appscript": "^1.4.95",
53
+ "@etsoo/appscript": "^1.4.96",
54
54
  "@etsoo/notificationbase": "^1.1.42",
55
- "@etsoo/react": "^1.7.52",
56
- "@etsoo/shared": "^1.2.40",
55
+ "@etsoo/react": "^1.7.54",
56
+ "@etsoo/shared": "^1.2.41",
57
57
  "@mui/icons-material": "^5.15.18",
58
58
  "@mui/material": "^5.15.18",
59
- "@mui/x-data-grid": "^7.5.0",
59
+ "@mui/x-data-grid": "^7.5.1",
60
60
  "chart.js": "^4.4.3",
61
61
  "chartjs-plugin-datalabels": "^2.2.0",
62
62
  "eventemitter3": "^5.0.1",
@@ -87,8 +87,8 @@
87
87
  "@types/react-dom": "^18.3.0",
88
88
  "@types/react-input-mask": "^3.0.5",
89
89
  "@types/react-window": "^1.8.8",
90
- "@typescript-eslint/eslint-plugin": "^7.10.0",
91
- "@typescript-eslint/parser": "^7.10.0",
90
+ "@typescript-eslint/eslint-plugin": "^7.11.0",
91
+ "@typescript-eslint/parser": "^7.11.0",
92
92
  "jest": "^29.7.0",
93
93
  "jest-environment-jsdom": "^29.7.0",
94
94
  "typescript": "^5.4.5"
@@ -17,6 +17,10 @@ import { FieldInput } from "./FieldInput";
17
17
  import { FieldLabel } from "./FieldLabel";
18
18
  import { FieldNumberInput } from "./FieldNumberInput";
19
19
  import { FieldTexarea } from "./FieldTexarea";
20
+ import { FieldJson } from "./FieldJson";
21
+ import { FieldRadio } from "./FieldRadio";
22
+ import { FieldSelect } from "./FieldSelect";
23
+ import { FieldSwitch } from "./FieldSwitch";
20
24
 
21
25
  /**
22
26
  * Custom field utilities
@@ -35,8 +39,12 @@ export namespace CustomFieldUtils {
35
39
  date: FieldDateInput,
36
40
  divider: FieldDivider,
37
41
  input: FieldInput,
42
+ json: FieldJson,
38
43
  label: FieldLabel,
39
44
  number: FieldNumberInput,
45
+ radio: FieldRadio,
46
+ select: FieldSelect,
47
+ switch: FieldSwitch,
40
48
  textarea: FieldTexarea
41
49
  };
42
50
 
@@ -0,0 +1,192 @@
1
+ import { CustomFieldData } from "@etsoo/appscript";
2
+ import { CustomFieldReactCollection } from "@etsoo/react";
3
+ import { Utils } from "@etsoo/shared";
4
+ import { Grid, GridProps, Stack } from "@mui/material";
5
+ import React from "react";
6
+ import { globalApp } from "../app/ReactApp";
7
+ import { MUGlobal } from "../MUGlobal";
8
+ import { CustomFieldUtils } from "./CustomFieldUtils";
9
+
10
+ function calculateKeys(data: Record<string, unknown>) {
11
+ let count = 0;
12
+ for (const key in data) {
13
+ const item = data[key];
14
+ if (typeof item === "object" && item !== null && !Array.isArray(item)) {
15
+ count += calculateKeys(item as Record<string, unknown>);
16
+ } else {
17
+ count++;
18
+ }
19
+ }
20
+ return count;
21
+ }
22
+
23
+ function parseJsonData(jsonData: unknown): [Record<string, unknown>, number] {
24
+ let data: Record<string, unknown> = {};
25
+ if (jsonData) {
26
+ try {
27
+ data =
28
+ typeof jsonData === "object"
29
+ ? jsonData
30
+ : typeof jsonData === "string"
31
+ ? JSON.parse(jsonData)
32
+ : {};
33
+ } catch {}
34
+ }
35
+ return [data, calculateKeys(data)];
36
+ }
37
+
38
+ /**
39
+ * Custom field window props
40
+ * 自定义字段窗口属性
41
+ */
42
+ export type CustomFieldWindowProps<D extends CustomFieldData> = {
43
+ /**
44
+ * Children creation callback
45
+ * 子元素创建回调
46
+ * @param open Open callback
47
+ * @param label Label
48
+ * @param pc Property count
49
+ * @returns Component
50
+ */
51
+ children: (
52
+ open: (customFields: D[], jsonData?: unknown) => void,
53
+ label: string | undefined,
54
+ pc: number
55
+ ) => React.ReactNode;
56
+
57
+ /**
58
+ * Label
59
+ * 标签
60
+ */
61
+ label?: string;
62
+
63
+ /**
64
+ * Grid props
65
+ * 网格属性
66
+ */
67
+ gridProps?: GridProps;
68
+
69
+ /**
70
+ * JSON data
71
+ * JSON 数据
72
+ */
73
+ jsonData?: unknown;
74
+
75
+ /**
76
+ * Input name
77
+ * 输入框名称
78
+ */
79
+ inputName?: string;
80
+
81
+ /**
82
+ * Input ref
83
+ * 输入框引用
84
+ */
85
+ inputRef?: React.MutableRefObject<HTMLInputElement | null>;
86
+
87
+ /**
88
+ * On update callback
89
+ * 更新回调
90
+ */
91
+ onUpdate?: (data: Record<string, unknown>) => void;
92
+ };
93
+
94
+ /**
95
+ * Custom field window
96
+ * 自定义字段窗口
97
+ * @param props Props
98
+ * @returns Component
99
+ */
100
+ export function CustomFieldWindow<D extends CustomFieldData = CustomFieldData>(
101
+ props: CustomFieldWindowProps<D>
102
+ ) {
103
+ // Validate app
104
+ const app = globalApp;
105
+ if (app == null) {
106
+ throw new Error("No globalApp");
107
+ }
108
+
109
+ const {
110
+ children,
111
+ label = app.get("jsonData"),
112
+ gridProps,
113
+ jsonData,
114
+ inputName = "jsonData",
115
+ inputRef,
116
+ onUpdate
117
+ } = props;
118
+
119
+ const spacing = MUGlobal.half(MUGlobal.pagePaddings);
120
+
121
+ const [count, setCount] = React.useState(0);
122
+
123
+ const [initData, propertyCount] = parseJsonData(jsonData);
124
+
125
+ React.useEffect(() => setCount(propertyCount), [propertyCount]);
126
+
127
+ return (
128
+ <React.Fragment>
129
+ <input type="hidden" name={inputName} ref={inputRef} />
130
+ {children(
131
+ (customFields, jsonData) => {
132
+ const collections: CustomFieldReactCollection<D> = {};
133
+ let data: Record<string, unknown> = {};
134
+ jsonData ??= inputRef?.current?.value;
135
+ if (jsonData) {
136
+ const [d, pc] = parseJsonData(jsonData);
137
+ data = d;
138
+ setCount(pc);
139
+ } else {
140
+ data = initData;
141
+ }
142
+
143
+ app.notifier.confirm(
144
+ label,
145
+ undefined,
146
+ (value) => {
147
+ if (value) {
148
+ if (inputRef?.current) {
149
+ inputRef.current.value = JSON.stringify(data);
150
+ }
151
+ setCount(calculateKeys(data));
152
+ if (onUpdate) onUpdate(data);
153
+ }
154
+ },
155
+ {
156
+ fullScreen: app.smDown,
157
+ inputs: (
158
+ <Stack marginTop={1.5}>
159
+ <Grid
160
+ container
161
+ justifyContent="left"
162
+ spacing={spacing}
163
+ sx={{
164
+ ".MuiTypography-subtitle2": {
165
+ fontWeight: "bold"
166
+ }
167
+ }}
168
+ {...gridProps}
169
+ >
170
+ {CustomFieldUtils.create(
171
+ customFields,
172
+ collections,
173
+ (field) => {
174
+ if (field.name == null) return;
175
+ return Utils.getNestedValue(data, field.name);
176
+ },
177
+ (name, value) => {
178
+ Utils.setNestedValue(data, name, value);
179
+ }
180
+ )}
181
+ </Grid>
182
+ </Stack>
183
+ )
184
+ }
185
+ );
186
+ },
187
+ label,
188
+ count
189
+ )}
190
+ </React.Fragment>
191
+ );
192
+ }
@@ -55,6 +55,7 @@ export const FieldSelect: ICustomFieldReact<IdType> = ({
55
55
  name={name}
56
56
  options={field.options}
57
57
  fullWidth
58
+ value={value}
58
59
  onChange={(event) => {
59
60
  const value = event.target.value;
60
61
  const newValue =
package/src/index.ts CHANGED
@@ -10,6 +10,7 @@ export * from "./app/ReactApp";
10
10
  export * from "./app/ServiceApp";
11
11
 
12
12
  export * from "./custom/CustomFieldUtils";
13
+ export * from "./custom/CustomFieldWindow";
13
14
 
14
15
  export * from "./messages/MessageUtils";
15
16
  export * from "./messages/OperationMessageContainer";