diff --git a/src/reanimated2/animation/timing.ts b/src/reanimated2/animation/timing.ts index dfbd8a46908b..3ed2ef224e1f 100644 --- a/src/reanimated2/animation/timing.ts +++ b/src/reanimated2/animation/timing.ts @@ -1,7 +1,11 @@ 'use strict'; import type { EasingFunction, EasingFunctionFactory } from '../Easing'; import { Easing } from '../Easing'; -import { defineAnimation, getReduceMotionForAnimation } from './util'; +import { + assertEasingIsWorklet, + defineAnimation, + getReduceMotionForAnimation, +} from './util'; import type { Animation, AnimationCallback, @@ -65,6 +69,10 @@ export const withTiming = function ( ): Animation { 'worklet'; + if (__DEV__ && userConfig?.easing) { + assertEasingIsWorklet(userConfig.easing); + } + return defineAnimation(toValue, () => { 'worklet'; const config: Required> = { diff --git a/src/reanimated2/animation/util.ts b/src/reanimated2/animation/util.ts index f3b4dc8e043d..dcd425d69062 100644 --- a/src/reanimated2/animation/util.ts +++ b/src/reanimated2/animation/util.ts @@ -9,7 +9,7 @@ import { toGammaSpace, toLinearSpace, } from '../Colors'; -import { ReduceMotion } from '../commonTypes'; +import { ReduceMotion, isWorkletFunction } from '../commonTypes'; import type { SharedValue, AnimatableValue, @@ -33,9 +33,11 @@ import { getRotationMatrix, } from './transformationMatrix/matrixUtils'; import { isReducedMotion, shouldBeUseWeb } from '../PlatformChecker'; +import type { EasingFunction, EasingFunctionFactory } from '../Easing'; let IN_STYLE_UPDATER = false; const IS_REDUCED_MOTION = isReducedMotion(); +const SHOULD_BE_USE_WEB = shouldBeUseWeb(); if (__DEV__ && IS_REDUCED_MOTION) { console.warn( @@ -43,6 +45,26 @@ if (__DEV__ && IS_REDUCED_MOTION) { ); } +export function assertEasingIsWorklet( + easing: EasingFunction | EasingFunctionFactory +): void { + 'worklet'; + if (_WORKLET) { + // If this is called on UI (for example from gesture handler with worklets), we don't get easing, + // but its bound copy, which is not a worklet. We don't want to throw any error then. + return; + } + if (SHOULD_BE_USE_WEB) { + // It is possible to run reanimated on web without plugin, so let's skip this check on web + return; + } + if (!isWorkletFunction(easing)) { + throw new Error( + '[Reanimated] The easing function is not a worklet. Please make sure you import `Easing` from react-native-reanimated.' + ); + } +} + export function initialUpdaterRun(updater: () => T) { IN_STYLE_UPDATER = true; const result = updater(); @@ -478,8 +500,6 @@ type AnimationToDecoration< ? Record : U | (() => U) | AnimatableValue; -const SHOULD_BE_USE_WEB = shouldBeUseWeb(); - export function defineAnimation< T extends AnimationObject | StyleLayoutAnimation, // type that's supposed to be returned U extends AnimationObject | StyleLayoutAnimation = T // type that's received diff --git a/src/reanimated2/commonTypes.ts b/src/reanimated2/commonTypes.ts index 189c32de6a17..58d9627b0771 100644 --- a/src/reanimated2/commonTypes.ts +++ b/src/reanimated2/commonTypes.ts @@ -131,7 +131,10 @@ export function isWorkletFunction< 'worklet'; // Since host objects always return true for `in` operator, we have to use dot notation to check if the property exists. // See https://github.com/facebook/hermes/blob/340726ef8cf666a7cce75bc60b02fa56b3e54560/lib/VM/JSObject.cpp#L1276. - return !!(value as Record).__workletHash; + return ( + typeof value === 'function' && + !!(value as unknown as Record).__workletHash + ); } export type AnimatedPropsAdapterFunction = ( diff --git a/src/reanimated2/layoutReanimation/animationBuilder/ComplexAnimationBuilder.ts b/src/reanimated2/layoutReanimation/animationBuilder/ComplexAnimationBuilder.ts index fd92dc4818af..115c09a41672 100644 --- a/src/reanimated2/layoutReanimation/animationBuilder/ComplexAnimationBuilder.ts +++ b/src/reanimated2/layoutReanimation/animationBuilder/ComplexAnimationBuilder.ts @@ -8,6 +8,7 @@ import type { import type { EasingFunction } from '../../Easing'; import { BaseAnimationBuilder } from './BaseAnimationBuilder'; import type { StyleProps } from '../../commonTypes'; +import { assertEasingIsWorklet } from '../../animation/util'; export class ComplexAnimationBuilder extends BaseAnimationBuilder { easingV?: EasingFunction; @@ -40,6 +41,9 @@ export class ComplexAnimationBuilder extends BaseAnimationBuilder { } easing(easingFunction: EasingFunction): this { + if (__DEV__) { + assertEasingIsWorklet(easingFunction); + } this.easingV = easingFunction; return this; } diff --git a/src/reanimated2/layoutReanimation/animationBuilder/Keyframe.ts b/src/reanimated2/layoutReanimation/animationBuilder/Keyframe.ts index d6ae78a246ed..5dfe172fa59a 100644 --- a/src/reanimated2/layoutReanimation/animationBuilder/Keyframe.ts +++ b/src/reanimated2/layoutReanimation/animationBuilder/Keyframe.ts @@ -12,7 +12,10 @@ import type { import type { StyleProps } from '../../commonTypes'; import type { TransformArrayItem } from '../../helperTypes'; import { ReduceMotion } from '../../commonTypes'; -import { getReduceMotionFromConfig } from '../../animation/util'; +import { + assertEasingIsWorklet, + getReduceMotionFromConfig, +} from '../../animation/util'; interface KeyframePoint { duration: number; @@ -129,6 +132,11 @@ class InnerKeyframe implements IEntryExitAnimationBuilder { "[Reanimated] Keyframe can contain only that set of properties that were provide with initial values (keyframe 0 or 'from')" ); } + + if (__DEV__ && easing) { + assertEasingIsWorklet(easing); + } + parsedKeyframes[key].push({ duration: getAnimationDuration(key, currentKeyPoint), value, diff --git a/src/reanimated2/layoutReanimation/defaultTransitions/CurvedTransition.ts b/src/reanimated2/layoutReanimation/defaultTransitions/CurvedTransition.ts index c93aa5bebe6b..c2235363bfd1 100644 --- a/src/reanimated2/layoutReanimation/defaultTransitions/CurvedTransition.ts +++ b/src/reanimated2/layoutReanimation/defaultTransitions/CurvedTransition.ts @@ -7,6 +7,7 @@ import { BaseAnimationBuilder } from '../animationBuilder'; import type { EasingFunction } from '../../Easing'; import { Easing } from '../../Easing'; import { withTiming } from '../../animation'; +import { assertEasingIsWorklet } from '../../animation/util'; /** * Layout transitions with a curved animation. You can modify the behavior by chaining methods like `.duration(500)` or `.delay(500)`. @@ -36,6 +37,9 @@ export class CurvedTransition } easingX(easing: EasingFunction): CurvedTransition { + if (__DEV__) { + assertEasingIsWorklet(easing); + } this.easingXV = easing; return this; } @@ -46,6 +50,9 @@ export class CurvedTransition } easingY(easing: EasingFunction): CurvedTransition { + if (__DEV__) { + assertEasingIsWorklet(easing); + } this.easingYV = easing; return this; } @@ -56,6 +63,9 @@ export class CurvedTransition } easingWidth(easing: EasingFunction): CurvedTransition { + if (__DEV__) { + assertEasingIsWorklet(easing); + } this.easingWidthV = easing; return this; } @@ -66,6 +76,9 @@ export class CurvedTransition } easingHeight(easing: EasingFunction): CurvedTransition { + if (__DEV__) { + assertEasingIsWorklet(easing); + } this.easingHeightV = easing; return this; }