From cdc56f5848b739e95d932c03bf1a2fe24af68787 Mon Sep 17 00:00:00 2001 From: DarkWiiPlayer Date: Wed, 27 Dec 2023 19:44:50 +0100 Subject: [PATCH] Add nested state change forwarding --- state.js | 51 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/state.js b/state.js index c0bfef7..22e99a7 100644 --- a/state.js +++ b/state.js @@ -14,6 +14,8 @@ export class ChangeEvent extends Event { } } +class SimpleState extends EventTarget {} + export class MapStorage extends Storage { #map = new Map() key(index) { @@ -36,20 +38,36 @@ export class MapStorage extends Storage { } } -export class State extends EventTarget { +export class State extends SimpleState { #target #options #queue #forwardCache + #abortController + #nested = new Map() + #weakRef = new WeakRef(this) + + static isState(object) { return SimpleState.prototype.isPrototypeOf(object) } constructor(target={}, options={}) { super() + + this.#abortController = new AbortController + abortRegistry.register(this, this.#abortController) + this.#options = options this.#target = target this.proxy = new Proxy(target, { set: (_target, prop, value) => { - this.emit(prop, value) - this.set(prop, value) + const old = this.get(prop) + if (old !== value) { + this.emit(prop, value) + if (this.#options.deep !== false) { + if (State.isState(old)) this.disown(prop, old) + if (State.isState(value)) this.adopt(prop, value) + } + this.set(prop, value) + } return true }, get: (_target, prop) => this.get(prop), @@ -72,6 +90,29 @@ export class State extends EventTarget { set value(value) { this.proxy.value = value } get value() { return this.proxy.value } + adopt(prop, state) { + let handlers = this.#nested.get(state) + if (!handlers) { + // Actual adoption + handlers = new Map() + this.#nested.set(state, handlers) + } + const ref = this.#weakRef + const handler = () => ref.deref()?.emit(prop, state) + + handlers.set(prop, handler) + state.addEventListener("change", handler, {signal: this.#abortController.signal}) + } + disown(prop, state) { + const handlers = this.#nested.get(state) + const handler = handlers.get(prop) + state.removeEventListener("change", handler) + handlers.delete(prop) + if (handlers.size == 0) { + this.#nested.delete(state) + } + } + // Anounces that a prop has changed emit(prop, value) { if (this.#options.defer ?? true) { @@ -115,7 +156,7 @@ const forwardFinalizationRegistry = new FinalizationRegistry(([cache, name]) => cache.remove(name) }) -export class ForwardState extends EventTarget { +export class ForwardState extends SimpleState { #backend #property #fallback @@ -235,7 +276,7 @@ export const component = (generator, name) => { return Element; } -class ComposedState extends EventTarget { +class ComposedState extends SimpleState { #func #states #options