fix(tableaux): option A — sync barre avec window.scrollX + style orange #82

Abandonne le wrapper overflow-x (incompatible avec position:sticky des
en-têtes). La barre contrôle désormais le scroll horizontal de la page
via window.scrollTo/scrollX, ce qui préserve le sticky des th.
Scrollbar orange 12px, styling webkit + Firefox.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Colin Maudry
2026-06-23 18:22:08 +02:00
parent 817aeedc6f
commit 099fba58f7
2 changed files with 30 additions and 27 deletions
+17 -10
View File
@@ -379,14 +379,29 @@ table.cell-table th {
background-color: rgb(255 240 240 / 40%); background-color: rgb(255 240 240 / 40%);
} }
/* Barre de défilement horizontale miroir, collée en haut */ /* Barre de défilement horizontale, collée en haut au-dessus des en-têtes */
.marches_table .dt-hscroll { .marches_table .dt-hscroll {
position: sticky; position: sticky;
top: 0; top: 0;
z-index: 11; /* au-dessus des en-têtes sticky */ z-index: 11; /* au-dessus des en-têtes sticky */
overflow-x: auto; overflow-x: auto;
overflow-y: hidden; overflow-y: hidden;
height: 14px; height: 12px;
scrollbar-color: orange #e0e0e0;
scrollbar-width: thin;
}
.marches_table .dt-hscroll::-webkit-scrollbar {
height: 12px;
}
.marches_table .dt-hscroll::-webkit-scrollbar-thumb {
background-color: orange;
border-radius: 6px;
}
.marches_table .dt-hscroll::-webkit-scrollbar-track {
background-color: #e0e0e0;
} }
.marches_table .dt-hscroll-inner { .marches_table .dt-hscroll-inner {
@@ -397,14 +412,6 @@ 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;
+13 -17
View File
@@ -12,45 +12,41 @@
// qui déclencherait rootObs et provoquerait une re-entrée dans setup(). // qui déclencherait rootObs et provoquerait une re-entrée dans setup().
wrapper.dataset.hscrollReady = "1"; wrapper.dataset.hscrollReady = "1";
// 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, scrollWrapper); wrapper.insertBefore(bar, wrapper.firstChild);
// syncing évite les boucles dans la même pile d'exécution ; l'affectation de scrollLeft // La barre synchronise le scroll horizontal de la page (overflow: visible sur les
// déclenche l'événement scroll de façon synchrone dans Chromium. // conteneurs Dash laisse le scroll se faire au niveau de la page, ce qui permet
// aux en-têtes position:sticky de rester calés sur la fenêtre).
// syncing évite les boucles dans la même pile d'exécution.
let syncing = false; let syncing = false;
const onBar = () => { const onBar = () => {
if (syncing) return; if (syncing) return;
syncing = true; syncing = true;
scrollWrapper.scrollLeft = bar.scrollLeft; window.scrollTo(bar.scrollLeft, window.scrollY);
syncing = false; syncing = false;
}; };
const onTable = () => { const onPage = () => {
if (syncing) return; if (syncing) return;
syncing = true; syncing = true;
bar.scrollLeft = scrollWrapper.scrollLeft; bar.scrollLeft = window.scrollX;
syncing = false; syncing = false;
}; };
bar.addEventListener("scroll", onBar); bar.addEventListener("scroll", onBar);
scrollWrapper.addEventListener("scroll", onTable); // SPA : ce listener est intentionnellement conservé pour toute la durée de vie de la page.
window.addEventListener("scroll", onPage);
const refresh = () => { const refresh = () => {
const total = scrollWrapper.scrollWidth; const total = document.documentElement.scrollWidth;
const visible = scrollWrapper.clientWidth; const visible = window.innerWidth;
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 = scrollWrapper.scrollLeft; bar.scrollLeft = window.scrollX;
}; };
// Recalcule quand le tableau change (pagination, tri, filtre, données). // Recalcule quand le tableau change (pagination, tri, filtre, données).