// fixtures/strip-reorder.mjs — reordering slides from the strip, three // ways to one op (move_slide): HTML5 drag and drop of an item, Alt+Up // / Alt+Down on a focused item's link, and the item menu's Move up / // Move down (the Slide section's buttons are the same commands). The // drop target is the item under the pointer, before or after it by // which half was crossed, and said with a data-drop attribute the // stylesheet draws as a line. // // The pure part is dropTarget: given the item ids in order, the // dragged id, the item dropped on and the half, the `after` a // move_slide wants (null for first) — or undefined when the drop // changes nothing. /** The `after` for dropping `id` before/after `on`: null puts it first; undefined means no move. */ export function dropTarget (ids, id, on, half) { if (id === on) return undefined const rest = ids.filter(x => x !== id) const i = rest.indexOf(on) if (i < 0) return undefined const at = half === 'before' ? i : i + 1 const after = at === 0 ? null : rest[at - 1] const was = ids.indexOf(id) const now = rest.slice(0, at).length if (was === now) return undefined return after } /** move_slide from a drop, or null. */ export function dropOp (ids, id, on, half) { const after = dropTarget(ids, id, on, half) if (after === undefined) return null return after === null ? { op: 'move_slide', id } : { op: 'move_slide', id, after } } /** * Wires drag and drop and the keyboard onto the strip's
    . * getIDs() is the slide order; onMove(op) applies a move_slide. The * item is what is dragged (draggable="true" in the markup); the link * inside it is what takes the keys. */ export function mountReorder ({ root, getIDs, onMove, onNudge }) { let dragging = null const itemOf = el => el && el.closest('[slides-strip-item][data-slide-id]') function clearMarks () { for (const li of root.querySelectorAll('[data-drop]')) delete li.dataset.drop } root.addEventListener('dragstart', e => { const li = itemOf(e.target) if (!li) return dragging = li.dataset.slideId li.dataset.dragging = 'true' e.dataTransfer.effectAllowed = 'move' e.dataTransfer.setData('text/plain', dragging) }) root.addEventListener('dragover', e => { const li = itemOf(e.target) if (!li || !dragging) return e.preventDefault() e.dataTransfer.dropEffect = 'move' const r = li.getBoundingClientRect() const vertical = getComputedStyle(root).flexDirection === 'column' const before = vertical ? e.clientY < r.top + r.height / 2 : e.clientX < r.left + r.width / 2 clearMarks() li.dataset.drop = before ? 'before' : 'after' }) root.addEventListener('dragleave', e => { if (!root.contains(e.relatedTarget)) clearMarks() }) root.addEventListener('drop', e => { const li = itemOf(e.target) if (!li || !dragging) return e.preventDefault() const op = dropOp(getIDs(), dragging, li.dataset.slideId, li.dataset.drop || 'after') clearMarks() if (op) onMove(op) }) root.addEventListener('dragend', () => { for (const li of root.querySelectorAll('[data-dragging]')) delete li.dataset.dragging clearMarks() dragging = null }) root.addEventListener('keydown', e => { if (!e.altKey || (e.key !== 'ArrowUp' && e.key !== 'ArrowDown')) return const li = itemOf(e.target) if (!li) return e.preventDefault() onNudge(li.dataset.slideId, e.key === 'ArrowUp' ? 'up' : 'down') }) }