diff --git a/packages/react-native-gesture-handler/android/paper/src/main/java/com/swmansion/gesturehandler/NativeRNGestureHandlerModuleSpec.java b/packages/react-native-gesture-handler/android/paper/src/main/java/com/swmansion/gesturehandler/NativeRNGestureHandlerModuleSpec.java index 2ed12ac36f..df701ab79b 100644 --- a/packages/react-native-gesture-handler/android/paper/src/main/java/com/swmansion/gesturehandler/NativeRNGestureHandlerModuleSpec.java +++ b/packages/react-native-gesture-handler/android/paper/src/main/java/com/swmansion/gesturehandler/NativeRNGestureHandlerModuleSpec.java @@ -57,6 +57,10 @@ public NativeRNGestureHandlerModuleSpec(ReactApplicationContext reactContext) { @DoNotStrip public abstract void updateGestureHandlerConfig(double handlerTag, ReadableMap newConfig); + @ReactMethod + @DoNotStrip + public abstract void configureRelations(double handlerTag, ReadableMap relations); + @ReactMethod @DoNotStrip public abstract void dropGestureHandler(double handlerTag); diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerEvent.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerEvent.kt index 153105fbff..d656a31f01 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerEvent.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerEvent.kt @@ -61,7 +61,8 @@ class RNGestureHandlerEvent private constructor() : Event EVENT_NAME } - override fun canCoalesce() = true + // Unfortunately getCoalescingKey is not considered when sending event to C++, therefore we have to disable coalescing in v3 + override fun canCoalesce() = actionType != GestureHandler.ACTION_TYPE_NATIVE_DETECTOR override fun getCoalescingKey() = coalescingKey diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerModule.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerModule.kt index 3e47d7f5ce..4e3711722b 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerModule.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerModule.kt @@ -96,8 +96,6 @@ class RNGestureHandlerModule(reactContext: ReactApplicationContext?) : val handler = registry.getHandler(handlerTag) ?: return val factory = RNGestureHandlerFactoryUtil.findFactoryForHandler(handler) ?: return - interactionManager.dropRelationsForHandlerWithTag(handlerTag) - interactionManager.configureInteractions(handler, config) factory.setConfig(handler, config) } @@ -110,6 +108,15 @@ class RNGestureHandlerModule(reactContext: ReactApplicationContext?) : factory.updateConfig(handler, config) } + @ReactMethod + override fun configureRelations(handlerTagDouble: Double, relations: ReadableMap) { + val handlerTag = handlerTagDouble.toInt() + val handler = registry.getHandler(handlerTag) ?: return + + interactionManager.dropRelationsForHandlerWithTag(handlerTag) + interactionManager.configureInteractions(handler, relations) + } + @ReactMethod override fun dropGestureHandler(handlerTagDouble: Double) { val handlerTag = handlerTagDouble.toInt() diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandler.h b/packages/react-native-gesture-handler/apple/RNGestureHandler.h index 7958add607..14e325729e 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandler.h +++ b/packages/react-native-gesture-handler/apple/RNGestureHandler.h @@ -88,6 +88,7 @@ - (void)resetConfig NS_REQUIRES_SUPER; - (void)setConfig:(nullable NSDictionary *)config NS_REQUIRES_SUPER; - (void)updateConfig:(nullable NSDictionary *)config NS_REQUIRES_SUPER; +- (void)updateRelations:(nonnull NSDictionary *)relations; - (void)handleGesture:(nonnull id)recognizer; - (void)handleGesture:(nonnull id)recognizer inState:(RNGestureHandlerState)state; - (BOOL)containsPointInView; diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandler.mm b/packages/react-native-gesture-handler/apple/RNGestureHandler.mm index 9db05d11b2..67fbae4a6c 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandler.mm +++ b/packages/react-native-gesture-handler/apple/RNGestureHandler.mm @@ -125,10 +125,6 @@ - (void)setConfig:(NSDictionary *)config - (void)updateConfig:(NSDictionary *)config { - _handlersToWaitFor = [RCTConvert NSNumberArray:config[@"waitFor"]]; - _simultaneousHandlers = [RCTConvert NSNumberArray:config[@"simultaneousHandlers"]]; - _handlersThatShouldWait = [RCTConvert NSNumberArray:config[@"blocksHandlers"]]; - id prop = config[@"enabled"]; if (prop != nil) { self.enabled = [RCTConvert BOOL:prop]; @@ -188,6 +184,13 @@ - (void)updateConfig:(NSDictionary *)config } } +- (void)updateRelations:(NSDictionary *)relations +{ + _handlersToWaitFor = [RCTConvert NSNumberArray:relations[@"waitFor"]]; + _simultaneousHandlers = [RCTConvert NSNumberArray:relations[@"simultaneousHandlers"]]; + _handlersThatShouldWait = [RCTConvert NSNumberArray:relations[@"blocksHandlers"]]; +} + - (void)setEnabled:(BOOL)enabled { _enabled = enabled; diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerManager.h b/packages/react-native-gesture-handler/apple/RNGestureHandlerManager.h index 6240098425..e18368a633 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandlerManager.h +++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerManager.h @@ -33,6 +33,8 @@ - (void)updateGestureHandlerConfig:(nonnull NSNumber *)handlerTag config:(nonnull NSDictionary *)config; +- (void)updateGestureHandlerRelations:(nonnull NSNumber *)handlerTag relations:(nonnull NSDictionary *)relations; + - (void)dropGestureHandler:(nonnull NSNumber *)handlerTag; - (void)dropAllGestureHandlers; diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerManager.mm b/packages/react-native-gesture-handler/apple/RNGestureHandlerManager.mm index a99588cbdd..00a0bb27d8 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandlerManager.mm +++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerManager.mm @@ -217,6 +217,12 @@ - (void)updateGestureHandlerConfig:(NSNumber *)handlerTag config:(NSDictionary * [handler updateConfig:config]; } +- (void)updateGestureHandlerRelations:(NSNumber *)handlerTag relations:(NSDictionary *)relations +{ + RNGestureHandler *handler = [_registry handlerWithTag:handlerTag]; + [handler updateRelations:relations]; +} + - (void)dropGestureHandler:(NSNumber *)handlerTag { [_registry dropHandlerWithTag:handlerTag]; diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.mm b/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.mm index d0b9a2573b..40b6669536 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.mm +++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerModule.mm @@ -188,6 +188,12 @@ - (void)updateGestureHandlerConfig:(double)handlerTag newConfig:(NSDictionary *) [manager updateGestureHandlerConfig:[NSNumber numberWithDouble:handlerTag] config:config]; } +- (void)configureRelations:(double)handlerTag relations:(NSDictionary *)relations +{ + RNGestureHandlerManager *manager = [RNGestureHandlerModule handlerManagerForModuleId:_moduleId]; + [manager updateGestureHandlerRelations:[NSNumber numberWithDouble:handlerTag] relations:relations]; +} + - (void)dropGestureHandler:(double)handlerTag { [self addOperationBlock:^(RNGestureHandlerManager *manager) { diff --git a/packages/react-native-gesture-handler/src/index.ts b/packages/react-native-gesture-handler/src/index.ts index 1f6ad3b622..3b54e09b2c 100644 --- a/packages/react-native-gesture-handler/src/index.ts +++ b/packages/react-native-gesture-handler/src/index.ts @@ -162,9 +162,14 @@ export type { } from './components/DrawerLayout'; export { default as DrawerLayout } from './components/DrawerLayout'; -export type { NativeDetectorProps } from './v3/NativeDetector'; -export { NativeDetector } from './v3/NativeDetector'; +export type { NativeDetectorProps } from './v3/NativeDetector/NativeDetector'; +export { NativeDetector } from './v3/NativeDetector/NativeDetector'; export * from './v3/hooks/useGesture'; +export * from './v3/hooks/relations/useSimultaneous'; +export * from './v3/hooks/relations/useExclusive'; +export * from './v3/hooks/relations/useRace'; + +export { HandlerType } from './v3/types'; initialize(); diff --git a/packages/react-native-gesture-handler/src/specs/NativeRNGestureHandlerModule.ts b/packages/react-native-gesture-handler/src/specs/NativeRNGestureHandlerModule.ts index f3d3ae181a..25cf9733dd 100644 --- a/packages/react-native-gesture-handler/src/specs/NativeRNGestureHandlerModule.ts +++ b/packages/react-native-gesture-handler/src/specs/NativeRNGestureHandlerModule.ts @@ -22,6 +22,8 @@ export interface Spec extends TurboModule { setGestureHandlerConfig: (handlerTag: Double, newConfig: Object) => void; // eslint-disable-next-line @typescript-eslint/ban-types updateGestureHandlerConfig: (handlerTag: Double, newConfig: Object) => void; + // eslint-disable-next-line @typescript-eslint/ban-types + configureRelations: (handlerTag: Double, relations: Object) => void; dropGestureHandler: (handlerTag: Double) => void; flushOperations: () => void; } diff --git a/packages/react-native-gesture-handler/src/v3/HostGestureDetector.tsx b/packages/react-native-gesture-handler/src/v3/HostGestureDetector.tsx deleted file mode 100644 index 1bc9860d9a..0000000000 --- a/packages/react-native-gesture-handler/src/v3/HostGestureDetector.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import RNGestureHandlerDetectorNativeComponent from '../specs/RNGestureHandlerDetectorNativeComponent'; -const HostGestureDetector = RNGestureHandlerDetectorNativeComponent; -export default HostGestureDetector; diff --git a/packages/react-native-gesture-handler/src/v3/NativeDetector/HostGestureDetector.tsx b/packages/react-native-gesture-handler/src/v3/NativeDetector/HostGestureDetector.tsx new file mode 100644 index 0000000000..5b91d456e2 --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/NativeDetector/HostGestureDetector.tsx @@ -0,0 +1,3 @@ +import RNGestureHandlerDetectorNativeComponent from '../../specs/RNGestureHandlerDetectorNativeComponent'; +const HostGestureDetector = RNGestureHandlerDetectorNativeComponent; +export default HostGestureDetector; diff --git a/packages/react-native-gesture-handler/src/v3/HostGestureDetector.web.tsx b/packages/react-native-gesture-handler/src/v3/NativeDetector/HostGestureDetector.web.tsx similarity index 91% rename from packages/react-native-gesture-handler/src/v3/HostGestureDetector.web.tsx rename to packages/react-native-gesture-handler/src/v3/NativeDetector/HostGestureDetector.web.tsx index 8f193f10b8..b6819cc543 100644 --- a/packages/react-native-gesture-handler/src/v3/HostGestureDetector.web.tsx +++ b/packages/react-native-gesture-handler/src/v3/NativeDetector/HostGestureDetector.web.tsx @@ -1,9 +1,9 @@ import { Ref, useEffect, useRef } from 'react'; -import RNGestureHandlerModule from '../RNGestureHandlerModule.web'; -import { ActionType } from '../ActionType'; -import { PropsRef } from '../web/interfaces'; +import RNGestureHandlerModule from '../../RNGestureHandlerModule.web'; +import { ActionType } from '../../ActionType'; +import { PropsRef } from '../../web/interfaces'; import { View } from 'react-native'; -import { tagMessage } from '../utils'; +import { tagMessage } from '../../utils'; export interface GestureHandlerDetectorProps extends PropsRef { handlerTags: number[]; diff --git a/packages/react-native-gesture-handler/src/v3/NativeDetector.tsx b/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx similarity index 71% rename from packages/react-native-gesture-handler/src/v3/NativeDetector.tsx rename to packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx index 22d347d1f5..f747837a7a 100644 --- a/packages/react-native-gesture-handler/src/v3/NativeDetector.tsx +++ b/packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx @@ -1,14 +1,16 @@ import React from 'react'; -import { NativeGesture } from './hooks/useGesture'; -import { Reanimated } from '../handlers/gestures/reanimatedWrapper'; +import { NativeGesture, ComposedGesture, ComposedGestureType } from '../types'; +import { Reanimated } from '../../handlers/gestures/reanimatedWrapper'; import { Animated, StyleSheet } from 'react-native'; import HostGestureDetector from './HostGestureDetector'; -import { tagMessage } from '../utils'; +import { tagMessage } from '../../utils'; +import { isComposedGesture } from '../hooks/utils'; +import { dfs } from './utils'; export interface NativeDetectorProps { children?: React.ReactNode; - gesture: NativeGesture; + gesture: NativeGesture | ComposedGesture; } const AnimatedNativeDetector = @@ -34,20 +36,32 @@ export function NativeDetector({ gesture, children }: NativeDetectorProps) { ); } + if ( + isComposedGesture(gesture) && + gesture.type === ComposedGestureType.Simultaneous + ) { + dfs(gesture, new Set(gesture.tags)); + } else { + dfs(gesture); + } + return ( {children} diff --git a/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts b/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts new file mode 100644 index 0000000000..04e860e19d --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/NativeDetector/utils.ts @@ -0,0 +1,115 @@ +// This piece of magic traverses the gesture tree and populates `waitFor` and `simultaneousHandlers` +// arrays for each gesture. It traverses the tree recursively using DFS. +// `waitFor` and `simultaneousHandlers` are global data structures that will be populated into each gesture. +// For `waitFor` we need array as order of the gestures matters. +// For `simultaneousHandlers` we use Set as the order doesn't matter. + +import RNGestureHandlerModule from '../../RNGestureHandlerModule'; +import { isComposedGesture } from '../hooks/utils'; +import { ComposedGesture, ComposedGestureType, NativeGesture } from '../types'; + +// The tree consists of ComposedGestures and NativeGestures. NativeGestures are always leaf nodes. +export const dfs = ( + node: NativeGesture | ComposedGesture, + simultaneousHandlers: Set = new Set(), + waitFor: number[] = [] +) => { + // If we are in the leaf node, we want to fill gesture relations arrays with current + // waitFor and simultaneousHandlers. We also want to configure relations on the native side. + // TODO: handle `simultaneousWithExternalGesture`, `requreExternalGestureToFail`, `blocksExternalGesture` + if (!isComposedGesture(node)) { + node.simultaneousHandlers.push(...simultaneousHandlers); + node.waitFor.push(...waitFor); + + RNGestureHandlerModule.configureRelations(node.tag, { + waitFor, + simultaneousHandlers: Array.from(simultaneousHandlers), + blocksHandlers: node.blocksHandlers || [], // TODO: handle `blocksExternalGesture` + }); + + return; + } + + // If we are in the composed gesture, we want to traverse its children. + node.gestures.forEach((child) => { + // If child is composed gesture, we have to correctly fill `waitFor` and `simultaneousHandlers`. + if (isComposedGesture(child)) { + // We have to update `simultaneousHandlers` before traversing the child. + + // If we go from a non-simultaneous gesture to a simultaneous gesture, + // we add the tags of the simultaneous gesture to the `simultaneousHandlers`. + // This way when we traverse the child, we already have the tags of the simultaneous gestures + if ( + node.type !== ComposedGestureType.Simultaneous && + child.type === ComposedGestureType.Simultaneous + ) { + child.tags.forEach((tag) => simultaneousHandlers.add(tag)); + } + + // If we go from a simultaneous gesture to a non-simultaneous gesture, + // we remove the tags of the child gestures from the `simultaneousHandlers`, + // as those are not simultaneous with each other. + if ( + node.type === ComposedGestureType.Simultaneous && + child.type !== ComposedGestureType.Simultaneous + ) { + child.tags.forEach((tag) => simultaneousHandlers.delete(tag)); + } + + // We will keep the current length of `waitFor` to reset it to previous state + // after traversing the child. + const length = waitFor.length; + + // We traverse the child, passing the current `waitFor` and `simultaneousHandlers`. + dfs(child, simultaneousHandlers, waitFor); + + // After traversing the child, we need to update `waitFor` and `simultaneousHandlers` + + // If we go back from a simultaneous gesture to a non-simultaneous gesture, + // we want to delete the tags of the simultaneous gesture from the `simultaneousHandlers` - + // those gestures are not simultaneous with each other anymore. + if ( + child.type === ComposedGestureType.Simultaneous && + node.type !== ComposedGestureType.Simultaneous + ) { + node.tags.forEach((tag) => simultaneousHandlers.delete(tag)); + } + + // If we go back from a non-simultaneous gesture to a simultaneous gesture, + // we want to add the tags of the simultaneous gesture to the `simultaneousHandlers`, + // as those gestures are simultaneous with other children of the current node. + if ( + child.type !== ComposedGestureType.Simultaneous && + node.type === ComposedGestureType.Simultaneous + ) { + node.tags.forEach((tag) => simultaneousHandlers.add(tag)); + } + + // If we go back to an exclusive gesture, we want to add the tags of the child gesture to the `waitFor` array. + // This will allow us to pass exclusive gesture tags to the right subtree of the current node. + if (node.type === ComposedGestureType.Exclusive) { + child.tags.forEach((tag) => waitFor.push(tag)); + } + + // If we go back from an exclusive gesture to a non-exclusive gesture, we want to reset the `waitFor` array + // to the previous state, siblings of the exclusive gesture are not exclusive with it. Since we use `push` method to + // add tags to the `waitFor` array, we can override `length` property to reset it to the previous state. + if ( + child.type === ComposedGestureType.Exclusive && + node.type !== ComposedGestureType.Exclusive + ) { + waitFor.length = length; + } + } + // This means that child is a leaf node. + else { + // In the leaf node, we only care about filling `waitFor` array. First we traverse the child... + dfs(child, simultaneousHandlers, waitFor); + + // ..and when we go back we add the tag of the child to the `waitFor` array. + if (node.type === ComposedGestureType.Exclusive) { + waitFor.push(child.tag); + } + } + }); +}; diff --git a/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts b/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts new file mode 100644 index 0000000000..243a72b156 --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts @@ -0,0 +1,89 @@ +import { + NativeGesture, + StateChangeEvent, + UpdateEvent, + TouchEvent, + ComposedGesture, + ComposedGestureType, +} from '../../types'; +import { isComposedGesture } from '../utils'; +import { tagMessage } from '../../../utils'; + +// TODO: Simplify repeated relations (Simultaneous with Simultaneous, Exclusive with Exclusive, etc.) +// eslint-disable-next-line @eslint-react/hooks-extra/ensure-custom-hooks-using-other-hooks, @eslint-react/hooks-extra/no-unnecessary-use-prefix +export function useComposedGesture( + ...gestures: (NativeGesture | ComposedGesture)[] +): ComposedGesture { + const tags = gestures.flatMap((gesture) => + isComposedGesture(gesture) ? gesture.tags : gesture.tag + ); + + const config = { + shouldUseReanimated: gestures.some( + (gesture) => gesture.config.shouldUseReanimated + ), + dispatchesAnimatedEvents: gestures.some( + (gesture) => gesture.config.dispatchesAnimatedEvents + ), + }; + + if (config.shouldUseReanimated && config.dispatchesAnimatedEvents) { + throw new Error( + tagMessage( + 'Composed gestures cannot use both Reanimated and Animated events at the same time.' + ) + ); + } + + const onGestureHandlerStateChange = ( + event: StateChangeEvent> + ) => { + for (const gesture of gestures) { + if (gesture.gestureEvents.onGestureHandlerStateChange) { + gesture.gestureEvents.onGestureHandlerStateChange(event); + } + } + }; + + const onGestureHandlerEvent = ( + event: UpdateEvent> + ) => { + for (const gesture of gestures) { + if (gesture.gestureEvents.onGestureHandlerEvent) { + gesture.gestureEvents.onGestureHandlerEvent(event); + } + } + }; + + const onGestureHandlerTouchEvent = (event: TouchEvent) => { + for (const gesture of gestures) { + if (gesture.gestureEvents.onGestureHandlerTouchEvent) { + gesture.gestureEvents.onGestureHandlerTouchEvent(event); + } + } + }; + + let onGestureHandlerAnimatedEvent; + + for (const gesture of gestures) { + if (gesture.gestureEvents.onGestureHandlerAnimatedEvent) { + onGestureHandlerAnimatedEvent = + gesture.gestureEvents.onGestureHandlerAnimatedEvent; + + break; + } + } + + return { + tags, + type: ComposedGestureType.Race, + config, + gestureEvents: { + onGestureHandlerStateChange, + onGestureHandlerEvent, + onGestureHandlerAnimatedEvent, + onGestureHandlerTouchEvent, + }, + gestures, + }; +} diff --git a/packages/react-native-gesture-handler/src/v3/hooks/relations/useExclusive.ts b/packages/react-native-gesture-handler/src/v3/hooks/relations/useExclusive.ts new file mode 100644 index 0000000000..b402c8080a --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/hooks/relations/useExclusive.ts @@ -0,0 +1,14 @@ +import { + NativeGesture, + ComposedGesture, + ComposedGestureType, +} from '../../types'; +import { useComposedGesture } from './useComposedGesture'; + +export function useExclusive(...gestures: (NativeGesture | ComposedGesture)[]) { + const composedGesture = useComposedGesture(...gestures); + + composedGesture.type = ComposedGestureType.Exclusive; + + return composedGesture; +} diff --git a/packages/react-native-gesture-handler/src/v3/hooks/relations/useRace.ts b/packages/react-native-gesture-handler/src/v3/hooks/relations/useRace.ts new file mode 100644 index 0000000000..a04e21b38d --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/hooks/relations/useRace.ts @@ -0,0 +1,6 @@ +import { ComposedGesture, NativeGesture } from '../../types'; +import { useComposedGesture } from './useComposedGesture'; + +export function useRace(...gestures: (NativeGesture | ComposedGesture)[]) { + return useComposedGesture(...gestures); +} diff --git a/packages/react-native-gesture-handler/src/v3/hooks/relations/useSimultaneous.ts b/packages/react-native-gesture-handler/src/v3/hooks/relations/useSimultaneous.ts new file mode 100644 index 0000000000..f046b05ff7 --- /dev/null +++ b/packages/react-native-gesture-handler/src/v3/hooks/relations/useSimultaneous.ts @@ -0,0 +1,16 @@ +import { + ComposedGesture, + ComposedGestureType, + NativeGesture, +} from '../../types'; +import { useComposedGesture } from './useComposedGesture'; + +export function useSimultaneous( + ...gestures: (NativeGesture | ComposedGesture)[] +) { + const composedGesture = useComposedGesture(...gestures); + + composedGesture.type = ComposedGestureType.Simultaneous; + + return composedGesture; +} diff --git a/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts b/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts index 8ab3295414..24c5ef736f 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts @@ -7,32 +7,7 @@ import { SharedValue, } from '../../handlers/gestures/reanimatedWrapper'; import { tagMessage } from '../../utils'; -import { AnimatedEvent } from '../types'; - -type GestureType = - | 'TapGestureHandler' - | 'LongPressGestureHandler' - | 'PanGestureHandler' - | 'PinchGestureHandler' - | 'RotationGestureHandler' - | 'FlingGestureHandler' - | 'ForceTouchGestureHandler' - | 'ManualGestureHandler' - | 'NativeViewGestureHandler'; - -type GestureEvents = { - onGestureHandlerStateChange: (event: any) => void; - onGestureHandlerEvent: undefined | ((event: any) => void); - onGestureHandlerTouchEvent: (event: any) => void; - onGestureHandlerAnimatedEvent: undefined | AnimatedEvent; -}; - -export interface NativeGesture { - tag: number; - name: GestureType; - config: Record; - gestureEvents: GestureEvents; -} +import { HandlerType, NativeGesture } from '../types'; function hasWorkletEventHandlers(config: Record) { return Object.values(config).some( @@ -98,7 +73,7 @@ function unbindSharedValues(config: any, tag: number) { } export function useGesture( - type: GestureType, + type: HandlerType, config: Record ): NativeGesture { const tag = useMemo(() => getNextHandlerTag(), []); @@ -171,8 +146,8 @@ export function useGesture( }, [config, tag]); return { - tag: tag, - name: type, + tag, + type, config, gestureEvents: { onGestureHandlerStateChange, @@ -180,5 +155,8 @@ export function useGesture( onGestureHandlerTouchEvent, onGestureHandlerAnimatedEvent, }, + simultaneousHandlers: [], + waitFor: [], + blocksHandlers: [], }; } diff --git a/packages/react-native-gesture-handler/src/v3/hooks/utils.ts b/packages/react-native-gesture-handler/src/v3/hooks/utils.ts index 7365f5882b..33415e61dc 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/utils.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/utils.ts @@ -7,6 +7,8 @@ import { GestureHandlerEvent, GestureStateChangeEventWithData, GestureUpdateEventWithData, + NativeGesture, + ComposedGesture, } from '../types'; import { GestureTouchEvent } from '../../handlers/gestureHandlerCommon'; import { tagMessage } from '../../utils'; @@ -109,3 +111,9 @@ export function checkMappingForChangeProperties(obj: Animated.Mapping) { } } } + +export function isComposedGesture( + gesture: NativeGesture | ComposedGesture +): gesture is ComposedGesture { + return 'tags' in gesture; +} diff --git a/packages/react-native-gesture-handler/src/v3/types.ts b/packages/react-native-gesture-handler/src/v3/types.ts index 3f04f28a9f..0a882b63cf 100644 --- a/packages/react-native-gesture-handler/src/v3/types.ts +++ b/packages/react-native-gesture-handler/src/v3/types.ts @@ -48,3 +48,60 @@ export type CallbackHandlers = Omit< export type AnimatedEvent = ((...args: any[]) => void) & { _argMapping?: unknown; }; + +export enum SingleGestureType { + Tap = 'TapGestureHandler', + LongPress = 'LongPressGestureHandler', + Pan = 'PanGestureHandler', + Pinch = 'PinchGestureHandler', + Rotation = 'RotationGestureHandler', + Fling = 'FlingGestureHandler', + Manual = 'ManualGestureHandler', + Native = 'NativeGestureHandler', +} + +export enum ComposedGestureType { + Simultaneous = 'SimultaneousGesture', + Exclusive = 'ExclusiveGesture', + Race = 'RaceGesture', +} + +// TODO: Find better name +export type HandlerType = SingleGestureType | ComposedGestureType; + +export type GestureEvents = { + onGestureHandlerStateChange: ( + event: StateChangeEvent> + ) => void; + onGestureHandlerEvent: + | undefined + | ((event: UpdateEvent>) => void); + onGestureHandlerTouchEvent: (event: TouchEvent) => void; + onGestureHandlerAnimatedEvent: undefined | AnimatedEvent; +}; + +export type GestureRelations = { + simultaneousGestures: number[]; + exclusiveGestures: number[]; +}; + +export type NativeGesture = { + tag: number; + type: HandlerType; + config: Record; + gestureEvents: GestureEvents; + simultaneousHandlers: number[]; + waitFor: number[]; + blocksHandlers: number[]; +}; + +export type ComposedGesture = { + tags: number[]; + type: ComposedGestureType; + config: { + shouldUseReanimated: boolean; + dispatchesAnimatedEvents: boolean; + }; + gestureEvents: GestureEvents; + gestures: (NativeGesture | ComposedGesture)[]; +};