Svelte
@watchstop/svelte adapter.
Wave 1 adapter exposing Store as a Svelte readable store so $ auto-subscription works. @watchstop/core is a peer dependency — install both.
Exact public names
createStopwatch is the entire public API.
type CreateStopwatchOptions = {
clock?: Clock
}
type StopwatchStore = Readable<number> & {
start: () => void
stop: () => void
reset: () => void
stopwatch: Stopwatch
}
declare function createStopwatch(options?: CreateStopwatchOptions): StopwatchStoreIt is named createStopwatch, not fromStore or toStore. svelte/store already exports both of those for converting between Svelte stores and runes.
createStopwatch
createStopwatch owns a Stopwatch and returns the Svelte custom-store shape: a subscribe for $ auto-subscription plus callable controls on the same object.
<script lang="ts">
import { createStopwatch } from '@watchstop/svelte'
const stopwatch = createStopwatch()
</script>
<p>{$stopwatch} ms</p>
<button onclick={stopwatch.start}>Start</button>
<button onclick={stopwatch.stop}>Stop</button>
<button onclick={stopwatch.reset}>Reset</button>- Construction is inert. Nothing is scheduled until
start(). - The component
<script>runs once, sostart,stop, andresetare bound once and keep the same identity for the life of the component. subscribecalls the listener synchronously with the current value before registering it, as Svelte's readable contract requires.stopwatch.stopwatchis the owned instance, exposed for passing elsewhere.
Options
| Option | Type | Purpose |
|---|---|---|
clock | Clock | Use this clock instead of detectClock(). Pass createMockClock() in tests. |
Teardown is tied to the component, not to subscriber count
createStopwatch registers onDestroy, so destroy() runs when the component that created the store is destroyed.
It deliberately does not destroy on last-unsubscribe, which is what svelte/store's readable start/stop notifier does. A Svelte store may be subscribed and unsubscribed many times during one component's life — {#if} blocks and conditional {$stopwatch} reads do exactly that — and a stopwatch is controlled imperatively, so it can legitimately be running with zero subscribers. Destroying at subscriber zero would silently kill a running stopwatch and leave a dead instance behind for the next subscriber. Component lifetime is the only boundary that matches who owns the instance.
Calling createStopwatch() outside component initialisation — at module level, for a singleton — is allowed and simply registers no automatic teardown. Call stopwatch.stopwatch.destroy() yourself in that case.
Sharing one stopwatch across components
Not supported in v1. Each createStopwatch() call constructs and owns its own instance, so two components calling it get two unrelated stopwatches, and there is no supported way to wrap a stopwatch handed in as a prop or through context.
Stopwatch is the only Store in core today, so a generic Store<T> → readable helper for instances the component does not own would have been speculative. Tracked in issue #6, to be revisited when Countdown / Ticker land. Until then, share the store object a single createStopwatch() call returns — a module-level call registers no automatic teardown, as above, and $store auto-subscription works from any component that imports it.
Contract
subscribecalls the listener synchronously with the current elapsed value before registering it, as Svelte's readable contract requires.- The returned unsubscribe function is the underlying
Stopwatch.subscribe's own, so$auto-subscription cleanup unsubscribes from the instance. - The
Readablehalf is value-only; controls are callable properties on the same object.
Re-render cost
$stopwatch is raw milliseconds delivered at the clock's tick cadence, so the markup reading it updates roughly 60 times a second under createBrowserClock. A readout that only shows whole seconds does about sixty times the work it needs.
A precision-style option that coarsens notifications is deliberately out of scope for this change and will follow separately. Until then, keep the $stopwatch read in the smallest possible component.