Restructure code
This commit is contained in:
214
mixins/tree-dnd.js
Normal file
214
mixins/tree-dnd.js
Normal file
@@ -0,0 +1,214 @@
|
||||
export const TreeDnDMixin = (superClass) => class TreeDnDMixin extends superClass {
|
||||
_onDragStart(item, e) {
|
||||
if (!this.allowDragAndDrop) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
this._dragSource = item;
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
|
||||
const row = e.currentTarget;
|
||||
const clone = document.createElement('div');
|
||||
clone.classList.add('drag-clone');
|
||||
clone.textContent = row.textContent;
|
||||
this.shadowRoot.appendChild(clone);
|
||||
|
||||
e.dataTransfer.setDragImage(clone, 0, 0);
|
||||
|
||||
setTimeout(() => {
|
||||
clone.remove();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
_onDragEnd(item, e) {
|
||||
this._dragSource = null;
|
||||
}
|
||||
|
||||
_onDragOver(item, e) {
|
||||
if (!this.allowDragAndDrop || !this._dragSource) return;
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
const row = e.currentTarget;
|
||||
const rect = row.getBoundingClientRect();
|
||||
const y = e.clientY - rect.top;
|
||||
const h = rect.height;
|
||||
|
||||
let position = 'inside';
|
||||
if (y < h * 0.25) position = 'before';
|
||||
else if (y > h * 0.75) position = 'after';
|
||||
|
||||
if (this._dragSource.key === item.key) {
|
||||
this._clearDragClasses(row);
|
||||
e.dataTransfer.dropEffect = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
if (position === 'after' && item.expanded && item.hasChildren) {
|
||||
this._clearDragClasses(row);
|
||||
e.dataTransfer.dropEffect = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
const { target: logicalTarget, position: logicalPosition, depth: dragDepth } = this._resolveDropTarget(item, position, e);
|
||||
|
||||
if (this._isNoOp(this._dragSource, logicalTarget, logicalPosition)) {
|
||||
this._clearDragClasses(row);
|
||||
e.dataTransfer.dropEffect = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.canDrop && !this.canDrop(this._dragSource, logicalTarget, logicalPosition)) {
|
||||
this._clearDragClasses(row);
|
||||
e.dataTransfer.dropEffect = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
e.dataTransfer.dropEffect = 'move';
|
||||
|
||||
this._clearDragClasses(row);
|
||||
row.classList.add(`drag-over-${position}`);
|
||||
if (dragDepth !== undefined) {
|
||||
row.style.setProperty('--drag-depth', dragDepth);
|
||||
}
|
||||
}
|
||||
|
||||
_onDragLeave(item, e) {
|
||||
const row = e.currentTarget;
|
||||
if (row.contains(e.relatedTarget)) return;
|
||||
this._clearDragClasses(row);
|
||||
}
|
||||
|
||||
_onDrop(item, e) {
|
||||
if (!this.allowDragAndDrop || !this._dragSource) return;
|
||||
e.preventDefault();
|
||||
const row = e.currentTarget;
|
||||
this._clearDragClasses(row);
|
||||
|
||||
const rect = row.getBoundingClientRect();
|
||||
const y = e.clientY - rect.top;
|
||||
const h = rect.height;
|
||||
|
||||
let position = 'inside';
|
||||
if (y < h * 0.25) position = 'before';
|
||||
else if (y > h * 0.75) position = 'after';
|
||||
|
||||
if (this._dragSource.key === item.key) return;
|
||||
|
||||
if (position === 'after' && item.expanded && item.hasChildren) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { target: logicalTarget, position: logicalPosition } = this._resolveDropTarget(item, position, e);
|
||||
|
||||
if (this._isNoOp(this._dragSource, logicalTarget, logicalPosition)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.canDrop && !this.canDrop(this._dragSource, logicalTarget, logicalPosition)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.dispatchEvent(new CustomEvent('node-drop', {
|
||||
detail: {
|
||||
source: this._dragSource,
|
||||
target: logicalTarget,
|
||||
position: logicalPosition,
|
||||
originalEvent: e
|
||||
},
|
||||
bubbles: true,
|
||||
composed: true
|
||||
}));
|
||||
|
||||
this._dragSource = null;
|
||||
}
|
||||
|
||||
_isNoOp(source, target, position) {
|
||||
if (!source || !target) return true;
|
||||
if (source.key === target.key) return true;
|
||||
|
||||
const srcPathStr = source.path.slice(0, -1).join('/');
|
||||
const tgtPathStr = target.path.slice(0, -1).join('/');
|
||||
|
||||
if (srcPathStr !== tgtPathStr) return false;
|
||||
|
||||
const siblings = this._getSiblings(source.path);
|
||||
if (!siblings) return false;
|
||||
|
||||
const srcIdx = siblings.indexOf(source.node);
|
||||
const tgtIdx = siblings.indexOf(target.node);
|
||||
|
||||
if (srcIdx === -1 || tgtIdx === -1) return false;
|
||||
|
||||
if (position === 'before') {
|
||||
if (srcIdx === tgtIdx - 1) return true;
|
||||
}
|
||||
if (position === 'after') {
|
||||
if (srcIdx === tgtIdx + 1) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
_getSiblings(path) {
|
||||
if (path.length === 1) return this.roots;
|
||||
|
||||
let nodes = this.roots;
|
||||
for (let i = 0; i < path.length - 1; i++) {
|
||||
const slug = path[i];
|
||||
const node = nodes.find((n, idx) => (n.slug ?? `${idx}`) === slug);
|
||||
if (!node) return null;
|
||||
nodes = node.children || [];
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
_resolveDropTarget(item, position, e) {
|
||||
if (!this._flatItems) return { target: item, position };
|
||||
|
||||
const index = this._flatItems.findIndex(i => i.key === item.key);
|
||||
if (index === -1) return { target: item, position };
|
||||
|
||||
const currentItem = this._flatItems[index];
|
||||
|
||||
if (position === 'after' && e) {
|
||||
const nextItem = this._flatItems[index + 1];
|
||||
const currentDepth = currentItem.depth;
|
||||
const nextDepth = nextItem ? nextItem.depth : -1;
|
||||
|
||||
if (nextDepth < currentDepth) {
|
||||
const row = e.currentTarget;
|
||||
const style = getComputedStyle(row);
|
||||
const indentVal = style.getPropertyValue('--tp-tree-indent').trim();
|
||||
const indent = indentVal ? parseInt(indentVal, 10) : 16;
|
||||
const padding = parseFloat(style.paddingLeft) || 8;
|
||||
|
||||
const rowRect = row.getBoundingClientRect();
|
||||
const mouseX = e.clientX - rowRect.left;
|
||||
|
||||
let targetDepth = Math.floor((mouseX - padding) / indent);
|
||||
|
||||
if (targetDepth < nextDepth) targetDepth = nextDepth;
|
||||
if (targetDepth > currentDepth) targetDepth = currentDepth;
|
||||
|
||||
if (targetDepth < currentDepth) {
|
||||
let ancestor = currentItem;
|
||||
for (let i = index; i >= 0; i--) {
|
||||
if (this._flatItems[i].depth === targetDepth) {
|
||||
ancestor = this._flatItems[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return { target: ancestor, position: 'after', depth: targetDepth };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { target: currentItem, position };
|
||||
}
|
||||
|
||||
_clearDragClasses(row) {
|
||||
row.classList.remove('drag-over-inside', 'drag-over-before', 'drag-over-after');
|
||||
row.style.removeProperty('--drag-depth');
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user