import type { Effect, Signal, ValueReactor } from "../reactor-core/index.ts" import { effect, signal } from "../reactor-core/index.ts" import type { AnyValueReactorArray, AnyValueReactorObject, GetValuesInArray, GetValuesInObject, } from "./utility.ts" import { castValueInitializer, getValuesInArray, getValuesInObject } from "./utility.ts" export type CombineLatestArray = Signal> export interface CombineLatestArrayOptions { target: T } /** * @description Combines multiple ValueReactors into a Signal that emits the latest values * from each ValueReactor whenever any of them updates. */ export const combineLatestArray = ( options: CombineLatestArrayOptions, ): CombineLatestArray => { const { target } = options const result = signal(castValueInitializer>(), { onDispose: () => { e.dispose() }, }) const e = effect(() => { result.set(getValuesInArray({ target })) }) return result } export type CombineLatestObject = Signal> export interface CombineLatestObjectOptions { target: T } /** * @description Combines multiple ValueReactors in an object into a Signal that emits the latest values * from each ValueReactor whenever any of them updates. */ export const combineLatestObject = ( options: CombineLatestObjectOptions, ): CombineLatestObject => { const { target } = options const result = signal(castValueInitializer>(), { onDispose: () => { e.dispose() }, }) const e = effect(() => { result.set(getValuesInObject({ target })) }) return result } export type MergeArray = Signal[number]> export interface MergeArrayOptions { target: T } /** * @description Merges multiple ValueReactors into a Signal that emits values * from any of the ValueReactors as they update. */ export const mergeArray = ( options: MergeArrayOptions, ): MergeArray => { const { target } = options const result = signal(castValueInitializer[number]>(), { onDispose: () => { for (const e of effects) { e.dispose() } }, }) const effects: Effect[] = [] for (const valueReactor of target) { const e = effect(() => { const value = valueReactor.get() as GetValuesInArray[number] result.set(value) }) effects.push(e) } return result } export interface WithLatestFromOptions { target: ValueReactor other: ValueReactor } /** * @description Combines two ValueReactors into a Signal that emits the latest values * from the source ValueReactor along with the latest value from the other ValueReactor. */ export const withLatestFrom = (options: WithLatestFromOptions): Signal<[T, U]> => { const { target, other } = options const result = signal(castValueInitializer<[T, U]>(), { onDispose: () => { eTarget.dispose() eOther.dispose() }, }) let lastOther = other.get() const eOther = effect(() => { const b = other.get() lastOther = b }) const eTarget = effect(() => { const a = target.get() result.set([a, lastOther]) }) return result } export interface ZipOptions { a: ValueReactor b: ValueReactor } /** * @description Zips two ValueReactors into a Signal that emits pairs of values. * Each time both ValueReactors have new values, a new pair is emitted. */ export const zip = (options: ZipOptions): Signal<[VA, VB]> => { const valueReactorA = options.a const valueReactorB = options.b let lastA = valueReactorA.getWithoutTrack() let lastB = valueReactorB.getWithoutTrack() const result = signal<[VA, VB]>(() => [lastA, lastB], { onDispose: () => { e.dispose() }, }) const queueA: VA[] = [] const queueB: VB[] = [] const e = effect(() => { const a = valueReactorA.get() const b = valueReactorB.get() if (a !== lastA) { queueA.push(a) lastA = a } if (b !== lastB) { queueB.push(b) lastB = b } if (queueA.length !== 0 && queueB.length !== 0) { const va = queueA.shift() const vb = queueB.shift() result.set([va, vb] as [VA, VB]) } }) return result }