import { ReadonlySignal, Signal, computed, untracked, } from "@preact-signals/unified-signals"; import { Accessor } from "./utils"; const enum UncachedField { Accessor = "_a", Setter = "_s", } /** * ReactiveRef class extends Signal class and allows to use it in JSX. * Usually you don't need to use it directly, use `$` function instead. */ declare class ReactiveRef extends Signal { constructor(accessor: Accessor); get value(): T; peek(): T; valueOf(): T; toString(): string; /** @internal */ [UncachedField.Accessor](): T; } /** * WritableReactiveRef class extends ReactiveRef class and allows to use it in JSX. * Usually you don't need to use it directly, use `$w` function instead. */ declare class WritableReactiveRef extends ReactiveRef { set value(value: T); constructor(get: () => T, set: (value: T) => void); /** @internal */ [UncachedField.Setter](value: T): void; } // @ts-expect-error interface WritableReactiveRef extends JSX.Element {} // @ts-expect-error interface ReactiveRef extends JSX.Element {} function ReactiveRef(this: ReactiveRef, accessor: Accessor) { this[UncachedField.Accessor] = accessor; } export type WritableRefOptions = { get(): T; set(value: T): void; }; function WritableReactiveRef( this: WritableReactiveRef, get: () => T, set: (value: T) => void, ) { this[UncachedField.Accessor] = get; this[UncachedField.Setter] = set; } ReactiveRef.prototype = Object.create(Signal.prototype); WritableReactiveRef.prototype = Object.create(ReactiveRef.prototype); Object.defineProperties(ReactiveRef.prototype, { value: { get(this: ReactiveRef) { return this[UncachedField.Accessor](); }, set() { throw new Error("Uncached value is readonly"); }, }, peek: { value(this: ReactiveRef) { return untracked(() => this[UncachedField.Accessor]()); }, }, valueOf: { value(this: ReactiveRef) { return this[UncachedField.Accessor](); }, }, toString: { value(this: ReactiveRef) { return String(this[UncachedField.Accessor]()); }, }, }); Object.defineProperty(WritableReactiveRef.prototype, "value", { get(this: WritableReactiveRef) { return this[UncachedField.Accessor](); }, set(this: WritableReactiveRef, value: any) { this[UncachedField.Setter](value); }, }); /** * ReactiveRef is just accessor function in object wrapper, that allows to use it in JSX * and use `instanceof` to check if it is ReactiveRef. Main difference with Signal is that you shouldn't care about using hooks for it creation. * @example * ```tsx * const sig = signal(1); * *
{$(() => sig.value * 10)}
* ``` * * Using with component wrapped in `withSignalProps` * ```tsx * const C = withSignalProps((props: { a: number }) => { * return
{props.a}
; * }); * * const sig = signal(1); * * sig.value)} /> * ``` */ export const $ = (accessor: Accessor): ReactiveRef => new ReactiveRef(accessor); /** * @description `WritableReactiveRef` is accessor and setter function in object wrapper, that allows to use it in JSX. * @example * ```tsx * const sig = signal({ * a: [1,2,3] * }); * * const ref = $w({ * get: () => sig.value["a"][0], * set: (value) => sig.value = { ...sig.value, a: [value, ...sig.value["a"].slice(1)] } * }); * *
ref.value++}>{ref}
* ``` * * `WritableReactiveRef` is also handy with deep reactivity tracking system * ```tsx * const sig = deepSignal({ * a: [1,2,3] * }); * * // more efficient because it doesn't recreate object on each change * const ref = $w({ * get: () => sig.value["a"][0], * set: (value) => sig.value["a"][0] = value * }); * ``` * * `WritableReactiveRef` can be used as prop with component wrapped in `withSignalProps` * ```tsx * const C = withSignalProps((props: { a: number }) => { * return
{props.a}
; * }); * * const sig = signal(1); * const ref = $w({ * get: () => sig.value, * set(value) { * sig.value = value; * }, * }) * * * ``` */ export const $w = (options: WritableRefOptions): WritableReactiveRef => new WritableReactiveRef(options.get, options.set); const computesCache = new WeakMap, ReadonlySignal>(); export const signalOf$ = ($value: ReactiveRef): ReadonlySignal => computesCache.get($value[UncachedField.Accessor]) ?? (computesCache.set( $value[UncachedField.Accessor], computed($value[UncachedField.Accessor]), ), computesCache.get($value[UncachedField.Accessor])!); export { /** * @deprecated use `ReactiveRef` */ ReactiveRef as Uncached, /** * @deprecated use `WritableReactiveRef` */ WritableReactiveRef as WritableUncached, ReactiveRef, WritableReactiveRef, };