/* * ShrinkVertically -- optional custom horizontal scrollbar above the board. * * Kanboard's board scrolls horizontally inside #board-container (overflow-x: auto), so the * only native horizontal scrollbar sits at the very bottom of the board. On a wide board * you have to scroll all the way down to reach it. This adds a second scrollbar just above * the columns and keeps it in sync with the board. * * Unlike a native scrollbar (whose thickness cannot be sized in Firefox), this is a custom * track + thumb widget: it renders and behaves the same in every browser (Chrome, Firefox, * Safari) and on touch, and its thickness follows the --sv-bar-size setting. * * Loaded only when the "Add a top horizontal scrollbar" setting is enabled. */ (function () { "use strict"; // Keep the thumb wide enough to grab, even on a very wide board. var MIN_THUMB = 24; function init() { var container = document.getElementById("board-container"); // Only on the board, and never add it twice. if (!container || document.querySelector(".sv-top-scroll")) { return; } var track = document.createElement("div"); track.className = "sv-top-scroll"; var thumb = document.createElement("div"); thumb.className = "sv-top-scroll-thumb"; track.appendChild(thumb); container.parentNode.insertBefore(track, container); var dragging = false; var dragStartX = 0; var dragStartScrollLeft = 0; function metrics() { return { viewport: container.clientWidth, content: container.scrollWidth, trackW: track.clientWidth }; } function positionThumb(m, thumbW) { var maxScroll = m.content - m.viewport; var maxThumb = m.trackW - thumbW; var left = maxScroll > 0 ? (container.scrollLeft / maxScroll) * maxThumb : 0; thumb.style.transform = "translateX(" + Math.round(left) + "px)"; } // Recompute thumb width/position; hide the whole bar when nothing to scroll. function layout() { var m = metrics(); if (m.content <= m.viewport || m.trackW <= 0) { track.style.display = "none"; return; } track.style.display = ""; var thumbW = Math.round(m.trackW * m.viewport / m.content); if (thumbW < MIN_THUMB) { thumbW = MIN_THUMB; } if (thumbW > m.trackW) { thumbW = m.trackW; } thumb.style.width = thumbW + "px"; positionThumb(m, thumbW); } // Board scrolled by any other means -> keep the thumb in sync. container.addEventListener("scroll", function () { if (dragging) { return; } var m = metrics(); if (m.content <= m.viewport) { return; } positionThumb(m, thumb.offsetWidth); }); // Drag the thumb (mouse + touch via Pointer Events). thumb.addEventListener("pointerdown", function (e) { e.preventDefault(); e.stopPropagation(); dragging = true; dragStartX = e.clientX; dragStartScrollLeft = container.scrollLeft; track.classList.add("sv-dragging"); if (thumb.setPointerCapture) { thumb.setPointerCapture(e.pointerId); } }); thumb.addEventListener("pointermove", function (e) { if (!dragging) { return; } var m = metrics(); var maxThumb = m.trackW - thumb.offsetWidth; var maxScroll = m.content - m.viewport; if (maxThumb <= 0) { return; } container.scrollLeft = dragStartScrollLeft + (e.clientX - dragStartX) * (maxScroll / maxThumb); positionThumb(m, thumb.offsetWidth); }); function endDrag(e) { if (!dragging) { return; } dragging = false; track.classList.remove("sv-dragging"); if (thumb.releasePointerCapture && e && e.pointerId != null) { try { thumb.releasePointerCapture(e.pointerId); } catch (err) { /* ignore */ } } } thumb.addEventListener("pointerup", endDrag); thumb.addEventListener("pointercancel", endDrag); // Click on the empty track -> jump so the thumb centers under the pointer. track.addEventListener("pointerdown", function (e) { if (e.target === thumb || dragging) { return; } var m = metrics(); if (m.content <= m.viewport) { return; } var thumbW = thumb.offsetWidth; var maxThumb = m.trackW - thumbW; var maxScroll = m.content - m.viewport; if (maxThumb <= 0) { return; } var clickX = e.clientX - track.getBoundingClientRect().left; var targetLeft = clickX - thumbW / 2; if (targetLeft < 0) { targetLeft = 0; } if (targetLeft > maxThumb) { targetLeft = maxThumb; } container.scrollLeft = (targetLeft / maxThumb) * maxScroll; positionThumb(m, thumbW); }); window.addEventListener("resize", layout); // The board can be redrawn by AJAX polling / drag-and-drop; keep sizes current. if (window.MutationObserver) { new MutationObserver(layout).observe(container, { childList: true, subtree: true }); } layout(); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", init); } else { init(); } })();