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

inputhub

v0.4.0

Published

A hub for registering input events, marking them as handled (fulfilled) and for getting rid of ghost mouse events on touch devices.

Readme

InputHub.js

A tiny javascript hub for recording input events, marking them as handled (fulfilled) and for filtering out ghost events.

Motivation

Input event handling in javascript works well ... until clicks are no longer enough, or you start nesting interactive elements within each other. All of a sudden you have multiple event handlers triggering, when you only wanted the "closest" one. Clicking an image in your fancy new gallery will select it ... until the click propagates through to the gallery window, where it is promptly deselected. Because clicking next to or between images should deselect them right?

Stack Overflow will recommend that you sprinkle your code with event.stopPropagation() (and probably event.preventDefault() for good measure), but this is an anti-pattern. stopPropagation is a Guillotine when all you want is an earmark. stopPropagation will completely stop the events, which means your analytics will not see the full picture, and your bootstrap dropdown menu will stay open since it listens to document clicks to know whether you still click around within it or not.

My motivation for creating InputHub was to simplify advanced event handling. The core idea is that the vast majority of input events only expect a single immediate result. This result is normally achieved by a single handler, the handler that is closest to the event.target. By earmarking the event as fulfilled, all later "main" handlers know that this event was not intended for them. Everything else is syntactic sugar and quality of life perks.

Ghost event cycle

const types = 'mousedown/mouseup/mousemove/pointerdown/pointerup/pointermove/touchstart/touchend/touchmove/click'.split('/');
types.forEach(type => document.addEventListener(type, event => console.log(event.type, event.target)));

Touch

pointerdown touchstart pointerup touchend mousemove mousedown mouseup click

Mouse

pointermove mousemove

pointerdown mousedown pointerup mouseup click

Example: Automatically "fulfilling" ghost mouse events.

After the touchend of a "tap", mobile browsers emit mousemove, mouseenter, mousedown, mouseup, click, all in one go. If you bind the same handler to both touchstart and mousedown, with no further detection, then your handler will execute twice on touch devices.

There is a very similar issue on desktop browsers, if you want to use pointerevents. You get in fact: pointerdown, mousedown, pointerup, mouseup, click. However, in this it is easy enough to bind to only one of pointerdown and mousedown, by checking pointer support. You can also simply use hub.pointerdown, which is a constant that resolves to either pointerdown or mousedown/touchstart.

In the example below, hub.fulfillGhost() is triggered during the capture phase of the event, i.e. when the event is on its way down from the document to the event.target. As the ghost events aren already fulfilled, hub.fulfill(event) will later return false and the event handler will short-circuit.

import InputHub from 'inputhub';

const hub = new InputHub();

// Fulfill ghost mouse events during the capture phase, i.e. during the events way from the document down to the target. (before normal handlers)
hub.on('mousedown/mouseup', hub.fulfillGhost.bind(hub), { capture: true, passive: true });

// Record events that have not yet been seen by InputHub. Needed for e.g. fulfillGhost to work.
hub.on('mousedown/mouseup/touchstart/touchend', (event) => {
  if (!hub.isFulfilled(event)) {
    hub.register(event);
  }
}, { passive: true });

hub.on('mousedown/touchstart', (event) => {
  if (!hub.fulfill(event)) {
    // Fulfilled events (e.g. our ghost events) will end up here.
    return;
  }
  if (event.type === 'mousedown') {
    console.log('This will only fire if a mouse was used.');
    // Your mouse code here
  }
  if (event.type === 'touchstart') {
    console.log('This will only fire on touch devices.');
    // Your touch code here
  }
});