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 (defaultapp:timers:timers). Set it to keep a key your app already uses, e.g.app:cooking-timer:timersfor 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)andremove(id).list(),get(id),remaining(item),ringing(item),anyRunning()andnow().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
alarmsetting:{ sound, vibration, keepAwake }, all on by default, in thetimerssettings section.keepAwakeholds a screen wake lock while a timer runs. - An
onBeforeReloadreason,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 (openLabelnames the Open link for screen readers; defaults to "Open timers").
- Pure functions from the main entry (
start,pause,add,remaining,formatDurationand 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.pagestring | 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 | undefinedapp.timers.subscribe(listener: (items: readonly TimerItem[]) => void) => () => void- Calls
listenernow, 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()andringing()use: the clock at the latest tick. app.timers.remaining(item: TimerItem) => numberapp.timers.ringing(item: TimerItem) => booleanapp.timers.anyRunning() => booleanapp.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) => voidapp.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.alarmAlarmSetting- From
@xcwds/plugin-timers.settings.alarm.soundbooleansettings.alarm.vibrationbooleansettings.alarm.keepAwakeboolean