@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.
@@ -0,0 +1,345 @@
1
+ import { DataTypes, IdType } from "@etsoo/shared";
2
+ import React from "react";
3
+ import { CSSProperties, Theme, useTheme } from "@mui/material/styles";
4
+ import { isSortableOperation, useSortable } from "@dnd-kit/react/sortable";
5
+ import { DragDropEvents, DragDropProvider } from "@dnd-kit/react";
6
+
7
+ /**
8
+ * DnD sortable item default style
9
+ * @param index Item index
10
+ * @param isDragging Is dragging
11
+ * @param theme Theme
12
+ * @returns Style
13
+ */
14
+ export const DnDSortableItemStyle = (
15
+ index: number,
16
+ isDragging: boolean,
17
+ theme: Theme
18
+ ) => ({
19
+ padding: theme.spacing(1),
20
+ transform: isDragging ? "scale(1.03)" : "none",
21
+ zIndex: isDragging ? 1 : "auto",
22
+ boxShadow: isDragging
23
+ ? `-1px 0 8px 0 ${theme.palette.grey[400]}, 0px 8px 8px 0 ${theme.palette.grey[200]}`
24
+ : "none",
25
+ background: isDragging
26
+ ? theme.palette.primary.light
27
+ : index % 2 === 0
28
+ ? theme.palette.grey[100]
29
+ : theme.palette.grey[50]
30
+ });
31
+
32
+ /**
33
+ * DnD sortable list forward ref
34
+ */
35
+ export interface DnDSortableListRef<D extends object> {
36
+ /**
37
+ * Add item
38
+ * @param item New item
39
+ */
40
+ addItem(item: D): void;
41
+
42
+ /**
43
+ * Add items
44
+ * @param items items
45
+ */
46
+ addItems(items: D[]): void;
47
+
48
+ /**
49
+ * Delete item
50
+ * @param index Item index
51
+ */
52
+ deleteItem(index: number): void;
53
+
54
+ /**
55
+ * Edit item
56
+ * @param newItem New item
57
+ * @param index Index
58
+ */
59
+ editItem(newItem: D, index: number): boolean;
60
+
61
+ /**
62
+ * Get all items
63
+ */
64
+ getItems(): D[];
65
+ }
66
+
67
+ /**
68
+ * DnD sortable list props
69
+ */
70
+ export type DnDSortableListProps<
71
+ D extends object,
72
+ E extends React.ElementType = React.ElementType
73
+ > = {
74
+ /**
75
+ * Component type to render the list into
76
+ * Default is React.Fragment
77
+ */
78
+ component?: E;
79
+
80
+ /**
81
+ * Component props
82
+ */
83
+ componentProps?: React.ComponentProps<E>;
84
+
85
+ /**
86
+ * List items
87
+ */
88
+ items: D[];
89
+
90
+ /**
91
+ * Id field
92
+ */
93
+ idField?: DataTypes.Keys<D> | ((item: D) => IdType);
94
+
95
+ /**
96
+ * Label field
97
+ */
98
+ labelField: DataTypes.Keys<D, string> | ((item: D) => string);
99
+
100
+ /**
101
+ * Methods ref
102
+ */
103
+ mRef?: React.Ref<DnDSortableListRef<D>>;
104
+
105
+ /**
106
+ * Item renderer
107
+ */
108
+ itemRenderer: (
109
+ data: D,
110
+ style: CSSProperties,
111
+ state: ReturnType<typeof useSortable<D>>
112
+ ) => React.ReactElement;
113
+
114
+ /**
115
+ * Get list item style callback
116
+ */
117
+ itemStyle?: (index: number, isDragging: boolean) => CSSProperties;
118
+
119
+ /**
120
+ * Data change handler
121
+ */
122
+ onChange?: (items: D[]) => void;
123
+
124
+ /**
125
+ * Drag start handler
126
+ */
127
+ onDragStart?: (
128
+ items: D[],
129
+ event: Parameters<DragDropEvents["dragstart"]>[0]
130
+ ) => void;
131
+
132
+ /**
133
+ * Drag end handler
134
+ */
135
+ onDragEnd?: (
136
+ items: D[],
137
+ ...args: Parameters<DragDropEvents["dragend"]>
138
+ ) => void;
139
+ };
140
+
141
+ type SortableItemProps<D extends object> = {
142
+ id: IdType;
143
+ index: number;
144
+ data: D;
145
+ itemStyle?: (index: number, isDragging: boolean) => CSSProperties;
146
+ } & Pick<DnDSortableListProps<D>, "itemRenderer">;
147
+
148
+ function SortableItem<D extends object>(props: SortableItemProps<D>) {
149
+ const theme = useTheme();
150
+ const {
151
+ id,
152
+ data,
153
+ index,
154
+ itemRenderer,
155
+ itemStyle = (index, isDragging) =>
156
+ DnDSortableItemStyle(index, isDragging, theme)
157
+ } = props;
158
+ const state = useSortable<D>({ id, data, index });
159
+ const style = itemStyle(index, state.isDragging);
160
+ return itemRenderer(data, style, state);
161
+ }
162
+
163
+ /**
164
+ * DnD sortable list component
165
+ * @param props Props
166
+ * @returns Component
167
+ */
168
+ export function DnDSortableList<
169
+ D extends object,
170
+ E extends React.ElementType = React.ElementType
171
+ >(props: DnDSortableListProps<D, E>) {
172
+ // Destruct
173
+ const Component = props.component || React.Fragment;
174
+ const {
175
+ componentProps,
176
+ idField,
177
+ itemRenderer,
178
+ itemStyle,
179
+ labelField,
180
+ mRef,
181
+ onChange,
182
+ onDragStart,
183
+ onDragEnd
184
+ } = props;
185
+
186
+ const idFn =
187
+ typeof idField === "function"
188
+ ? idField
189
+ : (item: D) =>
190
+ !idField
191
+ ? (Reflect.get(item, "id") as IdType)
192
+ : (item[idField] as IdType);
193
+
194
+ const labelFn = React.useCallback(
195
+ typeof labelField === "function"
196
+ ? labelField
197
+ : (item: D) => item[labelField] as string,
198
+ [labelField]
199
+ );
200
+
201
+ // States
202
+ const [items, setItems] = React.useState<D[]>([]);
203
+
204
+ React.useEffect(() => {
205
+ setItems(props.items);
206
+ }, [props.items]);
207
+
208
+ const changeItems = React.useCallback(
209
+ (newItems: D[]) => {
210
+ // Possible to alter items with the handler
211
+ onChange?.(newItems);
212
+
213
+ // Update state
214
+ setItems(newItems);
215
+ },
216
+ [onChange]
217
+ );
218
+
219
+ // Methods
220
+ React.useImperativeHandle(mRef, () => {
221
+ return {
222
+ addItem(newItem: D) {
223
+ // Existence check
224
+ if (items.some((item) => labelFn(item) === labelFn(newItem))) {
225
+ return false;
226
+ }
227
+
228
+ // Clone
229
+ const newItems = [newItem, ...items];
230
+
231
+ // Update the state
232
+ changeItems(newItems);
233
+
234
+ return true;
235
+ },
236
+
237
+ addItems(inputItems: D[]) {
238
+ // Clone
239
+ const newItems = [...items];
240
+
241
+ // Insert items
242
+ inputItems.forEach((newItem) => {
243
+ // Existence check
244
+ if (newItems.some((item) => labelFn(item) === labelFn(newItem))) {
245
+ return;
246
+ }
247
+
248
+ newItems.push(newItem);
249
+ });
250
+
251
+ // Update the state
252
+ changeItems(newItems);
253
+
254
+ return newItems.length - items.length;
255
+ },
256
+
257
+ editItem(newItem: D, index: number) {
258
+ // Existence check
259
+ const newIndex = items.findIndex(
260
+ (item) => labelFn(item) === labelFn(newItem)
261
+ );
262
+ if (newIndex >= 0 && newIndex !== index) {
263
+ // Label field is the same with a different item
264
+ return false;
265
+ }
266
+
267
+ // Clone
268
+ const newItems = [...items];
269
+
270
+ // Remove the item
271
+ newItems.splice(index, 1, newItem);
272
+
273
+ // Update the state
274
+ changeItems(newItems);
275
+
276
+ return true;
277
+ },
278
+
279
+ deleteItem(index: number) {
280
+ // Clone
281
+ const newItems = [...items];
282
+
283
+ // Remove the item
284
+ newItems.splice(index, 1);
285
+
286
+ // Update the state
287
+ changeItems(newItems);
288
+ },
289
+
290
+ getItems() {
291
+ return items;
292
+ }
293
+ };
294
+ }, [items, labelFn, changeItems]);
295
+
296
+ function handleDragEnd(...args: Parameters<DragDropEvents["dragend"]>) {
297
+ // Event
298
+ const event = args[0];
299
+
300
+ // Cancelled
301
+ if (event.canceled) return;
302
+
303
+ if (isSortableOperation(event.operation) && event.operation.source) {
304
+ const { initialIndex, index } = event.operation.source;
305
+ if (initialIndex === index) return;
306
+
307
+ // Clone
308
+ const newItems = [...items];
309
+
310
+ // Removed item
311
+ const [removed] = newItems.splice(initialIndex, 1);
312
+
313
+ // Insert to the destination index
314
+ newItems.splice(index, 0, removed);
315
+
316
+ changeItems(newItems);
317
+
318
+ // Drag end handler
319
+ onDragEnd?.(newItems, ...args);
320
+ }
321
+ }
322
+
323
+ return (
324
+ <DragDropProvider
325
+ onDragStart={(event) => onDragStart?.(items, event)}
326
+ onDragEnd={(event, manager) => handleDragEnd(event, manager)}
327
+ >
328
+ <Component {...componentProps}>
329
+ {items.map((item, index) => {
330
+ const id = idFn(item);
331
+ return (
332
+ <SortableItem
333
+ key={id}
334
+ id={id}
335
+ index={index}
336
+ data={item}
337
+ itemRenderer={itemRenderer}
338
+ itemStyle={itemStyle}
339
+ />
340
+ );
341
+ })}
342
+ </Component>
343
+ </DragDropProvider>
344
+ );
345
+ }
package/src/index.ts CHANGED
@@ -60,7 +60,7 @@ export * from "./DataLoadField";
60
60
  export * from "./DataSteps";
