|
| 1 | +/** |
| 2 | + * Copyright (c) 2022 Jonas "DerZade" Schade |
| 3 | + * |
| 4 | + * SPDX-License-Identifier: MIT |
| 5 | + * |
| 6 | + * https://github.com/DerZade/typescript-event-target/blob/master/src/TypedEventTarget.ts |
| 7 | + */ |
| 8 | + |
| 9 | +/** |
| 10 | + * A function that can be passed to the `listener` parameter of {@link TypedEventTarget.addEventListener} and {@link TypedEventTarget.removeEventListener}. |
| 11 | + * |
| 12 | + * @template M A map of event types to their respective event classes. |
| 13 | + * @template T The type of event to listen for (has to be keyof `M`). |
| 14 | + */ |
| 15 | +export type TypedEventListener<M, T extends keyof M> = ( |
| 16 | + evt: M[T] |
| 17 | +) => void | Promise<void>; |
| 18 | + |
| 19 | +/** |
| 20 | + * An object that can be passed to the `listener` parameter of {@link TypedEventTarget.addEventListener} and {@link TypedEventTarget.removeEventListener}. |
| 21 | + * |
| 22 | + * @template M A map of event types to their respective event classes. |
| 23 | + * @template T The type of event to listen for (has to be keyof `M`). |
| 24 | + */ |
| 25 | +export interface TypedEventListenerObject<M, T extends keyof M> { |
| 26 | + handleEvent: (evt: M[T]) => void | Promise<void>; |
| 27 | +} |
| 28 | + |
| 29 | +/** |
| 30 | + * Type of parameter `listener` in {@link TypedEventTarget.addEventListener} and {@link TypedEventTarget.removeEventListener}. |
| 31 | + * |
| 32 | + * The object that receives a notification (an object that implements the Event interface) when an event of the specified type occurs. |
| 33 | + * |
| 34 | + * Can be either an object with a handleEvent() method, or a JavaScript function. |
| 35 | + * |
| 36 | + * @template M A map of event types to their respective event classes. |
| 37 | + * @template T The type of event to listen for (has to be keyof `M`). |
| 38 | + */ |
| 39 | +export type TypedEventListenerOrEventListenerObject<M, T extends keyof M> = |
| 40 | + | TypedEventListener<M, T> |
| 41 | + | TypedEventListenerObject<M, T>; |
| 42 | + |
| 43 | +type ValueIsEvent<T> = { |
| 44 | + [key in keyof T]: Event; |
| 45 | +}; |
| 46 | + |
| 47 | +/** |
| 48 | + * Typescript friendly version of {@link EventTarget} |
| 49 | + * |
| 50 | + * @template M A map of event types to their respective event classes. |
| 51 | + * |
| 52 | + * @example |
| 53 | + * ```typescript |
| 54 | + * interface MyEventMap { |
| 55 | + * hello: Event; |
| 56 | + * time: CustomEvent<number>; |
| 57 | + * } |
| 58 | + * |
| 59 | + * const eventTarget = new TypedEventTarget<MyEventMap>(); |
| 60 | + * |
| 61 | + * eventTarget.addEventListener('time', (event) => { |
| 62 | + * // event is of type CustomEvent<number> |
| 63 | + * }); |
| 64 | + * ``` |
| 65 | + */ |
| 66 | +export interface TypedEventTarget<M extends ValueIsEvent<M>> { |
| 67 | + /** Appends an event listener for events whose type attribute value is type. |
| 68 | + * The callback argument sets the callback that will be invoked when the event |
| 69 | + * is dispatched. |
| 70 | + * |
| 71 | + * The options argument sets listener-specific options. For compatibility this |
| 72 | + * can be a boolean, in which case the method behaves exactly as if the value |
| 73 | + * was specified as options's capture. |
| 74 | + * |
| 75 | + * When set to true, options's capture prevents callback from being invoked |
| 76 | + * when the event's eventPhase attribute value is BUBBLING_PHASE. When false |
| 77 | + * (or not present), callback will not be invoked when event's eventPhase |
| 78 | + * attribute value is CAPTURING_PHASE. Either way, callback will be invoked if |
| 79 | + * event's eventPhase attribute value is AT_TARGET. |
| 80 | + * |
| 81 | + * When set to true, options's passive indicates that the callback will not |
| 82 | + * cancel the event by invoking preventDefault(). This is used to enable |
| 83 | + * performance optimizations described in § 2.8 Observing event listeners. |
| 84 | + * |
| 85 | + * When set to true, options's once indicates that the callback will only be |
| 86 | + * invoked once after which the event listener will be removed. |
| 87 | + * |
| 88 | + * The event listener is appended to target's event listener list and is not |
| 89 | + * appended if it has the same type, callback, and capture. */ |
| 90 | + addEventListener: <T extends keyof M & string>( |
| 91 | + type: T, |
| 92 | + listener: TypedEventListenerOrEventListenerObject<M, T> | null, |
| 93 | + options?: boolean | AddEventListenerOptions |
| 94 | + ) => void; |
| 95 | + |
| 96 | + /** Removes the event listener in target's event listener list with the same |
| 97 | + * type, callback, and options. */ |
| 98 | + removeEventListener: <T extends keyof M & string>( |
| 99 | + type: T, |
| 100 | + callback: TypedEventListenerOrEventListenerObject<M, T> | null, |
| 101 | + options?: EventListenerOptions | boolean |
| 102 | + ) => void; |
| 103 | + |
| 104 | + /** |
| 105 | + * Dispatches a synthetic event event to target and returns true if either |
| 106 | + * event's cancelable attribute value is false or its preventDefault() method |
| 107 | + * was not invoked, and false otherwise. |
| 108 | + * @deprecated To ensure type safety use `dispatchTypedEvent` instead. |
| 109 | + */ |
| 110 | + dispatchEvent: (event: Event) => boolean; |
| 111 | +} |
| 112 | +// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging |
| 113 | +export class TypedEventTarget<M extends ValueIsEvent<M>> extends EventTarget { |
| 114 | + /** |
| 115 | + * Dispatches a synthetic event event to target and returns true if either |
| 116 | + * event's cancelable attribute value is false or its preventDefault() method |
| 117 | + * was not invoked, and false otherwise. |
| 118 | + */ |
| 119 | + public dispatchTypedEvent<T extends keyof M>(_type: T, event: M[T]): boolean { |
| 120 | + return super.dispatchEvent(event); |
| 121 | + } |
| 122 | +} |
0 commit comments