react-native-reanimated
Version:
More powerful alternative to Animated library for React Native.
144 lines (123 loc) • 4.21 kB
text/typescript
import type { NativeSyntheticEvent } from 'react-native';
import { registerEventHandler, unregisterEventHandler } from './core';
import type {
EventPayload,
IWorkletEventHandler,
ReanimatedEvent,
} from './hook/commonTypes';
import { shouldBeUseWeb } from './PlatformChecker';
const SHOULD_BE_USE_WEB = shouldBeUseWeb();
type JSEvent<Event extends object> = NativeSyntheticEvent<EventPayload<Event>>;
// In JS implementation (e.g. for web) we don't use Reanimated's
// event emitter, therefore we have to handle here
// the event that came from React Native and convert it.
function jsListener<Event extends object>(
eventName: string,
handler: (event: ReanimatedEvent<Event>) => void
) {
return (evt: JSEvent<Event>) => {
handler({ ...evt.nativeEvent, eventName } as ReanimatedEvent<Event>);
};
}
class WorkletEventHandlerNative<Event extends object>
implements IWorkletEventHandler<Event>
{
eventNames: string[];
worklet: (event: ReanimatedEvent<Event>) => void;
#viewTags: Set<number>;
#registrations: Map<number, number[]>; // keys are viewTags, values are arrays of registration ID's for each viewTag
constructor(
worklet: (event: ReanimatedEvent<Event>) => void,
eventNames: string[]
) {
this.worklet = worklet;
this.eventNames = eventNames;
this.#viewTags = new Set<number>();
this.#registrations = new Map<number, number[]>();
}
updateEventHandler(
newWorklet: (event: ReanimatedEvent<Event>) => void,
newEvents: string[]
): void {
// Update worklet and event names
this.worklet = newWorklet;
this.eventNames = newEvents;
// Detach all events
this.#registrations.forEach((registrationIDs) => {
registrationIDs.forEach((id) => unregisterEventHandler(id));
// No need to remove registrationIDs from map, since it gets overwritten when attaching
});
// Attach new events with new worklet
Array.from(this.#viewTags).forEach((tag) => {
const newRegistrations = this.eventNames.map((eventName) =>
registerEventHandler(this.worklet, eventName, tag)
);
this.#registrations.set(tag, newRegistrations);
});
}
registerForEvents(viewTag: number, fallbackEventName?: string): void {
this.#viewTags.add(viewTag);
const newRegistrations = this.eventNames.map((eventName) =>
registerEventHandler(this.worklet, eventName, viewTag)
);
this.#registrations.set(viewTag, newRegistrations);
if (this.eventNames.length === 0 && fallbackEventName) {
const newRegistration = registerEventHandler(
this.worklet,
fallbackEventName,
viewTag
);
this.#registrations.set(viewTag, [newRegistration]);
}
}
unregisterFromEvents(viewTag: number): void {
this.#viewTags.delete(viewTag);
this.#registrations.get(viewTag)?.forEach((id) => {
unregisterEventHandler(id);
});
this.#registrations.delete(viewTag);
}
}
class WorkletEventHandlerWeb<Event extends object>
implements IWorkletEventHandler<Event>
{
eventNames: string[];
listeners:
| Record<string, (event: ReanimatedEvent<ReanimatedEvent<Event>>) => void>
| Record<string, (event: JSEvent<Event>) => void>;
worklet: (event: ReanimatedEvent<Event>) => void;
constructor(
worklet: (event: ReanimatedEvent<Event>) => void,
eventNames: string[] = []
) {
this.worklet = worklet;
this.eventNames = eventNames;
this.listeners = {};
this.setupWebListeners();
}
setupWebListeners() {
this.listeners = {};
this.eventNames.forEach((eventName) => {
this.listeners[eventName] = jsListener(eventName, this.worklet);
});
}
updateEventHandler(
newWorklet: (event: ReanimatedEvent<Event>) => void,
newEvents: string[]
): void {
// Update worklet and event names
this.worklet = newWorklet;
this.eventNames = newEvents;
this.setupWebListeners();
}
registerForEvents(_viewTag: number, _fallbackEventName?: string): void {
// noop
}
unregisterFromEvents(_viewTag: number): void {
// noop
}
}
export const WorkletEventHandler = SHOULD_BE_USE_WEB
? WorkletEventHandlerWeb
: WorkletEventHandlerNative;
;