) : null;
- const textEditorOverlay = isTextEditorOpen && textBoxPosition ? (
-
-
- ) : null;
-
- const canClickToEdit = selected && (annotationType === 'text' || annotationType === 'note') && !isTextEditorOpen;
-
return (
<>
- {/* Invisible wrapper that provides positioning - uses EmbedPDF's menuWrapperProps */}
+ {/* Invisible wrapper that provides positioning - uses EmbedPDF's menuWrapperProps.
+ Must stay pointerEvents:none so EmbedPDF's internal drag handlers receive events.
+ Edit Text button dispatches dblclick on this wrapper's parent to use EmbedPDF's built-in inline editing. */}
{typeof document !== 'undefined' && menuContent
? createPortal(menuContent, document.body)
: null}
- {typeof document !== 'undefined' && textEditorOverlay
- ? createPortal(textEditorOverlay, document.body)
- : null}
>
);
}
diff --git a/frontend/src/core/components/viewer/EmbedPdfViewer.tsx b/frontend/src/core/components/viewer/EmbedPdfViewer.tsx
index f6904eee3..f8f9359ac 100644
--- a/frontend/src/core/components/viewer/EmbedPdfViewer.tsx
+++ b/frontend/src/core/components/viewer/EmbedPdfViewer.tsx
@@ -1066,6 +1066,8 @@ const EmbedPdfViewerContent = ({
onDiscardAndContinue={async () => {
// Save applied redactions (if any) while discarding pending ones
await discardAndSaveApplied();
+ // Reset annotation changes ref so future show/hide doesn't re-prompt
+ hasAnnotationChangesRef.current = false;
}}
/>
)}
diff --git a/frontend/src/core/components/viewer/LocalEmbedPDF.tsx b/frontend/src/core/components/viewer/LocalEmbedPDF.tsx
index 6a8a1de6a..f7d7fc65e 100644
--- a/frontend/src/core/components/viewer/LocalEmbedPDF.tsx
+++ b/frontend/src/core/components/viewer/LocalEmbedPDF.tsx
@@ -1,5 +1,6 @@
import React, { useEffect, useMemo, useState } from 'react';
import { createPluginRegistration } from '@embedpdf/core';
+import type { PluginRegistry } from '@embedpdf/core';
import { EmbedPDF } from '@embedpdf/core/react';
import { usePdfiumEngine } from '@embedpdf/engines/react';
import { PrivateContent } from '@app/components/shared/PrivateContent';
@@ -24,7 +25,9 @@ import { AttachmentPluginPackage } from '@embedpdf/plugin-attachment/react';
import { PrintPluginPackage } from '@embedpdf/plugin-print/react';
import { HistoryPluginPackage } from '@embedpdf/plugin-history/react';
import { AnnotationLayer, AnnotationPluginPackage } from '@embedpdf/plugin-annotation/react';
+import type { AnnotationTool, AnnotationEvent } from '@embedpdf/plugin-annotation';
import { PdfAnnotationSubtype } from '@embedpdf/models';
+import type { PdfAnnotationObject, Rect } from '@embedpdf/models';
import { RedactionPluginPackage, RedactionLayer } from '@embedpdf/plugin-redaction/react';
import { CustomSearchLayer } from '@app/components/viewer/CustomSearchLayer';
import { ZoomAPIBridge } from '@app/components/viewer/ZoomAPIBridge';
@@ -68,7 +71,7 @@ interface LocalEmbedPDFProps {
enableFormFill?: boolean;
isManualRedactionMode?: boolean;
showBakedAnnotations?: boolean;
- onSignatureAdded?: (annotation: any) => void;
+ onSignatureAdded?: (annotation: PdfAnnotationObject) => void;
signatureApiRef?: React.RefObject
;
annotationApiRef?: React.RefObject;
historyApiRef?: React.RefObject;
@@ -80,7 +83,7 @@ interface LocalEmbedPDFProps {
export function LocalEmbedPDF({ file, url, fileName, enableAnnotations = false, enableRedaction = false, enableFormFill = false, isManualRedactionMode = false, showBakedAnnotations = true, onSignatureAdded, signatureApiRef, annotationApiRef, historyApiRef, redactionTrackerRef, fileId }: LocalEmbedPDFProps) {
const { t } = useTranslation();
const [pdfUrl, setPdfUrl] = useState(null);
- const [, setAnnotations] = useState>([]);
+ const [, setAnnotations] = useState>([]);
// Convert File to URL if needed
useEffect(() => {
@@ -125,7 +128,7 @@ export function LocalEmbedPDF({ file, url, fileName, enableAnnotations = false,
createPluginRegistration(ScrollPluginPackage),
createPluginRegistration(RenderPluginPackage, {
withForms: !enableFormFill,
- withAnnotations: showBakedAnnotations && !enableAnnotations, // Show baked annotations only when: visibility is ON and annotation layer is OFF
+ withAnnotations: !enableAnnotations, // Show baked annotations only when annotation layer is OFF; live layer visibility is controlled via CSS
}),
// Register interaction manager (required for zoom and selection features)
@@ -203,7 +206,7 @@ export function LocalEmbedPDF({ file, url, fileName, enableAnnotations = false,
// Register print plugin for printing PDFs
createPluginRegistration(PrintPluginPackage),
];
- }, [pdfUrl, enableAnnotations, showBakedAnnotations, fileName, file, url]);
+ }, [pdfUrl, enableAnnotations, fileName, file, url]);
// Initialize the engine with the React hook - use local WASM for offline support
const { engine, isLoading, error } = usePdfiumEngine({
@@ -280,7 +283,7 @@ export function LocalEmbedPDF({ file, url, fileName, enableAnnotations = false,
{
+ onInitialized={async (registry: PluginRegistry) => {
// v2.0: Use registry.getPlugin() to access plugin APIs
const annotationPlugin = registry.getPlugin('annotation');
if (!annotationPlugin || !annotationPlugin.provides) return;
@@ -289,10 +292,22 @@ export function LocalEmbedPDF({ file, url, fileName, enableAnnotations = false,
if (!annotationApi) return;
if (enableAnnotations) {
- const ensureTool = (tool: any) => {
+ // LooseAnnotationTool bypasses strict Partial defaults typing from the library —
+ // EmbedPDF accepts extra runtime properties (borderWidth, textColor, finishOnDoubleClick,
+ // etc.) that aren't reflected in the TypeScript model types.
+ type LooseAnnotationTool = {
+ id: string;
+ name: string;
+ interaction?: { exclusive: boolean; cursor: string; textSelection?: boolean; isRotatable?: boolean };
+ matchScore?: (annotation: PdfAnnotationObject) => number;
+ defaults?: Record;
+ clickBehavior?: Record;
+ behavior?: { deactivateToolAfterCreate?: boolean; selectAfterCreate?: boolean };
+ };
+ const ensureTool = (tool: LooseAnnotationTool) => {
const existing = annotationApi.getTool?.(tool.id);
if (!existing) {
- annotationApi.addTool(tool);
+ annotationApi.addTool(tool as unknown as AnnotationTool);
}
};
@@ -300,7 +315,7 @@ export function LocalEmbedPDF({ file, url, fileName, enableAnnotations = false,
id: 'highlight',
name: 'Highlight',
interaction: { exclusive: true, cursor: 'text', textSelection: true },
- matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.HIGHLIGHT ? 10 : 0),
+ matchScore: (annotation: PdfAnnotationObject) => (annotation.type === PdfAnnotationSubtype.HIGHLIGHT ? 10 : 0),
defaults: {
type: PdfAnnotationSubtype.HIGHLIGHT,
strokeColor: '#ffd54f',
@@ -317,7 +332,7 @@ export function LocalEmbedPDF({ file, url, fileName, enableAnnotations = false,
id: 'underline',
name: 'Underline',
interaction: { exclusive: true, cursor: 'text', textSelection: true },
- matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.UNDERLINE ? 10 : 0),
+ matchScore: (annotation: PdfAnnotationObject) => (annotation.type === PdfAnnotationSubtype.UNDERLINE ? 10 : 0),
defaults: {
type: PdfAnnotationSubtype.UNDERLINE,
strokeColor: '#ffb300',
@@ -334,7 +349,7 @@ export function LocalEmbedPDF({ file, url, fileName, enableAnnotations = false,
id: 'strikeout',
name: 'Strikeout',
interaction: { exclusive: true, cursor: 'text', textSelection: true },
- matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.STRIKEOUT ? 10 : 0),
+ matchScore: (annotation: PdfAnnotationObject) => (annotation.type === PdfAnnotationSubtype.STRIKEOUT ? 10 : 0),
defaults: {
type: PdfAnnotationSubtype.STRIKEOUT,
strokeColor: '#e53935',
@@ -351,7 +366,7 @@ export function LocalEmbedPDF({ file, url, fileName, enableAnnotations = false,
id: 'squiggly',
name: 'Squiggly',
interaction: { exclusive: true, cursor: 'text', textSelection: true },
- matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.SQUIGGLY ? 10 : 0),
+ matchScore: (annotation: PdfAnnotationObject) => (annotation.type === PdfAnnotationSubtype.SQUIGGLY ? 10 : 0),
defaults: {
type: PdfAnnotationSubtype.SQUIGGLY,
strokeColor: '#00acc1',
@@ -368,7 +383,7 @@ export function LocalEmbedPDF({ file, url, fileName, enableAnnotations = false,
id: 'ink',
name: 'Pen',
interaction: { exclusive: true, cursor: 'crosshair' },
- matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.INK ? 10 : 0),
+ matchScore: (annotation: PdfAnnotationObject) => (annotation.type === PdfAnnotationSubtype.INK ? 10 : 0),
defaults: {
type: PdfAnnotationSubtype.INK,
strokeColor: '#1f2933',
@@ -388,7 +403,7 @@ export function LocalEmbedPDF({ file, url, fileName, enableAnnotations = false,
id: 'inkHighlighter',
name: 'Ink Highlighter',
interaction: { exclusive: true, cursor: 'crosshair' },
- matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.INK && (annotation.strokeColor === '#ffd54f' || annotation.color === '#ffd54f') ? 8 : 0),
+ matchScore: (annotation: PdfAnnotationObject) => (annotation.type === PdfAnnotationSubtype.INK && (annotation.strokeColor === '#ffd54f' || annotation.color === '#ffd54f') ? 8 : 0),
defaults: {
type: PdfAnnotationSubtype.INK,
strokeColor: '#ffd54f',
@@ -408,7 +423,7 @@ export function LocalEmbedPDF({ file, url, fileName, enableAnnotations = false,
id: 'square',
name: 'Square',
interaction: { exclusive: true, cursor: 'crosshair' },
- matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.SQUARE ? 10 : 0),
+ matchScore: (annotation: PdfAnnotationObject) => (annotation.type === PdfAnnotationSubtype.SQUARE ? 10 : 0),
defaults: {
type: PdfAnnotationSubtype.SQUARE,
color: '#0000ff', // fill color (blue)
@@ -432,7 +447,7 @@ export function LocalEmbedPDF({ file, url, fileName, enableAnnotations = false,
id: 'circle',
name: 'Circle',
interaction: { exclusive: true, cursor: 'crosshair' },
- matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.CIRCLE ? 10 : 0),
+ matchScore: (annotation: PdfAnnotationObject) => (annotation.type === PdfAnnotationSubtype.CIRCLE ? 10 : 0),
defaults: {
type: PdfAnnotationSubtype.CIRCLE,
color: '#0000ff', // fill color (blue)
@@ -456,7 +471,7 @@ export function LocalEmbedPDF({ file, url, fileName, enableAnnotations = false,
id: 'line',
name: 'Line',
interaction: { exclusive: true, cursor: 'crosshair' },
- matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.LINE ? 10 : 0),
+ matchScore: (annotation: PdfAnnotationObject) => (annotation.type === PdfAnnotationSubtype.LINE ? 10 : 0),
defaults: {
type: PdfAnnotationSubtype.LINE,
color: '#1565c0',
@@ -480,7 +495,12 @@ export function LocalEmbedPDF({ file, url, fileName, enableAnnotations = false,
id: 'lineArrow',
name: 'Arrow',
interaction: { exclusive: true, cursor: 'crosshair' },
- matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.LINE && (annotation.endStyle === 'ClosedArrow' || annotation.lineEndingStyles?.end === 'ClosedArrow') ? 9 : 0),
+ matchScore: (annotation: PdfAnnotationObject) => {
+ if (annotation.type !== PdfAnnotationSubtype.LINE) return 0;
+ // EmbedPDF stores endStyle/lineEndingStyles at runtime; library types use lineEndings
+ const ann = annotation as PdfAnnotationObject & { endStyle?: string; lineEndingStyles?: { end?: string } };
+ return (ann.endStyle === 'ClosedArrow' || ann.lineEndingStyles?.end === 'ClosedArrow') ? 9 : 0;
+ },
defaults: {
type: PdfAnnotationSubtype.LINE,
color: '#1565c0',
@@ -505,7 +525,7 @@ export function LocalEmbedPDF({ file, url, fileName, enableAnnotations = false,
id: 'polyline',
name: 'Polyline',
interaction: { exclusive: true, cursor: 'crosshair' },
- matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.POLYLINE ? 10 : 0),
+ matchScore: (annotation: PdfAnnotationObject) => (annotation.type === PdfAnnotationSubtype.POLYLINE ? 10 : 0),
defaults: {
type: PdfAnnotationSubtype.POLYLINE,
color: '#1565c0',
@@ -526,7 +546,7 @@ export function LocalEmbedPDF({ file, url, fileName, enableAnnotations = false,
id: 'polygon',
name: 'Polygon',
interaction: { exclusive: true, cursor: 'crosshair' },
- matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.POLYGON ? 10 : 0),
+ matchScore: (annotation: PdfAnnotationObject) => (annotation.type === PdfAnnotationSubtype.POLYGON ? 10 : 0),
defaults: {
type: PdfAnnotationSubtype.POLYGON,
color: '#0000ff', // fill color (blue)
@@ -548,8 +568,8 @@ export function LocalEmbedPDF({ file, url, fileName, enableAnnotations = false,
ensureTool({
id: 'text',
name: 'Text',
- interaction: { exclusive: true, cursor: 'text' },
- matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.FREETEXT ? 10 : 0),
+ interaction: { exclusive: true, cursor: 'text', isRotatable: false },
+ matchScore: (annotation: PdfAnnotationObject) => (annotation.type === PdfAnnotationSubtype.FREETEXT ? 10 : 0),
defaults: {
type: PdfAnnotationSubtype.FREETEXT,
textColor: '#111111',
@@ -560,7 +580,7 @@ export function LocalEmbedPDF({ file, url, fileName, enableAnnotations = false,
contents: 'Text',
},
behavior: {
- deactivateToolAfterCreate: false,
+ deactivateToolAfterCreate: true,
selectAfterCreate: true,
},
});
@@ -568,8 +588,8 @@ export function LocalEmbedPDF({ file, url, fileName, enableAnnotations = false,
ensureTool({
id: 'note',
name: 'Note',
- interaction: { exclusive: true, cursor: 'pointer' },
- matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.FREETEXT ? 8 : 0),
+ interaction: { exclusive: true, cursor: 'pointer', isRotatable: false },
+ matchScore: (annotation: PdfAnnotationObject) => (annotation.type === PdfAnnotationSubtype.FREETEXT ? 8 : 0),
defaults: {
type: PdfAnnotationSubtype.FREETEXT,
textColor: '#1b1b1b',
@@ -584,7 +604,7 @@ export function LocalEmbedPDF({ file, url, fileName, enableAnnotations = false,
defaultSize: { width: 160, height: 100 },
},
behavior: {
- deactivateToolAfterCreate: false,
+ deactivateToolAfterCreate: true,
selectAfterCreate: true,
},
});
@@ -593,7 +613,7 @@ export function LocalEmbedPDF({ file, url, fileName, enableAnnotations = false,
id: 'stamp',
name: 'Image Stamp',
interaction: { exclusive: false, cursor: 'copy' },
- matchScore: (annotation: any) => (annotation.type === PdfAnnotationSubtype.STAMP ? 5 : 0),
+ matchScore: (annotation: PdfAnnotationObject) => (annotation.type === PdfAnnotationSubtype.STAMP ? 5 : 0),
defaults: {
type: PdfAnnotationSubtype.STAMP,
},
@@ -627,7 +647,7 @@ export function LocalEmbedPDF({ file, url, fileName, enableAnnotations = false,
},
});
- annotationApi.onAnnotationEvent((event: any) => {
+ annotationApi.onAnnotationEvent((event: AnnotationEvent) => {
if (event.type === 'create' && event.committed) {
setAnnotations(prev => [...prev, {
id: event.annotation.id,
@@ -635,19 +655,11 @@ export function LocalEmbedPDF({ file, url, fileName, enableAnnotations = false,
rect: event.annotation.rect
}]);
-
if (onSignatureAdded) {
onSignatureAdded(event.annotation);
}
} else if (event.type === 'delete' && event.committed) {
setAnnotations(prev => prev.filter(ann => ann.id !== event.annotation.id));
- } else if (event.type === 'loaded') {
- const loadedAnnotations = event.annotations || [];
- setAnnotations(loadedAnnotations.map((ann: any) => ({
- id: ann.id,
- pageIndex: ann.pageIndex || 0,
- rect: ann.rect
- })));
}
});
}
@@ -750,8 +762,9 @@ export function LocalEmbedPDF({ file, url, fileName, enableAnnotations = false,
}
+ style={!showBakedAnnotations ? { opacity: 0, pointerEvents: 'none' } : undefined}
/>
)}
diff --git a/frontend/src/core/components/viewer/LocalEmbedPDFWithAnnotations.tsx b/frontend/src/core/components/viewer/LocalEmbedPDFWithAnnotations.tsx
index ac7b58ac7..9b5a86fd2 100644
--- a/frontend/src/core/components/viewer/LocalEmbedPDFWithAnnotations.tsx
+++ b/frontend/src/core/components/viewer/LocalEmbedPDFWithAnnotations.tsx
@@ -1,5 +1,6 @@
import { useEffect, useMemo, useState } from 'react';
import { createPluginRegistration } from '@embedpdf/core';
+import type { PluginRegistry } from '@embedpdf/core';
import { EmbedPDF } from '@embedpdf/core/react';
import { usePdfiumEngine } from '@embedpdf/engines/react';
@@ -18,6 +19,8 @@ import { SearchPluginPackage } from '@embedpdf/plugin-search/react';
import { ThumbnailPluginPackage } from '@embedpdf/plugin-thumbnail/react';
import { RotatePluginPackage, Rotate } from '@embedpdf/plugin-rotate/react';
import { Rotation, PdfAnnotationSubtype } from '@embedpdf/models';
+import type { PdfAnnotationObject } from '@embedpdf/models';
+import type { AnnotationEvent } from '@embedpdf/plugin-annotation';
// Import annotation plugins
import { HistoryPluginPackage } from '@embedpdf/plugin-history/react';
@@ -41,7 +44,7 @@ const DOCUMENT_NAME = 'stirling-pdf-signing-viewer';
interface LocalEmbedPDFWithAnnotationsProps {
file?: File | Blob;
url?: string | null;
- onAnnotationChange?: (annotations: any[]) => void;
+ onAnnotationChange?: (annotations: PdfAnnotationObject[]) => void;
}
export function LocalEmbedPDFWithAnnotations({
@@ -187,7 +190,7 @@ export function LocalEmbedPDFWithAnnotations({
{
+ onInitialized={async (registry: PluginRegistry) => {
// v2.0: Use registry.getPlugin() to access plugin APIs
const annotationPlugin = registry.getPlugin('annotation');
if (!annotationPlugin || !annotationPlugin.provides) return;
@@ -223,10 +226,8 @@ export function LocalEmbedPDFWithAnnotations({
// Listen for annotation events to notify parent
if (onAnnotationChange) {
- annotationApi.onAnnotationEvent((event: any) => {
- if (event.committed) {
- // Get all annotations and notify parent
- // This is a simplified approach - in reality you'd need to get all annotations
+ annotationApi.onAnnotationEvent((event: AnnotationEvent) => {
+ if (event.type !== 'loaded' && event.committed) {
onAnnotationChange([event.annotation]);
}
});
@@ -295,7 +296,7 @@ export function LocalEmbedPDFWithAnnotations({
diff --git a/frontend/src/core/components/viewer/useViewerRightRailButtons.tsx b/frontend/src/core/components/viewer/useViewerRightRailButtons.tsx
index 224d3a022..6da7a3371 100644
--- a/frontend/src/core/components/viewer/useViewerRightRailButtons.tsx
+++ b/frontend/src/core/components/viewer/useViewerRightRailButtons.tsx
@@ -58,9 +58,11 @@ export function useViewerRightRailButtons(
useEffect(() => {
if (selectedTool === 'annotate') {
setIsAnnotationsActive(true);
- } else if (selectedTool === 'redact') {
+ } else if (selectedTool) {
+ // Any other tool is active — annotate button should not be highlighted
setIsAnnotationsActive(false);
} else {
+ // No tool selected — fall back to URL path check
setIsAnnotationsActive(isAnnotationsPath());
}
}, [selectedTool, isAnnotationsPath]);
diff --git a/frontend/src/core/components/viewer/viewerTypes.ts b/frontend/src/core/components/viewer/viewerTypes.ts
index c7cecfec2..b4f83ec7d 100644
--- a/frontend/src/core/components/viewer/viewerTypes.ts
+++ b/frontend/src/core/components/viewer/viewerTypes.ts
@@ -28,6 +28,10 @@ export interface AnnotationAPI {
getSelectedAnnotation: () => AnnotationSelection | null;
deselectAnnotation: () => void;
updateAnnotation: (pageIndex: number, annotationId: string, patch: AnnotationPatch) => void;
+ deleteAnnotation?: (pageIndex: number, annotationId: string) => void;
+ deleteAnnotations?: (annotations: Array<{ pageIndex: number; id: string }>) => void;
+ createAnnotation?: (pageIndex: number, annotation: Record