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 {
|
#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: 1px solid var(--figli-border);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
max-height: 75vh;
|
max-height: 75vh;
|
||||||
overflow-y: auto;
|
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 {
|
#figli-home-app table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
table-layout: fixed;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
background: var(--figli-bg);
|
background: var(--figli-bg);
|
||||||
color: var(--figli-text);
|
color: var(--figli-text);
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
white-space: nowrap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#figli-home-app th,
|
#figli-home-app th,
|
||||||
#figli-home-app td {
|
#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;
|
text-align: left;
|
||||||
padding: 0.3rem 0.6rem;
|
padding: 0.3rem 0.6rem;
|
||||||
border-bottom: 1px solid var(--figli-border);
|
border-bottom: 1px solid var(--figli-border);
|
||||||
@@ -201,16 +226,16 @@ html.gin--dark-mode #figli-home-app {
|
|||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#figli-home-app th:nth-child(6),
|
||||||
#figli-home-app td.figli-libelle {
|
#figli-home-app td.figli-libelle {
|
||||||
|
width: 15%;
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
min-width: 220px;
|
|
||||||
max-width: 340px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#figli-home-app th:nth-child(7),
|
||||||
#figli-home-app td.figli-flag-cell {
|
#figli-home-app td.figli-flag-cell {
|
||||||
|
width: 8%;
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
min-width: 120px;
|
|
||||||
max-width: 220px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#figli-home-app tr.figli-group-row td {
|
#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. */
|
table's default 0.6rem horizontal cell padding. */
|
||||||
#figli-home-app td.actions-col,
|
#figli-home-app td.actions-col,
|
||||||
#figli-home-app th.actions-col {
|
#figli-home-app th.actions-col {
|
||||||
width: 1%;
|
width: 3%;
|
||||||
padding: 0.2rem 0.1rem;
|
padding: 0.2rem 0.1rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
white-space: nowrap;
|
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
|
/* Buttons stay inline (not flex) inside the <td> -- display: flex on a
|
||||||
table cell breaks the table's own column-width calculation (observed:
|
table cell breaks the table's own column-width calculation (observed:
|
||||||
a ~55px gap opening up between this column and the next, and the
|
a ~55px gap opening up between this column and the next, and the
|
||||||
|
|||||||
@@ -250,8 +250,7 @@
|
|||||||
<tfoot>
|
<tfoot>
|
||||||
<tr class="figli-totals-row">
|
<tr class="figli-totals-row">
|
||||||
<td class="actions-col"></td>
|
<td class="actions-col"></td>
|
||||||
<td class="actions-col"></td>
|
<td colspan="6">
|
||||||
<td colspan="5">
|
|
||||||
Solde {{ currentYear || '…' }} (créditeur / débiteur)
|
Solde {{ currentYear || '…' }} (créditeur / débiteur)
|
||||||
<span v-if="currentYearLoading" class="figli-note">chargement…</span>
|
<span v-if="currentYearLoading" class="figli-note">chargement…</span>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
Reference in New Issue
Block a user