Skip to content

Commit

Permalink
Separate Package for @react-native/event-emitter
Browse files Browse the repository at this point in the history
  • Loading branch information
yungsters committed Aug 16, 2022
1 parent be8fe7a commit a9bf973
Show file tree
Hide file tree
Showing 15 changed files with 2,168 additions and 160 deletions.
144 changes: 8 additions & 136 deletions Libraries/vendor/emitter/EventEmitter.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,139 +8,11 @@
* @format
*/

export interface EventSubscription {
remove(): void;
}

export interface IEventEmitter<TEventToArgsMap: {...}> {
addListener<TEvent: $Keys<TEventToArgsMap>>(
eventType: TEvent,
listener: (...args: $ElementType<TEventToArgsMap, TEvent>) => mixed,
context?: mixed,
): EventSubscription;

emit<TEvent: $Keys<TEventToArgsMap>>(
eventType: TEvent,
...args: $ElementType<TEventToArgsMap, TEvent>
): void;

removeAllListeners<TEvent: $Keys<TEventToArgsMap>>(eventType?: ?TEvent): void;

listenerCount<TEvent: $Keys<TEventToArgsMap>>(eventType: TEvent): number;
}

interface Registration<TArgs> {
+context: mixed;
+listener: (...args: TArgs) => mixed;
+remove: () => void;
}

type Registry<TEventToArgsMap: {...}> = $ObjMap<
TEventToArgsMap,
<TArgs>(TArgs) => Set<Registration<TArgs>>,
>;

/**
* EventEmitter manages listeners and publishes events to them.
*
* EventEmitter accepts a single type parameter that defines the valid events
* and associated listener argument(s).
*
* @example
*
* const emitter = new EventEmitter<{
* success: [number, string],
* error: [Error],
* }>();
*
* emitter.on('success', (statusCode, responseText) => {...});
* emitter.emit('success', 200, '...');
*
* emitter.on('error', error => {...});
* emitter.emit('error', new Error('Resource not found'));
*
*/
export default class EventEmitter<TEventToArgsMap: {...}>
implements IEventEmitter<TEventToArgsMap>
{
_registry: Registry<TEventToArgsMap> = {};

/**
* Registers a listener that is called when the supplied event is emitted.
* Returns a subscription that has a `remove` method to undo registration.
*/
addListener<TEvent: $Keys<TEventToArgsMap>>(
eventType: TEvent,
listener: (...args: $ElementType<TEventToArgsMap, TEvent>) => mixed,
context: mixed,
): EventSubscription {
const registrations = allocate(this._registry, eventType);
const registration: Registration<$ElementType<TEventToArgsMap, TEvent>> = {
context,
listener,
remove(): void {
registrations.delete(registration);
},
};
registrations.add(registration);
return registration;
}

/**
* Emits the supplied event. Additional arguments supplied to `emit` will be
* passed through to each of the registered listeners.
*
* If a listener modifies the listeners registered for the same event, those
* changes will not be reflected in the current invocation of `emit`.
*/
emit<TEvent: $Keys<TEventToArgsMap>>(
eventType: TEvent,
...args: $ElementType<TEventToArgsMap, TEvent>
): void {
const registrations: ?Set<
Registration<$ElementType<TEventToArgsMap, TEvent>>,
> = this._registry[eventType];
if (registrations != null) {
for (const registration of [...registrations]) {
registration.listener.apply(registration.context, args);
}
}
}

/**
* Removes all registered listeners.
*/
removeAllListeners<TEvent: $Keys<TEventToArgsMap>>(
eventType?: ?TEvent,
): void {
if (eventType == null) {
this._registry = {};
} else {
delete this._registry[eventType];
}
}

/**
* Returns the number of registered listeners for the supplied event.
*/
listenerCount<TEvent: $Keys<TEventToArgsMap>>(eventType: TEvent): number {
const registrations: ?Set<Registration<mixed>> = this._registry[eventType];
return registrations == null ? 0 : registrations.size;
}
}

function allocate<
TEventToArgsMap: {...},
TEvent: $Keys<TEventToArgsMap>,
TEventArgs: $ElementType<TEventToArgsMap, TEvent>,
>(
registry: Registry<TEventToArgsMap>,
eventType: TEvent,
): Set<Registration<TEventArgs>> {
let registrations: ?Set<Registration<TEventArgs>> = registry[eventType];
if (registrations == null) {
registrations = new Set();
registry[eventType] = registrations;
}
return registrations;
}
import type {
EventSubscription,
IEventEmitter,
} from '@react-native/event-emitter';
import EventEmitter from '@react-native/event-emitter';

export type {EventSubscription, IEventEmitter};
export default EventEmitter;
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
"@react-native-community/cli-platform-android": "^9.0.0-alpha.10",
"@react-native-community/cli-platform-ios": "^9.0.0-alpha.10",
"@react-native/assets": "1.0.0",
"@react-native/event-emitter": "^0.71.0",
"@react-native/normalize-color": "2.0.0",
"@react-native/polyfills": "2.0.0",
"abort-controller": "^3.0.0",
Expand Down
3 changes: 3 additions & 0 deletions packages/event-emitter/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"presets": ["@babel/preset-flow"]
}
10 changes: 10 additions & 0 deletions packages/event-emitter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# @react-native/event-emitter

