1
0
mirror of https://github.com/morris/vanilla-todo.git synced 2025-08-21 13:21:29 +02:00
Files
vanilla-todo/public/scripts/AppSortable.js
Morris Brodersen 6a640515b2 refactor state
2023-11-26 11:54:04 +01:00

146 lines
3.4 KiB
JavaScript

/**
* @param {HTMLElement} el
* @param {{
* direction?: 'horizontal' | 'vertical';
* }} options
*/
export function AppSortable(el, options) {
let placeholder;
let placeholderSource;
let currentIndex = -1;
const isBefore = options.direction === 'horizontal' ? isLeft : isAbove;
el.addEventListener('draggableStart', (e) =>
e.detail.image.addEventListener('draggableCancel', cleanUp),
);
el.addEventListener('draggableOver', (e) =>
maybeDispatchUpdate(calculateIndex(e.detail.image), e),
);
el.addEventListener('draggableLeave', (e) => maybeDispatchUpdate(-1, e));
el.addEventListener('draggableDrop', (e) =>
el.dispatchEvent(
new CustomEvent('sortableDrop', {
detail: buildDetail(e),
bubbles: true,
}),
),
);
el.addEventListener('sortableUpdate', (e) => {
if (!placeholder) {
e.detail.setPlaceholder(e.detail.originalEvent.detail.imageSource);
}
if (e.detail.index >= 0) {
insertPlaceholder(e.detail.index);
} else {
removePlaceholder();
}
removeByKey(e.detail.data.key);
});
el.addEventListener('sortableDrop', cleanUp);
function maybeDispatchUpdate(index, originalEvent) {
if (index !== currentIndex) {
currentIndex = index;
el.dispatchEvent(
new CustomEvent('sortableUpdate', {
detail: buildDetail(originalEvent),
bubbles: true,
}),
);
}
}
function cleanUp() {
removePlaceholder();
placeholder = null;
placeholderSource = null;
currentIndex = -1;
}
function buildDetail(e) {
const detail = {
data: e.detail.data,
index: currentIndex,
placeholder,
setPlaceholder: (source) => {
setPlaceholder(source);
detail.placeholder = placeholder;
},
originalEvent: e,
};
return detail;
}
function setPlaceholder(source) {
if (placeholderSource === source) return;
placeholderSource = source;
removePlaceholder();
placeholder = placeholderSource.cloneNode(true);
placeholder.classList.add('-placeholder');
placeholder.removeAttribute('data-key');
}
function insertPlaceholder(index) {
if (placeholder && el.children[index] !== placeholder) {
if (placeholder.parentNode === el) el.removeChild(placeholder);
el.insertBefore(placeholder, el.children[index]);
}
}
function removePlaceholder() {
placeholder?.parentNode?.removeChild(placeholder);
}
function removeByKey(key) {
for (let i = 0, l = el.children.length; i < l; ++i) {
const child = el.children[i];
if (child && child.dataset.key === key) {
el.removeChild(child);
}
}
}
function calculateIndex(image) {
if (el.children.length === 0) return 0;
const rect = image.getBoundingClientRect();
let p = 0;
for (let i = 0, l = el.children.length; i < l; ++i) {
const child = el.children[i];
if (isBefore(rect, child.getBoundingClientRect())) return i - p;
if (child === placeholder) p = 1;
}
return el.children.length - p;
}
function isAbove(rectA, rectB) {
return (
rectA.top + (rectA.bottom - rectA.top) / 2 <=
rectB.top + (rectB.bottom - rectB.top) / 2
);
}
function isLeft(rectA, rectB) {
return (
rectA.left + (rectA.right - rectA.left) / 2 <=
rectB.left + (rectB.right - rectB.left) / 2
);
}
}