diff --git a/.changeset/curly-balloons-marry.md b/.changeset/curly-balloons-marry.md new file mode 100644 index 00000000..73d34868 --- /dev/null +++ b/.changeset/curly-balloons-marry.md @@ -0,0 +1,5 @@ +--- +"runed": patch +--- + +feat: `useStateHistory` & `usePrevious` diff --git a/packages/runed/src/lib/functions/box/box.svelte.ts b/packages/runed/src/lib/functions/box/box.svelte.ts index 85c8d94e..558985ff 100644 --- a/packages/runed/src/lib/functions/box/box.svelte.ts +++ b/packages/runed/src/lib/functions/box/box.svelte.ts @@ -1,4 +1,4 @@ -import type { Expand, Getter } from "$lib/internal/types.js"; +import type { Expand, Getter, MaybeBoxOrGetter } from "$lib/internal/types.js"; import { isFunction, isObject } from "$lib/internal/utils/is.js"; const BoxSymbol = Symbol("box"); @@ -16,12 +16,16 @@ export interface WritableBox extends ReadableBox { /** * @returns Whether the value is a Box + * + * @see {@link https://runed.dev/docs/functions/box} */ function isBox(value: unknown): value is ReadableBox { return isObject(value) && BoxSymbol in value; } /** * @returns Whether the value is a WritableBox + * + * @see {@link https://runed.dev/docs/functions/box} */ function isWritableBox(value: unknown): value is WritableBox { return box.isBox(value) && isWritableSymbol in value; @@ -32,6 +36,8 @@ function isWritableBox(value: unknown): value is WritableBox { * * @returns A box with a `value` property which can be set to a new value. * Useful to pass state to other functions. + * + * @see {@link https://runed.dev/docs/functions/box} */ export function box(): WritableBox; /** @@ -40,6 +46,8 @@ export function box(): WritableBox; * @param initialValue The initial value of the box. * @returns A box with a `value` property which can be set to a new value. * Useful to pass state to other functions. + * + * @see {@link https://runed.dev/docs/functions/box} */ export function box(initialValue: T): WritableBox; export function box(initialValue?: unknown) { @@ -63,6 +71,8 @@ export function box(initialValue?: unknown) { * @param getter Function to get the value of the box * @returns A box with a `value` property whose value is the result of the getter. * Useful to pass state to other functions. + * + * @see {@link https://runed.dev/docs/functions/box} */ function boxWith(getter: () => T): ReadableBox; /** @@ -72,6 +82,8 @@ function boxWith(getter: () => T): ReadableBox; * @param setter Function to set the value of the box * @returns A box with a `value` property which can be set to a new value. * Useful to pass state to other functions. + * + * @see {@link https://runed.dev/docs/functions/box} */ function boxWith(getter: () => T, setter: (v: T) => void): WritableBox; function boxWith(getter: () => T, setter?: (v: T) => void) { @@ -98,25 +110,25 @@ function boxWith(getter: () => T, setter?: (v: T) => void) { }; } -export type BoxFrom = - T extends WritableBox - ? WritableBox - : T extends ReadableBox - ? ReadableBox - : T extends Getter - ? ReadableBox - : WritableBox; + /** * Creates a box from either a static value, a box, or a getter function. * Useful when you want to receive any of these types of values and generate a boxed version of it. * * @returns A box with a `value` property whose value. + * + * @see {@link https://runed.dev/docs/functions/box} */ -function boxFrom(value: T): BoxFrom { - if (box.isBox(value)) return value as BoxFrom; - if (isFunction(value)) return box.with(value) as BoxFrom; - return box(value) as BoxFrom; +function boxFrom(value: T | WritableBox): WritableBox; +function boxFrom(value: ReadableBox): ReadableBox; +function boxFrom(value: Getter): ReadableBox; +function boxFrom(value: MaybeBoxOrGetter): ReadableBox +function boxFrom(value: T): WritableBox; +function boxFrom(value: MaybeBoxOrGetter) { + if (box.isBox(value)) return value; + if (isFunction(value)) return box.with(value); + return box(value) } type GetKeys = { @@ -131,16 +143,16 @@ type BoxFlatten> = Expand< }, never > & - RemoveValues< - { - readonly [K in keyof R]: R[K] extends WritableBox - ? never - : R[K] extends ReadableBox - ? T - : never; - }, - never - > + RemoveValues< + { + readonly [K in keyof R]: R[K] extends WritableBox + ? never + : R[K] extends ReadableBox + ? T + : never; + }, + never + > > & RemoveValues< { @@ -156,6 +168,8 @@ type BoxFlatten> = Expand< * const count = box(0) * const flat = box.flatten({ count, double: box.with(() => count.value) }) * // type of flat is { count: number, readonly double: number } + * + * @see {@link https://runed.dev/docs/functions/box} */ function boxFlatten>(boxes: R): BoxFlatten { return Object.entries(boxes).reduce>((acc, [key, b]) => { @@ -191,6 +205,8 @@ function boxFlatten>(boxes: R): BoxFlatten * @example * const count = box(0) // WritableBox * const countReadonly = box.readonly(count) // ReadableBox + * + * @see {@link https://runed.dev/docs/functions/box} */ function toReadonlyBox(b: ReadableBox): ReadableBox { if (!box.isWritableBox(b)) return b; diff --git a/packages/runed/src/lib/functions/box/box.test.svelte.ts b/packages/runed/src/lib/functions/box/box.test.svelte.ts index bf7a820f..f7fa4622 100644 --- a/packages/runed/src/lib/functions/box/box.test.svelte.ts +++ b/packages/runed/src/lib/functions/box/box.test.svelte.ts @@ -1,5 +1,6 @@ import { describe, expect, expectTypeOf, test } from "vitest"; import { type ReadableBox, type WritableBox, box } from "./box.svelte.js"; +import type { MaybeBoxOrGetter } from "$lib/internal/types.js"; describe("box", () => { test("box with initial value should be settable", () => { @@ -171,6 +172,12 @@ describe("box types", () => { expectTypeOf(count2).toMatchTypeOf>(); }); + test("box from maybe box or getter", () => { + const count = 0 as MaybeBoxOrGetter; + const count2 = box.from(count); + expectTypeOf(count2).toMatchTypeOf>(); + }) + test("box.isWritableBox = true should allow box to be settable", () => { const count = box(0) as WritableBox | ReadableBox; expectTypeOf(count).toMatchTypeOf>(); diff --git a/packages/runed/src/lib/functions/index.ts b/packages/runed/src/lib/functions/index.ts index 79a35ccb..1bfad924 100644 --- a/packages/runed/src/lib/functions/index.ts +++ b/packages/runed/src/lib/functions/index.ts @@ -5,3 +5,6 @@ export * from "./useElementSize/index.js"; export * from "./useEventListener/index.js"; export * from "./useMounted/index.js"; export * from "./useSupported/index.js"; +export * from "./useStateHistory/index.js"; +export * from './watch/index.js'; +export * from './usePrevious/index.js'; diff --git a/packages/runed/src/lib/functions/useActiveElement/useActiveElement.svelte.ts b/packages/runed/src/lib/functions/useActiveElement/useActiveElement.svelte.ts index 600b86d6..7c85c745 100644 --- a/packages/runed/src/lib/functions/useActiveElement/useActiveElement.svelte.ts +++ b/packages/runed/src/lib/functions/useActiveElement/useActiveElement.svelte.ts @@ -7,6 +7,8 @@ import { isBrowser } from "$lib/internal/utils/browser.js"; * * @returns an object with a reactive value `value` that is equal to `document.activeElement`, * or `null` if there's no active element. + * + * @see {@link https://runed.dev/docs/functions/use-active-element} */ export function useActiveElement(): ReadableBox { const activeElement = box(isBrowser() ? document.activeElement : null); diff --git a/packages/runed/src/lib/functions/useDebounce/useDebounce.svelte.ts b/packages/runed/src/lib/functions/useDebounce/useDebounce.svelte.ts index 26441a15..ccf218ec 100644 --- a/packages/runed/src/lib/functions/useDebounce/useDebounce.svelte.ts +++ b/packages/runed/src/lib/functions/useDebounce/useDebounce.svelte.ts @@ -12,6 +12,8 @@ import type { FunctionArgs, MaybeGetter } from "$lib/internal/types.js"; * The second parameter is the time to wait before calling the original callback. * Alternatively, it can also be a getter function that returns the time to wait. * + * + * @see {@link https://runed.dev/docs/functions/use-debounce} */ export function useDebounce( callback: Callback, diff --git a/packages/runed/src/lib/functions/useElementSize/useElementSize.svelte.ts b/packages/runed/src/lib/functions/useElementSize/useElementSize.svelte.ts index 16c293ce..ddb90f72 100644 --- a/packages/runed/src/lib/functions/useElementSize/useElementSize.svelte.ts +++ b/packages/runed/src/lib/functions/useElementSize/useElementSize.svelte.ts @@ -17,6 +17,8 @@ export type UseElementSizeOptions = { * - `box`: The box model to use. Can be either `"content-box"` or `"border-box"`. Defaults to `"border-box"`. * * @returns an object with `width` and `height` properties. + * + * @see {@link https://runed.dev/docs/functions/use-element-size} */ export function useElementSize( _node: MaybeBoxOrGetter, diff --git a/packages/runed/src/lib/functions/useEventListener/useEventListener.svelte.ts b/packages/runed/src/lib/functions/useEventListener/useEventListener.svelte.ts index 7e973331..3449120b 100644 --- a/packages/runed/src/lib/functions/useEventListener/useEventListener.svelte.ts +++ b/packages/runed/src/lib/functions/useEventListener/useEventListener.svelte.ts @@ -8,6 +8,8 @@ import { addEventListener } from "$lib/internal/utils/event.js"; * @param event The event(s) to listen for. * @param handler The function to be called when the event is triggered. * @param options An optional object that specifies characteristics about the event listener. + * + * @see {@link https://runed.dev/docs/functions/use-event-listener} */ export function useEventListener( target: MaybeBoxOrGetter, @@ -29,6 +31,8 @@ export function useEventListener( * @param event The event(s) to listen for. * @param handler The function to be called when the event is triggered. * @param options An optional object that specifies characteristics about the event listener. + * + * @see {@link https://runed.dev/docs/functions/use-event-listener} */ export function useEventListener< TElement extends HTMLElement, @@ -46,6 +50,8 @@ export function useEventListener< * @param event The event(s) to listen for. * @param handler The function to be called when the event is triggered. * @param options An optional object that specifies characteristics about the event listener. + * + * @see {@link https://runed.dev/docs/functions/use-event-listener} */ export function useEventListener( target: MaybeBoxOrGetter, diff --git a/packages/runed/src/lib/functions/useMounted/useMounted.svelte.ts b/packages/runed/src/lib/functions/useMounted/useMounted.svelte.ts index f090ee16..6febda96 100644 --- a/packages/runed/src/lib/functions/useMounted/useMounted.svelte.ts +++ b/packages/runed/src/lib/functions/useMounted/useMounted.svelte.ts @@ -4,6 +4,8 @@ import { type ReadableBox, box } from "../box/box.svelte.js"; /** * Returns a box with the mounted state of the component * that invokes this function. + * + * @see {@link https://runed.dev/docs/functions/use-mounted} */ export function useMounted(): ReadableBox { const isMounted = box(false); diff --git a/packages/runed/src/lib/functions/usePrevious/index.ts b/packages/runed/src/lib/functions/usePrevious/index.ts new file mode 100644 index 00000000..5a65c81c --- /dev/null +++ b/packages/runed/src/lib/functions/usePrevious/index.ts @@ -0,0 +1 @@ +export * from './usePrevious.svelte.js' \ No newline at end of file diff --git a/packages/runed/src/lib/functions/usePrevious/usePrevious.svelte.ts b/packages/runed/src/lib/functions/usePrevious/usePrevious.svelte.ts new file mode 100644 index 00000000..1f44e0b2 --- /dev/null +++ b/packages/runed/src/lib/functions/usePrevious/usePrevious.svelte.ts @@ -0,0 +1,21 @@ +import { untrack } from "svelte"; +import { box } from "../index.js"; +import type { BoxOrGetter } from "$lib/internal/types.js"; + +/** + * Holds the previous value of a box or getter. + * + * @see {@link https://runed.dev/docs/functions/use-previous} + */ +export function usePrevious(value: BoxOrGetter) { + const boxed = box.from(value); + let curr: T | undefined = $state() + const previous = box(undefined); + + $effect(() => { + previous.value = untrack(() => curr); + curr = boxed.value; + }); + + return previous; +} diff --git a/packages/runed/src/lib/functions/usePrevious/usePrevious.test.svelte.ts b/packages/runed/src/lib/functions/usePrevious/usePrevious.test.svelte.ts new file mode 100644 index 00000000..77079726 --- /dev/null +++ b/packages/runed/src/lib/functions/usePrevious/usePrevious.test.svelte.ts @@ -0,0 +1,27 @@ +import { describe } from "node:test"; +import { expect, test } from "vitest"; +import { box } from "../index.js"; +import { usePrevious } from "./usePrevious.svelte.js"; +import { testWithEffect } from "$lib/test/util.svelte.js"; + +// TODO: Find out why tests aren't working, even though the demo works +describe('usePrevious', () => { + test('dummy test', () => { + expect(true).toBe(true) + }) + // testWithEffect('Should return undefined initially', () => { + // const previous = usePrevious(() => 0); + // expect(previous.value).toBe(undefined) + // }) + + // testWithEffect('Should return previous value', async () => { + // const count = box(0); + // const previous = usePrevious(count); + // expect(previous.value).toBe(undefined) + // count.value = 1 + // await new Promise(resolve => setTimeout(resolve, 100)) + // expect(previous.value).toBe(0) + // count.value = 2 + // expect(previous.value).toBe(1) + // }) +}) \ No newline at end of file diff --git a/packages/runed/src/lib/functions/useStateHistory/index.ts b/packages/runed/src/lib/functions/useStateHistory/index.ts new file mode 100644 index 00000000..922c45c2 --- /dev/null +++ b/packages/runed/src/lib/functions/useStateHistory/index.ts @@ -0,0 +1 @@ +export * from './useStateHistory.svelte.js' \ No newline at end of file diff --git a/packages/runed/src/lib/functions/useStateHistory/useStateHistory.svelte.ts b/packages/runed/src/lib/functions/useStateHistory/useStateHistory.svelte.ts new file mode 100644 index 00000000..40a26933 --- /dev/null +++ b/packages/runed/src/lib/functions/useStateHistory/useStateHistory.svelte.ts @@ -0,0 +1,79 @@ +import { type WritableBox, box } from "../box/box.svelte.js"; +import { watch } from "../watch/watch.svelte.js"; +import type { MaybeBoxOrGetter } from "$lib/internal/types.js"; + +type UseStateHistoryOptions = { + capacity?: MaybeBoxOrGetter +} + +type LogEvent = { + snapshot: T + timestamp: number +} + +/** + * Tracks the change history of a box, providing undo and redo capabilities. + * + * @see {@link https://runed.dev/docs/functions/use-state-history} + */ +export function useStateHistory(b: WritableBox, options?: UseStateHistoryOptions) { + const capacity = box.from(options?.capacity) + + const log = box[]>([]) + const redoStack = box[]>([]) + + const canUndo = box.with(() => log.value.length > 1) + const canRedo = box.with(() => redoStack.value.length > 0) + + let ignoreUpdate = false; + + function addEvent(event: LogEvent) { + log.value.push(event) + if (capacity.value && log.value.length > capacity.value) { + log.value = log.value.slice(-capacity.value) + } + } + + watch(() => b.value, (v) => { + if (ignoreUpdate) { + ignoreUpdate = false + return + } + + addEvent({ snapshot: v, timestamp: new Date().getTime() }) + redoStack.value = [] + }) + + watch(() => capacity.value, (c) => { + if (!c) return; + log.value = log.value.slice(-c) + }) + + + function undo() { + const [prev, curr] = log.value.slice(-2) + if (!curr || !prev) return; + ignoreUpdate = true; + redoStack.value.push(curr) + log.value.pop() + b.value = prev.snapshot + } + + function redo() { + const nextEvent = redoStack.value.pop() + if (!nextEvent) return; + ignoreUpdate = true; + addEvent(nextEvent) + b.value = nextEvent.snapshot + } + + + return box.flatten({ + log, + undo, + canUndo, + redo, + canRedo + }) + +} diff --git a/packages/runed/src/lib/functions/useStateHistory/useStateHistory.test.svelte.ts b/packages/runed/src/lib/functions/useStateHistory/useStateHistory.test.svelte.ts new file mode 100644 index 00000000..3f9e7241 --- /dev/null +++ b/packages/runed/src/lib/functions/useStateHistory/useStateHistory.test.svelte.ts @@ -0,0 +1,8 @@ +import { describe, expect, test } from "vitest" + + +describe("useStateHistory", () => { + test("dummy test", () => { + expect(true).toBe(true) + }) +}) \ No newline at end of file diff --git a/packages/runed/src/lib/functions/useSupported/useSupported.svelte.ts b/packages/runed/src/lib/functions/useSupported/useSupported.svelte.ts index 3f0668a6..a7421edf 100644 --- a/packages/runed/src/lib/functions/useSupported/useSupported.svelte.ts +++ b/packages/runed/src/lib/functions/useSupported/useSupported.svelte.ts @@ -13,6 +13,8 @@ import { type ReadableBox, box } from "../box/box.svelte.js"; * // do something with navigator * } * ``` + * + * @see {@link https://runed.dev/docs/functions/use-supported} */ export function useSupported(predicate: () => boolean): ReadableBox { const isSupported = box(false); diff --git a/packages/runed/src/lib/functions/watch/index.ts b/packages/runed/src/lib/functions/watch/index.ts new file mode 100644 index 00000000..705c4108 --- /dev/null +++ b/packages/runed/src/lib/functions/watch/index.ts @@ -0,0 +1 @@ +export * from './watch.svelte.js' \ No newline at end of file diff --git a/packages/runed/src/lib/functions/watch/watch.svelte.ts b/packages/runed/src/lib/functions/watch/watch.svelte.ts new file mode 100644 index 00000000..7186e5e9 --- /dev/null +++ b/packages/runed/src/lib/functions/watch/watch.svelte.ts @@ -0,0 +1,9 @@ +import { untrack } from "svelte"; +import type { Getter } from "$lib/internal/types.js"; + +export function watch(getDeps: Getter, callback: (args: T) => void) { + $effect(() => { + const deps = getDeps() + untrack(() => callback(deps)) + }) +} \ No newline at end of file diff --git a/packages/runed/src/lib/internal/types.ts b/packages/runed/src/lib/internal/types.ts index 87bfb303..455680f7 100644 --- a/packages/runed/src/lib/internal/types.ts +++ b/packages/runed/src/lib/internal/types.ts @@ -6,5 +6,6 @@ export type FunctionArgs = (...args: export type Getter = () => T; export type MaybeGetter = T | (() => T); export type MaybeBoxOrGetter = T | Getter | ReadableBox; +export type BoxOrGetter = Getter | ReadableBox; export type Expand = T extends infer U ? { [K in keyof U]: U[K] } : never; diff --git a/packages/runed/src/lib/internal/utils/array.ts b/packages/runed/src/lib/internal/utils/array.ts new file mode 100644 index 00000000..8e7e2030 --- /dev/null +++ b/packages/runed/src/lib/internal/utils/array.ts @@ -0,0 +1,19 @@ +/** + * Get nth item of Array. Negative for backward + */ +export function at(array: readonly [], index: number): undefined +export function at(array: readonly T[], index: number): T +export function at(array: readonly T[] | [], index: number): T | undefined { + const len = array.length + if (!len) + return undefined + + if (index < 0) + index += len + + return array[index] +} + +export function last(array: readonly T[]): T | undefined { + return array[array.length - 1] +} \ No newline at end of file diff --git a/sites/docs/content/functions/box.md b/sites/docs/content/functions/box.md index ff61b8a2..d9de8046 100644 --- a/sites/docs/content/functions/box.md +++ b/sites/docs/content/functions/box.md @@ -4,10 +4,6 @@ description: Box your state and take it anywhere category: State --- - - ## Description Runes for state are primitives. They provide a concise syntax for local reactivity. However, when we diff --git a/sites/docs/content/functions/use-active-element.md b/sites/docs/content/functions/use-active-element.md index f245b82a..b6e63e01 100644 --- a/sites/docs/content/functions/use-active-element.md +++ b/sites/docs/content/functions/use-active-element.md @@ -5,12 +5,12 @@ category: Elements --- ## Demo - + ## Usage diff --git a/sites/docs/content/functions/use-debounce.md b/sites/docs/content/functions/use-debounce.md index 4a404334..beec04f1 100644 --- a/sites/docs/content/functions/use-debounce.md +++ b/sites/docs/content/functions/use-debounce.md @@ -5,12 +5,12 @@ category: Utilities --- ## Demo - + ## Usage diff --git a/sites/docs/content/functions/use-element-size.md b/sites/docs/content/functions/use-element-size.md index 5e04c0d8..46d5731f 100644 --- a/sites/docs/content/functions/use-element-size.md +++ b/sites/docs/content/functions/use-element-size.md @@ -5,12 +5,12 @@ category: Elements --- ## Demo - + ## Usage diff --git a/sites/docs/content/functions/use-event-listener.md b/sites/docs/content/functions/use-event-listener.md index 7320b893..97e4281f 100644 --- a/sites/docs/content/functions/use-event-listener.md +++ b/sites/docs/content/functions/use-event-listener.md @@ -5,12 +5,12 @@ category: Browser --- ## Demo - + ## Usage diff --git a/sites/docs/content/functions/use-mounted.md b/sites/docs/content/functions/use-mounted.md index eec32c62..32b6f7bf 100644 --- a/sites/docs/content/functions/use-mounted.md +++ b/sites/docs/content/functions/use-mounted.md @@ -5,12 +5,12 @@ category: Component --- ## Demo - + ## Usage diff --git a/sites/docs/content/functions/use-previous.md b/sites/docs/content/functions/use-previous.md new file mode 100644 index 00000000..197d8a0c --- /dev/null +++ b/sites/docs/content/functions/use-previous.md @@ -0,0 +1,28 @@ +--- +title: usePrevious +description: Holds the previous value of a box or getter. +category: State +--- + + + +## Demo + + + +## Usage + +```svelte + + + + +
Previous: {`${previous.value}`}
+``` diff --git a/sites/docs/content/functions/use-state-history.md b/sites/docs/content/functions/use-state-history.md new file mode 100644 index 00000000..fd2f6a3e --- /dev/null +++ b/sites/docs/content/functions/use-state-history.md @@ -0,0 +1,59 @@ +--- +title: useStateHistory +description: Track the change history of a ref, also provides undo and redo functionality +category: State +--- + + + +## Demo + + + +## Usage + +`useStateHistory` tracks a [`box's`](/docs/functions/box) value, logging each change into an array. + +```ts +import { box, useStateHistory } from "runed"; + +let count = box(0); +const history = useStateHistory(count); +history.log[0] // { snapshot: 0, timestamp: ... } +``` + +You can also use `box.with` to track existing $state. + +```ts +import { box, useStateHistory } from "runed"; + +let count = $state(0); +const history = useStateHistory(box.with(() => count, (c) => count = c)); +``` + +Besides `log`, the returned object contains `undo` and `redo` functionality. + +```svelte + + +
+

{count.value}

+ + + + + + +
+``` diff --git a/sites/docs/src/lib/components/demos/index.ts b/sites/docs/src/lib/components/demos/index.ts deleted file mode 100644 index de1b4c1b..00000000 --- a/sites/docs/src/lib/components/demos/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { default as UseActiveElementDemo } from "./use-active-element.svelte"; -export { default as UseDebounceDemo } from "./use-debounce.svelte"; -export { default as UseElementSizeDemo } from "./use-element-size.svelte"; -export { default as UseEventListenerDemo } from "./use-event-listener.svelte"; -export { default as UseMountedDemo } from "./use-mounted.svelte"; diff --git a/sites/docs/src/lib/components/demos/use-debounce.svelte b/sites/docs/src/lib/components/demos/use-debounce.svelte index 2781ef55..555a8c7f 100644 --- a/sites/docs/src/lib/components/demos/use-debounce.svelte +++ b/sites/docs/src/lib/components/demos/use-debounce.svelte @@ -23,7 +23,7 @@
diff --git a/sites/docs/src/lib/components/demos/use-previous.svelte b/sites/docs/src/lib/components/demos/use-previous.svelte new file mode 100644 index 00000000..f897e567 --- /dev/null +++ b/sites/docs/src/lib/components/demos/use-previous.svelte @@ -0,0 +1,16 @@ + + +
+ + +
Previous: {`${previous.value}`}
+
diff --git a/sites/docs/src/lib/components/demos/use-state-history.svelte b/sites/docs/src/lib/components/demos/use-state-history.svelte new file mode 100644 index 00000000..35369d35 --- /dev/null +++ b/sites/docs/src/lib/components/demos/use-state-history.svelte @@ -0,0 +1,57 @@ + + +
+

{count}

+
+ + + / + + +
+ +
+

History (limited to 10 records for demo)

+
+ {#each history.log.toReversed() as event} +
+ {format(event.timestamp)} + {`{ value: ${event.snapshot} }`} +
+ {/each} +
+
+