From fe577a674cd7fff4170a43cdae7374f10ef5b2d3 Mon Sep 17 00:00:00 2001 From: Ruben Carlo Benante Date: Mon, 6 Jul 2026 20:25:02 -0300 Subject: [PATCH] drag autoscroll: add vertical (page) scroll + percentage edge zones (15%, 30-160px); v1.2 --- Asset/js/drag-autoscroll.js | 116 +++++++++++++++++++++++++----------- Plugin.php | 2 +- README.md | 15 +++-- VERSION | 2 +- 4 files changed, 94 insertions(+), 41 deletions(-) diff --git a/Asset/js/drag-autoscroll.js b/Asset/js/drag-autoscroll.js index 4caafc0..55a2376 100644 --- a/Asset/js/drag-autoscroll.js +++ b/Asset/js/drag-autoscroll.js @@ -2,11 +2,20 @@ * 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. + * (jQuery UI's autoscroll is unreliable, and on touch the bundled Touch Punch shim + * suppresses native scrolling while a card is held), so you can only drop where already on + * screen. While a card drag is active and the pointer nears an edge, this scrolls toward it + * with a smooth continuous speed that grows toward the edge: + * + * - Horizontal: scrolls #board-container (near its left/right edge). + * - Vertical: scrolls the window/page (near the viewport top/bottom). This matters when + * the vertical-collapse view is OFF and the whole page scrolls; when collapse is ON the + * page is not scrollable so window scrolling no-ops and the per-column scroll stays + * native. + * + * The edge zone is a percentage of each axis (clamped), so it adapts to screen size and + * orientation: a narrow phone width gets a small horizontal zone, a tall phone height a + * larger vertical one, a wide desktop the reverse -- with no device detection. * * 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. @@ -14,16 +23,17 @@ (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 EDGE_ZONE_PCT = 0.15; // edge zone = 15% of the axis length... + var EDGE_ZONE_MIN = 30; // ...clamped to at least 30px... + var EDGE_ZONE_MAX = 160; // ...and at most 160px. + 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; @@ -39,9 +49,17 @@ } } - // 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; + // Edge zone for an axis of the given length (px), 15% clamped to [30, 160]. + function zoneFor(length) { + var z = length * EDGE_ZONE_PCT; + if (z < EDGE_ZONE_MIN) { z = EDGE_ZONE_MIN; } + if (z > EDGE_ZONE_MAX) { z = EDGE_ZONE_MAX; } + return z; + } + + // Distance is px from the edge; 0 at the zone border up to MAX_SPEED at the edge. + function speedFor(distance, zone) { + var p = (zone - distance) / zone; if (p <= 0) { return 0; } @@ -55,42 +73,72 @@ } function step() { - if (!dragging || !container) { + if (!dragging) { return; } - var rect = container.getBoundingClientRect(); - var dLeft = lastX - rect.left; - var dRight = rect.right - lastX; - var speed = 0; + var moved = false; - if (dLeft < EDGE_ZONE) { - speed = -speedFor(dLeft); - } else if (dRight < EDGE_ZONE) { - speed = speedFor(dRight); + // Horizontal: scroll the board container near its left/right edge. + var c = document.getElementById("board-container"); + if (c) { + var rect = c.getBoundingClientRect(); + var zoneH = zoneFor(rect.width); + var hspeed = 0; + var dLeft = lastX - rect.left; + var dRight = rect.right - lastX; + + if (dLeft < zoneH) { + hspeed = -speedFor(dLeft, zoneH); + } else if (dRight < zoneH) { + hspeed = speedFor(dRight, zoneH); + } + + if (hspeed !== 0) { + var beforeX = c.scrollLeft; + c.scrollLeft = beforeX + hspeed; + if (c.scrollLeft !== beforeX) { + moved = true; + } + } } - if (speed !== 0) { - var before = container.scrollLeft; - container.scrollLeft = before + speed; + // Vertical: scroll the window near the viewport top/bottom (collapse-off case; + // a natural no-op when the page is not scrollable). + var zoneV = zoneFor(window.innerHeight); + var vspeed = 0; + var dTop = lastY; + var dBottom = window.innerHeight - lastY; - // 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 - })); + if (dTop < zoneV) { + vspeed = -speedFor(dTop, zoneV); + } else if (dBottom < zoneV) { + vspeed = speedFor(dBottom, zoneV); + } + + if (vspeed !== 0) { + var beforeY = window.pageYOffset; + window.scrollBy(0, vspeed); + if (window.pageYOffset !== beforeY) { + moved = true; } } + // When the board/page actually moved, nudge jQuery UI to recompute drop positions + // so the placeholder follows onto the newly revealed area (either axis). + if (moved) { + document.dispatchEvent(new MouseEvent("mousemove", { + clientX: lastX, + clientY: lastY, + bubbles: true + })); + } + rafId = window.requestAnimationFrame(step); } function startLoop() { - container = document.getElementById("board-container"); - if (!container) { + if (!document.getElementById("board-container")) { return; } dragging = true; diff --git a/Plugin.php b/Plugin.php index b6a2168..1e997bd 100644 --- a/Plugin.php +++ b/Plugin.php @@ -50,7 +50,7 @@ class Plugin extends Base public function getPluginVersion() { - return '1.1.0'; + return '1.2.0'; } public function getPluginHomepage() diff --git a/README.md b/README.md index 75cce88..8bdbff6 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,9 @@ A Kanboard plugin that groups board dragging and spacing tweaks, configurable un - **Mobile drag handle size** -- enlarge the card drag handle Kanboard shows on mobile so it is easy to grab with a finger. - **Drag auto-scroll** -- while dragging a card, the board scrolls when the pointer nears a - screen edge, so you can reach a column that is off screen (desktop and mobile). + screen edge, so you can reach a column or row that is off screen. Horizontal scrolls the + board; vertical scrolls the page (when the vertical-collapse view is off). Works on + desktop and mobile. These options were originally part of the ShrinkVertically plugin and moved here so that drag/touch behaviour can grow on its own (planned: mobile drag-to-move cards, and a drag @@ -34,10 +36,13 @@ movement threshold so ending a drag does not open the task) without bloating tha keeps the handle hidden. - **Drag auto-scroll**: a script (`Asset/js/drag-autoscroll.js`) listens for jQuery UI sortable's `sortstart`/`sortstop` (which also fire on touch via Kanboard's bundled Touch - Punch) and, while a card drag is active, scrolls `#board-container` when the pointer nears - its left/right edge. Speed ramps up smoothly toward the edge. After each scroll step it - dispatches a synthetic `mousemove` so jQuery UI moves the drop placeholder onto the newly - revealed column. It only acts during an active drag, so it is idle otherwise. + Punch) and, while a card drag is active, scrolls toward whichever edge the pointer nears: + the board (`#board-container`) horizontally, and the window/page vertically (for the + collapse-off view; a no-op when the page does not scroll). Speed ramps up smoothly toward + the edge. The edge zone is 15% of each axis, clamped to 30-160px, so it adapts to screen + size and orientation. After each scroll step it dispatches a synthetic `mousemove` so + jQuery UI moves the drop placeholder onto the newly revealed area. It only acts during an + active drag, so it is idle otherwise. ## Requirements diff --git a/VERSION b/VERSION index 9fa4ced..759433d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -TweakDrag v1.1 +TweakDrag v1.2