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

snapinp

v0.1.0

Published

One line. Instant UI. Every interaction. Automatically optimizes Interaction to Next Paint (INP).

Readme

SnapINP

npm version bundle size TypeScript License: MIT

One line. Instant UI. Every interaction.

SnapINP helps your app feel less frozen during expensive interactions by yielding to the browser between chunks of work.

It is designed to improve perceived responsiveness and Interaction to Next Paint (INP), not to magically reduce total CPU work.

npm install snapinp
import { snap } from "snapinp";
snap();

That's it. Your app can acknowledge clicks, inputs, and heavy UI updates sooner under load.

Demo

Watch the package in action:

SnapINP demo

Why Use It?

  • Show loading states and progress sooner during expensive interactions
  • Reduce the "the UI froze when I clicked" feeling
  • Improve INP without building your own scheduling layer
  • Stay framework-agnostic and dependency-free

See It In Action

  • Local demo:
npm install
npm run build
python -m http.server 4174

Then open http://127.0.0.1:4174/demo/index.html.


How It Works

When a user clicks a button, the browser needs to: run your JavaScript handler, update the DOM, and paint the result. If your handler takes too long, the browser can't paint — the UI feels frozen.

SnapINP helps in two ways:

  1. yieldToMain() lets you intentionally pause between chunks of expensive work so the browser can paint.
  2. snap() and wrap() help you apply that behavior with less manual plumbing.

The result is usually better responsiveness, even if total runtime is sometimes slightly higher.

Quick Example

Before:

button.addEventListener("click", async () => {
  setStatus("Loading...");
  const result = expensiveRender();
  showResult(result);
});

After:

import { yieldToMain } from "snapinp";

button.addEventListener("click", async () => {
  setStatus("Loading...");
  await yieldToMain();
  const result = expensiveRender();
  showResult(result);
});

API Reference

snap(options?): Disposable

Auto-mode. Patches addEventListener to wrap interaction handlers.

const { restore } = snap({
  threshold: 50,        // ms before yielding (default: 50)
  events: ["click"],    // which events to intercept (default: all interaction events)
  exclude: [".no-snap"], // CSS selectors to skip
  adaptive: true,       // back off when INP is already good (default: true)
  debug: false,         // structured debug logging (default: false)
});

// Undo all patches
restore();

wrap(fn, options?): fn

Manual mode. Wrap a single function without global patching.

import { wrap } from "snapinp";

const optimizedHandler = wrap(expensiveHandler, { threshold: 30 });
button.addEventListener("click", optimizedHandler);

yieldToMain(): Promise<void>

Cooperative yield point. This is the recommended approach for optimal INP.

import { yieldToMain } from "snapinp";

async function handleSearch(query: string) {
  updateSearchUI(query);        // instant visual feedback
  await yieldToMain();          // let the browser paint
  const results = search(query); // expensive work
  renderResults(results);
}

observe(callback): Disposable

Observe INP metrics without any patching. Tree-shakes cleanly (<1KB).

import { observe } from "snapinp";

const { disconnect } = observe((metric) => {
  console.log(`INP: ${metric.inp}ms on ${metric.target}`);
});

report(options?): INPReport

Get aggregated INP metrics. Optionally beacon on page hide.

import { report } from "snapinp";

const r = report({ beacon: "/api/vitals" });
console.log(`INP: ${r.inp}ms (${r.rating})`);

Framework Guides

React

For React apps, prefer yieldToMain() over snap() auto-mode to avoid state tearing:

import { yieldToMain } from "snapinp";
import { startTransition } from "react";

function SearchBar() {
  const handleInput = async (e: React.ChangeEvent<HTMLInputElement>) => {
    const query = e.target.value;
    await yieldToMain();
    startTransition(() => setResults(search(query)));
  };

  return <input onChange={handleInput} />;
}

Vue

SnapINP auto-mode works well with Vue — handlers are attached directly to elements:

import { snap } from "snapinp";
snap(); // Works with v-on directives

Svelte

Svelte compiles to direct addEventListener calls. Auto-mode works transparently:

import { snap } from "snapinp";
snap();

Angular

Load SnapINP AFTER Angular bootstraps (Zone.js patches addEventListener first):

platformBrowserDynamic().bootstrapModule(AppModule).then(() => {
  import("snapinp").then(({ snap }) => snap());
});

Vanilla JS

<script src="https://unpkg.com/snapinp/dist/index.global.js"></script>
<script>SnapINP.snap();</script>

Browser Support

| Browser | Version | Notes | | -------------- | ------- | ---------------------------------------- | | Chrome/Edge | 90+ | Full support including scheduler.yield | | Firefox | 95+ | Full support (MessageChannel fallback) | | Safari | 15.4+ | Full support (MessageChannel fallback) | | Node.js / SSR | Any | Safe no-op — all functions return defaults |


Honest Limitations

  1. Cannot fix synchronous long tasks. If your click handler runs a 300ms for loop, SnapINP cannot interrupt it mid-execution. It can detect it and warn. Use yieldToMain() in an async handler to break up the work.

  2. Monkey-patching caveats. snap() patches EventTarget.prototype.addEventListener. If another library patches it AFTER SnapINP, calling restore() will break that library's patch. Initialize SnapINP last, or use wrap() instead.

  3. Overhead budget. The wrapper adds ~0.01ms per handler invocation (two performance.now() calls). For the vast majority of apps this is negligible, but measure in your specific case.

  4. INP measurement requires PerformanceObserver event timing. Firefox and Safari have limited interactionId support. Metrics may be incomplete on non-Chromium browsers.

  5. It optimizes responsiveness, not raw throughput. In some cases total elapsed time can be slightly higher because yielding adds coordination overhead.


Comparison with Alternatives

| Feature | SnapINP | web-vitals | Manual scheduling | | ------------------------ | ------------- | ---------- | ----------------- | | Auto-patches handlers | Yes | No | No | | Observe-only mode | Yes | Yes | No | | Cooperative yield helper | yieldToMain | No | DIY | | Framework-agnostic | Yes | Yes | Varies | | Bundle size | <3KB | ~2KB | 0 (your code) | | Zero dependencies | Yes | Yes | N/A |


Contributing

See CONTRIBUTING.md for setup instructions, PR checklist, and coding conventions.


License

MIT