diff --git a/.changeset/angry-sheep-matter.md b/.changeset/angry-sheep-matter.md new file mode 100644 index 0000000..a743ae0 --- /dev/null +++ b/.changeset/angry-sheep-matter.md @@ -0,0 +1,5 @@ +--- +'@layerstack/svelte-state': patch +--- + +feat: Add `TimerState` diff --git a/packages/svelte-state/src/lib/index.ts b/packages/svelte-state/src/lib/index.ts index 44a397c..0a641cf 100644 --- a/packages/svelte-state/src/lib/index.ts +++ b/packages/svelte-state/src/lib/index.ts @@ -1,2 +1,3 @@ export { SelectionState } from './selectionState.svelte.js'; +export { TimerState } from './timerState.svelte.js'; export { UniqueState } from './uniqueState.svelte.js'; diff --git a/packages/svelte-state/src/lib/selectionState.svelte.ts b/packages/svelte-state/src/lib/selectionState.svelte.ts index 06763e0..96e0c78 100644 --- a/packages/svelte-state/src/lib/selectionState.svelte.ts +++ b/packages/svelte-state/src/lib/selectionState.svelte.ts @@ -1,6 +1,6 @@ import { UniqueState } from './uniqueState.svelte.js'; -export type SelectionProps = { +export type SelectionOptions = { /** Initial values */ initial?: T[]; @@ -14,8 +14,6 @@ export type SelectionProps = { max?: number; }; -// export type SelectionState = ReturnType>; - export class SelectionState { #initial: T[]; #selected: UniqueState; @@ -24,13 +22,13 @@ export class SelectionState { single: boolean; max: number | undefined; - constructor(props: SelectionProps = {}) { - this.#initial = props.initial ?? []; + constructor(options: SelectionOptions = {}) { + 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() { diff --git a/packages/svelte-state/src/lib/timerState.svelte.ts b/packages/svelte-state/src/lib/timerState.svelte.ts new file mode 100644 index 0000000..7e8128e --- /dev/null +++ b/packages/svelte-state/src/lib/timerState.svelte.ts @@ -0,0 +1,91 @@ +export type TimerOptions = { + 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 { + #initial: T | null; + #current: T | null = $state(null); + #intervalId: ReturnType | null = null; + #delay: number; + #disabled: boolean; + #running = $state(false); + #onTick: (current: T | null) => any; + + constructor(options: TimerOptions = {}) { + 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; + } +} diff --git a/sites/docs/src/routes/_NavMenu.svelte b/sites/docs/src/routes/_NavMenu.svelte index 85b35dd..4ae4ba2 100644 --- a/sites/docs/src/routes/_NavMenu.svelte +++ b/sites/docs/src/routes/_NavMenu.svelte @@ -19,7 +19,7 @@ 'styles', ]; - const state = ['SelectionState', 'UniqueState']; + const state = ['SelectionState', 'TimerState', 'UniqueState']; const stores = [ 'changeStore', diff --git a/sites/docs/src/routes/docs/svelte-state/TimerState/+page.svelte b/sites/docs/src/routes/docs/svelte-state/TimerState/+page.svelte new file mode 100644 index 0000000..ffbc494 --- /dev/null +++ b/sites/docs/src/routes/docs/svelte-state/TimerState/+page.svelte @@ -0,0 +1,54 @@ + + +

Usage

+ + + +({ initial?: T, onTick?: (value: T) => {...}, delay?: number, disabled?: boolean })`} + language="javascript" +/> + +

Examples

+ +

Default

+ + + + +
{dateTimer.current}
+ { + // @ts-expect-error + e.target?.checked ? dateTimer.start() : dateTimer.stop(); + }} + /> +
+ +

Tick count

+ + value + 1 });`} + language="javascript" +/> + + +
{tickTimer.current}
+ { + // @ts-expect-error + e.target?.checked ? tickTimer.start() : tickTimer.stop(); + }} + /> +
diff --git a/sites/docs/src/routes/docs/svelte-state/TimerState/+page.ts b/sites/docs/src/routes/docs/svelte-state/TimerState/+page.ts new file mode 100644 index 0000000..43030ae --- /dev/null +++ b/sites/docs/src/routes/docs/svelte-state/TimerState/+page.ts @@ -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'], + }, + }; +} diff --git a/sites/docs/src/routes/docs/svelte-state/selectionState/+page.md b/sites/docs/src/routes/docs/svelte-state/selectionState/+page.md deleted file mode 100644 index 77a396f..0000000 --- a/sites/docs/src/routes/docs/svelte-state/selectionState/+page.md +++ /dev/null @@ -1,109 +0,0 @@ - - -

Usage

- -

Max

- -```js -const selection = new SelectionState({ max: 3 }); -``` - - - {#each data as d} -
- selection5.toggleSelected(d.id)} disabled={selection5.isDisabled(d.id)}> - {d.id} - -
- {/each} - selected: {JSON.stringify(selection5.current)} -
- -

Basic

- -```js -const selection = new SelectionState(); -``` - - - {#each data as d} -
- selection.toggleSelected(d.id)}> - {d.id} - -
- {/each} - selected: {JSON.stringify(selection.current)} -
- -

Initial selection

- -```js -const selection2 = new SelectionState({ initial: [3, 4, 5] }); -``` - - - {#each data as d} -
- selection2.toggleSelected(d.id)}> - {d.id} - -
- {/each} - selected: {JSON.stringify(selection2.current)} -
- -

Select all

- -```js -const selection3 = new SelectionState({ all: data.map((d) => d.id) }); -``` - - - selection3.toggleAll()}> - Select all - - {#each data as d} -
- selection3.toggleSelected(d.id)}> - {d.id} - -
- {/each} - selected: {JSON.stringify(selection3.current)} -
- -

Single

- -```js -const selection4 = new SelectionState({ single: true }); -``` - - - {#each data as d} -
- selection4.toggleSelected(d.id)}> - {d.id} - -
- {/each} - selected: {JSON.stringify(selection4.current)} -
diff --git a/sites/docs/src/routes/docs/svelte-state/selectionState/+page.svelte b/sites/docs/src/routes/docs/svelte-state/selectionState/+page.svelte new file mode 100644 index 0000000..45db5aa --- /dev/null +++ b/sites/docs/src/routes/docs/svelte-state/selectionState/+page.svelte @@ -0,0 +1,141 @@ + + +

Usage

+ + + +

Examples

+ +

Basic

+ + + + + {#each data as d} +
+ selection.toggleSelected(d.id)} + > + {d.id} + +
+ {/each} + selected: {JSON.stringify(selection.current)} +
+ +

Initial selection

+ + + + + {#each data as d} +
+ selection2.toggleSelected(d.id)} + > + {d.id} + +
+ {/each} + selected: {JSON.stringify(selection2.current)} +
+ +

Max

+ + + + + {#each data as d} +
+ selection5.toggleSelected(d.id)} + disabled={selection5.isDisabled(d.id)} + > + {d.id} + +
+ {/each} + selected: {JSON.stringify(selection5.current)} +
+ +

Select all

+ + d.id) });`} + language="javascript" +/> + + + selection3.toggleAll()} + > + Select all + + {#each data as d} +
+ selection3.toggleSelected(d.id)} + > + {d.id} + +
+ {/each} + selected: {JSON.stringify(selection3.current)} +
+ +

Single

+ + + + + {#each data as d} +
+ selection4.toggleSelected(d.id)} + > + {d.id} + +
+ {/each} + selected: {JSON.stringify(selection4.current)} +
diff --git a/sites/docs/src/routes/docs/svelte-state/selectionState/+page.ts b/sites/docs/src/routes/docs/svelte-state/selectionState/+page.ts index b6f1695..0158feb 100644 --- a/sites/docs/src/routes/docs/svelte-state/selectionState/+page.ts +++ b/sites/docs/src/routes/docs/svelte-state/selectionState/+page.ts @@ -1,5 +1,5 @@ import source from '$svelte-state/selectionState.svelte.ts?raw'; -import pageSource from './+page.md?raw'; +import pageSource from './+page.svelte?raw'; export async function load() { return { @@ -8,7 +8,7 @@ export async function load() { pageSource, description: 'Manage item selection state including toggling values, selecting all, and clear or reset selection', - // related: ['components/MultiSelect', 'components/Selection'], + related: ['svelte-state/UniqueState', 'components/MultiSelect', 'components/Selection'], }, }; } diff --git a/sites/docs/src/routes/docs/svelte-state/uniqueState/+page.md b/sites/docs/src/routes/docs/svelte-state/uniqueState/+page.md deleted file mode 100644 index 2ea6e15..0000000 --- a/sites/docs/src/routes/docs/svelte-state/uniqueState/+page.md +++ /dev/null @@ -1,41 +0,0 @@ - - -

Usage

- -```js -import { uniqueState } from '@layerstack/svelte-state'; - -const state = new UniqueState(); -// state.current.has(value) -// state.current.size -// state.add(value); -// state.delete(value); -// state.toggle(value); -``` - -

