@wcstack/timer
v1.13.0
Published
Declarative timer component for Web Components. Framework-agnostic interval/timeout primitive via wc-bindable-protocol.
Maintainers
Readme
@wcstack/timer
@wcstack/timer is a headless timer component for the wcstack ecosystem.
It is not a visual UI widget.
It is an async primitive node that turns the passage of time into reactive state — the same way @wcstack/fetch turns a network request into reactive state.
With @wcstack/state, <wcs-timer> can be bound directly through path contracts:
- input surface:
interval,once,repeat,immediate,manual,trigger - output state surface:
tick,elapsed,running - commands:
start,stop,reset,pause,resume
triggeris a momentary command-property (an input), not a command: afalse→truewrite starts the timer. To start from state via the command-token protocol usecommand.start:(see Commands) — there is nocommand.trigger.
This means recurring work can be expressed declaratively in HTML, without writing setInterval(), clearInterval(), or teardown glue in your UI layer.
@wcstack/timer follows the CSBC (Core / Shell / Binding Contract) architecture:
- Core (
TimerCore) handles scheduling, tick counting, elapsed time, and pause/resume - Shell (
<wcs-timer>) connects that state to DOM attributes, lifecycle, and declarative commands - Binding Contract (
static wcBindable) declares observableproperties, writableinputs, and callablecommands
Why this exists
A timer is, like fetch, an asynchronous source of values over time. Imperatively it requires lifecycle management: starting, clearing, counting, and cleanup on disconnect.
@wcstack/timer moves that logic into a reusable component and exposes the result as bindable state. Time becomes a state transition, not imperative event wiring.
With @wcstack/state, the flow becomes:
<wcs-timer>is connected to the DOM and starts ticking- each tick increments
tickand updateselapsed - UI binds to those paths with
data-wcs - a state getter can react to
tickchanges and chain into other commands (e.g. trigger a<wcs-fetch>poll)
Install
npm install @wcstack/timerQuick Start
1. Reactive ticking from state
When <wcs-timer> is connected to the DOM, it automatically starts an interval timer. Bind tick / elapsed / running to state paths.
<script type="module" src="https://esm.run/@wcstack/state/auto"></script>
<script type="module" src="https://esm.run/@wcstack/timer/auto"></script>
<wcs-state>
<script type="module">
export default {
count: 0,
isRunning: false,
get statusLabel() {
return this.isRunning ? "Running" : "Stopped";
}
};
</script>
</wcs-state>
<wcs-timer
interval="1000"
data-wcs="tick: count; running: isRunning">
</wcs-timer>
<p data-wcs="textContent: count"></p>
<p data-wcs="textContent: statusLabel"></p>2. One-shot timeout (setTimeout equivalent)
once fires exactly one tick after interval ms, then auto-stops. (once is sugar for repeat="1".)
<wcs-timer interval="3000" once data-wcs="tick: showBanner"></wcs-timer>3. Bounded repetition
repeat="N" fires N ticks and then stops (running becomes false).
<wcs-timer interval="1000" repeat="5" data-wcs="tick: countdownStep"></wcs-timer>4. Fire immediately
immediate fires the first tick at start instead of waiting one full interval.
<wcs-timer interval="5000" immediate data-wcs="tick: pollNow"></wcs-timer>Attributes / Inputs
| Attribute | Type | Default | Description |
| ----------- | ------- | ------- | ------------------------------------------------------------------ |
| interval | number | 1000 | Tick period in milliseconds. Must be a finite value > 0; invalid values (0, negative, non-numeric) fall back to 1000. |
| once | boolean | false | Fire a single tick, then stop. Sugar for repeat="1". |
| repeat | number | 0 | Stop after N ticks (0 = unlimited). Takes precedence over once. |
| immediate | boolean | false | Fire one tick at start instead of waiting the first interval. |
| manual | boolean | false | Do not auto-start on connect; start via command / trigger. |
Observable Properties (outputs)
| Property | Event | Description |
| --------- | --------------------------- | ------------------------------------------------------ |
| tick | wcs-timer:tick | Tick counter, increments on every fire (reset to 0 on reset). |
| elapsed | wcs-timer:tick | Running time in ms since the last reset. |
| running | wcs-timer:running-changed | true while ticking, false when stopped/paused. |
Commands
| Command | Description |
| --------- | ----------------------------------------------------------------------- |
| start | Begin ticking (no-op if already running). |
| stop | Stop ticking; tick / elapsed are retained. |
| reset | Stop and reset tick / elapsed to 0. |
| pause | Suspend ticking, preserving the partial period and elapsed time. |
| resume | Continue from a pause, honoring the remaining time of the period. |
A live interval change is applied immediately only while the timer is running. Changing interval while paused has no effect on the current period; the new value takes effect on the next start.
State-driven invocation uses the command-token protocol:
<wcs-timer manual data-wcs="command.start: $command.beginPolling"></wcs-timer>Optional DOM Triggering
If autoTrigger is enabled (default), clicking an element carrying data-timertarget="<id>" calls start() on the referenced <wcs-timer>:
<button data-timertarget="poll">Start polling</button>
<wcs-timer id="poll" interval="5000" manual data-wcs="tick: pollNow"></wcs-timer>Event delegation is used, so it also works for dynamically added elements, and closest() handles nested targets (e.g. an icon inside the button). A matched click calls event.preventDefault() before starting the timer, so the element's default action is suppressed — do not put data-timertarget on an element whose default action you also want (a real <a href> link, a form-submit button), as it will be cancelled.
Configuration
bootstrapTimer() registers <wcs-timer> and optionally overrides defaults. Pass a partial config:
import { bootstrapTimer } from "@wcstack/timer";
bootstrapTimer({
autoTrigger: true, // enable data-timertarget click triggering (default: true)
triggerAttribute: "data-timertarget", // attribute scanned for click triggering
tagNames: {
timer: "wcs-timer", // custom element tag name
},
});getConfig() returns a deep-frozen snapshot of the current configuration:
import { getConfig } from "@wcstack/timer";
const { autoTrigger, triggerAttribute, tagNames } = getConfig();| Option | Type | Default | Description |
| ------------------ | -------------------- | ------------------ | --------------------------------------------------- |
| autoTrigger | boolean | true | Enable data-timertarget click triggering. |
| triggerAttribute | string | data-timertarget | Attribute scanned for DOM click triggering. |
| tagNames.timer | string | wcs-timer | Custom element tag name to register. |
Headless usage (TimerCore)
The Core has no DOM dependency and can be used directly with bind() from @wc-bindable/core:
import { TimerCore } from "@wcstack/timer";
const timer = new TimerCore();
timer.addEventListener("wcs-timer:tick", (e) => {
console.log((e as CustomEvent).detail); // { count, elapsed }
});
timer.start({ interval: 1000, repeat: 10 });License
MIT
