import {deep} from "../tools/deep/deep.js" import {SignalListener} from "./parts/listener.js" import {accessed} from "./parts/accessed_symbol.js" import {debounce} from "../tools/debounce/debounce.js" import {SignalCircularError} from "./parts/circular_error.js" export class Signal { static unwrap = ( (anything: any): A extends Signal ? V : A => { return anything instanceof Signal ? anything.value : anything } ) #value: V #lock = false #wait: Promise #listeners = new Set>() ;[accessed] = false constructor(v: V) { this.#value = v this.#wait = Promise.resolve(v) } /** @deprecated use `on` method instead */ subscribe(listener: SignalListener) { return this.on(listener) } on(listener: SignalListener) { this.#listeners.add(listener) return (): void => void this.#listeners.delete(listener) } once(listener: SignalListener) { const actual_listener: SignalListener = v => { listener(v) this.#listeners.delete(actual_listener) } this.#listeners.add(actual_listener) return (): void => void this.#listeners.delete(actual_listener) } clear() { return this.#listeners.clear() } #invoke_listeners = debounce(0, () => { const value = this.#value this.#lock = true for (const listener of this.#listeners) listener(value) this.#lock = false return value }) async publish() { this.#wait = this.#invoke_listeners() await this.#wait } get wait() { return this.#wait } get value() { this[accessed] = true return this.#value } set value(v: V) { if (this.#lock) throw new SignalCircularError( "you can't set a signal in a signal's subscription listener (infinite loop forbidden)" ) if (this.#value !== v) { this.#value = v this.publish() } } /** set the signal value and publish, only if a deep change is detected (uses deep.equal to scan whole object trees) */ setDeep(v: V) { if (this.#lock) throw new SignalCircularError( "you can't set a signal in a signal's subscription listener (infinite loop forbidden)" ) if (!deep.equal(v, this.#value)) { this.#value = v this.publish() } } /** set the signal value and publish, even if there's no change detected */ setAndPublish(v: V) { if (this.#lock) throw new SignalCircularError( "you can't set a signal in a signal's subscription listener (infinite loop forbidden)" ) this.#value = v this.publish() } /** set the signal value, but do not publish (perhaps to prevent views rerendering) */ setWithoutPublish(v: V) { if (this.#lock) throw new SignalCircularError( "you can't set a signal in a signal's subscription listener (infinite loop forbidden)" ) this.#value = v } /** @deprecated use `setWithoutPublish` instead */ setValueNoPublish(v: V) { return this.setWithoutPublish(v) } }