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

@cujuju/solidjs-hold-action

v0.1.0

Published

Press-and-hold primitive for SolidJS — useHoldAction hook + HoldIndicator visual. Stages, hover trigger, click suppression.

Readme

@cujuju/solidjs-hold-action

Press-and-hold primitive for SolidJS. Pair the useHoldAction hook (timing + event handlers + progress signal) with the <HoldIndicator> component (SVG ring / rectangle border trace / linear bar) for hold-to-confirm UX.

Install

pnpm add @cujuju/solidjs-hold-action

No stylesheet — <HoldIndicator> uses inline SVG with currentColor by default.

Usage

import { useHoldAction, HoldIndicator } from '@cujuju/solidjs-hold-action';

function DeleteButton() {
  const hold = useHoldAction({
    durationMs: 1500,
    onComplete: () => performDelete(),
  });
  return (
    <button
      {...hold.handlers}
      style={{ position: 'relative', color: 'red' }}
      title="Hold to delete"
    >
      <Icon name="trash-2" />
      <Show when={hold.holding()}>
        <HoldIndicator progress={hold.progress} shape="rect" />
      </Show>
    </button>
  );
}

useHoldAction(options)

| Option | Default | Description | |---|---|---| | durationMs | (required) | Duration to complete. Intentionally has no default — pick per context (250ms for confirmations, 1500-2000ms for destructive). | | onComplete | (required) | Called once when progress reaches 1. | | onProgress(p) | — | RAF-tick callback with progress 0→1. Use for custom visuals that need per-frame access. | | stages | — | Array of { at: ms, onReach } intermediate callbacks. Each fires once per hold when its at ms is crossed. Not re-fired if the hold cancels. | | trigger | 'press' | 'press' = pointerdown starts, pointerup stops. 'hover' = pointerenter starts, pointerleave stops. | | enabled | () => true | Accessor; when false, holds do not start. | | onCancel | — | Called when an in-progress hold is cancelled (pointerup, pointerleave, doc-mouseup, or imperative cancel()). Does NOT fire after onComplete, and does NOT fire on component cleanup. Use for reverting visual state set during the hold. | | suppressClickAfterComplete | true | Completed hold swallows the subsequent click event (prevents double-firing with the parent element's click handler). | | cancelOnLeave | true | Pointer leaving the element cancels. | | cancelOnDocumentMouseUp | true | Mouse up anywhere on the document cancels an in-progress hold (catches release outside the element). |

Returns

{
  handlers: {
    onPointerDown, onPointerUp, onPointerEnter, onPointerLeave, onClick
  },                           // spread onto the target element
  progress: Accessor<number>,  // 0 → 1
  holding: Accessor<boolean>,
  cancel: () => void,
}

<HoldIndicator>

| Prop | Default | Description | |---|---|---| | progress | (required) | Accessor or plain number 0→1. | | shape | 'circle' | 'circle' / 'rect' / 'bar'. | | width, height, size | — | Raw sizing. size is shorthand for square. | | fillParent | true when no explicit size | Positions absolutely inside a position: relative parent with inset: 0. | | stroke | 'currentColor' | Path color. | | strokeWidth | 2 | In px. | | strokeLinecap | 'round' (circle, bar), 'square' (rect) | | | easing | — | Optional (t: number) => number curve applied to progress before geometry is computed. Output is clamped to [0, 1], so overshoot easings won't break geometry. Default is linear (no transform). Examples: ease-in t => t*t, ease-out t => 1 - Math.pow(1-t, 3). | | radius | 6 | Corner radius for shape="rect". | | startAngle | 0 (top) | Circle only — offset in degrees. | | direction | 'clockwise' | | | class, style | — | Passthrough. |

The component measures its parent (or self, when explicit size) via getBoundingClientRect + ResizeObserver for correct geometry.

Multi-stage holds

const hold = useHoldAction({
  durationMs: 3000,
  onComplete: () => finalAction(),
  stages: [
    { at: 1000, onReach: () => navigator.vibrate?.(10) },
    { at: 2000, onReach: () => navigator.vibrate?.([20, 20, 20]) },
  ],
});

Hover trigger

const hold = useHoldAction({
  durationMs: 800,
  trigger: 'hover',
  onComplete: () => openFlyout(),
});
return (
  <div onPointerEnter={hold.handlers.onPointerEnter} onPointerLeave={hold.handlers.onPointerLeave}>
    …
  </div>
);

Suppression details

The click event fires AFTER pointerup on mouse interactions. Without suppression, a hold that completes on pointerup would also fire the parent's onClick (e.g., opening a popover). With suppressClickAfterComplete: true (default), the hold-action's onClick handler in the returned handlers object will preventDefault + stopPropagation on the next click event immediately after a completed hold. Tap-without-holding still fires onClick normally.

Future ideas

  • Keyboard support (Space/Enter held) — deferred pending accessibility review for destructive actions
  • Touch-specific duration presets
  • Haptic feedback integration

License

MIT