Skip to content

Commit 0e06d8d

Browse files
authored
Merge pull request #56 from techniq/timerstate
Add TimerState (rune-based timerStore)
2 parents 84c1a1c + c83a5cc commit 0e06d8d

14 files changed

Lines changed: 364 additions & 164 deletions

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: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export { SelectionState } from './selectionState.svelte.js';
2+
export { TimerState } from './timerState.svelte.js';
23
export { UniqueState } from './uniqueState.svelte.js';

packages/svelte-state/src/lib/selectionState.svelte.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { UniqueState } from './uniqueState.svelte.js';
22

3-
export type SelectionProps<T> = {
3+
export type SelectionOptions<T> = {
44
/** Initial values */
55
initial?: T[];
66

@@ -14,8 +14,6 @@ export type SelectionProps<T> = {
1414
max?: number;
1515
};
1616

17-
// export type SelectionState<T> = ReturnType<typeof selectionState<T>>;
18-
1917
export class SelectionState<T> {
2018
#initial: T[];
2119
#selected: UniqueState<T>;
@@ -24,13 +22,13 @@ export class SelectionState<T> {
2422
single: boolean;
2523
max: number | undefined;
2624

27-
constructor(props: SelectionProps<T> = {}) {
28-
this.#initial = props.initial ?? [];
25+
constructor(options: SelectionOptions<T> = {}) {
26+
this.#initial = options.initial ?? [];
2927
this.#selected = new UniqueState(this.#initial);
3028

31-
this.all = props.all ?? [];
32-
this.single = props.single ?? false;
33-
this.max = props.max;
29+
this.all = options.all ?? [];
30+
this.single = options.single ?? false;
31+
this.max = options.max;
3432
}
3533

3634
get current() {
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: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<script lang="ts">
2+
import { TimerState } from '@layerstack/svelte-state';
3+
import { Switch } from 'svelte-ux';
4+
5+
import Preview from '$docs/Preview.svelte';
6+
import Code from '$docs/Code.svelte';
7+
8+
const dateTimer = new TimerState({ initial: new Date(), onTick: () => new Date() });
9+
const tickTimer = new TimerState({ initial: 0, onTick: (value) => (value ?? 0) + 1 });
10+
</script>
11+
12+
<h1>Usage</h1>
13+
14+
<Code source={`const timer = new TimerState();`} language="javascript" />
15+
16+
<Code
17+
source={`const timer = new TimerState<T>({ initial?: T, onTick?: (value: T) => {...}, delay?: number, disabled?: boolean })`}
18+
language="javascript"
19+
/>
20+
21+
<h1>Examples</h1>
22+
23+
<h2>Default</h2>
24+
25+
<Code source={`const dateTimer = new TimerState();`} language="javascript" />
26+
27+
<Preview>
28+
<div>{dateTimer.current}</div>
29+
<Switch
30+
checked={dateTimer.running}
31+
on:change={(e) => {
32+
// @ts-expect-error
33+
e.target?.checked ? dateTimer.start() : dateTimer.stop();
34+
}}
35+
/>
36+
</Preview>
37+
38+
<h2>Tick count</h2>
39+
40+
<Code
41+
source={`const tickTimer = new TimerState({ initial: 0, onTick: (value) => value + 1 });`}
42+
language="javascript"
43+
/>
44+
45+
<Preview>
46+
<div>{tickTimer.current}</div>
47+
<Switch
48+
checked={tickTimer.running}
49+
on:change={(e) => {
50+
// @ts-expect-error
51+
e.target?.checked ? tickTimer.start() : tickTimer.stop();
52+
}}
53+
/>
54+
</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+
}

sites/docs/src/routes/docs/svelte-state/selectionState/+page.md

Lines changed: 0 additions & 109 deletions
This file was deleted.

0 commit comments

Comments
 (0)