Skip to content

Commit 4b935d3

Browse files
authored
feat: 新增拖拽组件 (#1655)
1 parent 3f6acb0 commit 4b935d3

File tree

8 files changed

+327
-449
lines changed

8 files changed

+327
-449
lines changed

packages/devui-vue/devui/dragdrop/index.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,9 @@ export { DraggableDirective, DroppableDirective, SortableDirective };
77

88
export default {
99
title: 'Dragdrop 拖拽',
10-
category: '通用',
11-
status: '10%',
10+
category: '演进中',
11+
status: '100%',
1212
install(app: App): void {
13-
app.directive('DDraggable', DraggableDirective);
14-
app.directive('DDroppable', DroppableDirective);
1513
app.directive('DSortable', SortableDirective);
1614
}
1715
};
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +0,0 @@
1-
export const SHADOW_ID = 'devui-dragdrop-placeholder-shadow';
Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,3 @@
1-
import { changeDragState, deleteInsertedSortableShadow } from './utils';
2-
import { SHADOW_ID } from './const';
31

42
export default {
5-
/**
6-
*
7-
* @param el
8-
* @description
9-
* 1、绑定该指令的element将会具备拖拽能力
10-
* 2、为各元素进行初始化配置
11-
* 2.1、dragFlag: 是否处于拖拽中
12-
* 2.2、dragOverFlag: 是否处于可放置区域
13-
*
14-
* 1、整体思路
15-
* 1.1、为每个绑定drag指令的元素维护状态
16-
* 1.1.1、状态集合:dragStart、drag、dragover、drop、shouldCreateShadow
17-
*
18-
* 1.2、进入drop区域后,确保drop区域能够获取正在进行drag的元素
19-
*/
20-
mounted(el: HTMLElement, binding: unknown): void {
21-
el.setAttribute('draggable', 'true');
22-
el.style.cursor = 'grab';
23-
24-
// dragstart/drag/dragend
25-
el.addEventListener('drag', () => {
26-
changeDragState(el, el.id, 'true', 'true', 'false', 'false', 'false', 'true');
27-
if (binding.instance.$root.dropElement && document.getElementById(SHADOW_ID)){
28-
deleteInsertedSortableShadow(binding.instance.$root.dropElement); // 如何让它仅执行1次?
29-
binding.instance.$root.dropElement = null;
30-
}
31-
}, false);
32-
33-
// dragStart事件为每个绑定元素进行初始化
34-
el.addEventListener('dragstart', ()=>{
35-
// el or binding.instance or vnode.context
36-
changeDragState(el, el.id, 'true', 'true', 'false', 'false', 'false', 'false');
37-
binding.instance.$root.identity = el.id;
38-
el.dataset.dragArea = el.parentNode.className;
39-
}, false);
40-
},
413
};
Lines changed: 0 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,3 @@
1-
import { changeDragState } from './utils';
2-
31
export default {
4-
/**
5-
*
6-
* @param el
7-
* @description
8-
* dragOver:
9-
* 1、生成与清除阴影的时机
10-
* 1.1、生成时机(只生成一次): dragFlag === true && dragOverFlag === true
11-
* drop:
12-
* 1、完成放的操作
13-
* 1.1、清除相应的阴影
14-
*/
15-
mounted(el: HTMLElement, binding: unknown): void {
16-
// dragenter/dragover/dragend/drop
17-
el.addEventListener('dragover', (event: DragEvent) => {
18-
event.preventDefault();
19-
const dragId = binding.instance.$root.identity;
20-
changeDragState(document.getElementById(dragId), dragId, 'true', 'false', 'true', 'false', 'false', 'false');
21-
document.getElementById(dragId).dataset.dropArea = [...el.childNodes][1].className;
22-
}, false);
232

24-
// 新增两个标识解决战斗,即dragStart区域、drop区域、sortableDrop区域
25-
el.addEventListener('drop', (event: DragEvent) => {
26-
event.preventDefault();
27-
const dragId = binding.instance.$root.identity;
28-
document.getElementById(dragId).dataset.parent = 'not-sortable-drop-area';
29-
if (document.getElementById(dragId).dataset.dropArea === document.getElementById(dragId).dataset.dragArea){
30-
return;
31-
}
32-
// 如何定义可放置区域这个问题得商榷一下
33-
const childrenArr = [...Array.from(el.children)[1].children];
34-
if (childrenArr.length > 0){
35-
for (let index = 0; index < childrenArr.length; index++){
36-
const childrenYRange = childrenArr[index].getBoundingClientRect().top + childrenArr[index].offsetHeight / 2;
37-
if (parseFloat(event.clientY) < parseFloat(childrenYRange)){
38-
el.children[1].insertBefore(document.getElementById(dragId), childrenArr[index]);
39-
break;
40-
}
41-
if (index === childrenArr.length-1){
42-
el.children[1].appendChild(document.getElementById(dragId));
43-
}
44-
}
45-
}else {
46-
el.childNodes[1].appendChild(document.getElementById(dragId));
47-
}
48-
});
49-
},
503
};
Lines changed: 134 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,146 @@
1-
import { SHADOW_ID } from './const';
2-
import {
3-
createInsertSortableShadow,
4-
judgeMouseIsInSortableArea,
5-
exchangeShadowPosition,
6-
sameOriginExchangeElementPosition,
7-
} from './utils';
8-
1+
import { matches } from './utils';
92

103
export default {
11-
/**
12-
*
13-
* @param el
14-
* @description
15-
* 此命令用于将元素变为可放置的元素并且支持排序
16-
* 功能分析
17-
* 1、非自身区域内拖动,生成shadow
18-
* 2、自身区域内拖动,不生成shadow
19-
* 实现分析(根据ng-devui)
20-
* shadow的生成规则
21-
* shadow的生成位置
22-
* 待思考问题
23-
* 1、整个拖拽过程中,是否有必要添加节流防抖?
24-
*/
25-
mounted(el: HTMLElement, binding: unknown): void {
26-
const self = el;
27-
el.addEventListener('dragover', function (event: DragEvent){
28-
event.preventDefault();
29-
const dragId = binding.instance.$root.identity;
30-
if (document.getElementById(dragId)?.dataset.parent === 'sortable-drop-area'){
31-
// 说明此时是同源操作(不需要生成shadow)
32-
// sameOriginExchangeElementPosition(event, [...dropArea.children], dragId, dropArea);
33-
return;
4+
mounted(el: HTMLElement, binding: any): void {
5+
let sourceElement: HTMLElement;
6+
let sourceIndex: number;
7+
let targetIndex: number;
8+
let mouseoverElement: HTMLElement;
9+
let isDrop: boolean;
10+
11+
const canDrag = (): boolean => {
12+
if (binding?.value?.handle) {
13+
const handleSelector = binding?.value?.handle;
14+
let element = mouseoverElement;
15+
while (element !== el) {
16+
if (matches(element, handleSelector)) {
17+
return true;
18+
}
19+
element = element.parentNode as HTMLElement;
20+
}
21+
return false;
3422
}
35-
// 需要判定是否存在阴影,否则会出现严重的抖动情况
36-
if (!document.getElementById(SHADOW_ID) && [...self.childNodes[1].children].length === 0){
37-
createInsertSortableShadow([...self.childNodes][1], event, dragId);
38-
} else if ([...self.childNodes[1].children].length >= 1){
39-
// 说明此时想要进行换位操作
40-
// 需要得到此时shadow的位置,遇到shadow则跳过,否则当鼠标出现在shadow上时,会出现严重的抖动操作
41-
exchangeShadowPosition(event, [...self.childNodes[1].children], dragId, self.childNodes[1]);
23+
return true;
24+
};
25+
26+
const getCurrentTarget = (event: DragEvent): number => {
27+
let index = -1;
28+
let element: any = event.target;
29+
while (element !== el) {
30+
if (element.parentNode === el) {
31+
index = Array.from(el.children).indexOf(element);
32+
}
33+
element = element.parentNode;
4234
}
35+
return index;
36+
};
37+
38+
const getDragClass = (): string => {
39+
return binding?.value?.dragClass || 'devui-drag-item';
40+
};
41+
42+
const emitEvent = (funcName: string, event: any): void => {
43+
if (binding?.value[funcName]) {
44+
binding?.value[funcName](event);
45+
}
46+
};
47+
48+
el.addEventListener('mouseover', (event: MouseEvent) => {
49+
mouseoverElement = event.target as HTMLElement;
50+
});
51+
52+
Array.from(el.children).forEach((element: any) => {
53+
element.setAttribute('draggable', 'true');
54+
element.addEventListener('dragstart', (event: DragEvent): void => {
55+
if (canDrag()) {
56+
isDrop = false;
57+
sourceElement = element;
58+
sourceIndex = Array.from(el.children).indexOf(sourceElement);
59+
setTimeout(() => {
60+
sourceElement.classList.add(getDragClass());
61+
});
62+
} else {
63+
event.preventDefault();
64+
event.stopPropagation();
65+
}
66+
emitEvent('dragStart', event);
67+
});
4368
});
44-
el.addEventListener('drop', function (event: DragEvent){
45-
// 获取可放置区域
46-
const dropArea = [...el.childNodes][1];
47-
const dragId = binding.instance.$root.identity;
48-
if (document.getElementById(dragId)?.dataset.parent === 'sortable-drop-area'){
49-
// 说明是同源(不产生shadow,直接替换)
50-
sameOriginExchangeElementPosition(event, [...dropArea.children], dragId, dropArea);
51-
return;
69+
70+
el.addEventListener('dragenter', function (event: DragEvent) {
71+
emitEvent('dragEnter', event);
72+
});
73+
74+
el.addEventListener('dragover', function (event: DragEvent) {
75+
const currentIndex = Array.from(el.children).indexOf(sourceElement);
76+
const toIndex = getCurrentTarget(event);
77+
78+
if (currentIndex !== -1 && toIndex !== -1) { // 这里的算法可能是有问题的
79+
if (currentIndex > toIndex) {
80+
el.removeChild(sourceElement);
81+
el.insertBefore(sourceElement, el.children[toIndex]);
82+
targetIndex = toIndex;
83+
} else if (currentIndex < toIndex) {
84+
el.removeChild(sourceElement);
85+
if (el.children[toIndex]) {
86+
el.insertBefore(sourceElement, el.children[toIndex]);
87+
targetIndex = toIndex;
88+
} else {
89+
el.appendChild(sourceElement);
90+
targetIndex = el.children.length - 1;
91+
}
92+
}
93+
binding?.value?.dragover && binding?.value?.dragover(event);
5294
}
53-
// 判断鼠标是否处于drop区域
54-
if (document.getElementById(SHADOW_ID)){
55-
dropArea.replaceChild(document.getElementById(dragId), document.getElementById(SHADOW_ID));
56-
if (document.getElementById(dragId)){
57-
document.getElementById(dragId).dataset.parent = 'sortable-drop-area';
95+
96+
event.preventDefault();
97+
emitEvent('dragOver', event);
98+
});
99+
100+
el.addEventListener('dragleave', function (event: Event) {
101+
emitEvent('dragLeave', event);
102+
});
103+
104+
el.addEventListener('drop', function (event: DragEvent) {
105+
isDrop = true;
106+
if (binding?.value?.list) {
107+
const list = binding?.value?.list;
108+
const item = list[sourceIndex];
109+
list.splice(sourceIndex, 1);
110+
list.splice(targetIndex, 0, item);
111+
if (binding?.value?.drop) {
112+
emitEvent('drop', {
113+
event: event,
114+
list: list,
115+
fromIndex: sourceIndex,
116+
targetIndex: targetIndex,
117+
});
58118
}
59119
}
60120
});
61-
// 主要用来移除shadow
62-
el.addEventListener('dragleave', function (event: Event){
63-
const dropArea = [...el.childNodes][1];
64-
if (document.getElementById(SHADOW_ID) && !judgeMouseIsInSortableArea(event, el)){
65-
dropArea.removeChild(document.getElementById(SHADOW_ID));
121+
122+
el.addEventListener('dragend', function (event: DragEvent) {
123+
if (!isDrop) {
124+
if (sourceIndex !== -1 && targetIndex !== -1) {
125+
if (targetIndex > sourceIndex) {
126+
el.removeChild(sourceElement);
127+
el.insertBefore(sourceElement, el.children[sourceIndex]);
128+
} else if (sourceIndex > targetIndex) {
129+
el.removeChild(sourceElement);
130+
if (el.children[sourceIndex]) {
131+
el.insertBefore(sourceElement, el.children[sourceIndex]);
132+
} else {
133+
el.appendChild(sourceElement);
134+
}
135+
}
136+
}
66137
}
138+
sourceIndex = -1;
139+
targetIndex = -1;
140+
setTimeout(()=> {
141+
sourceElement.classList.remove(getDragClass());
142+
});
143+
emitEvent('dragEnd', event);
67144
});
68-
}
145+
},
69146
};

0 commit comments

Comments
 (0)