@os-design/drag-sort 1.0.18 → 1.0.20

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.
Files changed (92) hide show
  1. package/dist/cjs/DragAndDrop.js +9 -9
  2. package/dist/cjs/DragAndDrop.js.map +1 -1
  3. package/dist/cjs/Draggable.js +6 -6
  4. package/dist/cjs/Draggable.js.map +1 -1
  5. package/dist/cjs/Droppable.js +2 -2
  6. package/dist/cjs/Droppable.js.map +1 -1
  7. package/dist/cjs/index.js.map +1 -1
  8. package/dist/cjs/utils/ListStore.js.map +1 -1
  9. package/dist/cjs/utils/NodeList.js +27 -35
  10. package/dist/cjs/utils/NodeList.js.map +1 -1
  11. package/dist/cjs/utils/getElementOffset.js.map +1 -1
  12. package/dist/cjs/utils/getElementScroll.js.map +1 -1
  13. package/dist/cjs/utils/getNodeRect.js +1 -1
  14. package/dist/cjs/utils/getNodeRect.js.map +1 -1
  15. package/dist/cjs/utils/useAppendClassName.js.map +1 -1
  16. package/dist/cjs/utils/useBlankNode.js +2 -2
  17. package/dist/cjs/utils/useBlankNode.js.map +1 -1
  18. package/dist/cjs/utils/useDragAndDrop.js.map +1 -1
  19. package/dist/cjs/utils/useDragEffect.js +14 -14
  20. package/dist/cjs/utils/useDragEffect.js.map +1 -1
  21. package/dist/cjs/utils/useDroppable.js.map +1 -1
  22. package/dist/cjs/utils/useGeneratedId.js.map +1 -1
  23. package/dist/cjs/utils/useGetNodeStyle.js.map +1 -1
  24. package/dist/cjs/utils/useInitRect.js.map +1 -1
  25. package/dist/cjs/utils/useInitScrollOffset.js.map +1 -1
  26. package/dist/cjs/utils/useMoveNode.js +2 -2
  27. package/dist/cjs/utils/useMoveNode.js.map +1 -1
  28. package/dist/cjs/utils/useScrollEventByPoint.js +1 -1
  29. package/dist/cjs/utils/useScrollEventByPoint.js.map +1 -1
  30. package/dist/cjs/utils/useTargetList.js +3 -3
  31. package/dist/cjs/utils/useTargetList.js.map +1 -1
  32. package/dist/cjs/utils/useTransitionStyle.js.map +1 -1
  33. package/dist/esm/DragAndDrop.js +6 -6
  34. package/dist/esm/DragAndDrop.js.map +1 -1
  35. package/dist/esm/Draggable.js +3 -3
  36. package/dist/esm/Draggable.js.map +1 -1
  37. package/dist/esm/Droppable.js +1 -1
  38. package/dist/esm/Droppable.js.map +1 -1
  39. package/dist/esm/utils/ListStore.js.map +1 -1
  40. package/dist/esm/utils/NodeList.js +26 -37
  41. package/dist/esm/utils/NodeList.js.map +1 -1
  42. package/dist/esm/utils/getNodeRect.js +1 -1
  43. package/dist/esm/utils/getNodeRect.js.map +1 -1
  44. package/dist/esm/utils/useAppendClassName.js.map +1 -1
  45. package/dist/esm/utils/useBlankNode.js.map +1 -1
  46. package/dist/esm/utils/useDragAndDrop.js.map +1 -1
  47. package/dist/esm/utils/useDragEffect.js +6 -6
  48. package/dist/esm/utils/useDragEffect.js.map +1 -1
  49. package/dist/esm/utils/useDroppable.js.map +1 -1
  50. package/dist/esm/utils/useGeneratedId.js.map +1 -1
  51. package/dist/esm/utils/useInitRect.js.map +1 -1
  52. package/dist/esm/utils/useMoveNode.js.map +1 -1
  53. package/dist/esm/utils/useScrollEventByPoint.js +1 -1
  54. package/dist/esm/utils/useScrollEventByPoint.js.map +1 -1
  55. package/dist/esm/utils/useTargetList.js +1 -1
  56. package/dist/esm/utils/useTargetList.js.map +1 -1
  57. package/dist/types/DragAndDrop.d.ts +1 -1
  58. package/dist/types/DragAndDrop.d.ts.map +1 -1
  59. package/dist/types/Draggable.d.ts +1 -1
  60. package/dist/types/Draggable.d.ts.map +1 -1
  61. package/dist/types/Droppable.d.ts.map +1 -1
  62. package/dist/types/utils/useDragAndDrop.d.ts +1 -1
  63. package/dist/types/utils/useDragAndDrop.d.ts.map +1 -1
  64. package/dist/types/utils/useDragEffect.d.ts +1 -1
  65. package/dist/types/utils/useDragEffect.d.ts.map +1 -1
  66. package/dist/types/utils/useDroppable.d.ts.map +1 -1
  67. package/dist/types/utils/useMoveNode.d.ts.map +1 -1
  68. package/dist/types/utils/useTargetList.d.ts +1 -1
  69. package/dist/types/utils/useTargetList.d.ts.map +1 -1
  70. package/package.json +21 -13
  71. package/src/DragAndDrop.tsx +235 -0
  72. package/src/Draggable.tsx +109 -0
  73. package/src/Droppable.tsx +142 -0
  74. package/src/index.ts +7 -0
  75. package/src/utils/ListStore.ts +47 -0
  76. package/src/utils/NodeList.ts +245 -0
  77. package/src/utils/getElementOffset.ts +13 -0
  78. package/src/utils/getElementScroll.ts +13 -0
  79. package/src/utils/getNodeRect.ts +29 -0
  80. package/src/utils/useAppendClassName.ts +20 -0
  81. package/src/utils/useBlankNode.ts +104 -0
  82. package/src/utils/useDragAndDrop.ts +32 -0
  83. package/src/utils/useDragEffect.ts +490 -0
  84. package/src/utils/useDroppable.ts +21 -0
  85. package/src/utils/useGeneratedId.ts +6 -0
  86. package/src/utils/useGetNodeStyle.ts +34 -0
  87. package/src/utils/useInitRect.ts +17 -0
  88. package/src/utils/useInitScrollOffset.ts +16 -0
  89. package/src/utils/useMoveNode.ts +97 -0
  90. package/src/utils/useScrollEventByPoint.ts +56 -0
  91. package/src/utils/useTargetList.ts +31 -0
  92. package/src/utils/useTransitionStyle.ts +26 -0
