Files
colibre/src/assets/table_hscroll.js
T
Colin Maudry 2e22fcabe9 fix(tableaux): scroll contenu dans dashContainer, plus de scrollbar page #82
Remplace le scroll de page (window.scrollTo) par dashContainer.scrollLeft :
- overflow-x:hidden sur .dash-spreadsheet-container → la table ne déborde
  plus la page → scrollbar navigateur en bas éliminée
- overflow-y:clip évite la conversion CSS visible→auto (pas de scrollbar
  vertical parasite sur le conteneur)
- metrics(), syncThumb, drag et clic utilisent tous dashContainer
- wheel handler capture les gestures trackpad horizontaux et les redirige
  vers dashContainer.scrollLeft

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 19:20:41 +02:00

155 lines
5.1 KiB
JavaScript

// Barre de défilement horizontale pour les tableaux (.marches_table) — #82
(function () {
"use strict";
function setup(wrapper) {
if (wrapper.dataset.hscrollReady === "1") return;
const dashContainer = wrapper.querySelector(".dash-spreadsheet-container");
if (!dashContainer) return;
// Garde posée après la vérification de dashContainer, avant toute manipulation DOM
// qui déclencherait rootObs et provoquerait une re-entrée dans setup().
wrapper.dataset.hscrollReady = "1";
const bar = document.createElement("div");
bar.className = "dt-hscroll is-hidden";
const thumb = document.createElement("div");
thumb.className = "dt-hscroll-thumb";
bar.appendChild(thumb);
wrapper.insertBefore(bar, wrapper.firstChild);
// Métriques basées sur le conteneur scrollable (pas la page).
// dashContainer a overflow-x:hidden → scrollLeft est contrôlable par JS.
const metrics = () => {
const total = dashContainer.scrollWidth;
const visible = dashContainer.clientWidth;
const thumbW = Math.max(40, (visible / total) * visible);
const scrollRange = total - visible;
const thumbRange = visible - thumbW;
return { total, visible, thumbW, scrollRange, thumbRange };
};
// Mise à jour de la position du thumb selon dashContainer.scrollLeft.
const syncThumb = () => {
const { total, visible, thumbW, scrollRange, thumbRange } = metrics();
if (total <= visible + 1 || thumbRange <= 0) return;
thumb.style.width = thumbW + "px";
const fraction =
scrollRange > 0 ? dashContainer.scrollLeft / scrollRange : 0;
thumb.style.left = Math.round(fraction * thumbRange) + "px";
};
// --- Drag souris + tactile ---
let dragStartX = null;
let dragScrollStart = null;
const startDrag = (clientX) => {
dragStartX = clientX;
dragScrollStart = dashContainer.scrollLeft;
};
const moveDrag = (clientX) => {
if (dragStartX === null) return;
const dx = clientX - dragStartX;
const { scrollRange, thumbRange } = metrics();
if (thumbRange <= 0) return;
dashContainer.scrollLeft = Math.max(
0,
Math.min(scrollRange, dragScrollStart + (dx / thumbRange) * scrollRange)
);
};
const endDrag = () => {
dragStartX = null;
};
thumb.addEventListener("mousedown", (e) => {
startDrag(e.clientX);
e.preventDefault();
});
document.addEventListener("mousemove", (e) => moveDrag(e.clientX));
document.addEventListener("mouseup", endDrag);
thumb.addEventListener(
"touchstart",
(e) => {
startDrag(e.touches[0].clientX);
e.preventDefault();
},
{ passive: false }
);
document.addEventListener(
"touchmove",
(e) => {
if (dragStartX !== null) {
moveDrag(e.touches[0].clientX);
e.preventDefault();
}
},
{ passive: false }
);
document.addEventListener("touchend", endDrag);
// Clic sur le track (hors thumb) : saute à la position cliquée.
bar.addEventListener("click", (e) => {
if (e.target === thumb) return;
const rect = bar.getBoundingClientRect();
const { scrollRange, thumbW, thumbRange } = metrics();
const fraction = Math.max(
0,
Math.min(1, (e.clientX - rect.left - thumbW / 2) / thumbRange)
);
dashContainer.scrollLeft = fraction * scrollRange;
});
// Scroll molette/trackpad horizontal → redirigé vers le conteneur.
// SPA : ce listener est intentionnellement conservé pour toute la durée de vie de la page.
wrapper.addEventListener(
"wheel",
(e) => {
if (Math.abs(e.deltaX) <= Math.abs(e.deltaY)) return;
e.preventDefault();
dashContainer.scrollLeft = Math.max(
0,
Math.min(
dashContainer.scrollWidth - dashContainer.clientWidth,
dashContainer.scrollLeft + e.deltaX
)
);
},
{ passive: false }
);
// Synchronise le thumb quand le conteneur défile (drag, wheel, ou autre).
// SPA : ce listener est intentionnellement conservé pour toute la durée de vie de la page.
dashContainer.addEventListener("scroll", syncThumb);
const refresh = () => {
const hasOverflow =
dashContainer.scrollWidth > dashContainer.clientWidth + 1;
bar.classList.toggle("is-hidden", !hasOverflow);
if (hasOverflow) syncThumb();
};
// Recalcule quand le tableau change (pagination, tri, filtre, données).
const obs = new MutationObserver(() => refresh());
obs.observe(dashContainer, {
childList: true,
subtree: true,
attributes: true,
});
// SPA : ce listener est intentionnellement conservé pour toute la durée de vie de la page.
window.addEventListener("resize", refresh);
refresh();
}
function scan() {
document.querySelectorAll(".marches_table").forEach(setup);
}
// Les tableaux apparaissent après le rendu Dash : observer le body.
const rootObs = new MutationObserver(() => scan());
rootObs.observe(document.body, { childList: true, subtree: true });
scan();
})();