diff --git a/src/components/ai-toolbar.tsx b/src/components/ai-toolbar.tsx index 43f7658..01c7ae9 100644 --- a/src/components/ai-toolbar.tsx +++ b/src/components/ai-toolbar.tsx @@ -241,6 +241,8 @@ export const AIToolbar = ({ // Mark that the synchronous paste event handled images so keydown // doesn't double-fire const handlePasteHandled = (e: ClipboardEvent) => { + if (isEditableTarget(e.target)) return; + const images = extractAllImagesFromClipboard(e.clipboardData ?? null); if (images.length > 0) pasteHandledByEvent = true; }; diff --git a/tests/ai-toolbar.test.ts b/tests/ai-toolbar.test.ts index ec01646..b0f1769 100644 --- a/tests/ai-toolbar.test.ts +++ b/tests/ai-toolbar.test.ts @@ -376,6 +376,24 @@ describe("AI capture redesign", () => { expect(onImagesSelect).toHaveBeenCalledTimes(1); }); + test("capture-phase document paste ignores textarea targets before reading clipboard data", async () => { + await renderToolbar(); + + const handleCapturePaste = getDocumentListener( + "paste", + (entry) => entry.options?.capture === true, + ); + + expect(() => + handleCapturePaste({ + target: { tagName: "TEXTAREA", isContentEditable: false }, + get clipboardData() { + throw new Error("editable paste should stay on the component path"); + }, + } as unknown as Event), + ).not.toThrow(); + }); + test("document paste forwards clipboard images to onImagesSelect only for non-editable targets", async () => { const onImagesSelect = mock(); const image = new File(["image-bytes"], "clipboard.png", {