Extend touch-scroll rule to the global pointer layer and harden the pan config canary

This commit is contained in:
Anthony Stirling
2026-08-30 10:44:09 +01:00
parent 91d322dcaa
commit 1ebfc42db2
6 changed files with 24 additions and 6 deletions
@@ -43,6 +43,8 @@ function RedactionAPIBridgeInner({ documentId }: { documentId: string }) {
};
}, [setBridgeReady]);
// The interaction mode is viewer-global, so a stranded redaction mode would block
// selection for the next document.
useEffect(() => {
return () => {
try {
@@ -33,6 +33,7 @@ export function ViewerGlobalPointerProvider({
return (
<GlobalPointerProvider
documentId={documentId}
// Consumed by the touch-action rule in core/styles/theme.css.
data-viewer-touch-scroll={mode === POINTER_MODE ? "on" : "off"}
>
{children}
@@ -53,6 +54,7 @@ export function ViewerPagePointerProvider({
<PagePointerProvider
documentId={documentId}
pageIndex={pageIndex}
// Consumed by the touch-action rule in core/styles/theme.css.
className="pdf-page-pointer-layer"
>
{children}
@@ -3,12 +3,12 @@ import { PanPluginPackage } from "@embedpdf/plugin-pan";
import { VIEWER_PAN_CONFIG } from "@app/components/viewer/viewerPanConfig";
describe("viewer pan plugin config", () => {
it("resolves defaultMode to never so pan never becomes the interaction default", () => {
const resolved = {
...PanPluginPackage.manifest.defaultConfig,
...VIEWER_PAN_CONFIG,
};
it("overrides the plugin default so pan never becomes the interaction default", () => {
expect(VIEWER_PAN_CONFIG.defaultMode).toBe("never");
});
expect(resolved.defaultMode).toBe("never");
// Canary: if upstream ever ships a safe default, this override can go.
it("still needs the override because the plugin ships defaultMode mobile", () => {
expect(PanPluginPackage.manifest.defaultConfig.defaultMode).toBe("mobile");
});
});
@@ -1,5 +1,7 @@
import type { PanPluginConfig } from "@embedpdf/plugin-pan";
// The plugin's own defaultConfig is "mobile", which makes pan the global default
// mode on any touch-capable device and locks activateDefaultMode into pan (#5175).
export const VIEWER_PAN_CONFIG: PanPluginConfig = {
defaultMode: "never",
};
@@ -602,6 +602,8 @@
background-color: var(--p-gray-500) !important;
}
/* !important beats the inline touch-action:none the interaction manager writes for pointerMode. */
[data-viewer-touch-scroll="on"],
[data-viewer-touch-scroll="on"] .pdf-page-pointer-layer {
touch-action: pan-y pinch-zoom !important;
}
@@ -249,6 +249,16 @@ test.describe("touch-primary device", () => {
}),
).toBe("pan-y pinch-zoom");
// The global provider is an ancestor of the scroller, so its inline none would veto pinch-zoom.
expect(
await page.evaluate(() => {
const el = document.querySelector<HTMLElement>(
'[data-viewer-touch-scroll="on"]',
);
return el ? getComputedStyle(el).touchAction : null;
}),
).toBe("pan-y pinch-zoom");
const cdp = await page.context().newCDPSession(page);
const touchDrag = async (x: number, y: number, dx: number, dy: number) => {
await cdp.send("Input.dispatchTouchEvent", {