Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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';
import { isSharedValue } from '../reanimated2';
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
79 changes: 41 additions & 38 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 @@ -55,6 +55,7 @@ import { updateLayoutAnimations } from '../reanimated2/UpdateLayoutAnimations';
import type { CustomConfig } from '../reanimated2/layoutReanimation/web/config';
import type { FlatList, FlatListProps } from 'react-native';
import { addHTMLMutationObserver } from '../reanimated2/layoutReanimation/web/domUtils';
import type { IWorkletEventHandler } from '../reanimated2/hook/commonTypes';
import { getViewInfo } from './getViewInfo';

const IS_WEB = isWeb();
Expand Down Expand Up @@ -254,61 +255,63 @@ export function createAnimatedComponent(
has('workletEventHandler', prop) &&
prop.workletEventHandler instanceof WorkletEventHandler
) {
prop.workletEventHandler.unregisterFromEvents();
}
}
}

_detachStyles() {
if (IS_WEB && this._styles !== null) {
for (const style of this._styles) {
style.viewsRef.remove(this);
}
} else if (this._viewTag !== -1 && this._styles !== null) {
for (const style of this._styles) {
style.viewDescriptors.remove(this._viewTag);
}
if (this.props.animatedProps?.viewDescriptors) {
this.props.animatedProps.viewDescriptors.remove(this._viewTag);
}
if (isFabric()) {
removeFromPropsRegistry(this._viewTag);
prop.workletEventHandler.unregisterFromEvents(
this._getViewInfo().viewTag as number
Comment thread
szydlovsky marked this conversation as resolved.
Outdated
);
}
}
}

_reattachNativeEvents(
_updateNativeEvents(
Comment thread
szydlovsky marked this conversation as resolved.
prevProps: AnimatedComponentProps<InitialComponentProps>
) {
let previousHandler: IWorkletEventHandler<Event> | undefined;
let newHandler: IWorkletEventHandler<Event> | undefined;

// Get previous handler
Comment thread
szydlovsky marked this conversation as resolved.
Outdated
for (const key in prevProps) {
const prop = this.props[key];
const prop = prevProps[key];
if (
has('workletEventHandler', prop) &&
prop.workletEventHandler instanceof WorkletEventHandler &&
prop.workletEventHandler.reattachNeeded
prop.workletEventHandler instanceof WorkletEventHandler
) {
prop.workletEventHandler.unregisterFromEvents();
previousHandler = prop.workletEventHandler;
}
}

let viewTag = null;

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

viewTag = IS_WEB
? this._component
: findNodeHandle(options?.setNativeProps ? this : node);
}
prop.workletEventHandler.registerForEvents(viewTag as number, key);
prop.workletEventHandler.reattachNeeded = false;
if (previousHandler !== newHandler) {
// Handler changed, we need to unregister from the previous one and register to the new one
const viewTag = this._getViewInfo().viewTag as number;
Comment thread
szydlovsky marked this conversation as resolved.
Outdated
previousHandler?.unregisterFromEvents(viewTag);
newHandler?.registerForEvents(viewTag);
}
}

_detachStyles() {
if (IS_WEB && this._styles !== null) {
for (const style of this._styles) {
style.viewsRef.remove(this);
}
} else if (this._viewTag !== -1 && this._styles !== null) {
for (const style of this._styles) {
style.viewDescriptors.remove(this._viewTag);
}
if (this.props.animatedProps?.viewDescriptors) {
this.props.animatedProps.viewDescriptors.remove(this._viewTag);
}
if (isFabric()) {
removeFromPropsRegistry(this._viewTag);
}
}
}
Expand Down Expand Up @@ -460,7 +463,7 @@ export function createAnimatedComponent(
) {
this._configureSharedTransition();
}
this._reattachNativeEvents(prevProps);
this._updateNativeEvents(prevProps);
this._attachAnimatedStyles();
this._InlinePropManager.attachInlineProps(this, this._getViewInfo());

Expand Down
153 changes: 113 additions & 40 deletions src/reanimated2/WorkletEventHandler.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,93 @@
'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>>;
type EventHandlerRegistration = {
id: number;
viewTag: number;
};
Comment thread
szydlovsky marked this conversation as resolved.
Outdated

class WorkletEventHandlerNative<Event extends object>
implements IWorkletEventHandler<Event>
{
worklet: (event: ReanimatedEvent<Event>) => void;
eventNames: string[];
viewTags: Set<number>;
registrations: EventHandlerRegistration[];
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;
this.eventNames = eventNames;
this.viewTags = new Set<number>();
this.registrations = [];
}

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((registration) => {
unregisterEventHandler(registration.id);
});
this.registrations = [];

// Attach new events with new worklet
this.registrations = this.eventNames.flatMap((eventName) => {
return Array.from(this.viewTags).map((tag) => {
return {
id: registerEventHandler(this.worklet, eventName, tag),
viewTag: tag,
};
});
});
Comment thread
szydlovsky marked this conversation as resolved.
Outdated
}

registerForEvents(viewTag: number, fallbackEventName?: string): void {
this.viewTags.add(viewTag);

Comment thread
szydlovsky marked this conversation as resolved.
this.registrations = this.registrations.concat(
this.eventNames.map((eventName) => {
return {
id: registerEventHandler(this.worklet, eventName, viewTag),
viewTag,
};
})
);

if (this.eventNames.length === 0 && fallbackEventName) {
Comment thread
szydlovsky marked this conversation as resolved.
this.registrations.push({
id: registerEventHandler(this.worklet, fallbackEventName, viewTag),
viewTag,
});
}
}

unregisterFromEvents(viewTag: number): void {
this.viewTags.delete(viewTag);
this.registrations.forEach((registration) => {
if (registration.viewTag === viewTag) {
unregisterEventHandler(registration.id);
}
});
}
}

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.
Expand All @@ -20,64 +100,57 @@ function jsListener<Event extends object>(
};
}

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

viewTag: number | undefined;
registrations: number[];
constructor(
worklet: (event: ReanimatedEvent<Event>) => void,
eventNames: string[] = []
) {
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;
},
{}
);
}

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
}
}

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
8 changes: 4 additions & 4 deletions src/reanimated2/hook/useScrollViewOffset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,15 @@ function useScrollViewOffsetNative(
useEffect(() => {
// We need to make sure that listener for old animatedRef value is removed
if (scrollRef.current !== null) {
eventHandler.workletEventHandler.unregisterFromEvents();
const oldTag = findNodeHandle(scrollRef.current);
Comment thread
szydlovsky marked this conversation as resolved.
Outdated
eventHandler.workletEventHandler.unregisterFromEvents(oldTag as number);
}
scrollRef.current = animatedRef.current;

const component = animatedRef.current;
const viewTag = findNodeHandle(component);
const viewTag = findNodeHandle(animatedRef.current);
eventHandler.workletEventHandler.registerForEvents(viewTag as number);
return () => {
eventHandler.workletEventHandler.unregisterFromEvents();
eventHandler.workletEventHandler.unregisterFromEvents(viewTag 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