vue-easy-dnd
Version:
Easy-DnD is a drag and drop implementation for Vue 3 that uses only standard mouse events instead of the HTML5 drag and drop API, which is [impossible to work with](https://www.quirksmode.org/blog/archives/2009/09/the_html5_drag.html). Think of it as a wa
107 lines (100 loc) • 3.89 kB
JavaScript
export default class Grid {
reference;
referenceOriginalPosition;
magnets = [];
constructor (collection, upToIndex, direction, fromIndex) {
this.reference = collection.item(0).parentNode;
this.referenceOriginalPosition = {
x: this.reference.getBoundingClientRect().left - this.reference.scrollLeft,
y: this.reference.getBoundingClientRect().top - this.reference.scrollTop,
};
let index = 0;
for (const child of collection) {
if (index > upToIndex) break;
const rect = child.getBoundingClientRect();
const hasNestedDrop = child.classList.contains('dnd-drop') || child.getElementsByClassName('dnd-drop').length > 0;
let horizontal = false;
if (hasNestedDrop) {
if (direction === 'auto') {
// Auto mode not supported for now. Row or column must be defined explicitly if there are nested drop lists.
throw 'Easy-DnD error : a drop list is missing one of these attributes : \'row\' or \'column\'.';
}
else {
horizontal = direction === 'row';
}
}
if (fromIndex === null) {
// Inserting mode.
this.magnets.push(hasNestedDrop ? this.before(rect, horizontal) : this.center(rect));
}
else {
// Reordering mode.
this.magnets.push(hasNestedDrop ? (
fromIndex < index ? this.after : this.before
)(rect, horizontal) : this.center(rect));
}
// Debug : show magnets :
//document.body.insertAdjacentHTML("beforeend", "<div style='background-color: red; position: fixed; width: 1px; height: 1px; top:" + this.magnets[index].y + "px; left:" + this.magnets[index].x + "px;' ></div>")
index++;
}
}
/**
* Returns the center of the rectangle.
*/
center (rect) {
return {
x: rect.left + rect.width / 2,
y: rect.top + rect.height / 2
};
}
/**
* When horizontal is true / false, returns middle of the left / top side of the rectangle.
*/
before (rect, horizontal) {
return horizontal ? {
x: rect.left,
y: rect.top + rect.height / 2
} : {
x: rect.left + rect.width / 2,
y: rect.top
};
}
/**
* When horizontal is true / false, returns middle of the right / bottom side of the rectangle.
*/
after (rect, horizontal) {
return horizontal ? {
x: rect.left + rect.width,
y: rect.top + rect.height / 2
} : {
x: rect.left + rect.width / 2,
y: rect.top + rect.height
};
}
/**
* In case the user scrolls during the drag, the position of the magnets are not what they used to be when the drag
* started. A correction must be applied that takes into account the amount of scroll. This correction is the
* difference between the current position of the parent element and its position when the drag started.
*/
correction () {
return {
x: this.reference.getBoundingClientRect().left - this.reference.scrollLeft - this.referenceOriginalPosition.x,
y: this.reference.getBoundingClientRect().top - this.reference.scrollTop - this.referenceOriginalPosition.y,
};
}
closestIndex (position) {
const x = position.x - this.correction().x;
const y = position.y - this.correction().y;
let minDist = 999999;
let index = -1;
for (let i = 0; i < this.magnets.length; i++) {
const magnet = this.magnets[i];
const dist = Math.sqrt(Math.pow(magnet.x - x, 2) + Math.pow(magnet.y - y, 2));
if (dist < minDist) {
minDist = dist;
index = i;
}
}
return index;
}
}