@etsoo/materialui 1.5.60 → 1.5.62

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.
@@ -16,10 +16,10 @@ const IconButton_1 = __importDefault(require("@mui/material/IconButton"));
16
16
  const FormControlLabel_1 = __importDefault(require("@mui/material/FormControlLabel"));
17
17
  const Checkbox_1 = __importDefault(require("@mui/material/Checkbox"));
18
18
  const TextField_1 = __importDefault(require("@mui/material/TextField"));
19
- const DnDList_1 = require("./DnDList");
20
19
  const FlexBox_1 = require("./FlexBox");
21
20
  const ReactApp_1 = require("./app/ReactApp");
22
21
  const FormLabel_1 = __importDefault(require("@mui/material/FormLabel"));
22
+ const DnDList_1 = require("./DnDList");
23
23
  function ButtonPopupList(props) {
24
24
  // Destruct
25
25
  const { addSplitter = /\s*[,;]\s*/, value = [], items, labelField, labelFormatter, labels, onAdd, onValueChange } = props;
@@ -35,18 +35,18 @@ function ButtonPopupList(props) {
35
35
  // Set selected ids
36
36
  setSelectedIds([...value]);
37
37
  }, [value]);
38
- return ((0, jsx_runtime_1.jsxs)(FlexBox_1.VBox, { gap: 2, 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, onFormChange: (items) => {
39
- const ids = items
40
- .filter((item) => selectedIds.includes(item.id))
41
- .map((item) => item.id);
42
- onValueChange(ids);
43
- }, 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) => {
44
- const checked = e.target.checked;
45
- const newIds = [
46
- ...selectedIds.toggleItem(item.id, checked)
47
- ];
48
- setSelectedIds(newIds);
49
- } }), 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)(DnDList_1.DnDList, { component: Grid_1.default, componentProps: { container: true, spacing: 0 }, items: items, labelField: labelField, onFormChange: (items) => {
39
+ const ids = items
40
+ .filter((item) => selectedIds.includes(item.id))
41
+ .map((item) => item.id);
42
+ onValueChange(ids);
43
+ }, 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) => {
44
+ const checked = e.target.checked;
45
+ const newIds = [
46
+ ...selectedIds.toggleItem(item.id, checked)
47
+ ];
48
+ setSelectedIds(newIds);
49
+ } }), 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 () => {
50
50
  if (inputRef.current == null)
51
51
  return;
52
52
  const input = inputRef.current.value.trim();
@@ -50,7 +50,16 @@ export interface DnDListRef<D extends object> {
50
50
  */
51
51
  export interface DnDListPros<D extends {
52
52
  id: UniqueIdentifier;
53
- }> {
53
+ }, 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>;
54
63
  /**
55
64
  * Get list item style callback
56
65
  */
@@ -99,4 +108,4 @@ export interface DnDListPros<D extends {
99
108
  */
100
109
  export declare function DnDList<D extends {
101
110
  id: UniqueIdentifier;
102
- }>(props: DnDListPros<D>): import("react/jsx-runtime").JSX.Element;
111
+ }, E extends React.ElementType = React.ElementType>(props: DnDListPros<D, E>): import("react/jsx-runtime").JSX.Element;
@@ -54,7 +54,8 @@ exports.DnDItemStyle = DnDItemStyle;
54
54
  */
55
55
  function DnDList(props) {
56
56
  // Destruct
57
- const { height = 360, itemRenderer, labelField, mRef, sortingStrategy, onChange, onFormChange, onDragEnd } = props;
57
+ const { componentProps, height = 360, itemRenderer, labelField, mRef, sortingStrategy, onChange, onFormChange, onDragEnd } = props;
58
+ const Component = props.component || react_1.default.Fragment;
58
59
  // Theme
59
60
  const theme = (0, styles_1.useTheme)();
60
61
  // States
@@ -64,8 +65,10 @@ function DnDList(props) {
64
65
  setItems(props.items);
65
66
  }, [props.items]);
66
67
  const doFormChange = react_1.default.useCallback((newItems) => {
67
- if (onFormChange)
68
- onFormChange(newItems ?? items);
68
+ if (onFormChange) {
69
+ const locals = Array.isArray(newItems) ? newItems : items;
70
+ onFormChange(locals);
71
+ }
69
72
  }, [items, onFormChange]);
70
73
  const changeItems = react_1.default.useCallback((newItems) => {
71
74
  // Possible to alter items with the handler
@@ -152,38 +155,27 @@ function DnDList(props) {
152
155
  ]);
153
156
  });
154
157
  }, []);
