/**
* Creates a reorderable list from any `container` and viable list item selector.
*
* This function is absolute magic and I love it
*
* Example:
* ```html
*
* - Item 1
* - Item 2
* - Item 3
*
* ```
* ```js
* // javascript
* makeMagicList(document.getElementById("list"), "li");
* ```
*
* @param {HTMLElement} container The parent container to use as a list.
* @param {string} itemSelector The selector name of list item elements.
* @param {Function} callback A function to call after each reordering.
*/
export function makeMagicList(container, itemSelector, callback) {
if (!container)
throw new Error("container not provided");
if (!itemSelector)
throw new Error("itemSelector not provided");
container.querySelectorAll(itemSelector).forEach(item => {
item.draggable = true;
item.addEventListener("dragstart", () => { item.classList.add("moving") });
item.addEventListener("dragend", () => { item.classList.remove("moving") });
// dragging on inputs should take priority
item.querySelectorAll("input").forEach(el => {
el.addEventListener("mousedown", () => { item.draggable = false });
el.addEventListener("mouseup", () => { item.draggable = true });
el.addEventListener("dragstart", e => { e.stopPropagation() });
});
});
var lastCursorY;
container.addEventListener("dragover", event => {
const dragging = container.querySelector(itemSelector + ".moving");
if (!dragging) return;
let cursorY = event.touches ? event.touches[0].clientY : event.clientY;
// don't bother processing if we haven't moved
if (lastCursorY === cursorY) return
lastCursorY = cursorY;
// get the element positioned ahead of the cursor
const notMoving = [...container.querySelectorAll(itemSelector + ":not(.moving)")];
const afterElement = notMoving.reduce((previous, current) => {
const box = current.getBoundingClientRect();
const offset = cursorY - box.top - box.height / 2;
if (offset < 0 && offset > previous.offset)
return { offset: offset, element: current };
return previous;
}, { offset: Number.NEGATIVE_INFINITY }).element;
if (afterElement) {
container.insertBefore(dragging, afterElement);
} else {
container.appendChild(dragging);
}
if (callback) callback();
});
}