Skip to content

Commit 00e03bd

Browse files
committed
feat: Add TimerState
1 parent 0848b05 commit 00e03bd

5 files changed

Lines changed: 165 additions & 1 deletion

File tree

.changeset/angry-sheep-matter.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@layerstack/svelte-state': patch
3+
---
4+
5+
feat: Add `TimerState`
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
export type TimerOptions<T> = {
2+
initial?: T;
3+
4+
/** Delay between ticks in milliseconds
5+
* @default 1000
6+
*/
7+
delay?: number;
8+
9+
/** Start disabled (manually call `start()`)
10+
* @default false
11+
*/
12+
disabled?: boolean;
13+
14+
/** Called on each interval tick. Returned value is used to update store value, defaulting to current Date */
15+
onTick?: (current: T | null) => any;
16+
};
17+
18+
/**
19+
* Subscribable timer/interval state
20+
*/
21+
export class TimerState<T = any> {
22+
#initial: T | null;
23+
#current: T | null = $state(null);
24+
#intervalId: ReturnType<typeof setInterval> | null = null;
25+
#delay: number;
26+
#disabled: boolean;
27+
#running = $state(false);
28+
#onTick: (current: T | null) => any;
29+
30+
constructor(options: TimerOptions<T> = {}) {
31+
this.#initial = options.initial ?? null;
32+
this.#current = this.#initial;
33+
this.#delay = options.delay ?? 1000;
34+
this.#disabled = options.disabled ?? false;
35+
this.#onTick = options.onTick ?? (() => new Date());
36+
37+
if (!this.#disabled) {
38+
this.start();
39+
}
40+
41+
$effect(() => {
42+
return this.stop;
43+
});
44+
}
45+
46+
get current() {
47+
return this.#current;
48+
}
49+
50+
set current(value: T | null) {
51+
if (!this.#disabled) {
52+
this.start();
53+
}
54+
55+
this.#current = value;
56+
}
57+
58+
get delay() {
59+
return this.#delay;
60+
}
61+
62+
set delay(value: number) {
63+
this.stop();
64+
this.#delay = value;
65+
this.start();
66+
}
67+
68+
start = () => {
69+
stop();
70+
this.#intervalId = setInterval(() => {
71+
this.#current = this.#onTick(this.#current) ?? new Date();
72+
}, this.#delay);
73+
this.#running = true;
74+
};
75+
76+
stop = () => {
77+
if (this.#intervalId) {
78+
clearInterval(this.#intervalId);
79+
}
80+
this.#intervalId = null;
81+
this.#running = false;
82+
};
83+
84+
reset = () => {
85+
return (this.#current = this.#initial);
86+
};
87+
88+
get running() {
89+
return this.#running;
90+
}
91+
}

sites/docs/src/routes/_NavMenu.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
'styles',
2020
];
2121
22-
const state = ['SelectionState', 'UniqueState'];
22+
const state = ['SelectionState', 'TimerState', 'UniqueState'];
2323
2424
const stores = [
2525
'changeStore',
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<script lang="ts">
2+
import { subDays, subMonths } from 'date-fns';
3+
4+
import { Switch } from 'svelte-ux';
5+
import { TimerState } from '@layerstack/svelte-state';
6+
import Preview from '$docs/Preview.svelte';
7+
import Code from '$docs/Code.svelte';
8+
9+
const dateTimer = new TimerState({ initial: new Date(), onTick: () => new Date() });
10+
const tickTimer = new TimerState({ initial: 0, onTick: (value) => (value ?? 0) + 1 });
11+
</script>
12+
13+
<h1>Usage</h1>
14+
15+
<Code source={`const timer = new TimerState();`} language="javascript" />
16+
17+
<Code
18+
source={`const timer = new TimerState<T>({ initial?: T, onTick?: (value: T) => {...}, delay?: number, disabled?: boolean })`}
19+
language="javascript"
20+
/>
21+
22+
<h1>Examples</h1>
23+
24+
<h2>Default</h2>
25+
26+
<Code source={`const dateTimer = new TimerState();`} language="javascript" />
27+
28+
<Preview>
29+
<div>{dateTimer.current}</div>
30+
<Switch
31+
checked={dateTimer.running}
32+
on:change={(e) => {
33+
// @ts-expect-error
34+
e.target?.checked ? dateTimer.start() : dateTimer.stop();
35+
}}
36+
/>
37+
</Preview>
38+
39+
<h2>Tick count</h2>
40+
41+
<Code
42+
source={`const tickTimer = new TimerState({ initial: 0, onTick: (value) => value + 1 });`}
43+
language="javascript"
44+
/>
45+
46+
<Preview>
47+
<div>{tickTimer.current}</div>
48+
<Switch
49+
checked={tickTimer.running}
50+
on:change={(e) => {
51+
// @ts-expect-error
52+
e.target?.checked ? tickTimer.start() : tickTimer.stop();
53+
}}
54+
/>
55+
</Preview>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import source from '$svelte-state/timerState.svelte.ts?raw';
2+
import pageSource from './+page.svelte?raw';
3+
4+
export async function load() {
5+
return {
6+
meta: {
7+
source,
8+
pageSource,
9+
description: 'Manage interval ticks, useful for timely updates and countdowns',
10+
related: ['components/Duration', 'components/ScrollingValue'],
11+
},
12+
};
13+
}

0 commit comments

Comments
 (0)