Examples

- -

Basic

- - - {#each data as d} -
- state.toggle(d.id)}> - {d.id} - -
- {/each} - selected: {JSON.stringify([...state.current])} -
diff --git a/sites/docs/src/routes/docs/svelte-state/uniqueState/+page.svelte b/sites/docs/src/routes/docs/svelte-state/uniqueState/+page.svelte new file mode 100644 index 0000000..33fc105 --- /dev/null +++ b/sites/docs/src/routes/docs/svelte-state/uniqueState/+page.svelte @@ -0,0 +1,45 @@ + + +

Usage

+ + + +

Examples

+ +

Basic

+ + + {#each data as d} +
+ state.toggle(d.id)}> + {d.id} + +
+ {/each} + selected: {JSON.stringify([...state.current])} +
diff --git a/sites/docs/src/routes/docs/svelte-state/uniqueState/+page.ts b/sites/docs/src/routes/docs/svelte-state/uniqueState/+page.ts index d5f5db7..b55a2ee 100644 --- a/sites/docs/src/routes/docs/svelte-state/uniqueState/+page.ts +++ b/sites/docs/src/routes/docs/svelte-state/uniqueState/+page.ts @@ -1,5 +1,5 @@ import source from '$svelte-state/uniqueState.svelte.ts?raw'; -import pageSource from './+page.md?raw'; +import pageSource from './+page.svelte?raw'; export async function load() { return { @@ -8,7 +8,7 @@ export async function load() { pageSource, description: 'State to manage unique values using `Set` with improves ergonomics and better control of updates', - related: ['svelte-state/selectionState'], + related: ['svelte-state/SelectionState'], }, }; } diff --git a/sites/docs/src/routes/docs/svelte-stores/timerStore/+page.md b/sites/docs/src/routes/docs/svelte-stores/timerStore/+page.md index fd76221..1572f1a 100644 --- a/sites/docs/src/routes/docs/svelte-stores/timerStore/+page.md +++ b/sites/docs/src/routes/docs/svelte-stores/timerStore/+page.md @@ -22,7 +22,9 @@ const timer = timerStore(); const timer = timerStore({ initial?: T, onTick?: (value: T) => {...}, delay?: number, disabled?: boolean }) ``` -

Example

+

Examples

+ +

Default

```svelte