From a08d51e4db381ab31de843d6d80bd2115421a230 Mon Sep 17 00:00:00 2001 From: DarkWiiPlayer Date: Mon, 24 Jun 2024 12:26:41 +0200 Subject: [PATCH] More code documentation --- observable.js | 48 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 6 deletions(-) diff --git a/observable.js b/observable.js index 8fc3125..ff63d91 100644 --- a/observable.js +++ b/observable.js @@ -12,17 +12,31 @@ const target = Symbol("Proxy Target") /* Custom Event Classes */ +/** + * @typedef {Object} Change + * @property {string} property + * @property {any} from + * @property {any} to + * @property {boolean} mutation - The change happened inside the value without a new assignment + */ + +/** Event fired for every change before the internal state has been updated that can be canceled. */ export class SynchronousChangeEvent extends Event { + /** @param {Change} change */ constructor(change) { super('synchronous', {cancelable: true}) this.change = Object.freeze(change) } } +/** Event fired for one or more changed values after the internal state has been updated. */ export class MultiChangeEvent extends Event { + /** @type {any} */ #final + /** @type {any} */ #values + /** @param {Change[]} changes */ constructor(...changes) { super('change') this.changes = changes @@ -67,7 +81,7 @@ export class ValueChangeEvent extends MultiChangeEvent { export class Observable extends EventTarget { #synchronous - /** @type Array<{name:string, from, to}> */ + /** @type Change[]> */ #queue #abortController = new AbortController @@ -89,24 +103,37 @@ export class Observable extends EventTarget { }) } - proxy(prop, {get, set, ...options}={}) { + /** + * @param {string} prop + */ + proxy(prop, {get=undefined, set=undefined, ...options}={}) { const proxy = new ProxiedObservableValue(this, prop, options) if (get) proxy.get = get if (set) proxy.set = set return proxy } + /** + * @param {string} prop + * @param {function} callback + */ subscribe(prop, callback) { - if (!callback) return this.subscribe("value", prop) - const controller = new AbortController() + // @ts-ignore this.addEventListener("change", ({final}) => { if (final.has(prop)) return callback(final.get(prop)) }, {signal: controller.signal}) - callback(this.value) + + callback(this[prop]) return () => controller.abort() } + /** Queues up a change event + * @param {string} property - Name of the changed property + * @param {any} from + * @param {any} to + * @param {boolean} mutation - whether a change was an assignment or a mutation (nested change) + */ enqueue(property, from, to, mutation=false) { const change = {property, from, to, mutation} if (!this.dispatchEvent(new SynchronousChangeEvent(change))) return false @@ -125,7 +152,8 @@ export class Observable extends EventTarget { return true } - emit() { + /** @param {any[]} _args */ + emit(..._args) { throw new TypeError(`${this.constructor.name} did not define an 'emit' method`) } @@ -216,6 +244,14 @@ export class ObservableValue extends Observable { } } + /** + * @param {function(any):undefined} callback + * @param {function(any):undefined} _callback + */ + subscribe(callback, _callback) { + this.constructor.prototype.subscribe.call(this, "value", callback) + } + emit(...changes) { this.dispatchEvent(new ValueChangeEvent(...changes)) }