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

@wcstack/debounce

v1.13.0

Published

Declarative debounce/throttle components for Web Components. Framework-agnostic signal coalescing via wc-bindable-protocol.

Readme

@wcstack/debounce

@wcstack/debounce is a headless debounce / throttle component for the wcstack ecosystem.

It is not a visual UI widget. It is an async primitive node that coalesces a noisy stream of signals into a single quiet-period emission — the same way @wcstack/timer turns the passage of time into reactive state.

It ships two custom elements over one engine:

  • <wcs-debounce> — emit once after the signal has been idle for wait ms.
  • <wcs-throttle> — emit at most once per wait ms (debounce with maxWait === wait, leading on).

@wcstack/debounce follows the CSBC (Core / Shell / Binding Contract) architecture:

  • Core (DebounceCore) ports lodash's debounce algorithm (leading / trailing / maxWait) and publishes results as events.
  • Shell (<wcs-debounce> / <wcs-throttle>) connects that engine to DOM attributes, lifecycle, and declarative commands.
  • Binding Contract (static wcBindable) declares observable properties, writable inputs, and callable commands.

Two surfaces

The essence is "debouncing a signal". A signal can carry a value or be a bare pulse with arguments, so there are two surfaces — use one per element.

Value surface — sourcevalue

Write to source; after the quiet period the debounced value is published on the value property (event wcs-debounce:settled). Wire it as source: src; value: debouncedvalue flows back into state.

Signal surface — trigger(...args)fired

Call the trigger command repeatedly; after the quiet period one wcs-debounce:fired event carries the last args. Because state cannot read a transient pulse as a value, the relay uses tokens:

source →(command-token)→ debounce.trigger →[coalesce]→ fired →(event-token)→ state → target.method

State fires the entry with the command-token protocol (command.trigger: $command.X) and receives the single coalesced pulse with the event-token protocol (eventToken.fired: Y), then re-dispatches it to the real method.

A single element instance is meant for one surface. If both source and trigger are driven on the same element, the last scheduled signal wins (lodash's last-args semantics).

Install

npm install @wcstack/debounce

Quick Start

1. Debounce an input value

<script type="module" src="https://esm.run/@wcstack/state/auto"></script>
<script type="module" src="https://esm.run/@wcstack/debounce/auto"></script>

<wcs-state>
  <script type="module">
    export default {
      query: "",
      debouncedQuery: ""
    };
  </script>
</wcs-state>

<input data-wcs="value: query">
<wcs-debounce wait="300" data-wcs="source: query; value: debouncedQuery"></wcs-debounce>
<p>Searching for: {{ debouncedQuery }}</p>

2. Debounce a method call (signal surface)

<wcs-state>
  <script type="module">
    export default {
      $commandTokens: ["search"],
      $eventTokens: ["searchSettled"],
      query: "",
      $on: {
        searchSettled: (state, event) => {
          // fires once, 300ms after the last keystroke
          const [q] = event.detail.args;
          state.results = doSearch(q);
        }
      }
    };
  </script>
</wcs-state>

<input data-wcs="oninput: $command.search">
<wcs-debounce
  wait="300"
  data-wcs="command.trigger: $command.search; eventToken.fired: searchSettled">
</wcs-debounce>

3. Throttle a fast value stream

<wcs-throttle wait="100" data-wcs="source: scrollY; value: throttledScrollY"></wcs-throttle>

<wcs-throttle> leads by default (emits immediately, then at most once per wait ms).

Attributes / Inputs

| Attribute | Type | Default (<wcs-debounce>) | Default (<wcs-throttle>) | Description | | ------------- | ------- | -------------------------- | -------------------------- | ----------- | | wait | number | 250 | 250 | Quiet period in ms. Invalid / negative / non-numeric values fall back to the default. | | leading | boolean | off | on (no-leading opts out) | Emit on the first signal of a burst. | | no-trailing | boolean | off (trailing on) | off (trailing on) | Opt out of the trailing-edge emission. | | max-wait | number | none | wait | Force an emission at least every max-wait ms under continuous input. Clamped to >= wait. | | source | any | — | — | Value-surface input; its debounced echo returns on value. |

Observable Properties (outputs)

| Property | Event | Description | | --------- | ------------------------------ | ---------------------------------------------------- | | value | wcs-debounce:settled | The debounced value of the latest source write. | | fired | wcs-debounce:fired | The coalesced args of the latest trigger() pulse. | | pending | wcs-debounce:pending-changed | true while a debounce is in flight. |

<wcs-throttle> publishes the same shape under the wcs-throttle:* namespace.

Commands

| Command | Description | | --------- | ---------------------------------------------------------------- | | trigger | Signal-surface entry: coalesce a pulse carrying ...args. | | cancel | Drop any pending emission without firing (getters keep values). | | flush | Emit any buffered payload immediately (no-op if nothing pending). |

Optional DOM Triggering

When config.autoTrigger is on (default), a click on an element carrying data-debouncetarget="<id>" fires a single coalesced trigger() pulse on the referenced <wcs-debounce> / <wcs-throttle> (the click's default action is suppressed).

Headless usage (DebounceCore)

The Core has no DOM dependency and can be used directly with bind() from @wc-bindable/core:

import { DebounceCore } from "@wcstack/debounce";

const core = new DebounceCore("wcs-debounce", undefined, { wait: 300 });
core.addEventListener("wcs-debounce:settled", (e) => {
  console.log((e as CustomEvent).detail.value);
});
core.setSource("a");
core.setSource("b"); // only "b" settles, 300ms later

Throttle is the same engine with a different prefix and maxWait === wait:

const throttle = new DebounceCore("wcs-throttle", undefined, { wait: 100, leading: true, maxWait: 100 });

License

MIT