/** * Creates a reorderable list from any `container` and viable list item selector. * * This function is absolute magic and I love it * * Example: * ```html * * ``` * ```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(); }); }