@splicetree/plugin-dnd 2.0.0 → 3.0.0
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 +14 -0
- package/dist/index.d.ts +13 -14
- package/dist/index.js +245 -46
- package/package.json +2 -2
- package/src/index.ts +243 -34
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @splicetree/plugin-dnd
|
|
2
2
|
|
|
3
|
+
## 3.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- 拖拽插件(dnd)交互与事件重构:
|
|
8
|
+
- 支持在容器“外部顶部/外部底部”释放也触发移动:新增 containerProps,用于绑定到列表容器;顶部释放移动到顶级 index=0、底部释放移动到顶级末尾
|
|
9
|
+
- 将 move 事件负载重构为:{ index, parent, oldIndex, oldParent, node, event },便于直接按索引/父节点进行写回与业务处理(破坏性变更)
|
|
10
|
+
- 在 dragend 增加回退逻辑:容器外释放也会执行移动并清理状态;ghost 先隐藏再移动,降低延迟感知
|
|
11
|
+
- 支持“上部分包含上外部、下部分包含下外部”:节点 dragleave 根据指针越界方向归并为 BEFORE/AFTER,一致化落点判定
|
|
12
|
+
|
|
13
|
+
迁移指南:
|
|
14
|
+
- 监听 move 事件的代码需改用新的负载字段;例如在 autoUpdateParent=false 时按 { node, parent, index } 执行 moveNode 写回
|
|
15
|
+
- 若需要顶级外部释放行为,请在顶级列表容器上绑定 containerProps()
|
|
16
|
+
|
|
3
17
|
## 2.0.0
|
|
4
18
|
|
|
5
19
|
### Minor Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -37,18 +37,13 @@ declare module '@splicetree/core' {
|
|
|
37
37
|
dnd?: DndOptions;
|
|
38
38
|
}
|
|
39
39
|
interface SpliceTreeEventPayloadMap {
|
|
40
|
-
/**
|
|
41
|
-
* 节点移动事件负载
|
|
42
|
-
* @property id 源节点 id
|
|
43
|
-
* @property parentId 新父级节点 id(INSIDE/BEFORE/AFTER 场景下可能不同)
|
|
44
|
-
* @property position 落点位置(BEFORE/INSIDE/AFTER)
|
|
45
|
-
* @property beforeId 插入到谁之前(AFTER/BEFORE 场景下提供)
|
|
46
|
-
*/
|
|
47
40
|
move: {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
41
|
+
index: number;
|
|
42
|
+
parent: SpliceTreeNode<any> | undefined;
|
|
43
|
+
oldIndex: number;
|
|
44
|
+
oldParent: SpliceTreeNode<any> | undefined;
|
|
45
|
+
node: SpliceTreeNode<any>;
|
|
46
|
+
event?: DragEvent | MouseEvent;
|
|
52
47
|
};
|
|
53
48
|
}
|
|
54
49
|
interface SpliceTreeInstance {
|
|
@@ -58,7 +53,7 @@ declare module '@splicetree/core' {
|
|
|
58
53
|
* @param targetId 目标节点 id
|
|
59
54
|
* @param position 落点位置(前/内/后)
|
|
60
55
|
*/
|
|
61
|
-
drop: (srcId: string, targetId: string, position: DropPosition) => void;
|
|
56
|
+
drop: (srcId: string, targetId: string, position: DropPosition, e?: DragEvent | MouseEvent) => void;
|
|
62
57
|
/** 当前拖拽源节点 id */
|
|
63
58
|
draggingId?: string;
|
|
64
59
|
/** 目标节点的悬停位置映射 */
|
|
@@ -68,9 +63,9 @@ declare module '@splicetree/core' {
|
|
|
68
63
|
/** DOM 事件:悬停计算并更新位置 */
|
|
69
64
|
onDragOver: (id: string, el: HTMLElement, e: DragEvent | MouseEvent) => void;
|
|
70
65
|
/** DOM 事件:离开目标,清理悬停状态 */
|
|
71
|
-
onDragLeave: (id: string) => void;
|
|
66
|
+
onDragLeave: (id: string, e?: DragEvent) => void;
|
|
72
67
|
/** DOM 事件:在目标上释放后执行移动 */
|
|
73
|
-
onDrop: (targetId: string) => void;
|
|
68
|
+
onDrop: (targetId: string, e?: DragEvent | MouseEvent) => void;
|
|
74
69
|
/** 可直接 v-bind 到节点的拖拽属性集合(逐节点) */
|
|
75
70
|
dragProps: (id: string, behavior?: DragBehavior) => {
|
|
76
71
|
/** 是否可拖拽 */
|
|
@@ -93,6 +88,10 @@ declare module '@splicetree/core' {
|
|
|
93
88
|
padding?: boolean;
|
|
94
89
|
margin?: boolean;
|
|
95
90
|
}) => Record<string, any>;
|
|
91
|
+
containerProps: (parentId?: string) => {
|
|
92
|
+
onDragover: (e: DragEvent) => void;
|
|
93
|
+
onDrop: (e: DragEvent) => void;
|
|
94
|
+
};
|
|
96
95
|
}
|
|
97
96
|
/**
|
|
98
97
|
* 节点扩展(DnD)
|
package/dist/index.js
CHANGED
|
@@ -40,6 +40,38 @@ const dnd = {
|
|
|
40
40
|
let ghostMarginLeft = 0;
|
|
41
41
|
let ghostMarginRight = 0;
|
|
42
42
|
const behaviors = /* @__PURE__ */ new Map();
|
|
43
|
+
let containerHoverParentId;
|
|
44
|
+
let containerHoverPos;
|
|
45
|
+
let lastHoverTargetId;
|
|
46
|
+
const refresh = () => ctx.events.emit({
|
|
47
|
+
name: "visibility",
|
|
48
|
+
keys: ctx.tree.expandedKeys()
|
|
49
|
+
});
|
|
50
|
+
const setGhostByEl = (el, pos) => {
|
|
51
|
+
ghostTop = el.offsetTop;
|
|
52
|
+
ghostHeight = el.offsetHeight;
|
|
53
|
+
ghostPos = pos;
|
|
54
|
+
const parent = el.parentElement;
|
|
55
|
+
if (parent) {
|
|
56
|
+
const styles = getComputedStyle(parent);
|
|
57
|
+
ghostInsetLeft = Number.parseFloat(styles.paddingLeft || "0");
|
|
58
|
+
ghostInsetRight = Number.parseFloat(styles.paddingRight || "0");
|
|
59
|
+
ghostMarginLeft = Number.parseFloat(styles.marginLeft || "0");
|
|
60
|
+
ghostMarginRight = Number.parseFloat(styles.marginRight || "0");
|
|
61
|
+
} else {
|
|
62
|
+
ghostInsetLeft = 0;
|
|
63
|
+
ghostInsetRight = 0;
|
|
64
|
+
ghostMarginLeft = 0;
|
|
65
|
+
ghostMarginRight = 0;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
const topLevelItems = () => ctx.tree.items().filter((n) => !n.getParent());
|
|
69
|
+
const siblingsOf = (parentId) => parentId ? ctx.tree.getNode(parentId).getChildren() : topLevelItems();
|
|
70
|
+
const computeOldIndexForNode = (node) => {
|
|
71
|
+
const p = node.getParent();
|
|
72
|
+
if (p) return p.getChildren().findIndex((n) => n.id === node.id);
|
|
73
|
+
return topLevelItems().findIndex((n) => n.id === node.id);
|
|
74
|
+
};
|
|
43
75
|
const isDisabledById = (_id) => !!readonly;
|
|
44
76
|
const getDraggedNodeIds = (primaryId) => {
|
|
45
77
|
const tree = ctx.tree;
|
|
@@ -61,10 +93,7 @@ const dnd = {
|
|
|
61
93
|
const onDragStart = (id) => {
|
|
62
94
|
if (isDisabledById(id)) return;
|
|
63
95
|
draggingId = id;
|
|
64
|
-
|
|
65
|
-
name: "visibility",
|
|
66
|
-
keys: ctx.tree.expandedKeys()
|
|
67
|
-
});
|
|
96
|
+
refresh();
|
|
68
97
|
};
|
|
69
98
|
/**
|
|
70
99
|
* 依据指针相对目标节点的垂直比例计算落点
|
|
@@ -145,9 +174,8 @@ const dnd = {
|
|
|
145
174
|
return;
|
|
146
175
|
}
|
|
147
176
|
hoverPositions.set(id, pos);
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
ghostPos = pos;
|
|
177
|
+
setGhostByEl(el, pos);
|
|
178
|
+
lastHoverTargetId = id;
|
|
151
179
|
const parent = el.parentElement;
|
|
152
180
|
if (parent) {
|
|
153
181
|
const styles = getComputedStyle(parent);
|
|
@@ -161,21 +189,35 @@ const dnd = {
|
|
|
161
189
|
ghostMarginLeft = 0;
|
|
162
190
|
ghostMarginRight = 0;
|
|
163
191
|
}
|
|
164
|
-
|
|
165
|
-
name: "visibility",
|
|
166
|
-
keys: ctx.tree.expandedKeys()
|
|
167
|
-
});
|
|
192
|
+
refresh();
|
|
168
193
|
};
|
|
169
194
|
/**
|
|
170
195
|
* 离开目标:清理该目标的悬停位置并刷新视图
|
|
171
196
|
* @param id 目标节点 id
|
|
172
197
|
*/
|
|
173
|
-
const onDragLeave = (id) => {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
198
|
+
const onDragLeave = (id, e) => {
|
|
199
|
+
if (!draggingId) return;
|
|
200
|
+
const el = e?.currentTarget ?? document.querySelector(`[data-id="${id}"]`);
|
|
201
|
+
if (!el) {
|
|
202
|
+
ctx.events.emit({
|
|
203
|
+
name: "visibility",
|
|
204
|
+
keys: ctx.tree.expandedKeys()
|
|
205
|
+
});
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const rect = el.getBoundingClientRect();
|
|
209
|
+
const cy = e?.clientY;
|
|
210
|
+
if (typeof cy === "number") {
|
|
211
|
+
if (cy < rect.top) {
|
|
212
|
+
hoverPositions.set(id, DropPosition.BEFORE);
|
|
213
|
+
setGhostByEl(el, DropPosition.BEFORE);
|
|
214
|
+
} else if (cy > rect.bottom) {
|
|
215
|
+
hoverPositions.set(id, DropPosition.AFTER);
|
|
216
|
+
setGhostByEl(el, DropPosition.AFTER);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
lastHoverTargetId = id;
|
|
220
|
+
refresh();
|
|
179
221
|
};
|
|
180
222
|
/**
|
|
181
223
|
* 执行移动:依据落点位置更新树结构,必要时写回父字段并派发事件
|
|
@@ -185,7 +227,7 @@ const dnd = {
|
|
|
185
227
|
* @param targetId 目标节点 id
|
|
186
228
|
* @param position 落点位置
|
|
187
229
|
*/
|
|
188
|
-
const drop = (srcId, targetId, position) => {
|
|
230
|
+
const drop = (srcId, targetId, position, e) => {
|
|
189
231
|
const src = ctx.tree.getNode(srcId);
|
|
190
232
|
const target = ctx.tree.getNode(targetId);
|
|
191
233
|
if (!src || !target) return;
|
|
@@ -197,25 +239,30 @@ const dnd = {
|
|
|
197
239
|
if (srcId === targetId) return;
|
|
198
240
|
const srcParentId = src.getParent()?.id;
|
|
199
241
|
if (position === DropPosition.INSIDE && srcParentId === targetId) return;
|
|
242
|
+
const oldParent = src.getParent();
|
|
243
|
+
const oldIndex = computeOldIndexForNode(src);
|
|
200
244
|
if (position === DropPosition.INSIDE) {
|
|
201
245
|
if (autoUpdateParent) {
|
|
202
246
|
ctx.tree.moveNode(srcId, targetId);
|
|
203
247
|
Reflect.set(src.original, parentField, targetId);
|
|
204
248
|
if (autoExpandOnDrop) ctx.tree.expand(targetId);
|
|
205
|
-
|
|
206
|
-
name: "visibility",
|
|
207
|
-
keys: ctx.tree.expandedKeys()
|
|
208
|
-
});
|
|
249
|
+
refresh();
|
|
209
250
|
}
|
|
251
|
+
const newParent$1 = ctx.tree.getNode(targetId);
|
|
252
|
+
const newIndex$1 = autoUpdateParent ? newParent$1.getChildren().findIndex((n) => n.id === srcId) : newParent$1.getChildren().length;
|
|
210
253
|
ctx.events.emit({
|
|
211
254
|
name: "move",
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
255
|
+
index: newIndex$1,
|
|
256
|
+
parent: newParent$1,
|
|
257
|
+
oldIndex,
|
|
258
|
+
oldParent,
|
|
259
|
+
node: src,
|
|
260
|
+
event: e
|
|
215
261
|
});
|
|
216
262
|
return;
|
|
217
263
|
}
|
|
218
|
-
const
|
|
264
|
+
const parent = target.getParent();
|
|
265
|
+
const parentId = parent?.id;
|
|
219
266
|
if (position === DropPosition.BEFORE) {
|
|
220
267
|
if (autoUpdateParent) {
|
|
221
268
|
ctx.tree.moveNode(srcId, parentId, targetId);
|
|
@@ -225,39 +272,46 @@ const dnd = {
|
|
|
225
272
|
keys: ctx.tree.expandedKeys()
|
|
226
273
|
});
|
|
227
274
|
}
|
|
275
|
+
const idx$1 = siblingsOf(parentId).filter((n) => n.id !== srcId).findIndex((n) => n.id === targetId);
|
|
276
|
+
const newParent$1 = parent;
|
|
277
|
+
const newIndex$1 = autoUpdateParent ? newParent$1 ? newParent$1.getChildren().findIndex((n) => n.id === srcId) : ctx.tree.items().filter((n) => !n.getParent()).findIndex((n) => n.id === srcId) : idx$1 >= 0 ? idx$1 : 0;
|
|
228
278
|
ctx.events.emit({
|
|
229
279
|
name: "move",
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
280
|
+
index: newIndex$1,
|
|
281
|
+
parent: newParent$1,
|
|
282
|
+
oldIndex,
|
|
283
|
+
oldParent,
|
|
284
|
+
node: src,
|
|
285
|
+
event: e
|
|
234
286
|
});
|
|
235
287
|
return;
|
|
236
288
|
}
|
|
237
|
-
const siblings = (parentId
|
|
289
|
+
const siblings = siblingsOf(parentId).filter((n) => n.id !== srcId);
|
|
238
290
|
const idx = siblings.findIndex((n) => n.id === targetId);
|
|
239
291
|
const afterSibling = idx >= 0 ? siblings[idx + 1]?.id : void 0;
|
|
240
292
|
if (autoUpdateParent) {
|
|
241
293
|
ctx.tree.moveNode(srcId, parentId, afterSibling);
|
|
242
294
|
Reflect.set(src.original, parentField, parentId);
|
|
243
|
-
|
|
244
|
-
name: "visibility",
|
|
245
|
-
keys: ctx.tree.expandedKeys()
|
|
246
|
-
});
|
|
295
|
+
refresh();
|
|
247
296
|
}
|
|
297
|
+
const newParent = parent;
|
|
298
|
+
const predictedIndex = afterSibling ? idx + 1 : siblings.length;
|
|
299
|
+
const newIndex = autoUpdateParent ? newParent ? newParent.getChildren().findIndex((n) => n.id === srcId) : ctx.tree.items().filter((n) => !n.getParent()).findIndex((n) => n.id === srcId) : predictedIndex;
|
|
248
300
|
ctx.events.emit({
|
|
249
301
|
name: "move",
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
302
|
+
index: newIndex,
|
|
303
|
+
parent: newParent,
|
|
304
|
+
oldIndex,
|
|
305
|
+
oldParent,
|
|
306
|
+
node: src,
|
|
307
|
+
event: e
|
|
254
308
|
});
|
|
255
309
|
};
|
|
256
310
|
/**
|
|
257
311
|
* 完成拖拽:依据最后一次悬停位置执行移动并清理状态
|
|
258
312
|
* @param targetId 释放时所处的目标节点 id
|
|
259
313
|
*/
|
|
260
|
-
const onDrop = (targetId) => {
|
|
314
|
+
const onDrop = (targetId, e) => {
|
|
261
315
|
if (!draggingId) return;
|
|
262
316
|
const pos = hoverPositions.get(targetId);
|
|
263
317
|
if (pos === void 0) {
|
|
@@ -272,7 +326,7 @@ const dnd = {
|
|
|
272
326
|
const draggedIds = getDraggedNodeIds(draggingId);
|
|
273
327
|
let idsToProcess = draggedIds;
|
|
274
328
|
if (pos === DropPosition.AFTER) idsToProcess = [...draggedIds].reverse();
|
|
275
|
-
for (const id of idsToProcess) drop(id, targetId, pos);
|
|
329
|
+
for (const id of idsToProcess) drop(id, targetId, pos, e);
|
|
276
330
|
hoverPositions.clear();
|
|
277
331
|
draggingId = void 0;
|
|
278
332
|
ctx.events.emit({
|
|
@@ -306,24 +360,168 @@ const dnd = {
|
|
|
306
360
|
onDragStart(id);
|
|
307
361
|
}
|
|
308
362
|
},
|
|
309
|
-
onDragend: (
|
|
363
|
+
onDragend: (e) => {
|
|
310
364
|
handleActive.delete(id);
|
|
365
|
+
if (!draggingId) return;
|
|
366
|
+
const draggedIds = getDraggedNodeIds(draggingId);
|
|
367
|
+
ghostPos = void 0;
|
|
368
|
+
ctx.events.emit({
|
|
369
|
+
name: "visibility",
|
|
370
|
+
keys: ctx.tree.expandedKeys()
|
|
371
|
+
});
|
|
372
|
+
if (containerHoverPos !== void 0) {
|
|
373
|
+
const parentId2 = containerHoverParentId;
|
|
374
|
+
const rawSiblings = parentId2 ? ctx.tree.getNode(parentId2).getChildren() : ctx.tree.items().filter((n) => !n.getParent());
|
|
375
|
+
const processIds = containerHoverPos === DropPosition.AFTER ? [...draggedIds].reverse() : draggedIds;
|
|
376
|
+
for (const did of processIds) {
|
|
377
|
+
const siblings = rawSiblings.filter((n) => n.id !== did);
|
|
378
|
+
const firstId = siblings[0]?.id;
|
|
379
|
+
const beforeId = containerHoverPos === DropPosition.BEFORE ? firstId : void 0;
|
|
380
|
+
const src2 = ctx.tree.getNode(did);
|
|
381
|
+
if (!src2) continue;
|
|
382
|
+
const oldParent2 = src2.getParent();
|
|
383
|
+
const oldIndex2 = oldParent2 ? oldParent2.getChildren().findIndex((n) => n.id === did) : ctx.tree.items().filter((n) => !n.getParent()).findIndex((n) => n.id === did);
|
|
384
|
+
if (autoUpdateParent) {
|
|
385
|
+
ctx.tree.moveNode(did, parentId2, beforeId);
|
|
386
|
+
Reflect.set(src2.original, parentField, parentId2);
|
|
387
|
+
ctx.events.emit({
|
|
388
|
+
name: "visibility",
|
|
389
|
+
keys: ctx.tree.expandedKeys()
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
const newParent2 = parentId2 ? ctx.tree.getNode(parentId2) : void 0;
|
|
393
|
+
const newIndex2 = autoUpdateParent ? newParent2 ? newParent2.getChildren().findIndex((n) => n.id === did) : ctx.tree.items().filter((n) => !n.getParent()).findIndex((n) => n.id === did) : containerHoverPos === DropPosition.BEFORE ? 0 : siblings.length;
|
|
394
|
+
ctx.events.emit({
|
|
395
|
+
name: "move",
|
|
396
|
+
index: newIndex2,
|
|
397
|
+
parent: newParent2,
|
|
398
|
+
oldIndex: oldIndex2,
|
|
399
|
+
oldParent: oldParent2,
|
|
400
|
+
node: src2,
|
|
401
|
+
event: e
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
hoverPositions.clear();
|
|
405
|
+
draggingId = void 0;
|
|
406
|
+
containerHoverParentId = void 0;
|
|
407
|
+
containerHoverPos = void 0;
|
|
408
|
+
ctx.events.emit({
|
|
409
|
+
name: "visibility",
|
|
410
|
+
keys: ctx.tree.expandedKeys()
|
|
411
|
+
});
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
const targetId = lastHoverTargetId;
|
|
415
|
+
const pos = targetId ? hoverPositions.get(targetId) : void 0;
|
|
416
|
+
if (targetId && pos !== void 0) {
|
|
417
|
+
let idsToProcess = draggedIds;
|
|
418
|
+
if (pos === DropPosition.AFTER) idsToProcess = [...draggedIds].reverse();
|
|
419
|
+
for (const did of idsToProcess) drop(did, targetId, pos, e);
|
|
420
|
+
}
|
|
421
|
+
hoverPositions.clear();
|
|
422
|
+
draggingId = void 0;
|
|
423
|
+
ctx.events.emit({
|
|
424
|
+
name: "visibility",
|
|
425
|
+
keys: ctx.tree.expandedKeys()
|
|
426
|
+
});
|
|
311
427
|
},
|
|
312
428
|
onDragover: (e) => {
|
|
313
429
|
e.preventDefault();
|
|
314
430
|
const el = e.currentTarget;
|
|
315
431
|
onDragOver(id, el, e);
|
|
316
432
|
},
|
|
317
|
-
onDragleave: (
|
|
318
|
-
onDragLeave(id);
|
|
433
|
+
onDragleave: (e) => {
|
|
434
|
+
onDragLeave(id, e);
|
|
319
435
|
},
|
|
320
436
|
onDrop: (e) => {
|
|
321
437
|
e.preventDefault();
|
|
322
|
-
onDrop(id);
|
|
438
|
+
onDrop(id, e);
|
|
323
439
|
handleActive.delete(id);
|
|
324
440
|
}
|
|
325
441
|
};
|
|
326
442
|
};
|
|
443
|
+
const containerProps = (parentId) => {
|
|
444
|
+
return {
|
|
445
|
+
onDragover: (e) => {
|
|
446
|
+
if (!draggingId) return;
|
|
447
|
+
e.preventDefault();
|
|
448
|
+
const el = e.currentTarget;
|
|
449
|
+
const rect = el.getBoundingClientRect();
|
|
450
|
+
const y = ("clientY" in e ? e.clientY : 0) - rect.top;
|
|
451
|
+
if (Math.max(0, Math.min(1, rect.height ? y / rect.height : 0)) < .5) {
|
|
452
|
+
containerHoverPos = DropPosition.BEFORE;
|
|
453
|
+
ghostPos = DropPosition.BEFORE;
|
|
454
|
+
ghostTop = el.offsetTop;
|
|
455
|
+
ghostHeight = el.offsetHeight;
|
|
456
|
+
} else {
|
|
457
|
+
containerHoverPos = DropPosition.AFTER;
|
|
458
|
+
ghostPos = DropPosition.AFTER;
|
|
459
|
+
ghostTop = el.offsetTop;
|
|
460
|
+
ghostHeight = el.offsetHeight;
|
|
461
|
+
}
|
|
462
|
+
containerHoverParentId = parentId;
|
|
463
|
+
const styles = getComputedStyle(el);
|
|
464
|
+
ghostInsetLeft = Number.parseFloat(styles.paddingLeft || "0");
|
|
465
|
+
ghostInsetRight = Number.parseFloat(styles.paddingRight || "0");
|
|
466
|
+
ghostMarginLeft = Number.parseFloat(styles.marginLeft || "0");
|
|
467
|
+
ghostMarginRight = Number.parseFloat(styles.marginRight || "0");
|
|
468
|
+
ctx.events.emit({
|
|
469
|
+
name: "visibility",
|
|
470
|
+
keys: ctx.tree.expandedKeys()
|
|
471
|
+
});
|
|
472
|
+
},
|
|
473
|
+
onDrop: (e) => {
|
|
474
|
+
e.preventDefault();
|
|
475
|
+
if (!draggingId || containerHoverPos === void 0) {
|
|
476
|
+
containerHoverParentId = void 0;
|
|
477
|
+
containerHoverPos = void 0;
|
|
478
|
+
ghostPos = void 0;
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
const draggedIds = getDraggedNodeIds(draggingId);
|
|
482
|
+
const parentId2 = containerHoverParentId;
|
|
483
|
+
const rawSiblings = parentId2 ? ctx.tree.getNode(parentId2).getChildren() : ctx.tree.items().filter((n) => !n.getParent());
|
|
484
|
+
const processIds = containerHoverPos === DropPosition.AFTER ? [...draggedIds].reverse() : draggedIds;
|
|
485
|
+
for (const id of processIds) {
|
|
486
|
+
const siblings = rawSiblings.filter((n) => n.id !== id);
|
|
487
|
+
const firstId = siblings[0]?.id;
|
|
488
|
+
const beforeId = containerHoverPos === DropPosition.BEFORE ? firstId : void 0;
|
|
489
|
+
const src = ctx.tree.getNode(id);
|
|
490
|
+
if (!src) continue;
|
|
491
|
+
const oldParent = src.getParent();
|
|
492
|
+
const oldIndex = oldParent ? oldParent.getChildren().findIndex((n) => n.id === id) : ctx.tree.items().filter((n) => !n.getParent()).findIndex((n) => n.id === id);
|
|
493
|
+
if (autoUpdateParent) {
|
|
494
|
+
ctx.tree.moveNode(id, parentId2, beforeId);
|
|
495
|
+
Reflect.set(src.original, parentField, parentId2);
|
|
496
|
+
ctx.events.emit({
|
|
497
|
+
name: "visibility",
|
|
498
|
+
keys: ctx.tree.expandedKeys()
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
const newParent = parentId2 ? ctx.tree.getNode(parentId2) : void 0;
|
|
502
|
+
const newIndex = autoUpdateParent ? newParent ? newParent.getChildren().findIndex((n) => n.id === id) : ctx.tree.items().filter((n) => !n.getParent()).findIndex((n) => n.id === id) : containerHoverPos === DropPosition.BEFORE ? 0 : siblings.length;
|
|
503
|
+
ctx.events.emit({
|
|
504
|
+
name: "move",
|
|
505
|
+
index: newIndex,
|
|
506
|
+
parent: newParent,
|
|
507
|
+
oldIndex,
|
|
508
|
+
oldParent,
|
|
509
|
+
node: src,
|
|
510
|
+
event: e
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
hoverPositions.clear();
|
|
514
|
+
draggingId = void 0;
|
|
515
|
+
containerHoverParentId = void 0;
|
|
516
|
+
containerHoverPos = void 0;
|
|
517
|
+
ghostPos = void 0;
|
|
518
|
+
ctx.events.emit({
|
|
519
|
+
name: "visibility",
|
|
520
|
+
keys: ctx.tree.expandedKeys()
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
};
|
|
327
525
|
const ghostStyle = (opts$1) => {
|
|
328
526
|
if (!draggingId || ghostPos === void 0) return { style: { display: "none" } };
|
|
329
527
|
const usePadding = opts$1?.padding ?? true;
|
|
@@ -381,7 +579,8 @@ const dnd = {
|
|
|
381
579
|
return draggingId;
|
|
382
580
|
},
|
|
383
581
|
dragProps,
|
|
384
|
-
ghostStyle
|
|
582
|
+
ghostStyle,
|
|
583
|
+
containerProps
|
|
385
584
|
};
|
|
386
585
|
},
|
|
387
586
|
extendNode(node, ctx) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@splicetree/plugin-dnd",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "3.0.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"email": "michael.cocova@gmail.com",
|
|
7
7
|
"name": "Michael Cocova"
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"access": "public"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"@splicetree/core": "
|
|
26
|
+
"@splicetree/core": "3.0.0"
|
|
27
27
|
},
|
|
28
28
|
"scripts": {
|
|
29
29
|
"dev": "tsdown --watch",
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SpliceTreePlugin, SpliceTreePluginContext } from '@splicetree/core'
|
|
1
|
+
import type { SpliceTreeNode, SpliceTreePlugin, SpliceTreePluginContext } from '@splicetree/core'
|
|
2
2
|
import type { DndNode, DndOptions, DragBehavior } from './types'
|
|
3
3
|
import { computePosition } from './position'
|
|
4
4
|
import { DropPosition } from './types'
|
|
@@ -9,14 +9,14 @@ declare module '@splicetree/core' {
|
|
|
9
9
|
dnd?: DndOptions
|
|
10
10
|
}
|
|
11
11
|
interface SpliceTreeEventPayloadMap {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
move: {
|
|
13
|
+
index: number
|
|
14
|
+
parent: SpliceTreeNode<any> | undefined
|
|
15
|
+
oldIndex: number
|
|
16
|
+
oldParent: SpliceTreeNode<any> | undefined
|
|
17
|
+
node: SpliceTreeNode<any>
|
|
18
|
+
event?: DragEvent | MouseEvent
|
|
19
|
+
}
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
interface SpliceTreeInstance {
|
|
@@ -26,7 +26,7 @@ declare module '@splicetree/core' {
|
|
|
26
26
|
* @param targetId 目标节点 id
|
|
27
27
|
* @param position 落点位置(前/内/后)
|
|
28
28
|
*/
|
|
29
|
-
drop: (srcId: string, targetId: string, position: DropPosition) => void
|
|
29
|
+
drop: (srcId: string, targetId: string, position: DropPosition, e?: DragEvent | MouseEvent) => void
|
|
30
30
|
/** 当前拖拽源节点 id */
|
|
31
31
|
draggingId?: string
|
|
32
32
|
/** 目标节点的悬停位置映射 */
|
|
@@ -36,9 +36,9 @@ declare module '@splicetree/core' {
|
|
|
36
36
|
/** DOM 事件:悬停计算并更新位置 */
|
|
37
37
|
onDragOver: (id: string, el: HTMLElement, e: DragEvent | MouseEvent) => void
|
|
38
38
|
/** DOM 事件:离开目标,清理悬停状态 */
|
|
39
|
-
onDragLeave: (id: string) => void
|
|
39
|
+
onDragLeave: (id: string, e?: DragEvent) => void
|
|
40
40
|
/** DOM 事件:在目标上释放后执行移动 */
|
|
41
|
-
onDrop: (targetId: string) => void
|
|
41
|
+
onDrop: (targetId: string, e?: DragEvent | MouseEvent) => void
|
|
42
42
|
/** 可直接 v-bind 到节点的拖拽属性集合(逐节点) */
|
|
43
43
|
dragProps: (id: string, behavior?: DragBehavior) => {
|
|
44
44
|
/** 是否可拖拽 */
|
|
@@ -58,6 +58,10 @@ declare module '@splicetree/core' {
|
|
|
58
58
|
}
|
|
59
59
|
/** 统一占位样式绑定对象 */
|
|
60
60
|
ghostStyle: (opts?: { padding?: boolean, margin?: boolean }) => Record<string, any>
|
|
61
|
+
containerProps: (parentId?: string) => {
|
|
62
|
+
onDragover: (e: DragEvent) => void
|
|
63
|
+
onDrop: (e: DragEvent) => void
|
|
64
|
+
}
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
/**
|
|
@@ -103,6 +107,39 @@ export const dnd: SpliceTreePlugin = {
|
|
|
103
107
|
let ghostMarginLeft = 0
|
|
104
108
|
let ghostMarginRight = 0
|
|
105
109
|
const behaviors = new Map<string, DragBehavior>()
|
|
110
|
+
let containerHoverParentId: string | undefined
|
|
111
|
+
let containerHoverPos: DropPosition | undefined
|
|
112
|
+
let lastHoverTargetId: string | undefined
|
|
113
|
+
const refresh = () => ctx.events.emit({ name: 'visibility', keys: ctx.tree.expandedKeys() })
|
|
114
|
+
const setGhostByEl = (el: HTMLElement, pos: DropPosition) => {
|
|
115
|
+
ghostTop = el.offsetTop
|
|
116
|
+
ghostHeight = el.offsetHeight
|
|
117
|
+
ghostPos = pos
|
|
118
|
+
const parent = el.parentElement
|
|
119
|
+
if (parent) {
|
|
120
|
+
const styles = getComputedStyle(parent)
|
|
121
|
+
ghostInsetLeft = Number.parseFloat(styles.paddingLeft || '0')
|
|
122
|
+
ghostInsetRight = Number.parseFloat(styles.paddingRight || '0')
|
|
123
|
+
ghostMarginLeft = Number.parseFloat(styles.marginLeft || '0')
|
|
124
|
+
ghostMarginRight = Number.parseFloat(styles.marginRight || '0')
|
|
125
|
+
} else {
|
|
126
|
+
ghostInsetLeft = 0
|
|
127
|
+
ghostInsetRight = 0
|
|
128
|
+
ghostMarginLeft = 0
|
|
129
|
+
ghostMarginRight = 0
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const topLevelItems = () => ctx.tree.items().filter(n => !n.getParent())
|
|
133
|
+
const siblingsOf = (parentId?: string) => parentId ? ctx.tree.getNode(parentId)!.getChildren() : topLevelItems()
|
|
134
|
+
const computeOldIndexForNode = (node: SpliceTreeNode<any>) => {
|
|
135
|
+
const p = node.getParent()
|
|
136
|
+
if (p) {
|
|
137
|
+
const list = p.getChildren()
|
|
138
|
+
return list.findIndex(n => n.id === node.id)
|
|
139
|
+
}
|
|
140
|
+
const list = topLevelItems()
|
|
141
|
+
return list.findIndex(n => n.id === node.id)
|
|
142
|
+
}
|
|
106
143
|
|
|
107
144
|
const isDisabledById = (_id: string | undefined): boolean => !!readonly
|
|
108
145
|
|
|
@@ -137,7 +174,7 @@ export const dnd: SpliceTreePlugin = {
|
|
|
137
174
|
return
|
|
138
175
|
}
|
|
139
176
|
draggingId = id
|
|
140
|
-
|
|
177
|
+
refresh()
|
|
141
178
|
}
|
|
142
179
|
|
|
143
180
|
/**
|
|
@@ -228,9 +265,8 @@ export const dnd: SpliceTreePlugin = {
|
|
|
228
265
|
}
|
|
229
266
|
|
|
230
267
|
hoverPositions.set(id, pos)
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
ghostPos = pos
|
|
268
|
+
setGhostByEl(el, pos)
|
|
269
|
+
lastHoverTargetId = id
|
|
234
270
|
const parent = el.parentElement
|
|
235
271
|
if (parent) {
|
|
236
272
|
const styles = getComputedStyle(parent)
|
|
@@ -244,16 +280,40 @@ export const dnd: SpliceTreePlugin = {
|
|
|
244
280
|
ghostMarginLeft = 0
|
|
245
281
|
ghostMarginRight = 0
|
|
246
282
|
}
|
|
247
|
-
|
|
283
|
+
refresh()
|
|
248
284
|
}
|
|
249
285
|
|
|
250
286
|
/**
|
|
251
287
|
* 离开目标:清理该目标的悬停位置并刷新视图
|
|
252
288
|
* @param id 目标节点 id
|
|
253
289
|
*/
|
|
254
|
-
const onDragLeave = (id: string) => {
|
|
255
|
-
|
|
256
|
-
|
|
290
|
+
const onDragLeave = (id: string, e?: DragEvent) => {
|
|
291
|
+
if (!draggingId) {
|
|
292
|
+
return
|
|
293
|
+
}
|
|
294
|
+
const el = (e?.currentTarget ?? document.querySelector(`[data-id="${id}"]`)) as HTMLElement | null
|
|
295
|
+
if (!el) {
|
|
296
|
+
// 无法定位元素时,保留最后一次悬停位置,避免外部释放丢失落点
|
|
297
|
+
ctx.events.emit({ name: 'visibility', keys: ctx.tree.expandedKeys() })
|
|
298
|
+
return
|
|
299
|
+
}
|
|
300
|
+
const rect = el.getBoundingClientRect()
|
|
301
|
+
const cy = e?.clientY
|
|
302
|
+
if (typeof cy === 'number') {
|
|
303
|
+
if (cy < rect.top) {
|
|
304
|
+
// 上外部:归并为 BEFORE
|
|
305
|
+
hoverPositions.set(id, DropPosition.BEFORE)
|
|
306
|
+
setGhostByEl(el, DropPosition.BEFORE)
|
|
307
|
+
} else if (cy > rect.bottom) {
|
|
308
|
+
// 下外部:归并为 AFTER
|
|
309
|
+
hoverPositions.set(id, DropPosition.AFTER)
|
|
310
|
+
setGhostByEl(el, DropPosition.AFTER)
|
|
311
|
+
} else {
|
|
312
|
+
// 水平离开或无法判断:保留原有位置,不删除
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
lastHoverTargetId = id
|
|
316
|
+
refresh()
|
|
257
317
|
}
|
|
258
318
|
|
|
259
319
|
/**
|
|
@@ -264,7 +324,7 @@ export const dnd: SpliceTreePlugin = {
|
|
|
264
324
|
* @param targetId 目标节点 id
|
|
265
325
|
* @param position 落点位置
|
|
266
326
|
*/
|
|
267
|
-
const drop = (srcId: string, targetId: string, position: DropPosition) => {
|
|
327
|
+
const drop = (srcId: string, targetId: string, position: DropPosition, e?: DragEvent | MouseEvent) => {
|
|
268
328
|
const src = ctx.tree.getNode(srcId)
|
|
269
329
|
const target = ctx.tree.getNode(targetId)
|
|
270
330
|
if (!src || !target) {
|
|
@@ -292,6 +352,9 @@ export const dnd: SpliceTreePlugin = {
|
|
|
292
352
|
return
|
|
293
353
|
}
|
|
294
354
|
|
|
355
|
+
const oldParent = src.getParent()
|
|
356
|
+
const oldIndex = computeOldIndexForNode(src)
|
|
357
|
+
|
|
295
358
|
if (position === DropPosition.INSIDE) {
|
|
296
359
|
if (autoUpdateParent) {
|
|
297
360
|
ctx.tree.moveNode(srcId, targetId)
|
|
@@ -299,9 +362,13 @@ export const dnd: SpliceTreePlugin = {
|
|
|
299
362
|
if (autoExpandOnDrop) {
|
|
300
363
|
ctx.tree.expand(targetId)
|
|
301
364
|
}
|
|
302
|
-
|
|
365
|
+
refresh()
|
|
303
366
|
}
|
|
304
|
-
ctx.
|
|
367
|
+
const newParent = ctx.tree.getNode(targetId)
|
|
368
|
+
const newIndex = autoUpdateParent
|
|
369
|
+
? newParent!.getChildren().findIndex(n => n.id === srcId)
|
|
370
|
+
: newParent!.getChildren().length
|
|
371
|
+
ctx.events.emit({ name: 'move', index: newIndex, parent: newParent, oldIndex, oldParent, node: src, event: e })
|
|
305
372
|
return
|
|
306
373
|
}
|
|
307
374
|
|
|
@@ -313,27 +380,38 @@ export const dnd: SpliceTreePlugin = {
|
|
|
313
380
|
Reflect.set(src.original, parentField, parentId)
|
|
314
381
|
ctx.events.emit({ name: 'visibility', keys: ctx.tree.expandedKeys() })
|
|
315
382
|
}
|
|
316
|
-
|
|
383
|
+
const siblings = siblingsOf(parentId).filter(n => n.id !== srcId)
|
|
384
|
+
const idx = siblings.findIndex(n => n.id === targetId)
|
|
385
|
+
const newParent = parent
|
|
386
|
+
const newIndex = autoUpdateParent
|
|
387
|
+
? (newParent ? newParent.getChildren().findIndex(n => n.id === srcId) : ctx.tree.items().filter(n => !n.getParent()).findIndex(n => n.id === srcId))
|
|
388
|
+
: (idx >= 0 ? idx : 0)
|
|
389
|
+
ctx.events.emit({ name: 'move', index: newIndex, parent: newParent, oldIndex, oldParent, node: src, event: e })
|
|
317
390
|
return
|
|
318
391
|
}
|
|
319
392
|
|
|
320
|
-
const rawSiblings =
|
|
393
|
+
const rawSiblings = siblingsOf(parentId)
|
|
321
394
|
const siblings = rawSiblings.filter(n => n.id !== srcId)
|
|
322
395
|
const idx = siblings.findIndex(n => n.id === targetId)
|
|
323
396
|
const afterSibling = idx >= 0 ? siblings[idx + 1]?.id : undefined
|
|
324
397
|
if (autoUpdateParent) {
|
|
325
398
|
ctx.tree.moveNode(srcId, parentId, afterSibling)
|
|
326
399
|
Reflect.set(src.original, parentField, parentId)
|
|
327
|
-
|
|
400
|
+
refresh()
|
|
328
401
|
}
|
|
329
|
-
|
|
402
|
+
const newParent = parent
|
|
403
|
+
const predictedIndex = afterSibling ? (idx + 1) : siblings.length
|
|
404
|
+
const newIndex = autoUpdateParent
|
|
405
|
+
? (newParent ? newParent.getChildren().findIndex(n => n.id === srcId) : ctx.tree.items().filter(n => !n.getParent()).findIndex(n => n.id === srcId))
|
|
406
|
+
: predictedIndex
|
|
407
|
+
ctx.events.emit({ name: 'move', index: newIndex, parent: newParent, oldIndex, oldParent, node: src, event: e })
|
|
330
408
|
}
|
|
331
409
|
|
|
332
410
|
/**
|
|
333
411
|
* 完成拖拽:依据最后一次悬停位置执行移动并清理状态
|
|
334
412
|
* @param targetId 释放时所处的目标节点 id
|
|
335
413
|
*/
|
|
336
|
-
const onDrop = (targetId: string) => {
|
|
414
|
+
const onDrop = (targetId: string, e?: DragEvent | MouseEvent) => {
|
|
337
415
|
if (!draggingId) {
|
|
338
416
|
return
|
|
339
417
|
}
|
|
@@ -352,7 +430,7 @@ export const dnd: SpliceTreePlugin = {
|
|
|
352
430
|
}
|
|
353
431
|
|
|
354
432
|
for (const id of idsToProcess) {
|
|
355
|
-
drop(id, targetId, pos)
|
|
433
|
+
drop(id, targetId, pos, e)
|
|
356
434
|
}
|
|
357
435
|
|
|
358
436
|
hoverPositions.clear()
|
|
@@ -372,7 +450,9 @@ export const dnd: SpliceTreePlugin = {
|
|
|
372
450
|
return {
|
|
373
451
|
draggable: canDrag,
|
|
374
452
|
onMousedown: (e: MouseEvent) => {
|
|
375
|
-
if (!handleSelector)
|
|
453
|
+
if (!handleSelector) {
|
|
454
|
+
return
|
|
455
|
+
}
|
|
376
456
|
const path = (e as any).composedPath?.() as Element[] | undefined
|
|
377
457
|
const allowed = path?.some(el => (el as Element)?.matches?.(handleSelector)) ?? false
|
|
378
458
|
if (allowed) {
|
|
@@ -393,25 +473,153 @@ export const dnd: SpliceTreePlugin = {
|
|
|
393
473
|
onDragStart(id)
|
|
394
474
|
}
|
|
395
475
|
},
|
|
396
|
-
onDragend: (
|
|
476
|
+
onDragend: (e: DragEvent) => {
|
|
397
477
|
handleActive.delete(id)
|
|
478
|
+
if (!draggingId) {
|
|
479
|
+
return
|
|
480
|
+
}
|
|
481
|
+
const draggedIds = getDraggedNodeIds(draggingId)
|
|
482
|
+
// 提前隐藏 ghost,降低感知延迟
|
|
483
|
+
ghostPos = undefined
|
|
484
|
+
ctx.events.emit({ name: 'visibility', keys: ctx.tree.expandedKeys() })
|
|
485
|
+
|
|
486
|
+
if (containerHoverPos !== undefined) {
|
|
487
|
+
const parentId2 = containerHoverParentId
|
|
488
|
+
const rawSiblings = parentId2 ? ctx.tree.getNode(parentId2)!.getChildren() : ctx.tree.items().filter(n => !n.getParent())
|
|
489
|
+
const processIds = containerHoverPos === DropPosition.AFTER ? [...draggedIds].reverse() : draggedIds
|
|
490
|
+
for (const did of processIds) {
|
|
491
|
+
const siblings = rawSiblings.filter(n => n.id !== did)
|
|
492
|
+
const firstId = siblings[0]?.id
|
|
493
|
+
const beforeId = containerHoverPos === DropPosition.BEFORE ? firstId : undefined
|
|
494
|
+
const src2 = ctx.tree.getNode(did)
|
|
495
|
+
if (!src2) {
|
|
496
|
+
continue
|
|
497
|
+
}
|
|
498
|
+
const oldParent2 = src2.getParent()
|
|
499
|
+
const oldIndex2 = oldParent2 ? oldParent2.getChildren().findIndex(n => n.id === did) : ctx.tree.items().filter(n => !n.getParent()).findIndex(n => n.id === did)
|
|
500
|
+
if (autoUpdateParent) {
|
|
501
|
+
ctx.tree.moveNode(did, parentId2, beforeId)
|
|
502
|
+
Reflect.set(src2.original, parentField, parentId2)
|
|
503
|
+
ctx.events.emit({ name: 'visibility', keys: ctx.tree.expandedKeys() })
|
|
504
|
+
}
|
|
505
|
+
const newParent2 = parentId2 ? ctx.tree.getNode(parentId2)! : undefined
|
|
506
|
+
const newIndex2 = autoUpdateParent
|
|
507
|
+
? (newParent2 ? newParent2.getChildren().findIndex(n => n.id === did) : ctx.tree.items().filter(n => !n.getParent()).findIndex(n => n.id === did))
|
|
508
|
+
: (containerHoverPos === DropPosition.BEFORE ? 0 : siblings.length)
|
|
509
|
+
ctx.events.emit({ name: 'move', index: newIndex2, parent: newParent2, oldIndex: oldIndex2, oldParent: oldParent2, node: src2!, event: e })
|
|
510
|
+
}
|
|
511
|
+
hoverPositions.clear()
|
|
512
|
+
draggingId = undefined
|
|
513
|
+
containerHoverParentId = undefined
|
|
514
|
+
containerHoverPos = undefined
|
|
515
|
+
ctx.events.emit({ name: 'visibility', keys: ctx.tree.expandedKeys() })
|
|
516
|
+
return
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const targetId = lastHoverTargetId
|
|
520
|
+
const pos = targetId ? hoverPositions.get(targetId) : undefined
|
|
521
|
+
if (targetId && pos !== undefined) {
|
|
522
|
+
let idsToProcess = draggedIds
|
|
523
|
+
if (pos === DropPosition.AFTER) {
|
|
524
|
+
idsToProcess = [...draggedIds].reverse()
|
|
525
|
+
}
|
|
526
|
+
for (const did of idsToProcess) {
|
|
527
|
+
drop(did, targetId, pos, e)
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
hoverPositions.clear()
|
|
531
|
+
draggingId = undefined
|
|
532
|
+
ctx.events.emit({ name: 'visibility', keys: ctx.tree.expandedKeys() })
|
|
398
533
|
},
|
|
399
534
|
onDragover: (e: DragEvent) => {
|
|
400
535
|
e.preventDefault()
|
|
401
536
|
const el = e.currentTarget as HTMLElement
|
|
402
537
|
onDragOver(id, el, e)
|
|
403
538
|
},
|
|
404
|
-
onDragleave: (
|
|
405
|
-
onDragLeave(id)
|
|
539
|
+
onDragleave: (e: DragEvent) => {
|
|
540
|
+
onDragLeave(id, e)
|
|
406
541
|
},
|
|
407
542
|
onDrop: (e: DragEvent) => {
|
|
408
543
|
e.preventDefault()
|
|
409
|
-
onDrop(id)
|
|
544
|
+
onDrop(id, e)
|
|
410
545
|
handleActive.delete(id)
|
|
411
546
|
},
|
|
412
547
|
}
|
|
413
548
|
}
|
|
414
549
|
|
|
550
|
+
const containerProps = (parentId?: string) => {
|
|
551
|
+
return {
|
|
552
|
+
onDragover: (e: DragEvent) => {
|
|
553
|
+
if (!draggingId) {
|
|
554
|
+
return
|
|
555
|
+
}
|
|
556
|
+
e.preventDefault()
|
|
557
|
+
const el = e.currentTarget as HTMLElement
|
|
558
|
+
const rect = el.getBoundingClientRect()
|
|
559
|
+
const y = ('clientY' in e ? e.clientY : 0) - rect.top
|
|
560
|
+
const ratio = Math.max(0, Math.min(1, rect.height ? y / rect.height : 0))
|
|
561
|
+
if (ratio < 0.5) {
|
|
562
|
+
containerHoverPos = DropPosition.BEFORE
|
|
563
|
+
ghostPos = DropPosition.BEFORE
|
|
564
|
+
ghostTop = el.offsetTop
|
|
565
|
+
ghostHeight = el.offsetHeight
|
|
566
|
+
} else {
|
|
567
|
+
containerHoverPos = DropPosition.AFTER
|
|
568
|
+
ghostPos = DropPosition.AFTER
|
|
569
|
+
ghostTop = el.offsetTop
|
|
570
|
+
ghostHeight = el.offsetHeight
|
|
571
|
+
}
|
|
572
|
+
containerHoverParentId = parentId
|
|
573
|
+
const styles = getComputedStyle(el)
|
|
574
|
+
ghostInsetLeft = Number.parseFloat(styles.paddingLeft || '0')
|
|
575
|
+
ghostInsetRight = Number.parseFloat(styles.paddingRight || '0')
|
|
576
|
+
ghostMarginLeft = Number.parseFloat(styles.marginLeft || '0')
|
|
577
|
+
ghostMarginRight = Number.parseFloat(styles.marginRight || '0')
|
|
578
|
+
ctx.events.emit({ name: 'visibility', keys: ctx.tree.expandedKeys() })
|
|
579
|
+
},
|
|
580
|
+
onDrop: (e: DragEvent) => {
|
|
581
|
+
e.preventDefault()
|
|
582
|
+
if (!draggingId || containerHoverPos === undefined) {
|
|
583
|
+
containerHoverParentId = undefined
|
|
584
|
+
containerHoverPos = undefined
|
|
585
|
+
ghostPos = undefined
|
|
586
|
+
return
|
|
587
|
+
}
|
|
588
|
+
const draggedIds = getDraggedNodeIds(draggingId)
|
|
589
|
+
const parentId2 = containerHoverParentId
|
|
590
|
+
const rawSiblings = parentId2 ? ctx.tree.getNode(parentId2)!.getChildren() : ctx.tree.items().filter(n => !n.getParent())
|
|
591
|
+
const processIds = containerHoverPos === DropPosition.AFTER ? [...draggedIds].reverse() : draggedIds
|
|
592
|
+
for (const id of processIds) {
|
|
593
|
+
const siblings = rawSiblings.filter(n => n.id !== id)
|
|
594
|
+
const firstId = siblings[0]?.id
|
|
595
|
+
const beforeId = containerHoverPos === DropPosition.BEFORE ? firstId : undefined
|
|
596
|
+
const src = ctx.tree.getNode(id)
|
|
597
|
+
if (!src) {
|
|
598
|
+
continue
|
|
599
|
+
}
|
|
600
|
+
const oldParent = src.getParent()
|
|
601
|
+
const oldIndex = oldParent ? oldParent.getChildren().findIndex(n => n.id === id) : ctx.tree.items().filter(n => !n.getParent()).findIndex(n => n.id === id)
|
|
602
|
+
if (autoUpdateParent) {
|
|
603
|
+
ctx.tree.moveNode(id, parentId2, beforeId)
|
|
604
|
+
Reflect.set(src.original, parentField, parentId2)
|
|
605
|
+
ctx.events.emit({ name: 'visibility', keys: ctx.tree.expandedKeys() })
|
|
606
|
+
}
|
|
607
|
+
const newParent = parentId2 ? ctx.tree.getNode(parentId2)! : undefined
|
|
608
|
+
const newIndex = autoUpdateParent
|
|
609
|
+
? (newParent ? newParent.getChildren().findIndex(n => n.id === id) : ctx.tree.items().filter(n => !n.getParent()).findIndex(n => n.id === id))
|
|
610
|
+
: (containerHoverPos === DropPosition.BEFORE ? 0 : siblings.length)
|
|
611
|
+
ctx.events.emit({ name: 'move', index: newIndex, parent: newParent, oldIndex, oldParent, node: src!, event: e })
|
|
612
|
+
}
|
|
613
|
+
hoverPositions.clear()
|
|
614
|
+
draggingId = undefined
|
|
615
|
+
containerHoverParentId = undefined
|
|
616
|
+
containerHoverPos = undefined
|
|
617
|
+
ghostPos = undefined
|
|
618
|
+
ctx.events.emit({ name: 'visibility', keys: ctx.tree.expandedKeys() })
|
|
619
|
+
},
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
415
623
|
const ghostStyle = (opts?: { padding?: boolean, margin?: boolean }) => {
|
|
416
624
|
if (!draggingId || ghostPos === undefined) {
|
|
417
625
|
return { style: { display: 'none' } }
|
|
@@ -450,6 +658,7 @@ export const dnd: SpliceTreePlugin = {
|
|
|
450
658
|
// 通用 DOM 绑定集合
|
|
451
659
|
dragProps,
|
|
452
660
|
ghostStyle,
|
|
661
|
+
containerProps,
|
|
453
662
|
}
|
|
454
663
|
},
|
|
455
664
|
/**
|