155
- const doChange = react_1.default.useCallback(() => doFormChange(), []);
156
158
  const setupDiv = (div, clearup = false) => {
157
159
  // Inputs
158
160
  div
159
161
  .querySelectorAll("input")
160
162
  .forEach((input) => clearup
161
- ? input.removeEventListener("change", doChange)
162
- : input.addEventListener("change", doChange));
163
+ ? input.removeEventListener("change", doFormChange)
164
+ : input.addEventListener("change", doFormChange));
163
165
  // Textareas
164
166
  div
165
167
  .querySelectorAll("textarea")
166
168
  .forEach((input) => clearup
167
- ? input.removeEventListener("change", doChange)
168
- : input.addEventListener("change", doChange));
169
+ ? input.removeEventListener("change", doFormChange)
170
+ : input.addEventListener("change", doFormChange));
169
171
  // Select
170
172
  div
171
173
  .querySelectorAll("select")
172
174
  .forEach((input) => clearup
173
- ? input.removeEventListener("change", doChange)
174
- : input.addEventListener("change", doChange));
175
+ ? input.removeEventListener("change", doFormChange)
176
+ : input.addEventListener("change", doFormChange));
175
177
  };
176
- const divRef = react_1.default.useRef(null);
177
- react_1.default.useEffect(() => {
178
- if (divRef.current) {
179
- setupDiv(divRef.current);
180
- }
181
- return () => {
182
- if (divRef.current) {
183
- setupDiv(divRef.current, true);
184
- }
185
- };
186
- }, []);
178
+ const divRef = react_1.default.useRef();
187
179
  if (dnd == null) {
188
180
  return (0, jsx_runtime_1.jsx)(Skeleton_1.default, { variant: "rectangular", width: "100%", height: height });
189
181
  }
@@ -227,12 +219,20 @@ function DnDList(props) {
227
219
  }
228
220
  setActiveId(undefined);
229
221
  }
230
- const children = ((0, jsx_runtime_1.jsx)(DndContextType, { onDragStart: handleDragStart, onDragEnd: handleDragEnd, children: (0, jsx_runtime_1.jsx)(SortableContextType, { items: items, strategy: strategy, children: items.map((item, index) => {
231
- const id = item.id;
232
- return ((0, jsx_runtime_1.jsx)(SortableItem, { id: id, useSortableType: useSortableType, CSSType: CSSType, style: getItemStyle(index, id === activeId), itemRenderer: (nodeRef, actionNodeRef) => itemRenderer(item, index, nodeRef, actionNodeRef) }, id));
233
- }) }) }));
222
+ const children = ((0, jsx_runtime_1.jsx)(DndContextType, { onDragStart: handleDragStart, onDragEnd: handleDragEnd, children: (0, jsx_runtime_1.jsx)(SortableContextType, { items: items, strategy: strategy, children: (0, jsx_runtime_1.jsx)(Component, { ...componentProps, children: items.map((item, index) => {
223
+ const id = item.id;
224
+ return ((0, jsx_runtime_1.jsx)(SortableItem, { id: id, useSortableType: useSortableType, CSSType: CSSType, style: getItemStyle(index, id === activeId), itemRenderer: (nodeRef, actionNodeRef) => itemRenderer(item, index, nodeRef, actionNodeRef) }, id));
225
+ }) }) }) }));
234
226
  if (onFormChange) {
235
- return ((0, jsx_runtime_1.jsx)("div", { style: { width: "100%" }, ref: divRef, children: children }));
227
+ return ((0, jsx_runtime_1.jsx)("div", { style: { width: "100%" }, ref: (div) => {
228
+ if (div && divRef.current != div) {
229
+ if (divRef.current) {
230
+ setupDiv(divRef.current, true);
231
+ }
232
+ divRef.current = div;
233
+ setupDiv(div);
234
+ }
235
+ }, children: children }));
236
236
  }