`@react-native/event-emitter` is an event library used by React Native that is
published as an independent package so it can also be used by applications.

Consult the included type definitions for usage instructions.

## License

`idx` is [MIT licensed](./LICENSE).
83 changes: 83 additions & 0 deletions packages/event-emitter/lib/EventEmitter.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/

export interface EventSubscription {
remove(): void;
}

export interface IEventEmitter<TEventToArgsMap extends {[eventType: string]: unknown[]}> {
addListener<TEvent extends keyof TEventToArgsMap>(
eventType: TEvent,
listener: (...args: TEventToArgsMap[TEvent]) => void,
context?: unknown,
): EventSubscription;

emit<TEvent extends keyof TEventToArgsMap>(
eventType: TEvent,
...args: TEventToArgsMap[TEvent]
): void;

removeAllListeners<TEvent extends keyof TEventToArgsMap>(eventType?: TEvent | null): void;

listenerCount<TEvent extends keyof TEventToArgsMap>(eventType: TEvent): number;
}

/**
* EventEmitter manages listeners and publishes events to them.
*
* EventEmitter accepts a single type parameter that defines the valid events
* and associated listener argument(s).
*
* @example
*
* const emitter = new EventEmitter<{
* success: [number, string],
* error: [Error],
* }>();
*
* emitter.on('success', (statusCode, responseText) => {...});
* emitter.emit('success', 200, '...');
*
* emitter.on('error', error => {...});
* emitter.emit('error', new Error('Resource not found'));
*
*/
export default class EventEmitter<TEventToArgsMap extends {[eventType: string]: unknown[]}> implements IEventEmitter<TEventToArgsMap> {
/**
* Registers a listener that is called when the supplied event is emitted.
* Returns a subscription that has a `remove` method to undo registration.
*/
addListener<TEvent extends keyof TEventToArgsMap>(
eventType: TEvent,
listener: (...args: TEventToArgsMap[TEvent]) => void,
context?: unknown,
): EventSubscription;

/**
* Emits the supplied event. Additional arguments supplied to `emit` will be
* passed through to each of the registered listeners.
*
* If a listener modifies the listeners registered for the same event, those
* changes will not be reflected in the current invocation of `emit`.
*/
emit<TEvent extends keyof TEventToArgsMap>(
eventType: TEvent,
...args: TEventToArgsMap[TEvent]
): void;

/**
* Removes all registered listeners.
*/
removeAllListeners<TEvent extends keyof TEventToArgsMap>(eventType?: TEvent | null): void;

/**
* Returns the number of registered listeners for the supplied event.
*/
listenerCount<TEvent extends keyof TEventToArgsMap>(eventType: TEvent): number;
}
98 changes: 98 additions & 0 deletions packages/event-emitter/lib/EventEmitter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/

/**
* EventEmitter manages listeners and publishes events to them.
*
* EventEmitter accepts a single type parameter that defines the valid events
* and associated listener argument(s).
*
* @example
*
* const emitter = new EventEmitter<{
* success: [number, string],
* error: [Error],
* }>();
*
* emitter.on('success', (statusCode, responseText) => {...});
* emitter.emit('success', 200, '...');
*
* emitter.on('error', error => {...});
* emitter.emit('error', new Error('Resource not found'));
*
*/
export default class EventEmitter {
_registry = {};
/**
* Registers a listener that is called when the supplied event is emitted.
* Returns a subscription that has a `remove` method to undo registration.
*/

addListener(eventType, listener, context) {
const registrations = allocate(this._registry, eventType);
const registration = {
context,
listener,

remove() {
registrations.delete(registration);
},
};
registrations.add(registration);
return registration;
}
/**
* Emits the supplied event. Additional arguments supplied to `emit` will be
* passed through to each of the registered listeners.
*
* If a listener modifies the listeners registered for the same event, those
* changes will not be reflected in the current invocation of `emit`.
*/

emit(eventType, ...args) {
const registrations = this._registry[eventType];

if (registrations != null) {
for (const registration of [...registrations]) {
registration.listener.apply(registration.context, args);
}
}
}
/**
* Removes all registered listeners.
*/

removeAllListeners(eventType) {
if (eventType == null) {
this._registry = {};
} else {
delete this._registry[eventType];
}
}
/**
* Returns the number of registered listeners for the supplied event.
*/

listenerCount(eventType) {
const registrations = this._registry[eventType];
return registrations == null ? 0 : registrations.size;
}
}

function allocate(registry, eventType) {
let registrations = registry[eventType];

if (registrations == null) {
registrations = new Set();
registry[eventType] = registrations;
}

return registrations;
}
Loading

0 comments on commit a9bf973

Please sign in to comment.