@etsoo/materialui 1.6.11 → 1.6.13
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 +9 -2
- package/lib/cjs/ButtonPopupCheckbox.js +9 -7
- package/lib/cjs/ButtonPopupRadio.d.ts +6 -0
- package/lib/cjs/ButtonPopupRadio.js +4 -4
- package/lib/cjs/DnDSortableList.d.ts +105 -0
- package/lib/cjs/DnDSortableList.js +151 -0
- package/lib/cjs/index.d.ts +1 -1
- package/lib/cjs/index.js +1 -1
- package/lib/mjs/ButtonPopupCheckbox.d.ts +9 -2
- package/lib/mjs/ButtonPopupCheckbox.js +9 -7
- package/lib/mjs/ButtonPopupRadio.d.ts +6 -0
- package/lib/mjs/ButtonPopupRadio.js +4 -4
- package/lib/mjs/DnDSortableList.d.ts +105 -0
- package/lib/mjs/DnDSortableList.js +143 -0
- package/lib/mjs/index.d.ts +1 -1
- package/lib/mjs/index.js +1 -1
- package/package.json +3 -4
- package/src/ButtonPopupCheckbox.tsx +32 -13
- package/src/ButtonPopupRadio.tsx +12 -3
- package/src/DnDSortableList.tsx +345 -0
- package/src/index.ts +1 -1
- package/lib/cjs/DnDList.d.ts +0 -111
- package/lib/cjs/DnDList.js +0 -238
- package/lib/mjs/DnDList.d.ts +0 -111
- package/lib/mjs/DnDList.js +0 -230
- package/src/DnDList.tsx +0 -528
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { ButtonProps } from "@mui/material/Button";
|
|
2
|
-
import {
|
|
2
|
+
import { IdType } from "@etsoo/shared";
|
|
3
|
+
import { GridSize } from "@mui/material/Grid";
|
|
4
|
+
import { DnDSortableListProps } from "./DnDSortableList";
|
|
5
|
+
import { ResponsiveStyleValue } from "./ResponsiveStyleValue";
|
|
3
6
|
type DnDItemType = {
|
|
4
7
|
id: IdType;
|
|
5
8
|
};
|
|
@@ -23,7 +26,7 @@ export type ButtonPopupCheckboxProps<D extends DnDItemType> = Omit<ButtonProps,
|
|
|
23
26
|
/**
|
|
24
27
|
* Label field in items
|
|
25
28
|
*/
|
|
26
|
-
labelField:
|
|
29
|
+
labelField: DnDSortableListProps<D>["labelField"];
|
|
27
30
|
/**
|
|
28
31
|
* Label formatter
|
|
29
32
|
* @param item Item to be formatted
|
|
@@ -63,6 +66,10 @@ export type ButtonPopupCheckboxProps<D extends DnDItemType> = Omit<ButtonProps,
|
|
|
63
66
|
* The field is required or not
|
|
64
67
|
*/
|
|
65
68
|
required?: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Item size
|
|
71
|
+
*/
|
|
72
|
+
itemSize?: ResponsiveStyleValue<GridSize>;
|
|
66
73
|
/**
|
|
67
74
|
* Value
|
|
68
75
|
*/
|
|
@@ -19,10 +19,10 @@ const TextField_1 = __importDefault(require("@mui/material/TextField"));
|
|
|
19
19
|
const FlexBox_1 = require("./FlexBox");
|
|
20
20
|
const ReactApp_1 = require("./app/ReactApp");
|
|
21
21
|
const FormLabel_1 = __importDefault(require("@mui/material/FormLabel"));
|
|
22
|
-
const
|
|
22
|
+
const DnDSortableList_1 = require("./DnDSortableList");
|
|
23
23
|
function ButtonPopupList(props) {
|
|
24
24
|
// Destruct
|
|
25
|
-
const { addSplitter = /\s*[,;]\s*/, value = [], items, labelField, labelFormatter, labels, onAdd, onValueChange } = props;
|
|
25
|
+
const { addSplitter = /\s*[,;]\s*/, value = [], items, itemSize = { xs: 12, md: 6, lx: 4 }, labelField, labelFormatter, labels, onAdd, onValueChange } = props;
|
|
26
26
|
// Methods
|
|
27
27
|
const dndRef = react_1.default.createRef();
|
|
28
28
|
// Refs
|
|
@@ -40,18 +40,18 @@ function ButtonPopupList(props) {
|
|
|
40
40
|
// Set selected ids
|
|
41
41
|
setSelectedIdsHandler([...value]);
|
|
42
42
|
}, [value]);
|
|
43
|
-
return ((0, jsx_runtime_1.jsxs)(FlexBox_1.VBox, { gap: 2, children: [(0, jsx_runtime_1.jsx)(
|
|
43
|
+
return ((0, jsx_runtime_1.jsxs)(FlexBox_1.VBox, { gap: 2, children: [(0, jsx_runtime_1.jsx)(DnDSortableList_1.DnDSortableList, { component: Grid_1.default, componentProps: { container: true, spacing: 0 }, items: items, labelField: labelField, onChange: (items) => {
|
|
44
44
|
const ids = items
|
|
45
45
|
.filter((item) => tempSelectedIds.current.includes(item.id))
|
|
46
46
|
.map((item) => item.id);
|
|
47
47
|
onValueChange(ids);
|
|
48
|
-
}, itemRenderer: (item, index,
|
|
48
|
+
}, itemRenderer: (item, style, { sortable: { index }, ref, handleRef }) => ((0, jsx_runtime_1.jsxs)(Grid_1.default, { size: itemSize, display: "flex", justifyContent: "flex-start", alignItems: "center", gap: 1, ref: ref, style: style, children: [(0, jsx_runtime_1.jsx)(IconButton_1.default, { style: { cursor: "move" }, size: "small", title: labels?.dragIndicator, ref: handleRef, 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) => {
|
|
49
49
|
const checked = e.target.checked;
|
|
50
50
|
const newIds = [
|
|
51
51
|
...selectedIds.toggleItem(item.id, checked)
|
|
52
52
|
];
|
|
53
53
|
setSelectedIdsHandler(newIds);
|
|
54
|
-
} }), label: `${index + 1}. ${labelFormatter(item)}` })] })),
|
|
54
|
+
} }), label: `${index + 1}. ${labelFormatter(item)}` })] })), 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 () => {
|
|
55
55
|
if (inputRef.current == null)
|
|
56
56
|
return;
|
|
57
57
|
const input = inputRef.current.value.trim();
|
|
@@ -81,7 +81,9 @@ function ButtonPopupCheckbox(props) {
|
|
|
81
81
|
// App
|
|
82
82
|
const app = (0, ReactApp_1.useRequiredAppContext)();
|
|
83
83
|
// Destruct
|
|
84
|
-
const { addSplitter, value = [], inputName, label, labelEnd, labelFormatter = (data) => {
|
|
84
|
+
const { addSplitter, value = [], inputName, itemSize, label, labelEnd, labelFormatter = (data) => {
|
|
85
|
+
if (typeof labelField === "function")
|
|
86
|
+
return labelField(data);
|
|
85
87
|
if (labelField in data) {
|
|
86
88
|
return data[labelField];
|
|
87
89
|
}
|
|
@@ -128,7 +130,7 @@ function ButtonPopupCheckbox(props) {
|
|
|
128
130
|
setSelectedIds(ids);
|
|
129
131
|
onValueChange?.(ids);
|
|
130
132
|
},
|
|
131
|
-
inputs: ((0, jsx_runtime_1.jsx)(ButtonPopupList, { addSplitter: addSplitter, value: selectedIds, items: items, labelFormatter: labelFormatter, labelField: labelField, labels: labels, onAdd: onAdd, onValueChange: (ids) => {
|
|
133
|
+
inputs: ((0, jsx_runtime_1.jsx)(ButtonPopupList, { addSplitter: addSplitter, value: selectedIds, items: items, labelFormatter: labelFormatter, labelField: labelField, itemSize: itemSize, labels: labels, onAdd: onAdd, onValueChange: (ids) => {
|
|
132
134
|
tempSelectedIds.current = ids;
|
|
133
135
|
} })),
|
|
134
136
|
fullScreen: app.smDown
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { ButtonProps } from "@mui/material/Button";
|
|
2
2
|
import { DataTypes, IdType } from "@etsoo/shared";
|
|
3
|
+
import { GridSize } from "@mui/material/Grid";
|
|
4
|
+
import { ResponsiveStyleValue } from "./ResponsiveStyleValue";
|
|
3
5
|
type DnDItemType = {
|
|
4
6
|
id: IdType;
|
|
5
7
|
};
|
|
@@ -63,6 +65,10 @@ export type ButtonPopupRadioProps<D extends DnDItemType> = Omit<ButtonProps, "ch
|
|
|
63
65
|
* The field is required or not
|
|
64
66
|
*/
|
|
65
67
|
required?: boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Item size
|
|
70
|
+
*/
|
|
71
|
+
itemSize?: ResponsiveStyleValue<GridSize>;
|
|
66
72
|
/**
|
|
67
73
|
* Value
|
|
68
74
|
*/
|
|
@@ -21,7 +21,7 @@ const FlexBox_1 = require("./FlexBox");
|
|
|
21
21
|
const ReactApp_1 = require("./app/ReactApp");
|
|
22
22
|
function ButtonPopupList(props) {
|
|
23
23
|
// Destruct
|
|
24
|
-
const { addSplitter = /\s*[,;]\s*/, value, items, labelFormatter, labels, onAdd, onValueChange } = props;
|
|
24
|
+
const { addSplitter = /\s*[,;]\s*/, value, items, labelFormatter, labels, itemSize = { xs: 12, md: 6, lx: 4 }, onAdd, onValueChange } = props;
|
|
25
25
|
// Ref
|
|
26
26
|
const inputRef = react_1.default.useRef(null);
|
|
27
27
|
// State
|
|
@@ -38,7 +38,7 @@ function ButtonPopupList(props) {
|
|
|
38
38
|
: undefined;
|
|
39
39
|
setCurrentValue(value);
|
|
40
40
|
onValueChange(value);
|
|
41
|
-
}, children: (0, jsx_runtime_1.jsx)(Grid_1.default, { container: true, spacing: 0, children: items.map((item) => ((0, jsx_runtime_1.jsx)(Grid_1.default, { size:
|
|
41
|
+
}, children: (0, jsx_runtime_1.jsx)(Grid_1.default, { container: true, spacing: 0, children: items.map((item) => ((0, jsx_runtime_1.jsx)(Grid_1.default, { size: itemSize, display: "flex", justifyContent: "flex-start", alignItems: "center", gap: 1, children: (0, jsx_runtime_1.jsx)(FormControlLabel_1.default, { control: (0, jsx_runtime_1.jsx)(Radio_1.default, { value: item.id }), label: `${labelFormatter(item)}` }) }, item.id))) }) }), 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 () => {
|
|
42
42
|
if (inputRef.current == null)
|
|
43
43
|
return;
|
|
44
44
|
const input = inputRef.current.value.trim();
|
|
@@ -66,7 +66,7 @@ function ButtonPopupRadio(props) {
|
|
|
66
66
|
// App
|
|
67
67
|
const app = (0, ReactApp_1.useRequiredAppContext)();
|
|
68
68
|
// Destruct
|
|
69
|
-
const { addSplitter, inputName, label, labelEnd, labelFormatter = (data) => {
|
|
69
|
+
const { addSplitter, inputName, itemSize, label, labelEnd, labelFormatter = (data) => {
|
|
70
70
|
if (labelField in data) {
|
|
71
71
|
return data[labelField];
|
|
72
72
|
}
|
|
@@ -115,7 +115,7 @@ function ButtonPopupRadio(props) {
|
|
|
115
115
|
setCurrentValue(id);
|
|
116
116
|
onValueChange?.(id);
|
|
117
117
|
},
|
|
118
|
-
inputs: ((0, jsx_runtime_1.jsx)(ButtonPopupList, { addSplitter: addSplitter, value: currentValue, items: items, labelFormatter: labelFormatter, labels: labels, onAdd: onAdd, onValueChange: (id) => {
|
|
118
|
+
inputs: ((0, jsx_runtime_1.jsx)(ButtonPopupList, { addSplitter: addSplitter, value: currentValue, items: items, itemSize: itemSize, labelFormatter: labelFormatter, labels: labels, onAdd: onAdd, onValueChange: (id) => {
|
|
119
119
|
tempSelectedId.current = id;
|
|
120
120
|
} })),
|
|
121
121
|
fullScreen: app.smDown
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { DataTypes, IdType } from "@etsoo/shared";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { CSSProperties, Theme } from "@mui/material/styles";
|
|
4
|
+
import { useSortable } from "@dnd-kit/react/sortable";
|
|
5
|
+
import { DragDropEvents } from "@dnd-kit/react";
|
|
6
|
+
/**
|
|
7
|
+
* DnD sortable item default style
|
|
8
|
+
* @param index Item index
|
|
9
|
+
* @param isDragging Is dragging
|
|
10
|
+
* @param theme Theme
|
|
11
|
+
* @returns Style
|
|
12
|
+
*/
|
|
13
|
+
export declare const DnDSortableItemStyle: (index: number, isDragging: boolean, theme: Theme) => {
|
|
14
|
+
padding: string;
|
|
15
|
+
transform: string;
|
|
16
|
+
zIndex: string | number;
|
|
17
|
+
boxShadow: string;
|
|
18
|
+
background: string;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* DnD sortable list forward ref
|
|
22
|
+
*/
|
|
23
|
+
export interface DnDSortableListRef<D extends object> {
|
|
24
|
+
/**
|
|
25
|
+
* Add item
|
|
26
|
+
* @param item New item
|
|
27
|
+
*/
|
|
28
|
+
addItem(item: D): void;
|
|
29
|
+
/**
|
|
30
|
+
* Add items
|
|
31
|
+
* @param items items
|
|
32
|
+
*/
|
|
33
|
+
addItems(items: D[]): void;
|
|
34
|
+
/**
|
|
35
|
+
* Delete item
|
|
36
|
+
* @param index Item index
|
|
37
|
+
*/
|
|
38
|
+
deleteItem(index: number): void;
|
|
39
|
+
/**
|
|
40
|
+
* Edit item
|
|
41
|
+
* @param newItem New item
|
|
42
|
+
* @param index Index
|
|
43
|
+
*/
|
|
44
|
+
editItem(newItem: D, index: number): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Get all items
|
|
47
|
+
*/
|
|
48
|
+
getItems(): D[];
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* DnD sortable list props
|
|
52
|
+
*/
|
|
53
|
+
export type DnDSortableListProps<D extends object, E extends React.ElementType = React.ElementType> = {
|
|
54
|
+
/**
|
|
55
|
+
* Component type to render the list into
|
|
56
|
+
* Default is React.Fragment
|
|
57
|
+
*/
|
|
58
|
+
component?: E;
|
|
59
|
+
/**
|
|
60
|
+
* Component props
|
|
61
|
+
*/
|
|
62
|
+
componentProps?: React.ComponentProps<E>;
|
|
63
|
+
/**
|
|
64
|
+
* List items
|
|
65
|
+
*/
|
|
66
|
+
items: D[];
|
|
67
|
+
/**
|
|
68
|
+
* Id field
|
|
69
|
+
*/
|
|
70
|
+
idField?: DataTypes.Keys<D> | ((item: D) => IdType);
|
|
71
|
+
/**
|
|
72
|
+
* Label field
|
|
73
|
+
*/
|
|
74
|
+
labelField: DataTypes.Keys<D, string> | ((item: D) => string);
|
|
75
|
+
/**
|
|
76
|
+
* Methods ref
|
|
77
|
+
*/
|
|
78
|
+
mRef?: React.Ref<DnDSortableListRef<D>>;
|
|
79
|
+
/**
|
|
80
|
+
* Item renderer
|
|
81
|
+
*/
|
|
82
|
+
itemRenderer: (data: D, style: CSSProperties, state: ReturnType<typeof useSortable<D>>) => React.ReactElement;
|
|
83
|
+
/**
|
|
84
|
+
* Get list item style callback
|
|
85
|
+
*/
|
|
86
|
+
itemStyle?: (index: number, isDragging: boolean) => CSSProperties;
|
|
87
|
+
/**
|
|
88
|
+
* Data change handler
|
|
89
|
+
*/
|
|
90
|
+
onChange?: (items: D[]) => void;
|
|
91
|
+
/**
|
|
92
|
+
* Drag start handler
|
|
93
|
+
*/
|
|
94
|
+
onDragStart?: (items: D[], event: Parameters<DragDropEvents["dragstart"]>[0]) => void;
|
|
95
|
+
/**
|
|
96
|
+
* Drag end handler
|
|
97
|
+
*/
|
|
98
|
+
onDragEnd?: (items: D[], ...args: Parameters<DragDropEvents["dragend"]>) => void;
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* DnD sortable list component
|
|
102
|
+
* @param props Props
|
|
103
|
+
* @returns Component
|
|
104
|
+
*/
|
|
105
|
+
export declare function DnDSortableList<D extends object, E extends React.ElementType = React.ElementType>(props: DnDSortableListProps<D, E>): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.DnDSortableItemStyle = void 0;
|
|
7
|
+
exports.DnDSortableList = DnDSortableList;
|
|
8
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
9
|
+
const react_1 = __importDefault(require("react"));
|
|
10
|
+
const styles_1 = require("@mui/material/styles");
|
|
11
|
+
const sortable_1 = require("@dnd-kit/react/sortable");
|
|
12
|
+
const react_2 = require("@dnd-kit/react");
|
|
13
|
+
/**
|
|
14
|
+
* DnD sortable item default style
|
|
15
|
+
* @param index Item index
|
|
16
|
+
* @param isDragging Is dragging
|
|
17
|
+
* @param theme Theme
|
|
18
|
+
* @returns Style
|
|
19
|
+
*/
|
|
20
|
+
const DnDSortableItemStyle = (index, isDragging, theme) => ({
|
|
21
|
+
padding: theme.spacing(1),
|
|
22
|
+
transform: isDragging ? "scale(1.03)" : "none",
|
|
23
|
+
zIndex: isDragging ? 1 : "auto",
|
|
24
|
+
boxShadow: isDragging
|
|
25
|
+
? `-1px 0 8px 0 ${theme.palette.grey[400]}, 0px 8px 8px 0 ${theme.palette.grey[200]}`
|
|
26
|
+
: "none",
|
|
27
|
+
background: isDragging
|
|
28
|
+
? theme.palette.primary.light
|
|
29
|
+
: index % 2 === 0
|
|
30
|
+
? theme.palette.grey[100]
|
|
31
|
+
: theme.palette.grey[50]
|
|
32
|
+
});
|
|
33
|
+
exports.DnDSortableItemStyle = DnDSortableItemStyle;
|
|
34
|
+
function SortableItem(props) {
|
|
35
|
+
const theme = (0, styles_1.useTheme)();
|
|
36
|
+
const { id, data, index, itemRenderer, itemStyle = (index, isDragging) => (0, exports.DnDSortableItemStyle)(index, isDragging, theme) } = props;
|
|
37
|
+
const state = (0, sortable_1.useSortable)({ id, data, index });
|
|
38
|
+
const style = itemStyle(index, state.isDragging);
|
|
39
|
+
return itemRenderer(data, style, state);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* DnD sortable list component
|
|
43
|
+
* @param props Props
|
|
44
|
+
* @returns Component
|
|
45
|
+
*/
|
|
46
|
+
function DnDSortableList(props) {
|
|
47
|
+
// Destruct
|
|
48
|
+
const Component = props.component || react_1.default.Fragment;
|
|
49
|
+
const { componentProps, idField, itemRenderer, itemStyle, labelField, mRef, onChange, onDragStart, onDragEnd } = props;
|
|
50
|
+
const idFn = typeof idField === "function"
|
|
51
|
+
? idField
|
|
52
|
+
: (item) => !idField
|
|
53
|
+
? Reflect.get(item, "id")
|
|
54
|
+
: item[idField];
|
|
55
|
+
const labelFn = react_1.default.useCallback(typeof labelField === "function"
|
|
56
|
+
? labelField
|
|
57
|
+
: (item) => item[labelField], [labelField]);
|
|
58
|
+
// States
|
|
59
|
+
const [items, setItems] = react_1.default.useState([]);
|
|
60
|
+
react_1.default.useEffect(() => {
|
|
61
|
+
setItems(props.items);
|
|
62
|
+
}, [props.items]);
|
|
63
|
+
const changeItems = react_1.default.useCallback((newItems) => {
|
|
64
|
+
// Possible to alter items with the handler
|
|
65
|
+
onChange?.(newItems);
|
|
66
|
+
// Update state
|
|
67
|
+
setItems(newItems);
|
|
68
|
+
}, [onChange]);
|
|
69
|
+
// Methods
|
|
70
|
+
react_1.default.useImperativeHandle(mRef, () => {
|
|
71
|
+
return {
|
|
72
|
+
addItem(newItem) {
|
|
73
|
+
// Existence check
|
|
74
|
+
if (items.some((item) => labelFn(item) === labelFn(newItem))) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
// Clone
|
|
78
|
+
const newItems = [newItem, ...items];
|
|
79
|
+
// Update the state
|
|
80
|
+
changeItems(newItems);
|
|
81
|
+
return true;
|
|
82
|
+
},
|
|
83
|
+
addItems(inputItems) {
|
|
84
|
+
// Clone
|
|
85
|
+
const newItems = [...items];
|
|
86
|
+
// Insert items
|
|
87
|
+
inputItems.forEach((newItem) => {
|
|
88
|
+
// Existence check
|
|
89
|
+
if (newItems.some((item) => labelFn(item) === labelFn(newItem))) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
newItems.push(newItem);
|
|
93
|
+
});
|
|
94
|
+
// Update the state
|
|
95
|
+
changeItems(newItems);
|
|
96
|
+
return newItems.length - items.length;
|
|
97
|
+
},
|
|
98
|
+
editItem(newItem, index) {
|
|
99
|
+
// Existence check
|
|
100
|
+
const newIndex = items.findIndex((item) => labelFn(item) === labelFn(newItem));
|
|
101
|
+
if (newIndex >= 0 && newIndex !== index) {
|
|
102
|
+
// Label field is the same with a different item
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
// Clone
|
|
106
|
+
const newItems = [...items];
|
|
107
|
+
// Remove the item
|
|
108
|
+
newItems.splice(index, 1, newItem);
|
|
109
|
+
// Update the state
|
|
110
|
+
changeItems(newItems);
|
|
111
|
+
return true;
|
|
112
|
+
},
|
|
113
|
+
deleteItem(index) {
|
|
114
|
+
// Clone
|
|
115
|
+
const newItems = [...items];
|
|
116
|
+
// Remove the item
|
|
117
|
+
newItems.splice(index, 1);
|
|
118
|
+
// Update the state
|
|
119
|
+
changeItems(newItems);
|
|
120
|
+
},
|
|
121
|
+
getItems() {
|
|
122
|
+
return items;
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
}, [items, labelFn, changeItems]);
|
|
126
|
+
function handleDragEnd(...args) {
|
|
127
|
+
// Event
|
|
128
|
+
const event = args[0];
|
|
129
|
+
// Cancelled
|
|
130
|
+
if (event.canceled)
|
|
131
|
+
return;
|
|
132
|
+
if ((0, sortable_1.isSortableOperation)(event.operation) && event.operation.source) {
|
|
133
|
+
const { initialIndex, index } = event.operation.source;
|
|
134
|
+
if (initialIndex === index)
|
|
135
|
+
return;
|
|
136
|
+
// Clone
|
|
137
|
+
const newItems = [...items];
|
|
138
|
+
// Removed item
|
|
139
|
+
const [removed] = newItems.splice(initialIndex, 1);
|
|
140
|
+
// Insert to the destination index
|
|
141
|
+
newItems.splice(index, 0, removed);
|
|
142
|
+
changeItems(newItems);
|
|
143
|
+
// Drag end handler
|
|
144
|
+
onDragEnd?.(newItems, ...args);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return ((0, jsx_runtime_1.jsx)(react_2.DragDropProvider, { onDragStart: (event) => onDragStart?.(items, event), onDragEnd: (event, manager) => handleDragEnd(event, manager), children: (0, jsx_runtime_1.jsx)(Component, { ...componentProps, children: items.map((item, index) => {
|
|
148
|
+
const id = idFn(item);
|
|
149
|
+
return ((0, jsx_runtime_1.jsx)(SortableItem, { id: id, index: index, data: item, itemRenderer: itemRenderer, itemStyle: itemStyle }, id));
|
|
150
|
+
}) }) }));
|
|
151
|
+
}
|
package/lib/cjs/index.d.ts
CHANGED
|
@@ -54,7 +54,7 @@ export * from "./DataLoadField";
|
|
|
54
54
|
export * from "./DataSteps";
|
|
55
55
|
export * from "./DataTable";
|
|
56
56
|
export * from "./DialogButton";
|
|
57
|
-
export * from "./
|
|
57
|
+
export * from "./DnDSortableList";
|
|
58
58
|
export * from "./DownUpButton";
|
|
59
59
|
export * from "./DraggablePaperComponent";
|
|
60
60
|
export * from "./EmailInput";
|
package/lib/cjs/index.js
CHANGED
|
@@ -70,7 +70,7 @@ __exportStar(require("./DataLoadField"), exports);
|
|
|
70
70
|
__exportStar(require("./DataSteps"), exports);
|
|
71
71
|
__exportStar(require("./DataTable"), exports);
|
|
72
72
|
__exportStar(require("./DialogButton"), exports);
|
|
73
|
-
__exportStar(require("./
|
|
73
|
+
__exportStar(require("./DnDSortableList"), exports);
|
|
74
74
|
__exportStar(require("./DownUpButton"), exports);
|
|
75
75
|
__exportStar(require("./DraggablePaperComponent"), exports);
|
|
76
76
|
__exportStar(require("./EmailInput"), exports);
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { ButtonProps } from "@mui/material/Button";
|
|
2
|
-
import {
|
|
2
|
+
import { IdType } from "@etsoo/shared";
|
|
3
|
+
import { GridSize } from "@mui/material/Grid";
|
|
4
|
+
import { DnDSortableListProps } from "./DnDSortableList";
|
|
5
|
+
import { ResponsiveStyleValue } from "./ResponsiveStyleValue";
|
|
3
6
|
type DnDItemType = {
|
|
4
7
|
id: IdType;
|
|
5
8
|
};
|
|
@@ -23,7 +26,7 @@ export type ButtonPopupCheckboxProps<D extends DnDItemType> = Omit<ButtonProps,
|
|
|
23
26
|
/**
|
|
24
27
|
* Label field in items
|
|
25
28
|
*/
|
|
26
|
-
labelField:
|
|
29
|
+
labelField: DnDSortableListProps<D>["labelField"];
|
|
27
30
|
/**
|
|
28
31
|
* Label formatter
|
|
29
32
|
* @param item Item to be formatted
|
|
@@ -63,6 +66,10 @@ export type ButtonPopupCheckboxProps<D extends DnDItemType> = Omit<ButtonProps,
|
|
|
63
66
|
* The field is required or not
|
|
64
67
|
*/
|
|
65
68
|
required?: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Item size
|
|
71
|
+
*/
|
|
72
|
+
itemSize?: ResponsiveStyleValue<GridSize>;
|
|
66
73
|
/**
|
|
67
74
|
* Value
|
|
68
75
|
*/
|
|
@@ -13,10 +13,10 @@ import TextField from "@mui/material/TextField";
|
|
|
13
13
|
import { HBox, VBox } from "./FlexBox";
|
|
14
14
|
import { useRequiredAppContext } from "./app/ReactApp";
|
|
15
15
|
import FormLabel from "@mui/material/FormLabel";
|
|
16
|
-
import {
|
|
16
|
+
import { DnDSortableList } from "./DnDSortableList";
|
|
17
17
|
function ButtonPopupList(props) {
|
|
18
18
|
// Destruct
|
|
19
|
-
const { addSplitter = /\s*[,;]\s*/, value = [], items, labelField, labelFormatter, labels, onAdd, onValueChange } = props;
|
|
19
|
+
const { addSplitter = /\s*[,;]\s*/, value = [], items, itemSize = { xs: 12, md: 6, lx: 4 }, labelField, labelFormatter, labels, onAdd, onValueChange } = props;
|
|
20
20
|
// Methods
|
|
21
21
|
const dndRef = React.createRef();
|
|
22
22
|
// Refs
|
|
@@ -34,18 +34,18 @@ function ButtonPopupList(props) {
|
|
|
34
34
|
// Set selected ids
|
|
35
35
|
setSelectedIdsHandler([...value]);
|
|
36
36
|
}, [value]);
|
|
37
|
-
return (_jsxs(VBox, { gap: 2, children: [_jsx(
|
|
37
|
+
return (_jsxs(VBox, { gap: 2, children: [_jsx(DnDSortableList, { component: Grid, componentProps: { container: true, spacing: 0 }, items: items, labelField: labelField, onChange: (items) => {
|
|
38
38
|
const ids = items
|
|
39
39
|
.filter((item) => tempSelectedIds.current.includes(item.id))
|
|
40
40
|
.map((item) => item.id);
|
|
41
41
|
onValueChange(ids);
|
|
42
|
-
}, itemRenderer: (item, index,
|
|
42
|
+
}, itemRenderer: (item, style, { sortable: { index }, ref, handleRef }) => (_jsxs(Grid, { size: itemSize, display: "flex", justifyContent: "flex-start", alignItems: "center", gap: 1, ref: ref, style: style, children: [_jsx(IconButton, { style: { cursor: "move" }, size: "small", title: labels?.dragIndicator, ref: handleRef, children: _jsx(DragIndicatorIcon, {}) }), _jsx(FormControlLabel, { control: _jsx(Checkbox, { name: "item", value: item.id, checked: selectedIds.includes(item.id), onChange: (e) => {
|
|
43
43
|
const checked = e.target.checked;
|
|
44
44
|
const newIds = [
|
|
45
45
|
...selectedIds.toggleItem(item.id, checked)
|
|
46
46
|
];
|
|
47
47
|
setSelectedIdsHandler(newIds);
|
|
48
|
-
} }), label: `${index + 1}. ${labelFormatter(item)}` })] })),
|
|
48
|
+
} }), label: `${index + 1}. ${labelFormatter(item)}` })] })), 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 () => {
|
|
49
49
|
if (inputRef.current == null)
|
|
50
50
|
return;
|
|
51
51
|
const input = inputRef.current.value.trim();
|
|
@@ -75,7 +75,9 @@ export function ButtonPopupCheckbox(props) {
|
|
|
75
75
|
// App
|
|
76
76
|
const app = useRequiredAppContext();
|
|
77
77
|
// Destruct
|
|
78
|
-
const { addSplitter, value = [], inputName, label, labelEnd, labelFormatter = (data) => {
|
|
78
|
+
const { addSplitter, value = [], inputName, itemSize, label, labelEnd, labelFormatter = (data) => {
|
|
79
|
+
if (typeof labelField === "function")
|
|
80
|
+
return labelField(data);
|
|
79
81
|
if (labelField in data) {
|
|
80
82
|
return data[labelField];
|
|
81
83
|
}
|
|
@@ -122,7 +124,7 @@ export function ButtonPopupCheckbox(props) {
|
|
|
122
124
|
setSelectedIds(ids);
|
|
123
125
|
onValueChange?.(ids);
|
|
124
126
|
},
|
|
125
|
-
inputs: (_jsx(ButtonPopupList, { addSplitter: addSplitter, value: selectedIds, items: items, labelFormatter: labelFormatter, labelField: labelField, labels: labels, onAdd: onAdd, onValueChange: (ids) => {
|
|
127
|
+
inputs: (_jsx(ButtonPopupList, { addSplitter: addSplitter, value: selectedIds, items: items, labelFormatter: labelFormatter, labelField: labelField, itemSize: itemSize, labels: labels, onAdd: onAdd, onValueChange: (ids) => {
|
|
126
128
|
tempSelectedIds.current = ids;
|
|
127
129
|
} })),
|
|
128
130
|
fullScreen: app.smDown
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { ButtonProps } from "@mui/material/Button";
|
|
2
2
|
import { DataTypes, IdType } from "@etsoo/shared";
|
|
3
|
+
import { GridSize } from "@mui/material/Grid";
|
|
4
|
+
import { ResponsiveStyleValue } from "./ResponsiveStyleValue";
|
|
3
5
|
type DnDItemType = {
|
|
4
6
|
id: IdType;
|
|
5
7
|
};
|
|
@@ -63,6 +65,10 @@ export type ButtonPopupRadioProps<D extends DnDItemType> = Omit<ButtonProps, "ch
|
|
|
63
65
|
* The field is required or not
|
|
64
66
|
*/
|
|
65
67
|
required?: boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Item size
|
|
70
|
+
*/
|
|
71
|
+
itemSize?: ResponsiveStyleValue<GridSize>;
|
|
66
72
|
/**
|
|
67
73
|
* Value
|
|
68
74
|
*/
|
|
@@ -15,7 +15,7 @@ import { HBox, VBox } from "./FlexBox";
|
|
|
15
15
|
import { useRequiredAppContext } from "./app/ReactApp";
|
|
16
16
|
function ButtonPopupList(props) {
|
|
17
17
|
// Destruct
|
|
18
|
-
const { addSplitter = /\s*[,;]\s*/, value, items, labelFormatter, labels, onAdd, onValueChange } = props;
|
|
18
|
+
const { addSplitter = /\s*[,;]\s*/, value, items, labelFormatter, labels, itemSize = { xs: 12, md: 6, lx: 4 }, onAdd, onValueChange } = props;
|
|
19
19
|
// Ref
|
|
20
20
|
const inputRef = React.useRef(null);
|
|
21
21
|
// State
|
|
@@ -32,7 +32,7 @@ function ButtonPopupList(props) {
|
|
|
32
32
|
: undefined;
|
|
33
33
|
setCurrentValue(value);
|
|
34
34
|
onValueChange(value);
|
|
35
|
-
}, children: _jsx(Grid, { container: true, spacing: 0, children: items.map((item) => (_jsx(Grid, { size:
|
|
35
|
+
}, children: _jsx(Grid, { container: true, spacing: 0, children: items.map((item) => (_jsx(Grid, { size: itemSize, display: "flex", justifyContent: "flex-start", alignItems: "center", gap: 1, children: _jsx(FormControlLabel, { control: _jsx(Radio, { value: item.id }), label: `${labelFormatter(item)}` }) }, item.id))) }) }), 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 () => {
|
|
36
36
|
if (inputRef.current == null)
|
|
37
37
|
return;
|
|
38
38
|
const input = inputRef.current.value.trim();
|
|
@@ -60,7 +60,7 @@ export function ButtonPopupRadio(props) {
|
|
|
60
60
|
// App
|
|
61
61
|
const app = useRequiredAppContext();
|
|
62
62
|
// Destruct
|
|
63
|
-
const { addSplitter, inputName, label, labelEnd, labelFormatter = (data) => {
|
|
63
|
+
const { addSplitter, inputName, itemSize, label, labelEnd, labelFormatter = (data) => {
|
|
64
64
|
if (labelField in data) {
|
|
65
65
|
return data[labelField];
|
|
66
66
|
}
|
|
@@ -109,7 +109,7 @@ export function ButtonPopupRadio(props) {
|
|
|
109
109
|
setCurrentValue(id);
|
|
110
110
|
onValueChange?.(id);
|
|
111
111
|
},
|
|
112
|
-
inputs: (_jsx(ButtonPopupList, { addSplitter: addSplitter, value: currentValue, items: items, labelFormatter: labelFormatter, labels: labels, onAdd: onAdd, onValueChange: (id) => {
|
|
112
|
+
inputs: (_jsx(ButtonPopupList, { addSplitter: addSplitter, value: currentValue, items: items, itemSize: itemSize, labelFormatter: labelFormatter, labels: labels, onAdd: onAdd, onValueChange: (id) => {
|
|
113
113
|
tempSelectedId.current = id;
|
|
114
114
|
} })),
|
|
115
115
|
fullScreen: app.smDown
|