237
237
  return children;
238
238
  }
@@ -10,10 +10,10 @@ import IconButton from "@mui/material/IconButton";
10
10
  import FormControlLabel from "@mui/material/FormControlLabel";
11
11
  import Checkbox from "@mui/material/Checkbox";
12
12
  import TextField from "@mui/material/TextField";
13
- import { DnDList } from "./DnDList";
14
13
  import { HBox, VBox } from "./FlexBox";
15
14
  import { useRequiredAppContext } from "./app/ReactApp";
16
15
  import FormLabel from "@mui/material/FormLabel";
16
+ import { DnDList } from "./DnDList";
17
17
  function ButtonPopupList(props) {
18
18
  // Destruct
19
19
  const { addSplitter = /\s*[,;]\s*/, value = [], items, labelField, labelFormatter, labels, onAdd, onValueChange } = props;
@@ -29,18 +29,18 @@ function ButtonPopupList(props) {
29
29
  // Set selected ids
30
30
  setSelectedIds([...value]);
31
31
  }, [value]);
32
- return (_jsxs(VBox, { gap: 2, children: [_jsx(Grid, { container: true, spacing: 0, children: _jsx(DnDList, { items: items, labelField: labelField, onFormChange: (items) => {
33
- const ids = items
34
- .filter((item) => selectedIds.includes(item.id))
35
- .map((item) => item.id);
36
- onValueChange(ids);
37
- }, 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) => {
38
- const checked = e.target.checked;
39
- const newIds = [
40
- ...selectedIds.toggleItem(item.id, checked)
41
- ];
42
- setSelectedIds(newIds);
43
- } }), 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(DnDList, { component: Grid, componentProps: { container: true, spacing: 0 }, items: items, labelField: labelField, onFormChange: (items) => {
33
+ const ids = items
34
+ .filter((item) => selectedIds.includes(item.id))
35
+ .map((item) => item.id);
36
+ onValueChange(ids);
37
+ }, 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) => {
38
+ const checked = e.target.checked;
39
+ const newIds = [
40
+ ...selectedIds.toggleItem(item.id, checked)
41
+ ];
42
+ setSelectedIds(newIds);
43
+ } }), 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 () => {
44
44
  if (inputRef.current == null)
45
45
  return;
46
46
  const input = inputRef.current.value.trim();
@@ -50,7 +50,16 @@ export interface DnDListRef<D extends object> {
50
50
  */
51
51
  export interface DnDListPros<D extends {
52
52
  id: UniqueIdentifier;
53
- }> {
53
+ }, 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>;
54
63
  /**
55
64
  * Get list item style callback
56
65
  */
@@ -99,4 +108,4 @@ export interface DnDListPros<D extends {
99
108
  */
100
109
  export declare function DnDList<D extends {
101
110
  id: UniqueIdentifier;
102
- }>(props: DnDListPros<D>): import("react/jsx-runtime").JSX.Element;
111
+ }, E extends React.ElementType = React.ElementType>(props: DnDListPros<D, E>): import("react/jsx-runtime").JSX.Element;
@@ -46,7 +46,8 @@ export const DnDItemStyle = (index, isDragging, theme) => ({
46
46
  */
47
47
  export function DnDList(props) {
48
48
  // Destruct
49
- const { height = 360, itemRenderer, labelField, mRef, sortingStrategy, onChange, onFormChange, onDragEnd } = props;
49
+ const { componentProps, height = 360, itemRenderer, labelField, mRef, sortingStrategy, onChange, onFormChange, onDragEnd } = props;
50
+ const Component = props.component || React.Fragment;
50
51
  // Theme
51
52
  const theme = useTheme();
52
53
  // States
@@ -56,8 +57,10 @@ export function DnDList(props) {
56
57
  setItems(props.items);
57
58
  }, [props.items]);
58
59
  const doFormChange = React.useCallback((newItems) => {
59
- if (onFormChange)
60
- onFormChange(newItems ?? items);
60
+ if (onFormChange) {
61
+ const locals = Array.isArray(newItems) ? newItems : items;
62
+ onFormChange(locals);
63
+ }
61
64
  }, [items, onFormChange]);
62
65
  const changeItems = React.useCallback((newItems) => {
63
66
  // Possible to alter items with the handler
@@ -144,38 +147,27 @@ export function DnDList(props) {
144
147
  ]);
145
148
  });
146
149
  }, []);
147
- const doChange = React.useCallback(() => doFormChange(), []);
148
150
  const setupDiv = (div, clearup = false) => {
149
151
  // Inputs
150
152
  div
151
153
  .querySelectorAll("input")
152
154
  .forEach((input) => clearup
153
- ? input.removeEventListener("change", doChange)
154
- : input.addEventListener("change", doChange));
155
+ ? input.removeEventListener("change", doFormChange)
156
+ : input.addEventListener("change", doFormChange));
155
157
  // Textareas
156
158
  div
157
159
  .querySelectorAll("textarea")
158
160
  .forEach((input) => clearup
159
- ? input.removeEventListener("change", doChange)
160
- : input.addEventListener("change", doChange));
161
+ ? input.removeEventListener("change", doFormChange)
162
+ : input.addEventListener("change", doFormChange));
161
163
  // Select
162
164
  div
163
165
  .querySelectorAll("select")
164
166
  .forEach((input) => clearup
165
- ? input.removeEventListener("change", doChange)
166
- : input.addEventListener("change", doChange));
167
+ ? input.removeEventListener("change", doFormChange)
168
+ : input.addEventListener("change", doFormChange));
167
169
  };
168
- const divRef = React.useRef(null);
169
- React.useEffect(() => {
170
- if (divRef.current) {
171
- setupDiv(divRef.current);
172
- }
173
- return () => {
174
- if (divRef.current) {
175
- setupDiv(divRef.current, true);
176
- }
177
- };
178
- }, []);
170
+ const divRef = React.useRef();
179
171
  if (dnd == null) {
180
172
  return _jsx(Skeleton, { variant: "rectangular", width: "100%", height: height });
181
173
  }
@@ -219,12 +211,20 @@ export function DnDList(props) {
219
211
  }
220
212
  setActiveId(undefined);
221
213
  }
222
- const children = (_jsx(DndContextType, { onDragStart: handleDragStart, onDragEnd: handleDragEnd, children: _jsx(SortableContextType, { items: items, strategy: strategy, children: items.map((item, index) => {
223
- const id = item.id;
224
- return (_jsx(SortableItem, { id: id, useSortableType: useSortableType, CSSType: CSSType, style: getItemStyle(index, id === activeId), itemRenderer: (nodeRef, actionNodeRef) => itemRenderer(item, index, nodeRef, actionNodeRef) }, id));
225
- }) }) }));
214
+ const children = (_jsx(DndContextType, { onDragStart: handleDragStart, onDragEnd: handleDragEnd, children: _jsx(SortableContextType, { items: items, strategy: strategy, children: _jsx(Component, { ...componentProps, children: items.map((item, index) => {
215
+ const id = item.id;
216
+ return (_jsx(SortableItem, { id: id, useSortableType: useSortableType, CSSType: CSSType, style: getItemStyle(index, id === activeId), itemRenderer: (nodeRef, actionNodeRef) => itemRenderer(item, index, nodeRef, actionNodeRef) }, id));
217
+ }) }) }) }));
226
218
  if (onFormChange) {
227
- return (_jsx("div", { style: { width: "100%" }, ref: divRef, children: children }));
219
+ return (_jsx("div", { style: { width: "100%" }, ref: (div) => {
220
+ if (div && divRef.current != div) {
221
+ if (divRef.current) {
222
+ setupDiv(divRef.current, true);
223
+ }
224
+ divRef.current = div;
225
+ setupDiv(div);
226
+ }
227
+ }, children: children }));
228
228
  }
229
229
  return children;
230
230
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@etsoo/materialui",
3
- "version": "1.5.60",
3
+ "version": "1.5.62",
4
4
  "description": "TypeScript Material-UI Implementation",
5
5
  "main": "lib/cjs/index.js",
6
6
  "module": "lib/mjs/index.js",
@@ -40,10 +40,10 @@
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.38",
44
- "@etsoo/notificationbase": "^1.1.62",
45
- "@etsoo/react": "^1.8.46",
46
- "@etsoo/shared": "^1.2.74",
43
+ "@etsoo/appscript": "^1.6.39",
44
+ "@etsoo/notificationbase": "^1.1.63",
45
+ "@etsoo/react": "^1.8.49",
46
+ "@etsoo/shared": "^1.2.75",
47
47
  "@mui/icons-material": "^7.1.1",
48
48
  "@mui/material": "^7.1.1",
49
49
  "@mui/x-data-grid": "^8.5.1",
@@ -81,9 +81,9 @@
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.5.1",
84
+ "@vitejs/plugin-react": "^4.5.2",
85
85
  "jsdom": "^26.1.0",
86
86
  "typescript": "^5.8.3",
87
- "vitest": "^3.2.2"
87
+ "vitest": "^3.2.3"
88
88
  }
