@etsoo/materialui 1.6.12 → 1.6.14
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/lib/cjs/ButtonPopupCheckbox.d.ts +6 -0
- package/lib/cjs/ButtonPopupCheckbox.js +4 -4
- package/lib/cjs/ButtonPopupRadio.d.ts +6 -0
- package/lib/cjs/ButtonPopupRadio.js +4 -4
- package/lib/cjs/DnDSortableList.d.ts +1 -1
- package/lib/cjs/DnDSortableList.js +22 -1
- package/lib/cjs/JsonTextField.d.ts +20 -0
- package/lib/cjs/JsonTextField.js +62 -0
- package/lib/cjs/custom/CustomAttributeArea.d.ts +11 -0
- package/lib/cjs/custom/CustomAttributeArea.js +194 -0
- package/lib/cjs/index.d.ts +2 -0
- package/lib/cjs/index.js +2 -0
- package/lib/mjs/ButtonPopupCheckbox.d.ts +6 -0
- package/lib/mjs/ButtonPopupCheckbox.js +4 -4
- package/lib/mjs/ButtonPopupRadio.d.ts +6 -0
- package/lib/mjs/ButtonPopupRadio.js +4 -4
- package/lib/mjs/DnDSortableList.d.ts +1 -1
- package/lib/mjs/DnDSortableList.js +23 -2
- package/lib/mjs/JsonTextField.d.ts +20 -0
- package/lib/mjs/JsonTextField.js +56 -0
- package/lib/mjs/custom/CustomAttributeArea.d.ts +11 -0
- package/lib/mjs/custom/CustomAttributeArea.js +188 -0
- package/lib/mjs/index.d.ts +2 -0
- package/lib/mjs/index.js +2 -0
- package/package.json +3 -3
- package/src/ButtonPopupCheckbox.tsx +12 -3
- package/src/ButtonPopupRadio.tsx +12 -3
- package/src/DnDSortableList.tsx +31 -4
- package/src/JsonTextField.tsx +104 -0
- package/src/custom/CustomAttributeArea.tsx +454 -0
- package/src/index.ts +2 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import React from "react";
|
|
3
3
|
import { useTheme } from "@mui/material/styles";
|
|
4
|
-
import { useSortable } from "@dnd-kit/react/sortable";
|
|
4
|
+
import { isSortableOperation, useSortable } from "@dnd-kit/react/sortable";
|
|
5
5
|
import { DragDropProvider } from "@dnd-kit/react";
|
|
6
6
|
/**
|
|
7
7
|
* DnD sortable item default style
|
|
@@ -115,7 +115,28 @@ export function DnDSortableList(props) {
|
|
|
115
115
|
}
|
|
116
116
|
};
|
|
117
117
|
}, [items, labelFn, changeItems]);
|
|
118
|
-
|
|
118
|
+
function handleDragEnd(...args) {
|
|
119
|
+
// Event
|
|
120
|
+
const event = args[0];
|
|
121
|
+
// Cancelled
|
|
122
|
+
if (event.canceled)
|
|
123
|
+
return;
|
|
124
|
+
if (isSortableOperation(event.operation) && event.operation.source) {
|
|
125
|
+
const { initialIndex, index } = event.operation.source;
|
|
126
|
+
if (initialIndex === index)
|
|
127
|
+
return;
|
|
128
|
+
// Clone
|
|
129
|
+
const newItems = [...items];
|
|
130
|
+
// Removed item
|
|
131
|
+
const [removed] = newItems.splice(initialIndex, 1);
|
|
132
|
+
// Insert to the destination index
|
|
133
|
+
newItems.splice(index, 0, removed);
|
|
134
|
+
changeItems(newItems);
|
|
135
|
+
// Drag end handler
|
|
136
|
+
onDragEnd?.(newItems, ...args);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return (_jsx(DragDropProvider, { onDragStart: (event) => onDragStart?.(items, event), onDragEnd: (event, manager) => handleDragEnd(event, manager), children: _jsx(Component, { ...componentProps, children: items.map((item, index) => {
|
|
119
140
|
const id = idFn(item);
|
|
120
141
|
return (_jsx(SortableItem, { id: id, index: index, data: item, itemRenderer: itemRenderer, itemStyle: itemStyle }, id));
|
|
121
142
|
}) }) }));
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { TextFieldProps } from "@mui/material/TextField";
|
|
2
|
+
/**
|
|
3
|
+
* JSON text field props
|
|
4
|
+
*/
|
|
5
|
+
export type JsonTextFieldProps = TextFieldProps & {
|
|
6
|
+
/**
|
|
7
|
+
* Whether the value is an array
|
|
8
|
+
*/
|
|
9
|
+
isArray?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Edit button click handler
|
|
12
|
+
*/
|
|
13
|
+
onEdit?: (input: HTMLInputElement) => void;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* JSON text field component
|
|
17
|
+
* @param props Props
|
|
18
|
+
* @returns Component
|
|
19
|
+
*/
|
|
20
|
+
export declare function JsonTextField(props: JsonTextFieldProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useCombinedRefs } from "@etsoo/react";
|
|
3
|
+
import IconButton from "@mui/material/IconButton";
|
|
4
|
+
import InputAdornment from "@mui/material/InputAdornment";
|
|
5
|
+
import TextField from "@mui/material/TextField";
|
|
6
|
+
import EditIcon from "@mui/icons-material/Edit";
|
|
7
|
+
import React from "react";
|
|
8
|
+
import { MUGlobal } from "./MUGlobal";
|
|
9
|
+
import { useRequiredAppContext } from "./app/ReactApp";
|
|
10
|
+
/**
|
|
11
|
+
* JSON text field component
|
|
12
|
+
* @param props Props
|
|
13
|
+
* @returns Component
|
|
14
|
+
*/
|
|
15
|
+
export function JsonTextField(props) {
|
|
16
|
+
// Destruct
|
|
17
|
+
const { fullWidth = true, inputRef, isArray = false, multiline = true, onChange, onEdit, rows = 3, slotProps, ...rest } = props;
|
|
18
|
+
// Slot props
|
|
19
|
+
const { input, inputLabel, ...restSlotProps } = slotProps ?? {};
|
|
20
|
+
const localRef = React.useRef(null);
|
|
21
|
+
// Global app
|
|
22
|
+
const app = useRequiredAppContext();
|
|
23
|
+
return (_jsx(TextField, { fullWidth: fullWidth, inputRef: useCombinedRefs(inputRef, localRef), multiline: multiline, onChange: (event) => {
|
|
24
|
+
const value = event.target.value.trim();
|
|
25
|
+
let errorMessage = "";
|
|
26
|
+
if (value.length > 0) {
|
|
27
|
+
try {
|
|
28
|
+
const parsed = JSON.parse(value);
|
|
29
|
+
if (isArray && !Array.isArray(parsed)) {
|
|
30
|
+
errorMessage =
|
|
31
|
+
app.get("jsonDataArrayError") || "Value must be a JSON array";
|
|
32
|
+
}
|
|
33
|
+
if (typeof parsed !== "object") {
|
|
34
|
+
throw new Error("Parsed value is not an object");
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch (e) {
|
|
38
|
+
errorMessage =
|
|
39
|
+
(app.get("jsonDataError") || "Invalid JSON text") + " - " + e;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
event.target.setCustomValidity(errorMessage);
|
|
43
|
+
event.target.reportValidity();
|
|
44
|
+
onChange?.(event);
|
|
45
|
+
}, rows: rows, slotProps: {
|
|
46
|
+
input: {
|
|
47
|
+
endAdornment: onEdit ? (_jsx(InputAdornment, { position: "end", children: _jsx(IconButton, { onClick: () => onEdit?.(localRef.current), children: _jsx(EditIcon, {}) }) })) : undefined,
|
|
48
|
+
...input
|
|
49
|
+
},
|
|
50
|
+
inputLabel: {
|
|
51
|
+
shrink: MUGlobal.inputFieldShrink,
|
|
52
|
+
...inputLabel
|
|
53
|
+
},
|
|
54
|
+
...restSlotProps
|
|
55
|
+
}, ...rest }));
|
|
56
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { TextFieldProps } from "@mui/material/TextField";
|
|
2
|
+
/**
|
|
3
|
+
* Custom attribute area properties
|
|
4
|
+
*/
|
|
5
|
+
export type CustomAttributeAreaProps = TextFieldProps & {};
|
|
6
|
+
/**
|
|
7
|
+
* Custom attribute area
|
|
8
|
+
* @param props Properties
|
|
9
|
+
* @returns Component
|
|
10
|
+
*/
|
|
11
|
+
export declare function CustomAttributeArea(props: CustomAttributeAreaProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import IconButton from "@mui/material/IconButton";
|
|
3
|
+
import AddIcon from "@mui/icons-material/Add";
|
|
4
|
+
import EditIcon from "@mui/icons-material/Edit";
|
|
5
|
+
import DeleteIcon from "@mui/icons-material/Delete";
|
|
6
|
+
import DragIndicatorIcon from "@mui/icons-material/DragIndicator";
|
|
7
|
+
import React from "react";
|
|
8
|
+
import Card from "@mui/material/Card";
|
|
9
|
+
import CardContent from "@mui/material/CardContent";
|
|
10
|
+
import Grid from "@mui/material/Grid";
|
|
11
|
+
import { CustomFieldSpaceValues } from "@etsoo/appscript";
|
|
12
|
+
import Button from "@mui/material/Button";
|
|
13
|
+
import { Typography } from "@mui/material";
|
|
14
|
+
import { DataTypes, DomUtils } from "@etsoo/shared";
|
|
15
|
+
import { CustomFieldUtils } from "./CustomFieldUtils";
|
|
16
|
+
import { ComboBox } from "../ComboBox";
|
|
17
|
+
import { InputField } from "../InputField";
|
|
18
|
+
import { JsonTextField } from "../JsonTextField";
|
|
19
|
+
import { HBox } from "../FlexBox";
|
|
20
|
+
import { DnDSortableList } from "../DnDSortableList";
|
|
21
|
+
import { useRequiredAppContext } from "../app/ReactApp";
|
|
22
|
+
const size = { xs: 6, sm: 4, lg: 3, xl: 2 };
|
|
23
|
+
const smallSize = { xs: 3, sm: 2, xl: 1 };
|
|
24
|
+
const random4Digit = () => {
|
|
25
|
+
return Math.floor(1000 + Math.random() * 9000);
|
|
26
|
+
};
|
|
27
|
+
const isCamelCase = (name) => {
|
|
28
|
+
return /^[a-z][a-zA-Z0-9]*$/.test(name);
|
|
29
|
+
};
|
|
30
|
+
function InputItemUIs({ data }) {
|
|
31
|
+
// Global app
|
|
32
|
+
const app = useRequiredAppContext();
|
|
33
|
+
// Labels
|
|
34
|
+
const labels = app.getLabels("gridItemProps", "helperText", "label", "mainSlotProps", "nameB", "options", "optionsFormat", "refs", "size", "type");
|
|
35
|
+
const types = Object.keys(CustomFieldUtils.customFieldCreators);
|
|
36
|
+
const nameRef = React.useRef(null);
|
|
37
|
+
const optionsRef = React.useRef(null);
|
|
38
|
+
return (_jsxs(Grid, { container: true, spacing: 2, marginTop: 1, children: [_jsx(Grid, { size: { xs: 12, sm: 6 }, children: _jsx(ComboBox, { name: "type", label: labels.type, inputRequired: true, size: "small", loadData: () => Promise.resolve(types.map((t) => ({ id: t, label: t }))), onValueChange: (item) => {
|
|
39
|
+
const type = item?.id;
|
|
40
|
+
optionsRef.current.disabled =
|
|
41
|
+
type !== "combobox" && type !== "select";
|
|
42
|
+
const nameInput = nameRef.current;
|
|
43
|
+
if (nameInput.value === "" &&
|
|
44
|
+
(type === "amountlabel" || type === "divider" || type === "label")) {
|
|
45
|
+
nameInput.value = type + random4Digit();
|
|
46
|
+
}
|
|
47
|
+
}, idValue: data?.type, fullWidth: true }) }), _jsx(Grid, { size: { xs: 12, sm: 6 }, children: _jsx(ComboBox, { name: "space", label: labels.size, inputRequired: true, size: "small", loadData: () => Promise.resolve(CustomFieldSpaceValues.map((t) => ({ id: t, label: t }))), idValue: data?.space, fullWidth: true }) }), _jsx(Grid, { size: { xs: 12, sm: 6 }, children: _jsx(InputField, { fullWidth: true, required: true, name: "name", size: "small", inputRef: nameRef, slotProps: { htmlInput: { maxLength: 128, readOnly: !!data } }, label: labels.nameB, defaultValue: data?.name ?? "" }) }), _jsx(Grid, { size: { xs: 12, sm: 6 }, children: _jsx(InputField, { fullWidth: true, name: "label", size: "small", slotProps: { htmlInput: { maxLength: 128 } }, label: labels.label, defaultValue: data?.label ?? "" }) }), _jsx(Grid, { size: { xs: 12, sm: 12 }, children: _jsx(InputField, { fullWidth: true, name: "helperText", size: "small", label: labels.helperText, defaultValue: data?.helperText ?? "" }) }), _jsx(Grid, { size: { xs: 12, sm: 12 }, children: _jsx(InputField, { fullWidth: true, name: "options", size: "small", multiline: true, rows: 2, label: labels.options, inputRef: optionsRef, helperText: labels.optionsFormat, slotProps: { htmlInput: { disabled: true } }, defaultValue: data?.options
|
|
48
|
+
? data.options
|
|
49
|
+
.map((o) => `${o.id}=${DataTypes.getListItemLabel(o)}`)
|
|
50
|
+
.join("\n")
|
|
51
|
+
: "" }) }), _jsx(Grid, { size: { xs: 12, sm: 12 }, children: _jsx(JsonTextField, { isArray: true, name: "refs", size: "small", multiline: false, label: labels.refs + " (JSON)", defaultValue: data?.refs ? JSON.stringify(data.refs) : "" }) }), _jsx(Grid, { size: { xs: 12, sm: 12 }, children: _jsx(JsonTextField, { name: "gridItemProps", size: "small", multiline: false, label: labels.gridItemProps + " (JSON)", defaultValue: data?.gridItemProps ? JSON.stringify(data.gridItemProps) : "" }) }), _jsx(Grid, { size: { xs: 12, sm: 12 }, children: _jsx(JsonTextField, { name: "mainSlotProps", size: "small", multiline: false, label: labels.mainSlotProps + " (JSON)", defaultValue: data?.mainSlotProps ? JSON.stringify(data.mainSlotProps) : "", helperText: '{"required":true}' }) })] }));
|
|
52
|
+
}
|
|
53
|
+
function InputUIs({ source, onChange }) {
|
|
54
|
+
// Global app
|
|
55
|
+
const app = useRequiredAppContext();
|
|
56
|
+
// Labels
|
|
57
|
+
const labels = app.getLabels("add", "delete", "edit", "sortTip", "dragIndicator");
|
|
58
|
+
const [items, setItems] = React.useState([]);
|
|
59
|
+
const doChange = (items) => {
|
|
60
|
+
setItems(items);
|
|
61
|
+
onChange(items);
|
|
62
|
+
};
|
|
63
|
+
const editItem = (item) => {
|
|
64
|
+
app.showInputDialog({
|
|
65
|
+
title: item ? labels.edit : labels.add,
|
|
66
|
+
message: "",
|
|
67
|
+
callback: async (form) => {
|
|
68
|
+
// Cancelled
|
|
69
|
+
if (form == null) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
// Validate form
|
|
73
|
+
if (!form.reportValidity()) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
// Form data
|
|
77
|
+
const { typeInput: type, spaceInput: space, name, label, helperText, options, refs, gridItemProps, mainSlotProps } = DomUtils.dataAs(new FormData(form), {
|
|
78
|
+
typeInput: "string",
|
|
79
|
+
spaceInput: "string",
|
|
80
|
+
name: "string",
|
|
81
|
+
label: "string",
|
|
82
|
+
helperText: "string",
|
|
83
|
+
options: "string",
|
|
84
|
+
refs: "string",
|
|
85
|
+
gridItemProps: "string",
|
|
86
|
+
mainSlotProps: "string"
|
|
87
|
+
});
|
|
88
|
+
if (!type || !space || !name) {
|
|
89
|
+
return app.get("noData");
|
|
90
|
+
}
|
|
91
|
+
if (!isCamelCase(name)) {
|
|
92
|
+
DomUtils.setFocus("name", form);
|
|
93
|
+
return app.get("invalidNaming") + " (camelCase)";
|
|
94
|
+
}
|
|
95
|
+
if (type !== "divider" && !label) {
|
|
96
|
+
DomUtils.setFocus("label", form);
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
if (item == null && items.some((item) => item.name === name)) {
|
|
100
|
+
return app.get("itemExists")?.format(name);
|
|
101
|
+
}
|
|
102
|
+
const optionsJson = options
|
|
103
|
+
? options.split("\n").map((line) => {
|
|
104
|
+
const [id, ...labelParts] = line.split("=");
|
|
105
|
+
return { id, label: labelParts.join("=") };
|
|
106
|
+
})
|
|
107
|
+
: undefined;
|
|
108
|
+
const refsJson = refs ? JSON.parse(refs) : undefined;
|
|
109
|
+
const gridItemPropsJson = gridItemProps
|
|
110
|
+
? JSON.parse(gridItemProps)
|
|
111
|
+
: undefined;
|
|
112
|
+
const mainSlotPropsJson = mainSlotProps
|
|
113
|
+
? JSON.parse(mainSlotProps)
|
|
114
|
+
: undefined;
|
|
115
|
+
if (item == null) {
|
|
116
|
+
const newItem = {
|
|
117
|
+
type,
|
|
118
|
+
name,
|
|
119
|
+
space: space,
|
|
120
|
+
label,
|
|
121
|
+
helperText,
|
|
122
|
+
options: optionsJson,
|
|
123
|
+
refs: refsJson,
|
|
124
|
+
gridItemProps: gridItemPropsJson,
|
|
125
|
+
mainSlotProps: mainSlotPropsJson
|
|
126
|
+
};
|
|
127
|
+
doChange([...items, newItem]);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
item.type = type;
|
|
131
|
+
item.space = space;
|
|
132
|
+
item.name = name;
|
|
133
|
+
item.label = label;
|
|
134
|
+
item.helperText = helperText;
|
|
135
|
+
item.options = optionsJson;
|
|
136
|
+
item.refs = refsJson;
|
|
137
|
+
item.gridItemProps = gridItemPropsJson;
|
|
138
|
+
item.mainSlotProps = mainSlotPropsJson;
|
|
139
|
+
doChange([...items]);
|
|
140
|
+
}
|
|
141
|
+
return;
|
|
142
|
+
},
|
|
143
|
+
inputs: _jsx(InputItemUIs, { data: item })
|
|
144
|
+
});
|
|
145
|
+
};
|
|
146
|
+
React.useEffect(() => {
|
|
147
|
+
try {
|
|
148
|
+
if (source) {
|
|
149
|
+
const parsed = JSON.parse(source);
|
|
150
|
+
if (Array.isArray(parsed)) {
|
|
151
|
+
setItems(parsed);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
console.error("Failed to parse source:", error);
|
|
157
|
+
}
|
|
158
|
+
}, [source]);
|
|
159
|
+
return (_jsxs(React.Fragment, { children: [_jsxs(HBox, { marginBottom: 0.5, gap: 1, alignItems: "center", children: [_jsx(Typography, { children: labels.sortTip }), _jsx(Button, { size: "small", color: "primary", variant: "outlined", startIcon: _jsx(AddIcon, {}), onClick: () => editItem(), children: labels.add })] }), _jsx(Card, { children: _jsx(CardContent, { children: _jsx(Grid, { container: true, spacing: 0, children: _jsx(DnDSortableList, { items: items, idField: (item) => item.name, labelField: (item) => item.label || item.name, onChange: (items) => doChange(items), itemRenderer: (data, style, { sortable: { index }, ref, handleRef }) => (_jsxs(Grid, { container: true, size: { xs: 12, sm: 12 }, ref: ref, style: style, gap: 1, children: [_jsx(Grid, { size: smallSize, children: _jsx(IconButton, { style: { cursor: "move" }, size: "small", title: labels.dragIndicator, ref: handleRef, children: _jsx(DragIndicatorIcon, {}) }) }), _jsxs(Grid, { size: size, children: [index + 1, " - ", data.type, " / ", data.space] }), _jsxs(Grid, { size: size, children: [data.name, " - ", data.label] }), _jsxs(Grid, { size: smallSize, children: [_jsx(IconButton, { size: "small", title: labels.delete, onClick: () => doChange(items.filter((item) => item.name !== data.name)), children: _jsx(DeleteIcon, {}) }), _jsx(IconButton, { size: "small", title: labels.edit, onClick: () => editItem(data), children: _jsx(EditIcon, {}) })] })] })) }) }) }) })] }));
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Custom attribute area
|
|
163
|
+
* @param props Properties
|
|
164
|
+
* @returns Component
|
|
165
|
+
*/
|
|
166
|
+
export function CustomAttributeArea(props) {
|
|
167
|
+
// Global app
|
|
168
|
+
const app = useRequiredAppContext();
|
|
169
|
+
// Destruct
|
|
170
|
+
const { label = app.get("attributeDefinition"), ...rest } = props;
|
|
171
|
+
const ref = React.useRef([]);
|
|
172
|
+
const showUI = (input) => {
|
|
173
|
+
app.showInputDialog({
|
|
174
|
+
title: label,
|
|
175
|
+
message: "",
|
|
176
|
+
fullScreen: true,
|
|
177
|
+
callback: (form) => {
|
|
178
|
+
if (form == null) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
input.value = ref.current.length > 0 ? JSON.stringify(ref.current) : "";
|
|
182
|
+
},
|
|
183
|
+
inputs: (_jsx(InputUIs, { source: input.value, onChange: (items) => (ref.current = items) }))
|
|
184
|
+
});
|
|
185
|
+
};
|
|
186
|
+
// Layout
|
|
187
|
+
return _jsx(JsonTextField, { label: label, onEdit: showUI, isArray: true, ...rest });
|
|
188
|
+
}
|
package/lib/mjs/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export * from "./app/ISmartERPUser";
|
|
|
6
6
|
export * from "./app/Labels";
|
|
7
7
|
export * from "./app/ReactApp";
|
|
8
8
|
export * from "./app/ServiceApp";
|
|
9
|
+
export * from "./custom/CustomAttributeArea";
|
|
9
10
|
export * from "./custom/CustomFieldUtils";
|
|
10
11
|
export * from "./custom/CustomFieldViewer";
|
|
11
12
|
export * from "./custom/CustomFieldWindow";
|
|
@@ -73,6 +74,7 @@ export * from "./InputTipField";
|
|
|
73
74
|
export * from "./IntInputField";
|
|
74
75
|
export * from "./ItemList";
|
|
75
76
|
export * from "./JsonDataInput";
|
|
77
|
+
export * from "./JsonTextField";
|
|
76
78
|
export * from "./LineChart";
|
|
77
79
|
export * from "./LinkEx";
|
|
78
80
|
export * from "./ListChooser";
|
package/lib/mjs/index.js
CHANGED
|
@@ -6,6 +6,7 @@ export * from "./app/ISmartERPUser";
|
|
|
6
6
|
export * from "./app/Labels";
|
|
7
7
|
export * from "./app/ReactApp";
|
|
8
8
|
export * from "./app/ServiceApp";
|
|
9
|
+
export * from "./custom/CustomAttributeArea";
|
|
9
10
|
export * from "./custom/CustomFieldUtils";
|
|
10
11
|
export * from "./custom/CustomFieldViewer";
|
|
11
12
|
export * from "./custom/CustomFieldWindow";
|
|
@@ -73,6 +74,7 @@ export * from "./InputTipField";
|
|
|
73
74
|
export * from "./IntInputField";
|
|
74
75
|
export * from "./ItemList";
|
|
75
76
|
export * from "./JsonDataInput";
|
|
77
|
+
export * from "./JsonTextField";
|
|
76
78
|
export * from "./LineChart";
|
|
77
79
|
export * from "./LinkEx";
|
|
78
80
|
export * from "./ListChooser";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@etsoo/materialui",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.14",
|
|
4
4
|
"description": "TypeScript Material-UI Implementation",
|
|
5
5
|
"main": "lib/cjs/index.js",
|
|
6
6
|
"module": "lib/mjs/index.js",
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
"@dnd-kit/react": "^0.3.2",
|
|
40
40
|
"@emotion/react": "^11.14.0",
|
|
41
41
|
"@emotion/styled": "^11.14.1",
|
|
42
|
-
"@etsoo/appscript": "^1.6.
|
|
42
|
+
"@etsoo/appscript": "^1.6.56",
|
|
43
43
|
"@etsoo/notificationbase": "^1.1.66",
|
|
44
|
-
"@etsoo/react": "^1.8.
|
|
44
|
+
"@etsoo/react": "^1.8.76",
|
|
45
45
|
"@etsoo/shared": "^1.2.80",
|
|
46
46
|
"@mui/icons-material": "^7.3.8",
|
|
47
47
|
"@mui/material": "^7.3.8",
|
|
@@ -2,7 +2,7 @@ import Button, { ButtonProps } from "@mui/material/Button";
|
|
|
2
2
|
import Chip from "@mui/material/Chip";
|
|
3
3
|
import React from "react";
|
|
4
4
|
import { DataTypes, IdType } from "@etsoo/shared";
|
|
5
|
-
import Grid from "@mui/material/Grid";
|
|
5
|
+
import Grid, { GridSize } from "@mui/material/Grid";
|
|
6
6
|
import Typography from "@mui/material/Typography";
|
|
7
7
|
import DragIndicatorIcon from "@mui/icons-material/DragIndicator";
|
|
8
8
|
import AddIcon from "@mui/icons-material/Add";
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
DnDSortableListProps,
|
|
19
19
|
DnDSortableListRef
|
|
20
20
|
} from "./DnDSortableList";
|
|
21
|
+
import { ResponsiveStyleValue } from "./ResponsiveStyleValue";
|
|
21
22
|
|
|
22
23
|
type DnDItemType = {
|
|
23
24
|
id: IdType;
|
|
@@ -99,6 +100,11 @@ export type ButtonPopupCheckboxProps<D extends DnDItemType> = Omit<
|
|
|
99
100
|
*/
|
|
100
101
|
required?: boolean;
|
|
101
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Item size
|
|
105
|
+
*/
|
|
106
|
+
itemSize?: ResponsiveStyleValue<GridSize>;
|
|
107
|
+
|
|
102
108
|
/**
|
|
103
109
|
* Value
|
|
104
110
|
*/
|
|
@@ -107,7 +113,7 @@ export type ButtonPopupCheckboxProps<D extends DnDItemType> = Omit<
|
|
|
107
113
|
|
|
108
114
|
type ButtonPopupListProps<D extends DnDItemType> = Pick<
|
|
109
115
|
ButtonPopupCheckboxProps<D>,
|
|
110
|
-
"addSplitter" | "labelField" | "labels" | "onAdd" | "value"
|
|
116
|
+
"addSplitter" | "labelField" | "labels" | "onAdd" | "value" | "itemSize"
|
|
111
117
|
> &
|
|
112
118
|
Required<Pick<ButtonPopupCheckboxProps<D>, "labelFormatter">> & {
|
|
113
119
|
/**
|
|
@@ -130,6 +136,7 @@ function ButtonPopupList<D extends DnDItemType>(
|
|
|
130
136
|
addSplitter = /\s*[,;]\s*/,
|
|
131
137
|
value = [],
|
|
132
138
|
items,
|
|
139
|
+
itemSize = { xs: 12, md: 6, lx: 4 },
|
|
133
140
|
labelField,
|
|
134
141
|
labelFormatter,
|
|
135
142
|
labels,
|
|
@@ -180,7 +187,7 @@ function ButtonPopupList<D extends DnDItemType>(
|
|
|
180
187
|
{ sortable: { index }, ref, handleRef }
|
|
181
188
|
) => (
|
|
182
189
|
<Grid
|
|
183
|
-
size={
|
|
190
|
+
size={itemSize}
|
|
184
191
|
display="flex"
|
|
185
192
|
justifyContent="flex-start"
|
|
186
193
|
alignItems="center"
|
|
@@ -281,6 +288,7 @@ export function ButtonPopupCheckbox<D extends DnDItemType>(
|
|
|
281
288
|
addSplitter,
|
|
282
289
|
value = [],
|
|
283
290
|
inputName,
|
|
291
|
+
itemSize,
|
|
284
292
|
label,
|
|
285
293
|
labelEnd,
|
|
286
294
|
labelFormatter = (data) => {
|
|
@@ -353,6 +361,7 @@ export function ButtonPopupCheckbox<D extends DnDItemType>(
|
|
|
353
361
|
items={items}
|
|
354
362
|
labelFormatter={labelFormatter}
|
|
355
363
|
labelField={labelField}
|
|
364
|
+
itemSize={itemSize}
|
|
356
365
|
labels={labels}
|
|
357
366
|
onAdd={onAdd}
|
|
358
367
|
onValueChange={(ids) => {
|
package/src/ButtonPopupRadio.tsx
CHANGED
|
@@ -2,7 +2,7 @@ import Button, { ButtonProps } from "@mui/material/Button";
|
|
|
2
2
|
import Chip from "@mui/material/Chip";
|
|
3
3
|
import React from "react";
|
|
4
4
|
import { DataTypes, IdType, NumberUtils } from "@etsoo/shared";
|
|
5
|
-
import Grid from "@mui/material/Grid";
|
|
5
|
+
import Grid, { GridSize } from "@mui/material/Grid";
|
|
6
6
|
import Typography from "@mui/material/Typography";
|
|
7
7
|
import AddIcon from "@mui/icons-material/Add";
|
|
8
8
|
import FormControlLabel from "@mui/material/FormControlLabel";
|
|
@@ -12,6 +12,7 @@ import FormLabel from "@mui/material/FormLabel";
|
|
|
12
12
|
import RadioGroup from "@mui/material/RadioGroup";
|
|
13
13
|
import { HBox, VBox } from "./FlexBox";
|
|
14
14
|
import { useRequiredAppContext } from "./app/ReactApp";
|
|
15
|
+
import { ResponsiveStyleValue } from "./ResponsiveStyleValue";
|
|
15
16
|
|
|
16
17
|
type DnDItemType = {
|
|
17
18
|
id: IdType;
|
|
@@ -93,6 +94,11 @@ export type ButtonPopupRadioProps<D extends DnDItemType> = Omit<
|
|
|
93
94
|
*/
|
|
94
95
|
required?: boolean;
|
|
95
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Item size
|
|
99
|
+
*/
|
|
100
|
+
itemSize?: ResponsiveStyleValue<GridSize>;
|
|
101
|
+
|
|
96
102
|
/**
|
|
97
103
|
* Value
|
|
98
104
|
*/
|
|
@@ -101,7 +107,7 @@ export type ButtonPopupRadioProps<D extends DnDItemType> = Omit<
|
|
|
101
107
|
|
|
102
108
|
type ButtonPopupListProps<D extends DnDItemType> = Pick<
|
|
103
109
|
ButtonPopupRadioProps<D>,
|
|
104
|
-
"addSplitter" | "labels" | "onAdd" | "value"
|
|
110
|
+
"addSplitter" | "labels" | "onAdd" | "value" | "itemSize"
|
|
105
111
|
> &
|
|
106
112
|
Required<Pick<ButtonPopupRadioProps<D>, "labelFormatter">> & {
|
|
107
113
|
/**
|
|
@@ -126,6 +132,7 @@ function ButtonPopupList<D extends DnDItemType>(
|
|
|
126
132
|
items,
|
|
127
133
|
labelFormatter,
|
|
128
134
|
labels,
|
|
135
|
+
itemSize = { xs: 12, md: 6, lx: 4 },
|
|
129
136
|
onAdd,
|
|
130
137
|
onValueChange
|
|
131
138
|
} = props;
|
|
@@ -159,7 +166,7 @@ function ButtonPopupList<D extends DnDItemType>(
|
|
|
159
166
|
<Grid container spacing={0}>
|
|
160
167
|
{items.map((item) => (
|
|
161
168
|
<Grid
|
|
162
|
-
size={
|
|
169
|
+
size={itemSize}
|
|
163
170
|
display="flex"
|
|
164
171
|
justifyContent="flex-start"
|
|
165
172
|
alignItems="center"
|
|
@@ -233,6 +240,7 @@ export function ButtonPopupRadio<D extends DnDItemType>(
|
|
|
233
240
|
const {
|
|
234
241
|
addSplitter,
|
|
235
242
|
inputName,
|
|
243
|
+
itemSize,
|
|
236
244
|
label,
|
|
237
245
|
labelEnd,
|
|
238
246
|
labelFormatter = (data) => {
|
|
@@ -305,6 +313,7 @@ export function ButtonPopupRadio<D extends DnDItemType>(
|
|
|
305
313
|
addSplitter={addSplitter}
|
|
306
314
|
value={currentValue}
|
|
307
315
|
items={items}
|
|
316
|
+
itemSize={itemSize}
|
|
308
317
|
labelFormatter={labelFormatter}
|
|
309
318
|
labels={labels}
|
|
310
319
|
onAdd={onAdd}
|
package/src/DnDSortableList.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { DataTypes, IdType } from "@etsoo/shared";
|
|
2
2
|
import React from "react";
|
|
3
3
|
import { CSSProperties, Theme, useTheme } from "@mui/material/styles";
|
|
4
|
-
import { useSortable } from "@dnd-kit/react/sortable";
|
|
4
|
+
import { isSortableOperation, useSortable } from "@dnd-kit/react/sortable";
|
|
5
5
|
import { DragDropEvents, DragDropProvider } from "@dnd-kit/react";
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -126,7 +126,7 @@ export type DnDSortableListProps<
|
|
|
126
126
|
*/
|
|
127
127
|
onDragStart?: (
|
|
128
128
|
items: D[],
|
|
129
|
-
|
|
129
|
+
event: Parameters<DragDropEvents["dragstart"]>[0]
|
|
130
130
|
) => void;
|
|
131
131
|
|
|
132
132
|
/**
|
|
@@ -293,10 +293,37 @@ export function DnDSortableList<
|
|
|
293
293
|
};
|
|
294
294
|
}, [items, labelFn, changeItems]);
|
|
295
295
|
|
|
296
|
+
function handleDragEnd(...args: Parameters<DragDropEvents["dragend"]>) {
|
|
297
|
+
// Event
|
|
298
|
+
const event = args[0];
|
|
299
|
+
|
|
300
|
+
// Cancelled
|
|
301
|
+
if (event.canceled) return;
|
|
302
|
+
|
|
303
|
+
if (isSortableOperation(event.operation) && event.operation.source) {
|
|
304
|
+
const { initialIndex, index } = event.operation.source;
|
|
305
|
+
if (initialIndex === index) return;
|
|
306
|
+
|
|
307
|
+
// Clone
|
|
308
|
+
const newItems = [...items];
|
|
309
|
+
|
|
310
|
+
// Removed item
|
|
311
|
+
const [removed] = newItems.splice(initialIndex, 1);
|
|
312
|
+
|
|
313
|
+
// Insert to the destination index
|
|
314
|
+
newItems.splice(index, 0, removed);
|
|
315
|
+
|
|
316
|
+
changeItems(newItems);
|
|
317
|
+
|
|
318
|
+
// Drag end handler
|
|
319
|
+
onDragEnd?.(newItems, ...args);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
296
323
|
return (
|
|
297
324
|
<DragDropProvider
|
|
298
|
-
onDragStart={(
|
|
299
|
-
onDragEnd={(
|
|
325
|
+
onDragStart={(event) => onDragStart?.(items, event)}
|
|
326
|
+
onDragEnd={(event, manager) => handleDragEnd(event, manager)}
|
|
300
327
|
>
|
|
301
328
|
<Component {...componentProps}>
|
|
302
329
|
{items.map((item, index) => {
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { useCombinedRefs } from "@etsoo/react";
|
|
2
|
+
import IconButton from "@mui/material/IconButton";
|
|
3
|
+
import InputAdornment from "@mui/material/InputAdornment";
|
|
4
|
+
import TextField, { TextFieldProps } from "@mui/material/TextField";
|
|
5
|
+
import EditIcon from "@mui/icons-material/Edit";
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { MUGlobal } from "./MUGlobal";
|
|
8
|
+
import { useRequiredAppContext } from "./app/ReactApp";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* JSON text field props
|
|
12
|
+
*/
|
|
13
|
+
export type JsonTextFieldProps = TextFieldProps & {
|
|
14
|
+
/**
|
|
15
|
+
* Whether the value is an array
|
|
16
|
+
*/
|
|
17
|
+
isArray?: boolean;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Edit button click handler
|
|
21
|
+
*/
|
|
22
|
+
onEdit?: (input: HTMLInputElement) => void;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* JSON text field component
|
|
27
|
+
* @param props Props
|
|
28
|
+
* @returns Component
|
|
29
|
+
*/
|
|
30
|
+
export function JsonTextField(props: JsonTextFieldProps) {
|
|
31
|
+
// Destruct
|
|
32
|
+
const {
|
|
33
|
+
fullWidth = true,
|
|
34
|
+
inputRef,
|
|
35
|
+
isArray = false,
|
|
36
|
+
multiline = true,
|
|
37
|
+
onChange,
|
|
38
|
+
onEdit,
|
|
39
|
+
rows = 3,
|
|
40
|
+
slotProps,
|
|
41
|
+
...rest
|
|
42
|
+
} = props;
|
|
43
|
+
|
|
44
|
+
// Slot props
|
|
45
|
+
const { input, inputLabel, ...restSlotProps } = slotProps ?? {};
|
|
46
|
+
|
|
47
|
+
const localRef = React.useRef<HTMLInputElement>(null);
|
|
48
|
+
|
|
49
|
+
// Global app
|
|
50
|
+
const app = useRequiredAppContext();
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<TextField
|
|
54
|
+
fullWidth={fullWidth}
|
|
55
|
+
inputRef={useCombinedRefs(inputRef, localRef)}
|
|
56
|
+
multiline={multiline}
|
|
57
|
+
onChange={(event) => {
|
|
58
|
+
const value = event.target.value.trim();
|
|
59
|
+
let errorMessage = "";
|
|
60
|
+
|
|
61
|
+
if (value.length > 0) {
|
|
62
|
+
try {
|
|
63
|
+
const parsed = JSON.parse(value);
|
|
64
|
+
if (isArray && !Array.isArray(parsed)) {
|
|
65
|
+
errorMessage =
|
|
66
|
+
app.get("jsonDataArrayError") || "Value must be a JSON array";
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (typeof parsed !== "object") {
|
|
70
|
+
throw new Error("Parsed value is not an object");
|
|
71
|
+
}
|
|
72
|
+
} catch (e) {
|
|
73
|
+
errorMessage =
|
|
74
|
+
(app.get("jsonDataError") || "Invalid JSON text") + " - " + e;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
event.target.setCustomValidity(errorMessage);
|
|
79
|
+
event.target.reportValidity();
|
|
80
|
+
|
|
81
|
+
onChange?.(event);
|
|
82
|
+
}}
|
|
83
|
+
rows={rows}
|
|
84
|
+
slotProps={{
|
|
85
|
+
input: {
|
|
86
|
+
endAdornment: onEdit ? (
|
|
87
|
+
<InputAdornment position="end">
|
|
88
|
+
<IconButton onClick={() => onEdit?.(localRef.current!)}>
|
|
89
|
+
<EditIcon />
|
|
90
|
+
</IconButton>
|
|
91
|
+
</InputAdornment>
|
|
92
|
+
) : undefined,
|
|
93
|
+
...input
|
|
94
|
+
},
|
|
95
|
+
inputLabel: {
|
|
96
|
+
shrink: MUGlobal.inputFieldShrink,
|
|
97
|
+
...inputLabel
|
|
98
|
+
},
|
|
99
|
+
...restSlotProps
|
|
100
|
+
}}
|
|
101
|
+
{...rest}
|
|
102
|
+
/>
|
|
103
|
+
);
|
|
104
|
+
}
|