clipboard-drop
v0.1.1
Published
One small library for everything clipboard + paste + drag-and-drop: copy text/HTML/images, read the clipboard, handle paste (including pasted images/files), and build a dropzone. Framework-agnostic with optional React/Vue bindings.
Downloads
284
Maintainers
Readme
clipboard-drop
One small library for everything clipboard + paste + drag-and-drop: copy text/HTML/images, read the clipboard, handle paste-into-a-textbox (including pasted images/files), and build a dropzone — framework-agnostic, with optional React/Vue hooks.
TypeScript · Browser (Vite-friendly) · Graceful fallbacks for old browsers.
npm install clipboard-dropWhy
The Clipboard API, paste events, and the Drag-and-Drop API are three different, fiddly browser APIs that every app re-implements badly. clipboard-drop wraps all three with a consistent interface, sensible fallbacks, and "give me the files/images the user pasted or dropped" helpers.
1. Copy — copy()
import { copy } from "clipboard-drop";
await copy("hello"); // plain text
await copy({ text: "hello", html: "<b>hello</b>" }); // rich text
await copy({ image: blob }); // copy an image
await copy({ "application/json": JSON.stringify(x) });// custom MIME
const ok = await copy("hello"); // returns boolean; falls back to execCommand on old browsers"Copied!" state helper
import { useCopy } from "clipboard-drop/react"; // also /vue
function CopyButton({ text }) {
const { copy, copied } = useCopy({ timeout: 2000 });
return <button onClick={() => copy(text)}>{copied ? "Copied!" : "Copy"}</button>;
}2. Read — read()
import { readText, read } from "clipboard-drop";
const text = await readText(); // requires user gesture + permission
const items = await read(); // [{ type, kind, getText(), getBlob() }, ...]Permission-aware: returns a typed error if the user denies clipboard read.
3. Paste into a textbox — onPaste()
Handle paste events on an input/textarea/contenteditable, including pasted images and files (e.g. screenshots).
import { onPaste } from "clipboard-drop";
const off = onPaste(textareaEl, (data) => {
console.log(data.text); // pasted plain text
console.log(data.html); // pasted HTML (if any)
console.log(data.files); // File[] — e.g. a screenshot pasted with Ctrl+V
console.log(data.images); // File[] filtered to images
if (data.files.length) {
data.preventDefault(); // stop the browser inserting the file path/text
uploadScreenshot(data.files[0]);
}
});
off(); // remove the listenerCommon recipe: paste a screenshot to upload
onPaste(editor, async ({ images, preventDefault }) => {
if (!images.length) return;
preventDefault();
const url = await uploadImage(images[0]);
insertMarkdownImage(editor, url);
});Sanitized HTML paste (strip junk from Word/Google Docs)
onPaste(editor, ({ html, text, insertText }) => {
insertText(cleanHtml(html) ?? text); // helper inserts at caret, plays nice with undo
}, { sanitize: true });React hook:
import { usePaste } from "clipboard-drop/react";
const ref = usePaste((data) => { /* ... */ });
return <textarea ref={ref} />;4. Dropzone — dropzone()
Turn any element into a drag-and-drop file zone.
import { dropzone } from "clipboard-drop";
const zone = dropzone(el, {
accept: ["image/*", ".pdf"], // MIME globs or extensions
multiple: true,
maxFiles: 5,
maxSize: 5 * 1024 * 1024, // 5 MB per file
onDrop: (files) => upload(files), // accepted files
onReject: (rejections) => console.warn(rejections), // {file, reasons[]}
onDragOver:(active) => el.classList.toggle("dragging", active),
onError: (err) => {},
});
zone.open(); // programmatically open the OS file picker
zone.destroy(); // remove all listenersFeatures
- File-type filtering by extension and MIME glob (
image/*). - Size + count limits with structured rejection reasons.
- Folder drop support (recursively reads dropped directories where supported).
- Hover/active state callback for styling.
- Click-to-open file picker (acts like a hidden
<input type=file>). - Paste-to-dropzone: optionally also accept files pasted over the zone.
- Image previews helper (
getPreviewURL(file)→ object URL, auto-revoked). - Prevents the browser's default "open file in tab" behavior globally (opt-in).
React hook:
import { useDropzone } from "clipboard-drop/react";
function Uploader() {
const { getRootProps, getInputProps, isDragging, open } = useDropzone({
accept: ["image/*"],
onDrop: (files) => upload(files),
});
return (
<div {...getRootProps()} className={isDragging ? "active" : ""}>
<input {...getInputProps()} />
Drop images here, paste, or <button onClick={open}>browse</button>
</div>
);
}5. Unified "give me what the user provided"
When you want files from either paste or drop with one handler:
import { fileIntake } from "clipboard-drop";
const intake = fileIntake(el, {
accept: ["image/*"],
onFiles: (files, source) => upload(files), // source: "drop" | "paste"
});
intake.destroy();Fallbacks & compatibility
copy()uses the async Clipboard API, falling back todocument.execCommand("copy")via a hidden textarea on older browsers.- Clipboard read and image copy require secure context (HTTPS) + permission; the library detects support and returns typed errors instead of throwing.
- Drag-and-drop works everywhere; folder reading degrades gracefully where the entries API is unavailable.
import { isSupported } from "clipboard-drop";
isSupported(); // { copy:true, readText:true, readImage:false, dropzone:true }TypeScript
import type {
CopyPayload, PasteData, DropzoneOptions, FileRejection, IntakeSource
} from "clipboard-drop";Edge cases handled
- Pasted screenshots arrive as
Filewith no name → auto-named (pasted-image-<n>.png). - Multiple MIME types in one paste/copy.
- Caret-aware insert helpers preserve native undo.
- Object URLs from previews are revoked automatically on cleanup.
- Drop outside the zone never hijacks the page (opt-in global prevention).
Roadmap
- Svelte + Solid bindings.
- Chunked/large-file streaming hooks.
- Built-in client-side image downscale before upload (pairs well with an
image-compresspackage).
