From 2d34bbebc4885a79646007acd2d46583114b8f62 Mon Sep 17 00:00:00 2001 From: Dmytro Stanchiev Date: Wed, 8 Apr 2026 19:58:01 -0400 Subject: [PATCH] feat: add extractImageFromClipboard utility with broad image/* MIME matching --- src/lib/clipboard-image.ts | 45 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/lib/clipboard-image.ts diff --git a/src/lib/clipboard-image.ts b/src/lib/clipboard-image.ts new file mode 100644 index 0000000..e917416 --- /dev/null +++ b/src/lib/clipboard-image.ts @@ -0,0 +1,45 @@ +/** + * Extracts the first image File from a DataTransfer object. + * + * Resolution order (most → least reliable across browsers/OS): + * 1. clipboardData.files – browser-normalised FileList; Chrome/Linux/Mac/Safari + * all populate this for real paste events. Most reliable. + * 2. clipboardData.items – DataTransferItemList fallback for edge cases where + * files is empty but items contains a "file" kind entry. + * + * MIME matching: type.startsWith("image/") intentionally accepts any image + * subtype, including OS-specific variants like "image/x-png" on Linux that + * a strict allowlist (["image/png", ...]) would silently reject. + * + * The caller (onImageSelect / handleImageSelect) still runs validateImageFile + * which enforces the app's supported format allowlist — so we stay permissive + * here and strict at the validation boundary. + */ +export function extractImageFromClipboard( + clipboardData: DataTransfer | null | undefined, +): File | null { + if (!clipboardData) return null; + + // ── 1. files array (primary) ────────────────────────────────────────────── + const { files } = clipboardData; + if (files?.length) { + for (let i = 0; i < files.length; i++) { + const file = files[i]; + if (file.type.startsWith("image/")) return file; + } + } + + // ── 2. items fallback ───────────────────────────────────────────────────── + const { items } = clipboardData; + if (items?.length) { + for (let i = 0; i < items.length; i++) { + const item = items[i]; + if (item.kind === "file" && item.type.startsWith("image/")) { + const file = item.getAsFile(); + if (file) return file; + } + } + } + + return null; +}