UNPKG

vuse-rx

Version:

First-class rxjs support for Vue 3

206 lines (181 loc) 5.5 kB
import { Ref, UnwrapRef, ref, watch, WatchOptions, WatchStopHandle } from 'vue'; /** * Creates a binding between two refs.\ * The binding can be: * - One-way if only the one mapper is defined. * - Two-way if both mappers (`to` and `from`) are defined. * * The resulting ref serves as an origin point for the binding,\ * values **from** the resulting ref and **to** the resulting ref are mapped onto the first. */ export function syncRef<R1, R2>( ref1: Ref<R1>, { to }: { to: Mapper<R1, UnwrapRef<R2>> }, ): SyncedRef<R1, 'to', UnwrapRef<R2>>; export function syncRef<R1, R2>( ref1: Ref<R1>, { from }: { from: Mapper<UnwrapRef<R2>, R1> }, defaultValue?: R2, ): SyncedRef<R1, 'from', UnwrapRef<R2>>; export function syncRef<R1, R2>( ref1: Ref<R1>, { to, from }: { to: Mapper<R1, UnwrapRef<R2>>, from: Mapper<UnwrapRef<R2>, R1> }, ): SyncedRef<R1, 'to' | 'from', UnwrapRef<R2>>; /** * Creates a binding between two refs.\ * The binding can be: * - One-way if only the one mapper is defined. * - Two-way if both mappers (`to` and `from`) are defined. * * The resulting ref serves as an origin point for the binding,\ * values **from** the resulting ref and **to** the resulting ref are mapped onto the first. */ export function syncRef<R1, R2, M extends Readonly<Mappers<R1, R2>> = Mappers<R1, R2>>( ref1: Ref<R1>, { to, from }: M, defaultValue?: R2, ): SyncedRef<R1, keyof M, UnwrapRef<R2>>; /** * Creates a binding between two refs.\ * The binding can be: * - One-way if only the one mapper is defined. * - Two-way if both mappers (`to` and `from`) are defined. * * The second ref serves as an origin point for the binding,\ * values **from** the second ref and **to** the second ref are mapped onto the first. */ export function syncRef<R1, R2, M extends Readonly<Mappers<R1, R2>> = Mappers<R1, R2>>( ref1: Ref<R1>, { to, from }: M, ref2: Ref<R2>, ): SyncedRef<R1, keyof M, R2>; export function syncRef<R1, R2>( this: WatchOptions, ref1: Ref<R1>, maps: Mappers<R1, R2>, _ref2?: Ref<R2> | R2, ): _SyncedRef<R1, keyof Mappers<R1, R2>, R2> { const ref2 = ref( _ref2 == null ? maps.to ? maps.to(ref1.value) : ref1.value : _ref2 ) as _SyncedRef<R1, keyof Mappers<R1, R2>, R2>; for (const key in maps) ( ref2[key as keyof typeof maps] = bind(ref1, ref2, maps, key as any, this) ).bind(); return ref2; } syncRef.with = <T extends Readonly<boolean> = false>( ...options: WatchOptions<T>[] ): typeof syncRef => { const opts = Object.assign({}, ...options); const f = syncRef.bind(opts); f.with = syncRef.with.bind(opts); return f; } const bind = <R1, R2>( refBase: Ref<R1>, refDest: _SyncedRef<R1, keyof Mappers<R1, R2>, R2>, maps: Mappers<R1, R2>, dir: keyof Mappers<R1, R2>, options: WatchOptions, ) => ({ bind: (bindOptions?: Partial<BindingOptions<R1, R2, keyof Mappers<R1, R2>>>) => { const { ref, map, watch: opts }: BindingOptions<any, R2, any> = { ref: refBase, map: maps[dir]!, watch: options, ...bindOptions, }; refDest[dir]!.stop(); refDest[dir]!.stop = dir === 'to' ? watch(ref, v => refDest.value = map(v), Object.assign({}, options, opts)) : watch(refDest, v => ref.value = map(v), Object.assign({}, options, opts)); }, stop: () => {} }); type Mapper<F, T> = (value: F) => T; type Mappers<R1, R2> = { /** * A map from the first ref to the second */ to: Mapper<R1, R2>, /** * A map from the second ref to the first */ from?: never, } | { /** * A map from the first ref to the second */ to?: never, /** * A map from the second ref to the first */ from: Mapper<R2, R1>, } | { /** * A map from the first ref to the second */ to: Mapper<R1, R2>, /** * A map from the second ref to the first */ from: Mapper<R2, R1>, }; type Binders<R1, R2, Keys extends PropertyKey> = { [key in Keys]: { /** * Cut the binding in this direction */ stop: WatchStopHandle; bind: { /** * Reapply the binding in this direction */ (): void; /** * Bind this direction to a new ref */ (options?: BindingOptions<R1, R2, key>): void; /** * Bind this direction to a new ref with a new map */ <T>(options: CustomBindingOptions<T, R2, key>): void; }; }; }; /** * Allows to correctly declare a reusable options object for the [`.bind`](https://vuse-rx.raiondesu.dev/api/refs#change-ref-bindings) method. */ export type BindingOptions<R1, R2, Key extends PropertyKey> = { ref?: Ref<R1>; map?: Key extends 'to' ? Mapper<R1, R2> : Mapper<R2, R1>; watch?: WatchOptions; } /** * Allows to correctly declare a reusable options object for the [`.bind`](https://vuse-rx.raiondesu.dev/api/refs#change-ref-bindings) method. * Has a semantic difference from `BindingOptions` in that it's supposed to be used with a new ref type instead of the original first ref */ export type CustomBindingOptions<T, R2, Key extends PropertyKey> = { ref: Ref<T>; map: Key extends 'to' ? Mapper<T, R2> : Mapper<R2, T>; watch?: WatchOptions; } /** * Return type of the `syncRef` function, combined Vue Ref with `syncRef`'s bindings */ export type SyncedRef< R1, Keys extends PropertyKey, R2, > = Ref<R2> & Binders<R1, R2, Keys>; type _SyncedRef< R1, _Keys extends PropertyKey, R2, > = Ref<R2> & Partial<Binders<R1, R2, keyof Mappers<R1, R2>>>;