pdf-viewer: various visual and usability changes

- fix page nav buttons, smaller default pages, violet bg

  - Build toolbar after renderAllPages so it queries existing canvases;
    query canvases fresh in the scroll handler so page tracking works
    after re-render (zoom in/out).
  - Default data-pdf-scale 0.85 (JS fallback 0.8) so pages are a bit
    smaller by default.
  - Add dark transparent violet background (accent-secondary 22%) to
    .pdf-expanded, behind the pages.

  - expand to fill column top-to-bottom via CSS flex

    Use a flex chain (.tfe-files flex:1, .tfe-file-item:has(.pdf-expanded)
    flex:1) so the expanded PDF wrapper fills the right column from under
    the header to the bottom of the viewport, instead of viewport-based
    clamp math that didn't account for the header height.

  - fill right column, center pages, freeze column on expand

  The expanded viewer now stays inside the right column instead of
  growing the page: the column switches to overflow:hidden while the
  PDF panel scrolls internally. Pages are centered horizontally via
  flexbox align-items: center.

  - fix toolbar not full-width, hide sibling file items

  The toolbar was constrained by the parent's align-items:center; fixed
  with align-self:stretch. Hiding sibling .tfe-file-item elements when
  a PDF is expanded prevents them from rendering above the overlay.

  - disable overscroll on expanded view

  - use overscroll-behavior-y on container, none on toolbar

  - disable body overscroll via .pdf-viewer-open class

  - mobile full-viewport, iframe matches pdf height

  - mobile overlay above everything via position:fixed

  - opaque dark-violet bg on mobile overlay

  - tfe: add 'Open in new tab' link above website iframes

  - trim column padding when expanded for more vertical space

  - 5% zoom increments, subtle padding on expanded column

  - prevent horizontal overflow/overscroll
