const UNSEEN = ["script", "style", "template", "link", "meta", "noscript"];
const LEAVES = ["p", "h1", "h2", "h3", "h4", "h5", "h6", "a", "button", "label", "span", "strong", "em", "b", "i",
"small", "code", "img", "svg", "picture", "video", "canvas", "iframe", "input", "textarea", "select"];
const KINDS = ["ul", "ol", "menu", "dl", "table", "thead", "tbody", "tfoot", "tr", "select"];
const SHIFT_MS = 160;
const LIFT_PX = 4;
const EDGE_PX = 48;
const SETTLE_PX = 24;
const calm = matchMedia("(prefers-reduced-motion: reduce)");
const seen = (node) => node !== host && !UNSEEN.includes(node.localName) && getComputedStyle(node).display !== "none";
const blockOf = (node) =>
node.parentElement ? [...node.parentElement.children].filter((child) => child === node || seen(child)) : [node];
const kidsOf = (parent, node) => [...parent.children].filter((child) => child !== node && seen(child));
const placeOf = (node) => {
const all = blockOf(node);
return { at: all.indexOf(node) + 1, of: all.length };
};
const layoutBox = (node) => {
if (node.offsetLeft === undefined) return node.getBoundingClientRect();
const parent = node.offsetParent;
let [left, top] = [node.offsetLeft - scrollX, node.offsetTop - scrollY];
if (parent && parent !== document.body) {
const box = parent.getBoundingClientRect();
left = node.offsetLeft + box.left + parent.clientLeft - parent.scrollLeft;
top = node.offsetTop + box.top + parent.clientTop - parent.scrollTop;
}
return { left, top, width: node.offsetWidth, height: node.offsetHeight };
};
const glide = (nodes, change) => {
const from = nodes.map((node) => node.getBoundingClientRect());
change();
if (calm.matches) return;
nodes.forEach((node, index) => {
const to = node.getBoundingClientRect();
const [x, y] = [from[index].left - to.left, from[index].top - to.top];
if (!x && !y) return;
node.animate([{ transform: `translate(${x}px, ${y}px)` }, { transform: "translate(0, 0)" }],
{ duration: SHIFT_MS, easing: "cubic-bezier(.16, 1, .3, 1)", composite: "add" });
});
};
function moveInto(node, parent, at, dragged = false) {
const others = kidsOf(parent, node);
const index = Math.max(0, Math.min(at - 1, others.length));
const same = parent === node.parentElement;
if (same && (!others.length || index + 1 === placeOf(node).at)) return;
const moving = [...new Set([...others, ...blockOf(node)])];
glide(dragged ? moving.filter((child) => child !== node) : moving, () => {
if (index < others.length) others[index].before(node);
else if (others.length) others.at(-1).after(node);
else parent.append(node);
});
}
const moveTo = (node, at, dragged = false) => moveInto(node, node.parentElement, at, dragged);
const putBack = ({ node, parent, next }) => {
if (next && next.parentNode !== parent) return;
parent.insertBefore(node, next);
};
const placeIn = (parent, node, x, y) => {
const look = getComputedStyle(parent);
const row = (look.display.includes("flex") && !look.flexDirection.startsWith("column"))
|| (look.display.includes("grid") && look.gridTemplateColumns.split(" ").length > 1)
|| getComputedStyle(node).display.startsWith("inline");
const bounds = parent.getBoundingClientRect();
y = Math.max(bounds.top + 1, Math.min(y, bounds.bottom - 1));
const others = kidsOf(parent, node);
const index = others.findIndex((child) => {
const box = layoutBox(child);
return row ? y < box.top || (y <= box.top + box.height && x < box.left + box.width / 2) : y < box.top + box.height / 2;
});
return (index < 0 ? others.length : index) + 1;
};
const depthOf = (node) => {
let depth = 0;
for (; node.parentElement; node = node.parentElement) depth++;
return depth;
};
const blockUnder = (node, x, y, level) => {
const own = node.parentElement;
const takes = (block) => block === own || (block && ![document.body, document.documentElement].includes(block)
&& !LEAVES.includes(block.localName) && !node.contains(block) && kidsOf(block, node).length > 0
&& (KINDS.includes(own.localName) || KINDS.includes(block.localName) ? block.localName === own.localName : true));
const line = [];
const under = document.elementsFromPoint(x, y).find((child) => child !== host && !node.contains(child));
for (let at = under; at; at = at.parentElement) line.unshift(at);
if (takes(line[level])) return line[level];
const box = own.getBoundingClientRect();
const away = Math.max(box.left - x, x - box.right, box.top - y, y - box.bottom);
return away > SETTLE_PX && takes(line[level - 1]) ? line[level - 1] : own;
};
function grabNode(node, event, dragged) {
const box = layoutBox(node);
const start = { x: event.clientX, y: event.clientY };
const hold = { x: event.clientX - box.left, y: event.clientY - box.top };
const kept = keepInline(node, ["translate"]);
const level = depthOf(node.parentElement);
let [moving, last, frame, jumped] = [false, null, 0, null];
const place = ({ clientX: x, clientY: y }) => {
const settled = !jumped || Math.hypot(x - jumped.x, y - jumped.y) >= SETTLE_PX;
const block = settled ? blockUnder(node, x, y, level) : node.parentElement;
if (block !== node.parentElement) jumped = { x, y };
moveInto(node, block, placeIn(block, node, x, y), true);
const slot = layoutBox(node);
node.style.setProperty("translate", `${x - hold.x - slot.left}px ${y - hold.y - slot.top}px`, "important");
dragged(false);
};
const scroll = () => {
frame = 0;
const near = Math.min(last.clientY, innerHeight - last.clientY);
if (near >= EDGE_PX) return;
const before = scrollY;
scrollBy(0, Math.ceil((EDGE_PX - near) / 3) * (last.clientY < innerHeight / 2 ? -1 : 1));
if (scrollY === before) return;
place(last);
frame = requestAnimationFrame(scroll);
};
const follow = (move) => {
if (move.pointerId !== event.pointerId) return;
if (!moving && Math.hypot(move.clientX - start.x, move.clientY - start.y) < LIFT_PX) return;
moving = true;
last = move;
place(move);
if (!frame) frame = requestAnimationFrame(scroll);
};
const drop = (up) => {
if (up.pointerId !== event.pointerId) return;
removeEventListener("pointermove", follow, true);
removeEventListener("pointerup", drop, true);
removeEventListener("pointercancel", drop, true);
cancelAnimationFrame(frame);
if (!moving) return;
glide([node], () => putInline(node, kept));
dragged(true);
};
addEventListener("pointermove", follow, true);
addEventListener("pointerup", drop, true);
addEventListener("pointercancel", drop, true);
}