UNPKG

value-enhancer

Version:

Enhance value with plain and explicit reactive wrapper. Think of it as hook-style signals.

152 lines (134 loc) 4.14 kB
import type { ReadonlyVal, Val, ValDisposer, ValInputsValueTuple, ValSubscriber, ValVersion, } from "./typings"; export const INIT_VALUE: any = {}; /** * Set the value of a val. * It works for both `Val` and `ReadonlyVal` type (if the `ReadonlyVal` is actually a `Val`). * Do nothing if the val is really `ReadonlyVal`. */ export const setValue = <TValue>( val: ReadonlyVal<TValue>, value: TValue ): void => (val as Val<TValue>)?.set?.(value); /** * Subscribe to value changes with immediate emission. * @param val * @param subscriber * @param eager by default subscribers will be notified on next tick. set `true` to notify subscribers of value changes synchronously. * @returns a disposer function that cancels the subscription */ export const subscribe = <TValue>( val: ReadonlyVal<TValue>, subscriber: ValSubscriber<TValue>, eager?: boolean ): ValDisposer => val.subscribe(subscriber, eager); /** * Subscribe to value changes without immediate emission. * @param val * @param subscriber * @param eager by default subscribers will be notified on next tick. set `true` to notify subscribers of value changes synchronously. * @returns a disposer function that cancels the subscription */ export const reaction = <TValue>( val: ReadonlyVal<TValue>, subscriber: ValSubscriber<TValue>, eager?: boolean ): ValDisposer => val.reaction(subscriber, eager); /** * Remove the given subscriber. * Remove all if no subscriber provided. * @param val * @param subscriber */ export const unsubscribe = <TValue>( val: ReadonlyVal<TValue>, subscriber?: (...args: any[]) => any ): void => val.unsubscribe(subscriber); /** Returns the value passed in. */ export const identity = <TValue>(value: TValue): TValue => value; /** * `Object.is` */ export const strictEqual = Object.is; /** * Shallow compare two arrays. * @param arrA - any value * @param arrB - any value * @returns `false` if any of: * 1. one of arrA or arrB is an array and the other is not * 2. arrA and arrB have different lengths * 3. arrA and arrB have different values at any index */ export const arrayShallowEqual = (arrA: any, arrB: any): boolean => { if (strictEqual(arrA, arrB)) { return true; } if (!Array.isArray(arrA) || !Array.isArray(arrB)) { return false; } const len = arrA.length; if (arrB.length !== len) { return false; } for (let i = 0; i < len; i++) { if (!strictEqual(arrA[i], arrB[i])) { return false; } } return true; }; const getValue = <TValue>(val: ReadonlyVal<TValue>): TValue => val.value; export const getValues = <TValInputs extends readonly ReadonlyVal[]>( valInputs: TValInputs ): [...ValInputsValueTuple<TValInputs>] => valInputs.map(getValue) as [...ValInputsValueTuple<TValInputs>]; export const getValVersion = (val$: ReadonlyVal): ValVersion => val$.$version; export interface Invoke { (fn: () => void): void; <TValue>(fn: (value: TValue) => void, value: TValue): void; } export const invoke: Invoke = <TValue>( fn: (value?: TValue) => void, value?: TValue ): void => { try { fn(value); } catch (e) { console.error(e); } }; /** * Attach a new setter to a val. * @param val$ a readonly Val * @param set a function that sets the value of val$ * @returns The same val$ with the new setter. */ export const attachSetter = <TValue>( val$: ReadonlyVal<TValue>, set: (this: void, value: TValue) => void ): Val<TValue> => (((val$ as Val<TValue>).set = set), val$ as Val<TValue>); interface IsVal { (val$: unknown): val$ is ReadonlyVal; (val$: any): val$ is ReadonlyVal; <T extends ReadonlyVal>(val$: T): val$ is T extends ReadonlyVal ? T : never; } /** * Checks if `val` is `ReadonlyVal` or `Val`. * * @returns `true` if `val` is `ReadonlyVal` or `Val`. */ export const isVal: IsVal = (val$: unknown): val$ is ReadonlyVal => !!(val$ as any)?.$valCompute; /** * Checks if `val` is a writable `Val`. * @returns `true` if `val` is a writable `Val`. */ export const isWritable = <TValue>( val$: ReadonlyVal<TValue> ): val$ is Val<TValue> => !!(val$ as Val)?.set;