-
-
-
+ {frameOwnsZoomChrome ? null : (
+ <>
+
+
+
+ >
+ )}
- ) : null}
+ ) : (
+
+ )}
diff --git a/src/components/document-viewer/viewer-zoom.ts b/src/components/document-viewer/viewer-zoom.ts
new file mode 100644
index 0000000000..bb143e7a49
--- /dev/null
+++ b/src/components/document-viewer/viewer-zoom.ts
@@ -0,0 +1,18 @@
+/**
+ * Shared PDF/document viewing zoom contract (Phase 2a).
+ * DocumentFrame controls and PdfCanvasViewer must use the same clamp/step so
+ * Frame chrome and gesture/keyboard zoom stay aligned.
+ */
+export const VIEWER_MIN_ZOOM = 0.55;
+export const VIEWER_MAX_ZOOM = 4;
+export const VIEWER_ZOOM_STEP = 0.15;
+export const VIEWER_DEFAULT_ZOOM = 1.1;
+
+export function clampViewerZoom(value: number): number {
+ return Math.min(VIEWER_MAX_ZOOM, Math.max(VIEWER_MIN_ZOOM, value));
+}
+
+/** Resolve a zoom setter value against the latest known zoom (ref or React state). */
+export function resolveViewerZoomUpdate(current: number, next: number | ((current: number) => number)): number {
+ return clampViewerZoom(typeof next === "function" ? next(current) : next);
+}
diff --git a/src/components/ui/document-frame.tsx b/src/components/ui/document-frame.tsx
index e77ae4e251..02ed1c9053 100644
--- a/src/components/ui/document-frame.tsx
+++ b/src/components/ui/document-frame.tsx
@@ -3,6 +3,7 @@
import { CircleAlert, Eye, Loader2, Maximize2, Minus, Plus, RefreshCw } from "lucide-react";
import type { ReactNode } from "react";
+import { VIEWER_MAX_ZOOM, VIEWER_MIN_ZOOM, VIEWER_ZOOM_STEP } from "@/components/document-viewer/viewer-zoom";
import { cn, textMuted } from "@/components/ui-primitives";
export type DocumentFrameSource =
@@ -68,9 +69,9 @@ function boundedZoom(value: number, minimum: number, maximum: number) {
}
function DocumentControls({ controls }: { controls: DocumentFrameControls }) {
- const minimum = controls.minZoom ?? 0.5;
- const maximum = controls.maxZoom ?? 4;
- const step = controls.zoomStep ?? 0.25;
+ const minimum = controls.minZoom ?? VIEWER_MIN_ZOOM;
+ const maximum = controls.maxZoom ?? VIEWER_MAX_ZOOM;
+ const step = controls.zoomStep ?? VIEWER_ZOOM_STEP;
const zoom = boundedZoom(controls.zoom, minimum, maximum);
const zoomed = !controls.fitWidth;
const viewingAidActive = controls.viewingAid && !zoomed;
diff --git a/tests/document-frame-contract.test.ts b/tests/document-frame-contract.test.ts
index 241cf1527f..4989a172e2 100644
--- a/tests/document-frame-contract.test.ts
+++ b/tests/document-frame-contract.test.ts
@@ -35,16 +35,29 @@ describe("DocumentFrame contract", () => {
expect(imageOwnerSource).not.toMatch(forbiddenSourcePixelTreatment);
});
- it("adopts one frame around the existing PDF and non-PDF owners without adding another viewer toolbar", () => {
+ it("adopts one frame with DocumentFrame zoom/fit controls and keeps PDF page chrome separate", () => {
expect(viewerSource).toContain(
- 'import { DocumentFrame, type DocumentFrameSource } from "@/components/ui/document-frame"',
+ 'import { DocumentFrame, type DocumentFrameControls, type DocumentFrameSource } from "@/components/ui/document-frame"',
);
expect(viewerSource.match(/