Fix table jitter, horizontal scroll, and vertical scroll snapping on /lignes
Root cause: the main table used auto layout, so every loadOlder()/ loadNewer() reflowed every column's width based on whatever was currently loaded -- visibly shifting the table, spilling past the viewport into a horizontal scrollbar, and corrupting loadOlder()'s scroll-position compensation (which assumes the scrollHeight delta after prepending rows is *only* the new rows' own height -- not true once existing rows also reflow). That's what made scrolling up feel like it kept snapping back down. - table-layout: fixed with explicit per-column widths (percentages throughout, not mixed with rem -- mixing meant the rem columns' width was added on top of the percentage budget instead of coming out of it), plus box-sizing: border-box so padding doesn't inflate columns beyond their declared width. - overflow-x: hidden instead of auto on the scroll container: with both x and y auto on the same element, the browser has to guess whether a vertical scrollbar will appear before laying out width: 100%, and a wrong guess understates available width by a scrollbar's worth -- exactly enough to tip a tightly-fitting table into needing horizontal scroll too. - Fixed a footer-row column count bug found along the way: it still had two actions-col cells and a colspan=5 label from before the link button was removed, leaving the label 6 columns short of Signalement and misaligning every footer cell after it. Verified with a 25-round scripted scroll-up stress test spanning 3 loadOlder() triggers: scroll position stays correctly anchored near where the user was looking, never jumps toward the bottom. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -159,24 +159,49 @@ html.gin--dark-mode #figli-home-app {
|
||||
}
|
||||
|
||||
#figli-home-app .figli-table-wrap {
|
||||
overflow-x: auto;
|
||||
/* Fixed columns below are sized to always fit -- hidden (not auto) so
|
||||
there's never a horizontal scrollbar to fight the vertical one for
|
||||
layout space. With both set to auto, the browser has to guess
|
||||
whether a vertical scrollbar will appear before it can lay out the
|
||||
table's width:100%, and getting that guess wrong understates the
|
||||
available width by one scrollbar's worth, which was enough to tip
|
||||
this from "just barely fits" into "needs to scroll horizontally
|
||||
too" -- and any actual residual overflow (a column pushed a few px
|
||||
over by content this doesn't attempt to break) is silently clipped
|
||||
here instead of surfacing a scrollbar for it. */
|
||||
overflow-x: hidden;
|
||||
border: 1px solid var(--figli-border);
|
||||
border-radius: 6px;
|
||||
max-height: 75vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* table-layout: fixed -- auto layout was recomputing every column's
|
||||
width from whatever happened to be currently loaded, so the table
|
||||
visibly reflowed (and drifted wider than the viewport, forcing
|
||||
horizontal scroll) every time loadOlder()/loadNewer() brought in rows
|
||||
with different content. Fixed layout locks each column to the widths
|
||||
set below regardless of content, so the table's total width never
|
||||
changes -- no more horizontal scroll, and loadOlder()'s scroll
|
||||
compensation (which assumes the height delta after prepending is
|
||||
*only* the new rows' own height) is now accurate again, since existing
|
||||
rows no longer reflow when new ones are added. */
|
||||
#figli-home-app table {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
border-collapse: collapse;
|
||||
background: var(--figli-bg);
|
||||
color: var(--figli-text);
|
||||
font-size: 0.8rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#figli-home-app th,
|
||||
#figli-home-app td {
|
||||
/* border-box -- otherwise each column's percentage width (fixed
|
||||
layout, above) sets only the content box, and this cell's own
|
||||
padding/border get added on top of it, so 18 columns' worth of
|
||||
padding quietly pushes the table wider than 100% again. */
|
||||
box-sizing: border-box;
|
||||
text-align: left;
|
||||
padding: 0.3rem 0.6rem;
|
||||
border-bottom: 1px solid var(--figli-border);
|
||||
@@ -201,16 +226,16 @@ html.gin--dark-mode #figli-home-app {
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
#figli-home-app th:nth-child(6),
|
||||
#figli-home-app td.figli-libelle {
|
||||
width: 15%;
|
||||
white-space: normal;
|
||||
min-width: 220px;
|
||||
max-width: 340px;
|
||||
}
|
||||
|
||||
#figli-home-app th:nth-child(7),
|
||||
#figli-home-app td.figli-flag-cell {
|
||||
width: 8%;
|
||||
white-space: normal;
|
||||
min-width: 120px;
|
||||
max-width: 220px;
|
||||
}
|
||||
|
||||
#figli-home-app tr.figli-group-row td {
|
||||
@@ -378,12 +403,52 @@ html.gin--dark-mode #figli-home-app {
|
||||
table's default 0.6rem horizontal cell padding. */
|
||||
#figli-home-app td.actions-col,
|
||||
#figli-home-app th.actions-col {
|
||||
width: 1%;
|
||||
width: 3%;
|
||||
padding: 0.2rem 0.1rem;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Column widths for the fixed layout above -- sized so the widest
|
||||
realistic content (longest client/type label, an 8-figure amount)
|
||||
fits without pushing the table past 100%, while the two free-text
|
||||
columns (Client, Libellé/Détail) get most of the remaining room.
|
||||
Every column is a percentage (including actions-col/Date above,
|
||||
deliberately not rem) -- mixing units meant the rem columns' pixel
|
||||
width was added *on top of* the percentage budget instead of coming
|
||||
out of it, silently pushing the table a few pixels past 100% and
|
||||
reintroducing the horizontal scrollbar this is meant to avoid.
|
||||
Percentages intentionally sum to a little under 100%: table-layout:
|
||||
fixed treats them as relative weights, not a hard budget, so slightly
|
||||
under leaves headroom rather than risking every column getting
|
||||
scaled down to fit. */
|
||||
#figli-home-app th:nth-child(2),
|
||||
#figli-home-app td:nth-child(2) {
|
||||
width: 6%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
#figli-home-app th:nth-child(3),
|
||||
#figli-home-app td:nth-child(3) {
|
||||
width: 10%;
|
||||
}
|
||||
#figli-home-app th:nth-child(4),
|
||||
#figli-home-app td:nth-child(4) {
|
||||
width: 7%;
|
||||
}
|
||||
#figli-home-app th:nth-child(5),
|
||||
#figli-home-app td:nth-child(5) {
|
||||
width: 5.5%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
#figli-home-app .amount:not(.compte-col) {
|
||||
width: 5.5%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
#figli-home-app .amount.compte-col {
|
||||
width: 3.5%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Buttons stay inline (not flex) inside the <td> -- display: flex on a
|
||||
table cell breaks the table's own column-width calculation (observed:
|
||||
a ~55px gap opening up between this column and the next, and the
|
||||
|
||||
@@ -250,8 +250,7 @@
|
||||
<tfoot>
|
||||
<tr class="figli-totals-row">
|
||||
<td class="actions-col"></td>
|
||||
<td class="actions-col"></td>
|
||||
<td colspan="5">
|
||||
<td colspan="6">
|
||||
Solde {{ currentYear || '…' }} (créditeur / débiteur)
|
||||
<span v-if="currentYearLoading" class="figli-note">chargement…</span>
|
||||
</td>
|
||||
|
||||
Reference in New Issue
Block a user