@etsoo/materialui 1.5.48 → 1.5.50

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.
@@ -8,7 +8,6 @@ const jsx_runtime_1 = require("react/jsx-runtime");
8
8
  const Button_1 = __importDefault(require("@mui/material/Button"));
9
9
  const Chip_1 = __importDefault(require("@mui/material/Chip"));
10
10
  const react_1 = __importDefault(require("react"));
11
- const shared_1 = require("@etsoo/shared");
12
11
  const FormGroup_1 = __importDefault(require("@mui/material/FormGroup"));
13
12
  const Grid_1 = __importDefault(require("@mui/material/Grid"));
14
13
  const Typography_1 = __importDefault(require("@mui/material/Typography"));
@@ -23,25 +22,27 @@ const FlexBox_1 = require("./FlexBox");
23
22
  const ReactApp_1 = require("./app/ReactApp");
24
23
  function ButtonPopupList(props) {
25
24
  // Destruct
26
- const { addSplitter = /\s*[,;]\s*/, ids, labelField, labelFormatter = (data) => {
27
- console.log("data", data);
28
- if (labelField in data) {
29
- return data[labelField];
30
- }
31
- return data.id.toString();
32
- }, labels, loadData, onAdd } = props;
25
+ const { addSplitter = /\s*[,;]\s*/, ids = [], items, labelField, labelFormatter, labels, onAdd, onValueChange } = props;
33
26
  // Methods
34
27
  const dndRef = react_1.default.createRef();
35
28
  // Ref
36
29
  const inputRef = react_1.default.useRef(null);
30
+ // State
31
+ const [selectedIds, setSelectedIds] = react_1.default.useState([]);
37
32
  react_1.default.useEffect(() => {
38
- loadData(ids).then((data) => {
39
- if (data == null || dndRef.current == null)
40
- return;
41
- dndRef.current.addItems(data);
42
- });
33
+ // Sort items by ids for first load
34
+ items.sortByProperty("id", ids);
35
+ // Set selected ids
36
+ setSelectedIds([...ids]);
43
37
  }, [ids]);
44
- return ((0, jsx_runtime_1.jsxs)(FlexBox_1.VBox, { gap: 2, children: [(0, jsx_runtime_1.jsx)(FormGroup_1.default, { children: (0, jsx_runtime_1.jsx)(Grid_1.default, { container: true, spacing: 0, children: (0, jsx_runtime_1.jsx)(DnDList_1.DnDList, { items: [], labelField: labelField, itemRenderer: (item, index, nodeRef, actionNodeRef) => ((0, jsx_runtime_1.jsxs)(Grid_1.default, { size: { xs: 12, md: 6, lg: 4 }, display: "flex", justifyContent: "flex-start", alignItems: "center", gap: 1, ...nodeRef, children: [(0, jsx_runtime_1.jsx)(IconButton_1.default, { style: { cursor: "move" }, size: "small", title: labels?.dragIndicator, ...actionNodeRef, children: (0, jsx_runtime_1.jsx)(DragIndicator_1.default, {}) }), (0, jsx_runtime_1.jsx)(FormControlLabel_1.default, { control: (0, jsx_runtime_1.jsx)(Checkbox_1.default, { name: "item", value: item.id, defaultChecked: ids?.includes(item.id) }), label: `${index + 1}. ${labelFormatter(item)}` })] })), height: 200, mRef: dndRef }) }) }), onAdd && ((0, jsx_runtime_1.jsxs)(FlexBox_1.HBox, { gap: 1, children: [(0, jsx_runtime_1.jsx)(TextField_1.default, { variant: "outlined", label: labels?.more, fullWidth: true, inputRef: inputRef }), (0, jsx_runtime_1.jsx)(Button_1.default, { sx: { width: "120px" }, variant: "contained", startIcon: (0, jsx_runtime_1.jsx)(Add_1.default, {}), size: "small", onClick: async () => {
38
+ return ((0, jsx_runtime_1.jsxs)(FlexBox_1.VBox, { gap: 2, children: [(0, jsx_runtime_1.jsx)(FormGroup_1.default, { children: (0, jsx_runtime_1.jsx)(Grid_1.default, { container: true, spacing: 0, children: (0, jsx_runtime_1.jsx)(DnDList_1.DnDList, { items: items, labelField: labelField, itemRenderer: (item, index, nodeRef, actionNodeRef) => ((0, jsx_runtime_1.jsxs)(Grid_1.default, { size: { xs: 12, md: 6, lg: 4 }, display: "flex", justifyContent: "flex-start", alignItems: "center", gap: 1, ...nodeRef, children: [(0, jsx_runtime_1.jsx)(IconButton_1.default, { style: { cursor: "move" }, size: "small", title: labels?.dragIndicator, ...actionNodeRef, children: (0, jsx_runtime_1.jsx)(DragIndicator_1.default, {}) }), (0, jsx_runtime_1.jsx)(FormControlLabel_1.default, { control: (0, jsx_runtime_1.jsx)(Checkbox_1.default, { name: "item", value: item.id, checked: selectedIds.includes(item.id), onChange: (e) => {
39
+ const checked = e.target.checked;
40
+ const newIds = [
41
+ ...selectedIds.toggleItem(item.id, checked)
42
+ ];
43
+ setSelectedIds(newIds);
44
+ onValueChange(newIds);
45
+ } }), label: `${index + 1}. ${labelFormatter(item)}` })] })), height: 200, mRef: dndRef }) }) }), onAdd && ((0, jsx_runtime_1.jsxs)(FlexBox_1.HBox, { gap: 1, children: [(0, jsx_runtime_1.jsx)(TextField_1.default, { variant: "outlined", label: labels?.more, fullWidth: true, inputRef: inputRef }), (0, jsx_runtime_1.jsx)(Button_1.default, { sx: { width: "120px" }, variant: "contained", startIcon: (0, jsx_runtime_1.jsx)(Add_1.default, {}), size: "small", onClick: async () => {
45
46
  if (inputRef.current == null)
46
47
  return;
47
48
  const input = inputRef.current.value.trim();
@@ -71,7 +72,12 @@ function ButtonPopupCheckbox(props) {
71
72
  // App
72
73
  const app = (0, ReactApp_1.useRequiredAppContext)();
73
74
  // Destruct
74
- const { addSplitter, ids, inputName, label, labelEnd, labelFormatter, labelField, labels = {}, loadData, onAdd, onValueChange, popupTitle = label, popupMessage, required = false, sx = { gap: 1, justifyContent: "flex-start" }, variant = "outlined", ...rest } = props;
75
+ const { addSplitter, ids, inputName, label, labelEnd, labelFormatter = (data) => {
76
+ if (labelField in data) {
77
+ return data[labelField];
78
+ }
79
+ return data.id.toString();
80
+ }, labelField, labels = {}, loadData, onAdd, onValueChange, popupTitle = label, popupMessage, required = false, sx = { gap: 1, justifyContent: "flex-start" }, variant = "outlined", ...rest } = props;
75
81
  // Default labels
76
82
  if (!labels.add)
77
83
  labels.add = app.get("add");
@@ -80,34 +86,44 @@ function ButtonPopupCheckbox(props) {
80
86
  if (!labels.more)
81
87
  labels.more = app.get("more");
82
88
  // State
83
- const [values, setValues] = react_1.default.useState([]);
89
+ const [items, setItems] = react_1.default.useState([]);
90
+ const [selectedIds, setSelectedIds] = react_1.default.useState();
91
+ react_1.default.useEffect(() => {
92
+ // Load data
93
+ loadData().then((data) => {
94
+ if (data != null) {
95
+ setItems(data);
96
+ }
97
+ });
98
+ }, []);
84
99
  react_1.default.useEffect(() => {
85
- if (ids == null)
86
- return;
87
- setValues(ids);
100
+ // Set selected ids
101
+ setSelectedIds(ids);
88
102
  }, [ids]);
103
+ // Selected ids
104
+ const tempSelectedIds = react_1.default.useRef();
89
105
  // Click handler
90
106
  const clickHandler = () => {
91
107
  app.showInputDialog({
92
108
  title: popupTitle,
93
109
  message: popupMessage,
94
110
  callback: (form) => {
95
- if (form == null)
111
+ if (form == null || tempSelectedIds.current == null)
96
112
  return;
97
- // Form data
98
- const { item = [] } = shared_1.DomUtils.dataAs(new FormData(form), {
99
- item: "string[]"
100
- });
101
- if (required && item.length === 0) {
102
- shared_1.DomUtils.setFocus("item", form);
103
- return false;
104
- }
105
- setValues(item);
106
- onValueChange?.(item);
113
+ const ids = tempSelectedIds.current;
114
+ setSelectedIds(ids);
115
+ onValueChange?.(ids);
107
116
  },
108
- inputs: ((0, jsx_runtime_1.jsx)(ButtonPopupList, { addSplitter: addSplitter, ids: values, labelFormatter: labelFormatter, labelField: labelField, labels: labels, loadData: loadData, onAdd: onAdd })),
117
+ inputs: ((0, jsx_runtime_1.jsx)(ButtonPopupList, { addSplitter: addSplitter, ids: selectedIds, items: items, labelFormatter: labelFormatter, labelField: labelField, labels: labels, onAdd: onAdd, onValueChange: (ids) => {
118
+ tempSelectedIds.current = ids;
119
+ } })),
109
120
  fullScreen: app.smDown
110
121
  });
111
122
  };
112
- return ((0, jsx_runtime_1.jsxs)(react_1.default.Fragment, { children: [(0, jsx_runtime_1.jsx)("input", { type: "text", style: { position: "absolute", opacity: 0, width: 0 }, name: inputName, required: required, defaultValue: values.join(",") }), (0, jsx_runtime_1.jsxs)(Button_1.default, { variant: variant, sx: sx, onClick: () => clickHandler(), ...rest, children: [label && (0, jsx_runtime_1.jsxs)(Typography_1.default, { variant: "body2", children: [label, ":"] }), values.map((id) => ((0, jsx_runtime_1.jsx)(Chip_1.default, { size: "small", label: id }, id))), labelEnd && (0, jsx_runtime_1.jsx)(Typography_1.default, { variant: "caption", children: labelEnd })] })] }));
123
+ return ((0, jsx_runtime_1.jsxs)(react_1.default.Fragment, { children: [(0, jsx_runtime_1.jsx)("input", { type: "text", style: { position: "absolute", opacity: 0, width: 0 }, name: inputName, required: required, defaultValue: selectedIds?.join(",") }), (0, jsx_runtime_1.jsxs)(Button_1.default, { variant: variant, sx: sx, onClick: () => clickHandler(), ...rest, disabled: !items || items.length === 0, children: [label && (0, jsx_runtime_1.jsxs)(Typography_1.default, { variant: "body2", children: [label, ":"] }), selectedIds?.map((id) => {
124
+ const item = items.find((item) => item.id === id);
125
+ if (item == null)
126
+ return null;
127
+ return (0, jsx_runtime_1.jsx)(Chip_1.default, { size: "small", label: labelFormatter(item) }, id);
128
+ }), labelEnd && (0, jsx_runtime_1.jsx)(Typography_1.default, { variant: "caption", children: labelEnd })] })] }));
113
129
  }
@@ -2,7 +2,6 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import Button from "@mui/material/Button";
3
3
  import Chip from "@mui/material/Chip";
4
4
  import React from "react";
5
- import { DomUtils } from "@etsoo/shared";
6
5
  import FormGroup from "@mui/material/FormGroup";
7
6
  import Grid from "@mui/material/Grid";
8
7
  import Typography from "@mui/material/Typography";
@@ -17,25 +16,27 @@ import { HBox, VBox } from "./FlexBox";
17
16
  import { useRequiredAppContext } from "./app/ReactApp";
18
17
  function ButtonPopupList(props) {
19
18
  // Destruct
20
- const { addSplitter = /\s*[,;]\s*/, ids, labelField, labelFormatter = (data) => {
21
- console.log("data", data);
22
- if (labelField in data) {
23
- return data[labelField];
24
- }
25
- return data.id.toString();
26
- }, labels, loadData, onAdd } = props;
19
+ const { addSplitter = /\s*[,;]\s*/, ids = [], items, labelField, labelFormatter, labels, onAdd, onValueChange } = props;
27
20
  // Methods
28
21
  const dndRef = React.createRef();
29
22
  // Ref
30
23
  const inputRef = React.useRef(null);
24
+ // State
25
+ const [selectedIds, setSelectedIds] = React.useState([]);
31
26
  React.useEffect(() => {
32
- loadData(ids).then((data) => {
33
- if (data == null || dndRef.current == null)
34
- return;
35
- dndRef.current.addItems(data);
36
- });
27
+ // Sort items by ids for first load
28
+ items.sortByProperty("id", ids);
29
+ // Set selected ids
30
+ setSelectedIds([...ids]);
37
31
  }, [ids]);
38
- return (_jsxs(VBox, { gap: 2, children: [_jsx(FormGroup, { children: _jsx(Grid, { container: true, spacing: 0, children: _jsx(DnDList, { items: [], labelField: labelField, itemRenderer: (item, index, nodeRef, actionNodeRef) => (_jsxs(Grid, { size: { xs: 12, md: 6, lg: 4 }, display: "flex", justifyContent: "flex-start", alignItems: "center", gap: 1, ...nodeRef, children: [_jsx(IconButton, { style: { cursor: "move" }, size: "small", title: labels?.dragIndicator, ...actionNodeRef, children: _jsx(DragIndicatorIcon, {}) }), _jsx(FormControlLabel, { control: _jsx(Checkbox, { name: "item", value: item.id, defaultChecked: ids?.includes(item.id) }), label: `${index + 1}. ${labelFormatter(item)}` })] })), height: 200, mRef: dndRef }) }) }), onAdd && (_jsxs(HBox, { gap: 1, children: [_jsx(TextField, { variant: "outlined", label: labels?.more, fullWidth: true, inputRef: inputRef }), _jsx(Button, { sx: { width: "120px" }, variant: "contained", startIcon: _jsx(AddIcon, {}), size: "small", onClick: async () => {
32
+ return (_jsxs(VBox, { gap: 2, children: [_jsx(FormGroup, { children: _jsx(Grid, { container: true, spacing: 0, children: _jsx(DnDList, { items: items, labelField: labelField, itemRenderer: (item, index, nodeRef, actionNodeRef) => (_jsxs(Grid, { size: { xs: 12, md: 6, lg: 4 }, display: "flex", justifyContent: "flex-start", alignItems: "center", gap: 1, ...nodeRef, children: [_jsx(IconButton, { style: { cursor: "move" }, size: "small", title: labels?.dragIndicator, ...actionNodeRef, children: _jsx(DragIndicatorIcon, {}) }), _jsx(FormControlLabel, { control: _jsx(Checkbox, { name: "item", value: item.id, checked: selectedIds.includes(item.id), onChange: (e) => {
33
+ const checked = e.target.checked;
34
+ const newIds = [
35
+ ...selectedIds.toggleItem(item.id, checked)
36
+ ];
37
+ setSelectedIds(newIds);
38
+ onValueChange(newIds);
39
+ } }), label: `${index + 1}. ${labelFormatter(item)}` })] })), height: 200, mRef: dndRef }) }) }), onAdd && (_jsxs(HBox, { gap: 1, children: [_jsx(TextField, { variant: "outlined", label: labels?.more, fullWidth: true, inputRef: inputRef }), _jsx(Button, { sx: { width: "120px" }, variant: "contained", startIcon: _jsx(AddIcon, {}), size: "small", onClick: async () => {
39
40
  if (inputRef.current == null)
40
41
  return;
41
42
  const input = inputRef.current.value.trim();
@@ -65,7 +66,12 @@ export function ButtonPopupCheckbox(props) {
65
66
  // App
66
67
  const app = useRequiredAppContext();
67
68
  // Destruct
68
- const { addSplitter, ids, inputName, label, labelEnd, labelFormatter, labelField, labels = {}, loadData, onAdd, onValueChange, popupTitle = label, popupMessage, required = false, sx = { gap: 1, justifyContent: "flex-start" }, variant = "outlined", ...rest } = props;
69
+ const { addSplitter, ids, inputName, label, labelEnd, labelFormatter = (data) => {
70
+ if (labelField in data) {
71
+ return data[labelField];
72
+ }
73
+ return data.id.toString();
74
+ }, labelField, labels = {}, loadData, onAdd, onValueChange, popupTitle = label, popupMessage, required = false, sx = { gap: 1, justifyContent: "flex-start" }, variant = "outlined", ...rest } = props;
69
75
  // Default labels
70
76
  if (!labels.add)
71
77
  labels.add = app.get("add");
@@ -74,34 +80,44 @@ export function ButtonPopupCheckbox(props) {
74
80
  if (!labels.more)
75
81
  labels.more = app.get("more");
76
82
  // State
77
- const [values, setValues] = React.useState([]);
83
+ const [items, setItems] = React.useState([]);
84
+ const [selectedIds, setSelectedIds] = React.useState();
85
+ React.useEffect(() => {
86
+ // Load data
87
+ loadData().then((data) => {
88
+ if (data != null) {
89
+ setItems(data);
90
+ }
91
+ });
92
+ }, []);
78
93
  React.useEffect(() => {
79
- if (ids == null)
80
- return;
81
- setValues(ids);
94
+ // Set selected ids
95
+ setSelectedIds(ids);
82
96
  }, [ids]);
97
+ // Selected ids
98
+ const tempSelectedIds = React.useRef();
83
99
  // Click handler
84
100
  const clickHandler = () => {
85
101
  app.showInputDialog({
86
102
  title: popupTitle,
87
103
  message: popupMessage,
88
104
  callback: (form) => {
89
- if (form == null)
105
+ if (form == null || tempSelectedIds.current == null)
90
106
  return;
91
- // Form data
92
- const { item = [] } = DomUtils.dataAs(new FormData(form), {
93
- item: "string[]"
94
- });
95
- if (required && item.length === 0) {
96
- DomUtils.setFocus("item", form);
97
- return false;
98
- }
99
- setValues(item);
100
- onValueChange?.(item);
107
+ const ids = tempSelectedIds.current;
108
+ setSelectedIds(ids);
109
+ onValueChange?.(ids);
101
110
  },
102
- inputs: (_jsx(ButtonPopupList, { addSplitter: addSplitter, ids: values, labelFormatter: labelFormatter, labelField: labelField, labels: labels, loadData: loadData, onAdd: onAdd })),
111
+ inputs: (_jsx(ButtonPopupList, { addSplitter: addSplitter, ids: selectedIds, items: items, labelFormatter: labelFormatter, labelField: labelField, labels: labels, onAdd: onAdd, onValueChange: (ids) => {
112
+ tempSelectedIds.current = ids;
113
+ } })),
103
114
  fullScreen: app.smDown
104
115
  });
105
116
  };
106
- return (_jsxs(React.Fragment, { children: [_jsx("input", { type: "text", style: { position: "absolute", opacity: 0, width: 0 }, name: inputName, required: required, defaultValue: values.join(",") }), _jsxs(Button, { variant: variant, sx: sx, onClick: () => clickHandler(), ...rest, children: [label && _jsxs(Typography, { variant: "body2", children: [label, ":"] }), values.map((id) => (_jsx(Chip, { size: "small", label: id }, id))), labelEnd && _jsx(Typography, { variant: "caption", children: labelEnd })] })] }));
117
+ return (_jsxs(React.Fragment, { children: [_jsx("input", { type: "text", style: { position: "absolute", opacity: 0, width: 0 }, name: inputName, required: required, defaultValue: selectedIds?.join(",") }), _jsxs(Button, { variant: variant, sx: sx, onClick: () => clickHandler(), ...rest, disabled: !items || items.length === 0, children: [label && _jsxs(Typography, { variant: "body2", children: [label, ":"] }), selectedIds?.map((id) => {
118
+ const item = items.find((item) => item.id === id);
119
+ if (item == null)
120
+ return null;
121
+ return _jsx(Chip, { size: "small", label: labelFormatter(item) }, id);
122
+ }), labelEnd && _jsx(Typography, { variant: "caption", children: labelEnd })] })] }));
107
123
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@etsoo/materialui",
3
- "version": "1.5.48",
3
+ "version": "1.5.50",
4
4
  "description": "TypeScript Material-UI Implementation",
5
5
  "main": "lib/cjs/index.js",
6
6
  "module": "lib/mjs/index.js",
@@ -40,16 +40,16 @@
40
40
  "@dnd-kit/sortable": "^10.0.0",
41
41
  "@emotion/react": "^11.14.0",
42
42
  "@emotion/styled": "^11.14.0",
43
- "@etsoo/appscript": "^1.6.34",
44
- "@etsoo/notificationbase": "^1.1.61",
45
- "@etsoo/react": "^1.8.44",
46
- "@etsoo/shared": "^1.2.72",
43
+ "@etsoo/appscript": "^1.6.36",
44
+ "@etsoo/notificationbase": "^1.1.62",
45
+ "@etsoo/react": "^1.8.45",
46
+ "@etsoo/shared": "^1.2.74",
47
47
  "@mui/icons-material": "^7.1.0",
48
48
  "@mui/material": "^7.1.0",
49
- "@mui/x-data-grid": "^8.3.1",
49
+ "@mui/x-data-grid": "^8.4.0",
50
50
  "chart.js": "^4.4.9",
51
51
  "chartjs-plugin-datalabels": "^2.2.0",
52
- "dompurify": "^3.2.5",
52
+ "dompurify": "^3.2.6",
53
53
  "eventemitter3": "^5.0.1",
54
54
  "pica": "^9.0.1",
55
55
  "pulltorefreshjs": "^0.1.22",
@@ -76,14 +76,14 @@
76
76
  "@testing-library/react": "^16.3.0",
77
77
  "@types/pica": "^9.0.5",
78
78
  "@types/pulltorefreshjs": "^0.1.7",
79
- "@types/react": "^18.3.21",
79
+ "@types/react": "^18.3.22",
80
80
  "@types/react-avatar-editor": "^13.0.4",
81
81
  "@types/react-dom": "^18.3.7",
82
82
  "@types/react-input-mask": "^3.0.6",
83
83
  "@types/react-window": "^1.8.8",
84
- "@vitejs/plugin-react": "^4.4.1",
84
+ "@vitejs/plugin-react": "^4.5.0",
85
85
  "jsdom": "^26.1.0",
86
86
  "typescript": "^5.8.3",
87
- "vitest": "^3.1.3"
87
+ "vitest": "^3.1.4"
88
88
  }
89
89
  }
@@ -1,7 +1,7 @@
1
1
  import Button, { ButtonProps } from "@mui/material/Button";
2
2
  import Chip from "@mui/material/Chip";
3
3
  import React from "react";
4
- import { DataTypes, DomUtils, IdType } from "@etsoo/shared";
4
+ import { DataTypes, IdType } from "@etsoo/shared";
5
5
  import FormGroup from "@mui/material/FormGroup";
6
6
  import Grid from "@mui/material/Grid";
7
7
  import Typography from "@mui/material/Typography";
@@ -103,14 +103,20 @@ export type ButtonPopupCheckboxProps<D extends DnDItemType> = Omit<
103
103
 
104
104
  type ButtonPopupListProps<D extends DnDItemType> = Pick<
105
105
  ButtonPopupCheckboxProps<D>,
106
- | "addSplitter"
107
- | "labelField"
108
- | "labelFormatter"
109
- | "labels"
110
- | "loadData"
111
- | "onAdd"
112
- | "ids"
113
- >;
106
+ "addSplitter" | "labelField" | "labels" | "onAdd" | "ids"
107
+ > &
108
+ Required<Pick<ButtonPopupCheckboxProps<D>, "labelFormatter">> & {
109
+ /**
110
+ * Items to be displayed
111
+ */
112
+ items: D[];
113
+
114
+ /**
115
+ * On value change handler
116
+ * @param ids Ids
117
+ */
118
+ onValueChange: (ids: D["id"][]) => void;
119
+ };
114
120
 
115
121
  function ButtonPopupList<D extends DnDItemType>(
116
122
  props: ButtonPopupListProps<D>
@@ -118,19 +124,13 @@ function ButtonPopupList<D extends DnDItemType>(
118
124
  // Destruct
119
125
  const {
120
126
  addSplitter = /\s*[,;]\s*/,
121
- ids,
127
+ ids = [],
128
+ items,
122
129
  labelField,
123
- labelFormatter = (data) => {
124
- console.log("data", data);
125
- if (labelField in data) {
126
- return data[labelField] as string;
127
- }
128
-
129
- return data.id.toString();
130
- },
130
+ labelFormatter,
131
131
  labels,
132
- loadData,
133
- onAdd
132
+ onAdd,
133
+ onValueChange
134
134
  } = props;
135
135
 
136
136
  // Methods
@@ -139,11 +139,15 @@ function ButtonPopupList<D extends DnDItemType>(
139
139
  // Ref
140
140
  const inputRef = React.useRef<HTMLInputElement>(null);
141
141
 
142
+ // State
143
+ const [selectedIds, setSelectedIds] = React.useState<D["id"][]>([]);
144
+
142
145
  React.useEffect(() => {
143
- loadData(ids).then((data) => {
144
- if (data == null || dndRef.current == null) return;
145
- dndRef.current.addItems(data);
146
- });
146
+ // Sort items by ids for first load
147
+ items.sortByProperty("id", ids);
148
+
149
+ // Set selected ids
150
+ setSelectedIds([...ids]);
147
151
  }, [ids]);
148
152
 
149
153
  return (
@@ -151,7 +155,7 @@ function ButtonPopupList<D extends DnDItemType>(
151
155
  <FormGroup>
152
156
  <Grid container spacing={0}>
153
157
  <DnDList<D>
154
- items={[]}
158
+ items={items}
155
159
  labelField={labelField}
156
160
  itemRenderer={(item, index, nodeRef, actionNodeRef) => (
157
161
  <Grid
@@ -175,7 +179,15 @@ function ButtonPopupList<D extends DnDItemType>(
175
179
  <Checkbox
176
180
  name="item"
177
181
  value={item.id}
178
- defaultChecked={ids?.includes(item.id)}
182
+ checked={selectedIds.includes(item.id)}
183
+ onChange={(e) => {
184
+ const checked = e.target.checked;
185
+ const newIds = [
186
+ ...selectedIds.toggleItem(item.id, checked)
187
+ ];
188
+ setSelectedIds(newIds);
189
+ onValueChange(newIds);
190
+ }}
179
191
  />
180
192
  }
181
193
  label={`${index + 1}. ${labelFormatter(item)}`}
@@ -253,7 +265,13 @@ export function ButtonPopupCheckbox<D extends DnDItemType>(
253
265
  inputName,
254
266
  label,
255
267
  labelEnd,
256
- labelFormatter,
268
+ labelFormatter = (data) => {
269
+ if (labelField in data) {
270
+ return data[labelField] as string;
271
+ }
272
+
273
+ return data.id.toString();
274
+ },
257
275
  labelField,
258
276
  labels = {},
259
277
  loadData,
@@ -273,43 +291,49 @@ export function ButtonPopupCheckbox<D extends DnDItemType>(
273
291
  if (!labels.more) labels.more = app.get("more");
274
292
 
275
293
  // State
276
- const [values, setValues] = React.useState<D["id"][]>([]);
294
+ const [items, setItems] = React.useState<D[]>([]);
295
+ const [selectedIds, setSelectedIds] = React.useState<D["id"][]>();
296
+
277
297
  React.useEffect(() => {
278
- if (ids == null) return;
279
- setValues(ids);
298
+ // Load data
299
+ loadData().then((data) => {
300
+ if (data != null) {
301
+ setItems(data);
302
+ }
303
+ });
304
+ }, []);
305
+
306
+ React.useEffect(() => {
307
+ // Set selected ids
308
+ setSelectedIds(ids);
280
309
  }, [ids]);
281
310
 
311
+ // Selected ids
312
+ const tempSelectedIds = React.useRef<D["id"][]>();
313
+
282
314
  // Click handler
283
315
  const clickHandler = () => {
284
316
  app.showInputDialog({
285
317
  title: popupTitle,
286
318
  message: popupMessage,
287
319
  callback: (form) => {
288
- if (form == null) return;
289
-
290
- // Form data
291
- const { item = [] } = DomUtils.dataAs(new FormData(form), {
292
- item: "string[]"
293
- });
294
-
295
- if (required && item.length === 0) {
296
- DomUtils.setFocus("item", form);
297
- return false;
298
- }
299
-
300
- setValues(item);
301
-
302
- onValueChange?.(item);
320
+ if (form == null || tempSelectedIds.current == null) return;
321
+ const ids = tempSelectedIds.current;
322
+ setSelectedIds(ids);
323
+ onValueChange?.(ids);
303
324
  },
304
325
  inputs: (
305
326
  <ButtonPopupList
306
327
  addSplitter={addSplitter}
307
- ids={values}
328
+ ids={selectedIds}
329
+ items={items}
308
330
  labelFormatter={labelFormatter}
309
331
  labelField={labelField}
310
332
  labels={labels}
311
- loadData={loadData}
312
333
  onAdd={onAdd}
334
+ onValueChange={(ids) => {
335
+ tempSelectedIds.current = ids;
336
+ }}
313
337
  />
314
338
  ),
315
339
  fullScreen: app.smDown
@@ -323,18 +347,22 @@ export function ButtonPopupCheckbox<D extends DnDItemType>(
323
347
  style={{ position: "absolute", opacity: 0, width: 0 }}
324
348
  name={inputName}
325
349
  required={required}
326
- defaultValue={values.join(",")}
350
+ defaultValue={selectedIds?.join(",")}
327
351
  />
328
352
  <Button
329
353
  variant={variant}
330
354
  sx={sx}
331
355
  onClick={() => clickHandler()}
332
356
  {...rest}
357
+ disabled={!items || items.length === 0}
333
358
  >
334
359
  {label && <Typography variant="body2">{label}:</Typography>}
335
- {values.map((id) => (
336
- <Chip key={id} size="small" label={id} />
337
- ))}
360
+ {selectedIds?.map((id) => {
361
+ const item = items.find((item) => item.id === id);
362
+ if (item == null) return null;
363
+
364
+ return <Chip key={id} size="small" label={labelFormatter(item)} />;
365
+ })}
338
366
  {labelEnd && <Typography variant="caption">{labelEnd}</Typography>}
339
367
  </Button>
340
368
  </React.Fragment>