Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/createAnimatedComponent/PropsFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { shallowEqual } from '../reanimated2/hook/utils';
import type { StyleProps } from '../reanimated2/commonTypes';
import { isSharedValue } from '../reanimated2/isSharedValue';
import { isChromeDebugger } from '../reanimated2/PlatformChecker';
import WorkletEventHandler from '../reanimated2/WorkletEventHandler';
import { WorkletEventHandler } from '../reanimated2/WorkletEventHandler';
import { initialUpdaterRun } from '../reanimated2/animation';
import { hasInlineStyles, getInlineStyle } from './InlinePropManager';
import type {
Expand Down
80 changes: 41 additions & 39 deletions src/createAnimatedComponent/createAnimatedComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type {
} from 'react';
import React from 'react';
import { findNodeHandle, Platform } from 'react-native';
import WorkletEventHandler from '../reanimated2/WorkletEventHandler';
import { WorkletEventHandler } from '../reanimated2/WorkletEventHandler';
import '../reanimated2/layoutReanimation/animationsManager';
import invariant from 'invariant';
import { adaptViewConfig } from '../ConfigHelper';
Expand Down Expand Up @@ -254,7 +254,45 @@ export function createAnimatedComponent(
has('workletEventHandler', prop) &&
prop.workletEventHandler instanceof WorkletEventHandler
) {
prop.workletEventHandler.unregisterFromEvents();
prop.workletEventHandler.unregisterFromEvents(this._viewTag);
}
}
}

_updateNativeEvents(
Comment thread
szydlovsky marked this conversation as resolved.
prevProps: AnimatedComponentProps<InitialComponentProps>
) {
for (const key in prevProps) {
const prevProp = prevProps[key];
if (
has('workletEventHandler', prevProp) &&
prevProp.workletEventHandler instanceof WorkletEventHandler
) {
const newProp = this.props[key];
if (!newProp) {
// Prop got deleted
prevProp.workletEventHandler.unregisterFromEvents(this._viewTag);
} else if (
has('workletEventHandler', newProp) &&
newProp.workletEventHandler instanceof WorkletEventHandler &&
newProp.workletEventHandler !== prevProp.workletEventHandler
) {
// Prop got changed
prevProp.workletEventHandler.unregisterFromEvents(this._viewTag);
newProp.workletEventHandler.registerForEvents(this._viewTag);
}
}
}

for (const key in this.props) {
const newProp = this.props[key];
if (
has('workletEventHandler', newProp) &&
newProp.workletEventHandler instanceof WorkletEventHandler &&
!prevProps[key]
) {
// Prop got added
newProp.workletEventHandler.registerForEvents(this._viewTag);
Comment thread
szydlovsky marked this conversation as resolved.
}
}
}
Expand All @@ -277,42 +315,6 @@ export function createAnimatedComponent(
}
}

_reattachNativeEvents(
prevProps: AnimatedComponentProps<InitialComponentProps>
) {
for (const key in prevProps) {
const prop = this.props[key];
if (
has('workletEventHandler', prop) &&
prop.workletEventHandler instanceof WorkletEventHandler &&
prop.workletEventHandler.reattachNeeded
) {
prop.workletEventHandler.unregisterFromEvents();
}
}

let viewTag = null;

for (const key in this.props) {
const prop = this.props[key];
if (
has('workletEventHandler', prop) &&
prop.workletEventHandler instanceof WorkletEventHandler &&
prop.workletEventHandler.reattachNeeded
) {
if (viewTag === null) {
const node = this._getEventViewRef() as AnimatedComponentRef;

viewTag = IS_WEB
? this._component
: findNodeHandle(options?.setNativeProps ? this : node);
}
prop.workletEventHandler.registerForEvents(viewTag as number, key);
prop.workletEventHandler.reattachNeeded = false;
}
}
}

