167 lines
4.9 KiB
JavaScript
167 lines
4.9 KiB
JavaScript
var DraggableList = (function () {
|
|
'use strict';
|
|
|
|
/* eslint-env browser */
|
|
const CLS_TRANSFORMED = 'draggable-list-transformed';
|
|
function posToIndex(rects, startIndex, y, bound) {
|
|
if (y < rects[0].top && bound) return startIndex;
|
|
|
|
for (let i = 0; i < startIndex; i++) {
|
|
if (rects[i].bottom < y) continue;
|
|
return i;
|
|
}
|
|
|
|
if (y > rects[rects.length - 1].bottom && bound) return startIndex;
|
|
|
|
for (let i = rects.length - 1; i > startIndex; i--) {
|
|
if (rects[i].top > y) continue;
|
|
return i;
|
|
}
|
|
|
|
return startIndex;
|
|
}
|
|
function applyTransform(list, startIndex, oldIndex, newIndex, len) {
|
|
if (newIndex > oldIndex) {
|
|
transform(false, oldIndex, Math.min(startIndex - 1, newIndex - 1));
|
|
|
|
if (startIndex < list.length - 1) {
|
|
transform(true, Math.max(oldIndex + 1, startIndex + 1), newIndex, "translateY(".concat(-len, "px)"));
|
|
}
|
|
} else {
|
|
transform(false, Math.max(startIndex + 1, newIndex + 1), oldIndex);
|
|
|
|
if (startIndex > 0) {
|
|
transform(true, newIndex, Math.min(oldIndex - 1, startIndex - 1), "translateY(".concat(len, "px)"));
|
|
}
|
|
}
|
|
|
|
function transform(state, p, q, style) {
|
|
for (let i = p; i <= q; i++) {
|
|
if (state && !list[i].classList.contains(CLS_TRANSFORMED)) {
|
|
list[i].classList.add(CLS_TRANSFORMED);
|
|
list[i].style.transform = style;
|
|
} else if (!state && list[i].classList.contains(CLS_TRANSFORMED)) {
|
|
list[i].classList.remove(CLS_TRANSFORMED);
|
|
list[i].style = '';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function DraggableList(el, {
|
|
bound,
|
|
scrollContainer
|
|
} = {}) {
|
|
for (const c of el.children) {
|
|
c.draggable = true;
|
|
}
|
|
|
|
new MutationObserver(records => {
|
|
for (const r of records) {
|
|
for (const n of r.addedNodes) {
|
|
n.draggable = true;
|
|
}
|
|
}
|
|
}).observe(el, {
|
|
childList: true
|
|
});
|
|
let startPos = null;
|
|
let startIndex = 0;
|
|
let dragOverIndex = 0;
|
|
let dragOverPos = null;
|
|
let rects = [];
|
|
let dragTarget = null;
|
|
let dropped = false;
|
|
let itemSize = 0;
|
|
el.addEventListener('dragstart', e => {
|
|
if (e.target.parentNode !== el) return;
|
|
dragTarget = e.target;
|
|
dropped = false;
|
|
const scrollLeft = scrollContainer ? scrollContainer.scrollLeft : 0;
|
|
const scrollTop = scrollContainer ? scrollContainer.scrollTop : 0;
|
|
startPos = {
|
|
x: e.pageX + scrollLeft,
|
|
y: e.pageY + scrollTop
|
|
};
|
|
startIndex = [...el.children].indexOf(e.target);
|
|
dragOverIndex = startIndex;
|
|
dragOverPos = startPos;
|
|
rects = [...el.children].map(el => {
|
|
const r = el.getBoundingClientRect();
|
|
return {
|
|
top: r.top + window.scrollY + scrollTop,
|
|
bottom: r.bottom + window.scrollY + scrollTop
|
|
};
|
|
});
|
|
itemSize = startIndex + 1 < rects.length ? rects[startIndex + 1].top - rects[startIndex].top : startIndex > 0 ? rects[startIndex].bottom - rects[startIndex - 1].bottom : 0;
|
|
dragTarget.classList.add('draggable-list-target');
|
|
el.classList.add('draggable-list-dragging');
|
|
dispatch(e, 'd:dragstart');
|
|
});
|
|
el.addEventListener('dragenter', e => {
|
|
if (dragTarget) {
|
|
e.preventDefault();
|
|
dispatch(e, 'd:dragmove');
|
|
}
|
|
});
|
|
el.addEventListener('dragover', e => {
|
|
if (!dragTarget) return;
|
|
e.preventDefault();
|
|
const scrollLeft = scrollContainer ? scrollContainer.scrollLeft : 0;
|
|
const scrollTop = scrollContainer ? scrollContainer.scrollTop : 0;
|
|
const newPos = {
|
|
x: e.pageX + scrollLeft,
|
|
y: e.pageY + scrollTop
|
|
};
|
|
const newIndex = posToIndex(rects, startIndex, newPos.y, bound);
|
|
applyTransform(el.children, startIndex, dragOverIndex, newIndex, itemSize);
|
|
dragOverIndex = newIndex;
|
|
dragOverPos = newPos;
|
|
dispatch(e, 'd:dragmove');
|
|
});
|
|
document.addEventListener('dragend', e => {
|
|
if (!dragTarget) return;
|
|
|
|
for (const c of el.children) {
|
|
c.classList.remove(CLS_TRANSFORMED);
|
|
c.style = '';
|
|
}
|
|
|
|
dragTarget.classList.remove('draggable-list-target');
|
|
el.classList.remove('draggable-list-dragging');
|
|
dispatch(e, 'd:dragend', {
|
|
originalIndex: startIndex,
|
|
spliceIndex: dragOverIndex,
|
|
insertBefore: dragOverIndex < startIndex ? el.children[dragOverIndex] : el.children[dragOverIndex + 1],
|
|
dropped
|
|
});
|
|
dragTarget = null;
|
|
});
|
|
el.addEventListener('drop', e => {
|
|
if (dragTarget) {
|
|
dropped = true;
|
|
e.preventDefault();
|
|
}
|
|
});
|
|
|
|
function dispatch(e, name, props) {
|
|
const detail = {
|
|
origin: e,
|
|
startPos,
|
|
currentPos: dragOverPos,
|
|
dragTarget
|
|
};
|
|
|
|
if (props) {
|
|
Object.assign(detail, props);
|
|
}
|
|
|
|
el.dispatchEvent(new CustomEvent(name, {
|
|
detail
|
|
}));
|
|
}
|
|
}
|
|
|
|
return DraggableList;
|
|
|
|
})();
|