@xcwds/plugin-timers

Timers for an @xcwds app, extracted from xcwds.github.io's cooking and coffee timers. They count against wall-clock end times, so they stay right when a phone suspends the tab, and they're saved, so a reload resumes them. The app owns them, not a page: on every page a finished timer rings, a running one keeps the screen awake, and an app update waits until they stop.

// xcwds.config.ts
import timers from '@xcwds/plugin-timers';

export default defineConfig({
	brand,
	plugins: [shell({ sections }), timers({ page: '/utils/timer' })]
});
/* src/app.css, after Tailwind and the shell's styles */
@import '@xcwds/plugin-timers/styles.css';
<!-- src/routes/+layout.svelte: finished timers show as alerts in the notification stack -->
<Shell>
	{@render children()}
	{#snippet notices()}<TimerAlert />{/snippet}
</Shell>

<!-- src/routes/utils/timer/+page.svelte -->
<main class="page-narrow"><TimerList /></main>

Options

  • page: the timers page (an app path), for the alert's Open link.
  • storageKey: where timers are saved (default app:timers:timers). Set it to keep a key your app already uses, e.g. app:cooking-timer:timers for an app moving from xcwds.github.io's own code, which saves the same shape.

What it adds

  • app.timers:
    • create(label, ms) starts a timer of up to 7 days. Call it from a tap, so the alarm can play sound later on iOS.
    • toggle(id) pauses or resumes, add(id, ms) adds time (or snoozes a finished timer), reset(id) and remove(id).
    • list(), get(id), remaining(item), ringing(item), anyRunning() and now().
    • subscribe(listener) calls back on every change and on each tick while a timer runs.
    • show() says a page lists every timer, so <TimerAlert> stays out of the way. Recipe step timers and other plugins use the same timers.
  • The alarm: a finished timer beeps (Web Audio) and vibrates every 3 seconds until it's stopped or snoozed.
  • The alarm setting: { sound, vibration, keepAwake }, all on by default, in the timers settings section. keepAwake holds a screen wake lock while a timer runs.
  • An onBeforeReload reason, running timers, so @xcwds/plugin-update's banner asks to finish first.
  • Components:
    • TimerList.svelte: every timer, plus a form to start one.
    • Countdown.svelte: one timer's remaining time, <Countdown id={item.id} />.
    • TimerAlert.svelte: finished timers with +1 min, Stop and Open (openLabel names the Open link for screen readers; defaults to "Open timers").
  • Pure functions from the main entry (start, pause, add, remaining, formatDuration and others) for tests and custom timers.

API

From the package's types and doc comments.

Options (TimersOptions)

page? string
The page that lists timers (an app path), for the Open link on finished-timer alerts.
storageKey? string
Where timers are saved, as a full key starting with the storage prefix. Defaults to app:timers:timers; set it to keep the key an app already uses (xcwds.github.io: app:cooking-timer:timers).

Decorators

app.timers? AppTimers
From @xcwds/plugin-timers.
app.timers.page string | null
The timers page from the options (an app path), or null.
app.timers.list () => readonly TimerItem[]
Every timer, oldest first.
app.timers.get (id: number) => TimerItem | undefined
app.timers.subscribe (listener: (items: readonly TimerItem[]) => void) => () => void
Calls listener now, on every change and, while a timer runs, a few times a second (so remaining times stay current); returns a function that stops it.
app.timers.now () => number
The time remaining() and ringing() use: the clock at the latest tick.
app.timers.remaining (item: TimerItem) => number
app.timers.ringing (item: TimerItem) => boolean
app.timers.anyRunning () => boolean
app.timers.create (label: string, ms: number) => TimerItem | undefined
Starts a new timer of ms (up to 7 days); returns it, or undefined for no time. Call it from a tap, so the alarm can play sound later (iOS).
app.timers.toggle (id: number) => void
Pauses a running timer, or starts a paused one (also from a tap).
app.timers.add (id: number, ms: number) => void
Adds (or removes, with a negative ms) time; on a finished timer, snoozes it.
app.timers.reset (id: number) => void
Back to its full duration, paused.
app.timers.remove (id: number) => void
app.timers.show () => () => void
Says a page shows every timer, so finished ones aren't also shown as alerts (what <TimerList> does); returns a function that undoes it.
app.timers.shown () => boolean
Whether a page shows every timer now.

Settings fields

settings.alarm AlarmSetting
From @xcwds/plugin-timers.
settings.alarm.sound boolean
settings.alarm.vibration boolean
settings.alarm.keepAwake boolean

Edit this page on GitHub