61
61
  export * from "./DataTable";
62
62
  export * from "./DialogButton";
63
- export * from "./DnDList";
63
+ export * from "./DnDSortableList";
64
64
  export * from "./DownUpButton";
65
65
  export * from "./DraggablePaperComponent";
66
66
  export * from "./EmailInput";
@@ -1,111 +0,0 @@
1
- import type { UniqueIdentifier } from "@dnd-kit/core";
2
- import { SortingStrategy } from "@dnd-kit/sortable/dist/types/strategies";
3
- import { DataTypes } from "@etsoo/shared";
4
- import { Theme } from "@mui/material/styles";
5
- import React, { CSSProperties } from "react";
6
- /**
7
- * DnD 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 DnDItemStyle: (index: number, isDragging: boolean, theme: Theme) => {
14
- padding: string;
15
- zIndex: string | number;
16
- background: string;
17
- };
18
- /**
19
- * DnD list forward ref
20
- */
21
- export interface DnDListRef<D extends object> {
22
- /**
23
- * Add item
24
- * @param item New item
25
- */
26
- addItem(item: D): void;
27
- /**
28
- * Add items
29
- * @param items items
30
- */
31
- addItems(items: D[]): void;
32
- /**
33
- * Delete item
34
- * @param index Item index
35
- */
36
- deleteItem(index: number): void;
37
- /**
38
- * Edit item
39
- * @param newItem New item
40
- * @param index Index
41
- */
42
- editItem(newItem: D, index: number): boolean;
43
- /**
44
- * Get all items
45
- */
46
- getItems(): D[];
47
- }
48
- /**
49
- * DnD sortable list properties
50
- */
51
- export interface DnDListPros<D extends {
52
- id: UniqueIdentifier;
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>;
63
- /**
64
- * Get list item style callback
65
- */
66
- getItemStyle?: (index: number, isDragging: boolean) => CSSProperties;
67
- /**
68
- * Item renderer
69
- */
70
- itemRenderer: (item: D, index: number, nodeRef: React.ComponentProps<any>, actionNodeRef: React.ComponentProps<any>) => React.ReactElement;
71
- /**
72
- * Height
73
- */
74
- height?: string | number;
75
- /**
76
- * List items
77
- */
78
- items: D[];
79
- /**
80
- * Label field
81
- */
82
- labelField: DataTypes.Keys<D>;
83
- /**
84
- * Methods ref
85
- */
86
- mRef?: React.Ref<DnDListRef<D>>;
87
- /**
88
- * Sorting strategy
89
- */
90
- sortingStrategy?: "rect" | "vertical" | "horizontal" | "rectSwapping" | (() => SortingStrategy);
91
- /**
92
- * Data change handler
93
- */
94
- onChange?: (items: D[]) => void;
95
- /**
96
- * Form data change handler
97
- */
98
- onFormChange?: (items: D[]) => void;
99
- /**
100
- * Drag end handler
101
- */
102
- onDragEnd?: (items: D[]) => void;
103
- }
104
- /**
105
- * DnD (Drag and Drop) sortable list
106
- * @param props Props
107
- * @returns Component
108
- */
109
- export declare function DnDList<D extends {
110
- id: UniqueIdentifier;
111
- }, E extends React.ElementType = React.ElementType>(props: DnDListPros<D, E>): import("react/jsx-runtime").JSX.Element;
@@ -1,238 +0,0 @@
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.DnDItemStyle = void 0;
7
- exports.DnDList = DnDList;
8
- const jsx_runtime_1 = require("react/jsx-runtime");
9
- const Skeleton_1 = __importDefault(require("@mui/material/Skeleton"));
10
- const styles_1 = require("@mui/material/styles");
11
- const react_1 = __importDefault(require("react"));
12
- function SortableItem(props) {
13
- // Destruct
14
- const { id, useSortableType, CSSType, itemRenderer, style = {} } = props;
15
- // Use sortable
16
- const { attributes, listeners, setNodeRef, transform, transition, setActivatorNodeRef } = useSortableType({ id });
17
- const allStyle = {
18
- ...style,
19
- transform: CSSType.Transform.toString(transform),
20
- transition
21
- };
22
- const nodeRef = {
23
- style: allStyle,
24
- ref: setNodeRef,
25
- ...attributes
26
- };
27
- const actionNodeRef = {
28
- ...listeners,
29
- ref: setActivatorNodeRef
30
- };
31
- return itemRenderer(nodeRef, actionNodeRef);
32
- }
33
- /**
34
- * DnD item default style
35
- * @param index Item index
36
- * @param isDragging Is dragging
37
- * @param theme Theme
38
- * @returns Style
39
- */
40
- const DnDItemStyle = (index, isDragging, theme) => ({
41
- padding: theme.spacing(1),
42
- zIndex: isDragging ? 1 : "auto",
43
- background: isDragging
44
- ? theme.palette.primary.light
45
- : index % 2 === 0
46
- ? theme.palette.grey[100]
47
- : theme.palette.grey[50]
48
- });
49
- exports.DnDItemStyle = DnDItemStyle;
50
- /**
51
- * DnD (Drag and Drop) sortable list
52
- * @param props Props
53
- * @returns Component
54
- */
55
- function DnDList(props) {
56
- // Destruct
57
- const { componentProps, height = 360, itemRenderer, labelField, mRef, sortingStrategy, onChange, onFormChange, onDragEnd } = props;
58
- const Component = props.component || react_1.default.Fragment;
59
- // Theme
60
- const theme = (0, styles_1.useTheme)();
61
- // States
62
- const [items, setItems] = react_1.default.useState([]);
63
- const [activeId, setActiveId] = react_1.default.useState();
64
- react_1.default.useEffect(() => {
65
- setItems(props.items);
66
- }, [props.items]);
67
- const doFormChange = react_1.default.useCallback((newItems) => {
68
- if (onFormChange) {
69
- const locals = Array.isArray(newItems) ? newItems : items;
70
- onFormChange(locals);
71
- }
72
- }, [items, onFormChange]);
73
- const changeItems = react_1.default.useCallback((newItems) => {
74
- // Possible to alter items with the handler
75
- if (onChange)
76
- onChange(newItems);
77
- doFormChange(newItems);
78
- // Update state
79
- setItems(newItems);
80
- }, [onChange, doFormChange]);
81
- // Methods
82
- react_1.default.useImperativeHandle(mRef, () => {
83
- return {
84
- addItem(newItem) {
85
- // Existence check
86
- if (items.some((item) => item[labelField] === newItem[labelField])) {
87
- return false;
88
- }
89
- // Clone
90
- const newItems = [newItem, ...items];
91
- // Update the state
92
- changeItems(newItems);
93
- return true;
94
- },
95
- addItems(inputItems) {
96
- // Clone
97
- const newItems = [...items];
98
- // Insert items
99
- inputItems.forEach((newItem) => {
100
- // Existence check
101
- if (newItems.some((item) => item[labelField] === newItem[labelField])) {
102
- return;
103
- }
104
- newItems.push(newItem);
105
- });
106
- // Update the state
107
- changeItems(newItems);
108
- return newItems.length - items.length;
109
- },
110
- editItem(newItem, index) {
111
- // Existence check
112
- const newIndex = items.findIndex((item) => item[labelField] === newItem[labelField]);
113
- if (newIndex >= 0 && newIndex !== index) {
114
- // Label field is the same with a different item
115
- return false;
116
- }
117
- // Clone
118
- const newItems = [...items];
119
- // Remove the item
120
- newItems.splice(index, 1, newItem);
121
- // Update the state
122
- changeItems(newItems);
123
- return true;
124
- },
125
- deleteItem(index) {
126
- // Clone
127
- const newItems = [...items];
128
- // Remove the item
129
- newItems.splice(index, 1);
130
- // Update the state
131
- changeItems(newItems);
132
- },
133
- getItems() {
134
- return items;
135
- }
136
- };
137
- }, [items, labelField, changeItems]);
138
- // Dynamic import library
139
- const [dnd, setDnd] = react_1.default.useState();
140
- react_1.default.useEffect(() => {
141
- Promise.all([
142
- import("@dnd-kit/core"),
143
- import("@dnd-kit/sortable"),
144
- import("@dnd-kit/utilities")
145
- ]).then(([{ DndContext }, { SortableContext, useSortable, rectSortingStrategy, rectSwappingStrategy, horizontalListSortingStrategy, verticalListSortingStrategy }, { CSS }]) => {
146
- setDnd([
147
- DndContext,
148
- SortableContext,
149
- useSortable,
150
- rectSortingStrategy,
151
- rectSwappingStrategy,
152
- horizontalListSortingStrategy,
153
- verticalListSortingStrategy,
154
- CSS
155
- ]);
156
- });
157
- }, []);
158
- const setupDiv = (div, clearup = false) => {
159
- // Inputs
160
- div
161
- .querySelectorAll("input")
162
- .forEach((input) => clearup
163
- ? input.removeEventListener("change", doFormChange)
164
- : input.addEventListener("change", doFormChange));
165
- // Textareas
166
- div
167
- .querySelectorAll("textarea")
168
- .forEach((input) => clearup
169
- ? input.removeEventListener("change", doFormChange)
170
- : input.addEventListener("change", doFormChange));
171
- // Select
172
- div
173
- .querySelectorAll("select")
174
- .forEach((input) => clearup
175
- ? input.removeEventListener("change", doFormChange)
176
- : input.addEventListener("change", doFormChange));
177
- };
178
- const divRef = react_1.default.useRef(null);
179
- if (dnd == null) {
180
- return (0, jsx_runtime_1.jsx)(Skeleton_1.default, { variant: "rectangular", width: "100%", height: height });
181
- }
182
- const [DndContextType, SortableContextType, useSortableType, rectSortingStrategyType, rectSwappingStrategyType, horizontalListSortingStrategyType, verticalListSortingStrategyType, CSSType] = dnd;
183
- const strategy = typeof sortingStrategy === "function"
184
- ? sortingStrategy()
185
- : sortingStrategy === "rect"
186
- ? rectSortingStrategyType
187
- : sortingStrategy === "rectSwapping"
188
- ? rectSwappingStrategyType
189
- : sortingStrategy === "horizontal"
190
- ? horizontalListSortingStrategyType
191
- : sortingStrategy === "vertical"
192
- ? verticalListSortingStrategyType
193
- : undefined;
194
- let getItemStyle = props.getItemStyle;
195
- if (getItemStyle == null) {
196
- getItemStyle = (index, isDragging) => (0, exports.DnDItemStyle)(index, isDragging, theme);
197
- }
198
- // Drag event handlers
199
- function handleDragStart(event) {
200
- const { active } = event;
201
- setActiveId(active.id);
202
- }
203
- function handleDragEnd(event) {
204
- const { active, over } = event;
205
- if (over && active.id !== over.id) {
206
- // Indices
207
- const oldIndex = items.findIndex((item) => item.id === active.id);
208
- const newIndex = items.findIndex((item) => item.id === over.id);
209
- // Clone
210
- const newItems = [...items];
211
- // Removed item
212
- const [removed] = newItems.splice(oldIndex, 1);
213
- // Insert to the destination index
214
- newItems.splice(newIndex, 0, removed);
215
- changeItems(newItems);
216
- // Drag end handler
217
- if (onDragEnd)
218
- onDragEnd(newItems);
219
- }
220
- setActiveId(undefined);
221
- }
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
- }) }) }) }));
226
- if (onFormChange) {
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
- }
237
- return children;
238
- }