89
89
  }
@@ -10,10 +10,10 @@ import IconButton from "@mui/material/IconButton";
10
10
  import FormControlLabel from "@mui/material/FormControlLabel";
11
11
  import Checkbox from "@mui/material/Checkbox";
12
12
  import TextField from "@mui/material/TextField";
13
- import { DnDList, DnDListRef } from "./DnDList";
14
13
  import { HBox, VBox } from "./FlexBox";
15
14
  import { useRequiredAppContext } from "./app/ReactApp";
16
15
  import FormLabel from "@mui/material/FormLabel";
16
+ import { DnDList, DnDListRef } from "./DnDList";
17
17
 
18
18
  type DnDItemType = {
19
19
  id: IdType;
@@ -152,56 +152,56 @@ function ButtonPopupList<D extends DnDItemType>(
152
152
 
153
153
  return (
154
154
  <VBox gap={2}>
155
- <Grid container spacing={0}>
156
- <DnDList<D>
157
- items={items}
158
- labelField={labelField}
159
- onFormChange={(items) => {
160
- const ids = items
161
- .filter((item) => selectedIds.includes(item.id))
162
- .map((item) => item.id);
163
- onValueChange(ids);
164
- }}
165
- itemRenderer={(item, index, nodeRef, actionNodeRef) => (
166
- <Grid
167
- size={{ xs: 12, md: 6, lg: 4 }}
168
- display="flex"
169
- justifyContent="flex-start"
170
- alignItems="center"
171
- gap={1}
172
- {...nodeRef}
155
+ <DnDList<D>
156
+ component={Grid}
157
+ componentProps={{ container: true, spacing: 0 }}
158
+ items={items}
159
+ labelField={labelField}
160
+ onFormChange={(items) => {
161
+ const ids = items
162
+ .filter((item) => selectedIds.includes(item.id))
163
+ .map((item) => item.id);
164
+ onValueChange(ids);
165
+ }}
166
+ itemRenderer={(item, index, nodeRef, actionNodeRef) => (
167
+ <Grid
168
+ size={{ xs: 12, md: 6, lg: 4 }}
169
+ display="flex"
170
+ justifyContent="flex-start"
171
+ alignItems="center"
172
+ gap={1}
173
+ {...nodeRef}
174
+ >
175
+ <IconButton
176
+ style={{ cursor: "move" }}
177
+ size="small"
178
+ title={labels?.dragIndicator}
179
+ {...actionNodeRef}
173
180
  >
174
- <IconButton
175
- style={{ cursor: "move" }}
176
- size="small"
177
- title={labels?.dragIndicator}
178
- {...actionNodeRef}
179
- >
180
- <DragIndicatorIcon />
181
- </IconButton>
182
- <FormControlLabel
183
- control={
184
- <Checkbox
185
- name="item"
186
- value={item.id}
187
- checked={selectedIds.includes(item.id)}
188
- onChange={(e) => {
189
- const checked = e.target.checked;
190
- const newIds = [
191
- ...selectedIds.toggleItem(item.id, checked)
192
- ];
193
- setSelectedIds(newIds);
194
- }}
195
- />
196
- }
197
- label={`${index + 1}. ${labelFormatter(item)}`}
198
- />
199
- </Grid>
200
- )}
201
- height={200}
202
- mRef={dndRef}
203
- ></DnDList>
204
- </Grid>
181
+ <DragIndicatorIcon />
182
+ </IconButton>
183
+ <FormControlLabel
184
+ control={
185
+ <Checkbox
186
+ name="item"
187
+ value={item.id}
188
+ checked={selectedIds.includes(item.id)}
189
+ onChange={(e) => {
190
+ const checked = e.target.checked;
191
+ const newIds = [
192
+ ...selectedIds.toggleItem(item.id, checked)
193
+ ];
194
+ setSelectedIds(newIds);
195
+ }}
196
+ />
197
+ }
198
+ label={`${index + 1}. ${labelFormatter(item)}`}
199
+ />
200
+ </Grid>
201
+ )}
202
+ height={200}
203
+ mRef={dndRef}
204
+ ></DnDList>
205
205
  {onAdd && (
206
206
  <HBox gap={1}>
207
207
  <TextField
package/src/DnDList.tsx CHANGED
@@ -119,7 +119,21 @@ export interface DnDListRef<D extends object> {
119
119
  /**
120
120
  * DnD sortable list properties
121
121
  */
122
- export interface DnDListPros<D extends { id: UniqueIdentifier }> {
122
+ export interface DnDListPros<
123
+ D extends { id: UniqueIdentifier },
124
+ E extends React.ElementType = React.ElementType
125
+ > {
126
+ /**
127
+ * Component type to render the list into
128
+ * Default is React.Fragment
129
+ */
130
+ component?: E;
131
+
132
+ /**
133
+ * Component props
134
+ */
135
+ componentProps?: React.ComponentProps<E>;
136
+
123
137
  /**
124
138
  * Get list item style callback
125
139
  */
@@ -186,11 +200,13 @@ export interface DnDListPros<D extends { id: UniqueIdentifier }> {
186
200
  * @param props Props
187
201
  * @returns Component
188
202
  */
189
- export function DnDList<D extends { id: UniqueIdentifier }>(
190
- props: DnDListPros<D>
191
- ) {
203
+ export function DnDList<
204
+ D extends { id: UniqueIdentifier },
205
+ E extends React.ElementType = React.ElementType
206
+ >(props: DnDListPros<D, E>) {
192
207
  // Destruct
193
208
  const {
209
+ componentProps,
194
210
  height = 360,
195
211
  itemRenderer,
196
212
  labelField,
@@ -201,6 +217,8 @@ export function DnDList<D extends { id: UniqueIdentifier }>(
201
217
  onDragEnd
202
218
  } = props;
203
219
 
220
+ const Component = props.component || React.Fragment;
221
+
204
222
  // Theme
205
223
  const theme = useTheme();
206
224
 
@@ -213,8 +231,11 @@ export function DnDList<D extends { id: UniqueIdentifier }>(
213
231
  }, [props.items]);
214
232
 
215
233
  const doFormChange = React.useCallback(
216
- (newItems?: D[]) => {
217
- if (onFormChange) onFormChange(newItems ?? items);
234
+ (newItems?: D[] | Event) => {
235
+ if (onFormChange) {
236
+ const locals = Array.isArray(newItems) ? newItems : items;
237
+ onFormChange(locals);
238
+ }
218
239
  },
219
240
  [items, onFormChange]
220
241
  );
@@ -362,16 +383,14 @@ export function DnDList<D extends { id: UniqueIdentifier }>(
362
383
  );
363
384
  }, []);
364
385
 
365
- const doChange = React.useCallback(() => doFormChange(), []);
366
-
367
386
  const setupDiv = (div: HTMLDivElement, clearup: boolean = false) => {
368
387
  // Inputs
369
388
  div
370
389
  .querySelectorAll("input")
371
390
  .forEach((input) =>
372
391
  clearup
373
- ? input.removeEventListener("change", doChange)
374
- : input.addEventListener("change", doChange)
392
+ ? input.removeEventListener("change", doFormChange)
393
+ : input.addEventListener("change", doFormChange)
375
394
  );
376
395
 
377
396
  // Textareas
@@ -379,8 +398,8 @@ export function DnDList<D extends { id: UniqueIdentifier }>(
379
398
  .querySelectorAll("textarea")
380
399
  .forEach((input) =>
381
400
  clearup
382
- ? input.removeEventListener("change", doChange)
383
- : input.addEventListener("change", doChange)
401
+ ? input.removeEventListener("change", doFormChange)
402
+ : input.addEventListener("change", doFormChange)
384
403
  );
385
404
 
386
405
  // Select
@@ -388,24 +407,12 @@ export function DnDList<D extends { id: UniqueIdentifier }>(
388
407
  .querySelectorAll("select")
389
408
  .forEach((input) =>
390
409
  clearup
391
- ? input.removeEventListener("change", doChange)
392
- : input.addEventListener("change", doChange)
410
+ ? input.removeEventListener("change", doFormChange)
411
+ : input.addEventListener("change", doFormChange)
393
412
  );
394
413
  };
395
414
 
396
- const divRef = React.useRef<HTMLDivElement>(null);
397
-
398
- React.useEffect(() => {
399
- if (divRef.current) {
400
- setupDiv(divRef.current);
401
- }
402
-
403
- return () => {
404
- if (divRef.current) {
405
- setupDiv(divRef.current, true);
406
- }
407
- };
408
- }, []);
415
+ const divRef = React.useRef<HTMLDivElement>();
409
416
 
410
417
  if (dnd == null) {
411
418
  return <Skeleton variant="rectangular" width="100%" height={height} />;
@@ -476,28 +483,42 @@ export function DnDList<D extends { id: UniqueIdentifier }>(
476
483
  const children = (
477
484
  <DndContextType onDragStart={handleDragStart} onDragEnd={handleDragEnd}>
478
485
  <SortableContextType items={items} strategy={strategy}>
479
- {items.map((item, index) => {
480
- const id = item.id;
481
- return (
482
- <SortableItem
483
- id={id}
484
- useSortableType={useSortableType}
485
- CSSType={CSSType}
486
- key={id}
487
- style={getItemStyle!(index, id === activeId)}
488
- itemRenderer={(nodeRef, actionNodeRef) =>
489
- itemRenderer(item, index, nodeRef, actionNodeRef)
490
- }
491
- />
492
- );
493
- })}
486
+ <Component {...componentProps}>
487
+ {items.map((item, index) => {
488
+ const id = item.id;
489
+ return (
490
+ <SortableItem
491
+ id={id}
492
+ useSortableType={useSortableType}
493
+ CSSType={CSSType}
494
+ key={id}
495
+ style={getItemStyle!(index, id === activeId)}
496
+ itemRenderer={(nodeRef, actionNodeRef) =>
497
+ itemRenderer(item, index, nodeRef, actionNodeRef)
498
+ }
499
+ />
500
+ );
501
+ })}
502
+ </Component>
494
503
  </SortableContextType>
495
504
  </DndContextType>
496
505
  );
497
506
 
498
507
  if (onFormChange) {
499
508
  return (
500
- <div style={{ width: "100%" }} ref={divRef}>
509
+ <div
510
+ style={{ width: "100%" }}
511
+ ref={(div) => {
512
+ if (div && divRef.current != div) {
513
+ if (divRef.current) {
514
+ setupDiv(divRef.current, true);
515
+ }
516
+
517
+ divRef.current = div;
518
+ setupDiv(div);
519
+ }
520
+ }}
521
+ >
501
522
  {children}
502
523
  </div>
503
524
  );