diff --git a/api-documents/kit.watcher.addlistener.md b/api-documents/kit.watcher.addlistener.md index e226b51..60b5752 100644 --- a/api-documents/kit.watcher.addlistener.md +++ b/api-documents/kit.watcher.addlistener.md @@ -7,7 +7,7 @@ <b>Signature:</b> ```typescript -addListener<K extends keyof WatcherEvents<T>>(type: K, callback: EventListener<WatcherEvents<T>, K>): this; +addListener<K extends keyof WatcherEvents<T>>(type: K, callback: EventListener<WatcherEvents<T>, K>, options?: AddEventListenerOptions): this; ``` ## Parameters @@ -16,6 +16,7 @@ addListener<K extends keyof WatcherEvents<T>>(type: K, callback: EventListener<W | --- | --- | --- | | type | K | | | callback | EventListener<[WatcherEvents](./kit.watcherevents.md)<!-- --><T>, K> | | +| options | AddEventListenerOptions | <i>(Optional)</i> | <b>Returns:</b> diff --git a/api-documents/kit.watcher.md b/api-documents/kit.watcher.md index 30965b4..59c4cd2 100644 --- a/api-documents/kit.watcher.md +++ b/api-documents/kit.watcher.md @@ -49,7 +49,7 @@ export declare abstract class Watcher<T, Before extends Element, After extends E | Method | Modifiers | Description | | --- | --- | --- | -| [addListener(type, callback)](./kit.watcher.addlistener.md) | | | +| [addListener(type, callback, options)](./kit.watcher.addlistener.md) | | | | [assignKeys(keyAssigner)](./kit.watcher.assignkeys.md) | | To help identify same nodes in different iteration, you need to implement a map function that map <code>node</code> to <code>key</code>If the key is changed, the same node will call through <code>forEachRemove</code> then <code>forEach</code> | | [defaultStarterForThen()](./kit.watcher.defaultstarterforthen.md) | | | | [dismissSingleModeWarning()](./kit.watcher.dismisssinglemodewarning.md) | | Dismiss the warning that let you enable single mode but the warning is false positive. | diff --git a/api-documents/kit.webextensionmessage.eventregistry.md b/api-documents/kit.webextensionmessage.eventregistry.md index d749d5e..daab9fd 100644 --- a/api-documents/kit.webextensionmessage.eventregistry.md +++ b/api-documents/kit.webextensionmessage.eventregistry.md @@ -7,5 +7,5 @@ <b>Signature:</b> ```typescript -protected get eventRegistry(): EventRegistry; +protected get eventRegistry(): Emitter<any>; ``` diff --git a/api-documents/kit.webextensionmessage.md b/api-documents/kit.webextensionmessage.md index 97304d4..9892f7a 100644 --- a/api-documents/kit.webextensionmessage.md +++ b/api-documents/kit.webextensionmessage.md @@ -22,7 +22,7 @@ export declare class WebExtensionMessage<Message> | --- | --- | --- | --- | | [domain](./kit.webextensionmessage.domain.md) | | string | Same message name within different domain won't collide with each other. | | [enableLog](./kit.webextensionmessage.enablelog.md) | | boolean | | -| [eventRegistry](./kit.webextensionmessage.eventregistry.md) | | EventRegistry | | +| [eventRegistry](./kit.webextensionmessage.eventregistry.md) | | Emitter<any> | | | [events](./kit.webextensionmessage.events.md) | | { readonly \[K in keyof Message\]: [UnboundedRegistry](./kit.unboundedregistry.md)<!-- --><Message\[K\]>; } | Event listeners | | [log](./kit.webextensionmessage.log.md) | | (...args: unknown\[\]) => void | | | [logFormatter](./kit.webextensionmessage.logformatter.md) | | (instance: this, key: string, data: unknown) => unknown\[\] | | diff --git a/doc/holoflows-kit.api.report.md b/doc/holoflows-kit.api.report.md index ddf1d53..ccd41a9 100644 --- a/doc/holoflows-kit.api.report.md +++ b/doc/holoflows-kit.api.report.md @@ -7,7 +7,7 @@ /// <reference types="web-ext-types" /> import { Emitter } from '@servie/events'; -import { EventListener as EventListener_2 } from '@servie/events'; +import type { EventListener as EventListener_2 } from '@servie/events'; // @public export function assertEnvironment(env: Environment): void; @@ -258,7 +258,7 @@ export class ValueRef<T> { export abstract class Watcher<T, Before extends Element, After extends Element, SingleMode extends boolean> implements PromiseLike<ResultOf<SingleMode, T>> { constructor(liveSelector: LiveSelector<T, SingleMode>); // (undocumented) - addListener<K extends keyof WatcherEvents<T>>(type: K, callback: EventListener_2<WatcherEvents<T>, K>): this; + addListener<K extends keyof WatcherEvents<T>>(type: K, callback: EventListener_2<WatcherEvents<T>, K>, options?: AddEventListenerOptions): this; assignKeys<Q = unknown>(keyAssigner: (node: T, index: number, arr: readonly T[]) => Q): this; // (undocumented) protected defaultStarterForThen(): void; @@ -348,10 +348,8 @@ export class WebExtensionMessage<Message> { get domain(): string; // (undocumented) enableLog: boolean; - // Warning: (ae-forgotten-export) The symbol "EventRegistry" needs to be exported by the entry point index.d.ts - // // (undocumented) - protected get eventRegistry(): EventRegistry; + protected get eventRegistry(): Emitter<any>; get events(): { readonly [K in keyof Message]: UnboundedRegistry<Message[K]>; }; diff --git a/src/DOM/Watcher.ts b/src/DOM/Watcher.ts index 3f16059..10f4d61 100644 --- a/src/DOM/Watcher.ts +++ b/src/DOM/Watcher.ts @@ -10,12 +10,13 @@ * - Event watcher (based on addEventListener) */ import { DOMProxy, DOMProxyOptions } from './Proxy.js' -import { Emitter, EventListener } from '@servie/events' +import type { EventListener } from '@servie/events' import type { LiveSelector } from './LiveSelector.js' import { Deadline, requestIdleCallback } from '../util/requestIdleCallback.js' import { isNil, uniqWith, intersectionWith, differenceWith } from 'lodash-es' import { timeout } from '../util/timeout.js' +import { createEventTarget } from '../util/EventTarget.js' /** * Use LiveSelector to watch dom change @@ -23,8 +24,7 @@ import { timeout } from '../util/timeout.js' export abstract class Watcher<T, Before extends Element, After extends Element, SingleMode extends boolean> implements PromiseLike<ResultOf<SingleMode, T>> { - private eventEmitter: Emitter<WatcherEvents<T>> = new Emitter() - private removeListenerWeakMap = new Map<string, WeakMap<Function, Function>>() + private events = createEventTarget<WatcherEvents<T>>() /** * The liveSelector that this object holds. */ @@ -320,28 +320,28 @@ export abstract class Watcher<T, Before extends Element, After extends Element, this.lastKeyList = thisKeyList this.lastNodeList = currentIteration - if (this.eventEmitter.$.onIteration.size !== 0 && changedNodes.length + goneKeys.length + newKeys.length > 0) { + if (this.events.has('onIteration') && changedNodes.length + goneKeys.length + newKeys.length > 0) { // Make a copy to prevent modifications const newMap = new Map<unknown, T>(newKeys.map((key) => [key, findFromNew(key)!])) const removedMap = new Map<unknown, T>(goneKeys.map((key) => [key, findFromLast(key)!])) const currentMap = new Map<unknown, T>(thisKeyList.map((key) => [key, findFromNew(key)!])) - this.eventEmitter.emit('onIteration', { + this.events.emit('onIteration', { new: newMap, removed: removedMap, current: currentMap, }) } - if (this.eventEmitter.$.onChange.size !== 0) + if (this.events.has('onChange')) for (const [oldNode, newNode, oldKey, newKey] of changedNodes) { - this.eventEmitter.emit('onChange', { oldValue: oldNode, newValue: newNode, oldKey, newKey }) + this.events.emit('onChange', { oldValue: oldNode, newValue: newNode, oldKey, newKey }) } - if (this.eventEmitter.$.onRemove.size !== 0) + if (this.events.has('onRemove')) for (const key of goneKeys) { - this.eventEmitter.emit('onRemove', { key, value: findFromLast(key)! }) + this.events.emit('onRemove', { key, value: findFromLast(key)! }) } - if (this.eventEmitter.$.onAdd.size !== 0) + if (this.events.has('onAdd')) for (const key of newKeys) { - this.eventEmitter.emit('onAdd', { key, value: findFromNew(key)! }) + this.events.emit('onAdd', { key, value: findFromNew(key)! }) } // For firstDOMProxy const first = currentIteration[0] @@ -392,7 +392,7 @@ export abstract class Watcher<T, Before extends Element, After extends Element, if (this.singleModeLastValue instanceof Node) { this._firstDOMProxy.realCurrent = null } - this.eventEmitter.emit('onRemove', { key: undefined, value: this.singleModeLastValue! }) + this.events.emit('onRemove', { key: undefined, value: this.singleModeLastValue! }) this.singleModeLastValue = undefined this.singleModeHasLastValue = false } @@ -410,14 +410,14 @@ export abstract class Watcher<T, Before extends Element, After extends Element, applyUseForeachCallback(this.singleModeCallback)('warn mutation')(this._warning_mutation_) } } - this.eventEmitter.emit('onAdd', { key: undefined, value: firstValue }) + this.events.emit('onAdd', { key: undefined, value: firstValue }) this.singleModeLastValue = firstValue this.singleModeHasLastValue = true } // ? Case: value has changed else if (this.singleModeHasLastValue && !this.valueComparer(this.singleModeLastValue!, firstValue)) { applyUseForeachCallback(this.singleModeCallback)('target change')(firstValue, this.singleModeLastValue!) - this.eventEmitter.emit('onChange', { + this.events.emit('onChange', { newKey: undefined, oldKey: undefined, newValue: firstValue, @@ -471,13 +471,16 @@ export abstract class Watcher<T, Before extends Element, After extends Element, //#endregion //#region events - addListener<K extends keyof WatcherEvents<T>>(type: K, callback: EventListener<WatcherEvents<T>, K>): this { - if (!this.removeListenerWeakMap.has(type)) this.removeListenerWeakMap.set(type, new WeakMap()) - this.removeListenerWeakMap.get(type)!.set(callback, this.eventEmitter.on(type, callback)) + addListener<K extends keyof WatcherEvents<T>>( + type: K, + callback: EventListener<WatcherEvents<T>, K>, + options?: AddEventListenerOptions, + ): this { + this.events.add(type, callback, options) return this } removeListener<K extends keyof WatcherEvents<T>>(type: K, callback: EventListener<WatcherEvents<T>, K>): this { - this.removeListenerWeakMap.get(type)?.get(callback)?.() + this.events.remove(type, callback) return this } //#endregion diff --git a/src/DOM/Watchers/EventWatcher.ts b/src/DOM/Watchers/EventWatcher.ts index c4ccfc9..8a3810c 100644 --- a/src/DOM/Watchers/EventWatcher.ts +++ b/src/DOM/Watchers/EventWatcher.ts @@ -27,6 +27,7 @@ export class EventWatcher< this.requestIdleCallback(this.scheduleWatcherCheck, { timeout: 500 }) } override startWatch(signal?: AbortSignal) { + super.startWatch() signal?.addEventListener( 'abort', () => { diff --git a/src/Extension/MessageChannel.ts b/src/Extension/MessageChannel.ts index d39ebc3..e9de14d 100644 --- a/src/Extension/MessageChannel.ts +++ b/src/Extension/MessageChannel.ts @@ -2,6 +2,7 @@ /* eslint-disable no-bitwise */ import { Emitter } from '@servie/events' import { EventIterator } from 'event-iterator' +import { createEventTarget } from '../util/EventTarget.js' import { Environment, getEnvironment, isEnvironment } from './Context.js' /** @@ -231,9 +232,9 @@ export class WebExtensionMessage<Message> { } public enableLog = false public log: (...args: unknown[]) => void = console.log - #eventRegistry: EventRegistry = new Emitter<any>() + #eventRegistry = createEventTarget<Record<string, [unknown]>>() protected get eventRegistry() { - return this.#eventRegistry + return this.#eventRegistry.emitter } } //#region Internal message handling @@ -272,7 +273,7 @@ function shouldAcceptThisMessage(target: BoundTarget) { function UnboundedRegistry<T>( instance: WebExtensionMessage<T>, eventName: string, - eventListener: Emitter<any>, + eventListener: ReturnType<typeof createEventTarget<any>>, ): UnboundedRegistry<T> { //#region Batch message let pausing = false @@ -294,17 +295,11 @@ function UnboundedRegistry<T>( }) } let binder: TargetBoundEventRegistry<T> - const removeListenerWeakMap = new WeakMap<Function, Function>() function on(cb: (data: T) => void, options?: TargetBoundEventListenerOptions) { - const off = eventListener.on(eventName, cb) - removeListenerWeakMap.set(cb, off) - - if (options?.once) eventListener.on(eventName, off) - if (options?.signal) options.signal.addEventListener('abort', off, { once: true }) - return off + return eventListener.add(eventName, cb, options) } function off(cb: (data: T) => void) { - removeListenerWeakMap.get(cb)?.() + eventListener.remove(eventName, cb) } function pause() { pausing = true @@ -343,7 +338,6 @@ function UnboundedRegistry<T>( } return self } -type EventRegistry = Emitter<Record<string, [unknown]>> type BoundTarget = | { kind: 'tab'; id: number } | { kind: 'target'; target: MessageTarget | Environment } diff --git a/src/util/EventTarget.ts b/src/util/EventTarget.ts new file mode 100644 index 0000000..e8ae891 --- /dev/null +++ b/src/util/EventTarget.ts @@ -0,0 +1,33 @@ +import { Emitter, EventListener, once, ValidEventArgs } from '@servie/events' + +/** @internal */ +export function createEventTarget<T = any>() { + const emitter = new Emitter<T>() + const offWeakMap = new Map<PropertyKey, WeakMap<Function, Function>>() + function getOff(key: PropertyKey) { + if (offWeakMap.has(key)) return offWeakMap.get(key)! + const off = new WeakMap<Function, Function>() + offWeakMap.set(key, off) + return off + } + + return { + has(key: keyof T) { + return (emitter.$[key]?.size || 0) > 0 + }, + add<K extends keyof T>(event: K, callback: EventListener<T, K>, options?: AddEventListenerOptions) { + const off = options?.once ? once(emitter, event, callback) : emitter.on(event, callback) + + getOff(event).set(callback, off) + options?.signal?.addEventListener('abort', off, { once: true }) + return off + }, + remove<K extends keyof T>(event: K, callback: EventListener<T, K>) { + getOff(event).get(callback)?.() + }, + emitter, + emit<K extends keyof T>(type: K, ...args: ValidEventArgs<T, K>) { + emitter.emit(type, ...args) + } + } +}