Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/angry-sheep-matter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@layerstack/svelte-state': patch
---

feat: Add `TimerState`
1 change: 1 addition & 0 deletions packages/svelte-state/src/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { SelectionState } from './selectionState.svelte.js';
export { TimerState } from './timerState.svelte.js';
export { UniqueState } from './uniqueState.svelte.js';
14 changes: 6 additions & 8 deletions packages/svelte-state/src/lib/selectionState.svelte.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { UniqueState } from './uniqueState.svelte.js';

export type SelectionProps<T> = {
export type SelectionOptions<T> = {
/** Initial values */
initial?: T[];

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

// export type SelectionState<T> = ReturnType<typeof selectionState<T>>;

export class SelectionState<T> {
#initial: T[];
#selected: UniqueState<T>;
Expand All @@ -24,13 +22,13 @@ export class SelectionState<T> {
single: boolean;
max: number | undefined;

constructor(props: SelectionProps<T> = {}) {
this.#initial = props.initial ?? [];
constructor(options: SelectionOptions<T> = {}) {
this.#initial = options.initial ?? [];
this.#selected = new UniqueState(this.#initial);

this.all = props.all ?? [];
this.single = props.single ?? false;
this.max = props.max;
this.all = options.all ?? [];
this.single = options.single ?? false;
this.max = options.max;
}

get current() {
Expand Down
91 changes: 91 additions & 0 deletions packages/svelte-state/src/lib/timerState.svelte.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
export type TimerOptions<T> = {
initial?: T;

/** Delay between ticks in milliseconds
* @default 1000
*/
delay?: number;

/** Start disabled (manually call `start()`)
* @default false
*/
disabled?: boolean;

/** Called on each interval tick. Returned value is used to update store value, defaulting to current Date */
onTick?: (current: T | null) => any;
};

/**
* Subscribable timer/interval state
*/
export class TimerState<T = any> {
#initial: T | null;
#current: T | null = $state(null);
#intervalId: ReturnType<typeof setInterval> | null = null;
#delay: number;
#disabled: boolean;
#running = $state(false);
#onTick: (current: T | null) => any;

constructor(options: TimerOptions<T> = {}) {
this.#initial = options.initial ?? null;
this.#current = this.#initial;
this.#delay = options.delay ?? 1000;
this.#disabled = options.disabled ?? false;
this.#onTick = options.onTick ?? (() => new Date());

if (!this.#disabled) {
this.start();
}

$effect(() => {
return this.stop;
});
}

get current() {
return this.#current;
}

set current(value: T | null) {
if (!this.#disabled) {
this.start();
}

this.#current = value;
}

get delay() {
return this.#delay;
}

set delay(value: number) {
this.stop();
this.#delay = value;
this.start();
}

start = () => {
stop();
this.#intervalId = setInterval(() => {
this.#current = this.#onTick(this.#current) ?? new Date();
}, this.#delay);
this.#running = true;
};

stop = () => {
if (this.#intervalId) {
clearInterval(this.#intervalId);
}
this.#intervalId = null;
this.#running = false;
};

reset = () => {
return (this.#current = this.#initial);
};

get running() {
return this.#running;
}
}
2 changes: 1 addition & 1 deletion sites/docs/src/routes/_NavMenu.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
'styles',
];

const state = ['SelectionState', 'UniqueState'];
const state = ['SelectionState', 'TimerState', 'UniqueState'];

const stores = [
'changeStore',
Expand Down
54 changes: 54 additions & 0 deletions sites/docs/src/routes/docs/svelte-state/TimerState/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<script lang="ts">
import { TimerState } from '@layerstack/svelte-state';
import { Switch } from 'svelte-ux';

import Preview from '$docs/Preview.svelte';
import Code from '$docs/Code.svelte';

const dateTimer = new TimerState({ initial: new Date(), onTick: () => new Date() });
const tickTimer = new TimerState({ initial: 0, onTick: (value) => (value ?? 0) + 1 });
</script>

<h1>Usage</h1>

<Code source={`const timer = new TimerState();`} language="javascript" />

<Code
source={`const timer = new TimerState<T>({ initial?: T, onTick?: (value: T) => {...}, delay?: number, disabled?: boolean })`}
language="javascript"
/>

<h1>Examples</h1>

<h2>Default</h2>

<Code source={`const dateTimer = new TimerState();`} language="javascript" />

<Preview>
<div>{dateTimer.current}</div>
<Switch
checked={dateTimer.running}
on:change={(e) => {
// @ts-expect-error
e.target?.checked ? dateTimer.start() : dateTimer.stop();
}}
/>
</Preview>

<h2>Tick count</h2>

<Code
source={`const tickTimer = new TimerState({ initial: 0, onTick: (value) => value + 1 });`}
language="javascript"
/>

<Preview>
<div>{tickTimer.current}</div>
<Switch
checked={tickTimer.running}
on:change={(e) => {
// @ts-expect-error
e.target?.checked ? tickTimer.start() : tickTimer.stop();
}}
/>
</Preview>
13 changes: 13 additions & 0 deletions sites/docs/src/routes/docs/svelte-state/TimerState/+page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import source from '$svelte-state/timerState.svelte.ts?raw';
import pageSource from './+page.svelte?raw';

export async function load() {
return {
meta: {
source,
pageSource,
description: 'Manage interval ticks, useful for timely updates and countdowns',
related: ['components/Duration', 'components/ScrollingValue'],
},
};
}
109 changes: 0 additions & 109 deletions sites/docs/src/routes/docs/svelte-state/selectionState/+page.md

This file was deleted.

Loading