diff --git a/.changeset/prompt-input-paste-text-with-image.md b/.changeset/prompt-input-paste-text-with-image.md
new file mode 100644
index 00000000..c5e66d8c
--- /dev/null
+++ b/.changeset/prompt-input-paste-text-with-image.md
@@ -0,0 +1,5 @@
+---
+"ai-elements": patch
+---
+
+Paste the text of a spreadsheet or document selection in PromptInput instead of attaching its image
diff --git a/apps/docs/content/components/(chatbot)/prompt-input.mdx b/apps/docs/content/components/(chatbot)/prompt-input.mdx
index ad2c9f5b..11950e4d 100644
--- a/apps/docs/content/components/(chatbot)/prompt-input.mdx
+++ b/apps/docs/content/components/(chatbot)/prompt-input.mdx
@@ -241,6 +241,7 @@ export async function POST(req: Request) {
- Auto-resizing textarea that adjusts height based on content
- File attachment support with drag-and-drop
+- Paste files from the clipboard; a spreadsheet or document selection pastes as text
- Built-in screenshot capture action
- Image preview for image attachments
- Configurable file constraints (max files, max size, accepted types)
diff --git a/packages/elements/__tests__/prompt-input.test.tsx b/packages/elements/__tests__/prompt-input.test.tsx
index d961a3ba..7927892e 100644
--- a/packages/elements/__tests__/prompt-input.test.tsx
+++ b/packages/elements/__tests__/prompt-input.test.tsx
@@ -1997,6 +1997,7 @@ describe("paste functionality", () => {
// Mock clipboardData items
pasteEvent.clipboardData = {
+ getData: () => "",
items: [
{
getAsFile: () => file,
@@ -2014,6 +2015,96 @@ describe("paste functionality", () => {
});
});
+ it("pastes the text of a spreadsheet selection instead of its image", async () => {
+ setupPromptInputTests();
+ const onSubmit = vi.fn();
+
+ const AttachmentConsumer = () => {
+ const attachments = usePromptInputAttachments();
+ return
{attachments.files.length}
;
+ };
+
+ render(
+
+
+
+
+
+
+ );
+
+ const textarea = screen.getByPlaceholderText(
+ "What would you like to know?"
+ );
+ textarea.focus();
+
+ const clipboardData = new DataTransfer();
+ clipboardData.setData("text/plain", "Q1\t42");
+ clipboardData.setData(
+ "text/html",
+ ""
+ );
+ clipboardData.items.add(
+ new File(["image"], "image.png", { type: "image/png" })
+ );
+ const pasteEvent = new ClipboardEvent("paste", {
+ bubbles: true,
+ cancelable: true,
+ clipboardData,
+ });
+
+ await act(() => {
+ textarea.dispatchEvent(pasteEvent);
+ });
+
+ expect(pasteEvent.defaultPrevented).toBeFalsy();
+ expect(screen.getByTestId("count")).toHaveTextContent("0");
+ });
+
+ it("attaches a copied file whose name is also on the clipboard as text", async () => {
+ setupPromptInputTests();
+ const onSubmit = vi.fn();
+
+ const AttachmentConsumer = () => {
+ const attachments = usePromptInputAttachments();
+ return {attachments.files.length}
;
+ };
+
+ render(
+
+
+
+
+
+
+ );
+
+ const textarea = screen.getByPlaceholderText(
+ "What would you like to know?"
+ );
+ textarea.focus();
+
+ const clipboardData = new DataTransfer();
+ clipboardData.setData("text/plain", "report.png");
+ clipboardData.items.add(
+ new File(["image"], "report.png", { type: "image/png" })
+ );
+ const pasteEvent = new ClipboardEvent("paste", {
+ bubbles: true,
+ cancelable: true,
+ clipboardData,
+ });
+
+ await act(() => {
+ textarea.dispatchEvent(pasteEvent);
+ });
+
+ await vi.waitFor(() => {
+ expect(screen.getByTestId("count")).toHaveTextContent("1");
+ });
+ expect(pasteEvent.defaultPrevented).toBeTruthy();
+ });
+
it("handles paste with no files", () => {
setupPromptInputTests();
const onSubmit = vi.fn();
diff --git a/packages/elements/src/prompt-input.tsx b/packages/elements/src/prompt-input.tsx
index 412c846d..3deb37c6 100644
--- a/packages/elements/src/prompt-input.tsx
+++ b/packages/elements/src/prompt-input.tsx
@@ -1019,6 +1019,14 @@ export const PromptInputTextarea = ({
return;
}
+ // Copied spreadsheet cells also carry a picture of the selection; paste their text instead
+ if (
+ event.clipboardData.getData("text/plain") &&
+ event.clipboardData.getData("text/html")
+ ) {
+ return;
+ }
+
const files: File[] = [];
for (const item of items) {