Commit b436080d authored by Robert Knight's avatar Robert Knight

Make `ListenerCollection.add` smarter

Enable the `ListenerCollection.add` method to know the specific type of
event that a listener will receive, based on the event target and event
name. This simplifies usage by removing the need to cast the argument in
most cases.

The implementation relies on the target having an `on${eventName}`
property from which the event type can be extracted. If it doesn't, it
will fall back to `Event`.
parent 54cd9438
...@@ -252,7 +252,7 @@ export class Guest { ...@@ -252,7 +252,7 @@ export class Guest {
}; };
this._listeners.add(this.element, 'mouseup', event => { this._listeners.add(this.element, 'mouseup', event => {
const { target, metaKey, ctrlKey } = /** @type {MouseEvent} */ (event); const { target, metaKey, ctrlKey } = event;
const tags = annotationsAt(/** @type {Element} */ (target)); const tags = annotationsAt(/** @type {Element} */ (target));
if (tags.length && this._highlightsVisible) { if (tags.length && this._highlightsVisible) {
const toggle = metaKey || ctrlKey; const toggle = metaKey || ctrlKey;
......
...@@ -5,6 +5,23 @@ ...@@ -5,6 +5,23 @@
* @prop {(event: Event) => void} listener * @prop {(event: Event) => void} listener
*/ */
/**
* Return the event type that a listener will receive.
*
* For example `EventType<HTMLElement, 'keydown'>` evaluates to `KeyboardEvent`.
*
* The event type is extracted from the target's `on${Type}` property (eg.
* `HTMLElement.onkeydown` here) If there is no such property, the type defaults
* to `Event`.
*
* @template {EventTarget} Target
* @template {string} Type
* @typedef {`on${Type}` extends keyof Target ?
* Target[`on${Type}`] extends ((...args: any[]) => void)|null ?
* Parameters<NonNullable<Target[`on${Type}`]>>[0]
* : Event : Event} EventType
*/
/** /**
* Utility that provides a way to conveniently remove a set of DOM event * Utility that provides a way to conveniently remove a set of DOM event
* listeners when they are no longer needed. * listeners when they are no longer needed.
...@@ -18,20 +35,31 @@ export class ListenerCollection { ...@@ -18,20 +35,31 @@ export class ListenerCollection {
/** /**
* Add a listener and return an ID that can be used to remove it later * Add a listener and return an ID that can be used to remove it later
* *
* @param {Listener['eventTarget']} eventTarget * @template {string} Type
* @param {Listener['eventType']} eventType * @template {EventTarget} Target
* @param {Listener['listener']} listener * @param {Target} eventTarget
* @param {Type} eventType
* @param {(event: EventType<Target, Type>) => void} listener
* @param {AddEventListenerOptions} [options] * @param {AddEventListenerOptions} [options]
*/ */
add(eventTarget, eventType, listener, options) { add(eventTarget, eventType, listener, options) {
eventTarget.addEventListener(eventType, listener, options); eventTarget.addEventListener(
eventType,
/** @type {EventListener} */ (listener),
options
);
const symbol = Symbol(); const symbol = Symbol();
this._listeners.set(symbol, { eventTarget, eventType, listener }); this._listeners.set(symbol, {
eventTarget,
eventType,
// eslint-disable-next-line object-shorthand
listener: /** @type {EventListener} */ (listener),
});
return symbol; return symbol;
} }
/** /**
* Remove a listener using a listenerId * Remove a specific listener.
* *
* @param {Symbol} listenerId * @param {Symbol} listenerId
*/ */
......
...@@ -94,7 +94,7 @@ export class PortFinder { ...@@ -94,7 +94,7 @@ export class PortFinder {
}, MAX_WAIT_FOR_PORT); }, MAX_WAIT_FOR_PORT);
const listenerId = this._listeners.add(window, 'message', event => { const listenerId = this._listeners.add(window, 'message', event => {
const { data, ports } = /** @type {MessageEvent} */ (event); const { data, ports } = event;
if ( if (
isMessageEqual(data, { isMessageEqual(data, {
frame1: this._source, frame1: this._source,
......
...@@ -135,9 +135,9 @@ export class PortProvider { ...@@ -135,9 +135,9 @@ export class PortProvider {
sendError(new Error(message), errorContext); sendError(new Error(message), errorContext);
}; };
/** @param {Event} event */ /** @param {MessageEvent} event */
const handleRequest = event => { const handleRequest = event => {
const { data, origin, source } = /** @type {MessageEvent} */ (event); const { data, origin, source } = event;
if (!isMessage(data) || data.type !== 'request') { if (!isMessage(data) || data.type !== 'request') {
// If this does not look like a message intended for us, ignore it. // If this does not look like a message intended for us, ignore it.
......
...@@ -230,9 +230,7 @@ export class PortRPC { ...@@ -230,9 +230,7 @@ export class PortRPC {
*/ */
connect(port) { connect(port) {
this._port = port; this._port = port;
this._listeners.add(port, 'message', event => this._listeners.add(port, 'message', event => this._handle(event));
this._handle(/** @type {MessageEvent} */ (event))
);
port.start(); port.start();
sendCall(port, 'connect'); sendCall(port, 'connect');
......
...@@ -392,7 +392,7 @@ export class FrameSyncService { ...@@ -392,7 +392,7 @@ export class FrameSyncService {
// Listen for guests connecting to the sidebar. // Listen for guests connecting to the sidebar.
this._listeners.add(hostPort, 'message', event => { this._listeners.add(hostPort, 'message', event => {
const { data, ports } = /** @type {MessageEvent} */ (event); const { data, ports } = event;
if ( if (
isMessageEqual(data, { isMessageEqual(data, {
frame1: 'guest', frame1: 'guest',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment