diff --git a/frontend/public/locales/en-GB/translation.toml b/frontend/public/locales/en-GB/translation.toml
index beae38e178..16220a0c01 100644
--- a/frontend/public/locales/en-GB/translation.toml
+++ b/frontend/public/locales/en-GB/translation.toml
@@ -251,6 +251,8 @@ title = "How to add images"
move = "Move Image"
pause = "Pause placement"
place = "Place Image"
+placeMultiple = "Stay in placement mode after each placement"
+placeMultipleDesc = "Place several images in a row instead of exiting after the first."
resume = "Resume placement"
[addImage.results]
@@ -440,6 +442,8 @@ title = "How to add text"
move = "Move Text"
pause = "Pause placement"
place = "Place Text"
+placeMultiple = "Stay in placement mode after each placement"
+placeMultipleDesc = "Place several text stamps in a row instead of exiting after the first."
resume = "Resume placement"
[addText.results]
@@ -7127,6 +7131,8 @@ title = "How to add signature"
move = "Move Signature"
pause = "Pause placement"
place = "Place Signature"
+placeMultiple = "Stay in placement mode after each placement"
+placeMultipleDesc = "Place several signatures in a row instead of exiting after the first."
resume = "Resume placement"
[sign.results]
diff --git a/frontend/src/core/components/tools/sign/SignSettings.tsx b/frontend/src/core/components/tools/sign/SignSettings.tsx
index 17a348ed82..8090b743e7 100644
--- a/frontend/src/core/components/tools/sign/SignSettings.tsx
+++ b/frontend/src/core/components/tools/sign/SignSettings.tsx
@@ -11,6 +11,7 @@ import {
Tooltip,
Group,
Box,
+ Checkbox,
} from "@mantine/core";
import { SignParameters } from "@app/hooks/tools/sign/useSignParameters";
import { SuggestedToolsSection } from "@app/components/tools/shared/SuggestedToolsSection";
@@ -92,7 +93,13 @@ const SignSettings = ({
defaultSignatureSource,
}: SignSettingsProps) => {
const { t } = useTranslation();
- const { isPlacementMode, signaturesApplied, historyApiRef } = useSignature();
+ const {
+ isPlacementMode,
+ signaturesApplied,
+ historyApiRef,
+ placeMultiple,
+ setPlaceMultiple,
+ } = useSignature();
const { activeFileIndex } = useViewer();
const [historyAvailability, setHistoryAvailability] = useState({
canUndo: false,
@@ -1215,6 +1222,20 @@ const SignSettings = ({
{placementToggleControl}
+ setPlaceMultiple(event.currentTarget.checked)}
+ label={translate(
+ "mode.placeMultiple",
+ "Stay in placement mode after each placement",
+ )}
+ description={translate(
+ "mode.placeMultipleDesc",
+ "Place several stamps in a row instead of exiting after the first.",
+ )}
+ disabled={disabled}
+ />
+
{placementAlert.message}
diff --git a/frontend/src/core/components/viewer/SignatureAPIBridge.tsx b/frontend/src/core/components/viewer/SignatureAPIBridge.tsx
index b585eb8d31..dab1ada887 100644
--- a/frontend/src/core/components/viewer/SignatureAPIBridge.tsx
+++ b/frontend/src/core/components/viewer/SignatureAPIBridge.tsx
@@ -185,7 +185,16 @@ export const SignatureAPIBridge = forwardRef<
isPlacementMode,
placementPreviewSize,
setSignaturesApplied,
+ placeMultiple,
+ setPlacementMode,
} = useSignature();
+ // Track latest placeMultiple in a ref so the long-lived onAnnotationEvent
+ // subscription always reads the current value without re-subscribing on every
+ // toggle change (which would race with mid-flight create events).
+ const placeMultipleRef = useRef(placeMultiple);
+ useEffect(() => {
+ placeMultipleRef.current = placeMultiple;
+ }, [placeMultiple]);
const { getZoomState, registerImmediateZoomUpdate } = useViewer();
const documentReady = useDocumentReady();
const [currentZoom, setCurrentZoom] = useState(
@@ -555,6 +564,18 @@ export const SignatureAPIBridge = forwardRef<
// Mark signatures as not applied when a new signature is placed
if (event.type === "create") {
setSignaturesApplied(false);
+
+ // Auto-exit placement mode after a single STAMP placement unless the
+ // user opted into "place multiple". Only stamps trigger this — ink
+ // strokes (signatureInk) are also create events but a multi-stroke
+ // signature would break if we deactivated after the first stroke.
+ const annotationType =
+ annotation?.type ?? annotation?.object?.type ?? null;
+ const isStamp = annotationType === PdfAnnotationSubtype.STAMP;
+ if (isStamp && !placeMultipleRef.current) {
+ annotationApi.setActiveTool(null);
+ setPlacementMode(false);
+ }
}
const directData =
@@ -576,7 +597,13 @@ export const SignatureAPIBridge = forwardRef<
return () => {
unsubscribe?.();
};
- }, [annotationApi, storeImageData, setSignaturesApplied, documentReady]);
+ }, [
+ annotationApi,
+ storeImageData,
+ setSignaturesApplied,
+ setPlacementMode,
+ documentReady,
+ ]);
useEffect(() => {
if (!isPlacementMode || !documentReady) {
diff --git a/frontend/src/core/contexts/SignatureContext.tsx b/frontend/src/core/contexts/SignatureContext.tsx
index 1c1a53e05f..0c7263a513 100644
--- a/frontend/src/core/contexts/SignatureContext.tsx
+++ b/frontend/src/core/contexts/SignatureContext.tsx
@@ -23,6 +23,10 @@ interface SignatureState {
signaturesApplied: boolean;
// Size (in screen units) we want newly placed signatures to use
placementPreviewSize: { width: number; height: number } | null;
+ // When false (default), placement mode auto-exits after a single stamp is
+ // dropped so users don't accidentally place duplicates. When true, the user
+ // opted into staying in placement mode and can drop multiple stamps in a row.
+ placeMultiple: boolean;
}
// Signature actions interface
@@ -42,6 +46,7 @@ interface SignatureActions {
setPlacementPreviewSize: (
size: { width: number; height: number } | null,
) => void;
+ setPlaceMultiple: (enabled: boolean) => void;
}
// Combined context interface
@@ -62,6 +67,7 @@ const initialState: SignatureState = {
isPlacementMode: false,
signaturesApplied: true, // Start as true (no signatures placed yet)
placementPreviewSize: null,
+ placeMultiple: false,
};
// Provider component
@@ -174,6 +180,13 @@ export const SignatureProvider: React.FC<{ children: ReactNode }> = ({
[],
);
+ const setPlaceMultiple = useCallback((enabled: boolean) => {
+ setState((prev) => ({
+ ...prev,
+ placeMultiple: enabled,
+ }));
+ }, []);
+
// No auto-activation - all modes use manual buttons
const contextValue: SignatureContextValue = {
@@ -194,6 +207,7 @@ export const SignatureProvider: React.FC<{ children: ReactNode }> = ({
getImageData,
setSignaturesApplied,
setPlacementPreviewSize,
+ setPlaceMultiple,
};
return (