@etsoo/materialui 1.5.47 → 1.5.49

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,30 @@ 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, setSelectedIdsBase] = react_1.default.useState([]);
32
+ // Sort items
33
+ const setSelectedIds = (ids) => {
34
+ items.sortByProperty("id", ids);
35
+ setSelectedIdsBase(ids);
36
+ };
37
37
  react_1.default.useEffect(() => {
38
- loadData(ids).then((data) => {
39
- if (data == null || dndRef.current == null)
40
- return;
41
- dndRef.current.addItems(data);
42
- });
38
+ // Set selected ids
39
+ setSelectedIds([...ids]);
43
40
  }, [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 () => {
41
+ 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) => {
42
+ const checked = e.target.checked;
43
+ const newIds = [
44
+ ...selectedIds.toggleItem(item.id, checked)
45
+ ];
46
+ setSelectedIds(newIds);
47
+ onValueChange(newIds);
48
+ } }), 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
49
  if (inputRef.current == null)
46
50
  return;
47
51
  const input = inputRef.current.value.trim();
@@ -71,7 +75,12 @@ function ButtonPopupCheckbox(props) {
71
75
  // App
72
76
  const app = (0, ReactApp_1.useRequiredAppContext)();
73
77
  // 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;
78
+ const { addSplitter, ids, inputName, label, labelEnd, labelFormatter = (data) => {
79
+ if (labelField in data) {
80
+ return data[labelField];
81
+ }
82
+ return data.id.toString();
83
+ }, labelField, labels = {}, loadData, onAdd, onValueChange, popupTitle = label, popupMessage, required = false, sx = { gap: 1, justifyContent: "flex-start" }, variant = "outlined", ...rest } = props;
75
84
  // Default labels
76
85
  if (!labels.add)
77
86
  labels.add = app.get("add");
@@ -80,34 +89,44 @@ function ButtonPopupCheckbox(props) {
80
89
  if (!labels.more)
81
90
  labels.more = app.get("more");
82
91
  // State
83
- const [values, setValues] = react_1.default.useState([]);
92
+ const [items, setItems] = react_1.default.useState([]);
93
+ const [selectedIds, setSelectedIds] = react_1.default.useState();
94
+ react_1.default.useEffect(() => {
95
+ // Load data
96
+ loadData().then((data) => {
97
+ if (data != null) {
98
+ setItems(data);
99
+ }
100
+ });
101
+ }, []);
84
102
  react_1.default.useEffect(() => {
85
- if (ids == null)
86
- return;
87
- setValues(ids);
103
+ // Set selected ids
104
+ setSelectedIds(ids);
88
105
  }, [ids]);
106
+ // Selected ids
107
+ const tempSelectedIds = react_1.default.useRef();
89
108
  // Click handler
90
109
  const clickHandler = () => {
91
110
  app.showInputDialog({
92
111
  title: popupTitle,
93
112
  message: popupMessage,
94
113
  callback: (form) => {
95
- if (form == null)
114
+ if (form == null || tempSelectedIds.current == null)
96
115
  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);
116
+ const ids = tempSelectedIds.current;
117
+ setSelectedIds(ids);
118
+ onValueChange?.(ids);
107
119
  },
108
- inputs: ((0, jsx_runtime_1.jsx)(ButtonPopupList, { addSplitter: addSplitter, ids: values, labelFormatter: labelFormatter, labelField: labelField, labels: labels, loadData: loadData, onAdd: onAdd })),
120
+ inputs: ((0, jsx_runtime_1.jsx)(ButtonPopupList, { addSplitter: addSplitter, ids: selectedIds, items: items, labelFormatter: labelFormatter, labelField: labelField, labels: labels, onAdd: onAdd, onValueChange: (ids) => {
121
+ tempSelectedIds.current = ids;
122
+ } })),
109
123
  fullScreen: app.smDown
110
124
  });
111
125
  };
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 })] })] }));
126
+ 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) => {
127
+ const item = items.find((item) => item.id === id);
128
+ if (item == null)
129
+ return null;
130
+ return (0, jsx_runtime_1.jsx)(Chip_1.default, { size: "small", label: labelFormatter(item) }, id);
131
+ }), labelEnd && (0, jsx_runtime_1.jsx)(Typography_1.default, { variant: "caption", children: labelEnd })] })] }));
113
132
  }
