fix(tableaux): scroll horizontal via wrapper overflow-x auto #82

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Colin Maudry
2026-06-23 16:52:22 +02:00
parent ce4b06fe3d
commit 96c0b0dd35
2 changed files with 31 additions and 15 deletions
+8
View File
@@ -397,6 +397,14 @@ table.cell-table th {
display: none; display: none;
} }
/* Conteneur scrollable horizontal — overflow-y:clip ne crée pas de scroll container vertical */
/* donc position:sticky sur th.dash-header reste calé sur la page */
.marches_table .dt-scroll-wrapper {
overflow-x: auto;
overflow-y: clip;
width: 100%;
}
/* Column Visibility Menu */ /* Column Visibility Menu */
.column-actions { .column-actions {
margin-right: 8px; margin-right: 8px;
+23 -15
View File
@@ -1,22 +1,26 @@
// Barre de défilement horizontale miroir pour les tableaux (.marches_table) — #82 // Barre de défilement horizontale pour les tableaux (.marches_table) — #82
(function () { (function () {
"use strict"; "use strict";
// Renvoie le conteneur réellement scrollable horizontalement du tableau.
function getScrollEl(wrapper) {
return wrapper.querySelector(".dash-spreadsheet-container") || wrapper;
}
function setup(wrapper) { function setup(wrapper) {
if (wrapper.dataset.hscrollReady === "1") return; if (wrapper.dataset.hscrollReady === "1") return;
const scrollEl = getScrollEl(wrapper);
const dashContainer = wrapper.querySelector(".dash-spreadsheet-container");
if (!dashContainer) return;
// Injecter le conteneur scrollable horizontal autour du conteneur Dash
const scrollWrapper = document.createElement("div");
scrollWrapper.className = "dt-scroll-wrapper";
dashContainer.parentNode.insertBefore(scrollWrapper, dashContainer);
scrollWrapper.appendChild(dashContainer);
// Injecter la barre de défilement en haut, avant le wrapper scrollable
const bar = document.createElement("div"); const bar = document.createElement("div");
bar.className = "dt-hscroll is-hidden"; bar.className = "dt-hscroll is-hidden";
const inner = document.createElement("div"); const inner = document.createElement("div");
inner.className = "dt-hscroll-inner"; inner.className = "dt-hscroll-inner";
bar.appendChild(inner); bar.appendChild(inner);
wrapper.insertBefore(bar, wrapper.firstChild); wrapper.insertBefore(bar, scrollWrapper);
// syncing évite les boucles dans la même pile d'exécution ; l'affectation de scrollLeft // syncing évite les boucles dans la même pile d'exécution ; l'affectation de scrollLeft
// déclenche l'événement scroll de façon synchrone dans Chromium. // déclenche l'événement scroll de façon synchrone dans Chromium.
@@ -24,30 +28,34 @@
const onBar = () => { const onBar = () => {
if (syncing) return; if (syncing) return;
syncing = true; syncing = true;
scrollEl.scrollLeft = bar.scrollLeft; scrollWrapper.scrollLeft = bar.scrollLeft;
syncing = false; syncing = false;
}; };
const onTable = () => { const onTable = () => {
if (syncing) return; if (syncing) return;
syncing = true; syncing = true;
bar.scrollLeft = scrollEl.scrollLeft; bar.scrollLeft = scrollWrapper.scrollLeft;
syncing = false; syncing = false;
}; };
bar.addEventListener("scroll", onBar); bar.addEventListener("scroll", onBar);
scrollEl.addEventListener("scroll", onTable); scrollWrapper.addEventListener("scroll", onTable);
const refresh = () => { const refresh = () => {
const total = scrollEl.scrollWidth; const total = scrollWrapper.scrollWidth;
const visible = scrollEl.clientWidth; const visible = scrollWrapper.clientWidth;
inner.style.width = total + "px"; inner.style.width = total + "px";
// +1 absorbe les erreurs d'arrondi sous-pixel // +1 absorbe les erreurs d'arrondi sous-pixel
bar.classList.toggle("is-hidden", total <= visible + 1); bar.classList.toggle("is-hidden", total <= visible + 1);
bar.scrollLeft = scrollEl.scrollLeft; bar.scrollLeft = scrollWrapper.scrollLeft;
}; };
// Recalcule quand le tableau change (pagination, tri, filtre, données). // Recalcule quand le tableau change (pagination, tri, filtre, données).
const obs = new MutationObserver(() => refresh()); const obs = new MutationObserver(() => refresh());
obs.observe(scrollEl, { childList: true, subtree: true, attributes: true }); 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. // SPA : ce listener est intentionnellement conservé pour toute la durée de vie de la page.
window.addEventListener("resize", refresh); window.addEventListener("resize", refresh);