Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
47 lines
2.3 KiB
JavaScript
47 lines
2.3 KiB
JavaScript
// Reordering a row of id-tagged children by drag, shared by every element that
|
|
// has one (the scene strip, the timeline track).
|
|
//
|
|
// Delegated on the container: cells are torn down and rebuilt on every redraw,
|
|
// and listeners bound to them would have to be reattached every time. One set
|
|
// here survives all of that.
|
|
//
|
|
// The dragged item's id lives on `dataTransfer`, not in a closure variable. A
|
|
// closure var tracks by NOTHING the browser understands, so nothing keeps it in
|
|
// step with the actual drag session: `dragend` fires on the ORIGINAL source
|
|
// element, and a redraw mid-drag (an unrelated remove/undo firing on its own
|
|
// input channel while the mouse is still held down) can detach that element
|
|
// from the tree -- its `dragend` then never bubbles here, and a closure var set
|
|
// at dragstart is never cleared, ready to be picked up by the next unrelated
|
|
// drop that lands on this row from somewhere else entirely. `dataTransfer` has
|
|
// no such gap: it belongs to the actual OS-level drag session, unaffected by
|
|
// which DOM node currently receives the events, and a drop from any OTHER
|
|
// session simply never had this MIME type set.
|
|
const MIME = "text/x-funpack-reorder";
|
|
|
|
/** wireDragReorder(node, ".cx-cell-selector", (draggedId, targetId) => {}) */
|
|
export function wireDragReorder(node, cellSelector, onReorder) {
|
|
const cellOf = (e) => e.target.closest(cellSelector);
|
|
node.addEventListener("dragstart", (e) => {
|
|
const cell = cellOf(e);
|
|
if (!cell || !e.dataTransfer) return;
|
|
e.dataTransfer.effectAllowed = "move";
|
|
e.dataTransfer.setData(MIME, cell.dataset.id || "");
|
|
cell.classList.add("cx-dragging");
|
|
});
|
|
node.addEventListener("dragend", (e) => { cellOf(e)?.classList.remove("cx-dragging"); });
|
|
node.addEventListener("dragover", (e) => {
|
|
// `getData` only returns the real value on "drop" (a browser security
|
|
// restriction during drag) -- `types` is what dragover can actually read.
|
|
if (e.dataTransfer && e.dataTransfer.types.includes(MIME)) e.preventDefault();
|
|
});
|
|
node.addEventListener("drop", (e) => {
|
|
if (!e.dataTransfer) return;
|
|
const draggedId = e.dataTransfer.getData(MIME);
|
|
if (!draggedId) return;
|
|
e.preventDefault();
|
|
const cell = cellOf(e);
|
|
const targetId = cell ? cell.dataset.id : null;
|
|
if (onReorder && targetId && targetId !== draggedId) onReorder(draggedId, targetId);
|
|
});
|
|
}
|