This commit is contained in:
Pontoporeia
2026-07-08 22:41:52 +02:00
parent 82d8eff512
commit 20873f94af
4 changed files with 99 additions and 18 deletions
+75 -6
View File
@@ -57,6 +57,8 @@
display: flex;
flex-direction: column;
gap: var(--space-m);
flex: 1;
min-height: 0;
}
.tfe-file-item {
@@ -70,6 +72,30 @@
overflow: visible;
}
.tfe-file-item:has(.pdf-expanded) {
flex: 1;
min-height: 0;
overflow: visible;
}
.tfe-file-item:has(.tfe-file-iframe) {
flex: 1;
min-height: 0;
overflow: visible;
}
/* When a PDF is expanded, freeze the right column and hide
sibling file items so they can't render above the expanded view.
Also trim column padding so the PDF fills more vertical space. */
.tfe-right-column:has(.pdf-expanded) {
overflow: hidden;
padding-top: var(--space-xs);
padding-bottom: var(--space-xs);
}
.tfe-right-column:has(.pdf-expanded) .tfe-file-item:not(:has(.pdf-expanded)) {
display: none;
}
.tfe-file-item img {
width: 100%;
height: auto;
@@ -95,11 +121,29 @@
.tfe-file-iframe {
width: 100%;
max-width: 100%;
height: clamp(300px, 80vh, 700px);
height: 100%;
min-height: 400px;
display: block;
border: none;
}
.tfe-website-link {
display: inline-flex;
align-items: center;
gap: var(--space-3xs);
padding: var(--space-2xs) var(--space-xs);
font-size: var(--step--1);
color: var(--accent-primary);
text-decoration: none;
border: 1px solid var(--border);
border-radius: var(--radius);
margin-bottom: var(--space-2xs);
}
.tfe-website-link:hover {
background: color-mix(in srgb, var(--accent-primary) 10%, var(--bg-primary));
}
.tfe-file-item figcaption {
font-size: var(--step--2);
color: var(--text-secondary);
@@ -312,8 +356,8 @@
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
outline: none;
transition: height 0.35s ease, border-color 0.35s ease;
outline: none;
}
/* ── Collapsed state: thumbnail tile ────────── */
@@ -395,19 +439,30 @@
/* ── Expanded state: wrapper becomes scroll panel ── */
/* Disable root-level overscroll (rubberbanding) while a PDF is open. */
body.pdf-viewer-open {
overscroll-behavior: none;
}
.pdf-expanded {
/* Fill viewport minus the column's vertical padding */
max-height: calc(100vh - var(--space-xl) * 2);
height: clamp(400px, 80vh, calc(100vh - var(--space-xl) * 2));
/* Fill the right column from top to bottom (under the header). */
height: 100%;
min-height: 400px;
overflow-y: auto;
overflow-x: hidden;
overscroll-behavior-y: contain;
border: 2px solid var(--accent-primary);
display: block;
display: flex;
flex-direction: column;
align-items: center;
background: color-mix(in srgb, var(--accent-secondary) 22%, transparent);
}
/* ── Toolbar ────────────────────────────────── */
.pdf-toolbar {
position: sticky;
align-self: stretch;
top: 0;
z-index: 2;
display: flex;
@@ -420,6 +475,7 @@
flex-shrink: 0;
font-size: var(--step--1);
user-select: none;
overscroll-behavior: none;
}
.pdf-toolbar-btn {
@@ -505,6 +561,19 @@
.tfe-title {
font-size: var(--step-2);
}
/* PDF expands to full viewport height on mobile, above everything */
.pdf-expanded {
position: fixed;
inset: 0;
z-index: 100;
height: auto;
min-height: unset;
background: color-mix(in srgb, var(--accent-secondary) 92%, #000 8%);
}
.tfe-file-iframe {
height: 100dvh;
}
}
@media (max-width: 600px) {
+12 -11
View File
@@ -116,6 +116,7 @@
async function expand(container, pdfDoc) {
container.classList.remove("pdf-collapsed");
container.classList.add("pdf-expanded");
document.body.classList.add("pdf-viewer-open");
container.addEventListener("contextmenu", (e) => e.preventDefault());
container.addEventListener("keydown", (e) => {
@@ -132,10 +133,10 @@
if (oldThumb) oldThumb.remove();
// Build toolbar
buildToolbar(container, pdfDoc);
// Render pages directly into the wrapper
// Render pages directly into the wrapper, then build the toolbar
// (toolbar queries canvases, so it must run after they exist).
await renderAllPages(container, pdfDoc);
buildToolbar(container, pdfDoc);
container.scrollIntoView({ behavior: "smooth", block: "nearest" });
}
@@ -143,6 +144,7 @@
function collapse(container) {
container.classList.remove("pdf-expanded");
container.classList.add("pdf-collapsed");
document.body.classList.remove("pdf-viewer-open");
const oldToolbar = container.querySelector(".pdf-toolbar");
if (oldToolbar) oldToolbar.remove();
@@ -178,7 +180,7 @@
const rendering = new Set();
const resolveScale = () => {
const userScale = parseFloat(container.dataset.pdfScale ?? "1");
const userScale = parseFloat(container.dataset.pdfScale ?? "0.8");
const containerWidth = container.clientWidth - 16;
if (containerWidth <= 0) return userScale;
return (containerWidth / firstViewport.width) * userScale;
@@ -236,7 +238,7 @@
toolbar.className = "pdf-toolbar";
let currentPage = 1;
let currentScale = parseFloat(container.dataset.pdfScale ?? "1");
let currentScale = parseFloat(container.dataset.pdfScale ?? "0.8");
const numPages = pdfDoc.numPages;
const updateUI = () => {
@@ -261,14 +263,14 @@
const pageLabel = mkLabel("");
const zoomOutBtn = mkBtn("", "Zoom arrière", () => {
currentScale = Math.max(0.5, currentScale - 0.2);
currentScale = Math.max(0.5, currentScale - 0.05);
container.dataset.pdfScale = String(currentScale);
reRender(container, pdfDoc);
updateUI();
});
const zoomInBtn = mkBtn("+", "Zoom avant", () => {
currentScale = Math.min(3.0, currentScale + 0.2);
currentScale = Math.min(3.0, currentScale + 0.05);
container.dataset.pdfScale = String(currentScale);
reRender(container, pdfDoc);
updateUI();
@@ -296,9 +298,8 @@
updateUI();
const canvases = container.querySelectorAll(".pdf-canvas");
const onScroll = () => {
const canvases = container.querySelectorAll(".pdf-canvas");
const mid = container.scrollTop + container.clientHeight * 0.4;
let best = 1;
let bestDist = Infinity;
@@ -352,14 +353,14 @@
function reRender(container, pdfDoc) {
if (container._onScroll) {
container.removeEventListener("scroll", container._onScroll);
container._onScroll = null;
}
container.querySelectorAll(".pdf-canvas").forEach((c) => {
c.remove();
});
const oldToolbar = container.querySelector(".pdf-toolbar");
if (oldToolbar) oldToolbar.remove();
buildToolbar(container, pdfDoc);
renderAllPages(container, pdfDoc);
renderAllPages(container, pdfDoc).then(() => buildToolbar(container, pdfDoc));
}
// ── Bootstrap ──────────────────────────────────────────────────