From 3a267b057e0246ebdcf60a6fb05fd2f7870a0dbd Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Thu, 9 May 2024 11:33:50 +0200 Subject: [PATCH 01/26] Move to separate directory --- .../index.tsx} | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) rename src/handlers/gestures/{GestureDetector.tsx => GestureDetector/index.tsx} (94%) diff --git a/src/handlers/gestures/GestureDetector.tsx b/src/handlers/gestures/GestureDetector/index.tsx similarity index 94% rename from src/handlers/gestures/GestureDetector.tsx rename to src/handlers/gestures/GestureDetector/index.tsx index 45d927c230..f841ff34ce 100644 --- a/src/handlers/gestures/GestureDetector.tsx +++ b/src/handlers/gestures/GestureDetector/index.tsx @@ -5,10 +5,10 @@ import { BaseGesture, GestureRef, CALLBACK_TYPE, -} from './gesture'; -import { Reanimated, SharedValue } from './reanimatedWrapper'; -import { registerHandler, unregisterHandler } from '../handlersRegistry'; -import RNGestureHandlerModule from '../../RNGestureHandlerModule'; +} from '../gesture'; +import { Reanimated, SharedValue } from '../reanimatedWrapper'; +import { registerHandler, unregisterHandler } from '../../handlersRegistry'; +import RNGestureHandlerModule from '../../../RNGestureHandlerModule'; import { baseGestureHandlerWithMonitorProps, filterConfig, @@ -20,35 +20,35 @@ import { scheduleFlushOperations, UserSelect, TouchAction, -} from '../gestureHandlerCommon'; +} from '../../gestureHandlerCommon'; import { GestureStateManager, GestureStateManagerType, -} from './gestureStateManager'; -import { flingGestureHandlerProps } from '../FlingGestureHandler'; -import { forceTouchGestureHandlerProps } from '../ForceTouchGestureHandler'; -import { longPressGestureHandlerProps } from '../LongPressGestureHandler'; +} from '../gestureStateManager'; +import { flingGestureHandlerProps } from '../../FlingGestureHandler'; +import { forceTouchGestureHandlerProps } from '../../ForceTouchGestureHandler'; +import { longPressGestureHandlerProps } from '../../LongPressGestureHandler'; import { panGestureHandlerProps, panGestureHandlerCustomNativeProps, -} from '../PanGestureHandler'; -import { tapGestureHandlerProps } from '../TapGestureHandler'; -import { hoverGestureHandlerProps } from './hoverGesture'; -import { State } from '../../State'; -import { TouchEventType } from '../../TouchEventType'; -import { ComposedGesture } from './gestureComposition'; -import { ActionType } from '../../ActionType'; -import { isFabric, isJestEnv, tagMessage } from '../../utils'; -import { getReactNativeVersion } from '../../getReactNativeVersion'; -import { getShadowNodeFromRef } from '../../getShadowNodeFromRef'; +} from '../../PanGestureHandler'; +import { tapGestureHandlerProps } from '../../TapGestureHandler'; +import { hoverGestureHandlerProps } from '../hoverGesture'; +import { State } from '../../../State'; +import { TouchEventType } from '../../../TouchEventType'; +import { ComposedGesture } from '../gestureComposition'; +import { ActionType } from '../../../ActionType'; +import { isFabric, isJestEnv, tagMessage } from '../../../utils'; +import { getReactNativeVersion } from '../../../getReactNativeVersion'; +import { getShadowNodeFromRef } from '../../../getShadowNodeFromRef'; import { Platform } from 'react-native'; -import type RNGestureHandlerModuleWeb from '../../RNGestureHandlerModule.web'; -import { onGestureHandlerEvent } from './eventReceiver'; -import { RNRenderer } from '../../RNRenderer'; -import { isNewWebImplementationEnabled } from '../../EnableNewWebImplementation'; -import { nativeViewGestureHandlerProps } from '../NativeViewGestureHandler'; -import GestureHandlerRootViewContext from '../../GestureHandlerRootViewContext'; -import { ghQueueMicrotask } from '../../ghQueueMicrotask'; +import type RNGestureHandlerModuleWeb from '../../../RNGestureHandlerModule.web'; +import { onGestureHandlerEvent } from '../eventReceiver'; +import { RNRenderer } from '../../../RNRenderer'; +import { isNewWebImplementationEnabled } from '../../../EnableNewWebImplementation'; +import { nativeViewGestureHandlerProps } from '../../NativeViewGestureHandler'; +import GestureHandlerRootViewContext from '../../../GestureHandlerRootViewContext'; +import { ghQueueMicrotask } from '../../../ghQueueMicrotask'; declare const global: { isFormsStackingContext: (node: unknown) => boolean | null; // JSI function From 04ce6cb5317b14131b632ee1857f19282974b1c8 Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Thu, 9 May 2024 14:41:40 +0200 Subject: [PATCH 02/26] Split into multiple files --- src/handlers/gestureHandlerCommon.ts | 2 +- .../gestures/GestureDetector/Wrap.tsx | 35 + .../GestureDetector/attachHandlers.ts | 135 ++++ .../gestures/GestureDetector/dropHandlers.ts | 14 + .../gestures/GestureDetector/index.tsx | 621 +----------------- .../GestureDetector/needsToReattach.ts | 22 + .../gestures/GestureDetector/types.ts | 20 + .../GestureDetector/updateHandlers.ts | 103 +++ .../GestureDetector/useAnimatedGesture.ts | 204 ++++++ .../gestures/GestureDetector/utils.ts | 124 ++++ 10 files changed, 668 insertions(+), 612 deletions(-) create mode 100644 src/handlers/gestures/GestureDetector/Wrap.tsx create mode 100644 src/handlers/gestures/GestureDetector/attachHandlers.ts create mode 100644 src/handlers/gestures/GestureDetector/dropHandlers.ts create mode 100644 src/handlers/gestures/GestureDetector/needsToReattach.ts create mode 100644 src/handlers/gestures/GestureDetector/types.ts create mode 100644 src/handlers/gestures/GestureDetector/updateHandlers.ts create mode 100644 src/handlers/gestures/GestureDetector/useAnimatedGesture.ts create mode 100644 src/handlers/gestures/GestureDetector/utils.ts diff --git a/src/handlers/gestureHandlerCommon.ts b/src/handlers/gestureHandlerCommon.ts index aaac5b8b98..57425615fd 100644 --- a/src/handlers/gestureHandlerCommon.ts +++ b/src/handlers/gestureHandlerCommon.ts @@ -45,7 +45,7 @@ export const baseGestureHandlerProps = [ 'onHandlerStateChange', ] as const; -export const baseGestureHandlerWithMonitorProps = [ +export const baseGestureHandlerWithDetectorProps = [ ...commonProps, 'needsPointerData', 'manualActivation', diff --git a/src/handlers/gestures/GestureDetector/Wrap.tsx b/src/handlers/gestures/GestureDetector/Wrap.tsx new file mode 100644 index 0000000000..bbd3878594 --- /dev/null +++ b/src/handlers/gestures/GestureDetector/Wrap.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import { Reanimated } from '../reanimatedWrapper'; +import { tagMessage } from '../../../utils'; + +export class Wrap extends React.Component<{ + onGestureHandlerEvent?: unknown; + // implicit `children` prop has been removed in @types/react^18.0.0 + children?: React.ReactNode; +}> { + render() { + try { + // I don't think that fighting with types over such a simple function is worth it + // The only thing it does is add 'collapsable: false' to the child component + // to make sure it is in the native view hierarchy so the detector can find + // correct viewTag to attach to. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const child: any = React.Children.only(this.props.children); + return React.cloneElement( + child, + { collapsable: false }, + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + child.props.children + ); + } catch (e) { + throw new Error( + tagMessage( + `GestureDetector got more than one view as a child. If you want the gesture to work on multiple views, wrap them with a common parent and attach the gesture to that view.` + ) + ); + } + } +} + +export const AnimatedWrap = + Reanimated?.default?.createAnimatedComponent(Wrap) ?? Wrap; diff --git a/src/handlers/gestures/GestureDetector/attachHandlers.ts b/src/handlers/gestures/GestureDetector/attachHandlers.ts new file mode 100644 index 0000000000..4fd92abc0e --- /dev/null +++ b/src/handlers/gestures/GestureDetector/attachHandlers.ts @@ -0,0 +1,135 @@ +import React from 'react'; +import { GestureType, HandlerCallbacks } from '../gesture'; +import { registerHandler } from '../../handlersRegistry'; +import RNGestureHandlerModule from '../../../RNGestureHandlerModule'; +import { + filterConfig, + scheduleFlushOperations, +} from '../../gestureHandlerCommon'; +import { ComposedGesture } from '../gestureComposition'; +import { ActionType } from '../../../ActionType'; +import { Platform } from 'react-native'; +import type RNGestureHandlerModuleWeb from '../../../RNGestureHandlerModule.web'; +import { ghQueueMicrotask } from '../../../ghQueueMicrotask'; +import { GestureConfigReference, WebEventHandler } from './types'; +import { + extractValidHandlerTags, + checkGestureCallbacksForWorklets, + ALLOWED_PROPS, +} from './utils'; + +interface AttachHandlersConfig { + preparedGesture: GestureConfigReference; + gestureConfig: ComposedGesture | GestureType; + gesture: GestureType[]; + viewTag: number; + webEventHandlersRef: React.RefObject; + mountedRef: React.RefObject; +} + +export function attachHandlers({ + preparedGesture, + gestureConfig, + gesture, + viewTag, + webEventHandlersRef, + mountedRef, +}: AttachHandlersConfig) { + if (!preparedGesture.firstExecution) { + gestureConfig.initialize(); + } else { + preparedGesture.firstExecution = false; + } + + // use queueMicrotask to extract handlerTags, because all refs should be initialized + // when it's ran + ghQueueMicrotask(() => { + if (!mountedRef.current) { + return; + } + gestureConfig.prepare(); + }); + + for (const handler of gesture) { + checkGestureCallbacksForWorklets(handler); + RNGestureHandlerModule.createGestureHandler( + handler.handlerName, + handler.handlerTag, + filterConfig(handler.config, ALLOWED_PROPS) + ); + + registerHandler(handler.handlerTag, handler, handler.config.testId); + } + + // use queueMicrotask to extract handlerTags, because all refs should be initialized + // when it's ran + ghQueueMicrotask(() => { + if (!mountedRef.current) { + return; + } + for (const handler of gesture) { + let requireToFail: number[] = []; + if (handler.config.requireToFail) { + requireToFail = extractValidHandlerTags(handler.config.requireToFail); + } + + let simultaneousWith: number[] = []; + if (handler.config.simultaneousWith) { + simultaneousWith = extractValidHandlerTags( + handler.config.simultaneousWith + ); + } + + let blocksHandlers: number[] = []; + if (handler.config.blocksHandlers) { + blocksHandlers = extractValidHandlerTags(handler.config.blocksHandlers); + } + + RNGestureHandlerModule.updateGestureHandler( + handler.handlerTag, + filterConfig(handler.config, ALLOWED_PROPS, { + simultaneousHandlers: simultaneousWith, + waitFor: requireToFail, + blocksHandlers: blocksHandlers, + }) + ); + } + + scheduleFlushOperations(); + }); + + preparedGesture.config = gesture; + + for (const gesture of preparedGesture.config) { + const actionType = gesture.shouldUseReanimated + ? ActionType.REANIMATED_WORKLET + : ActionType.JS_FUNCTION_NEW_API; + + if (Platform.OS === 'web') { + ( + RNGestureHandlerModule.attachGestureHandler as typeof RNGestureHandlerModuleWeb.attachGestureHandler + )( + gesture.handlerTag, + viewTag, + ActionType.JS_FUNCTION_OLD_API, // ignored on web + webEventHandlersRef + ); + } else { + RNGestureHandlerModule.attachGestureHandler( + gesture.handlerTag, + viewTag, + actionType + ); + } + } + + if (preparedGesture.animatedHandlers) { + const isAnimatedGesture = (g: GestureType) => g.shouldUseReanimated; + + preparedGesture.animatedHandlers.value = gesture + .filter(isAnimatedGesture) + .map((g) => g.handlers) as unknown as HandlerCallbacks< + Record + >[]; + } +} diff --git a/src/handlers/gestures/GestureDetector/dropHandlers.ts b/src/handlers/gestures/GestureDetector/dropHandlers.ts new file mode 100644 index 0000000000..d50204aaf6 --- /dev/null +++ b/src/handlers/gestures/GestureDetector/dropHandlers.ts @@ -0,0 +1,14 @@ +import { unregisterHandler } from '../../handlersRegistry'; +import RNGestureHandlerModule from '../../../RNGestureHandlerModule'; +import { scheduleFlushOperations } from '../../gestureHandlerCommon'; +import { GestureConfigReference } from './types'; + +export function dropHandlers(preparedGesture: GestureConfigReference) { + for (const handler of preparedGesture.config) { + RNGestureHandlerModule.dropGestureHandler(handler.handlerTag); + + unregisterHandler(handler.handlerTag, handler.config.testId); + } + + scheduleFlushOperations(); +} diff --git a/src/handlers/gestures/GestureDetector/index.tsx b/src/handlers/gestures/GestureDetector/index.tsx index f841ff34ce..76569c2496 100644 --- a/src/handlers/gestures/GestureDetector/index.tsx +++ b/src/handlers/gestures/GestureDetector/index.tsx @@ -1,602 +1,32 @@ import React, { useContext, useEffect, useRef, useState } from 'react'; +import { GestureType } from '../gesture'; import { - GestureType, - HandlerCallbacks, - BaseGesture, - GestureRef, - CALLBACK_TYPE, -} from '../gesture'; -import { Reanimated, SharedValue } from '../reanimatedWrapper'; -import { registerHandler, unregisterHandler } from '../../handlersRegistry'; -import RNGestureHandlerModule from '../../../RNGestureHandlerModule'; -import { - baseGestureHandlerWithMonitorProps, - filterConfig, findNodeHandle, - GestureTouchEvent, - GestureUpdateEvent, - GestureStateChangeEvent, HandlerStateChangeEvent, - scheduleFlushOperations, UserSelect, TouchAction, } from '../../gestureHandlerCommon'; -import { - GestureStateManager, - GestureStateManagerType, -} from '../gestureStateManager'; -import { flingGestureHandlerProps } from '../../FlingGestureHandler'; -import { forceTouchGestureHandlerProps } from '../../ForceTouchGestureHandler'; -import { longPressGestureHandlerProps } from '../../LongPressGestureHandler'; -import { - panGestureHandlerProps, - panGestureHandlerCustomNativeProps, -} from '../../PanGestureHandler'; -import { tapGestureHandlerProps } from '../../TapGestureHandler'; -import { hoverGestureHandlerProps } from '../hoverGesture'; -import { State } from '../../../State'; -import { TouchEventType } from '../../../TouchEventType'; import { ComposedGesture } from '../gestureComposition'; -import { ActionType } from '../../../ActionType'; import { isFabric, isJestEnv, tagMessage } from '../../../utils'; -import { getReactNativeVersion } from '../../../getReactNativeVersion'; import { getShadowNodeFromRef } from '../../../getShadowNodeFromRef'; import { Platform } from 'react-native'; -import type RNGestureHandlerModuleWeb from '../../../RNGestureHandlerModule.web'; import { onGestureHandlerEvent } from '../eventReceiver'; -import { RNRenderer } from '../../../RNRenderer'; + import { isNewWebImplementationEnabled } from '../../../EnableNewWebImplementation'; -import { nativeViewGestureHandlerProps } from '../../NativeViewGestureHandler'; import GestureHandlerRootViewContext from '../../../GestureHandlerRootViewContext'; -import { ghQueueMicrotask } from '../../../ghQueueMicrotask'; +import { GestureConfigReference, WebEventHandler } from './types'; +import { useAnimatedGesture } from './useAnimatedGesture'; +import { attachHandlers } from './attachHandlers'; +import { updateHandlers } from './updateHandlers'; +import { needsToReattach } from './needsToReattach'; +import { dropHandlers } from './dropHandlers'; +import { validateDetectorChildren } from './utils'; +import { Wrap, AnimatedWrap } from './Wrap'; declare const global: { isFormsStackingContext: (node: unknown) => boolean | null; // JSI function }; -const ALLOWED_PROPS = [ - ...baseGestureHandlerWithMonitorProps, - ...tapGestureHandlerProps, - ...panGestureHandlerProps, - ...panGestureHandlerCustomNativeProps, - ...longPressGestureHandlerProps, - ...forceTouchGestureHandlerProps, - ...flingGestureHandlerProps, - ...hoverGestureHandlerProps, - ...nativeViewGestureHandlerProps, -]; - -export type GestureConfigReference = { - config: GestureType[]; - animatedEventHandler: unknown; - animatedHandlers: SharedValue< - HandlerCallbacks>[] | null - > | null; - firstExecution: boolean; - useReanimatedHook: boolean; -}; - -function convertToHandlerTag(ref: GestureRef): number { - if (typeof ref === 'number') { - return ref; - } else if (ref instanceof BaseGesture) { - return ref.handlerTag; - } else { - // @ts-ignore in this case it should be a ref either to gesture object or - // a gesture handler component, in both cases handlerTag property exists - return ref.current?.handlerTag ?? -1; - } -} - -function extractValidHandlerTags(interactionGroup: GestureRef[] | undefined) { - return ( - interactionGroup?.map(convertToHandlerTag)?.filter((tag) => tag > 0) ?? [] - ); -} - -function dropHandlers(preparedGesture: GestureConfigReference) { - for (const handler of preparedGesture.config) { - RNGestureHandlerModule.dropGestureHandler(handler.handlerTag); - - unregisterHandler(handler.handlerTag, handler.config.testId); - } - - scheduleFlushOperations(); -} - -function checkGestureCallbacksForWorklets(gesture: GestureType) { - // if a gesture is explicitly marked to run on the JS thread there is no need to check - // if callbacks are worklets as the user is aware they will be ran on the JS thread - if (gesture.config.runOnJS) { - return; - } - - const areSomeNotWorklets = gesture.handlers.isWorklet.includes(false); - const areSomeWorklets = gesture.handlers.isWorklet.includes(true); - - // if some of the callbacks are worklets and some are not, and the gesture is not - // explicitly marked with `.runOnJS(true)` show an error - if (areSomeNotWorklets && areSomeWorklets) { - console.error( - tagMessage( - `Some of the callbacks in the gesture are worklets and some are not. Either make sure that all calbacks are marked as 'worklet' if you wish to run them on the UI thread or use '.runOnJS(true)' modifier on the gesture explicitly to run all callbacks on the JS thread.` - ) - ); - } -} - -interface WebEventHandler { - onGestureHandlerEvent: (event: HandlerStateChangeEvent) => void; - onGestureHandlerStateChange?: ( - event: HandlerStateChangeEvent - ) => void; -} - -interface AttachHandlersConfig { - preparedGesture: GestureConfigReference; - gestureConfig: ComposedGesture | GestureType; - gesture: GestureType[]; - viewTag: number; - webEventHandlersRef: React.RefObject; - mountedRef: React.RefObject; -} - -function attachHandlers({ - preparedGesture, - gestureConfig, - gesture, - viewTag, - webEventHandlersRef, - mountedRef, -}: AttachHandlersConfig) { - if (!preparedGesture.firstExecution) { - gestureConfig.initialize(); - } else { - preparedGesture.firstExecution = false; - } - - // use queueMicrotask to extract handlerTags, because all refs should be initialized - // when it's ran - ghQueueMicrotask(() => { - if (!mountedRef.current) { - return; - } - gestureConfig.prepare(); - }); - - for (const handler of gesture) { - checkGestureCallbacksForWorklets(handler); - RNGestureHandlerModule.createGestureHandler( - handler.handlerName, - handler.handlerTag, - filterConfig(handler.config, ALLOWED_PROPS) - ); - - registerHandler(handler.handlerTag, handler, handler.config.testId); - } - - // use queueMicrotask to extract handlerTags, because all refs should be initialized - // when it's ran - ghQueueMicrotask(() => { - if (!mountedRef.current) { - return; - } - for (const handler of gesture) { - let requireToFail: number[] = []; - if (handler.config.requireToFail) { - requireToFail = extractValidHandlerTags(handler.config.requireToFail); - } - - let simultaneousWith: number[] = []; - if (handler.config.simultaneousWith) { - simultaneousWith = extractValidHandlerTags( - handler.config.simultaneousWith - ); - } - - let blocksHandlers: number[] = []; - if (handler.config.blocksHandlers) { - blocksHandlers = extractValidHandlerTags(handler.config.blocksHandlers); - } - - RNGestureHandlerModule.updateGestureHandler( - handler.handlerTag, - filterConfig(handler.config, ALLOWED_PROPS, { - simultaneousHandlers: simultaneousWith, - waitFor: requireToFail, - blocksHandlers: blocksHandlers, - }) - ); - } - - scheduleFlushOperations(); - }); - - preparedGesture.config = gesture; - - for (const gesture of preparedGesture.config) { - const actionType = gesture.shouldUseReanimated - ? ActionType.REANIMATED_WORKLET - : ActionType.JS_FUNCTION_NEW_API; - - if (Platform.OS === 'web') { - ( - RNGestureHandlerModule.attachGestureHandler as typeof RNGestureHandlerModuleWeb.attachGestureHandler - )( - gesture.handlerTag, - viewTag, - ActionType.JS_FUNCTION_OLD_API, // ignored on web - webEventHandlersRef - ); - } else { - RNGestureHandlerModule.attachGestureHandler( - gesture.handlerTag, - viewTag, - actionType - ); - } - } - - if (preparedGesture.animatedHandlers) { - const isAnimatedGesture = (g: GestureType) => g.shouldUseReanimated; - - preparedGesture.animatedHandlers.value = gesture - .filter(isAnimatedGesture) - .map((g) => g.handlers) as unknown as HandlerCallbacks< - Record - >[]; - } -} - -function updateHandlers( - preparedGesture: GestureConfigReference, - gestureConfig: ComposedGesture | GestureType, - gesture: GestureType[], - mountedRef: React.RefObject -) { - gestureConfig.prepare(); - - for (let i = 0; i < gesture.length; i++) { - const handler = preparedGesture.config[i]; - checkGestureCallbacksForWorklets(handler); - - // only update handlerTag when it's actually different, it may be the same - // if gesture config object is wrapped with useMemo - if (gesture[i].handlerTag !== handler.handlerTag) { - gesture[i].handlerTag = handler.handlerTag; - gesture[i].handlers.handlerTag = handler.handlerTag; - } - } - - // use queueMicrotask to extract handlerTags, because when it's ran, all refs should be updated - // and handlerTags in BaseGesture references should be updated in the loop above (we need to wait - // in case of external relations) - ghQueueMicrotask(() => { - if (!mountedRef.current) { - return; - } - for (let i = 0; i < gesture.length; i++) { - const handler = preparedGesture.config[i]; - - handler.config = gesture[i].config; - handler.handlers = gesture[i].handlers; - - const requireToFail = extractValidHandlerTags( - handler.config.requireToFail - ); - - const simultaneousWith = extractValidHandlerTags( - handler.config.simultaneousWith - ); - - RNGestureHandlerModule.updateGestureHandler( - handler.handlerTag, - filterConfig(handler.config, ALLOWED_PROPS, { - simultaneousHandlers: simultaneousWith, - waitFor: requireToFail, - }) - ); - - registerHandler(handler.handlerTag, handler, handler.config.testId); - } - - if (preparedGesture.animatedHandlers) { - const previousHandlersValue = - preparedGesture.animatedHandlers.value ?? []; - const newHandlersValue = preparedGesture.config - .filter((g) => g.shouldUseReanimated) // ignore gestures that shouldn't run on UI - .map((g) => g.handlers) as unknown as HandlerCallbacks< - Record - >[]; - - // if amount of gesture configs changes, we need to update the callbacks in shared value - let shouldUpdateSharedValue = - previousHandlersValue.length !== newHandlersValue.length; - - if (!shouldUpdateSharedValue) { - // if the amount is the same, we need to check if any of the configs inside has changed - for (let i = 0; i < newHandlersValue.length; i++) { - if ( - // we can use the `gestureId` prop as it's unique for every config instance - newHandlersValue[i].gestureId !== previousHandlersValue[i].gestureId - ) { - shouldUpdateSharedValue = true; - break; - } - } - } - - if (shouldUpdateSharedValue) { - preparedGesture.animatedHandlers.value = newHandlersValue; - } - } - - scheduleFlushOperations(); - }); -} - -function needsToReattach( - preparedGesture: GestureConfigReference, - gesture: GestureType[] -) { - if (gesture.length !== preparedGesture.config.length) { - return true; - } - for (let i = 0; i < gesture.length; i++) { - if ( - gesture[i].handlerName !== preparedGesture.config[i].handlerName || - gesture[i].shouldUseReanimated !== - preparedGesture.config[i].shouldUseReanimated - ) { - return true; - } - } - - return false; -} - -function isStateChangeEvent( - event: GestureUpdateEvent | GestureStateChangeEvent | GestureTouchEvent -): event is GestureStateChangeEvent { - 'worklet'; - // @ts-ignore Yes, the oldState prop is missing on GestureTouchEvent, that's the point - return event.oldState != null; -} - -function isTouchEvent( - event: GestureUpdateEvent | GestureStateChangeEvent | GestureTouchEvent -): event is GestureTouchEvent { - 'worklet'; - return event.eventType != null; -} - -function getHandler( - type: CALLBACK_TYPE, - gesture: HandlerCallbacks> -) { - 'worklet'; - switch (type) { - case CALLBACK_TYPE.BEGAN: - return gesture.onBegin; - case CALLBACK_TYPE.START: - return gesture.onStart; - case CALLBACK_TYPE.UPDATE: - return gesture.onUpdate; - case CALLBACK_TYPE.CHANGE: - return gesture.onChange; - case CALLBACK_TYPE.END: - return gesture.onEnd; - case CALLBACK_TYPE.FINALIZE: - return gesture.onFinalize; - case CALLBACK_TYPE.TOUCHES_DOWN: - return gesture.onTouchesDown; - case CALLBACK_TYPE.TOUCHES_MOVE: - return gesture.onTouchesMove; - case CALLBACK_TYPE.TOUCHES_UP: - return gesture.onTouchesUp; - case CALLBACK_TYPE.TOUCHES_CANCELLED: - return gesture.onTouchesCancelled; - } -} - -function touchEventTypeToCallbackType( - eventType: TouchEventType -): CALLBACK_TYPE { - 'worklet'; - switch (eventType) { - case TouchEventType.TOUCHES_DOWN: - return CALLBACK_TYPE.TOUCHES_DOWN; - case TouchEventType.TOUCHES_MOVE: - return CALLBACK_TYPE.TOUCHES_MOVE; - case TouchEventType.TOUCHES_UP: - return CALLBACK_TYPE.TOUCHES_UP; - case TouchEventType.TOUCHES_CANCELLED: - return CALLBACK_TYPE.TOUCHES_CANCELLED; - } - return CALLBACK_TYPE.UNDEFINED; -} - -function runWorklet( - type: CALLBACK_TYPE, - gesture: HandlerCallbacks>, - event: GestureStateChangeEvent | GestureUpdateEvent | GestureTouchEvent, - ...args: any[] -) { - 'worklet'; - const handler = getHandler(type, gesture); - if (gesture.isWorklet[type]) { - // @ts-ignore Logic below makes sure the correct event is send to the - // correct handler. - handler?.(event, ...args); - } else if (handler) { - console.warn(tagMessage('Animated gesture callback must be a worklet')); - } -} - -function useAnimatedGesture( - preparedGesture: GestureConfigReference, - needsRebuild: boolean -) { - if (!Reanimated) { - return; - } - - // Hooks are called conditionally, but the condition is whether the - // react-native-reanimated is installed, which shouldn't change while running - // eslint-disable-next-line react-hooks/rules-of-hooks - const sharedHandlersCallbacks = Reanimated.useSharedValue< - HandlerCallbacks>[] | null - >(null); - - // eslint-disable-next-line react-hooks/rules-of-hooks - const lastUpdateEvent = Reanimated.useSharedValue< - (GestureUpdateEvent | undefined)[] - >([]); - - // not every gesture needs a state controller, init them lazily - const stateControllers: GestureStateManagerType[] = []; - - const callback = ( - event: GestureStateChangeEvent | GestureUpdateEvent | GestureTouchEvent - ) => { - 'worklet'; - - const currentCallback = sharedHandlersCallbacks.value; - if (!currentCallback) { - return; - } - - for (let i = 0; i < currentCallback.length; i++) { - const gesture = currentCallback[i]; - - if (event.handlerTag === gesture.handlerTag) { - if (isStateChangeEvent(event)) { - if ( - event.oldState === State.UNDETERMINED && - event.state === State.BEGAN - ) { - runWorklet(CALLBACK_TYPE.BEGAN, gesture, event); - } else if ( - (event.oldState === State.BEGAN || - event.oldState === State.UNDETERMINED) && - event.state === State.ACTIVE - ) { - runWorklet(CALLBACK_TYPE.START, gesture, event); - lastUpdateEvent.value[gesture.handlerTag] = undefined; - } else if ( - event.oldState !== event.state && - event.state === State.END - ) { - if (event.oldState === State.ACTIVE) { - runWorklet(CALLBACK_TYPE.END, gesture, event, true); - } - runWorklet(CALLBACK_TYPE.FINALIZE, gesture, event, true); - } else if ( - (event.state === State.FAILED || event.state === State.CANCELLED) && - event.state !== event.oldState - ) { - if (event.oldState === State.ACTIVE) { - runWorklet(CALLBACK_TYPE.END, gesture, event, false); - } - runWorklet(CALLBACK_TYPE.FINALIZE, gesture, event, false); - } - } else if (isTouchEvent(event)) { - if (!stateControllers[i]) { - stateControllers[i] = GestureStateManager.create(event.handlerTag); - } - - if (event.eventType !== TouchEventType.UNDETERMINED) { - runWorklet( - touchEventTypeToCallbackType(event.eventType), - gesture, - event, - stateControllers[i] - ); - } - } else { - runWorklet(CALLBACK_TYPE.UPDATE, gesture, event); - - if (gesture.onChange && gesture.changeEventCalculator) { - runWorklet( - CALLBACK_TYPE.CHANGE, - gesture, - gesture.changeEventCalculator?.( - event, - lastUpdateEvent.value[gesture.handlerTag] - ) - ); - - lastUpdateEvent.value[gesture.handlerTag] = event; - } - } - } - } - }; - - // eslint-disable-next-line react-hooks/rules-of-hooks - const event = Reanimated.useEvent( - callback, - ['onGestureHandlerStateChange', 'onGestureHandlerEvent'], - needsRebuild - ); - - preparedGesture.animatedEventHandler = event; - preparedGesture.animatedHandlers = sharedHandlersCallbacks; -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function validateDetectorChildren(ref: any) { - // finds the first native view under the Wrap component and traverses the fiber tree upwards - // to check whether there is more than one native view as a pseudo-direct child of GestureDetector - // i.e. this is not ok: - // Wrap - // | - // / \ - // / \ - // / \ - // / \ - // NativeView NativeView - // - // but this is fine: - // Wrap - // | - // NativeView - // | - // / \ - // / \ - // / \ - // / \ - // NativeView NativeView - if (__DEV__ && Platform.OS !== 'web') { - const REACT_NATIVE_VERSION = getReactNativeVersion(); - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const wrapType = - REACT_NATIVE_VERSION.minor > 63 || REACT_NATIVE_VERSION.major > 0 - ? // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - ref._reactInternals.elementType - : // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - ref._reactInternalFiber.elementType; - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - let instance = - RNRenderer.findHostInstance_DEPRECATED( - ref - )._internalFiberInstanceHandleDEV; - - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - while (instance && instance.elementType !== wrapType) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - if (instance.sibling) { - throw new Error( - 'GestureDetector has more than one native view as its children. This can happen if you are using a custom component that renders multiple views, like React.Fragment. You should wrap content of GestureDetector with a or .' - ); - } - - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access - instance = instance.return; - } - } -} - const applyUserSelectProp = ( userSelect: UserSelect, gesture: ComposedGesture | GestureType @@ -856,34 +286,3 @@ export const GestureDetector = (props: GestureDetectorProps) => { return {props.children}; } }; - -class Wrap extends React.Component<{ - onGestureHandlerEvent?: unknown; - // implicit `children` prop has been removed in @types/react^18.0.0 - children?: React.ReactNode; -}> { - render() { - try { - // I don't think that fighting with types over such a simple function is worth it - // The only thing it does is add 'collapsable: false' to the child component - // to make sure it is in the native view hierarchy so the detector can find - // correct viewTag to attach to. - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const child: any = React.Children.only(this.props.children); - return React.cloneElement( - child, - { collapsable: false }, - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - child.props.children - ); - } catch (e) { - throw new Error( - tagMessage( - `GestureDetector got more than one view as a child. If you want the gesture to work on multiple views, wrap them with a common parent and attach the gesture to that view.` - ) - ); - } - } -} - -const AnimatedWrap = Reanimated?.default?.createAnimatedComponent(Wrap) ?? Wrap; diff --git a/src/handlers/gestures/GestureDetector/needsToReattach.ts b/src/handlers/gestures/GestureDetector/needsToReattach.ts new file mode 100644 index 0000000000..76ab3179ad --- /dev/null +++ b/src/handlers/gestures/GestureDetector/needsToReattach.ts @@ -0,0 +1,22 @@ +import { GestureType } from '../gesture'; +import { GestureConfigReference } from './types'; + +export function needsToReattach( + preparedGesture: GestureConfigReference, + gesture: GestureType[] +) { + if (gesture.length !== preparedGesture.config.length) { + return true; + } + for (let i = 0; i < gesture.length; i++) { + if ( + gesture[i].handlerName !== preparedGesture.config[i].handlerName || + gesture[i].shouldUseReanimated !== + preparedGesture.config[i].shouldUseReanimated + ) { + return true; + } + } + + return false; +} diff --git a/src/handlers/gestures/GestureDetector/types.ts b/src/handlers/gestures/GestureDetector/types.ts new file mode 100644 index 0000000000..43ff219c0b --- /dev/null +++ b/src/handlers/gestures/GestureDetector/types.ts @@ -0,0 +1,20 @@ +import { GestureType, HandlerCallbacks } from '../gesture'; +import { SharedValue } from '../reanimatedWrapper'; +import { HandlerStateChangeEvent } from '../../gestureHandlerCommon'; + +export type GestureConfigReference = { + config: GestureType[]; + animatedEventHandler: unknown; + animatedHandlers: SharedValue< + HandlerCallbacks>[] | null + > | null; + firstExecution: boolean; + useReanimatedHook: boolean; +}; + +export interface WebEventHandler { + onGestureHandlerEvent: (event: HandlerStateChangeEvent) => void; + onGestureHandlerStateChange?: ( + event: HandlerStateChangeEvent + ) => void; +} diff --git a/src/handlers/gestures/GestureDetector/updateHandlers.ts b/src/handlers/gestures/GestureDetector/updateHandlers.ts new file mode 100644 index 0000000000..02e01e0e7e --- /dev/null +++ b/src/handlers/gestures/GestureDetector/updateHandlers.ts @@ -0,0 +1,103 @@ +import React from 'react'; +import { GestureType, HandlerCallbacks } from '../gesture'; +import { registerHandler } from '../../handlersRegistry'; +import RNGestureHandlerModule from '../../../RNGestureHandlerModule'; +import { + filterConfig, + scheduleFlushOperations, +} from '../../gestureHandlerCommon'; +import { ComposedGesture } from '../gestureComposition'; +import { ghQueueMicrotask } from '../../../ghQueueMicrotask'; +import { GestureConfigReference } from './types'; +import { + extractValidHandlerTags, + checkGestureCallbacksForWorklets, + ALLOWED_PROPS, +} from './utils'; + +export function updateHandlers( + preparedGesture: GestureConfigReference, + gestureConfig: ComposedGesture | GestureType, + gesture: GestureType[], + mountedRef: React.RefObject +) { + gestureConfig.prepare(); + + for (let i = 0; i < gesture.length; i++) { + const handler = preparedGesture.config[i]; + checkGestureCallbacksForWorklets(handler); + + // only update handlerTag when it's actually different, it may be the same + // if gesture config object is wrapped with useMemo + if (gesture[i].handlerTag !== handler.handlerTag) { + gesture[i].handlerTag = handler.handlerTag; + gesture[i].handlers.handlerTag = handler.handlerTag; + } + } + + // use queueMicrotask to extract handlerTags, because when it's ran, all refs should be updated + // and handlerTags in BaseGesture references should be updated in the loop above (we need to wait + // in case of external relations) + ghQueueMicrotask(() => { + if (!mountedRef.current) { + return; + } + for (let i = 0; i < gesture.length; i++) { + const handler = preparedGesture.config[i]; + + handler.config = gesture[i].config; + handler.handlers = gesture[i].handlers; + + const requireToFail = extractValidHandlerTags( + handler.config.requireToFail + ); + + const simultaneousWith = extractValidHandlerTags( + handler.config.simultaneousWith + ); + + RNGestureHandlerModule.updateGestureHandler( + handler.handlerTag, + filterConfig(handler.config, ALLOWED_PROPS, { + simultaneousHandlers: simultaneousWith, + waitFor: requireToFail, + }) + ); + + registerHandler(handler.handlerTag, handler, handler.config.testId); + } + + if (preparedGesture.animatedHandlers) { + const previousHandlersValue = + preparedGesture.animatedHandlers.value ?? []; + const newHandlersValue = preparedGesture.config + .filter((g) => g.shouldUseReanimated) // ignore gestures that shouldn't run on UI + .map((g) => g.handlers) as unknown as HandlerCallbacks< + Record + >[]; + + // if amount of gesture configs changes, we need to update the callbacks in shared value + let shouldUpdateSharedValue = + previousHandlersValue.length !== newHandlersValue.length; + + if (!shouldUpdateSharedValue) { + // if the amount is the same, we need to check if any of the configs inside has changed + for (let i = 0; i < newHandlersValue.length; i++) { + if ( + // we can use the `gestureId` prop as it's unique for every config instance + newHandlersValue[i].gestureId !== previousHandlersValue[i].gestureId + ) { + shouldUpdateSharedValue = true; + break; + } + } + } + + if (shouldUpdateSharedValue) { + preparedGesture.animatedHandlers.value = newHandlersValue; + } + } + + scheduleFlushOperations(); + }); +} diff --git a/src/handlers/gestures/GestureDetector/useAnimatedGesture.ts b/src/handlers/gestures/GestureDetector/useAnimatedGesture.ts new file mode 100644 index 0000000000..4b9aadf962 --- /dev/null +++ b/src/handlers/gestures/GestureDetector/useAnimatedGesture.ts @@ -0,0 +1,204 @@ +import { HandlerCallbacks, CALLBACK_TYPE } from '../gesture'; +import { Reanimated } from '../reanimatedWrapper'; +import { + GestureTouchEvent, + GestureUpdateEvent, + GestureStateChangeEvent, +} from '../../gestureHandlerCommon'; +import { + GestureStateManager, + GestureStateManagerType, +} from '../gestureStateManager'; +import { State } from '../../../State'; +import { TouchEventType } from '../../../TouchEventType'; +import { tagMessage } from '../../../utils'; +import { GestureConfigReference } from './types'; + +function getHandler( + type: CALLBACK_TYPE, + gesture: HandlerCallbacks> +) { + 'worklet'; + switch (type) { + case CALLBACK_TYPE.BEGAN: + return gesture.onBegin; + case CALLBACK_TYPE.START: + return gesture.onStart; + case CALLBACK_TYPE.UPDATE: + return gesture.onUpdate; + case CALLBACK_TYPE.CHANGE: + return gesture.onChange; + case CALLBACK_TYPE.END: + return gesture.onEnd; + case CALLBACK_TYPE.FINALIZE: + return gesture.onFinalize; + case CALLBACK_TYPE.TOUCHES_DOWN: + return gesture.onTouchesDown; + case CALLBACK_TYPE.TOUCHES_MOVE: + return gesture.onTouchesMove; + case CALLBACK_TYPE.TOUCHES_UP: + return gesture.onTouchesUp; + case CALLBACK_TYPE.TOUCHES_CANCELLED: + return gesture.onTouchesCancelled; + } +} + +function touchEventTypeToCallbackType( + eventType: TouchEventType +): CALLBACK_TYPE { + 'worklet'; + switch (eventType) { + case TouchEventType.TOUCHES_DOWN: + return CALLBACK_TYPE.TOUCHES_DOWN; + case TouchEventType.TOUCHES_MOVE: + return CALLBACK_TYPE.TOUCHES_MOVE; + case TouchEventType.TOUCHES_UP: + return CALLBACK_TYPE.TOUCHES_UP; + case TouchEventType.TOUCHES_CANCELLED: + return CALLBACK_TYPE.TOUCHES_CANCELLED; + } + return CALLBACK_TYPE.UNDEFINED; +} + +function runWorklet( + type: CALLBACK_TYPE, + gesture: HandlerCallbacks>, + event: GestureStateChangeEvent | GestureUpdateEvent | GestureTouchEvent, + ...args: unknown[] +) { + 'worklet'; + const handler = getHandler(type, gesture); + if (gesture.isWorklet[type]) { + // @ts-ignore Logic below makes sure the correct event is send to the + // correct handler. + handler?.(event, ...args); + } else if (handler) { + console.warn(tagMessage('Animated gesture callback must be a worklet')); + } +} + +function isStateChangeEvent( + event: GestureUpdateEvent | GestureStateChangeEvent | GestureTouchEvent +): event is GestureStateChangeEvent { + 'worklet'; + // @ts-ignore Yes, the oldState prop is missing on GestureTouchEvent, that's the point + return event.oldState != null; +} + +function isTouchEvent( + event: GestureUpdateEvent | GestureStateChangeEvent | GestureTouchEvent +): event is GestureTouchEvent { + 'worklet'; + return event.eventType != null; +} + +export function useAnimatedGesture( + preparedGesture: GestureConfigReference, + needsRebuild: boolean +) { + if (!Reanimated) { + return; + } + + // Hooks are called conditionally, but the condition is whether the + // react-native-reanimated is installed, which shouldn't change while running + // eslint-disable-next-line react-hooks/rules-of-hooks + const sharedHandlersCallbacks = Reanimated.useSharedValue< + HandlerCallbacks>[] | null + >(null); + + // eslint-disable-next-line react-hooks/rules-of-hooks + const lastUpdateEvent = Reanimated.useSharedValue< + (GestureUpdateEvent | undefined)[] + >([]); + + // not every gesture needs a state controller, init them lazily + const stateControllers: GestureStateManagerType[] = []; + + const callback = ( + event: GestureStateChangeEvent | GestureUpdateEvent | GestureTouchEvent + ) => { + 'worklet'; + + const currentCallback = sharedHandlersCallbacks.value; + if (!currentCallback) { + return; + } + + for (let i = 0; i < currentCallback.length; i++) { + const gesture = currentCallback[i]; + + if (event.handlerTag === gesture.handlerTag) { + if (isStateChangeEvent(event)) { + if ( + event.oldState === State.UNDETERMINED && + event.state === State.BEGAN + ) { + runWorklet(CALLBACK_TYPE.BEGAN, gesture, event); + } else if ( + (event.oldState === State.BEGAN || + event.oldState === State.UNDETERMINED) && + event.state === State.ACTIVE + ) { + runWorklet(CALLBACK_TYPE.START, gesture, event); + lastUpdateEvent.value[gesture.handlerTag] = undefined; + } else if ( + event.oldState !== event.state && + event.state === State.END + ) { + if (event.oldState === State.ACTIVE) { + runWorklet(CALLBACK_TYPE.END, gesture, event, true); + } + runWorklet(CALLBACK_TYPE.FINALIZE, gesture, event, true); + } else if ( + (event.state === State.FAILED || event.state === State.CANCELLED) && + event.state !== event.oldState + ) { + if (event.oldState === State.ACTIVE) { + runWorklet(CALLBACK_TYPE.END, gesture, event, false); + } + runWorklet(CALLBACK_TYPE.FINALIZE, gesture, event, false); + } + } else if (isTouchEvent(event)) { + if (!stateControllers[i]) { + stateControllers[i] = GestureStateManager.create(event.handlerTag); + } + + if (event.eventType !== TouchEventType.UNDETERMINED) { + runWorklet( + touchEventTypeToCallbackType(event.eventType), + gesture, + event, + stateControllers[i] + ); + } + } else { + runWorklet(CALLBACK_TYPE.UPDATE, gesture, event); + + if (gesture.onChange && gesture.changeEventCalculator) { + runWorklet( + CALLBACK_TYPE.CHANGE, + gesture, + gesture.changeEventCalculator?.( + event, + lastUpdateEvent.value[gesture.handlerTag] + ) + ); + + lastUpdateEvent.value[gesture.handlerTag] = event; + } + } + } + } + }; + + // eslint-disable-next-line react-hooks/rules-of-hooks + const event = Reanimated.useEvent( + callback, + ['onGestureHandlerStateChange', 'onGestureHandlerEvent'], + needsRebuild + ); + + preparedGesture.animatedEventHandler = event; + preparedGesture.animatedHandlers = sharedHandlersCallbacks; +} diff --git a/src/handlers/gestures/GestureDetector/utils.ts b/src/handlers/gestures/GestureDetector/utils.ts new file mode 100644 index 0000000000..c9d79244c0 --- /dev/null +++ b/src/handlers/gestures/GestureDetector/utils.ts @@ -0,0 +1,124 @@ +import { Platform } from 'react-native'; + +import { tagMessage } from '../../../utils'; +import { GestureRef, BaseGesture, GestureType } from '../gesture'; + +import { flingGestureHandlerProps } from '../../FlingGestureHandler'; +import { forceTouchGestureHandlerProps } from '../../ForceTouchGestureHandler'; +import { longPressGestureHandlerProps } from '../../LongPressGestureHandler'; +import { + panGestureHandlerProps, + panGestureHandlerCustomNativeProps, +} from '../../PanGestureHandler'; +import { tapGestureHandlerProps } from '../../TapGestureHandler'; +import { hoverGestureHandlerProps } from '../hoverGesture'; +import { nativeViewGestureHandlerProps } from '../../NativeViewGestureHandler'; +import { baseGestureHandlerWithDetectorProps } from '../../gestureHandlerCommon'; +import { getReactNativeVersion } from '../../../getReactNativeVersion'; +import { RNRenderer } from '../../../RNRenderer'; + +export const ALLOWED_PROPS = [ + ...baseGestureHandlerWithDetectorProps, + ...tapGestureHandlerProps, + ...panGestureHandlerProps, + ...panGestureHandlerCustomNativeProps, + ...longPressGestureHandlerProps, + ...forceTouchGestureHandlerProps, + ...flingGestureHandlerProps, + ...hoverGestureHandlerProps, + ...nativeViewGestureHandlerProps, +]; + +function convertToHandlerTag(ref: GestureRef): number { + if (typeof ref === 'number') { + return ref; + } else if (ref instanceof BaseGesture) { + return ref.handlerTag; + } else { + // @ts-ignore in this case it should be a ref either to gesture object or + // a gesture handler component, in both cases handlerTag property exists + return ref.current?.handlerTag ?? -1; + } +} + +export function extractValidHandlerTags( + interactionGroup: GestureRef[] | undefined +) { + return ( + interactionGroup?.map(convertToHandlerTag)?.filter((tag) => tag > 0) ?? [] + ); +} + +export function checkGestureCallbacksForWorklets(gesture: GestureType) { + // if a gesture is explicitly marked to run on the JS thread there is no need to check + // if callbacks are worklets as the user is aware they will be ran on the JS thread + if (gesture.config.runOnJS) { + return; + } + + const areSomeNotWorklets = gesture.handlers.isWorklet.includes(false); + const areSomeWorklets = gesture.handlers.isWorklet.includes(true); + + // if some of the callbacks are worklets and some are not, and the gesture is not + // explicitly marked with `.runOnJS(true)` show an error + if (areSomeNotWorklets && areSomeWorklets) { + console.error( + tagMessage( + `Some of the callbacks in the gesture are worklets and some are not. Either make sure that all calbacks are marked as 'worklet' if you wish to run them on the UI thread or use '.runOnJS(true)' modifier on the gesture explicitly to run all callbacks on the JS thread.` + ) + ); + } +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function validateDetectorChildren(ref: any) { + // finds the first native view under the Wrap component and traverses the fiber tree upwards + // to check whether there is more than one native view as a pseudo-direct child of GestureDetector + // i.e. this is not ok: + // Wrap + // | + // / \ + // / \ + // / \ + // / \ + // NativeView NativeView + // + // but this is fine: + // Wrap + // | + // NativeView + // | + // / \ + // / \ + // / \ + // / \ + // NativeView NativeView + if (__DEV__ && Platform.OS !== 'web') { + const REACT_NATIVE_VERSION = getReactNativeVersion(); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const wrapType = + REACT_NATIVE_VERSION.minor > 63 || REACT_NATIVE_VERSION.major > 0 + ? // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + ref._reactInternals.elementType + : // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + ref._reactInternalFiber.elementType; + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + let instance = + RNRenderer.findHostInstance_DEPRECATED( + ref + )._internalFiberInstanceHandleDEV; + + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + while (instance && instance.elementType !== wrapType) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + if (instance.sibling) { + throw new Error( + 'GestureDetector has more than one native view as its children. This can happen if you are using a custom component that renders multiple views, like React.Fragment. You should wrap content of GestureDetector with a or .' + ); + } + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access + instance = instance.return; + } + } +} From 506629749d3cf19b5b829009aa1d737861e9aaac Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Thu, 9 May 2024 15:37:47 +0200 Subject: [PATCH 03/26] Propagate relevant detector props to gestures helper --- .../gestures/GestureDetector/index.tsx | 60 +++++++------------ 1 file changed, 23 insertions(+), 37 deletions(-) diff --git a/src/handlers/gestures/GestureDetector/index.tsx b/src/handlers/gestures/GestureDetector/index.tsx index 76569c2496..3941f16fca 100644 --- a/src/handlers/gestures/GestureDetector/index.tsx +++ b/src/handlers/gestures/GestureDetector/index.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react/no-unused-prop-types */ import React, { useContext, useEffect, useRef, useState } from 'react'; import { GestureType } from '../gesture'; import { @@ -27,34 +28,31 @@ declare const global: { isFormsStackingContext: (node: unknown) => boolean | null; // JSI function }; -const applyUserSelectProp = ( - userSelect: UserSelect, +function propagateDetectorConfig( + props: GestureDetectorProps, gesture: ComposedGesture | GestureType -): void => { - for (const g of gesture.toGestureArray()) { - g.config.userSelect = userSelect; - } -}; - -const applyEnableContextMenuProp = ( - enableContextMenu: boolean, - gesture: ComposedGesture | GestureType -): void => { - for (const g of gesture.toGestureArray()) { - g.config.enableContextMenu = enableContextMenu; - } -}; +) { + const keysToPropagate: (keyof GestureDetectorProps)[] = [ + 'userSelect', + 'enableContextMenu', + 'touchAction', + ]; + + for (const key of keysToPropagate) { + const value = props[key]; + if (value === undefined) { + continue; + } -const applyTouchActionProp = ( - touchAction: TouchAction, - gesture: ComposedGesture | GestureType -): void => { - for (const g of gesture.toGestureArray()) { - g.config.touchAction = touchAction; + for (const g of gesture.toGestureArray()) { + const config = g.config as { [key: string]: unknown }; + config[key] = value; + } } -}; +} interface GestureDetectorProps { + children?: React.ReactNode; /** * A gesture object containing the configuration and callbacks. * Can be any of: @@ -62,8 +60,6 @@ interface GestureDetectorProps { * - `ComposedGesture` (`Race`, `Simultaneous`, `Exclusive`) */ gesture: ComposedGesture | GestureType; - children?: React.ReactNode; - /** * #### Web only * This parameter allows to specify which `userSelect` property should be applied to underlying view. @@ -83,6 +79,7 @@ interface GestureDetectorProps { */ touchAction?: TouchAction; } + interface GestureDetectorState { firstRender: boolean; viewRef: React.Component | null; @@ -114,18 +111,7 @@ export const GestureDetector = (props: GestureDetectorProps) => { } const gestureConfig = props.gesture; - - if (props.userSelect) { - applyUserSelectProp(props.userSelect, gestureConfig); - } - - if (props.enableContextMenu !== undefined) { - applyEnableContextMenuProp(props.enableContextMenu, gestureConfig); - } - - if (props.touchAction !== undefined) { - applyTouchActionProp(props.touchAction, gestureConfig); - } + propagateDetectorConfig(props, gestureConfig); const gesture = gestureConfig.toGestureArray(); const useReanimatedHook = gesture.some((g) => g.shouldUseReanimated); From e2aca75f48d3b5bc0188f54f4bea97212aed6e68 Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Thu, 9 May 2024 15:41:53 +0200 Subject: [PATCH 04/26] Use correct tense in error message --- src/handlers/gestures/GestureDetector/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handlers/gestures/GestureDetector/index.tsx b/src/handlers/gestures/GestureDetector/index.tsx index 3941f16fca..fdd2203cfb 100644 --- a/src/handlers/gestures/GestureDetector/index.tsx +++ b/src/handlers/gestures/GestureDetector/index.tsx @@ -151,7 +151,7 @@ export const GestureDetector = (props: GestureDetectorProps) => { if (useReanimatedHook !== preparedGesture.useReanimatedHook) { throw new Error( tagMessage( - 'You cannot change the thread the callbacks are ran on while the app is running' + 'You cannot change the thread the callbacks are run on while the app is running' ) ); } From 23d8caa5eac4045032794c7c0f7aa3744caa5c9d Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Thu, 9 May 2024 15:48:22 +0200 Subject: [PATCH 05/26] Small renames --- .../gestures/GestureDetector/index.tsx | 41 ++++++++++--------- .../gestures/GestureDetector/utils.ts | 10 +++++ 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/src/handlers/gestures/GestureDetector/index.tsx b/src/handlers/gestures/GestureDetector/index.tsx index fdd2203cfb..f4265b7c3c 100644 --- a/src/handlers/gestures/GestureDetector/index.tsx +++ b/src/handlers/gestures/GestureDetector/index.tsx @@ -1,5 +1,5 @@ /* eslint-disable react/no-unused-prop-types */ -import React, { useContext, useEffect, useRef, useState } from 'react'; +import React, { useContext, useEffect, useRef } from 'react'; import { GestureType } from '../gesture'; import { findNodeHandle, @@ -21,7 +21,7 @@ import { attachHandlers } from './attachHandlers'; import { updateHandlers } from './updateHandlers'; import { needsToReattach } from './needsToReattach'; import { dropHandlers } from './dropHandlers'; -import { validateDetectorChildren } from './utils'; +import { useForceRender, validateDetectorChildren } from './utils'; import { Wrap, AnimatedWrap } from './Wrap'; declare const global: { @@ -109,12 +109,15 @@ export const GestureDetector = (props: GestureDetectorProps) => { 'GestureDetector must be used as a descendant of GestureHandlerRootView. Otherwise the gestures will not be recognized. See https://docs.swmansion.com/react-native-gesture-handler/docs/installation for more details.' ); } + const forceRender = useForceRender(); const gestureConfig = props.gesture; propagateDetectorConfig(props, gestureConfig); - const gesture = gestureConfig.toGestureArray(); - const useReanimatedHook = gesture.some((g) => g.shouldUseReanimated); + const gesturesToAttach = gestureConfig.toGestureArray(); + const shouldUseReanimated = gesturesToAttach.some( + (g) => g.shouldUseReanimated + ); // store state in ref to prevent unnecessary renders const state = useRef({ @@ -135,20 +138,15 @@ export const GestureDetector = (props: GestureDetectorProps) => { : undefined, }); - const [renderState, setRenderState] = useState(false); - function forceRender() { - setRenderState(!renderState); - } - const preparedGesture = React.useRef({ - config: gesture, + config: gesturesToAttach, animatedEventHandler: null, animatedHandlers: null, firstExecution: true, - useReanimatedHook: useReanimatedHook, + useReanimatedHook: shouldUseReanimated, }).current; - if (useReanimatedHook !== preparedGesture.useReanimatedHook) { + if (shouldUseReanimated !== preparedGesture.useReanimatedHook) { throw new Error( tagMessage( 'You cannot change the thread the callbacks are run on while the app is running' @@ -161,13 +159,13 @@ export const GestureDetector = (props: GestureDetectorProps) => { const viewTag = findNodeHandle(state.viewRef) as number; const forceReattach = viewTag !== state.previousViewTag; - if (forceReattach || needsToReattach(preparedGesture, gesture)) { + if (forceReattach || needsToReattach(preparedGesture, gesturesToAttach)) { validateDetectorChildren(state.viewRef); dropHandlers(preparedGesture); attachHandlers({ preparedGesture, gestureConfig, - gesture, + gesture: gesturesToAttach, webEventHandlersRef, viewTag, mountedRef, @@ -179,7 +177,12 @@ export const GestureDetector = (props: GestureDetectorProps) => { forceRender(); } } else if (!skipConfigUpdate) { - updateHandlers(preparedGesture, gestureConfig, gesture, mountedRef); + updateHandlers( + preparedGesture, + gestureConfig, + gesturesToAttach, + mountedRef + ); } } @@ -187,7 +190,7 @@ export const GestureDetector = (props: GestureDetectorProps) => { // config update will be enough as all necessary items are stored in shared values anyway const needsToRebuildReanimatedEvent = preparedGesture.firstExecution || - needsToReattach(preparedGesture, gesture) || + needsToReattach(preparedGesture, gesturesToAttach) || state.forceReattach; state.forceReattach = false; @@ -196,7 +199,7 @@ export const GestureDetector = (props: GestureDetectorProps) => { gestureConfig.initialize(); } - if (useReanimatedHook) { + if (shouldUseReanimated) { // Whether animatedGesture or gesture is used shouldn't change while the app is running // eslint-disable-next-line react-hooks/rules-of-hooks useAnimatedGesture(preparedGesture, needsToRebuildReanimatedEvent); @@ -212,7 +215,7 @@ export const GestureDetector = (props: GestureDetectorProps) => { attachHandlers({ preparedGesture, gestureConfig, - gesture, + gesture: gesturesToAttach, webEventHandlersRef, viewTag, mountedRef, @@ -260,7 +263,7 @@ export const GestureDetector = (props: GestureDetectorProps) => { } }; - if (useReanimatedHook) { + if (shouldUseReanimated) { return ( { + setRenderState(!renderState); + }, [renderState, setRenderState]); + + return forceRender; +} From 4ba074b35c73717a5fe9cf156ec66aa713330a4e Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Thu, 9 May 2024 15:56:36 +0200 Subject: [PATCH 06/26] More renames --- src/handlers/gestures/GestureDetector/attachHandlers.ts | 4 ++-- src/handlers/gestures/GestureDetector/dropHandlers.ts | 2 +- src/handlers/gestures/GestureDetector/index.tsx | 6 +++--- src/handlers/gestures/GestureDetector/needsToReattach.ts | 7 ++++--- src/handlers/gestures/GestureDetector/types.ts | 4 ++-- src/handlers/gestures/GestureDetector/updateHandlers.ts | 6 +++--- 6 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/handlers/gestures/GestureDetector/attachHandlers.ts b/src/handlers/gestures/GestureDetector/attachHandlers.ts index 4fd92abc0e..d5e592344e 100644 --- a/src/handlers/gestures/GestureDetector/attachHandlers.ts +++ b/src/handlers/gestures/GestureDetector/attachHandlers.ts @@ -98,9 +98,9 @@ export function attachHandlers({ scheduleFlushOperations(); }); - preparedGesture.config = gesture; + preparedGesture.gesturesToAttach = gesture; - for (const gesture of preparedGesture.config) { + for (const gesture of preparedGesture.gesturesToAttach) { const actionType = gesture.shouldUseReanimated ? ActionType.REANIMATED_WORKLET : ActionType.JS_FUNCTION_NEW_API; diff --git a/src/handlers/gestures/GestureDetector/dropHandlers.ts b/src/handlers/gestures/GestureDetector/dropHandlers.ts index d50204aaf6..44c4579ca6 100644 --- a/src/handlers/gestures/GestureDetector/dropHandlers.ts +++ b/src/handlers/gestures/GestureDetector/dropHandlers.ts @@ -4,7 +4,7 @@ import { scheduleFlushOperations } from '../../gestureHandlerCommon'; import { GestureConfigReference } from './types'; export function dropHandlers(preparedGesture: GestureConfigReference) { - for (const handler of preparedGesture.config) { + for (const handler of preparedGesture.gesturesToAttach) { RNGestureHandlerModule.dropGestureHandler(handler.handlerTag); unregisterHandler(handler.handlerTag, handler.config.testId); diff --git a/src/handlers/gestures/GestureDetector/index.tsx b/src/handlers/gestures/GestureDetector/index.tsx index f4265b7c3c..80c6e5374e 100644 --- a/src/handlers/gestures/GestureDetector/index.tsx +++ b/src/handlers/gestures/GestureDetector/index.tsx @@ -139,14 +139,14 @@ export const GestureDetector = (props: GestureDetectorProps) => { }); const preparedGesture = React.useRef({ - config: gesturesToAttach, + gesturesToAttach: gesturesToAttach, animatedEventHandler: null, animatedHandlers: null, firstExecution: true, - useReanimatedHook: shouldUseReanimated, + shouldUseReanimated: shouldUseReanimated, }).current; - if (shouldUseReanimated !== preparedGesture.useReanimatedHook) { + if (shouldUseReanimated !== preparedGesture.shouldUseReanimated) { throw new Error( tagMessage( 'You cannot change the thread the callbacks are run on while the app is running' diff --git a/src/handlers/gestures/GestureDetector/needsToReattach.ts b/src/handlers/gestures/GestureDetector/needsToReattach.ts index 76ab3179ad..2d25e72655 100644 --- a/src/handlers/gestures/GestureDetector/needsToReattach.ts +++ b/src/handlers/gestures/GestureDetector/needsToReattach.ts @@ -5,14 +5,15 @@ export function needsToReattach( preparedGesture: GestureConfigReference, gesture: GestureType[] ) { - if (gesture.length !== preparedGesture.config.length) { + if (gesture.length !== preparedGesture.gesturesToAttach.length) { return true; } for (let i = 0; i < gesture.length; i++) { if ( - gesture[i].handlerName !== preparedGesture.config[i].handlerName || + gesture[i].handlerName !== + preparedGesture.gesturesToAttach[i].handlerName || gesture[i].shouldUseReanimated !== - preparedGesture.config[i].shouldUseReanimated + preparedGesture.gesturesToAttach[i].shouldUseReanimated ) { return true; } diff --git a/src/handlers/gestures/GestureDetector/types.ts b/src/handlers/gestures/GestureDetector/types.ts index 43ff219c0b..8e53b7ef7d 100644 --- a/src/handlers/gestures/GestureDetector/types.ts +++ b/src/handlers/gestures/GestureDetector/types.ts @@ -3,13 +3,13 @@ import { SharedValue } from '../reanimatedWrapper'; import { HandlerStateChangeEvent } from '../../gestureHandlerCommon'; export type GestureConfigReference = { - config: GestureType[]; + gesturesToAttach: GestureType[]; animatedEventHandler: unknown; animatedHandlers: SharedValue< HandlerCallbacks>[] | null > | null; firstExecution: boolean; - useReanimatedHook: boolean; + shouldUseReanimated: boolean; }; export interface WebEventHandler { diff --git a/src/handlers/gestures/GestureDetector/updateHandlers.ts b/src/handlers/gestures/GestureDetector/updateHandlers.ts index 02e01e0e7e..8acccbfba5 100644 --- a/src/handlers/gestures/GestureDetector/updateHandlers.ts +++ b/src/handlers/gestures/GestureDetector/updateHandlers.ts @@ -24,7 +24,7 @@ export function updateHandlers( gestureConfig.prepare(); for (let i = 0; i < gesture.length; i++) { - const handler = preparedGesture.config[i]; + const handler = preparedGesture.gesturesToAttach[i]; checkGestureCallbacksForWorklets(handler); // only update handlerTag when it's actually different, it may be the same @@ -43,7 +43,7 @@ export function updateHandlers( return; } for (let i = 0; i < gesture.length; i++) { - const handler = preparedGesture.config[i]; + const handler = preparedGesture.gesturesToAttach[i]; handler.config = gesture[i].config; handler.handlers = gesture[i].handlers; @@ -70,7 +70,7 @@ export function updateHandlers( if (preparedGesture.animatedHandlers) { const previousHandlersValue = preparedGesture.animatedHandlers.value ?? []; - const newHandlersValue = preparedGesture.config + const newHandlersValue = preparedGesture.gesturesToAttach .filter((g) => g.shouldUseReanimated) // ignore gestures that shouldn't run on UI .map((g) => g.handlers) as unknown as HandlerCallbacks< Record From f92267e7192724e35c7bd4bd15e604b4c1b87ffb Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Thu, 9 May 2024 15:59:06 +0200 Subject: [PATCH 07/26] Warn when none of the callbacks are worklest, only throw in dev --- .../gestures/GestureDetector/index.tsx | 4 +- .../gestures/GestureDetector/utils.ts | 43 ++++++++++++------- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/src/handlers/gestures/GestureDetector/index.tsx b/src/handlers/gestures/GestureDetector/index.tsx index 80c6e5374e..71aacc39c0 100644 --- a/src/handlers/gestures/GestureDetector/index.tsx +++ b/src/handlers/gestures/GestureDetector/index.tsx @@ -146,7 +146,7 @@ export const GestureDetector = (props: GestureDetectorProps) => { shouldUseReanimated: shouldUseReanimated, }).current; - if (shouldUseReanimated !== preparedGesture.shouldUseReanimated) { + if (__DEV__ && shouldUseReanimated !== preparedGesture.shouldUseReanimated) { throw new Error( tagMessage( 'You cannot change the thread the callbacks are run on while the app is running' @@ -249,7 +249,7 @@ export const GestureDetector = (props: GestureDetectorProps) => { // in case the view has changed, while config update would be handled be the `useEffect` above onHandlersUpdate(true); - if (isFabric() && global.isFormsStackingContext) { + if (__DEV__ && isFabric() && global.isFormsStackingContext) { const node = getShadowNodeFromRef(ref); if (global.isFormsStackingContext(node) === false) { console.error( diff --git a/src/handlers/gestures/GestureDetector/utils.ts b/src/handlers/gestures/GestureDetector/utils.ts index e01d3d8d69..326322c5a0 100644 --- a/src/handlers/gestures/GestureDetector/utils.ts +++ b/src/handlers/gestures/GestureDetector/utils.ts @@ -51,23 +51,36 @@ export function extractValidHandlerTags( } export function checkGestureCallbacksForWorklets(gesture: GestureType) { - // if a gesture is explicitly marked to run on the JS thread there is no need to check - // if callbacks are worklets as the user is aware they will be ran on the JS thread - if (gesture.config.runOnJS) { - return; - } + if (__DEV__) { + // if a gesture is explicitly marked to run on the JS thread there is no need to check + // if callbacks are worklets as the user is aware they will be ran on the JS thread + if (gesture.config.runOnJS) { + return; + } - const areSomeNotWorklets = gesture.handlers.isWorklet.includes(false); - const areSomeWorklets = gesture.handlers.isWorklet.includes(true); + const areSomeNotWorklets = gesture.handlers.isWorklet.includes(false); + const areSomeWorklets = gesture.handlers.isWorklet.includes(true); - // if some of the callbacks are worklets and some are not, and the gesture is not - // explicitly marked with `.runOnJS(true)` show an error - if (areSomeNotWorklets && areSomeWorklets) { - console.error( - tagMessage( - `Some of the callbacks in the gesture are worklets and some are not. Either make sure that all calbacks are marked as 'worklet' if you wish to run them on the UI thread or use '.runOnJS(true)' modifier on the gesture explicitly to run all callbacks on the JS thread.` - ) - ); + // if some of the callbacks are worklets and some are not, and the gesture is not + // explicitly marked with `.runOnJS(true)` show an error + if (areSomeNotWorklets && areSomeWorklets) { + console.error( + tagMessage( + `Some of the callbacks in the gesture are worklets and some are not. Either make sure that all calbacks are marked as 'worklet' if you wish to run them on the UI thread or use '.runOnJS(true)' modifier on the gesture explicitly to run all callbacks on the JS thread.` + ) + ); + } + + const areAllNotWorklets = !areSomeWorklets && areSomeNotWorklets; + // if none of the callbacks are worklets and the gesture is not explicitly marked with + // `.runOnJS(true)` show an error + if (areAllNotWorklets) { + console.warn( + tagMessage( + `None of the callbacks in the gesture are worklets. If you wish to run them on the JS thread use '.runOnJS(true)' modifier on the gesture to make this explicit. Otherwise, mark the callbacks as 'worklet' to run them on the UI thread.` + ) + ); + } } } From d0d841babba0102ce8205b022d74b6756f5aea10 Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Thu, 9 May 2024 16:01:20 +0200 Subject: [PATCH 08/26] More renames --- .../gestures/GestureDetector/attachHandlers.ts | 12 ++++++------ src/handlers/gestures/GestureDetector/index.tsx | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/handlers/gestures/GestureDetector/attachHandlers.ts b/src/handlers/gestures/GestureDetector/attachHandlers.ts index d5e592344e..63ee2bd4f0 100644 --- a/src/handlers/gestures/GestureDetector/attachHandlers.ts +++ b/src/handlers/gestures/GestureDetector/attachHandlers.ts @@ -21,7 +21,7 @@ import { interface AttachHandlersConfig { preparedGesture: GestureConfigReference; gestureConfig: ComposedGesture | GestureType; - gesture: GestureType[]; + gestures: GestureType[]; viewTag: number; webEventHandlersRef: React.RefObject; mountedRef: React.RefObject; @@ -30,7 +30,7 @@ interface AttachHandlersConfig { export function attachHandlers({ preparedGesture, gestureConfig, - gesture, + gestures, viewTag, webEventHandlersRef, mountedRef, @@ -50,7 +50,7 @@ export function attachHandlers({ gestureConfig.prepare(); }); - for (const handler of gesture) { + for (const handler of gestures) { checkGestureCallbacksForWorklets(handler); RNGestureHandlerModule.createGestureHandler( handler.handlerName, @@ -67,7 +67,7 @@ export function attachHandlers({ if (!mountedRef.current) { return; } - for (const handler of gesture) { + for (const handler of gestures) { let requireToFail: number[] = []; if (handler.config.requireToFail) { requireToFail = extractValidHandlerTags(handler.config.requireToFail); @@ -98,7 +98,7 @@ export function attachHandlers({ scheduleFlushOperations(); }); - preparedGesture.gesturesToAttach = gesture; + preparedGesture.gesturesToAttach = gestures; for (const gesture of preparedGesture.gesturesToAttach) { const actionType = gesture.shouldUseReanimated @@ -126,7 +126,7 @@ export function attachHandlers({ if (preparedGesture.animatedHandlers) { const isAnimatedGesture = (g: GestureType) => g.shouldUseReanimated; - preparedGesture.animatedHandlers.value = gesture + preparedGesture.animatedHandlers.value = gestures .filter(isAnimatedGesture) .map((g) => g.handlers) as unknown as HandlerCallbacks< Record diff --git a/src/handlers/gestures/GestureDetector/index.tsx b/src/handlers/gestures/GestureDetector/index.tsx index 71aacc39c0..43f8d1276e 100644 --- a/src/handlers/gestures/GestureDetector/index.tsx +++ b/src/handlers/gestures/GestureDetector/index.tsx @@ -165,7 +165,7 @@ export const GestureDetector = (props: GestureDetectorProps) => { attachHandlers({ preparedGesture, gestureConfig, - gesture: gesturesToAttach, + gestures: gesturesToAttach, webEventHandlersRef, viewTag, mountedRef, @@ -215,7 +215,7 @@ export const GestureDetector = (props: GestureDetectorProps) => { attachHandlers({ preparedGesture, gestureConfig, - gesture: gesturesToAttach, + gestures: gesturesToAttach, webEventHandlersRef, viewTag, mountedRef, From 91c4fc08e237b7a80bb8c6842a68a682632ff47e Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Thu, 9 May 2024 16:22:25 +0200 Subject: [PATCH 09/26] This one may be tricky, but I think the end result is the same --- src/handlers/gestures/GestureDetector/attachHandlers.ts | 6 +----- src/handlers/gestures/GestureDetector/index.tsx | 8 +------- src/handlers/gestures/GestureDetector/types.ts | 1 - 3 files changed, 2 insertions(+), 13 deletions(-) diff --git a/src/handlers/gestures/GestureDetector/attachHandlers.ts b/src/handlers/gestures/GestureDetector/attachHandlers.ts index 63ee2bd4f0..cdda29d700 100644 --- a/src/handlers/gestures/GestureDetector/attachHandlers.ts +++ b/src/handlers/gestures/GestureDetector/attachHandlers.ts @@ -35,11 +35,7 @@ export function attachHandlers({ webEventHandlersRef, mountedRef, }: AttachHandlersConfig) { - if (!preparedGesture.firstExecution) { - gestureConfig.initialize(); - } else { - preparedGesture.firstExecution = false; - } + gestureConfig.initialize(); // use queueMicrotask to extract handlerTags, because all refs should be initialized // when it's ran diff --git a/src/handlers/gestures/GestureDetector/index.tsx b/src/handlers/gestures/GestureDetector/index.tsx index 43f8d1276e..d4bd788666 100644 --- a/src/handlers/gestures/GestureDetector/index.tsx +++ b/src/handlers/gestures/GestureDetector/index.tsx @@ -142,7 +142,6 @@ export const GestureDetector = (props: GestureDetectorProps) => { gesturesToAttach: gesturesToAttach, animatedEventHandler: null, animatedHandlers: null, - firstExecution: true, shouldUseReanimated: shouldUseReanimated, }).current; @@ -189,16 +188,12 @@ export const GestureDetector = (props: GestureDetectorProps) => { // Reanimated event should be rebuilt only when gestures are reattached, otherwise // config update will be enough as all necessary items are stored in shared values anyway const needsToRebuildReanimatedEvent = - preparedGesture.firstExecution || + state.firstRender || needsToReattach(preparedGesture, gesturesToAttach) || state.forceReattach; state.forceReattach = false; - if (preparedGesture.firstExecution) { - gestureConfig.initialize(); - } - if (shouldUseReanimated) { // Whether animatedGesture or gesture is used shouldn't change while the app is running // eslint-disable-next-line react-hooks/rules-of-hooks @@ -207,7 +202,6 @@ export const GestureDetector = (props: GestureDetectorProps) => { useEffect(() => { const viewTag = findNodeHandle(state.viewRef) as number; - state.firstRender = true; mountedRef.current = true; validateDetectorChildren(state.viewRef); diff --git a/src/handlers/gestures/GestureDetector/types.ts b/src/handlers/gestures/GestureDetector/types.ts index 8e53b7ef7d..ae6e3a81dd 100644 --- a/src/handlers/gestures/GestureDetector/types.ts +++ b/src/handlers/gestures/GestureDetector/types.ts @@ -8,7 +8,6 @@ export type GestureConfigReference = { animatedHandlers: SharedValue< HandlerCallbacks>[] | null > | null; - firstExecution: boolean; shouldUseReanimated: boolean; }; From 2626bb20327cf67ae81f20ea497d1cb4e6247237 Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Thu, 9 May 2024 16:37:22 +0200 Subject: [PATCH 10/26] Move relations to helper --- .../GestureDetector/attachHandlers.ts | 29 ++++--------------- .../GestureDetector/updateHandlers.ts | 19 ++++-------- .../gestures/GestureDetector/utils.ts | 18 ++++++++++-- 3 files changed, 27 insertions(+), 39 deletions(-) diff --git a/src/handlers/gestures/GestureDetector/attachHandlers.ts b/src/handlers/gestures/GestureDetector/attachHandlers.ts index cdda29d700..bb85fcbd9d 100644 --- a/src/handlers/gestures/GestureDetector/attachHandlers.ts +++ b/src/handlers/gestures/GestureDetector/attachHandlers.ts @@ -13,7 +13,7 @@ import type RNGestureHandlerModuleWeb from '../../../RNGestureHandlerModule.web' import { ghQueueMicrotask } from '../../../ghQueueMicrotask'; import { GestureConfigReference, WebEventHandler } from './types'; import { - extractValidHandlerTags, + extractGestureRelations, checkGestureCallbacksForWorklets, ALLOWED_PROPS, } from './utils'; @@ -64,30 +64,13 @@ export function attachHandlers({ return; } for (const handler of gestures) { - let requireToFail: number[] = []; - if (handler.config.requireToFail) { - requireToFail = extractValidHandlerTags(handler.config.requireToFail); - } - - let simultaneousWith: number[] = []; - if (handler.config.simultaneousWith) { - simultaneousWith = extractValidHandlerTags( - handler.config.simultaneousWith - ); - } - - let blocksHandlers: number[] = []; - if (handler.config.blocksHandlers) { - blocksHandlers = extractValidHandlerTags(handler.config.blocksHandlers); - } - RNGestureHandlerModule.updateGestureHandler( handler.handlerTag, - filterConfig(handler.config, ALLOWED_PROPS, { - simultaneousHandlers: simultaneousWith, - waitFor: requireToFail, - blocksHandlers: blocksHandlers, - }) + filterConfig( + handler.config, + ALLOWED_PROPS, + extractGestureRelations(handler) + ) ); } diff --git a/src/handlers/gestures/GestureDetector/updateHandlers.ts b/src/handlers/gestures/GestureDetector/updateHandlers.ts index 8acccbfba5..59ff669292 100644 --- a/src/handlers/gestures/GestureDetector/updateHandlers.ts +++ b/src/handlers/gestures/GestureDetector/updateHandlers.ts @@ -10,7 +10,7 @@ import { ComposedGesture } from '../gestureComposition'; import { ghQueueMicrotask } from '../../../ghQueueMicrotask'; import { GestureConfigReference } from './types'; import { - extractValidHandlerTags, + extractGestureRelations, checkGestureCallbacksForWorklets, ALLOWED_PROPS, } from './utils'; @@ -48,20 +48,13 @@ export function updateHandlers( handler.config = gesture[i].config; handler.handlers = gesture[i].handlers; - const requireToFail = extractValidHandlerTags( - handler.config.requireToFail - ); - - const simultaneousWith = extractValidHandlerTags( - handler.config.simultaneousWith - ); - RNGestureHandlerModule.updateGestureHandler( handler.handlerTag, - filterConfig(handler.config, ALLOWED_PROPS, { - simultaneousHandlers: simultaneousWith, - waitFor: requireToFail, - }) + filterConfig( + handler.config, + ALLOWED_PROPS, + extractGestureRelations(handler) + ) ); registerHandler(handler.handlerTag, handler, handler.config.testId); diff --git a/src/handlers/gestures/GestureDetector/utils.ts b/src/handlers/gestures/GestureDetector/utils.ts index 326322c5a0..c57a1b599c 100644 --- a/src/handlers/gestures/GestureDetector/utils.ts +++ b/src/handlers/gestures/GestureDetector/utils.ts @@ -42,14 +42,26 @@ function convertToHandlerTag(ref: GestureRef): number { } } -export function extractValidHandlerTags( - interactionGroup: GestureRef[] | undefined -) { +function extractValidHandlerTags(interactionGroup: GestureRef[] | undefined) { return ( interactionGroup?.map(convertToHandlerTag)?.filter((tag) => tag > 0) ?? [] ); } +export function extractGestureRelations(gesture: GestureType) { + const requireToFail = extractValidHandlerTags(gesture.config.requireToFail); + const simultaneousWith = extractValidHandlerTags( + gesture.config.simultaneousWith + ); + const blocksHandlers = extractValidHandlerTags(gesture.config.blocksHandlers); + + return { + waitFor: requireToFail, + simultaneousHandlers: simultaneousWith, + blocksHandlers: blocksHandlers, + }; +} + export function checkGestureCallbacksForWorklets(gesture: GestureType) { if (__DEV__) { // if a gesture is explicitly marked to run on the JS thread there is no need to check From 782ec5b080d98592a2f0f906c329909f601c30ee Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Fri, 10 May 2024 08:32:22 +0200 Subject: [PATCH 11/26] Rename `GestureConfigReference` -> `AttachedGestureState` --- src/handlers/gestures/GestureDetector/attachHandlers.ts | 4 ++-- src/handlers/gestures/GestureDetector/dropHandlers.ts | 4 ++-- src/handlers/gestures/GestureDetector/index.tsx | 4 ++-- src/handlers/gestures/GestureDetector/needsToReattach.ts | 4 ++-- src/handlers/gestures/GestureDetector/types.ts | 8 ++++++-- src/handlers/gestures/GestureDetector/updateHandlers.ts | 4 ++-- .../gestures/GestureDetector/useAnimatedGesture.ts | 4 ++-- 7 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/handlers/gestures/GestureDetector/attachHandlers.ts b/src/handlers/gestures/GestureDetector/attachHandlers.ts index bb85fcbd9d..dfa297af85 100644 --- a/src/handlers/gestures/GestureDetector/attachHandlers.ts +++ b/src/handlers/gestures/GestureDetector/attachHandlers.ts @@ -11,7 +11,7 @@ import { ActionType } from '../../../ActionType'; import { Platform } from 'react-native'; import type RNGestureHandlerModuleWeb from '../../../RNGestureHandlerModule.web'; import { ghQueueMicrotask } from '../../../ghQueueMicrotask'; -import { GestureConfigReference, WebEventHandler } from './types'; +import { AttachedGestureState, WebEventHandler } from './types'; import { extractGestureRelations, checkGestureCallbacksForWorklets, @@ -19,7 +19,7 @@ import { } from './utils'; interface AttachHandlersConfig { - preparedGesture: GestureConfigReference; + preparedGesture: AttachedGestureState; gestureConfig: ComposedGesture | GestureType; gestures: GestureType[]; viewTag: number; diff --git a/src/handlers/gestures/GestureDetector/dropHandlers.ts b/src/handlers/gestures/GestureDetector/dropHandlers.ts index 44c4579ca6..3c4e2ccc5a 100644 --- a/src/handlers/gestures/GestureDetector/dropHandlers.ts +++ b/src/handlers/gestures/GestureDetector/dropHandlers.ts @@ -1,9 +1,9 @@ import { unregisterHandler } from '../../handlersRegistry'; import RNGestureHandlerModule from '../../../RNGestureHandlerModule'; import { scheduleFlushOperations } from '../../gestureHandlerCommon'; -import { GestureConfigReference } from './types'; +import { AttachedGestureState } from './types'; -export function dropHandlers(preparedGesture: GestureConfigReference) { +export function dropHandlers(preparedGesture: AttachedGestureState) { for (const handler of preparedGesture.gesturesToAttach) { RNGestureHandlerModule.dropGestureHandler(handler.handlerTag); diff --git a/src/handlers/gestures/GestureDetector/index.tsx b/src/handlers/gestures/GestureDetector/index.tsx index d4bd788666..81870b778c 100644 --- a/src/handlers/gestures/GestureDetector/index.tsx +++ b/src/handlers/gestures/GestureDetector/index.tsx @@ -15,7 +15,7 @@ import { onGestureHandlerEvent } from '../eventReceiver'; import { isNewWebImplementationEnabled } from '../../../EnableNewWebImplementation'; import GestureHandlerRootViewContext from '../../../GestureHandlerRootViewContext'; -import { GestureConfigReference, WebEventHandler } from './types'; +import { AttachedGestureState, WebEventHandler } from './types'; import { useAnimatedGesture } from './useAnimatedGesture'; import { attachHandlers } from './attachHandlers'; import { updateHandlers } from './updateHandlers'; @@ -138,7 +138,7 @@ export const GestureDetector = (props: GestureDetectorProps) => { : undefined, }); - const preparedGesture = React.useRef({ + const preparedGesture = React.useRef({ gesturesToAttach: gesturesToAttach, animatedEventHandler: null, animatedHandlers: null, diff --git a/src/handlers/gestures/GestureDetector/needsToReattach.ts b/src/handlers/gestures/GestureDetector/needsToReattach.ts index 2d25e72655..860a7acc6f 100644 --- a/src/handlers/gestures/GestureDetector/needsToReattach.ts +++ b/src/handlers/gestures/GestureDetector/needsToReattach.ts @@ -1,8 +1,8 @@ import { GestureType } from '../gesture'; -import { GestureConfigReference } from './types'; +import { AttachedGestureState } from './types'; export function needsToReattach( - preparedGesture: GestureConfigReference, + preparedGesture: AttachedGestureState, gesture: GestureType[] ) { if (gesture.length !== preparedGesture.gesturesToAttach.length) { diff --git a/src/handlers/gestures/GestureDetector/types.ts b/src/handlers/gestures/GestureDetector/types.ts index ae6e3a81dd..581d0f457c 100644 --- a/src/handlers/gestures/GestureDetector/types.ts +++ b/src/handlers/gestures/GestureDetector/types.ts @@ -2,14 +2,18 @@ import { GestureType, HandlerCallbacks } from '../gesture'; import { SharedValue } from '../reanimatedWrapper'; import { HandlerStateChangeEvent } from '../../gestureHandlerCommon'; -export type GestureConfigReference = { +export interface AttachedGestureState { + // Array of gestures that should be attached to the view under that gesture detector gesturesToAttach: GestureType[]; + // Event handler for the gesture, returned by `useEvent` from Reanimated animatedEventHandler: unknown; + // Shared value that's responsible for transferring the callbacks to the UI thread handler animatedHandlers: SharedValue< HandlerCallbacks>[] | null > | null; + // Whether `useAnimatedGesture` should be called inside detector shouldUseReanimated: boolean; -}; +} export interface WebEventHandler { onGestureHandlerEvent: (event: HandlerStateChangeEvent) => void; diff --git a/src/handlers/gestures/GestureDetector/updateHandlers.ts b/src/handlers/gestures/GestureDetector/updateHandlers.ts index 59ff669292..5966cdec83 100644 --- a/src/handlers/gestures/GestureDetector/updateHandlers.ts +++ b/src/handlers/gestures/GestureDetector/updateHandlers.ts @@ -8,7 +8,7 @@ import { } from '../../gestureHandlerCommon'; import { ComposedGesture } from '../gestureComposition'; import { ghQueueMicrotask } from '../../../ghQueueMicrotask'; -import { GestureConfigReference } from './types'; +import { AttachedGestureState } from './types'; import { extractGestureRelations, checkGestureCallbacksForWorklets, @@ -16,7 +16,7 @@ import { } from './utils'; export function updateHandlers( - preparedGesture: GestureConfigReference, + preparedGesture: AttachedGestureState, gestureConfig: ComposedGesture | GestureType, gesture: GestureType[], mountedRef: React.RefObject diff --git a/src/handlers/gestures/GestureDetector/useAnimatedGesture.ts b/src/handlers/gestures/GestureDetector/useAnimatedGesture.ts index 4b9aadf962..f9ab90ed54 100644 --- a/src/handlers/gestures/GestureDetector/useAnimatedGesture.ts +++ b/src/handlers/gestures/GestureDetector/useAnimatedGesture.ts @@ -12,7 +12,7 @@ import { import { State } from '../../../State'; import { TouchEventType } from '../../../TouchEventType'; import { tagMessage } from '../../../utils'; -import { GestureConfigReference } from './types'; +import { AttachedGestureState } from './types'; function getHandler( type: CALLBACK_TYPE, @@ -93,7 +93,7 @@ function isTouchEvent( } export function useAnimatedGesture( - preparedGesture: GestureConfigReference, + preparedGesture: AttachedGestureState, needsRebuild: boolean ) { if (!Reanimated) { From 2d26817a9d298f86f6538991e79f0b714c30c914 Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Fri, 10 May 2024 08:52:18 +0200 Subject: [PATCH 12/26] Add comment --- src/handlers/gestures/GestureDetector/needsToReattach.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/handlers/gestures/GestureDetector/needsToReattach.ts b/src/handlers/gestures/GestureDetector/needsToReattach.ts index 860a7acc6f..b465fec6c3 100644 --- a/src/handlers/gestures/GestureDetector/needsToReattach.ts +++ b/src/handlers/gestures/GestureDetector/needsToReattach.ts @@ -1,6 +1,10 @@ import { GestureType } from '../gesture'; import { AttachedGestureState } from './types'; +// Checks whether the gesture should be reattached to the view, this will happen when: +// - The number of gestures in the preparedGesture is different than the number of gestures in the gesture +// - The handlerName is different in any of the gestures +// - At least one of the gestures changed the thread it runs on export function needsToReattach( preparedGesture: AttachedGestureState, gesture: GestureType[] From 48d339746e52eb79c4a4607a01455cc7fa29aaea Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Fri, 10 May 2024 08:53:03 +0200 Subject: [PATCH 13/26] Allow thread switching, no idea why it was disabled --- .../gestures/GestureDetector/index.tsx | 31 ++++++------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/src/handlers/gestures/GestureDetector/index.tsx b/src/handlers/gestures/GestureDetector/index.tsx index 81870b778c..7aa7a82a7d 100644 --- a/src/handlers/gestures/GestureDetector/index.tsx +++ b/src/handlers/gestures/GestureDetector/index.tsx @@ -145,13 +145,15 @@ export const GestureDetector = (props: GestureDetectorProps) => { shouldUseReanimated: shouldUseReanimated, }).current; - if (__DEV__ && shouldUseReanimated !== preparedGesture.shouldUseReanimated) { - throw new Error( - tagMessage( - 'You cannot change the thread the callbacks are run on while the app is running' - ) - ); - } + // Reanimated event should be rebuilt only when gestures are reattached, otherwise + // config update will be enough as all necessary items are stored in shared values anyway + const needsToRebuildReanimatedEvent = + state.firstRender || + needsToReattach(preparedGesture, gesturesToAttach) || + state.forceReattach; + state.forceReattach = false; + + useAnimatedGesture(preparedGesture, needsToRebuildReanimatedEvent); function onHandlersUpdate(skipConfigUpdate?: boolean) { // if the underlying view has changed we need to reattach handlers to the new view @@ -185,21 +187,6 @@ export const GestureDetector = (props: GestureDetectorProps) => { } } - // Reanimated event should be rebuilt only when gestures are reattached, otherwise - // config update will be enough as all necessary items are stored in shared values anyway - const needsToRebuildReanimatedEvent = - state.firstRender || - needsToReattach(preparedGesture, gesturesToAttach) || - state.forceReattach; - - state.forceReattach = false; - - if (shouldUseReanimated) { - // Whether animatedGesture or gesture is used shouldn't change while the app is running - // eslint-disable-next-line react-hooks/rules-of-hooks - useAnimatedGesture(preparedGesture, needsToRebuildReanimatedEvent); - } - useEffect(() => { const viewTag = findNodeHandle(state.viewRef) as number; mountedRef.current = true; From 16d4598bbebf5d1e1aa1c21e7140d66ed7a2637d Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Fri, 10 May 2024 09:06:41 +0200 Subject: [PATCH 14/26] Don't show warning when no Reanimated --- .../gestures/GestureDetector/utils.ts | 6 ++++ src/handlers/gestures/gestureStateManager.ts | 16 ++++++--- src/handlers/gestures/reanimatedWrapper.ts | 36 ++++++++++--------- 3 files changed, 37 insertions(+), 21 deletions(-) diff --git a/src/handlers/gestures/GestureDetector/utils.ts b/src/handlers/gestures/GestureDetector/utils.ts index c57a1b599c..3ca543c1d7 100644 --- a/src/handlers/gestures/GestureDetector/utils.ts +++ b/src/handlers/gestures/GestureDetector/utils.ts @@ -17,6 +17,7 @@ import { baseGestureHandlerWithDetectorProps } from '../../gestureHandlerCommon' import { getReactNativeVersion } from '../../../getReactNativeVersion'; import { RNRenderer } from '../../../RNRenderer'; import { useCallback, useState } from 'react'; +import { Reanimated } from '../reanimatedWrapper'; export const ALLOWED_PROPS = [ ...baseGestureHandlerWithDetectorProps, @@ -83,6 +84,11 @@ export function checkGestureCallbacksForWorklets(gesture: GestureType) { ); } + if (Reanimated === undefined) { + // if Reanimated is not available, we can't run worklets, so we shouldn't show the warning + return; + } + const areAllNotWorklets = !areSomeWorklets && areSomeNotWorklets; // if none of the callbacks are worklets and the gesture is not explicitly marked with // `.runOnJS(true)` show an error diff --git a/src/handlers/gestures/gestureStateManager.ts b/src/handlers/gestures/gestureStateManager.ts index 7dee92bbfe..b052e9d310 100644 --- a/src/handlers/gestures/gestureStateManager.ts +++ b/src/handlers/gestures/gestureStateManager.ts @@ -24,7 +24,9 @@ function create(handlerTag: number): GestureStateManagerType { begin: () => { 'worklet'; if (REANIMATED_AVAILABLE) { - setGestureState(handlerTag, State.BEGAN); + // When Reanimated is available, setGestureState should be defined + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + setGestureState!(handlerTag, State.BEGAN); } else { console.warn(warningMessage); } @@ -33,7 +35,9 @@ function create(handlerTag: number): GestureStateManagerType { activate: () => { 'worklet'; if (REANIMATED_AVAILABLE) { - setGestureState(handlerTag, State.ACTIVE); + // When Reanimated is available, setGestureState should be defined + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + setGestureState!(handlerTag, State.ACTIVE); } else { console.warn(warningMessage); } @@ -42,7 +46,9 @@ function create(handlerTag: number): GestureStateManagerType { fail: () => { 'worklet'; if (REANIMATED_AVAILABLE) { - setGestureState(handlerTag, State.FAILED); + // When Reanimated is available, setGestureState should be defined + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + setGestureState!(handlerTag, State.FAILED); } else { console.warn(warningMessage); } @@ -51,7 +57,9 @@ function create(handlerTag: number): GestureStateManagerType { end: () => { 'worklet'; if (REANIMATED_AVAILABLE) { - setGestureState(handlerTag, State.END); + // When Reanimated is available, setGestureState should be defined + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + setGestureState!(handlerTag, State.END); } else { console.warn(warningMessage); } diff --git a/src/handlers/gestures/reanimatedWrapper.ts b/src/handlers/gestures/reanimatedWrapper.ts index 6ef7f7dedb..67771b08ca 100644 --- a/src/handlers/gestures/reanimatedWrapper.ts +++ b/src/handlers/gestures/reanimatedWrapper.ts @@ -9,23 +9,25 @@ export interface SharedValue { value: T; } -let Reanimated: { - default: { - // Slightly modified definition copied from 'react-native-reanimated' - // eslint-disable-next-line @typescript-eslint/ban-types - createAnimatedComponent

( - component: ComponentClass

, - options?: unknown - ): ComponentClass

; - }; - useEvent: ( - callback: (event: GestureUpdateEvent | GestureStateChangeEvent) => void, - events: string[], - rebuild: boolean - ) => unknown; - useSharedValue: (value: T) => SharedValue; - setGestureState: (handlerTag: number, newState: number) => void; -}; +let Reanimated: + | { + default: { + // Slightly modified definition copied from 'react-native-reanimated' + // eslint-disable-next-line @typescript-eslint/ban-types + createAnimatedComponent

( + component: ComponentClass

, + options?: unknown + ): ComponentClass

; + }; + useEvent: ( + callback: (event: GestureUpdateEvent | GestureStateChangeEvent) => void, + events: string[], + rebuild: boolean + ) => unknown; + useSharedValue: (value: T) => SharedValue; + setGestureState: (handlerTag: number, newState: number) => void; + } + | undefined; try { Reanimated = require('react-native-reanimated'); From 90c9a5fa1028f56470159bb6d3344cc17014d4f3 Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Fri, 10 May 2024 09:15:01 +0200 Subject: [PATCH 15/26] Move mounted flag into the attached gesture state --- .../gestures/GestureDetector/attachHandlers.ts | 6 ++---- src/handlers/gestures/GestureDetector/index.tsx | 15 ++++----------- src/handlers/gestures/GestureDetector/types.ts | 2 ++ .../gestures/GestureDetector/updateHandlers.ts | 6 ++---- 4 files changed, 10 insertions(+), 19 deletions(-) diff --git a/src/handlers/gestures/GestureDetector/attachHandlers.ts b/src/handlers/gestures/GestureDetector/attachHandlers.ts index dfa297af85..90237bcbb2 100644 --- a/src/handlers/gestures/GestureDetector/attachHandlers.ts +++ b/src/handlers/gestures/GestureDetector/attachHandlers.ts @@ -24,7 +24,6 @@ interface AttachHandlersConfig { gestures: GestureType[]; viewTag: number; webEventHandlersRef: React.RefObject; - mountedRef: React.RefObject; } export function attachHandlers({ @@ -33,14 +32,13 @@ export function attachHandlers({ gestures, viewTag, webEventHandlersRef, - mountedRef, }: AttachHandlersConfig) { gestureConfig.initialize(); // use queueMicrotask to extract handlerTags, because all refs should be initialized // when it's ran ghQueueMicrotask(() => { - if (!mountedRef.current) { + if (!preparedGesture.isMounted) { return; } gestureConfig.prepare(); @@ -60,7 +58,7 @@ export function attachHandlers({ // use queueMicrotask to extract handlerTags, because all refs should be initialized // when it's ran ghQueueMicrotask(() => { - if (!mountedRef.current) { + if (!preparedGesture.isMounted) { return; } for (const handler of gestures) { diff --git a/src/handlers/gestures/GestureDetector/index.tsx b/src/handlers/gestures/GestureDetector/index.tsx index 7aa7a82a7d..d00b6d4595 100644 --- a/src/handlers/gestures/GestureDetector/index.tsx +++ b/src/handlers/gestures/GestureDetector/index.tsx @@ -126,7 +126,6 @@ export const GestureDetector = (props: GestureDetectorProps) => { previousViewTag: -1, forceReattach: false, }).current; - const mountedRef = useRef(false); const webEventHandlersRef = useRef({ onGestureHandlerEvent: (e: HandlerStateChangeEvent) => { onGestureHandlerEvent(e.nativeEvent); @@ -143,6 +142,7 @@ export const GestureDetector = (props: GestureDetectorProps) => { animatedEventHandler: null, animatedHandlers: null, shouldUseReanimated: shouldUseReanimated, + isMounted: false, }).current; // Reanimated event should be rebuilt only when gestures are reattached, otherwise @@ -169,7 +169,6 @@ export const GestureDetector = (props: GestureDetectorProps) => { gestures: gesturesToAttach, webEventHandlersRef, viewTag, - mountedRef, }); state.previousViewTag = viewTag; @@ -178,18 +177,13 @@ export const GestureDetector = (props: GestureDetectorProps) => { forceRender(); } } else if (!skipConfigUpdate) { - updateHandlers( - preparedGesture, - gestureConfig, - gesturesToAttach, - mountedRef - ); + updateHandlers(preparedGesture, gestureConfig, gesturesToAttach); } } useEffect(() => { const viewTag = findNodeHandle(state.viewRef) as number; - mountedRef.current = true; + preparedGesture.isMounted = true; validateDetectorChildren(state.viewRef); @@ -199,11 +193,10 @@ export const GestureDetector = (props: GestureDetectorProps) => { gestures: gesturesToAttach, webEventHandlersRef, viewTag, - mountedRef, }); return () => { - mountedRef.current = false; + preparedGesture.isMounted = false; dropHandlers(preparedGesture); }; }, []); diff --git a/src/handlers/gestures/GestureDetector/types.ts b/src/handlers/gestures/GestureDetector/types.ts index 581d0f457c..8de46839a2 100644 --- a/src/handlers/gestures/GestureDetector/types.ts +++ b/src/handlers/gestures/GestureDetector/types.ts @@ -13,6 +13,8 @@ export interface AttachedGestureState { > | null; // Whether `useAnimatedGesture` should be called inside detector shouldUseReanimated: boolean; + // Whether the GestureDetector is mounted + isMounted: boolean; } export interface WebEventHandler { diff --git a/src/handlers/gestures/GestureDetector/updateHandlers.ts b/src/handlers/gestures/GestureDetector/updateHandlers.ts index 5966cdec83..3e553ed500 100644 --- a/src/handlers/gestures/GestureDetector/updateHandlers.ts +++ b/src/handlers/gestures/GestureDetector/updateHandlers.ts @@ -1,4 +1,3 @@ -import React from 'react'; import { GestureType, HandlerCallbacks } from '../gesture'; import { registerHandler } from '../../handlersRegistry'; import RNGestureHandlerModule from '../../../RNGestureHandlerModule'; @@ -18,8 +17,7 @@ import { export function updateHandlers( preparedGesture: AttachedGestureState, gestureConfig: ComposedGesture | GestureType, - gesture: GestureType[], - mountedRef: React.RefObject + gesture: GestureType[] ) { gestureConfig.prepare(); @@ -39,7 +37,7 @@ export function updateHandlers( // and handlerTags in BaseGesture references should be updated in the loop above (we need to wait // in case of external relations) ghQueueMicrotask(() => { - if (!mountedRef.current) { + if (!preparedGesture.isMounted) { return; } for (let i = 0; i < gesture.length; i++) { From 9eee2b8d909d741e99e32c6c6b809d877530216b Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Fri, 10 May 2024 09:32:29 +0200 Subject: [PATCH 16/26] Rename and move assignment --- .../GestureDetector/attachHandlers.ts | 16 +++++++------- .../gestures/GestureDetector/dropHandlers.ts | 2 +- .../gestures/GestureDetector/index.tsx | 6 ++--- .../GestureDetector/needsToReattach.ts | 14 ++++++------ .../gestures/GestureDetector/types.ts | 2 +- .../GestureDetector/updateHandlers.ts | 22 +++++++++---------- 6 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/handlers/gestures/GestureDetector/attachHandlers.ts b/src/handlers/gestures/GestureDetector/attachHandlers.ts index 90237bcbb2..0ded87eb4e 100644 --- a/src/handlers/gestures/GestureDetector/attachHandlers.ts +++ b/src/handlers/gestures/GestureDetector/attachHandlers.ts @@ -21,7 +21,7 @@ import { interface AttachHandlersConfig { preparedGesture: AttachedGestureState; gestureConfig: ComposedGesture | GestureType; - gestures: GestureType[]; + gesturesToAttach: GestureType[]; viewTag: number; webEventHandlersRef: React.RefObject; } @@ -29,7 +29,7 @@ interface AttachHandlersConfig { export function attachHandlers({ preparedGesture, gestureConfig, - gestures, + gesturesToAttach, viewTag, webEventHandlersRef, }: AttachHandlersConfig) { @@ -44,7 +44,7 @@ export function attachHandlers({ gestureConfig.prepare(); }); - for (const handler of gestures) { + for (const handler of gesturesToAttach) { checkGestureCallbacksForWorklets(handler); RNGestureHandlerModule.createGestureHandler( handler.handlerName, @@ -61,7 +61,7 @@ export function attachHandlers({ if (!preparedGesture.isMounted) { return; } - for (const handler of gestures) { + for (const handler of gesturesToAttach) { RNGestureHandlerModule.updateGestureHandler( handler.handlerTag, filterConfig( @@ -75,9 +75,7 @@ export function attachHandlers({ scheduleFlushOperations(); }); - preparedGesture.gesturesToAttach = gestures; - - for (const gesture of preparedGesture.gesturesToAttach) { + for (const gesture of gesturesToAttach) { const actionType = gesture.shouldUseReanimated ? ActionType.REANIMATED_WORKLET : ActionType.JS_FUNCTION_NEW_API; @@ -100,10 +98,12 @@ export function attachHandlers({ } } + preparedGesture.attachedGestures = gesturesToAttach; + if (preparedGesture.animatedHandlers) { const isAnimatedGesture = (g: GestureType) => g.shouldUseReanimated; - preparedGesture.animatedHandlers.value = gestures + preparedGesture.animatedHandlers.value = gesturesToAttach .filter(isAnimatedGesture) .map((g) => g.handlers) as unknown as HandlerCallbacks< Record diff --git a/src/handlers/gestures/GestureDetector/dropHandlers.ts b/src/handlers/gestures/GestureDetector/dropHandlers.ts index 3c4e2ccc5a..94e9a2081b 100644 --- a/src/handlers/gestures/GestureDetector/dropHandlers.ts +++ b/src/handlers/gestures/GestureDetector/dropHandlers.ts @@ -4,7 +4,7 @@ import { scheduleFlushOperations } from '../../gestureHandlerCommon'; import { AttachedGestureState } from './types'; export function dropHandlers(preparedGesture: AttachedGestureState) { - for (const handler of preparedGesture.gesturesToAttach) { + for (const handler of preparedGesture.attachedGestures) { RNGestureHandlerModule.dropGestureHandler(handler.handlerTag); unregisterHandler(handler.handlerTag, handler.config.testId); diff --git a/src/handlers/gestures/GestureDetector/index.tsx b/src/handlers/gestures/GestureDetector/index.tsx index d00b6d4595..4ba3237e64 100644 --- a/src/handlers/gestures/GestureDetector/index.tsx +++ b/src/handlers/gestures/GestureDetector/index.tsx @@ -138,7 +138,7 @@ export const GestureDetector = (props: GestureDetectorProps) => { }); const preparedGesture = React.useRef({ - gesturesToAttach: gesturesToAttach, + attachedGestures: [], animatedEventHandler: null, animatedHandlers: null, shouldUseReanimated: shouldUseReanimated, @@ -166,7 +166,7 @@ export const GestureDetector = (props: GestureDetectorProps) => { attachHandlers({ preparedGesture, gestureConfig, - gestures: gesturesToAttach, + gesturesToAttach, webEventHandlersRef, viewTag, }); @@ -190,7 +190,7 @@ export const GestureDetector = (props: GestureDetectorProps) => { attachHandlers({ preparedGesture, gestureConfig, - gestures: gesturesToAttach, + gesturesToAttach, webEventHandlersRef, viewTag, }); diff --git a/src/handlers/gestures/GestureDetector/needsToReattach.ts b/src/handlers/gestures/GestureDetector/needsToReattach.ts index b465fec6c3..d755f15464 100644 --- a/src/handlers/gestures/GestureDetector/needsToReattach.ts +++ b/src/handlers/gestures/GestureDetector/needsToReattach.ts @@ -7,17 +7,17 @@ import { AttachedGestureState } from './types'; // - At least one of the gestures changed the thread it runs on export function needsToReattach( preparedGesture: AttachedGestureState, - gesture: GestureType[] + newGestures: GestureType[] ) { - if (gesture.length !== preparedGesture.gesturesToAttach.length) { + if (newGestures.length !== preparedGesture.attachedGestures.length) { return true; } - for (let i = 0; i < gesture.length; i++) { + for (let i = 0; i < newGestures.length; i++) { if ( - gesture[i].handlerName !== - preparedGesture.gesturesToAttach[i].handlerName || - gesture[i].shouldUseReanimated !== - preparedGesture.gesturesToAttach[i].shouldUseReanimated + newGestures[i].handlerName !== + preparedGesture.attachedGestures[i].handlerName || + newGestures[i].shouldUseReanimated !== + preparedGesture.attachedGestures[i].shouldUseReanimated ) { return true; } diff --git a/src/handlers/gestures/GestureDetector/types.ts b/src/handlers/gestures/GestureDetector/types.ts index 8de46839a2..f843b2c61d 100644 --- a/src/handlers/gestures/GestureDetector/types.ts +++ b/src/handlers/gestures/GestureDetector/types.ts @@ -4,7 +4,7 @@ import { HandlerStateChangeEvent } from '../../gestureHandlerCommon'; export interface AttachedGestureState { // Array of gestures that should be attached to the view under that gesture detector - gesturesToAttach: GestureType[]; + attachedGestures: GestureType[]; // Event handler for the gesture, returned by `useEvent` from Reanimated animatedEventHandler: unknown; // Shared value that's responsible for transferring the callbacks to the UI thread handler diff --git a/src/handlers/gestures/GestureDetector/updateHandlers.ts b/src/handlers/gestures/GestureDetector/updateHandlers.ts index 3e553ed500..a8508d7f0d 100644 --- a/src/handlers/gestures/GestureDetector/updateHandlers.ts +++ b/src/handlers/gestures/GestureDetector/updateHandlers.ts @@ -17,19 +17,19 @@ import { export function updateHandlers( preparedGesture: AttachedGestureState, gestureConfig: ComposedGesture | GestureType, - gesture: GestureType[] + newGestures: GestureType[] ) { gestureConfig.prepare(); - for (let i = 0; i < gesture.length; i++) { - const handler = preparedGesture.gesturesToAttach[i]; + for (let i = 0; i < newGestures.length; i++) { + const handler = preparedGesture.attachedGestures[i]; checkGestureCallbacksForWorklets(handler); // only update handlerTag when it's actually different, it may be the same // if gesture config object is wrapped with useMemo - if (gesture[i].handlerTag !== handler.handlerTag) { - gesture[i].handlerTag = handler.handlerTag; - gesture[i].handlers.handlerTag = handler.handlerTag; + if (newGestures[i].handlerTag !== handler.handlerTag) { + newGestures[i].handlerTag = handler.handlerTag; + newGestures[i].handlers.handlerTag = handler.handlerTag; } } @@ -40,11 +40,11 @@ export function updateHandlers( if (!preparedGesture.isMounted) { return; } - for (let i = 0; i < gesture.length; i++) { - const handler = preparedGesture.gesturesToAttach[i]; + for (let i = 0; i < newGestures.length; i++) { + const handler = preparedGesture.attachedGestures[i]; - handler.config = gesture[i].config; - handler.handlers = gesture[i].handlers; + handler.config = newGestures[i].config; + handler.handlers = newGestures[i].handlers; RNGestureHandlerModule.updateGestureHandler( handler.handlerTag, @@ -61,7 +61,7 @@ export function updateHandlers( if (preparedGesture.animatedHandlers) { const previousHandlersValue = preparedGesture.animatedHandlers.value ?? []; - const newHandlersValue = preparedGesture.gesturesToAttach + const newHandlersValue = preparedGesture.attachedGestures .filter((g) => g.shouldUseReanimated) // ignore gestures that shouldn't run on UI .map((g) => g.handlers) as unknown as HandlerCallbacks< Record From 3b07b17332c81c17a6c978e0b3369554ad93fac4 Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Fri, 10 May 2024 10:41:18 +0200 Subject: [PATCH 17/26] Update src/handlers/gestures/GestureDetector/utils.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Michał Bert <63123542+m-bert@users.noreply.github.com> --- src/handlers/gestures/GestureDetector/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handlers/gestures/GestureDetector/utils.ts b/src/handlers/gestures/GestureDetector/utils.ts index 3ca543c1d7..c3b546586f 100644 --- a/src/handlers/gestures/GestureDetector/utils.ts +++ b/src/handlers/gestures/GestureDetector/utils.ts @@ -91,7 +91,7 @@ export function checkGestureCallbacksForWorklets(gesture: GestureType) { const areAllNotWorklets = !areSomeWorklets && areSomeNotWorklets; // if none of the callbacks are worklets and the gesture is not explicitly marked with - // `.runOnJS(true)` show an error + // `.runOnJS(true)` show a warning if (areAllNotWorklets) { console.warn( tagMessage( From 781f2940789f807c29d88de4c7403f4d3cc766fa Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Fri, 10 May 2024 10:43:19 +0200 Subject: [PATCH 18/26] Update src/handlers/gestures/GestureDetector/index.tsx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Michał Bert <63123542+m-bert@users.noreply.github.com> --- src/handlers/gestures/GestureDetector/index.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/handlers/gestures/GestureDetector/index.tsx b/src/handlers/gestures/GestureDetector/index.tsx index 4ba3237e64..7f2f128c88 100644 --- a/src/handlers/gestures/GestureDetector/index.tsx +++ b/src/handlers/gestures/GestureDetector/index.tsx @@ -210,7 +210,9 @@ export const GestureDetector = (props: GestureDetectorProps) => { }, [props]); const refFunction = (ref: unknown) => { - if (ref !== null) { + if (ref === null) { + return; + } // @ts-ignore Just setting the view ref state.viewRef = ref; From d715f9c972429e3c513fd3067cd7b6b81a5ad291 Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Fri, 10 May 2024 10:44:31 +0200 Subject: [PATCH 19/26] Update src/handlers/gestures/GestureDetector/utils.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Michał Bert <63123542+m-bert@users.noreply.github.com> --- src/handlers/gestures/GestureDetector/utils.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/handlers/gestures/GestureDetector/utils.ts b/src/handlers/gestures/GestureDetector/utils.ts index c3b546586f..6ead800fa3 100644 --- a/src/handlers/gestures/GestureDetector/utils.ts +++ b/src/handlers/gestures/GestureDetector/utils.ts @@ -64,7 +64,9 @@ export function extractGestureRelations(gesture: GestureType) { } export function checkGestureCallbacksForWorklets(gesture: GestureType) { - if (__DEV__) { + if (!__DEV__) { + return; + } // if a gesture is explicitly marked to run on the JS thread there is no need to check // if callbacks are worklets as the user is aware they will be ran on the JS thread if (gesture.config.runOnJS) { From 7f83166c8fa5314fe8b9a8d56978f1971e96efb0 Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Fri, 10 May 2024 10:48:18 +0200 Subject: [PATCH 20/26] Fix braces --- .../gestures/GestureDetector/index.tsx | 40 ++++++------ .../gestures/GestureDetector/utils.ts | 61 +++++++++---------- 2 files changed, 50 insertions(+), 51 deletions(-) diff --git a/src/handlers/gestures/GestureDetector/index.tsx b/src/handlers/gestures/GestureDetector/index.tsx index 7f2f128c88..3e51917421 100644 --- a/src/handlers/gestures/GestureDetector/index.tsx +++ b/src/handlers/gestures/GestureDetector/index.tsx @@ -213,28 +213,28 @@ export const GestureDetector = (props: GestureDetectorProps) => { if (ref === null) { return; } - // @ts-ignore Just setting the view ref - state.viewRef = ref; - // if it's the first render, also set the previousViewTag to prevent reattaching gestures when not needed - if (state.previousViewTag === -1) { - state.previousViewTag = findNodeHandle(state.viewRef) as number; - } + // @ts-ignore Just setting the view ref + state.viewRef = ref; - // pass true as `skipConfigUpdate`, here we only want to trigger the eventual reattaching of handlers - // in case the view has changed, while config update would be handled be the `useEffect` above - onHandlersUpdate(true); - - if (__DEV__ && isFabric() && global.isFormsStackingContext) { - const node = getShadowNodeFromRef(ref); - if (global.isFormsStackingContext(node) === false) { - console.error( - tagMessage( - 'GestureDetector has received a child that may get view-flattened. ' + - '\nTo prevent it from misbehaving you need to wrap the child with a ``.' - ) - ); - } + // if it's the first render, also set the previousViewTag to prevent reattaching gestures when not needed + if (state.previousViewTag === -1) { + state.previousViewTag = findNodeHandle(state.viewRef) as number; + } + + // pass true as `skipConfigUpdate`, here we only want to trigger the eventual reattaching of handlers + // in case the view has changed, while config update would be handled be the `useEffect` above + onHandlersUpdate(true); + + if (__DEV__ && isFabric() && global.isFormsStackingContext) { + const node = getShadowNodeFromRef(ref); + if (global.isFormsStackingContext(node) === false) { + console.error( + tagMessage( + 'GestureDetector has received a child that may get view-flattened. ' + + '\nTo prevent it from misbehaving you need to wrap the child with a ``.' + ) + ); } } }; diff --git a/src/handlers/gestures/GestureDetector/utils.ts b/src/handlers/gestures/GestureDetector/utils.ts index 6ead800fa3..5e20008a82 100644 --- a/src/handlers/gestures/GestureDetector/utils.ts +++ b/src/handlers/gestures/GestureDetector/utils.ts @@ -67,40 +67,39 @@ export function checkGestureCallbacksForWorklets(gesture: GestureType) { if (!__DEV__) { return; } - // if a gesture is explicitly marked to run on the JS thread there is no need to check - // if callbacks are worklets as the user is aware they will be ran on the JS thread - if (gesture.config.runOnJS) { - return; - } + // if a gesture is explicitly marked to run on the JS thread there is no need to check + // if callbacks are worklets as the user is aware they will be ran on the JS thread + if (gesture.config.runOnJS) { + return; + } - const areSomeNotWorklets = gesture.handlers.isWorklet.includes(false); - const areSomeWorklets = gesture.handlers.isWorklet.includes(true); - - // if some of the callbacks are worklets and some are not, and the gesture is not - // explicitly marked with `.runOnJS(true)` show an error - if (areSomeNotWorklets && areSomeWorklets) { - console.error( - tagMessage( - `Some of the callbacks in the gesture are worklets and some are not. Either make sure that all calbacks are marked as 'worklet' if you wish to run them on the UI thread or use '.runOnJS(true)' modifier on the gesture explicitly to run all callbacks on the JS thread.` - ) - ); - } + const areSomeNotWorklets = gesture.handlers.isWorklet.includes(false); + const areSomeWorklets = gesture.handlers.isWorklet.includes(true); + + // if some of the callbacks are worklets and some are not, and the gesture is not + // explicitly marked with `.runOnJS(true)` show an error + if (areSomeNotWorklets && areSomeWorklets) { + console.error( + tagMessage( + `Some of the callbacks in the gesture are worklets and some are not. Either make sure that all calbacks are marked as 'worklet' if you wish to run them on the UI thread or use '.runOnJS(true)' modifier on the gesture explicitly to run all callbacks on the JS thread.` + ) + ); + } - if (Reanimated === undefined) { - // if Reanimated is not available, we can't run worklets, so we shouldn't show the warning - return; - } + if (Reanimated === undefined) { + // if Reanimated is not available, we can't run worklets, so we shouldn't show the warning + return; + } - const areAllNotWorklets = !areSomeWorklets && areSomeNotWorklets; - // if none of the callbacks are worklets and the gesture is not explicitly marked with - // `.runOnJS(true)` show a warning - if (areAllNotWorklets) { - console.warn( - tagMessage( - `None of the callbacks in the gesture are worklets. If you wish to run them on the JS thread use '.runOnJS(true)' modifier on the gesture to make this explicit. Otherwise, mark the callbacks as 'worklet' to run them on the UI thread.` - ) - ); - } + const areAllNotWorklets = !areSomeWorklets && areSomeNotWorklets; + // if none of the callbacks are worklets and the gesture is not explicitly marked with + // `.runOnJS(true)` show a warning + if (areAllNotWorklets) { + console.warn( + tagMessage( + `None of the callbacks in the gesture are worklets. If you wish to run them on the JS thread use '.runOnJS(true)' modifier on the gesture to make this explicit. Otherwise, mark the callbacks as 'worklet' to run them on the UI thread.` + ) + ); } } From 007a0d94b4163c73a23833d525e501c1f3f9c6b5 Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Fri, 10 May 2024 10:57:06 +0200 Subject: [PATCH 21/26] More renames, move web handlers creation to hook --- .../gestures/GestureDetector/index.tsx | 43 ++++++++----------- .../gestures/GestureDetector/utils.ts | 23 +++++++++- 2 files changed, 40 insertions(+), 26 deletions(-) diff --git a/src/handlers/gestures/GestureDetector/index.tsx b/src/handlers/gestures/GestureDetector/index.tsx index 3e51917421..6fdfee919c 100644 --- a/src/handlers/gestures/GestureDetector/index.tsx +++ b/src/handlers/gestures/GestureDetector/index.tsx @@ -3,7 +3,6 @@ import React, { useContext, useEffect, useRef } from 'react'; import { GestureType } from '../gesture'; import { findNodeHandle, - HandlerStateChangeEvent, UserSelect, TouchAction, } from '../../gestureHandlerCommon'; @@ -11,17 +10,19 @@ import { ComposedGesture } from '../gestureComposition'; import { isFabric, isJestEnv, tagMessage } from '../../../utils'; import { getShadowNodeFromRef } from '../../../getShadowNodeFromRef'; import { Platform } from 'react-native'; -import { onGestureHandlerEvent } from '../eventReceiver'; -import { isNewWebImplementationEnabled } from '../../../EnableNewWebImplementation'; import GestureHandlerRootViewContext from '../../../GestureHandlerRootViewContext'; -import { AttachedGestureState, WebEventHandler } from './types'; +import { AttachedGestureState } from './types'; import { useAnimatedGesture } from './useAnimatedGesture'; import { attachHandlers } from './attachHandlers'; import { updateHandlers } from './updateHandlers'; import { needsToReattach } from './needsToReattach'; import { dropHandlers } from './dropHandlers'; -import { useForceRender, validateDetectorChildren } from './utils'; +import { + useForceRender, + useWebEventHandlers, + validateDetectorChildren, +} from './utils'; import { Wrap, AnimatedWrap } from './Wrap'; declare const global: { @@ -84,7 +85,7 @@ interface GestureDetectorState { firstRender: boolean; viewRef: React.Component | null; previousViewTag: number; - forceReattach: boolean; + forceRebuildReanimatedEvent: boolean; } /** @@ -110,6 +111,7 @@ export const GestureDetector = (props: GestureDetectorProps) => { ); } const forceRender = useForceRender(); + const webEventHandlersRef = useWebEventHandlers(); const gestureConfig = props.gesture; propagateDetectorConfig(props, gestureConfig); @@ -124,18 +126,8 @@ export const GestureDetector = (props: GestureDetectorProps) => { firstRender: true, viewRef: null, previousViewTag: -1, - forceReattach: false, + forceRebuildReanimatedEvent: false, }).current; - const webEventHandlersRef = useRef({ - onGestureHandlerEvent: (e: HandlerStateChangeEvent) => { - onGestureHandlerEvent(e.nativeEvent); - }, - onGestureHandlerStateChange: isNewWebImplementationEnabled() - ? (e: HandlerStateChangeEvent) => { - onGestureHandlerEvent(e.nativeEvent); - } - : undefined, - }); const preparedGesture = React.useRef({ attachedGestures: [], @@ -150,17 +142,20 @@ export const GestureDetector = (props: GestureDetectorProps) => { const needsToRebuildReanimatedEvent = state.firstRender || needsToReattach(preparedGesture, gesturesToAttach) || - state.forceReattach; - state.forceReattach = false; + state.forceRebuildReanimatedEvent; + state.forceRebuildReanimatedEvent = false; useAnimatedGesture(preparedGesture, needsToRebuildReanimatedEvent); function onHandlersUpdate(skipConfigUpdate?: boolean) { // if the underlying view has changed we need to reattach handlers to the new view const viewTag = findNodeHandle(state.viewRef) as number; - const forceReattach = viewTag !== state.previousViewTag; + const didUnderlyingViewChange = viewTag !== state.previousViewTag; - if (forceReattach || needsToReattach(preparedGesture, gesturesToAttach)) { + if ( + didUnderlyingViewChange || + needsToReattach(preparedGesture, gesturesToAttach) + ) { validateDetectorChildren(state.viewRef); dropHandlers(preparedGesture); attachHandlers({ @@ -171,9 +166,9 @@ export const GestureDetector = (props: GestureDetectorProps) => { viewTag, }); - state.previousViewTag = viewTag; - state.forceReattach = forceReattach; - if (forceReattach) { + if (didUnderlyingViewChange) { + state.previousViewTag = viewTag; + state.forceRebuildReanimatedEvent = true; forceRender(); } } else if (!skipConfigUpdate) { diff --git a/src/handlers/gestures/GestureDetector/utils.ts b/src/handlers/gestures/GestureDetector/utils.ts index 5e20008a82..9ff719ce28 100644 --- a/src/handlers/gestures/GestureDetector/utils.ts +++ b/src/handlers/gestures/GestureDetector/utils.ts @@ -13,11 +13,17 @@ import { import { tapGestureHandlerProps } from '../../TapGestureHandler'; import { hoverGestureHandlerProps } from '../hoverGesture'; import { nativeViewGestureHandlerProps } from '../../NativeViewGestureHandler'; -import { baseGestureHandlerWithDetectorProps } from '../../gestureHandlerCommon'; +import { + HandlerStateChangeEvent, + baseGestureHandlerWithDetectorProps, +} from '../../gestureHandlerCommon'; +import { isNewWebImplementationEnabled } from '../../../EnableNewWebImplementation'; import { getReactNativeVersion } from '../../../getReactNativeVersion'; import { RNRenderer } from '../../../RNRenderer'; -import { useCallback, useState } from 'react'; +import { useCallback, useRef, useState } from 'react'; import { Reanimated } from '../reanimatedWrapper'; +import { onGestureHandlerEvent } from '../eventReceiver'; +import { WebEventHandler } from './types'; export const ALLOWED_PROPS = [ ...baseGestureHandlerWithDetectorProps, @@ -164,3 +170,16 @@ export function useForceRender() { return forceRender; } + +export function useWebEventHandlers() { + return useRef({ + onGestureHandlerEvent: (e: HandlerStateChangeEvent) => { + onGestureHandlerEvent(e.nativeEvent); + }, + onGestureHandlerStateChange: isNewWebImplementationEnabled() + ? (e: HandlerStateChangeEvent) => { + onGestureHandlerEvent(e.nativeEvent); + } + : undefined, + }); +} From 040e385990c492be86463cbe674cfc4b4db93c31 Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Fri, 10 May 2024 11:03:40 +0200 Subject: [PATCH 22/26] Add comment abount `blockHandler` --- src/handlers/gestures/gestureComposition.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/handlers/gestures/gestureComposition.ts b/src/handlers/gestures/gestureComposition.ts index cea793aa1a..d790c9c926 100644 --- a/src/handlers/gestures/gestureComposition.ts +++ b/src/handlers/gestures/gestureComposition.ts @@ -29,6 +29,8 @@ export class ComposedGesture extends Gesture { if (gesture instanceof BaseGesture) { const newConfig = { ...gesture.config }; + // no need to extend `blocksHandlers` here, because it's not changed in composition + // the same effect is achieved by reversing the order of 2 gestures in `Exclusive` newConfig.simultaneousWith = extendRelation( newConfig.simultaneousWith, simultaneousGestures From 9ad420bb11521b4dd6bb53b472fb7bad64e98a5c Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Fri, 10 May 2024 11:48:09 +0200 Subject: [PATCH 23/26] Extract more logic into hooks --- .../gestures/GestureDetector/index.tsx | 105 ++++-------------- .../gestures/GestureDetector/types.ts | 7 ++ .../GestureDetector/useDetectorUpdater.ts | 69 ++++++++++++ .../GestureDetector/useViewRefHandler.ts | 55 +++++++++ 4 files changed, 150 insertions(+), 86 deletions(-) create mode 100644 src/handlers/gestures/GestureDetector/useDetectorUpdater.ts create mode 100644 src/handlers/gestures/GestureDetector/useViewRefHandler.ts diff --git a/src/handlers/gestures/GestureDetector/index.tsx b/src/handlers/gestures/GestureDetector/index.tsx index 6fdfee919c..e77ee52e17 100644 --- a/src/handlers/gestures/GestureDetector/index.tsx +++ b/src/handlers/gestures/GestureDetector/index.tsx @@ -7,27 +7,19 @@ import { TouchAction, } from '../../gestureHandlerCommon'; import { ComposedGesture } from '../gestureComposition'; -import { isFabric, isJestEnv, tagMessage } from '../../../utils'; -import { getShadowNodeFromRef } from '../../../getShadowNodeFromRef'; +import { isJestEnv } from '../../../utils'; import { Platform } from 'react-native'; import GestureHandlerRootViewContext from '../../../GestureHandlerRootViewContext'; -import { AttachedGestureState } from './types'; +import { AttachedGestureState, GestureDetectorState } from './types'; import { useAnimatedGesture } from './useAnimatedGesture'; import { attachHandlers } from './attachHandlers'; -import { updateHandlers } from './updateHandlers'; import { needsToReattach } from './needsToReattach'; import { dropHandlers } from './dropHandlers'; -import { - useForceRender, - useWebEventHandlers, - validateDetectorChildren, -} from './utils'; +import { useWebEventHandlers } from './utils'; import { Wrap, AnimatedWrap } from './Wrap'; - -declare const global: { - isFormsStackingContext: (node: unknown) => boolean | null; // JSI function -}; +import { useDetectorUpdater } from './useDetectorUpdater'; +import { useViewRefHandler } from './useViewRefHandler'; function propagateDetectorConfig( props: GestureDetectorProps, @@ -81,13 +73,6 @@ interface GestureDetectorProps { touchAction?: TouchAction; } -interface GestureDetectorState { - firstRender: boolean; - viewRef: React.Component | null; - previousViewTag: number; - forceRebuildReanimatedEvent: boolean; -} - /** * `GestureDetector` is responsible for creating and updating native gesture handlers based on the config of provided gesture. * @@ -110,8 +95,6 @@ export const GestureDetector = (props: GestureDetectorProps) => { 'GestureDetector must be used as a descendant of GestureHandlerRootView. Otherwise the gestures will not be recognized. See https://docs.swmansion.com/react-native-gesture-handler/docs/installation for more details.' ); } - const forceRender = useForceRender(); - const webEventHandlersRef = useWebEventHandlers(); const gestureConfig = props.gesture; propagateDetectorConfig(props, gestureConfig); @@ -121,6 +104,7 @@ export const GestureDetector = (props: GestureDetectorProps) => { (g) => g.shouldUseReanimated ); + const webEventHandlersRef = useWebEventHandlers(); // store state in ref to prevent unnecessary renders const state = useRef({ firstRender: true, @@ -137,6 +121,16 @@ export const GestureDetector = (props: GestureDetectorProps) => { isMounted: false, }).current; + const updateAttachedGestures = useDetectorUpdater( + state, + preparedGesture, + gesturesToAttach, + gestureConfig, + webEventHandlersRef + ); + + const refHandler = useViewRefHandler(state, updateAttachedGestures); + // Reanimated event should be rebuilt only when gestures are reattached, otherwise // config update will be enough as all necessary items are stored in shared values anyway const needsToRebuildReanimatedEvent = @@ -147,41 +141,10 @@ export const GestureDetector = (props: GestureDetectorProps) => { useAnimatedGesture(preparedGesture, needsToRebuildReanimatedEvent); - function onHandlersUpdate(skipConfigUpdate?: boolean) { - // if the underlying view has changed we need to reattach handlers to the new view - const viewTag = findNodeHandle(state.viewRef) as number; - const didUnderlyingViewChange = viewTag !== state.previousViewTag; - - if ( - didUnderlyingViewChange || - needsToReattach(preparedGesture, gesturesToAttach) - ) { - validateDetectorChildren(state.viewRef); - dropHandlers(preparedGesture); - attachHandlers({ - preparedGesture, - gestureConfig, - gesturesToAttach, - webEventHandlersRef, - viewTag, - }); - - if (didUnderlyingViewChange) { - state.previousViewTag = viewTag; - state.forceRebuildReanimatedEvent = true; - forceRender(); - } - } else if (!skipConfigUpdate) { - updateHandlers(preparedGesture, gestureConfig, gesturesToAttach); - } - } - useEffect(() => { const viewTag = findNodeHandle(state.viewRef) as number; preparedGesture.isMounted = true; - validateDetectorChildren(state.viewRef); - attachHandlers({ preparedGesture, gestureConfig, @@ -198,51 +161,21 @@ export const GestureDetector = (props: GestureDetectorProps) => { useEffect(() => { if (!state.firstRender) { - onHandlersUpdate(); + updateAttachedGestures(); } else { state.firstRender = false; } }, [props]); - const refFunction = (ref: unknown) => { - if (ref === null) { - return; - } - - // @ts-ignore Just setting the view ref - state.viewRef = ref; - - // if it's the first render, also set the previousViewTag to prevent reattaching gestures when not needed - if (state.previousViewTag === -1) { - state.previousViewTag = findNodeHandle(state.viewRef) as number; - } - - // pass true as `skipConfigUpdate`, here we only want to trigger the eventual reattaching of handlers - // in case the view has changed, while config update would be handled be the `useEffect` above - onHandlersUpdate(true); - - if (__DEV__ && isFabric() && global.isFormsStackingContext) { - const node = getShadowNodeFromRef(ref); - if (global.isFormsStackingContext(node) === false) { - console.error( - tagMessage( - 'GestureDetector has received a child that may get view-flattened. ' + - '\nTo prevent it from misbehaving you need to wrap the child with a ``.' - ) - ); - } - } - }; - if (shouldUseReanimated) { return ( {props.children} ); } else { - return {props.children}; + return {props.children}; } }; diff --git a/src/handlers/gestures/GestureDetector/types.ts b/src/handlers/gestures/GestureDetector/types.ts index f843b2c61d..3b161ad137 100644 --- a/src/handlers/gestures/GestureDetector/types.ts +++ b/src/handlers/gestures/GestureDetector/types.ts @@ -17,6 +17,13 @@ export interface AttachedGestureState { isMounted: boolean; } +export interface GestureDetectorState { + firstRender: boolean; + viewRef: React.Component | null; + previousViewTag: number; + forceRebuildReanimatedEvent: boolean; +} + export interface WebEventHandler { onGestureHandlerEvent: (event: HandlerStateChangeEvent) => void; onGestureHandlerStateChange?: ( diff --git a/src/handlers/gestures/GestureDetector/useDetectorUpdater.ts b/src/handlers/gestures/GestureDetector/useDetectorUpdater.ts new file mode 100644 index 0000000000..dbbb362ddd --- /dev/null +++ b/src/handlers/gestures/GestureDetector/useDetectorUpdater.ts @@ -0,0 +1,69 @@ +import React, { useCallback } from 'react'; +import { GestureType } from '../gesture'; +import { findNodeHandle } from '../../gestureHandlerCommon'; +import { ComposedGesture } from '../gestureComposition'; + +import { + AttachedGestureState, + GestureDetectorState, + WebEventHandler, +} from './types'; +import { attachHandlers } from './attachHandlers'; +import { updateHandlers } from './updateHandlers'; +import { needsToReattach } from './needsToReattach'; +import { dropHandlers } from './dropHandlers'; +import { useForceRender, validateDetectorChildren } from './utils'; + +// Returns a function that's responsible for updating the attached gestures +// If the view has changed, it will reattach the handlers to the new view +// If the view remains the same, it will update the handlers with the new config +export function useDetectorUpdater( + state: GestureDetectorState, + preparedGesture: AttachedGestureState, + gesturesToAttach: GestureType[], + gestureConfig: ComposedGesture | GestureType, + webEventHandlersRef: React.RefObject +) { + const forceRender = useForceRender(); + const updateAttachedGestures = useCallback( + // skipConfigUpdate is used to prevent unnecessary updates when only checking if the view has changed + (skipConfigUpdate?: boolean) => { + // if the underlying view has changed we need to reattach handlers to the new view + const viewTag = findNodeHandle(state.viewRef) as number; + const didUnderlyingViewChange = viewTag !== state.previousViewTag; + + if ( + didUnderlyingViewChange || + needsToReattach(preparedGesture, gesturesToAttach) + ) { + validateDetectorChildren(state.viewRef); + dropHandlers(preparedGesture); + attachHandlers({ + preparedGesture, + gestureConfig, + gesturesToAttach, + webEventHandlersRef, + viewTag, + }); + + if (didUnderlyingViewChange) { + state.previousViewTag = viewTag; + state.forceRebuildReanimatedEvent = true; + forceRender(); + } + } else if (!skipConfigUpdate) { + updateHandlers(preparedGesture, gestureConfig, gesturesToAttach); + } + }, + [ + forceRender, + gestureConfig, + gesturesToAttach, + preparedGesture, + state, + webEventHandlersRef, + ] + ); + + return updateAttachedGestures; +} diff --git a/src/handlers/gestures/GestureDetector/useViewRefHandler.ts b/src/handlers/gestures/GestureDetector/useViewRefHandler.ts new file mode 100644 index 0000000000..0ca6b4e81e --- /dev/null +++ b/src/handlers/gestures/GestureDetector/useViewRefHandler.ts @@ -0,0 +1,55 @@ +import { findNodeHandle } from '../../gestureHandlerCommon'; +import { validateDetectorChildren } from './utils'; + +import { isFabric, tagMessage } from '../../../utils'; +import { getShadowNodeFromRef } from '../../../getShadowNodeFromRef'; + +import { GestureDetectorState } from './types'; +import React, { useCallback } from 'react'; + +declare const global: { + isFormsStackingContext: (node: unknown) => boolean | null; // JSI function +}; + +// Ref handler for the Wrap component attached under the GestureDetector. +// It's responsible for setting the viewRef on the state and triggering the reattaching of handlers +// if the view has changed. +export function useViewRefHandler( + state: GestureDetectorState, + updateAttachedGestures: (skipConfigUpdate?: boolean) => void +) { + const refHandler = useCallback( + (ref: React.Component | null) => { + if (ref === null) { + return; + } + + validateDetectorChildren(ref); + state.viewRef = ref; + + // if it's the first render, also set the previousViewTag to prevent reattaching gestures when not needed + if (state.previousViewTag === -1) { + state.previousViewTag = findNodeHandle(state.viewRef) as number; + } + + // Pass true as `skipConfigUpdate`. Here we only want to trigger the eventual reattaching of handlers + // in case the view has changed. If the view doesn't change, the update will be handled by detector. + updateAttachedGestures(true); + + if (__DEV__ && isFabric() && global.isFormsStackingContext) { + const node = getShadowNodeFromRef(ref); + if (global.isFormsStackingContext(node) === false) { + console.error( + tagMessage( + 'GestureDetector has received a child that may get view-flattened. ' + + '\nTo prevent it from misbehaving you need to wrap the child with a ``.' + ) + ); + } + } + }, + [state, updateAttachedGestures] + ); + + return refHandler; +} From d114ce455a107fac95d4cb71346d55e25200782f Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Fri, 10 May 2024 12:24:15 +0200 Subject: [PATCH 24/26] Small optimizations --- src/handlers/gestures/GestureDetector/index.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/handlers/gestures/GestureDetector/index.tsx b/src/handlers/gestures/GestureDetector/index.tsx index e77ee52e17..dea678b57c 100644 --- a/src/handlers/gestures/GestureDetector/index.tsx +++ b/src/handlers/gestures/GestureDetector/index.tsx @@ -1,5 +1,5 @@ /* eslint-disable react/no-unused-prop-types */ -import React, { useContext, useEffect, useRef } from 'react'; +import React, { useContext, useEffect, useMemo, useRef } from 'react'; import { GestureType } from '../gesture'; import { findNodeHandle, @@ -96,10 +96,14 @@ export const GestureDetector = (props: GestureDetectorProps) => { ); } + // Gesture config should be wrapped with useMemo to prevent unnecessary re-renders const gestureConfig = props.gesture; propagateDetectorConfig(props, gestureConfig); - const gesturesToAttach = gestureConfig.toGestureArray(); + const gesturesToAttach = useMemo( + () => gestureConfig.toGestureArray(), + [gestureConfig] + ); const shouldUseReanimated = gesturesToAttach.some( (g) => g.shouldUseReanimated ); @@ -135,8 +139,8 @@ export const GestureDetector = (props: GestureDetectorProps) => { // config update will be enough as all necessary items are stored in shared values anyway const needsToRebuildReanimatedEvent = state.firstRender || - needsToReattach(preparedGesture, gesturesToAttach) || - state.forceRebuildReanimatedEvent; + state.forceRebuildReanimatedEvent || + needsToReattach(preparedGesture, gesturesToAttach); state.forceRebuildReanimatedEvent = false; useAnimatedGesture(preparedGesture, needsToRebuildReanimatedEvent); From bab8167a43bba77ce5c844616fb6a20dbb4eef6c Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Tue, 14 May 2024 12:11:25 +0200 Subject: [PATCH 25/26] Flatten ifs --- .../GestureDetector/useAnimatedGesture.ts | 112 +++++++++--------- 1 file changed, 57 insertions(+), 55 deletions(-) diff --git a/src/handlers/gestures/GestureDetector/useAnimatedGesture.ts b/src/handlers/gestures/GestureDetector/useAnimatedGesture.ts index f9ab90ed54..c1cce09307 100644 --- a/src/handlers/gestures/GestureDetector/useAnimatedGesture.ts +++ b/src/handlers/gestures/GestureDetector/useAnimatedGesture.ts @@ -128,65 +128,67 @@ export function useAnimatedGesture( for (let i = 0; i < currentCallback.length; i++) { const gesture = currentCallback[i]; - if (event.handlerTag === gesture.handlerTag) { - if (isStateChangeEvent(event)) { - if ( - event.oldState === State.UNDETERMINED && - event.state === State.BEGAN - ) { - runWorklet(CALLBACK_TYPE.BEGAN, gesture, event); - } else if ( - (event.oldState === State.BEGAN || - event.oldState === State.UNDETERMINED) && - event.state === State.ACTIVE - ) { - runWorklet(CALLBACK_TYPE.START, gesture, event); - lastUpdateEvent.value[gesture.handlerTag] = undefined; - } else if ( - event.oldState !== event.state && - event.state === State.END - ) { - if (event.oldState === State.ACTIVE) { - runWorklet(CALLBACK_TYPE.END, gesture, event, true); - } - runWorklet(CALLBACK_TYPE.FINALIZE, gesture, event, true); - } else if ( - (event.state === State.FAILED || event.state === State.CANCELLED) && - event.state !== event.oldState - ) { - if (event.oldState === State.ACTIVE) { - runWorklet(CALLBACK_TYPE.END, gesture, event, false); - } - runWorklet(CALLBACK_TYPE.FINALIZE, gesture, event, false); + if (event.handlerTag !== gesture.handlerTag) { + continue; + } + + if (isStateChangeEvent(event)) { + if ( + event.oldState === State.UNDETERMINED && + event.state === State.BEGAN + ) { + runWorklet(CALLBACK_TYPE.BEGAN, gesture, event); + } else if ( + (event.oldState === State.BEGAN || + event.oldState === State.UNDETERMINED) && + event.state === State.ACTIVE + ) { + runWorklet(CALLBACK_TYPE.START, gesture, event); + lastUpdateEvent.value[gesture.handlerTag] = undefined; + } else if ( + event.oldState !== event.state && + event.state === State.END + ) { + if (event.oldState === State.ACTIVE) { + runWorklet(CALLBACK_TYPE.END, gesture, event, true); } - } else if (isTouchEvent(event)) { - if (!stateControllers[i]) { - stateControllers[i] = GestureStateManager.create(event.handlerTag); + runWorklet(CALLBACK_TYPE.FINALIZE, gesture, event, true); + } else if ( + (event.state === State.FAILED || event.state === State.CANCELLED) && + event.state !== event.oldState + ) { + if (event.oldState === State.ACTIVE) { + runWorklet(CALLBACK_TYPE.END, gesture, event, false); } + runWorklet(CALLBACK_TYPE.FINALIZE, gesture, event, false); + } + } else if (isTouchEvent(event)) { + if (!stateControllers[i]) { + stateControllers[i] = GestureStateManager.create(event.handlerTag); + } - if (event.eventType !== TouchEventType.UNDETERMINED) { - runWorklet( - touchEventTypeToCallbackType(event.eventType), - gesture, + if (event.eventType !== TouchEventType.UNDETERMINED) { + runWorklet( + touchEventTypeToCallbackType(event.eventType), + gesture, + event, + stateControllers[i] + ); + } + } else { + runWorklet(CALLBACK_TYPE.UPDATE, gesture, event); + + if (gesture.onChange && gesture.changeEventCalculator) { + runWorklet( + CALLBACK_TYPE.CHANGE, + gesture, + gesture.changeEventCalculator?.( event, - stateControllers[i] - ); - } - } else { - runWorklet(CALLBACK_TYPE.UPDATE, gesture, event); - - if (gesture.onChange && gesture.changeEventCalculator) { - runWorklet( - CALLBACK_TYPE.CHANGE, - gesture, - gesture.changeEventCalculator?.( - event, - lastUpdateEvent.value[gesture.handlerTag] - ) - ); - - lastUpdateEvent.value[gesture.handlerTag] = event; - } + lastUpdateEvent.value[gesture.handlerTag] + ) + ); + + lastUpdateEvent.value[gesture.handlerTag] = event; } } } From 067a8de773d321f52566698cc6978024c660616a Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Tue, 14 May 2024 14:46:47 +0200 Subject: [PATCH 26/26] Remove validation from ref handler --- src/handlers/gestures/GestureDetector/useViewRefHandler.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/handlers/gestures/GestureDetector/useViewRefHandler.ts b/src/handlers/gestures/GestureDetector/useViewRefHandler.ts index 0ca6b4e81e..363295ffd5 100644 --- a/src/handlers/gestures/GestureDetector/useViewRefHandler.ts +++ b/src/handlers/gestures/GestureDetector/useViewRefHandler.ts @@ -1,5 +1,4 @@ import { findNodeHandle } from '../../gestureHandlerCommon'; -import { validateDetectorChildren } from './utils'; import { isFabric, tagMessage } from '../../../utils'; import { getShadowNodeFromRef } from '../../../getShadowNodeFromRef'; @@ -24,7 +23,6 @@ export function useViewRefHandler( return; } - validateDetectorChildren(ref); state.viewRef = ref; // if it's the first render, also set the previousViewTag to prevent reattaching gestures when not needed