| 1 | // fixtures/strip-reorder.mjs — reordering slides from the strip, three |
| 2 | // ways to one op (move_slide): HTML5 drag and drop of an item, Alt+Up |
| 3 | // / Alt+Down on a focused item's link, and the item menu's Move up / |
| 4 | // Move down (the Slide section's buttons are the same commands). The |
| 5 | // drop target is the item under the pointer, before or after it by |
| 6 | // which half was crossed, and said with a data-drop attribute the |
| 7 | // stylesheet draws as a line. |
| 8 | // |
| 9 | // The pure part is dropTarget: given the item ids in order, the |
| 10 | // dragged id, the item dropped on and the half, the `after` a |
| 11 | // move_slide wants (null for first) — or undefined when the drop |
| 12 | // changes nothing. |
| 13 | |
| 14 | /** The `after` for dropping `id` before/after `on`: null puts it first; undefined means no move. */ |
| 15 | export function dropTarget (ids, id, on, half) { |
| 16 | if (id === on) return undefined |
| 17 | const rest = ids.filter(x => x !== id) |
| 18 | const i = rest.indexOf(on) |
| 19 | if (i < 0) return undefined |
| 20 | const at = half === 'before' ? i : i + 1 |
| 21 | const after = at === 0 ? null : rest[at - 1] |
| 22 | const was = ids.indexOf(id) |
| 23 | const now = rest.slice(0, at).length |
| 24 | if (was === now) return undefined |
| 25 | return after |
| 26 | } |
| 27 | |
| 28 | /** move_slide from a drop, or null. */ |
| 29 | export function dropOp (ids, id, on, half) { |
| 30 | const after = dropTarget(ids, id, on, half) |
| 31 | if (after === undefined) return null |
| 32 | return after === null ? { op: 'move_slide', id } : { op: 'move_slide', id, after } |
| 33 | } |
| 34 | |
| 35 | /** |
| 36 | * Wires drag and drop and the keyboard onto the strip's <ol>. |
| 37 | * getIDs() is the slide order; onMove(op) applies a move_slide. The |
| 38 | * item is what is dragged (draggable="true" in the markup); the link |
| 39 | * inside it is what takes the keys. |
| 40 | */ |
| 41 | export function mountReorder ({ root, getIDs, onMove, onNudge }) { |
| 42 | let dragging = null |
| 43 | |
| 44 | const itemOf = el => el && el.closest('[slides-strip-item][data-slide-id]') |
| 45 | function clearMarks () { |
| 46 | for (const li of root.querySelectorAll('[data-drop]')) delete li.dataset.drop |
| 47 | } |
| 48 | |
| 49 | root.addEventListener('dragstart', e => { |
| 50 | const li = itemOf(e.target) |
| 51 | if (!li) return |
| 52 | dragging = li.dataset.slideId |
| 53 | li.dataset.dragging = 'true' |
| 54 | e.dataTransfer.effectAllowed = 'move' |
| 55 | e.dataTransfer.setData('text/plain', dragging) |
| 56 | }) |
| 57 | root.addEventListener('dragover', e => { |
| 58 | const li = itemOf(e.target) |
| 59 | if (!li || !dragging) return |
| 60 | e.preventDefault() |
| 61 | e.dataTransfer.dropEffect = 'move' |
| 62 | const r = li.getBoundingClientRect() |
| 63 | const vertical = getComputedStyle(root).flexDirection === 'column' |
| 64 | const before = vertical ? e.clientY < r.top + r.height / 2 : e.clientX < r.left + r.width / 2 |
| 65 | clearMarks() |
| 66 | li.dataset.drop = before ? 'before' : 'after' |
| 67 | }) |
| 68 | root.addEventListener('dragleave', e => { |
| 69 | if (!root.contains(e.relatedTarget)) clearMarks() |
| 70 | }) |
| 71 | root.addEventListener('drop', e => { |
| 72 | const li = itemOf(e.target) |
| 73 | if (!li || !dragging) return |
| 74 | e.preventDefault() |
| 75 | const op = dropOp(getIDs(), dragging, li.dataset.slideId, li.dataset.drop || 'after') |
| 76 | clearMarks() |
| 77 | if (op) onMove(op) |
| 78 | }) |
| 79 | root.addEventListener('dragend', () => { |
| 80 | for (const li of root.querySelectorAll('[data-dragging]')) delete li.dataset.dragging |
| 81 | clearMarks() |
| 82 | dragging = null |
| 83 | }) |
| 84 | |
| 85 | root.addEventListener('keydown', e => { |
| 86 | if (!e.altKey || (e.key !== 'ArrowUp' && e.key !== 'ArrowDown')) return |
| 87 | const li = itemOf(e.target) |
| 88 | if (!li) return |
| 89 | e.preventDefault() |
| 90 | onNudge(li.dataset.slideId, e.key === 'ArrowUp' ? 'up' : 'down') |
| 91 | }) |
| 92 | } |
| 93 | |