fix(viewer): always restore text selection when pan is toggled off

This commit is contained in:
Anthony Stirling
2026-06-19 15:01:22 +01:00
parent e6d476297d
commit e104d47dc1
2 changed files with 30 additions and 17 deletions
@@ -1,5 +1,6 @@
import { useEffect, useRef } from "react";
import { usePan } from "@embedpdf/plugin-pan/react";
import { useInteractionManagerCapability } from "@embedpdf/plugin-interaction-manager/react";
import { useViewer } from "@app/contexts/ViewerContext";
import { useActiveDocumentId } from "@app/components/viewer/useActiveDocumentId";
import { useDocumentReady } from "@app/components/viewer/hooks/useDocumentReady";
@@ -21,13 +22,18 @@ export function PanAPIBridge() {
function PanAPIBridgeInner({ documentId }: { documentId: string }) {
const { provides: pan, isPanning } = usePan(documentId);
const { provides: imCapability } = useInteractionManagerCapability();
const { registerBridge, triggerImmediatePanUpdate } = useViewer();
// Keep pan ref updated to avoid re-running effect when object reference changes
// Keep refs updated to avoid re-running effect when object references change
const panRef = useRef(pan);
useEffect(() => {
panRef.current = pan;
}, [pan]);
const imRef = useRef(imCapability);
useEffect(() => {
imRef.current = imCapability;
}, [imCapability]);
// Track previous isPanning value to detect changes
const prevIsPanningRef = useRef<boolean>(isPanning);
@@ -39,6 +45,17 @@ function PanAPIBridgeInner({ documentId }: { documentId: string }) {
isPanning,
};
// Pan off must always land in selection (pointerMode), not the default mode -
// if pan ever became the default, disablePan/togglePan couldn't escape it (#5175).
const goToPointerMode = () => {
const im = imRef.current;
if (im) {
im.forDocument(documentId).activate("pointerMode");
} else {
currentPan.disablePan();
}
};
// Register this bridge with ViewerContext
registerBridge("pan", {
state: newState,
@@ -47,22 +64,20 @@ function PanAPIBridgeInner({ documentId }: { documentId: string }) {
currentPan.enablePan();
},
disable: () => {
currentPan.disablePan();
goToPointerMode();
},
toggle: () => {
currentPan.togglePan();
},
makePanDefault: () => {
// v2.5.0: makePanDefault may not exist, enable pan as fallback
if (
"makePanDefault" in currentPan &&
typeof (currentPan as any).makePanDefault === "function"
) {
(currentPan as any).makePanDefault();
if (isPanning) {
goToPointerMode();
} else {
currentPan.enablePan();
}
},
makePanDefault: () => {
// Never make pan the default mode (that is what locks the viewer in
// #5175). Just enable pan for the current interaction.
currentPan.enablePan();
},
},
});
@@ -75,7 +90,7 @@ function PanAPIBridgeInner({ documentId }: { documentId: string }) {
return () => {
registerBridge("pan", null);
};
}, [isPanning, registerBridge, triggerImmediatePanUpdate]);
}, [isPanning, registerBridge, triggerImmediatePanUpdate, documentId]);
return null;
}
@@ -211,12 +211,10 @@ export function useViewerWorkbenchBarButtons(
disabled:
!isPanning && pendingCount > 0 && redactionActiveType !== null,
onClick: () => {
// Don't optimistically flip isPanning - it must reflect the real EmbedPDF
// mode so the button can't show "off" while still stuck in pan (#5175).
viewer.panActions.togglePan();
setIsPanning((prev) => {
const next = !prev;
if (next && isRulerActive) setIsRulerActive?.(false);
return next;
});
if (!isPanning && isRulerActive) setIsRulerActive?.(false);
},
},
{