Ctrl and the wheel over a PDF

The same gesture Aether has, whose comment already claimed InkNote had it.
Registered by hand rather than through onWheel, because React attaches wheel
listeners passively and a passive listener may not call preventDefault —
without which the browser zooms the whole application instead of the
document.

What stays still depends on how the zoom was asked for, and the two answers
differ: the wheel holds the point under the pointer, the buttons hold the
page being read. The buttons held nothing at all before, so three presses of
+ left you somewhere else in the file; that came out of building this and is
fixed with it.

The checks are the step, that the default really was prevented, and that a
point measured on the page is still under the pointer afterwards.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
marcandClaude Opus 5 committed 2026-08-25 15:00:54 +02:00
1 parent 8f8fecfb96
commit 580eed8992
4 files changed
+211 -7

No files matched your search

+78
View File
@@ -270,6 +270,84 @@ try {
await p.getByTitle("Zoom out").click();
await p.waitForTimeout(1500);
/* ── Ctrl and the wheel ──
* The claim is not only "the number changes". It is that the browser is kept
* out of it — an unprevented Ctrl+wheel zooms the whole application, toolbar
* and all — and that the spot under the pointer is the spot that stays put,
* which is the whole difference between a viewer that feels steady and one
* that jumps to the top of the page at every step. */
const zoomNow = () => p.locator("[data-pdf-zoom]").evaluate((el) => Number(el.dataset.pdfZoom));
const pageRect = () =>
p.locator('[data-pdf-page="1"]').evaluate((el) => {
const r = el.getBoundingClientRect();
return { top: r.top, height: r.height };
});
const firstPage = await p.locator('[data-pdf-page="1"]').boundingBox();
// A point well inside the page, and low enough that holding it still means
// scrolling rather than doing nothing.
const at = { x: firstPage.x + firstPage.width * 0.5, y: firstPage.y + firstPage.height * 0.6 };
const beforeRect = await pageRect();
const fraction = (at.y - beforeRect.top) / beforeRect.height;
const zoomBefore = await zoomNow();
const wheeled = await p.evaluate(
([x, y]) => {
const el = document.elementFromPoint(x, y);
let prevented = null;
const spy = (e) => (prevented = e.defaultPrevented);
// Added after the viewer's own listener, so it runs after it and can see
// whether the default was called off.
document.addEventListener("wheel", spy, { passive: true });
el.dispatchEvent(
new WheelEvent("wheel", { deltaY: -120, ctrlKey: true, bubbles: true, cancelable: true, clientX: x, clientY: y })
);
document.removeEventListener("wheel", spy);
return prevented;
},
[at.x, at.y]
);
await p.waitForTimeout(2000);
check((await zoomNow()) > zoomBefore, "Ctrl and the wheel zoom in", `${zoomBefore} → ${await zoomNow()}`);
check(wheeled === true, "and the browser is stopped from zooming itself instead", `defaultPrevented ${wheeled}`);
const afterRect = await pageRect();
const held = afterRect.top + fraction * afterRect.height;
check(
Math.abs(held - at.y) < 12,
"and the spot under the pointer stays under the pointer",
`${Math.round(at.y)} → ${Math.round(held)}`
);
await p.evaluate(
([x, y]) => {
document
.elementFromPoint(x, y)
.dispatchEvent(
new WheelEvent("wheel", { deltaY: 120, ctrlKey: true, bubbles: true, cancelable: true, clientX: x, clientY: y })
);
},
[at.x, at.y]
);
await p.waitForTimeout(1800);
check((await zoomNow()) === zoomBefore, "and the other way out again", `${await zoomNow()}`);
/* The buttons hold something else still: the page being read. "Show me this
page larger" should not answer by moving to another one. */
await p.locator('[data-pdf-page="2"]').scrollIntoViewIfNeeded();
await p.waitForTimeout(1200);
const readingBefore = await p.locator("[data-pdf-position]").innerText();
await p.getByTitle("Zoom in").click();
await p.waitForTimeout(2200);
check(
(await p.locator("[data-pdf-position]").innerText()) === readingBefore,
"zooming from the buttons keeps the page being read",
`${readingBefore} → ${await p.locator("[data-pdf-position]").innerText()}`
);
await p.getByTitle("Zoom out").click();
await p.waitForTimeout(1500);
/* ── hiding it ── */
await p.locator("[data-pdf-hide-ink]").click();
await p.waitForTimeout(600);