@headless-tree/core 0.0.9 → 0.0.11
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 +27 -0
- package/lib/cjs/core/create-tree.js +55 -36
- package/lib/cjs/features/async-data-loader/feature.js +37 -23
- package/lib/cjs/features/async-data-loader/types.d.ts +2 -1
- package/lib/cjs/features/drag-and-drop/feature.js +64 -32
- package/lib/cjs/features/drag-and-drop/types.d.ts +13 -4
- package/lib/cjs/features/drag-and-drop/utils.d.ts +1 -2
- package/lib/cjs/features/drag-and-drop/utils.js +140 -37
- package/lib/cjs/features/expand-all/feature.js +12 -6
- package/lib/cjs/features/main/types.d.ts +8 -2
- package/lib/cjs/features/renaming/feature.js +33 -18
- package/lib/cjs/features/renaming/types.d.ts +1 -1
- 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.js +23 -14
- package/lib/cjs/features/sync-data-loader/feature.js +7 -2
- package/lib/cjs/features/tree/feature.d.ts +2 -1
- package/lib/cjs/features/tree/feature.js +85 -63
- package/lib/cjs/features/tree/types.d.ts +5 -3
- package/lib/cjs/index.d.ts +3 -1
- package/lib/cjs/index.js +2 -1
- 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 +195 -0
- package/lib/cjs/types/core.d.ts +31 -15
- 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 +23 -0
- package/lib/esm/core/create-tree.js +55 -36
- package/lib/esm/features/async-data-loader/feature.js +37 -23
- package/lib/esm/features/async-data-loader/types.d.ts +2 -1
- package/lib/esm/features/drag-and-drop/feature.js +64 -32
- package/lib/esm/features/drag-and-drop/types.d.ts +13 -4
- package/lib/esm/features/drag-and-drop/utils.d.ts +1 -2
- package/lib/esm/features/drag-and-drop/utils.js +138 -34
- package/lib/esm/features/expand-all/feature.js +12 -6
- package/lib/esm/features/main/types.d.ts +8 -2
- package/lib/esm/features/renaming/feature.js +33 -18
- package/lib/esm/features/renaming/types.d.ts +1 -1
- 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.js +23 -14
- package/lib/esm/features/sync-data-loader/feature.js +7 -2
- package/lib/esm/features/tree/feature.d.ts +2 -1
- package/lib/esm/features/tree/feature.js +86 -64
- package/lib/esm/features/tree/types.d.ts +5 -3
- package/lib/esm/index.d.ts +3 -1
- package/lib/esm/index.js +2 -1
- 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 +191 -0
- package/lib/esm/types/core.d.ts +31 -15
- 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 +115 -0
- package/src/core/build-static-instance.ts +28 -0
- package/src/core/create-tree.ts +60 -62
- package/src/features/async-data-loader/async-data-loader.spec.ts +143 -0
- package/src/features/async-data-loader/feature.ts +33 -31
- package/src/features/async-data-loader/types.ts +3 -1
- package/src/features/drag-and-drop/drag-and-drop.spec.ts +716 -0
- package/src/features/drag-and-drop/feature.ts +109 -85
- package/src/features/drag-and-drop/types.ts +21 -7
- package/src/features/drag-and-drop/utils.ts +196 -55
- package/src/features/expand-all/expand-all.spec.ts +52 -0
- package/src/features/expand-all/feature.ts +8 -12
- package/src/features/hotkeys-core/feature.ts +1 -1
- package/src/features/main/types.ts +14 -1
- package/src/features/renaming/feature.ts +30 -29
- package/src/features/renaming/renaming.spec.ts +125 -0
- package/src/features/renaming/types.ts +1 -1
- package/src/features/search/feature.ts +34 -38
- package/src/features/search/search.spec.ts +115 -0
- package/src/features/search/types.ts +0 -1
- package/src/features/selection/feature.ts +29 -30
- package/src/features/selection/selection.spec.ts +220 -0
- package/src/features/sync-data-loader/feature.ts +8 -11
- package/src/features/tree/feature.ts +82 -87
- package/src/features/tree/tree.spec.ts +515 -0
- package/src/features/tree/types.ts +5 -3
- package/src/index.ts +4 -1
- 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 +217 -0
- package/src/types/core.ts +92 -33
- 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
|
@@ -15,6 +15,7 @@ export const dragAndDropFeature: FeatureImplementation<
|
|
|
15
15
|
canDrop: (_, target) => target.item.isFolder(),
|
|
16
16
|
canDropForeignDragObject: () => false,
|
|
17
17
|
setDndState: makeStateUpdater("dnd", tree),
|
|
18
|
+
canDropInbetween: true,
|
|
18
19
|
...defaultConfig,
|
|
19
20
|
}),
|
|
20
21
|
|
|
@@ -22,116 +23,139 @@ export const dragAndDropFeature: FeatureImplementation<
|
|
|
22
23
|
dnd: "setDndState",
|
|
23
24
|
},
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
getDropTarget: () => {
|
|
26
|
+
treeInstance: {
|
|
27
|
+
getDropTarget: ({ tree }) => {
|
|
29
28
|
return tree.getState().dnd?.dragTarget ?? null;
|
|
30
29
|
},
|
|
31
30
|
|
|
32
|
-
getDragLineData: (): DragLineData | null => {
|
|
31
|
+
getDragLineData: ({ tree }): DragLineData | null => {
|
|
32
|
+
// TODO doesnt work if scrolled down!
|
|
33
33
|
const target = tree.getDropTarget();
|
|
34
|
-
const
|
|
34
|
+
const indent = (target?.item.getItemMeta().level ?? 0) + 1; // TODO rename to indent
|
|
35
35
|
|
|
36
36
|
if (!target || target.childIndex === null) return null;
|
|
37
37
|
|
|
38
|
-
const
|
|
38
|
+
const leftOffset = target.dragLineLevel * (tree.getConfig().indent ?? 1);
|
|
39
|
+
const targetItem = tree.getItems()[target.dragLineIndex];
|
|
39
40
|
|
|
40
|
-
if (
|
|
41
|
-
const bb =
|
|
42
|
-
|
|
41
|
+
if (!targetItem) {
|
|
42
|
+
const bb = tree
|
|
43
|
+
.getItems()
|
|
44
|
+
[target.dragLineIndex - 1]?.getElement()
|
|
43
45
|
?.getBoundingClientRect();
|
|
44
46
|
|
|
45
47
|
if (bb) {
|
|
46
48
|
return {
|
|
47
|
-
|
|
49
|
+
indent,
|
|
48
50
|
top: bb.bottom,
|
|
49
|
-
left: bb.left,
|
|
51
|
+
left: bb.left + leftOffset,
|
|
50
52
|
right: bb.right,
|
|
51
53
|
};
|
|
52
54
|
}
|
|
53
55
|
}
|
|
54
56
|
|
|
55
|
-
const bb =
|
|
56
|
-
?.getElement()
|
|
57
|
-
?.getBoundingClientRect();
|
|
57
|
+
const bb = targetItem.getElement()?.getBoundingClientRect();
|
|
58
58
|
|
|
59
59
|
if (bb) {
|
|
60
60
|
return {
|
|
61
|
-
|
|
61
|
+
indent,
|
|
62
62
|
top: bb.top,
|
|
63
|
-
left: bb.left,
|
|
63
|
+
left: bb.left + leftOffset,
|
|
64
64
|
right: bb.right,
|
|
65
65
|
};
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
return null;
|
|
69
69
|
},
|
|
70
|
-
}),
|
|
71
70
|
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
getDragLineStyle: ({ tree }, topOffset = -1, leftOffset = -8) => {
|
|
72
|
+
const dragLine = tree.getDragLineData();
|
|
73
|
+
return dragLine
|
|
74
|
+
? {
|
|
75
|
+
top: `${dragLine.top + topOffset}px`,
|
|
76
|
+
left: `${dragLine.left + leftOffset}px`,
|
|
77
|
+
width: `${dragLine.right - dragLine.left - leftOffset}px`,
|
|
78
|
+
pointerEvents: "none", // important to prevent capturing drag events
|
|
79
|
+
}
|
|
80
|
+
: { display: "none" };
|
|
81
|
+
},
|
|
82
|
+
},
|
|
74
83
|
|
|
75
|
-
|
|
76
|
-
|
|
84
|
+
itemInstance: {
|
|
85
|
+
// TODO instead of individual getMemoizedProp calls, use a wrapped getMemoizedProps or something (getProps: () => getMemoized({...})
|
|
86
|
+
getProps: ({ tree, item, prev }) => ({
|
|
87
|
+
...prev?.(),
|
|
77
88
|
|
|
78
89
|
draggable: tree.getConfig().isItemDraggable?.(item) ?? true,
|
|
79
90
|
|
|
80
|
-
onDragStart: item.getMemoizedProp(
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
91
|
+
onDragStart: item.getMemoizedProp(
|
|
92
|
+
"dnd/onDragStart",
|
|
93
|
+
() => (e: DragEvent) => {
|
|
94
|
+
const selectedItems = tree.getSelectedItems();
|
|
95
|
+
const items = selectedItems.includes(item) ? selectedItems : [item];
|
|
96
|
+
const config = tree.getConfig();
|
|
97
|
+
|
|
98
|
+
if (!selectedItems.includes(item)) {
|
|
99
|
+
tree.setSelectedItems([item.getItemMeta().itemId]);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (!(config.canDrag?.(items) ?? true)) {
|
|
103
|
+
e.preventDefault();
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (config.createForeignDragObject) {
|
|
108
|
+
const { format, data } = config.createForeignDragObject(items);
|
|
109
|
+
e.dataTransfer?.setData(format, data);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
tree.applySubStateUpdate("dnd", {
|
|
113
|
+
draggedItems: items,
|
|
114
|
+
draggingOverItem: tree.getFocusedItem(),
|
|
115
|
+
});
|
|
116
|
+
},
|
|
117
|
+
),
|
|
118
|
+
|
|
119
|
+
onDragOver: item.getMemoizedProp(
|
|
120
|
+
"dnd/onDragOver",
|
|
121
|
+
() => (e: DragEvent) => {
|
|
122
|
+
const dataRef = tree.getDataRef<DndDataRef>();
|
|
123
|
+
const nextDragCode = getDragCode(e, item, tree);
|
|
124
|
+
if (nextDragCode === dataRef.current.lastDragCode) {
|
|
125
|
+
if (dataRef.current.lastAllowDrop) {
|
|
126
|
+
e.preventDefault();
|
|
127
|
+
}
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
dataRef.current.lastDragCode = nextDragCode;
|
|
131
|
+
|
|
132
|
+
const target = getDropTarget(e, item, tree);
|
|
133
|
+
|
|
134
|
+
if (
|
|
135
|
+
!tree.getState().dnd?.draggedItems &&
|
|
136
|
+
(!e.dataTransfer ||
|
|
137
|
+
!tree
|
|
138
|
+
.getConfig()
|
|
139
|
+
.canDropForeignDragObject?.(e.dataTransfer, target))
|
|
140
|
+
) {
|
|
141
|
+
dataRef.current.lastAllowDrop = false;
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (!canDrop(e.dataTransfer, target, tree)) {
|
|
146
|
+
dataRef.current.lastAllowDrop = false;
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
tree.applySubStateUpdate("dnd", (state) => ({
|
|
151
|
+
...state,
|
|
152
|
+
dragTarget: target,
|
|
153
|
+
draggingOverItem: item,
|
|
154
|
+
}));
|
|
155
|
+
dataRef.current.lastAllowDrop = true;
|
|
90
156
|
e.preventDefault();
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if (config.createForeignDragObject) {
|
|
95
|
-
const { format, data } = config.createForeignDragObject(items);
|
|
96
|
-
e.dataTransfer?.setData(format, data);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
tree.applySubStateUpdate("dnd", {
|
|
100
|
-
draggedItems: items,
|
|
101
|
-
draggingOverItem: tree.getFocusedItem(),
|
|
102
|
-
});
|
|
103
|
-
}),
|
|
104
|
-
|
|
105
|
-
onDragOver: item.getMemoizedProp("dnd/onDragOver", () => (e) => {
|
|
106
|
-
const target = getDropTarget(e, item, tree);
|
|
107
|
-
const dataRef = tree.getDataRef<DndDataRef>();
|
|
108
|
-
|
|
109
|
-
if (
|
|
110
|
-
!tree.getState().dnd?.draggedItems &&
|
|
111
|
-
!tree.getConfig().canDropForeignDragObject?.(e.dataTransfer, target)
|
|
112
|
-
) {
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (!canDrop(e.dataTransfer, target, tree)) {
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
|
|
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
|
-
tree.applySubStateUpdate("dnd", (state) => ({
|
|
130
|
-
...state,
|
|
131
|
-
dragTarget: target,
|
|
132
|
-
draggingOverItem: item,
|
|
133
|
-
}));
|
|
134
|
-
}),
|
|
157
|
+
},
|
|
158
|
+
),
|
|
135
159
|
|
|
136
160
|
onDragLeave: item.getMemoizedProp("dnd/onDragLeave", () => () => {
|
|
137
161
|
const dataRef = tree.getDataRef<DndDataRef>();
|
|
@@ -143,18 +167,18 @@ export const dragAndDropFeature: FeatureImplementation<
|
|
|
143
167
|
}));
|
|
144
168
|
}),
|
|
145
169
|
|
|
146
|
-
onDragEnd: item.getMemoizedProp("dnd/onDragEnd", () => (e) => {
|
|
170
|
+
onDragEnd: item.getMemoizedProp("dnd/onDragEnd", () => (e: DragEvent) => {
|
|
147
171
|
const draggedItems = tree.getState().dnd?.draggedItems;
|
|
148
172
|
tree.applySubStateUpdate("dnd", null);
|
|
149
173
|
|
|
150
|
-
if (e.dataTransfer
|
|
174
|
+
if (e.dataTransfer?.dropEffect === "none" || !draggedItems) {
|
|
151
175
|
return;
|
|
152
176
|
}
|
|
153
177
|
|
|
154
178
|
tree.getConfig().onCompleteForeignDrop?.(draggedItems);
|
|
155
179
|
}),
|
|
156
180
|
|
|
157
|
-
onDrop: item.getMemoizedProp("dnd/onDrop", () => (e) => {
|
|
181
|
+
onDrop: item.getMemoizedProp("dnd/onDrop", () => (e: DragEvent) => {
|
|
158
182
|
const dataRef = tree.getDataRef<DndDataRef>();
|
|
159
183
|
const target = getDropTarget(e, item, tree);
|
|
160
184
|
|
|
@@ -171,19 +195,19 @@ export const dragAndDropFeature: FeatureImplementation<
|
|
|
171
195
|
|
|
172
196
|
if (draggedItems) {
|
|
173
197
|
config.onDrop?.(draggedItems, target);
|
|
174
|
-
} else {
|
|
198
|
+
} else if (e.dataTransfer) {
|
|
175
199
|
config.onDropForeignDragObject?.(e.dataTransfer, target);
|
|
176
200
|
}
|
|
177
201
|
// TODO rebuild tree?
|
|
178
202
|
}),
|
|
179
203
|
}),
|
|
180
204
|
|
|
181
|
-
isDropTarget: () => {
|
|
205
|
+
isDropTarget: ({ tree, item }) => {
|
|
182
206
|
const target = tree.getDropTarget();
|
|
183
207
|
return target ? target.item.getId() === item.getId() : false;
|
|
184
208
|
},
|
|
185
209
|
|
|
186
|
-
isDropTargetAbove: () => {
|
|
210
|
+
isDropTargetAbove: ({ tree, item }) => {
|
|
187
211
|
const target = tree.getDropTarget();
|
|
188
212
|
|
|
189
213
|
if (
|
|
@@ -195,7 +219,7 @@ export const dragAndDropFeature: FeatureImplementation<
|
|
|
195
219
|
return target.childIndex === item.getItemMeta().posInSet;
|
|
196
220
|
},
|
|
197
221
|
|
|
198
|
-
isDropTargetBelow: () => {
|
|
222
|
+
isDropTargetBelow: ({ tree, item }) => {
|
|
199
223
|
const target = tree.getDropTarget();
|
|
200
224
|
|
|
201
225
|
if (
|
|
@@ -207,8 +231,8 @@ export const dragAndDropFeature: FeatureImplementation<
|
|
|
207
231
|
return target.childIndex - 1 === item.getItemMeta().posInSet;
|
|
208
232
|
},
|
|
209
233
|
|
|
210
|
-
isDraggingOver: () => {
|
|
234
|
+
isDraggingOver: ({ tree, item }) => {
|
|
211
235
|
return tree.getState().dnd?.draggingOverItem?.getId() === item.getId();
|
|
212
236
|
},
|
|
213
|
-
}
|
|
237
|
+
},
|
|
214
238
|
};
|
|
@@ -2,6 +2,7 @@ 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> = {
|
|
@@ -11,7 +12,7 @@ export type DndState<T> = {
|
|
|
11
12
|
};
|
|
12
13
|
|
|
13
14
|
export type DragLineData = {
|
|
14
|
-
|
|
15
|
+
indent: number;
|
|
15
16
|
top: number;
|
|
16
17
|
left: number;
|
|
17
18
|
right: number;
|
|
@@ -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
|
-
|
|
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 `canDropInbetween` is `false`, this is ignored. */
|
|
53
|
+
reorderAreaPercentage?: number;
|
|
47
54
|
canDropInbetween?: 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,18 +60,96 @@ 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.canDropInbetween) {
|
|
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 = (
|
|
@@ -52,59 +158,94 @@ export const getDropTarget = (
|
|
|
52
158
|
tree: TreeInstance<any>,
|
|
53
159
|
canDropInbetween = tree.getConfig().canDropInbetween,
|
|
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
|
+
!canDropInbetween &&
|
|
188
|
+
parent &&
|
|
189
|
+
canBecomeSibling &&
|
|
190
|
+
placement.type !== PlacementType.MakeChild
|
|
191
|
+
) {
|
|
192
|
+
return parentTarget;
|
|
69
193
|
}
|
|
70
194
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
195
|
+
if (!canDropInbetween && 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,
|