_updateFromNative(props: StyleProps) {
if (options?.setNativeProps) {
options.setNativeProps(this._component as AnimatedComponentRef, props);
Expand Down Expand Up @@ -460,7 +462,7 @@ export function createAnimatedComponent(
) {
this._configureSharedTransition();
}
this._reattachNativeEvents(prevProps);
this._updateNativeEvents(prevProps);
this._attachAnimatedStyles();
this._InlinePropManager.attachInlineProps(this, this._getViewInfo());

Expand Down
170 changes: 118 additions & 52 deletions src/reanimated2/WorkletEventHandler.ts
Original file line number Diff line number Diff line change
@@ -1,83 +1,149 @@
'use strict';
import type { NativeSyntheticEvent } from 'react-native';
import { registerEventHandler, unregisterEventHandler } from './core';
import type { EventPayload, ReanimatedEvent } from './hook/commonTypes';
import type {
EventPayload,
ReanimatedEvent,
IWorkletEventHandler,
} from './hook/commonTypes';
import { shouldBeUseWeb } from './PlatformChecker';

const SHOULD_BE_USE_WEB = shouldBeUseWeb();

type JSEvent<Event extends object> = NativeSyntheticEvent<EventPayload<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
Comment thread
szydlovsky marked this conversation as resolved.
Outdated
constructor(
worklet: (event: ReanimatedEvent<Event>) => void,
eventNames: string[] = []
Comment thread
szydlovsky marked this conversation as resolved.
Outdated
) {
this.#worklet = worklet;
Comment thread
szydlovsky marked this conversation as resolved.
Outdated
this.eventNames = eventNames;
this.#viewTags = new Set<number>();
this.#registrations = new Map<number, number[]>();
}

Comment thread
szydlovsky marked this conversation as resolved.
// 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>);
};
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, tag) => {
Comment thread
tomekzaw marked this conversation as resolved.
Outdated
registrationIDs.forEach((id) => {
unregisterEventHandler(id);
});
Comment thread
szydlovsky marked this conversation as resolved.
Outdated
this.#registrations.set(tag, []);
Comment thread
szydlovsky marked this conversation as resolved.
Outdated
});

// Attach new events with new worklet
Array.from(this.#viewTags).forEach((tag) => {
const newRegistrations = this.eventNames.map((eventName) => {
return 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) => {
return registerEventHandler(this.#worklet, eventName, viewTag);
});
this.#registrations.set(viewTag, newRegistrations);

if (this.eventNames.length === 0 && fallbackEventName) {
Comment thread
szydlovsky marked this conversation as resolved.
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);
}
}