@@ -3,6 +3,15 @@ import React from "react";
3
3
  import { CommonPageProps } from "./CommonPage";
4
4
  import type { OperationMessageHandlerAll } from "../messages/OperationMessageHandler";
5
5
  import { ViewContainerProps } from "../ViewContainer";
6
+ import { StackProps } from "@mui/material/Stack";
7
+ /**
8
+ * View page action bar
9
+ * @param props Props
10
+ * @returns Component
11
+ */
12
+ export declare function ViewPageActionBar(props: StackProps & {
13
+ actionPaddings?: number | Record<string, string | number>;
14
+ }): import("react/jsx-runtime").JSX.Element;
6
15
  /**
7
16
  * View page props
8
17
  */
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ViewPageActionBar = ViewPageActionBar;
6
7
  exports.ViewPage = ViewPage;
7
8
  const jsx_runtime_1 = require("react/jsx-runtime");
8
9
  const react_1 = require("@etsoo/react");
@@ -17,6 +18,15 @@ const OperationMessageContainer_1 = require("../messages/OperationMessageContain
17
18
  const ViewContainer_1 = require("../ViewContainer");
18
19
  const LinearProgress_1 = __importDefault(require("@mui/material/LinearProgress"));
19
20
  const Stack_1 = __importDefault(require("@mui/material/Stack"));
21
+ /**
22
+ * View page action bar
23
+ * @param props Props
24
+ * @returns Component
25
+ */
26
+ function ViewPageActionBar(props) {
27
+ const { actionPaddings = MUGlobal_1.MUGlobal.pagePaddings, ...rest } = props;
28
+ return ((0, jsx_runtime_1.jsx)(Stack_1.default, { className: "ET-ViewPage-Actions", direction: "row", width: "100%", flexWrap: "wrap", justifyContent: "center", paddingTop: actionPaddings, paddingBottom: actionPaddings, gap: actionPaddings, ...rest }));
29
+ }
20
30
  /**
21
31
  * View page
22
32
  * @param props Props
@@ -51,7 +61,7 @@ function ViewPage(props) {
51
61
  refresh,
52
62
  operationMessageHandler.id
53
63
  ]
54
- : operationMessageHandler })), titleBar && titleBar(data), (0, jsx_runtime_1.jsx)(ViewContainer_1.ViewContainer, { data: data, fields: fields, gridRef: gridRef, leftContainer: leftContainer, leftContainerLines: leftContainerLines, leftContainerProps: leftContainerProps, refresh: refresh, spacing: spacing }), actions !== null && ((0, jsx_runtime_1.jsx)(Stack_1.default, { className: "ET-ViewPage-Actions", direction: "row", width: "100%", flexWrap: "wrap", justifyContent: "center", paddingTop: actions == null ? undefined : actionPaddings, paddingBottom: actionPaddings, gap: actionPaddings, children: actions != null && shared_1.Utils.getResult(actions, data, refresh) })), shared_1.Utils.getResult(children, data, refresh), pullToRefresh && ((0, jsx_runtime_1.jsx)(PullToRefreshUI_1.PullToRefreshUI, { mainElement: pullContainer, triggerElement: pullContainer, instructionsPullToRefresh: labels.pullToRefresh, instructionsReleaseToRefresh: labels.releaseToRefresh, instructionsRefreshing: labels.refreshing, onRefresh: refresh, shouldPullToRefresh: () => {
64
+ : operationMessageHandler })), titleBar && titleBar(data), (0, jsx_runtime_1.jsx)(ViewContainer_1.ViewContainer, { data: data, fields: fields, gridRef: gridRef, leftContainer: leftContainer, leftContainerLines: leftContainerLines, leftContainerProps: leftContainerProps, refresh: refresh, spacing: spacing }), actions !== null && ((0, jsx_runtime_1.jsx)(ViewPageActionBar, { actionPaddings: actionPaddings, children: shared_1.Utils.getResult(actions, data, refresh) })), shared_1.Utils.getResult(children, data, refresh), pullToRefresh && ((0, jsx_runtime_1.jsx)(PullToRefreshUI_1.PullToRefreshUI, { mainElement: pullContainer, triggerElement: pullContainer, instructionsPullToRefresh: labels.pullToRefresh, instructionsReleaseToRefresh: labels.releaseToRefresh, instructionsRefreshing: labels.refreshing, onRefresh: refresh, shouldPullToRefresh: () => {
55
65
  const container = document.querySelector(pullContainer);
56
66
  return !container?.scrollTop;
57
67
  } })), (0, jsx_runtime_1.jsx)(react_1.ScrollRestoration, {})] })) }));
@@ -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,30 @@ 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, setSelectedIdsBase] = React.useState([]);
26
+ // Sort items
27
+ const setSelectedIds = (ids) => {
28
+ items.sortByProperty("id", ids);
29
+ setSelectedIdsBase(ids);
30
+ };
31
31
  React.useEffect(() => {
32
- loadData(ids).then((data) => {
33
- if (data == null || dndRef.current == null)
34
- return;
35
- dndRef.current.addItems(data);
36
- });
32
+ // Set selected ids
33
+ setSelectedIds([...ids]);
37
34
  }, [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 () => {
35
+ 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) => {
36
+ const checked = e.target.checked;
37
+ const newIds = [
38
+ ...selectedIds.toggleItem(item.id, checked)
39
+ ];
40
+ setSelectedIds(newIds);
41
+ onValueChange(newIds);
42
+ } }), 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
43
  if (inputRef.current == null)
40
44
  return;
41
45
  const input = inputRef.current.value.trim();
@@ -65,7 +69,12 @@ export function ButtonPopupCheckbox(props) {
65
69
  // App
66
70
  const app = useRequiredAppContext();
67
71
  // 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;
72
+ const { addSplitter, ids, inputName, label, labelEnd, labelFormatter = (data) => {
73
+ if (labelField in data) {
74
+ return data[labelField];
75
+ }
76
+ return data.id.toString();
77
+ }, labelField, labels = {}, loadData, onAdd, onValueChange, popupTitle = label, popupMessage, required = false, sx = { gap: 1, justifyContent: "flex-start" }, variant = "outlined", ...rest } = props;
69
78
  // Default labels
70
79
  if (!labels.add)
71
80
  labels.add = app.get("add");
@@ -74,34 +83,44 @@ export function ButtonPopupCheckbox(props) {
74
83
  if (!labels.more)
75
84
  labels.more = app.get("more");
76
85
  // State
77
- const [values, setValues] = React.useState([]);
86
+ const [items, setItems] = React.useState([]);
87
+ const [selectedIds, setSelectedIds] = React.useState();
88
+ React.useEffect(() => {
89
+ // Load data
90
+ loadData().then((data) => {
91
+ if (data != null) {
92
+ setItems(data);
93
+ }
94
+ });
95
+ }, []);
78
96
  React.useEffect(() => {
79
- if (ids == null)
80
- return;
81
- setValues(ids);
97
+ // Set selected ids
98
+ setSelectedIds(ids);
82
99
  }, [ids]);
100
+ // Selected ids
101
+ const tempSelectedIds = React.useRef();
83
102
  // Click handler
84
103
  const clickHandler = () => {
85
104
  app.showInputDialog({
86
105
  title: popupTitle,
87
106
  message: popupMessage,
88
107
  callback: (form) => {
89
- if (form == null)
108
+ if (form == null || tempSelectedIds.current == null)
90
109
  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);
110
+ const ids = tempSelectedIds.current;
111
+ setSelectedIds(ids);
112
+ onValueChange?.(ids);
101
113
  },
102
- inputs: (_jsx(ButtonPopupList, { addSplitter: addSplitter, ids: values, labelFormatter: labelFormatter, labelField: labelField, labels: labels, loadData: loadData, onAdd: onAdd })),
114
+ inputs: (_jsx(ButtonPopupList, { addSplitter: addSplitter, ids: selectedIds, items: items, labelFormatter: labelFormatter, labelField: labelField, labels: labels, onAdd: onAdd, onValueChange: (ids) => {
115
+ tempSelectedIds.current = ids;
116
+ } })),
103
117
  fullScreen: app.smDown
104
118
  });
105
119
  };
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 })] })] }));
120
+ 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) => {
121
+ const item = items.find((item) => item.id === id);
122
+ if (item == null)
123
+ return null;
124
+ return _jsx(Chip, { size: "small", label: labelFormatter(item) }, id);
125
+ }), labelEnd && _jsx(Typography, { variant: "caption", children: labelEnd })] })] }));
107
126
  }
@@ -3,6 +3,15 @@ import React from "react";
3
3
  import { CommonPageProps } from "./CommonPage";
4
4
  import type { OperationMessageHandlerAll } from "../messages/OperationMessageHandler";
5
5
  import { ViewContainerProps } from "../ViewContainer";
6
+ import { StackProps } from "@mui/material/Stack";
7
+ /**
8
+ * View page action bar
9
+ * @param props Props
10
+ * @returns Component
11
+ */
12
+ export declare function ViewPageActionBar(props: StackProps & {
13
+ actionPaddings?: number | Record<string, string | number>;
14
+ }): import("react/jsx-runtime").JSX.Element;
6
15
  /**
7
16
  * View page props
8
17
  */
@@ -11,6 +11,15 @@ import { OperationMessageContainer } from "../messages/OperationMessageContainer
11
11
  import { ViewContainer } from "../ViewContainer";
12
12
  import LinearProgress from "@mui/material/LinearProgress";
13
13
  import Stack from "@mui/material/Stack";
14
+ /**
15
+ * View page action bar
16
+ * @param props Props
17
+ * @returns Component
18
+ */
19
+ export function ViewPageActionBar(props) {
20
+ const { actionPaddings = MUGlobal.pagePaddings, ...rest } = props;
21
+ return (_jsx(Stack, { className: "ET-ViewPage-Actions", direction: "row", width: "100%", flexWrap: "wrap", justifyContent: "center", paddingTop: actionPaddings, paddingBottom: actionPaddings, gap: actionPaddings, ...rest }));
22
+ }
14
23
  /**
15
24
  * View page
16
25
  * @param props Props
@@ -45,7 +54,7 @@ export function ViewPage(props) {
45
54
  refresh,
46
55
  operationMessageHandler.id
47
56
  ]
48
- : operationMessageHandler })), titleBar && titleBar(data), _jsx(ViewContainer, { data: data, fields: fields, gridRef: gridRef, leftContainer: leftContainer, leftContainerLines: leftContainerLines, leftContainerProps: leftContainerProps, refresh: refresh, spacing: spacing }), actions !== null && (_jsx(Stack, { className: "ET-ViewPage-Actions", direction: "row", width: "100%", flexWrap: "wrap", justifyContent: "center", paddingTop: actions == null ? undefined : actionPaddings, paddingBottom: actionPaddings, gap: actionPaddings, children: actions != null && Utils.getResult(actions, data, refresh) })), Utils.getResult(children, data, refresh), pullToRefresh && (_jsx(PullToRefreshUI, { mainElement: pullContainer, triggerElement: pullContainer, instructionsPullToRefresh: labels.pullToRefresh, instructionsReleaseToRefresh: labels.releaseToRefresh, instructionsRefreshing: labels.refreshing, onRefresh: refresh, shouldPullToRefresh: () => {
57
+ : operationMessageHandler })), titleBar && titleBar(data), _jsx(ViewContainer, { data: data, fields: fields, gridRef: gridRef, leftContainer: leftContainer, leftContainerLines: leftContainerLines, leftContainerProps: leftContainerProps, refresh: refresh, spacing: spacing }), actions !== null && (_jsx(ViewPageActionBar, { actionPaddings: actionPaddings, children: Utils.getResult(actions, data, refresh) })), Utils.getResult(children, data, refresh), pullToRefresh && (_jsx(PullToRefreshUI, { mainElement: pullContainer, triggerElement: pullContainer, instructionsPullToRefresh: labels.pullToRefresh, instructionsReleaseToRefresh: labels.releaseToRefresh, instructionsRefreshing: labels.refreshing, onRefresh: refresh, shouldPullToRefresh: () => {
49
58
  const container = document.querySelector(pullContainer);
50
59
  return !container?.scrollTop;
51
60
  } })), _jsx(ScrollRestoration, {})] })) }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@etsoo/materialui",
3
- "version": "1.5.47",
3
+ "version": "1.5.49",
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.0",
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,18 @@ function ButtonPopupList<D extends DnDItemType>(
139
139
  // Ref
140
140
  const inputRef = React.useRef<HTMLInputElement>(null);
141
141
 
142
+ // State
143
+ const [selectedIds, setSelectedIdsBase] = React.useState<D["id"][]>([]);
144
+
145
+ // Sort items
146
+ const setSelectedIds = (ids: D["id"][]) => {
147
+ items.sortByProperty("id", ids);
148
+ setSelectedIdsBase(ids);
149
+ };
150
+
142
151
  React.useEffect(() => {
143
- loadData(ids).then((data) => {
144
- if (data == null || dndRef.current == null) return;
145
- dndRef.current.addItems(data);
146
- });
152
+ // Set selected ids
153
+ setSelectedIds([...ids]);
147
154
  }, [ids]);
148
155
 
149
156
  return (
@@ -151,7 +158,7 @@ function ButtonPopupList<D extends DnDItemType>(
151
158
  <FormGroup>
152
159
  <Grid container spacing={0}>
153
160
  <DnDList<D>
154
- items={[]}
161
+ items={items}
155
162
  labelField={labelField}
156
163
  itemRenderer={(item, index, nodeRef, actionNodeRef) => (
157
164
  <Grid
@@ -175,7 +182,15 @@ function ButtonPopupList<D extends DnDItemType>(
175
182
  <Checkbox
176
183
  name="item"
177
184
  value={item.id}
178
- defaultChecked={ids?.includes(item.id)}
185
+ checked={selectedIds.includes(item.id)}
186
+ onChange={(e) => {
187
+ const checked = e.target.checked;
188
+ const newIds = [
189
+ ...selectedIds.toggleItem(item.id, checked)
190
+ ];
191
+ setSelectedIds(newIds);
192
+ onValueChange(newIds);
193
+ }}
179
194
  />
180
195
  }
181
196
  label={`${index + 1}. ${labelFormatter(item)}`}
@@ -253,7 +268,13 @@ export function ButtonPopupCheckbox<D extends DnDItemType>(
253
268
  inputName,
254
269
  label,
255
270
  labelEnd,
256
- labelFormatter,
271
+ labelFormatter = (data) => {
272
+ if (labelField in data) {
273
+ return data[labelField] as string;
274
+ }
275
+
276
+ return data.id.toString();
277
+ },
257
278
  labelField,
258
279
  labels = {},
259
280
  loadData,
@@ -273,43 +294,49 @@ export function ButtonPopupCheckbox<D extends DnDItemType>(
273
294
  if (!labels.more) labels.more = app.get("more");
274
295
 
275
296
  // State
276
- const [values, setValues] = React.useState<D["id"][]>([]);
297
+ const [items, setItems] = React.useState<D[]>([]);
298
+ const [selectedIds, setSelectedIds] = React.useState<D["id"][]>();
299
+
300
+ React.useEffect(() => {
301
+ // Load data
302
+ loadData().then((data) => {
303
+ if (data != null) {
304
+ setItems(data);
305
+ }
306
+ });
307
+ }, []);
308
+
277
309
  React.useEffect(() => {
278
- if (ids == null) return;
279
- setValues(ids);
310
+ // Set selected ids
311
+ setSelectedIds(ids);
280
312
  }, [ids]);
281
313
 
314
+ // Selected ids
315
+ const tempSelectedIds = React.useRef<D["id"][]>();
316
+
282
317
  // Click handler
283
318
  const clickHandler = () => {
284
319
  app.showInputDialog({
285
320
  title: popupTitle,
286
321
  message: popupMessage,
287
322
  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);
323
+ if (form == null || tempSelectedIds.current == null) return;
324
+ const ids = tempSelectedIds.current;
325
+ setSelectedIds(ids);
326
+ onValueChange?.(ids);
303
327
  },
304
328
  inputs: (
305
329
  <ButtonPopupList
306
330
  addSplitter={addSplitter}
307
- ids={values}
331
+ ids={selectedIds}
332
+ items={items}
308
333
  labelFormatter={labelFormatter}
309
334
  labelField={labelField}
310
335
  labels={labels}
311
- loadData={loadData}
312
336
  onAdd={onAdd}
337
+ onValueChange={(ids) => {
338
+ tempSelectedIds.current = ids;
339
+ }}
313
340
  />
314
341
  ),
315
342
  fullScreen: app.smDown
@@ -323,18 +350,22 @@ export function ButtonPopupCheckbox<D extends DnDItemType>(
323
350
  style={{ position: "absolute", opacity: 0, width: 0 }}
324
351
  name={inputName}
325
352
  required={required}
326
- defaultValue={values.join(",")}
353
+ defaultValue={selectedIds?.join(",")}
327
354
  />
328
355
  <Button
329
356
  variant={variant}
330
357
  sx={sx}
331
358
  onClick={() => clickHandler()}
332
359
  {...rest}
360
+ disabled={!items || items.length === 0}
333
361
  >
334
362
  {label && <Typography variant="body2">{label}:</Typography>}
335
- {values.map((id) => (
336
- <Chip key={id} size="small" label={id} />
337
- ))}
363
+ {selectedIds?.map((id) => {
364
+ const item = items.find((item) => item.id === id);
365
+ if (item == null) return null;
366
+
367
+ return <Chip key={id} size="small" label={labelFormatter(item)} />;
368
+ })}
338
369
  {labelEnd && <Typography variant="caption">{labelEnd}</Typography>}
339
370
  </Button>
340
371
  </React.Fragment>
@@ -11,7 +11,34 @@ import type { RefreshHandler } from "../messages/RefreshHandler";
11
11
  import { OperationMessageContainer } from "../messages/OperationMessageContainer";
12
12
  import { ViewContainer, ViewContainerProps } from "../ViewContainer";
13
13
  import LinearProgress from "@mui/material/LinearProgress";
14
- import Stack from "@mui/material/Stack";
14
+ import Stack, { StackProps } from "@mui/material/Stack";
15
+
16
+ /**
17
+ * View page action bar
18
+ * @param props Props
19
+ * @returns Component
20
+ */
21
+ export function ViewPageActionBar(
22
+ props: StackProps & {
23
+ actionPaddings?: number | Record<string, string | number>;
24
+ }
25
+ ) {
26
+ const { actionPaddings = MUGlobal.pagePaddings, ...rest } = props;
27
+
28
+ return (
29
+ <Stack
30
+ className="ET-ViewPage-Actions"
31
+ direction="row"
32
+ width="100%"
33
+ flexWrap="wrap"
34
+ justifyContent="center"
35
+ paddingTop={actionPaddings}
36
+ paddingBottom={actionPaddings}
37
+ gap={actionPaddings}
38
+ {...rest}
39
+ ></Stack>
40
+ );
41
+ }
15
42
 
16
43
  /**
17
44
  * View page props
@@ -166,18 +193,9 @@ export function ViewPage<T extends DataTypes.StringRecord>(
166
193
  spacing={spacing}
167
194
  />
168
195
  {actions !== null && (
169
- <Stack
170
- className="ET-ViewPage-Actions"
171
- direction="row"
172
- width="100%"
173
- flexWrap="wrap"
174
- justifyContent="center"
175
- paddingTop={actions == null ? undefined : actionPaddings}
176
- paddingBottom={actionPaddings}
177
- gap={actionPaddings}
178
- >
179
- {actions != null && Utils.getResult(actions, data, refresh)}
180
- </Stack>
196
+ <ViewPageActionBar actionPaddings={actionPaddings}>
197
+ {Utils.getResult(actions, data, refresh)}
198
+ </ViewPageActionBar>
181
199
  )}
182
200
  {Utils.getResult(children, data, refresh)}
183
201
  {pullToRefresh && (