diff --git a/Asset/js/drag-autoscroll.js b/Asset/js/drag-autoscroll.js new file mode 100644 index 0000000..4caafc0 --- /dev/null +++ b/Asset/js/drag-autoscroll.js @@ -0,0 +1,119 @@ +/* + * TweakDrag -- auto-scroll the board while dragging a card near a screen edge. + * + * Kanboard drags cards with jQuery UI sortable. The board does not scroll during a drag + * (jQuery UI's autoscroll is unreliable horizontally, and on touch the bundled Touch Punch + * shim suppresses native scrolling while a card is held), so you can only drop onto columns + * already on screen. This scrolls #board-container while a card drag is active and the + * pointer nears the left/right edge, with a smooth continuous speed that grows toward the + * edge -- so you can drag a card to the edge and roll the board to a far column. + * + * It only acts during an active card drag near an edge, so it is harmless when idle. Works + * on desktop (mouse) and mobile (touch, via Touch Punch). No core changes. + */ +(function () { + "use strict"; + + var EDGE_ZONE = 50; // px from the board edge where auto-scroll begins + var MAX_SPEED = 22; // px per animation frame at the very edge + var EASE = 1; // 1 = linear ramp, 2 = gentle-then-fast + + var jq = window.jQuery; + if (!jq) { + return; + } + + var container = null; + var dragging = false; + var rafId = 0; + var lastX = 0; + var lastY = 0; + + function onMove(e) { + if (e.touches && e.touches.length) { + lastX = e.touches[0].clientX; + lastY = e.touches[0].clientY; + } else { + lastX = e.clientX; + lastY = e.clientY; + } + } + + // Distance is px from the edge; returns 0 at the zone border up to MAX_SPEED at the edge. + function speedFor(distance) { + var p = (EDGE_ZONE - distance) / EDGE_ZONE; + if (p <= 0) { + return 0; + } + if (p > 1) { + p = 1; + } + if (EASE !== 1) { + p = Math.pow(p, EASE); + } + return MAX_SPEED * p; + } + + function step() { + if (!dragging || !container) { + return; + } + + var rect = container.getBoundingClientRect(); + var dLeft = lastX - rect.left; + var dRight = rect.right - lastX; + var speed = 0; + + if (dLeft < EDGE_ZONE) { + speed = -speedFor(dLeft); + } else if (dRight < EDGE_ZONE) { + speed = speedFor(dRight); + } + + if (speed !== 0) { + var before = container.scrollLeft; + container.scrollLeft = before + speed; + + // Only when the board actually moved: nudge jQuery UI to recompute drop + // positions so the placeholder follows onto the newly revealed column. + if (container.scrollLeft !== before) { + document.dispatchEvent(new MouseEvent("mousemove", { + clientX: lastX, + clientY: lastY, + bubbles: true + })); + } + } + + rafId = window.requestAnimationFrame(step); + } + + function startLoop() { + container = document.getElementById("board-container"); + if (!container) { + return; + } + dragging = true; + if (!rafId) { + rafId = window.requestAnimationFrame(step); + } + } + + function stopLoop() { + dragging = false; + if (rafId) { + window.cancelAnimationFrame(rafId); + rafId = 0; + } + } + + // Track the pointer at all times (cheap; only used while dragging). + document.addEventListener("mousemove", onMove, true); + document.addEventListener("touchmove", onMove, true); + document.addEventListener("pointermove", onMove, true); + + // A card drag started / stopped. jQuery UI sortable fires these (on touch too, via + // Touch Punch); delegated on the document so board redraws need no rebinding. + jq(document).on("sortstart", startLoop); + jq(document).on("sortstop", stopLoop); +})();