export default class WorkletEventHandler<Event extends object> {
worklet: (event: ReanimatedEvent<Event>) => void;
class WorkletEventHandlerWeb<Event extends object>
implements IWorkletEventHandler<Event>
{
eventNames: string[];
reattachNeeded: boolean;
listeners:
| Record<string, (event: ReanimatedEvent<ReanimatedEvent<Event>>) => void>
| Record<string, (event: JSEvent<Event>) => void>;

viewTag: number | undefined;
registrations: number[];
#worklet: (event: ReanimatedEvent<Event>) => void;

constructor(
worklet: (event: ReanimatedEvent<Event>) => void,
eventNames: string[] = []
) {
this.worklet = worklet;
this.#worklet = worklet;
this.eventNames = eventNames;
this.reattachNeeded = false;
this.listeners = {};
this.viewTag = undefined;
this.registrations = [];

if (SHOULD_BE_USE_WEB) {
this.listeners = eventNames.reduce(
(
acc: Record<string, (event: JSEvent<Event>) => void>,
eventName: string
) => {
acc[eventName] = jsListener(eventName, worklet);
return acc;
},
{}
);
}
this.setupWebListeners();
}

updateWorklet(newWorklet: (event: ReanimatedEvent<Event>) => void): void {
this.worklet = newWorklet;
this.reattachNeeded = true;
setupWebListeners() {
this.listeners = this.eventNames.reduce(
(
acc: Record<string, (event: JSEvent<Event>) => void>,
eventName: string
) => {
acc[eventName] = jsListener(eventName, this.#worklet);
return acc;
},
{}
);
Comment thread
szydlovsky marked this conversation as resolved.
Outdated
}

registerForEvents(viewTag: number, fallbackEventName?: string): void {
this.viewTag = viewTag;
this.registrations = this.eventNames.map((eventName) =>
registerEventHandler(this.worklet, eventName, viewTag)
);
if (this.registrations.length === 0 && fallbackEventName) {
this.registrations.push(
registerEventHandler(this.worklet, fallbackEventName, viewTag)
);
}
updateEventHandler(
newWorklet: (event: ReanimatedEvent<Event>) => void,
newEvents: string[]
): void {
// Update worklet and event names
this.#worklet = newWorklet;
this.eventNames = newEvents;
this.setupWebListeners();
}

registerForEventByName(eventName: string) {
this.registrations.push(registerEventHandler(this.worklet, eventName));
registerForEvents(_viewTag: number, _fallbackEventName?: string): void {
// noop
}

unregisterFromEvents(): void {
this.registrations.forEach((id) => unregisterEventHandler(id));
this.registrations = [];
unregisterFromEvents(_viewTag: number): void {
// noop
}
}

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>);
};
}
Comment thread
szydlovsky marked this conversation as resolved.
Outdated

export const WorkletEventHandler = SHOULD_BE_USE_WEB
? WorkletEventHandlerWeb
: WorkletEventHandlerNative;
9 changes: 9 additions & 0 deletions src/reanimated2/hook/commonTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,15 @@ export type RNNativeScrollEvent = NativeSyntheticEvent<NativeScrollEvent>;

export type ReanimatedScrollEvent = ReanimatedEvent<RNNativeScrollEvent>;

export interface IWorkletEventHandler<Event extends object> {
updateEventHandler: (
newWorklet: (event: ReanimatedEvent<Event>) => void,
newEvents: string[]
) => void;
registerForEvents: (viewTag: number, fallbackEventName?: string) => void;
unregisterFromEvents: (viewTag: number) => void;
}

export interface AnimatedStyleHandle<
Style extends DefaultStyle = DefaultStyle
> {
Expand Down
8 changes: 4 additions & 4 deletions src/reanimated2/hook/useEvent.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use strict';
import { useRef } from 'react';
import WorkletEventHandler from '../WorkletEventHandler';
import type { ReanimatedEvent } from './commonTypes';
import { WorkletEventHandler } from '../WorkletEventHandler';
import type { IWorkletEventHandler, ReanimatedEvent } from './commonTypes';

/**
* Worklet to provide as an argument to `useEvent` hook.
Expand All @@ -17,7 +17,7 @@ export type EventHandlerProcessed<
> = (event: Event, context?: Context) => void;

export type EventHandlerInternal<Event extends object> = {
workletEventHandler: WorkletEventHandler<Event>;
workletEventHandler: IWorkletEventHandler<Event>;
};

/**
Expand Down Expand Up @@ -57,7 +57,7 @@ export function useEvent<Event extends object, Context = never>(
initRef.current = { workletEventHandler };
} else if (rebuild) {
const workletEventHandler = initRef.current.workletEventHandler;
workletEventHandler.updateWorklet(handler);
workletEventHandler.updateEventHandler(handler, eventNames);
initRef.current = { workletEventHandler };
}

Expand Down
18 changes: 13 additions & 5 deletions src/reanimated2/hook/useScrollViewOffset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ function useScrollViewOffsetNative(
const internalOffset = useSharedValue(0);
const offset = useRef(providedOffset ?? internalOffset).current;
const scrollRef = useRef<AnimatedScrollView | null>(null);
const scrollRefTag = useRef<number | null>(null);

const eventHandler = useEvent<RNNativeScrollEvent>(
(event: ReanimatedScrollEvent) => {
Expand All @@ -99,15 +100,22 @@ function useScrollViewOffsetNative(
useEffect(() => {
// We need to make sure that listener for old animatedRef value is removed
if (scrollRef.current !== null) {
eventHandler.workletEventHandler.unregisterFromEvents();
eventHandler.workletEventHandler.unregisterFromEvents(
scrollRefTag.current as number
);
}

// Store the ref and viewTag for future cleanup
scrollRef.current = animatedRef.current;
scrollRefTag.current = findNodeHandle(scrollRef.current);

const component = animatedRef.current;
const viewTag = findNodeHandle(component);
eventHandler.workletEventHandler.registerForEvents(viewTag as number);
eventHandler.workletEventHandler.registerForEvents(
scrollRefTag.current as number
);
return () => {
eventHandler.workletEventHandler.unregisterFromEvents();
eventHandler.workletEventHandler.unregisterFromEvents(
scrollRefTag.current as number
);
};
// React here has a problem with `animatedRef.current` since a Ref .current
// field shouldn't be used as a dependency. However, in this case we have
Expand Down