@@ -0,0 +1,47 @@
1
+ import NodeList from './NodeList';
2
+
3
+ /**
4
+ * Stores all the lists where the draggable items is located.
5
+ */
6
+ class ListStore {
7
+ private lists: NodeList[];
8
+
9
+ public constructor() {
10
+ this.lists = [];
11
+ }
12
+
13
+ /**
14
+ * Adds a new list to the store.
15
+ */
16
+ public add(list: NodeList) {
17
+ this.lists.push(list);
18
+ }
19
+
20
+ /**
21
+ * Removes the list from the store.
22
+ */
23
+ public remove(id: string) {
24
+ const index = this.lists.findIndex((item) => item.id === id);
25
+ if (index === -1) return;
26
+ this.lists.splice(index, 1);
27
+ }
28
+
29
+ /**
30
+ * Returns the list in which the position is located.
31
+ */
32
+ public findByPosition(x: number, y: number) {
33
+ return this.lists.find((list) => {
34
+ const { current } = list.ref;
35
+ if (!current) return false;
36
+ const rect = current.getBoundingClientRect();
37
+ return (
38
+ x >= rect.x &&
39
+ x <= rect.x + rect.width &&
40
+ y >= rect.y &&
41
+ y <= rect.y + rect.height
42
+ );
43
+ });
44
+ }
45
+ }
46
+
47
+ export default ListStore;
@@ -0,0 +1,245 @@
1
+ import React, { CSSProperties, RefObject } from 'react';
2
+
3
+ /* eslint-disable @typescript-eslint/no-explicit-any,no-param-reassign,prefer-destructuring,no-constant-condition */
4
+
5
+ // [prev, next, ref, setStyle, index, id]
6
+ export type Node =
7
+ | [Node, Node, RefObject<any>, (style: CSSProperties) => void, number, string]
8
+ | null;
9
+
10
+ export type ExistingNode = Exclude<Node, null>;
11
+
12
+ export interface NodeProps {
13
+ ref: React.MutableRefObject<any>;
14
+ setStyle: (style: CSSProperties) => void;
15
+ index: number;
16
+ id: string;
17
+ }
18
+
19
+ interface RenderDraggedNodeProps {
20
+ /**
21
+ * The index of the dragged node.
22
+ */
23
+ index: number;
24
+ /**
25
+ * The ID of the dragged node.
26
+ */
27
+ id: string;
28
+ /**
29
+ * The style of the dragged node with position.
30
+ */
31
+ style: CSSProperties;
32
+ }
33
+
34
+ export type RenderDraggedNode = (
35
+ props: RenderDraggedNodeProps
36
+ ) => React.ReactNode;
37
+
38
+ interface InitProps {
39
+ id: string;
40
+ ref: RefObject<HTMLDivElement>;
41
+ innerRef: RefObject<HTMLDivElement>;
42
+ horizontal: boolean;
43
+ renderDraggedNode: RenderDraggedNode;
44
+ }
45
+
46
+ /**
47
+ * Stores all the draggable items in the list.
48
+ * The structure of the doubly linked list is used.
49
+ */
50
+ class NodeList {
51
+ /**
52
+ * The ID of the list.
53
+ */
54
+ public id: string;
55
+
56
+ /**
57
+ * The ref to the list.
58
+ */
59
+ public ref: RefObject<HTMLDivElement>;
60
+
61
+ /**
62
+ * The inner ref to the list.
63
+ * Used by the virtual list.
64
+ */
65
+ public innerRef: RefObject<HTMLDivElement>;
66
+
67
+ /**
68
+ * Whether the list is horizontal.
69
+ */
70
+ public horizontal: boolean;
71
+
72
+ /**
73
+ * The callback that renders the dragged node.
74
+ */
75
+ public renderDraggedNode: RenderDraggedNode;
76
+
77
+ /**
78
+ * The head of draggable nodes.
79
+ */
80
+ private head: Node;
81
+
82
+ /**
83
+ * The tail of draggable nodes.
84
+ */
85
+ private tail: Node;
86
+
87
+ /**
88
+ * Listeners of node addition events.
89
+ */
90
+ private listeners: Array<(props: NodeProps) => void>;
91
+
92
+ public constructor(props: InitProps) {
93
+ this.id = props.id;
94
+ this.ref = props.ref;
95
+ this.innerRef = props.innerRef;
96
+ this.horizontal = props.horizontal;
97
+ this.renderDraggedNode = props.renderDraggedNode;
98
+ this.head = null;
99
+ this.tail = null;
100
+ this.listeners = [];
101
+ }
102
+
103
+ public getHead() {
104
+ return this.head;
105
+ }
106
+
107
+ public getTail() {
108
+ return this.tail;
109
+ }
110
+
111
+ /**
112
+ * Adds the node to the beginning.
113
+ * TL: O(1).
114
+ */
115
+ private addToTheBeginning(props: NodeProps): ExistingNode {
116
+ this.head = [
117
+ null,
118
+ this.head,
119
+ props.ref,
120
+ props.setStyle,
121
+ props.index,
122
+ props.id,
123
+ ];
124
+ const [, next] = this.head;
125
+ if (next) next[0] = this.head; // Set the prev cursor of the next element
126
+ if (!this.tail) this.tail = this.head;
127
+ return this.head;
128
+ }
129
+
130
+ /**
131
+ * Adds the node to the end.
132
+ * TL: O(1).
133
+ */
134
+ private addToTheEnd(props: NodeProps): ExistingNode {
135
+ this.tail = [
136
+ this.tail,
137
+ null,
138
+ props.ref,
139
+ props.setStyle,
140
+ props.index,
141
+ props.id,
142
+ ];
143
+ const [prev] = this.tail;
144
+ if (prev) prev[1] = this.tail; // Set the next cursor of the prev element
145
+ if (!this.head) this.head = this.tail;
146
+ return this.tail;
147
+ }
148
+
149
+ /**
150
+ * Adds the node after the specified one.
151
+ * TL: O(1).
152
+ */
153
+ private static addAfter(node: ExistingNode, props: NodeProps): ExistingNode {
154
+ node[1] = [node, node[1], props.ref, props.setStyle, props.index, props.id];
155
+ if (node[1][1]) node[1][1][0] = node[1];
156
+ return node[1];
157
+ }
158
+
159
+ /**
160
+ * Adds a new node depends on its position.
161
+ * Called when a new node is mounted.
162
+ * TL: O(1) – add to the beginning or to the end, O(n) – add to the middle.
163
+ */
164
+ public add(props: NodeProps): ExistingNode {
165
+ const { index } = props;
166
+
167
+ // Run listeners
168
+ this.listeners.forEach((listener) => listener(props));
169
+
170
+ // Add the first node to the beginning
171
+ if (!this.head || !this.tail) {
172
+ return this.addToTheBeginning(props);
173
+ }
174
+
175
+ // Add the node to the beginning if it is located above the first one
176
+ const [, , , , headIndex] = this.head;
177
+ if (index < headIndex) {
178
+ return this.addToTheBeginning(props);
179
+ }
180
+
181
+ // Add the node to the end if it is located below the last one
182
+ const [, , , , tailIndex] = this.tail;
183
+ if (index === tailIndex) {
184
+ // The tail is the blank node
185
+ const [prev] = this.tail;
186
+ if (prev) {
187
+ this.tail[4] += 1;
188
+ return NodeList.addAfter(prev, props);
189
+ }
190
+ }
191
+ if (index > tailIndex) {
192
+ return this.addToTheEnd(props);
193
+ }
194
+
195
+ // Add the node after the one that is located above the current one
196
+ let node = this.head;
197
+ while (true) {
198
+ const [, next] = node;
199
+ if (!next) break;
200
+ const [, , , , nextIndex] = next;
201
+ if (index < nextIndex) break;
202
+ node = next;
203
+ }
204
+ return NodeList.addAfter(node, props);
205
+ }
206
+
207
+ /**
208
+ * Removes the existing node.
209
+ * Called when the node is unmounted.
210
+ * TL: O(1).
211
+ */
212
+ public remove(node: ExistingNode) {
213
+ const [prev, next] = node;
214
+ if (!prev && !next) {
215
+ this.head = null;
216
+ this.tail = null;
217
+ return;
218
+ }
219
+ if (!prev && next) {
220
+ next[0] = null;
221
+ this.head = next;
222
+ return;
223
+ }
224
+ if (prev && !next) {
225
+ prev[1] = null;
226
+ this.tail = prev;
227
+ return;
228
+ }
229
+ if (prev && next) {
230
+ prev[1] = next;
231
+ next[0] = prev;
232
+ }
233
+ }
234
+
235
+ public addListener(callback: (props: NodeProps) => void) {
236
+ this.listeners.push(callback);
237
+ }
238
+
239
+ public removeListener(callback: (props: NodeProps) => void) {
240
+ const index = this.listeners.findIndex((listener) => listener === callback);
241
+ this.listeners.splice(index, 1);
242
+ }
243
+ }
244
+
245
+ export default NodeList;
@@ -0,0 +1,13 @@
1
+ const getElementOffset = (element: HTMLElement) => {
2
+ let offsetLeft = 0;
3
+ let offsetTop = 0;
4
+ let el: HTMLElement | null = element;
5
+ while (el) {
6
+ offsetLeft += el.offsetLeft;
7
+ offsetTop += el.offsetTop;
8
+ el = el.offsetParent as HTMLElement;
9
+ }
10
+ return { offsetLeft, offsetTop };
11
+ };
12
+
13
+ export default getElementOffset;
@@ -0,0 +1,13 @@
1
+ const getElementScroll = (element: HTMLElement) => {
2
+ let scrollLeft = 0;
3
+ let scrollTop = 0;
4
+ let el: HTMLElement | null = element;
5
+ while (el) {
6
+ scrollLeft += el.scrollLeft;
7
+ scrollTop += el.scrollTop;
8
+ el = el.parentElement;
9
+ }
10
+ return { scrollLeft, scrollTop };
11
+ };
12
+
13
+ export default getElementScroll;
@@ -0,0 +1,29 @@
1
+ import { RefObject } from 'react';
2
+ import getElementOffset from './getElementOffset';
3
+ import getElementScroll from './getElementScroll';
4
+
5
+ /**
6
+ * Computes the bounds of the existing node without considering transforms.
7
+ */
8
+ const getNodeRect = (ref: RefObject<HTMLElement>) => {
9
+ if (!ref.current) return null;
10
+
11
+ const parent = ref.current.parentElement;
12
+ const { width, height } = ref.current.getBoundingClientRect();
13
+ const { offsetLeft, offsetTop } = getElementOffset(ref.current);
14
+ const { scrollLeft, scrollTop } = parent
15
+ ? getElementScroll(parent)
16
+ : { scrollLeft: 0, scrollTop: 0 };
17
+
18
+ const left = offsetLeft - scrollLeft;
19
+ const top = offsetTop - scrollTop;
20
+
21
+ return {
22
+ left,
23
+ top,
24
+ right: left + width,
25
+ bottom: top + height,
26
+ };
27
+ };
28
+
29
+ export default getNodeRect;
@@ -0,0 +1,20 @@
1
+ import { RefObject, useEffect } from 'react';
2
+
3
+ /**
4
+ * Adds a new class name to the element.
5
+ */
6
+ const useAppendClassName = (ref: RefObject<HTMLElement>, className: string) => {
7
+ useEffect(() => {
8
+ const element = ref.current;
9
+ if (!element) return () => {};
10
+
11
+ const initClassName = element.className;
12
+ element.className = `${initClassName} ${className}`.trim();
13
+
14
+ return () => {
15
+ element.className = initClassName;
16
+ };
17
+ }, [className, ref]);
18
+ };
19
+
20
+ export default useAppendClassName;
@@ -0,0 +1,104 @@
1
+ import { useCallback, useEffect, useRef } from 'react';
2
+ import NodeList, { ExistingNode } from './NodeList';
3
+
4
+ interface InitSize {
5
+ initWidth: number;
6
+ initHeight: number;
7
+ }
8
+
9
+ interface DraggedNode {
10
+ list: NodeList;
11
+ }
12
+
13
+ interface UseBlankNodeProps {
14
+ draggedNode: DraggedNode | null;
15
+ targetList: NodeList | null;
16
+ initDraggedNodeRect: InitSize | null;
17
+ }
18
+
19
+ interface BlankNode {
20
+ list: NodeList;
21
+ node: ExistingNode;
22
+ }
23
+
24
+ /**
25
+ * Appends the blank node to the list to increase the height of it.
26
+ * Used when the dragged node is located inside another list.
27
+ */
28
+ const useBlankNode = (props: UseBlankNodeProps) => {
29
+ const { draggedNode, targetList, initDraggedNodeRect } = props;
30
+ const initDraggedNodeRectRef = useRef(initDraggedNodeRect);
31
+ const blankNode = useRef<BlankNode | null>(null);
32
+
33
+ useEffect(() => {
34
+ initDraggedNodeRectRef.current = initDraggedNodeRect;
35
+ }, [initDraggedNodeRect]);
36
+
37
+ useEffect(() => {
38
+ if (
39
+ !draggedNode ||
40
+ !targetList ||
41
+ !targetList.ref.current ||
42
+ draggedNode.list === targetList ||
43
+ !initDraggedNodeRectRef.current
44
+ ) {
45
+ return;
46
+ }
47
+
48
+ // Create a new blank div
49
+ const { initWidth, initHeight } = initDraggedNodeRectRef.current;
50
+ const div = document.createElement('div');
51
+ div.style.width = `${initWidth}px`;
52
+ div.style.height = `${initHeight}px`;
53
+ div.style.minWidth = `${initWidth}px`;
54
+ div.style.minHeight = `${initHeight}px`;
55
+
56
+ let parent = targetList.ref.current;
57
+ const innerList = targetList.innerRef.current;
58
+
59
+ // The inner ref used only in the virtual list
60
+ if (innerList) {
61
+ const { width, height } = innerList.getBoundingClientRect();
62
+ div.style.position = 'absolute';
63
+ div.style.left = targetList.horizontal ? `${width}px` : '0px';
64
+ div.style.top = targetList.horizontal ? '0px' : `${height}px`;
65
+ parent = innerList;
66
+ }
67
+
68
+ // Append the blank div to increase the height of the list
69
+ parent.appendChild(div);
70
+
71
+ // Add the blank node to the node list
72
+ const tail = targetList.getTail();
73
+ blankNode.current = {
74
+ list: targetList,
75
+ node: targetList.add({
76
+ ref: { current: div },
77
+ setStyle: () => {},
78
+ index: tail ? tail[4] + 1 : 0,
79
+ id: 'blank',
80
+ }),
81
+ };
82
+ }, [draggedNode, targetList]);
83
+
84
+ const removeBlankNode = useCallback(() => {
85
+ const blank = blankNode.current;
86
+ if (!blank) return;
87
+ blankNode.current = null;
88
+ blank.list.remove(blank.node);
89
+ const [, , blankNodeRef] = blank.node;
90
+ if (!blank.list.ref.current || !blankNodeRef.current) return;
91
+ const parent = blankNodeRef.current.parentElement;
92
+ if (parent) parent.removeChild(blankNodeRef.current);
93
+ }, []);
94
+
95
+ // Remove the blank node if the dragged node has been dropped
96
+ useEffect(() => {
97
+ if (!draggedNode) return () => {};
98
+ return () => removeBlankNode();
99
+ }, [draggedNode, removeBlankNode]);
100
+
101
+ return removeBlankNode;
102
+ };
103
+
104
+ export default useBlankNode;
@@ -0,0 +1,32 @@
1
+ import { Position } from '@os-design/use-drag';
2
+ import React, { MouseEvent, TouchEvent, useContext } from 'react';
3
+ import NodeList, { ExistingNode } from './NodeList';
4
+
5
+ export interface SetDraggedNodeProps {
6
+ list: NodeList;
7
+ node: ExistingNode;
8
+ position: Position;
9
+ }
10
+
11
+ type NodeEventHandler<T> = (e: T, list: NodeList, node: ExistingNode) => void;
12
+
13
+ interface DragAndDropContextProps {
14
+ registerList: (list: NodeList) => void;
15
+ deregisterList: (id: string) => void;
16
+ onMouseDown: NodeEventHandler<MouseEvent>;
17
+ onTouchStart: NodeEventHandler<TouchEvent>;
18
+ nodeClassName: string;
19
+ }
20
+
21
+ export const DragAndDropContext = React.createContext<DragAndDropContextProps>({
22
+ registerList: () => {},
23
+ deregisterList: () => {},
24
+ onMouseDown: () => {},
25
+ onTouchStart: () => {},
26
+ nodeClassName: '',
27
+ });
28
+
29
+ const useDragAndDrop = (): DragAndDropContextProps =>
30
+ useContext(DragAndDropContext);
31
+
32
+ export default useDragAndDrop;