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

@symbo.ls/tap

v3.14.705

Published

Modern touch responsiveness for Symbols apps — the declarative FastClick successor: touch-action, tap-highlight, viewport guarantee, instant pressed feedback.

Readme

@symbo.ls/tap

Modern touch responsiveness for Symbols apps — the declarative successor to FastClick (FT Labs, archived 2022).

The subject, in short

Mobile browsers historically waited ~300ms after touchend before firing click, to disambiguate taps from double-tap-to-zoom. FastClick removed that delay by listening to touch events and synthesizing immediate clicks — which created its own bug family: ghost clicks, double-firing dropdowns, input focus quirks, and the needsclick escape hatch.

Browsers then fixed the root cause. The delay is gone when either:

  • the page declares <meta name="viewport" content="width=device-width"> (Chrome 32+, iOS Safari 9.3+), or
  • the element carries touch-action: manipulation (pan + pinch stay enabled, double-tap-zoom is disabled for that element).

So the modern solution is declarative — no event synthesis, no click suppression, no heuristics. That is what this plugin ships:

  1. Viewport guarantee — injects the device-width viewport meta only when the document lacks one (never overrides).
  2. touch-action: manipulation on every interactive element (tags + ARIA roles) — kills residual tap-to-zoom jank on controls.
  3. Tap-highlight removal on controls that own their pressed states — the OS gray/blue flash stops fighting design-system :active styles.
  4. Instant pressed feedback — passive, capture-phase pointer delegation stamps data-pressed on the pressed control. Style it from your design system ('[data-pressed]': { … }); nothing visual is imposed by default. Side effect: document-level pointer listeners also make CSS :active fire reliably on iOS Safari.

Deliberately not included by default: click synthesis or ghost-click suppression. With the declarative fixes in place, modern engines emit exactly one trusted click per tap; suppression heuristics are how FastClick ate legitimate clicks.

Usage

Runtime (DOMQL apps):

import { tapPlugin } from '@symbo.ls/tap'

// in the create() context
plugins: [tapPlugin]

Options ride the context: context.tap = { viewport, styles, pressed, pressedStyle, longPress } (all boolean except longPress, which is also boolean but can be an options object; defaults true, true, true, false, false).

SSR / static HTML (what the @symbo.ls/mermaid renderer does for every published site):

import { getTapStyles, getTapRuntimeSnippet } from '@symbo.ls/tap'

const head = `<style id="smbls-tap-styles">${getTapStyles()}</style>
<script>${getTapRuntimeSnippet()}</script>`

Both paths share the data-smbls-tap marker on <html> — whichever installs first wins; the other no-ops.

Long-press → contextmenu (opt-in)

iOS WKWebView never fires a native contextmenu on a long touch (and never for Apple Pencil), so any surface that wires per-item actions through onContextmenu / addEventListener('contextmenu', …) is unreachable by touch unless something synthesizes the event. longPress is the declarative, opt-in answer — OFF by default, because popping a menu is a UX decision a surface makes on purpose, not something every app should get for free:

// Runtime — every touch surface in this app gets the affordance:
plugins: [tapPlugin]
// context: { tap: { longPress: true } }

// or tune the defaults (500ms stationary press, 10px move tolerance):
// context: { tap: { longPress: { ms: 400, moveTolerance: 8 } } }

// SSR:
getTapRuntimeSnippet({ longPress: true })

A ~500ms stationary touch/pen press dispatches a real, bubbling MouseEvent('contextmenu', …) on the touched element — driving both DOMQL onContextmenu flat handlers and native addEventListener('contextmenu', …) listeners identically to an actual right-click. Mouse/trackpad presses are untouched (they already get a real contextmenu). This is not click synthesis — the FastClick trap this plugin otherwise avoids — it only ever dispatches contextmenu.

Gesture surfaces

Pair with the touchAction / overscrollBehavior element props (registered in css-in-props alongside this plugin):

  • touchAction: 'none' on drag handles and reorder surfaces (pointer-capture drags stop fighting page scroll)
  • touchAction: 'pan-y' on horizontal swipers
  • overscrollBehavior: 'contain' on inner scrollers (no scroll chaining)