@headless-tree/core 0.0.10 → 0.0.12
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/CHANGELOG.md +12 -0
- package/lib/cjs/core/build-proxified-instance.d.ts +2 -0
- package/lib/cjs/core/build-proxified-instance.js +58 -0
- package/lib/cjs/core/build-static-instance.d.ts +2 -0
- package/lib/cjs/core/build-static-instance.js +26 -0
- package/lib/cjs/core/create-tree.js +62 -40
- package/lib/cjs/features/async-data-loader/feature.d.ts +1 -4
- package/lib/cjs/features/async-data-loader/feature.js +35 -23
- package/lib/cjs/features/async-data-loader/types.d.ts +4 -6
- package/lib/cjs/features/drag-and-drop/feature.d.ts +2 -3
- package/lib/cjs/features/drag-and-drop/feature.js +79 -44
- package/lib/cjs/features/drag-and-drop/types.d.ts +15 -6
- package/lib/cjs/features/drag-and-drop/utils.d.ts +2 -3
- package/lib/cjs/features/drag-and-drop/utils.js +140 -37
- package/lib/cjs/features/expand-all/feature.d.ts +1 -5
- package/lib/cjs/features/expand-all/feature.js +12 -6
- package/lib/cjs/features/hotkeys-core/feature.d.ts +1 -3
- package/lib/cjs/features/main/types.d.ts +8 -2
- package/lib/cjs/features/prop-memoization/feature.d.ts +2 -0
- package/lib/cjs/features/prop-memoization/feature.js +48 -0
- package/lib/cjs/features/prop-memoization/types.d.ts +10 -0
- package/lib/cjs/features/prop-memoization/types.js +2 -0
- package/lib/cjs/features/renaming/feature.d.ts +1 -4
- package/lib/cjs/features/renaming/feature.js +36 -22
- package/lib/cjs/features/renaming/types.d.ts +2 -2
- package/lib/cjs/features/search/feature.d.ts +1 -4
- package/lib/cjs/features/search/feature.js +38 -24
- package/lib/cjs/features/search/types.d.ts +0 -1
- package/lib/cjs/features/selection/feature.d.ts +1 -4
- package/lib/cjs/features/selection/feature.js +54 -35
- package/lib/cjs/features/selection/types.d.ts +1 -1
- package/lib/cjs/features/sync-data-loader/feature.d.ts +1 -3
- package/lib/cjs/features/sync-data-loader/feature.js +7 -2
- package/lib/cjs/features/tree/feature.d.ts +1 -5
- package/lib/cjs/features/tree/feature.js +97 -92
- package/lib/cjs/features/tree/types.d.ts +5 -8
- package/lib/cjs/index.d.ts +5 -1
- package/lib/cjs/index.js +4 -1
- package/lib/cjs/mddocs-entry.d.ts +10 -0
- package/lib/cjs/test-utils/test-tree-do.d.ts +23 -0
- package/lib/cjs/test-utils/test-tree-do.js +99 -0
- package/lib/cjs/test-utils/test-tree-expect.d.ts +15 -0
- package/lib/cjs/test-utils/test-tree-expect.js +62 -0
- package/lib/cjs/test-utils/test-tree.d.ts +47 -0
- package/lib/cjs/test-utils/test-tree.js +203 -0
- package/lib/cjs/types/core.d.ts +39 -24
- package/lib/cjs/utilities/errors.d.ts +1 -0
- package/lib/cjs/utilities/errors.js +5 -0
- package/lib/cjs/utilities/insert-items-at-target.js +10 -3
- package/lib/cjs/utilities/remove-items-from-parents.js +14 -8
- package/lib/cjs/utils.d.ts +3 -3
- package/lib/cjs/utils.js +6 -6
- package/lib/esm/core/build-proxified-instance.d.ts +2 -0
- package/lib/esm/core/build-proxified-instance.js +54 -0
- package/lib/esm/core/build-static-instance.d.ts +2 -0
- package/lib/esm/core/build-static-instance.js +22 -0
- package/lib/esm/core/create-tree.js +62 -40
- package/lib/esm/features/async-data-loader/feature.d.ts +1 -4
- package/lib/esm/features/async-data-loader/feature.js +35 -23
- package/lib/esm/features/async-data-loader/types.d.ts +4 -6
- package/lib/esm/features/drag-and-drop/feature.d.ts +2 -3
- package/lib/esm/features/drag-and-drop/feature.js +79 -44
- package/lib/esm/features/drag-and-drop/types.d.ts +15 -6
- package/lib/esm/features/drag-and-drop/utils.d.ts +2 -3
- package/lib/esm/features/drag-and-drop/utils.js +138 -34
- package/lib/esm/features/expand-all/feature.d.ts +1 -5
- package/lib/esm/features/expand-all/feature.js +12 -6
- package/lib/esm/features/hotkeys-core/feature.d.ts +1 -3
- package/lib/esm/features/main/types.d.ts +8 -2
- package/lib/esm/features/prop-memoization/feature.d.ts +2 -0
- package/lib/esm/features/prop-memoization/feature.js +45 -0
- package/lib/esm/features/prop-memoization/types.d.ts +10 -0
- package/lib/esm/features/prop-memoization/types.js +1 -0
- package/lib/esm/features/renaming/feature.d.ts +1 -4
- package/lib/esm/features/renaming/feature.js +36 -22
- package/lib/esm/features/renaming/types.d.ts +2 -2
- package/lib/esm/features/search/feature.d.ts +1 -4
- package/lib/esm/features/search/feature.js +38 -24
- package/lib/esm/features/search/types.d.ts +0 -1
- package/lib/esm/features/selection/feature.d.ts +1 -4
- package/lib/esm/features/selection/feature.js +54 -35
- package/lib/esm/features/selection/types.d.ts +1 -1
- package/lib/esm/features/sync-data-loader/feature.d.ts +1 -3
- package/lib/esm/features/sync-data-loader/feature.js +7 -2
- package/lib/esm/features/tree/feature.d.ts +1 -5
- package/lib/esm/features/tree/feature.js +98 -93
- package/lib/esm/features/tree/types.d.ts +5 -8
- package/lib/esm/index.d.ts +5 -1
- package/lib/esm/index.js +4 -1
- package/lib/esm/mddocs-entry.d.ts +10 -0
- package/lib/esm/test-utils/test-tree-do.d.ts +23 -0
- package/lib/esm/test-utils/test-tree-do.js +95 -0
- package/lib/esm/test-utils/test-tree-expect.d.ts +15 -0
- package/lib/esm/test-utils/test-tree-expect.js +58 -0
- package/lib/esm/test-utils/test-tree.d.ts +47 -0
- package/lib/esm/test-utils/test-tree.js +199 -0
- package/lib/esm/types/core.d.ts +39 -24
- package/lib/esm/utilities/errors.d.ts +1 -0
- package/lib/esm/utilities/errors.js +1 -0
- package/lib/esm/utilities/insert-items-at-target.js +10 -3
- package/lib/esm/utilities/remove-items-from-parents.js +14 -8
- package/lib/esm/utils.d.ts +3 -3
- package/lib/esm/utils.js +3 -3
- package/package.json +7 -3
- package/src/core/build-proxified-instance.ts +117 -0
- package/src/core/build-static-instance.ts +27 -0
- package/src/core/core.spec.ts +210 -0
- package/src/core/create-tree.ts +73 -78
- package/src/features/async-data-loader/async-data-loader.spec.ts +124 -0
- package/src/features/async-data-loader/feature.ts +34 -44
- package/src/features/async-data-loader/types.ts +4 -6
- package/src/features/drag-and-drop/drag-and-drop.spec.ts +717 -0
- package/src/features/drag-and-drop/feature.ts +88 -63
- package/src/features/drag-and-drop/types.ts +24 -10
- package/src/features/drag-and-drop/utils.ts +197 -56
- package/src/features/expand-all/expand-all.spec.ts +56 -0
- package/src/features/expand-all/feature.ts +9 -24
- package/src/features/hotkeys-core/feature.ts +5 -14
- package/src/features/main/types.ts +14 -1
- package/src/features/prop-memoization/feature.ts +51 -0
- package/src/features/prop-memoization/prop-memoization.spec.ts +68 -0
- package/src/features/prop-memoization/types.ts +11 -0
- package/src/features/renaming/feature.ts +37 -45
- package/src/features/renaming/renaming.spec.ts +127 -0
- package/src/features/renaming/types.ts +2 -2
- package/src/features/search/feature.ts +36 -46
- package/src/features/search/search.spec.ts +117 -0
- package/src/features/search/types.ts +0 -1
- package/src/features/selection/feature.ts +50 -53
- package/src/features/selection/selection.spec.ts +219 -0
- package/src/features/selection/types.ts +0 -2
- package/src/features/sync-data-loader/feature.ts +9 -18
- package/src/features/tree/feature.ts +101 -144
- package/src/features/tree/tree.spec.ts +475 -0
- package/src/features/tree/types.ts +5 -9
- package/src/index.ts +6 -1
- package/src/mddocs-entry.ts +13 -0
- package/src/test-utils/test-tree-do.ts +136 -0
- package/src/test-utils/test-tree-expect.ts +86 -0
- package/src/test-utils/test-tree.ts +227 -0
- package/src/types/core.ts +76 -108
- package/src/utilities/errors.ts +2 -0
- package/src/utilities/insert-items-at-target.ts +10 -3
- package/src/utilities/remove-items-from-parents.ts +15 -10
- package/src/utils.spec.ts +89 -0
- package/src/utils.ts +6 -6
- package/tsconfig.json +1 -0
- package/vitest.config.ts +6 -0
|
@@ -1,20 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { DndDataRef,
|
|
1
|
+
import { FeatureImplementation } from "../../types/core";
|
|
2
|
+
import { DndDataRef, DragLineData } from "./types";
|
|
3
3
|
import { canDrop, getDragCode, getDropTarget } from "./utils";
|
|
4
4
|
import { makeStateUpdater } from "../../utils";
|
|
5
5
|
|
|
6
|
-
export const dragAndDropFeature: FeatureImplementation
|
|
7
|
-
|
|
8
|
-
DragAndDropFeatureDef<any>,
|
|
9
|
-
FeatureDefs<any>
|
|
10
|
-
> = {
|
|
11
|
-
key: "dragAndDrop",
|
|
6
|
+
export const dragAndDropFeature: FeatureImplementation = {
|
|
7
|
+
key: "drag-and-drop",
|
|
12
8
|
deps: ["selection"],
|
|
13
9
|
|
|
14
10
|
getDefaultConfig: (defaultConfig, tree) => ({
|
|
15
11
|
canDrop: (_, target) => target.item.isFolder(),
|
|
16
12
|
canDropForeignDragObject: () => false,
|
|
17
13
|
setDndState: makeStateUpdater("dnd", tree),
|
|
14
|
+
canReorder: true,
|
|
18
15
|
...defaultConfig,
|
|
19
16
|
}),
|
|
20
17
|
|
|
@@ -22,62 +19,83 @@ export const dragAndDropFeature: FeatureImplementation<
|
|
|
22
19
|
dnd: "setDndState",
|
|
23
20
|
},
|
|
24
21
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
getDropTarget: () => {
|
|
22
|
+
treeInstance: {
|
|
23
|
+
getDropTarget: ({ tree }) => {
|
|
29
24
|
return tree.getState().dnd?.dragTarget ?? null;
|
|
30
25
|
},
|
|
31
26
|
|
|
32
|
-
getDragLineData: (): DragLineData | null => {
|
|
27
|
+
getDragLineData: ({ tree }): DragLineData | null => {
|
|
33
28
|
const target = tree.getDropTarget();
|
|
34
|
-
const
|
|
29
|
+
const indent = (target?.item.getItemMeta().level ?? 0) + 1;
|
|
30
|
+
|
|
31
|
+
const treeBb = tree.getElement()?.getBoundingClientRect();
|
|
35
32
|
|
|
36
|
-
if (!target || target.childIndex === null) return null;
|
|
33
|
+
if (!target || !treeBb || target.childIndex === null) return null;
|
|
37
34
|
|
|
38
|
-
const
|
|
35
|
+
const leftOffset = target.dragLineLevel * (tree.getConfig().indent ?? 1);
|
|
36
|
+
const targetItem = tree.getItems()[target.dragLineIndex];
|
|
39
37
|
|
|
40
|
-
if (
|
|
41
|
-
const bb =
|
|
42
|
-
|
|
38
|
+
if (!targetItem) {
|
|
39
|
+
const bb = tree
|
|
40
|
+
.getItems()
|
|
41
|
+
[target.dragLineIndex - 1]?.getElement()
|
|
43
42
|
?.getBoundingClientRect();
|
|
44
43
|
|
|
45
44
|
if (bb) {
|
|
46
45
|
return {
|
|
47
|
-
|
|
48
|
-
top: bb.bottom,
|
|
49
|
-
left: bb.left,
|
|
50
|
-
|
|
46
|
+
indent,
|
|
47
|
+
top: bb.bottom - treeBb.bottom,
|
|
48
|
+
left: bb.left + leftOffset - treeBb.left,
|
|
49
|
+
width: bb.width - leftOffset,
|
|
51
50
|
};
|
|
52
51
|
}
|
|
53
52
|
}
|
|
54
53
|
|
|
55
|
-
const bb =
|
|
56
|
-
?.getElement()
|
|
57
|
-
?.getBoundingClientRect();
|
|
54
|
+
const bb = targetItem.getElement()?.getBoundingClientRect();
|
|
58
55
|
|
|
59
56
|
if (bb) {
|
|
60
57
|
return {
|
|
61
|
-
|
|
62
|
-
top: bb.top,
|
|
63
|
-
left: bb.left,
|
|
64
|
-
|
|
58
|
+
indent,
|
|
59
|
+
top: bb.top - treeBb.top,
|
|
60
|
+
left: bb.left + leftOffset - treeBb.left,
|
|
61
|
+
width: bb.width - leftOffset,
|
|
65
62
|
};
|
|
66
63
|
}
|
|
67
64
|
|
|
68
65
|
return null;
|
|
69
66
|
},
|
|
70
|
-
}),
|
|
71
67
|
|
|
72
|
-
|
|
73
|
-
|
|
68
|
+
getDragLineStyle: ({ tree }, topOffset = -1, leftOffset = -8) => {
|
|
69
|
+
const dragLine = tree.getDragLineData();
|
|
70
|
+
return dragLine
|
|
71
|
+
? {
|
|
72
|
+
top: `${dragLine.top + topOffset}px`,
|
|
73
|
+
left: `${dragLine.left + leftOffset}px`,
|
|
74
|
+
width: `${dragLine.width - leftOffset}px`,
|
|
75
|
+
pointerEvents: "none", // important to prevent capturing drag events
|
|
76
|
+
}
|
|
77
|
+
: { display: "none" };
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
getContainerProps: ({ prev }) => {
|
|
81
|
+
const prevProps = prev?.();
|
|
82
|
+
return {
|
|
83
|
+
...prevProps,
|
|
84
|
+
style: {
|
|
85
|
+
...prevProps?.style,
|
|
86
|
+
position: "relative",
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
},
|
|
90
|
+
},
|
|
74
91
|
|
|
75
|
-
|
|
76
|
-
|
|
92
|
+
itemInstance: {
|
|
93
|
+
getProps: ({ tree, item, prev }) => ({
|
|
94
|
+
...prev?.(),
|
|
77
95
|
|
|
78
96
|
draggable: tree.getConfig().isItemDraggable?.(item) ?? true,
|
|
79
97
|
|
|
80
|
-
onDragStart:
|
|
98
|
+
onDragStart: (e: DragEvent) => {
|
|
81
99
|
const selectedItems = tree.getSelectedItems();
|
|
82
100
|
const items = selectedItems.includes(item) ? selectedItems : [item];
|
|
83
101
|
const config = tree.getConfig();
|
|
@@ -100,40 +118,47 @@ export const dragAndDropFeature: FeatureImplementation<
|
|
|
100
118
|
draggedItems: items,
|
|
101
119
|
draggingOverItem: tree.getFocusedItem(),
|
|
102
120
|
});
|
|
103
|
-
}
|
|
121
|
+
},
|
|
104
122
|
|
|
105
|
-
onDragOver:
|
|
106
|
-
const target = getDropTarget(e, item, tree);
|
|
123
|
+
onDragOver: (e: DragEvent) => {
|
|
107
124
|
const dataRef = tree.getDataRef<DndDataRef>();
|
|
125
|
+
const nextDragCode = getDragCode(e, item, tree);
|
|
126
|
+
if (nextDragCode === dataRef.current.lastDragCode) {
|
|
127
|
+
if (dataRef.current.lastAllowDrop) {
|
|
128
|
+
e.preventDefault();
|
|
129
|
+
}
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
dataRef.current.lastDragCode = nextDragCode;
|
|
133
|
+
|
|
134
|
+
const target = getDropTarget(e, item, tree);
|
|
108
135
|
|
|
109
136
|
if (
|
|
110
137
|
!tree.getState().dnd?.draggedItems &&
|
|
111
|
-
!
|
|
138
|
+
(!e.dataTransfer ||
|
|
139
|
+
!tree
|
|
140
|
+
.getConfig()
|
|
141
|
+
.canDropForeignDragObject?.(e.dataTransfer, target))
|
|
112
142
|
) {
|
|
143
|
+
dataRef.current.lastAllowDrop = false;
|
|
113
144
|
return;
|
|
114
145
|
}
|
|
115
146
|
|
|
116
147
|
if (!canDrop(e.dataTransfer, target, tree)) {
|
|
148
|
+
dataRef.current.lastAllowDrop = false;
|
|
117
149
|
return;
|
|
118
150
|
}
|
|
119
151
|
|
|
120
|
-
e.preventDefault();
|
|
121
|
-
const nextDragCode = getDragCode(target);
|
|
122
|
-
|
|
123
|
-
if (nextDragCode === dataRef.current.lastDragCode) {
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
dataRef.current.lastDragCode = nextDragCode;
|
|
128
|
-
|
|
129
152
|
tree.applySubStateUpdate("dnd", (state) => ({
|
|
130
153
|
...state,
|
|
131
154
|
dragTarget: target,
|
|
132
155
|
draggingOverItem: item,
|
|
133
156
|
}));
|
|
134
|
-
|
|
157
|
+
dataRef.current.lastAllowDrop = true;
|
|
158
|
+
e.preventDefault();
|
|
159
|
+
},
|
|
135
160
|
|
|
136
|
-
onDragLeave:
|
|
161
|
+
onDragLeave: () => {
|
|
137
162
|
const dataRef = tree.getDataRef<DndDataRef>();
|
|
138
163
|
dataRef.current.lastDragCode = "no-drag";
|
|
139
164
|
tree.applySubStateUpdate("dnd", (state) => ({
|
|
@@ -141,20 +166,20 @@ export const dragAndDropFeature: FeatureImplementation<
|
|
|
141
166
|
draggingOverItem: undefined,
|
|
142
167
|
dragTarget: undefined,
|
|
143
168
|
}));
|
|
144
|
-
}
|
|
169
|
+
},
|
|
145
170
|
|
|
146
|
-
onDragEnd:
|
|
171
|
+
onDragEnd: (e: DragEvent) => {
|
|
147
172
|
const draggedItems = tree.getState().dnd?.draggedItems;
|
|
148
173
|
tree.applySubStateUpdate("dnd", null);
|
|
149
174
|
|
|
150
|
-
if (e.dataTransfer
|
|
175
|
+
if (e.dataTransfer?.dropEffect === "none" || !draggedItems) {
|
|
151
176
|
return;
|
|
152
177
|
}
|
|
153
178
|
|
|
154
179
|
tree.getConfig().onCompleteForeignDrop?.(draggedItems);
|
|
155
|
-
}
|
|
180
|
+
},
|
|
156
181
|
|
|
157
|
-
onDrop:
|
|
182
|
+
onDrop: (e: DragEvent) => {
|
|
158
183
|
const dataRef = tree.getDataRef<DndDataRef>();
|
|
159
184
|
const target = getDropTarget(e, item, tree);
|
|
160
185
|
|
|
@@ -171,19 +196,19 @@ export const dragAndDropFeature: FeatureImplementation<
|
|
|
171
196
|
|
|
172
197
|
if (draggedItems) {
|
|
173
198
|
config.onDrop?.(draggedItems, target);
|
|
174
|
-
} else {
|
|
199
|
+
} else if (e.dataTransfer) {
|
|
175
200
|
config.onDropForeignDragObject?.(e.dataTransfer, target);
|
|
176
201
|
}
|
|
177
202
|
// TODO rebuild tree?
|
|
178
|
-
}
|
|
203
|
+
},
|
|
179
204
|
}),
|
|
180
205
|
|
|
181
|
-
isDropTarget: () => {
|
|
206
|
+
isDropTarget: ({ tree, item }) => {
|
|
182
207
|
const target = tree.getDropTarget();
|
|
183
208
|
return target ? target.item.getId() === item.getId() : false;
|
|
184
209
|
},
|
|
185
210
|
|
|
186
|
-
isDropTargetAbove: () => {
|
|
211
|
+
isDropTargetAbove: ({ tree, item }) => {
|
|
187
212
|
const target = tree.getDropTarget();
|
|
188
213
|
|
|
189
214
|
if (
|
|
@@ -195,7 +220,7 @@ export const dragAndDropFeature: FeatureImplementation<
|
|
|
195
220
|
return target.childIndex === item.getItemMeta().posInSet;
|
|
196
221
|
},
|
|
197
222
|
|
|
198
|
-
isDropTargetBelow: () => {
|
|
223
|
+
isDropTargetBelow: ({ tree, item }) => {
|
|
199
224
|
const target = tree.getDropTarget();
|
|
200
225
|
|
|
201
226
|
if (
|
|
@@ -207,8 +232,8 @@ export const dragAndDropFeature: FeatureImplementation<
|
|
|
207
232
|
return target.childIndex - 1 === item.getItemMeta().posInSet;
|
|
208
233
|
},
|
|
209
234
|
|
|
210
|
-
isDraggingOver: () => {
|
|
235
|
+
isDraggingOver: ({ tree, item }) => {
|
|
211
236
|
return tree.getState().dnd?.draggingOverItem?.getId() === item.getId();
|
|
212
237
|
},
|
|
213
|
-
}
|
|
238
|
+
},
|
|
214
239
|
};
|
|
@@ -2,19 +2,20 @@ import { ItemInstance, SetStateFn } from "../../types/core";
|
|
|
2
2
|
|
|
3
3
|
export type DndDataRef = {
|
|
4
4
|
lastDragCode?: string;
|
|
5
|
+
lastAllowDrop?: boolean;
|
|
5
6
|
};
|
|
6
7
|
|
|
7
8
|
export type DndState<T> = {
|
|
8
|
-
draggedItems?: ItemInstance<T>[];
|
|
9
|
+
draggedItems?: ItemInstance<T>[];
|
|
9
10
|
draggingOverItem?: ItemInstance<T>;
|
|
10
11
|
dragTarget?: DropTarget<T>;
|
|
11
12
|
};
|
|
12
13
|
|
|
13
14
|
export type DragLineData = {
|
|
14
|
-
|
|
15
|
+
indent: number;
|
|
15
16
|
top: number;
|
|
16
17
|
left: number;
|
|
17
|
-
|
|
18
|
+
width: number;
|
|
18
19
|
};
|
|
19
20
|
|
|
20
21
|
export type DropTarget<T> =
|
|
@@ -22,11 +23,15 @@ export type DropTarget<T> =
|
|
|
22
23
|
item: ItemInstance<T>;
|
|
23
24
|
childIndex: number;
|
|
24
25
|
insertionIndex: number;
|
|
26
|
+
dragLineIndex: number;
|
|
27
|
+
dragLineLevel: number;
|
|
25
28
|
}
|
|
26
29
|
| {
|
|
27
|
-
item: ItemInstance<T>;
|
|
30
|
+
item: ItemInstance<T>; // TODO just omit values instead of nulls; or maybe just make it union of dropTarget+itemInstance?
|
|
28
31
|
childIndex: null;
|
|
29
32
|
insertionIndex: null;
|
|
33
|
+
dragLineIndex: null;
|
|
34
|
+
dragLineLevel: null;
|
|
30
35
|
};
|
|
31
36
|
|
|
32
37
|
export enum DropTargetPosition {
|
|
@@ -40,16 +45,21 @@ export type DragAndDropFeatureDef<T> = {
|
|
|
40
45
|
dnd?: DndState<T> | null;
|
|
41
46
|
};
|
|
42
47
|
config: {
|
|
43
|
-
setDndState?: SetStateFn<DndState<T> | null>;
|
|
48
|
+
setDndState?: SetStateFn<DndState<T> | undefined | null>;
|
|
44
49
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
50
|
+
/** Defines the size of the area at the top and bottom of an item where, when an item is dropped, the item willö
|
|
51
|
+
* be placed above or below the item within the same parent, as opposed to being placed inside the item.
|
|
52
|
+
* If `canReorder` is `false`, this is ignored. */
|
|
53
|
+
reorderAreaPercentage?: number;
|
|
54
|
+
canReorder?: boolean;
|
|
48
55
|
|
|
56
|
+
// TODO better document difference to canDrag(), or unify both
|
|
49
57
|
isItemDraggable?: (item: ItemInstance<T>) => boolean;
|
|
50
58
|
canDrag?: (items: ItemInstance<T>[]) => boolean;
|
|
51
59
|
canDrop?: (items: ItemInstance<T>[], target: DropTarget<T>) => boolean;
|
|
52
60
|
|
|
61
|
+
indent?: number;
|
|
62
|
+
|
|
53
63
|
createForeignDragObject?: (items: ItemInstance<T>[]) => {
|
|
54
64
|
format: string;
|
|
55
65
|
data: any;
|
|
@@ -78,11 +88,15 @@ export type DragAndDropFeatureDef<T> = {
|
|
|
78
88
|
treeInstance: {
|
|
79
89
|
getDropTarget: () => DropTarget<T> | null;
|
|
80
90
|
getDragLineData: () => DragLineData | null;
|
|
91
|
+
getDragLineStyle: (
|
|
92
|
+
topOffset?: number,
|
|
93
|
+
leftOffset?: number,
|
|
94
|
+
) => Record<string, any>;
|
|
81
95
|
};
|
|
82
96
|
itemInstance: {
|
|
83
97
|
isDropTarget: () => boolean;
|
|
84
|
-
isDropTargetAbove: () => boolean;
|
|
85
|
-
isDropTargetBelow: () => boolean;
|
|
98
|
+
isDropTargetAbove: () => boolean; // TODO still correct?
|
|
99
|
+
isDropTargetBelow: () => boolean; // TODO still correct?
|
|
86
100
|
isDraggingOver: () => boolean;
|
|
87
101
|
};
|
|
88
102
|
hotkeys: never;
|
|
@@ -1,13 +1,30 @@
|
|
|
1
1
|
import { ItemInstance, TreeInstance } from "../../types/core";
|
|
2
|
-
import { DropTarget
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
2
|
+
import { DropTarget } from "./types";
|
|
3
|
+
|
|
4
|
+
enum ItemDropCategory {
|
|
5
|
+
Item,
|
|
6
|
+
ExpandedFolder,
|
|
7
|
+
LastInGroup,
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
enum PlacementType {
|
|
11
|
+
ReorderAbove,
|
|
12
|
+
ReorderBelow,
|
|
13
|
+
MakeChild,
|
|
14
|
+
Reparent,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type TargetPlacement =
|
|
18
|
+
| {
|
|
19
|
+
type:
|
|
20
|
+
| PlacementType.ReorderAbove
|
|
21
|
+
| PlacementType.ReorderBelow
|
|
22
|
+
| PlacementType.MakeChild;
|
|
23
|
+
}
|
|
24
|
+
| {
|
|
25
|
+
type: PlacementType.Reparent;
|
|
26
|
+
reparentLevel: number;
|
|
27
|
+
};
|
|
11
28
|
|
|
12
29
|
export const canDrop = (
|
|
13
30
|
dataTransfer: DataTransfer | null,
|
|
@@ -21,6 +38,17 @@ export const canDrop = (
|
|
|
21
38
|
return false;
|
|
22
39
|
}
|
|
23
40
|
|
|
41
|
+
if (
|
|
42
|
+
draggedItems &&
|
|
43
|
+
draggedItems.some(
|
|
44
|
+
(draggedItem) =>
|
|
45
|
+
target.item.getId() === draggedItem.getId() ||
|
|
46
|
+
target.item.isDescendentOf(draggedItem.getId()),
|
|
47
|
+
)
|
|
48
|
+
) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
24
52
|
if (
|
|
25
53
|
!draggedItems &&
|
|
26
54
|
dataTransfer &&
|
|
@@ -32,79 +60,192 @@ export const canDrop = (
|
|
|
32
60
|
return true;
|
|
33
61
|
};
|
|
34
62
|
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
bottomLinePercentage: number,
|
|
39
|
-
) => {
|
|
40
|
-
if (offset < topLinePercentage) {
|
|
41
|
-
return DropTargetPosition.Top;
|
|
63
|
+
const getItemDropCategory = (item: ItemInstance<any>) => {
|
|
64
|
+
if (item.isExpanded()) {
|
|
65
|
+
return ItemDropCategory.ExpandedFolder;
|
|
42
66
|
}
|
|
43
|
-
|
|
44
|
-
|
|
67
|
+
|
|
68
|
+
const parent = item.getParent();
|
|
69
|
+
if (parent && item.getIndexInParent() === parent.getItemMeta().setSize - 1) {
|
|
70
|
+
return ItemDropCategory.LastInGroup;
|
|
45
71
|
}
|
|
46
|
-
|
|
72
|
+
|
|
73
|
+
return ItemDropCategory.Item;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const getTargetPlacement = (
|
|
77
|
+
e: any,
|
|
78
|
+
item: ItemInstance<any>,
|
|
79
|
+
tree: TreeInstance<any>,
|
|
80
|
+
canMakeChild: boolean,
|
|
81
|
+
): TargetPlacement => {
|
|
82
|
+
const config = tree.getConfig();
|
|
83
|
+
|
|
84
|
+
if (!config.canReorder) {
|
|
85
|
+
return canMakeChild
|
|
86
|
+
? { type: PlacementType.MakeChild }
|
|
87
|
+
: { type: PlacementType.ReorderBelow };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const bb = item.getElement()?.getBoundingClientRect();
|
|
91
|
+
const topPercent = bb ? (e.pageY - bb.top) / bb.height : 0.5;
|
|
92
|
+
const leftPixels = bb ? e.pageX - bb.left : 0;
|
|
93
|
+
const targetDropCategory = getItemDropCategory(item);
|
|
94
|
+
const reorderAreaPercentage = !canMakeChild
|
|
95
|
+
? 0.5
|
|
96
|
+
: config.reorderAreaPercentage ?? 0.3;
|
|
97
|
+
const indent = config.indent ?? 20;
|
|
98
|
+
const makeChildType = canMakeChild
|
|
99
|
+
? PlacementType.MakeChild
|
|
100
|
+
: PlacementType.ReorderBelow;
|
|
101
|
+
|
|
102
|
+
if (targetDropCategory === ItemDropCategory.ExpandedFolder) {
|
|
103
|
+
if (topPercent < reorderAreaPercentage) {
|
|
104
|
+
return { type: PlacementType.ReorderAbove };
|
|
105
|
+
}
|
|
106
|
+
return { type: makeChildType };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (targetDropCategory === ItemDropCategory.LastInGroup) {
|
|
110
|
+
if (leftPixels < item.getItemMeta().level * indent) {
|
|
111
|
+
if (topPercent < 0.5) {
|
|
112
|
+
return { type: PlacementType.ReorderAbove };
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
type: PlacementType.Reparent,
|
|
116
|
+
reparentLevel: Math.floor(leftPixels / indent),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
// if not at left of item area, treat as if it was a normal item
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// targetDropCategory === ItemDropCategory.Item
|
|
123
|
+
if (topPercent < reorderAreaPercentage) {
|
|
124
|
+
return { type: PlacementType.ReorderAbove };
|
|
125
|
+
}
|
|
126
|
+
if (topPercent > 1 - reorderAreaPercentage) {
|
|
127
|
+
return { type: PlacementType.ReorderBelow };
|
|
128
|
+
}
|
|
129
|
+
return { type: makeChildType };
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
export const getDragCode = (
|
|
133
|
+
e: any,
|
|
134
|
+
item: ItemInstance<any>,
|
|
135
|
+
tree: TreeInstance<any>,
|
|
136
|
+
) => {
|
|
137
|
+
const placement = getTargetPlacement(e, item, tree, true);
|
|
138
|
+
return [
|
|
139
|
+
item.getId(),
|
|
140
|
+
placement.type,
|
|
141
|
+
placement.type === PlacementType.Reparent ? placement.reparentLevel : 0,
|
|
142
|
+
].join("__");
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const getNthParent = (
|
|
146
|
+
item: ItemInstance<any>,
|
|
147
|
+
n: number,
|
|
148
|
+
): ItemInstance<any> => {
|
|
149
|
+
if (n === item.getItemMeta().level) {
|
|
150
|
+
return item;
|
|
151
|
+
}
|
|
152
|
+
return getNthParent(item.getParent()!, n);
|
|
47
153
|
};
|
|
48
154
|
|
|
49
155
|
export const getDropTarget = (
|
|
50
156
|
e: any,
|
|
51
157
|
item: ItemInstance<any>,
|
|
52
158
|
tree: TreeInstance<any>,
|
|
53
|
-
|
|
159
|
+
canReorder = tree.getConfig().canReorder,
|
|
54
160
|
): DropTarget<any> => {
|
|
55
|
-
const config = tree.getConfig();
|
|
56
161
|
const draggedItems = tree.getState().dnd?.draggedItems ?? [];
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
|
|
162
|
+
const itemMeta = item.getItemMeta();
|
|
163
|
+
const parent = item.getParent();
|
|
164
|
+
const itemTarget: DropTarget<any> = {
|
|
165
|
+
item,
|
|
60
166
|
childIndex: null,
|
|
61
167
|
insertionIndex: null,
|
|
168
|
+
dragLineIndex: null,
|
|
169
|
+
dragLineLevel: null,
|
|
62
170
|
};
|
|
171
|
+
const parentTarget: DropTarget<any> | null = parent
|
|
172
|
+
? {
|
|
173
|
+
item: parent,
|
|
174
|
+
childIndex: null,
|
|
175
|
+
insertionIndex: null,
|
|
176
|
+
dragLineIndex: null,
|
|
177
|
+
dragLineLevel: null,
|
|
178
|
+
}
|
|
179
|
+
: null;
|
|
180
|
+
const canBecomeSibling =
|
|
181
|
+
parentTarget && canDrop(e.dataTransfer, parentTarget, tree);
|
|
182
|
+
|
|
183
|
+
const canMakeChild = canDrop(e.dataTransfer, itemTarget, tree);
|
|
184
|
+
const placement = getTargetPlacement(e, item, tree, canMakeChild);
|
|
63
185
|
|
|
64
|
-
if (
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
186
|
+
if (
|
|
187
|
+
!canReorder &&
|
|
188
|
+
parent &&
|
|
189
|
+
canBecomeSibling &&
|
|
190
|
+
placement.type !== PlacementType.MakeChild
|
|
191
|
+
) {
|
|
192
|
+
return parentTarget;
|
|
69
193
|
}
|
|
70
194
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
195
|
+
if (!canReorder && parent && !canBecomeSibling) {
|
|
196
|
+
// TODO! this breaks in story DND/Can Drop. Maybe move this logic into a composable DropTargetStrategy[] ?
|
|
197
|
+
return getDropTarget(e, parent, tree, false);
|
|
198
|
+
}
|
|
74
199
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
config.bottomLinePercentage ?? 0.7,
|
|
80
|
-
)
|
|
81
|
-
: getDropTargetPosition(offset, 0.5, 0.5);
|
|
200
|
+
if (!parent) {
|
|
201
|
+
// Shouldn't happen, but if dropped "next" to root item, just drop it inside
|
|
202
|
+
return itemTarget;
|
|
203
|
+
}
|
|
82
204
|
|
|
83
|
-
if (
|
|
205
|
+
if (placement.type === PlacementType.MakeChild) {
|
|
84
206
|
return itemTarget;
|
|
85
207
|
}
|
|
86
208
|
|
|
87
|
-
if (!
|
|
88
|
-
return getDropTarget(e,
|
|
209
|
+
if (!canBecomeSibling) {
|
|
210
|
+
return getDropTarget(e, parent, tree, false);
|
|
89
211
|
}
|
|
90
212
|
|
|
91
|
-
|
|
92
|
-
|
|
213
|
+
if (placement.type === PlacementType.Reparent) {
|
|
214
|
+
const reparentedTarget = getNthParent(item, placement.reparentLevel - 1);
|
|
215
|
+
const targetItemAbove = getNthParent(item, placement.reparentLevel); // .getItemBelow()!;
|
|
216
|
+
const targetIndex = targetItemAbove.getIndexInParent() + 1;
|
|
217
|
+
|
|
218
|
+
// TODO possibly count items dragged out above the new target
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
item: reparentedTarget,
|
|
222
|
+
childIndex: targetIndex,
|
|
223
|
+
insertionIndex: targetIndex,
|
|
224
|
+
dragLineIndex: itemMeta.index + 1,
|
|
225
|
+
dragLineLevel: placement.reparentLevel,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
93
228
|
|
|
94
|
-
const
|
|
95
|
-
.
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
229
|
+
const maybeAddOneForBelow =
|
|
230
|
+
placement.type === PlacementType.ReorderAbove ? 0 : 1;
|
|
231
|
+
const childIndex = item.getIndexInParent() + maybeAddOneForBelow;
|
|
232
|
+
|
|
233
|
+
const numberOfDragItemsBeforeTarget =
|
|
234
|
+
parent
|
|
235
|
+
.getChildren()
|
|
236
|
+
.slice(0, childIndex)
|
|
237
|
+
.reduce(
|
|
238
|
+
(counter, child) =>
|
|
239
|
+
child && draggedItems?.some((i) => i.getId() === child.getId())
|
|
240
|
+
? ++counter
|
|
241
|
+
: counter,
|
|
242
|
+
0,
|
|
243
|
+
) ?? 0;
|
|
105
244
|
|
|
106
245
|
return {
|
|
107
|
-
item:
|
|
246
|
+
item: parent,
|
|
247
|
+
dragLineIndex: itemMeta.index + maybeAddOneForBelow,
|
|
248
|
+
dragLineLevel: itemMeta.level,
|
|
108
249
|
childIndex,
|
|
109
250
|
// TODO performance could be improved by computing this only when dragcode changed
|
|
110
251
|
insertionIndex: childIndex - numberOfDragItemsBeforeTarget,
|