diff options
| author | Pinapelz <yukais@pinapelz.com> | 2025-06-28 17:26:46 -0700 |
|---|---|---|
| committer | Pinapelz <yukais@pinapelz.com> | 2025-06-28 17:43:56 -0700 |
| commit | e4fa1e69e7ebfb627c7198fd1a9881e9327ec4d4 (patch) | |
| tree | 06284a538a6008eca75051399e47db4e5d50301c /node_modules/rxjs/src/internal/observable/fromEvent.ts | |
initial commit: scaffolding
Diffstat (limited to 'node_modules/rxjs/src/internal/observable/fromEvent.ts')
| -rw-r--r-- | node_modules/rxjs/src/internal/observable/fromEvent.ts | 339 |
1 files changed, 339 insertions, 0 deletions
diff --git a/node_modules/rxjs/src/internal/observable/fromEvent.ts b/node_modules/rxjs/src/internal/observable/fromEvent.ts new file mode 100644 index 0000000..ef15431 --- /dev/null +++ b/node_modules/rxjs/src/internal/observable/fromEvent.ts @@ -0,0 +1,339 @@ +import { innerFrom } from '../observable/innerFrom'; +import { Observable } from '../Observable'; +import { mergeMap } from '../operators/mergeMap'; +import { isArrayLike } from '../util/isArrayLike'; +import { isFunction } from '../util/isFunction'; +import { mapOneOrManyArgs } from '../util/mapOneOrManyArgs'; + +// These constants are used to create handler registry functions using array mapping below. +const nodeEventEmitterMethods = ['addListener', 'removeListener'] as const; +const eventTargetMethods = ['addEventListener', 'removeEventListener'] as const; +const jqueryMethods = ['on', 'off'] as const; + +export interface NodeStyleEventEmitter { + addListener(eventName: string | symbol, handler: NodeEventHandler): this; + removeListener(eventName: string | symbol, handler: NodeEventHandler): this; +} + +export type NodeEventHandler = (...args: any[]) => void; + +// For APIs that implement `addListener` and `removeListener` methods that may +// not use the same arguments or return EventEmitter values +// such as React Native +export interface NodeCompatibleEventEmitter { + addListener(eventName: string, handler: NodeEventHandler): void | {}; + removeListener(eventName: string, handler: NodeEventHandler): void | {}; +} + +// Use handler types like those in @types/jquery. See: +// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/847731ba1d7fa6db6b911c0e43aa0afe596e7723/types/jquery/misc.d.ts#L6395 +export interface JQueryStyleEventEmitter<TContext, T> { + on(eventName: string, handler: (this: TContext, t: T, ...args: any[]) => any): void; + off(eventName: string, handler: (this: TContext, t: T, ...args: any[]) => any): void; +} + +export interface EventListenerObject<E> { + handleEvent(evt: E): void; +} + +export interface HasEventTargetAddRemove<E> { + addEventListener( + type: string, + listener: ((evt: E) => void) | EventListenerObject<E> | null, + options?: boolean | AddEventListenerOptions + ): void; + removeEventListener( + type: string, + listener: ((evt: E) => void) | EventListenerObject<E> | null, + options?: EventListenerOptions | boolean + ): void; +} + +export interface EventListenerOptions { + capture?: boolean; + passive?: boolean; + once?: boolean; +} + +export interface AddEventListenerOptions extends EventListenerOptions { + once?: boolean; + passive?: boolean; +} + +export function fromEvent<T>(target: HasEventTargetAddRemove<T> | ArrayLike<HasEventTargetAddRemove<T>>, eventName: string): Observable<T>; +export function fromEvent<T, R>( + target: HasEventTargetAddRemove<T> | ArrayLike<HasEventTargetAddRemove<T>>, + eventName: string, + resultSelector: (event: T) => R +): Observable<R>; +export function fromEvent<T>( + target: HasEventTargetAddRemove<T> | ArrayLike<HasEventTargetAddRemove<T>>, + eventName: string, + options: EventListenerOptions +): Observable<T>; +export function fromEvent<T, R>( + target: HasEventTargetAddRemove<T> | ArrayLike<HasEventTargetAddRemove<T>>, + eventName: string, + options: EventListenerOptions, + resultSelector: (event: T) => R +): Observable<R>; + +export function fromEvent(target: NodeStyleEventEmitter | ArrayLike<NodeStyleEventEmitter>, eventName: string): Observable<unknown>; +/** @deprecated Do not specify explicit type parameters. Signatures with type parameters that cannot be inferred will be removed in v8. */ +export function fromEvent<T>(target: NodeStyleEventEmitter | ArrayLike<NodeStyleEventEmitter>, eventName: string): Observable<T>; +export function fromEvent<R>( + target: NodeStyleEventEmitter | ArrayLike<NodeStyleEventEmitter>, + eventName: string, + resultSelector: (...args: any[]) => R +): Observable<R>; + +export function fromEvent( + target: NodeCompatibleEventEmitter | ArrayLike<NodeCompatibleEventEmitter>, + eventName: string +): Observable<unknown>; +/** @deprecated Do not specify explicit type parameters. Signatures with type parameters that cannot be inferred will be removed in v8. */ +export function fromEvent<T>(target: NodeCompatibleEventEmitter | ArrayLike<NodeCompatibleEventEmitter>, eventName: string): Observable<T>; +export function fromEvent<R>( + target: NodeCompatibleEventEmitter | ArrayLike<NodeCompatibleEventEmitter>, + eventName: string, + resultSelector: (...args: any[]) => R +): Observable<R>; + +export function fromEvent<T>( + target: JQueryStyleEventEmitter<any, T> | ArrayLike<JQueryStyleEventEmitter<any, T>>, + eventName: string +): Observable<T>; +export function fromEvent<T, R>( + target: JQueryStyleEventEmitter<any, T> | ArrayLike<JQueryStyleEventEmitter<any, T>>, + eventName: string, + resultSelector: (value: T, ...args: any[]) => R +): Observable<R>; + +/** + * Creates an Observable that emits events of a specific type coming from the + * given event target. + * + * <span class="informal">Creates an Observable from DOM events, or Node.js + * EventEmitter events or others.</span> + * + *  + * + * `fromEvent` accepts as a first argument event target, which is an object with methods + * for registering event handler functions. As a second argument it takes string that indicates + * type of event we want to listen for. `fromEvent` supports selected types of event targets, + * which are described in detail below. If your event target does not match any of the ones listed, + * you should use {@link fromEventPattern}, which can be used on arbitrary APIs. + * When it comes to APIs supported by `fromEvent`, their methods for adding and removing event + * handler functions have different names, but they all accept a string describing event type + * and function itself, which will be called whenever said event happens. + * + * Every time resulting Observable is subscribed, event handler function will be registered + * to event target on given event type. When that event fires, value + * passed as a first argument to registered function will be emitted by output Observable. + * When Observable is unsubscribed, function will be unregistered from event target. + * + * Note that if event target calls registered function with more than one argument, second + * and following arguments will not appear in resulting stream. In order to get access to them, + * you can pass to `fromEvent` optional project function, which will be called with all arguments + * passed to event handler. Output Observable will then emit value returned by project function, + * instead of the usual value. + * + * Remember that event targets listed below are checked via duck typing. It means that + * no matter what kind of object you have and no matter what environment you work in, + * you can safely use `fromEvent` on that object if it exposes described methods (provided + * of course they behave as was described above). So for example if Node.js library exposes + * event target which has the same method names as DOM EventTarget, `fromEvent` is still + * a good choice. + * + * If the API you use is more callback then event handler oriented (subscribed + * callback function fires only once and thus there is no need to manually + * unregister it), you should use {@link bindCallback} or {@link bindNodeCallback} + * instead. + * + * `fromEvent` supports following types of event targets: + * + * **DOM EventTarget** + * + * This is an object with `addEventListener` and `removeEventListener` methods. + * + * In the browser, `addEventListener` accepts - apart from event type string and event + * handler function arguments - optional third parameter, which is either an object or boolean, + * both used for additional configuration how and when passed function will be called. When + * `fromEvent` is used with event target of that type, you can provide this values + * as third parameter as well. + * + * **Node.js EventEmitter** + * + * An object with `addListener` and `removeListener` methods. + * + * **JQuery-style event target** + * + * An object with `on` and `off` methods + * + * **DOM NodeList** + * + * List of DOM Nodes, returned for example by `document.querySelectorAll` or `Node.childNodes`. + * + * Although this collection is not event target in itself, `fromEvent` will iterate over all Nodes + * it contains and install event handler function in every of them. When returned Observable + * is unsubscribed, function will be removed from all Nodes. + * + * **DOM HtmlCollection** + * + * Just as in case of NodeList it is a collection of DOM nodes. Here as well event handler function is + * installed and removed in each of elements. + * + * + * ## Examples + * + * Emit clicks happening on the DOM document + * + * ```ts + * import { fromEvent } from 'rxjs'; + * + * const clicks = fromEvent(document, 'click'); + * clicks.subscribe(x => console.log(x)); + * + * // Results in: + * // MouseEvent object logged to console every time a click + * // occurs on the document. + * ``` + * + * Use `addEventListener` with capture option + * + * ```ts + * import { fromEvent } from 'rxjs'; + * + * const div = document.createElement('div'); + * div.style.cssText = 'width: 200px; height: 200px; background: #09c;'; + * document.body.appendChild(div); + * + * // note optional configuration parameter which will be passed to addEventListener + * const clicksInDocument = fromEvent(document, 'click', { capture: true }); + * const clicksInDiv = fromEvent(div, 'click'); + * + * clicksInDocument.subscribe(() => console.log('document')); + * clicksInDiv.subscribe(() => console.log('div')); + * + * // By default events bubble UP in DOM tree, so normally + * // when we would click on div in document + * // "div" would be logged first and then "document". + * // Since we specified optional `capture` option, document + * // will catch event when it goes DOWN DOM tree, so console + * // will log "document" and then "div". + * ``` + * + * @see {@link bindCallback} + * @see {@link bindNodeCallback} + * @see {@link fromEventPattern} + * + * @param target The DOM EventTarget, Node.js EventEmitter, JQuery-like event target, + * NodeList or HTMLCollection to attach the event handler to. + * @param eventName The event name of interest, being emitted by the `target`. + * @param options Options to pass through to the underlying `addListener`, + * `addEventListener` or `on` functions. + * @param resultSelector A mapping function used to transform events. It takes the + * arguments from the event handler and should return a single value. + * @return An Observable emitting events registered through `target`'s + * listener handlers. + */ +export function fromEvent<T>( + target: any, + eventName: string, + options?: EventListenerOptions | ((...args: any[]) => T), + resultSelector?: (...args: any[]) => T +): Observable<T> { + if (isFunction(options)) { + resultSelector = options; + options = undefined; + } + if (resultSelector) { + return fromEvent<T>(target, eventName, options as EventListenerOptions).pipe(mapOneOrManyArgs(resultSelector)); + } + + // Figure out our add and remove methods. In order to do this, + // we are going to analyze the target in a preferred order, if + // the target matches a given signature, we take the two "add" and "remove" + // method names and apply them to a map to create opposite versions of the + // same function. This is because they all operate in duplicate pairs, + // `addListener(name, handler)`, `removeListener(name, handler)`, for example. + // The call only differs by method name, as to whether or not you're adding or removing. + const [add, remove] = + // If it is an EventTarget, we need to use a slightly different method than the other two patterns. + isEventTarget(target) + ? eventTargetMethods.map((methodName) => (handler: any) => target[methodName](eventName, handler, options as EventListenerOptions)) + : // In all other cases, the call pattern is identical with the exception of the method names. + isNodeStyleEventEmitter(target) + ? nodeEventEmitterMethods.map(toCommonHandlerRegistry(target, eventName)) + : isJQueryStyleEventEmitter(target) + ? jqueryMethods.map(toCommonHandlerRegistry(target, eventName)) + : []; + + // If add is falsy, it's because we didn't match a pattern above. + // Check to see if it is an ArrayLike, because if it is, we want to + // try to apply fromEvent to all of it's items. We do this check last, + // because there are may be some types that are both ArrayLike *and* implement + // event registry points, and we'd rather delegate to that when possible. + if (!add) { + if (isArrayLike(target)) { + return mergeMap((subTarget: any) => fromEvent(subTarget, eventName, options as EventListenerOptions))( + innerFrom(target) + ) as Observable<T>; + } + } + + // If add is falsy and we made it here, it's because we didn't + // match any valid target objects above. + if (!add) { + throw new TypeError('Invalid event target'); + } + + return new Observable<T>((subscriber) => { + // The handler we are going to register. Forwards the event object, by itself, or + // an array of arguments to the event handler, if there is more than one argument, + // to the consumer. + const handler = (...args: any[]) => subscriber.next(1 < args.length ? args : args[0]); + // Do the work of adding the handler to the target. + add(handler); + // When we finalize, we want to remove the handler and free up memory. + return () => remove!(handler); + }); +} + +/** + * Used to create `add` and `remove` functions to register and unregister event handlers + * from a target in the most common handler pattern, where there are only two arguments. + * (e.g. `on(name, fn)`, `off(name, fn)`, `addListener(name, fn)`, or `removeListener(name, fn)`) + * @param target The target we're calling methods on + * @param eventName The event name for the event we're creating register or unregister functions for + */ +function toCommonHandlerRegistry(target: any, eventName: string) { + return (methodName: string) => (handler: any) => target[methodName](eventName, handler); +} + +/** + * Checks to see if the target implements the required node-style EventEmitter methods + * for adding and removing event handlers. + * @param target the object to check + */ +function isNodeStyleEventEmitter(target: any): target is NodeStyleEventEmitter { + return isFunction(target.addListener) && isFunction(target.removeListener); +} + +/** + * Checks to see if the target implements the required jQuery-style EventEmitter methods + * for adding and removing event handlers. + * @param target the object to check + */ +function isJQueryStyleEventEmitter(target: any): target is JQueryStyleEventEmitter<any, any> { + return isFunction(target.on) && isFunction(target.off); +} + +/** + * Checks to see if the target implements the required EventTarget methods + * for adding and removing event handlers. + * @param target the object to check + */ +function isEventTarget(target: any): target is HasEventTargetAddRemove<any> { + return isFunction(target.addEventListener) && isFunction(target.removeEventListener); +} |
