npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

paste-rich

v1.0.0

Published

Smart clipboard paste handler. Normalizes paste events into predictable types — image, file, HTML, or text. Zero dependencies.

Readme

📋 paste-rich

Smart clipboard paste handler. Normalizes paste events into predictable types — image, file, HTML, or text — in 3 lines. Zero dependencies.

npm version npm downloads bundle size license TypeScript zero dependencies

Live Demo →


Why

Handling paste events in the browser is deceptively complex. Screenshots paste as image/png blobs. Word documents include both HTML and plain text. File explorers inject DataTransferItem lists. Every app — Notion clones, WYSIWYG editors, comment boxes — ends up writing its own clipboardData parsing from scratch.

There's no standardized solution. Developers re-discover the same quirks every time: items vs files, getData('text/html') returning empty strings, images hidden inside DataTransferItem with kind === 'file'.

paste-rich normalizes all of it. ~0.8kB gzipped. Zero dependencies. One callback, one predictable shape: { type, data, files }.


Install

npm install paste-rich
# or
yarn add paste-rich
# or
pnpm add paste-rich

Quick Start

Listen for all paste types

import PasteRich from 'paste-rich';

const pr = new PasteRich({
  target: '#editor',
  onPaste: (result) => {
    console.log(result.type);  // 'image' | 'file' | 'html' | 'text'
    console.log(result.data);  // File or string
    console.log(result.files); // File[] (empty for text/html)
  },
});

Filter to specific types

import PasteRich from 'paste-rich';

const pr = new PasteRich({
  target: '#drop-zone',
  types: ['image'],
  onPaste: ({ data }) => {
    const url = URL.createObjectURL(data as File);
    document.getElementById('preview')!.src = url;
  },
});

Features

  • Zero dependencies — pure TypeScript, no external packages
  • Normalized output — every paste becomes { type, data, files }, no guesswork
  • Smart detection — images, files, rich HTML, and plain text, prioritized in that order
  • Type filtering — pass types: ['image', 'text'] to ignore everything else
  • Multiple files — all pasted files available via result.files
  • Flexible targeting — attach to any element via selector or reference, or the whole document
  • preventDefault control — on by default, opt out with preventDefault: false
  • SSR safe — guards all DOM access behind typeof window
  • ~0.8kB minified + gzipped

API

new PasteRich(options)

| Option | Type | Default | Description | |--------|------|---------|-------------| | target | HTMLElement \| string | document | Element or CSS selector to listen on | | onPaste | (result: PasteResult) => void | — | Required. Called with the normalized paste result | | types | PasteType[] | all | Filter to specific types; unmatched pastes are ignored | | preventDefault | boolean | true | Whether to call preventDefault() on handled paste events |

PasteResult

| Property | Type | Description | |----------|------|-------------| | type | 'image' \| 'file' \| 'html' \| 'text' | Detected paste type (highest priority match) | | data | string \| File | Primary data — File for image/file, string for html/text | | files | File[] | All files from the paste (empty array for text/html-only) |

Instance methods

| Method | Returns | Description | |--------|---------|-------------| | .destroy() | void | Remove the paste listener and clean up | | [Symbol.dispose]() | void | Alias for destroy() — enables using syntax |

Detection priority

| Priority | Type | Condition | |----------|------|-----------| | 1 | image | clipboardData.items with type.startsWith('image/') | | 2 | file | clipboardData.files with non-image MIME types | | 3 | html | clipboardData.getData('text/html') is non-empty | | 4 | text | clipboardData.getData('text/plain') fallback |


Examples

React hook

import { useEffect, useRef } from 'react';
import PasteRich, { PasteResult } from 'paste-rich';

function usePasteRich(
  ref: React.RefObject<HTMLElement>,
  onPaste: (result: PasteResult) => void
) {
  const prRef = useRef<PasteRich | null>(null);

  useEffect(() => {
    if (!ref.current) return;
    prRef.current = new PasteRich({
      target: ref.current,
      onPaste,
    });
    return () => prRef.current?.destroy();
  }, [ref, onPaste]);
}

Vue composable

import { onMounted, onUnmounted, ref } from 'vue';
import PasteRich, { PasteResult } from 'paste-rich';

export function usePasteRich(selector: string) {
  let pr: PasteRich;
  const lastPaste = ref<PasteResult | null>(null);

  onMounted(() => {
    pr = new PasteRich({
      target: selector,
      onPaste: (result) => { lastPaste.value = result; },
    });
  });

  onUnmounted(() => pr?.destroy());

  return { lastPaste };
}

Image upload on paste

import PasteRich from 'paste-rich';

const pr = new PasteRich({
  types: ['image'],
  onPaste: async ({ data }) => {
    const form = new FormData();
    form.append('image', data as File);
    await fetch('/api/upload', { method: 'POST', body: form });
  },
});

CDN (no build step)

<script type="module">
  import PasteRich from 'https://esm.sh/paste-rich';

  new PasteRich({
    target: '#editor',
    onPaste: ({ type, data }) => {
      console.log('Pasted:', type, data);
    },
  });
</script>

License

MIT