diff --git a/src/controller-registry.js b/src/controller-registry.js index 160f087..d75e91a 100644 --- a/src/controller-registry.js +++ b/src/controller-registry.js @@ -1,5 +1,7 @@ /** @typedef {Promise & {signal: AbortSignal}} PromiseWithSignal */ -/** @typedef {(element: HTMLElement, detach: PromiseWithSignal) => void} Callback */ +/** @typedef {(element: HTMLElement, detached: PromiseWithSignal) => void} Callback */ +/** @typedef {new (element: HTMLElement, detached: PromiseWithSignal) => Object} ControllerClass */ +/** @typedef {Callback|ControllerClass} Controller */ export class ControllerList { /** @type {HTMLElement} */ @@ -105,6 +107,11 @@ export class ControllerRegistry { /** @type {Map} */ #defined = new Map() + /** @type {Map} */ + #lookup = new Map() + /** @type {Map} */ + #nameLookup = new Map() + #attribute /** @typedef {Document|DocumentFragment|HTMLElement} Root */ @@ -141,20 +148,25 @@ export class ControllerRegistry { /** * @param {string} name - * @param {Callback} callback + * @param {Controller} callback */ define(name, callback) { + if (this.#nameLookup.has(callback)) console.warn(`Redefining controller ${this.#nameLookup.get(callback)} under new name ${name}:`, callback) + + this.#lookup.set(name, callback) + this.#nameLookup.set(callback, name) + if (("function" == typeof callback) && callback.prototype) { callback = async (element, disconnected) => { const {proxy, revoke} = Proxy.revocable(element, {}) - const controller = new callback(proxy, disconnected) + const controller = new /** @type {ControllerClass} */(callback)(proxy, disconnected) await disconnected revoke() if ("detach" in controller) controller.detach(element) } } - this.#defined.set(name, callback) + this.#defined.set(name, /** @type {Callback} */(callback)) const waitingList = this.#waiting.get(name) @@ -162,13 +174,42 @@ export class ControllerRegistry { this.#attach(element, name) } this.#waiting.delete(name) + + if (this.#whenDefined.has(name)) { + this.#whenDefined.get(name)[1]?.() + } } /** Gets a controller associated with a given name * @param {string} name */ get(name) { - return this.#defined.get(name) + return this.#lookup.get(name) + } + + /** Gets the name a controller is registered with + * @param {Controller} controller + */ + getName(controller) { + return this.#nameLookup.get(controller) + } + + /** @type {Mapvoid]>} */ + #whenDefined = new Map() + /** + * @param {string} name + */ + whenDefined(name) { + if (!this.#whenDefined.has(name)) { + if (this.#defined.has(name)) { + this.#whenDefined.set(name, [Promise.resolve(), undefined]) + } else { + let resolve + const promise = new Promise(_resolve => {resolve = _resolve}) + this.#whenDefined.set(name, [promise, resolve]) + } + } + return this.#whenDefined.get(name)[0] } /** @type {WeakMap} */ @@ -192,18 +233,10 @@ export class ControllerRegistry { const attached = this.#attached.get(element) if (attached) return [...attached.entries().filter(pair => pair[1]).map(pair => pair[0])] - else + else return [] } - getName(controller) { - // TODO: Return name of controller - } - - whenDefined(name) { - // TODO: Return a promise - } - /** @param {HTMLElement} element */ #update(element) { const names = this.#getControllerNames(element) @@ -238,7 +271,7 @@ export class ControllerRegistry { if (!this.#attached.has(element)) this.#attached.set(element, new Map()) const attached = this.#attached.get(element) - const callback = this.get(name) + const callback = this.#defined.get(name) if (callback) { if (attached.has("name") && attached.get("name")) return console.warn(`Controller ${name} already fully attached`, element)