collin/anvil · 273fd302
fix: make board drag reliable on touch (document listeners, not capture)
Collin Richards · 2026-06-10 18:37 UTC · 273fd302197f146bb5d74bf62cc8c92d09e66e95 · parent 7e03cd50 · browse files
modifiedcrates/anvil-web/src/todomd.rs+31 −19
| ⋯ 694 unchanged lines | |||
| 695 | 695 | var url = board.dataset.moveUrl, tip = board.dataset.tip, csrf = board.dataset.csrf; | |
| 696 | 696 | var card = null, fromSection = null, grip = null, pid = null, moved = false; | |
| 697 | 697 | ||
| 698 | + | // The column whose box contains (x, y) — by geometry, so it works for both | |
| 699 | + | // the horizontal desktop layout and the stacked mobile layout, and doesn't | |
| 700 | + | // depend on elementFromPoint (which the dragged card can shadow). | |
| 701 | + | function colAt(x, y){ | |
| 702 | + | var cols = board.querySelectorAll('.col'); | |
| 703 | + | for (var i = 0; i < cols.length; i++){ | |
| 704 | + | var b = cols[i].getBoundingClientRect(); | |
| 705 | + | if (x >= b.left && x <= b.right && y >= b.top && y <= b.bottom) return cols[i]; | |
| 706 | + | } | |
| 707 | + | return null; | |
| 708 | + | } | |
| 698 | 709 | function afterCard(col, y){ | |
| 699 | 710 | var cards = [].slice.call(col.querySelectorAll('.card:not(.dragging)')); | |
| 700 | 711 | var best = null, bestOffset = -Infinity; | |
| ⋯ 5 unchanged lines | |||
| 706 | 717 | return best; | |
| 707 | 718 | } | |
| 708 | 719 | function onMove(e){ | |
| 709 | - | if (!card) return; | |
| 710 | - | e.preventDefault(); | |
| 720 | + | if (!card || e.pointerId !== pid) return; | |
| 721 | + | e.preventDefault(); // stop the page scrolling mid-drag | |
| 711 | 722 | moved = true; | |
| 712 | - | var under = document.elementFromPoint(e.clientX, e.clientY); | |
| 713 | - | var col = (under && under.closest && under.closest('.col')) || card.closest('.col'); | |
| 723 | + | var col = colAt(e.clientX, e.clientY) || card.closest('.col'); | |
| 714 | 724 | var after = afterCard(col, e.clientY); | |
| 715 | 725 | if (after == null) col.appendChild(card); | |
| 716 | 726 | else col.insertBefore(card, after); | |
| 717 | 727 | } | |
| 718 | - | function onUp(){ | |
| 719 | - | if (!card) return; | |
| 720 | - | grip.removeEventListener('pointermove', onMove); | |
| 721 | - | grip.removeEventListener('pointerup', onUp); | |
| 722 | - | grip.removeEventListener('pointercancel', onUp); | |
| 728 | + | function onUp(e){ | |
| 729 | + | if (!card || (e && e.pointerId !== pid)) return; | |
| 730 | + | document.removeEventListener('pointermove', onMove); | |
| 731 | + | document.removeEventListener('pointerup', onUp); | |
| 732 | + | document.removeEventListener('pointercancel', onUp); | |
| 723 | 733 | var c = card; | |
| 724 | 734 | card = null; | |
| 725 | 735 | c.classList.remove('dragging'); | |
| 736 | + | try { grip.releasePointerCapture(pid); } catch (_) {} | |
| 726 | 737 | if (!moved) return; // a tap or cancelled gesture — nothing to commit | |
| 727 | 738 | var col = c.closest('.col'); | |
| 728 | 739 | var cards = [].slice.call(col.querySelectorAll('.card')); | |
| ⋯ 16 unchanged lines | |||
| 745 | 756 | board.addEventListener('pointerdown', function(e){ | |
| 746 | 757 | var g = e.target.closest('.card-grip'); | |
| 747 | 758 | if (!g) return; | |
| 748 | - | card = g.closest('.card'); | |
| 749 | - | if (!card) return; | |
| 759 | + | var c = g.closest('.card'); | |
| 760 | + | if (!c) return; | |
| 750 | 761 | e.preventDefault(); | |
| 751 | - | grip = g; | |
| 752 | - | pid = e.pointerId; | |
| 753 | - | fromSection = card.closest('.col').dataset.section; | |
| 754 | - | card.classList.add('dragging'); | |
| 755 | - | grip.setPointerCapture(pid); | |
| 756 | - | grip.addEventListener('pointermove', onMove); | |
| 757 | - | grip.addEventListener('pointerup', onUp); | |
| 758 | - | grip.addEventListener('pointercancel', onUp); | |
| 762 | + | card = c; grip = g; pid = e.pointerId; moved = false; | |
| 763 | + | fromSection = c.closest('.col').dataset.section; | |
| 764 | + | c.classList.add('dragging'); | |
| 765 | + | // Capture is best-effort (it can throw on some browsers); the move/up | |
| 766 | + | // listeners live on `document` so the drag works whether or not it sticks. | |
| 767 | + | try { g.setPointerCapture(e.pointerId); } catch (_) {} | |
| 768 | + | document.addEventListener('pointermove', onMove, { passive: false }); | |
| 769 | + | document.addEventListener('pointerup', onUp); | |
| 770 | + | document.addEventListener('pointercancel', onUp); | |
| 759 | 771 | }); | |
| 760 | 772 | })(); | |
| 761 | 773 | "#; | |
| ⋯ 287 unchanged lines | |||