Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
121 commits
Select commit Hold shift + click to select a range
7d9de2e
logic detector web
Aug 25, 2025
4ccf49e
avoiding propsRef
Aug 25, 2025
69e1c5d
refactor
Aug 27, 2025
9959d8b
logicChildren in specs
Aug 27, 2025
1f1f681
ios
Sep 1, 2025
a982767
merge with next
Sep 1, 2025
81bf405
fix reanimated version
Sep 1, 2025
a90b83c
yarn lock
Sep 1, 2025
8c03f58
worklets
Sep 1, 2025
a3256d4
detach handlers
Sep 2, 2025
6f45a33
worklets fix
Sep 3, 2025
66390ab
android
Sep 4, 2025
1ef1969
Merge branch 'next' into @akwasniewski/logic-detector
Sep 4, 2025
3379714
yarn lock fix
Sep 4, 2025
e9795cc
removed circular dependency
Sep 4, 2025
7fb8ea9
simplify ios
Sep 5, 2025
5c24151
removed logic events web/android
Sep 8, 2025
593c44f
android and web refactor
Sep 8, 2025
fe1d67d
one path
Sep 8, 2025
b147c7b
merge with next
Sep 8, 2025
4e718b3
merge fix
Sep 8, 2025
8e9d2dc
adjust for reanimated/animated separation
Sep 9, 2025
97b7369
general refactor
Sep 9, 2025
8b6df8c
simplified attaching
Sep 9, 2025
dab9e5f
tag in callback
Sep 10, 2025
089bbf2
not sharing web invoke
Sep 10, 2025
5fbe993
renamed logic props
Sep 10, 2025
ddce1fb
logic animated event
Sep 11, 2025
50c1af4
removed child tag from web
Sep 11, 2025
5f3f1eb
safe detaching
Sep 11, 2025
1f22ef4
typo fix
Sep 11, 2025
3e7bc43
more typos
Sep 11, 2025
cd4d158
comment styling
Sep 12, 2025
164c4e8
moved detector context
Sep 12, 2025
84bdf3d
renamed helper function
Sep 12, 2025
4365e3e
explained invoke method
Sep 12, 2025
33fe818
isV3Api helper function
Sep 12, 2025
6c42eae
renamed logic children
Sep 12, 2025
a11b6d3
renamed var containing logic handlers
Sep 12, 2025
a70f868
action type helper on android
Sep 12, 2025
c0c9bb0
simplify logic children to delete creation
Sep 12, 2025
a2ee13d
removed redundant check
Sep 12, 2025
f0f24b8
renamed parent tag to host detector tag
Sep 12, 2025
f0fb458
removed unnecessary variable
Sep 12, 2025
5e5e917
renamed set
Sep 12, 2025
abcac98
separate function to detach all handlers
Sep 15, 2025
5c367b5
fix testing remnant
Sep 15, 2025
b1d620a
non default useDetectorContext
Sep 15, 2025
157d87b
android action type whitespace
Sep 15, 2025
a1b94d5
clean up extension
Sep 15, 2025
75f218b
removed remnants
Sep 15, 2025
344617a
renamed logic children web
Sep 15, 2025
03b37c7
android whitespace
Sep 15, 2025
9420ead
removed unnecessary annotation
Sep 15, 2025
06bb0e1
whitespace
Sep 15, 2025
474e301
delete obsolete
Sep 15, 2025
1fef44a
Fallthrough comment
Sep 15, 2025
cb9ab7a
defining registry outside of loop
Sep 15, 2025
76fd9ff
merge with next
Sep 15, 2025
2a727cb
cleaner extension function
Sep 16, 2025
e6fbdba
extract repetition
Sep 16, 2025
6fa67e3
moved file
Sep 16, 2025
31fad02
fix bad merge
Sep 16, 2025
c9ffbdb
better error
Sep 16, 2025
f00e0dd
Merge branch 'next' into @akwasniewski/logic-detector
akwasniewski Sep 16, 2025
72e7813
updating logic children
Sep 16, 2025
79f1d96
removed unnecessary return
Sep 16, 2025
86ff725
hostGestureDetector property
Sep 17, 2025
7213108
extracted update logic children
Sep 17, 2025
8015ea5
rename logic children deleton set
Sep 17, 2025
e589043
move invoke detector event
Sep 17, 2025
57b6b57
auto type
Sep 17, 2025
a8fa637
rename isv3api
Sep 17, 2025
468e23f
no put
Sep 17, 2025
63d44f7
simplify mapping
Sep 17, 2025
0b362be
better web detaching
Sep 17, 2025
20038b1
typing
Sep 17, 2025
f3f985c
Merge branch 'next' into @akwasniewski/logic-detector
akwasniewski Sep 17, 2025
832a039
passing helper function directly
Sep 18, 2025
1d96fa1
Merge branch 'next' into @akwasniewski/logic-detector
Sep 18, 2025
590a5cd
resolved workaround
Sep 19, 2025
f25cd9e
Merge branch 'next' into @akwasniewski/logic-detector
Sep 19, 2025
0f58258
empty set array
Sep 22, 2025
93412f4
avoiding indentation
Sep 22, 2025
df8c9b7
reformated native children detach
Sep 22, 2025
0cabeb7
fix logic error
Sep 22, 2025
adc3c22
send event refactor
Sep 29, 2025
149c323
moved empty set to utils
Sep 29, 2025
1bcda3c
fix can coalesce check to v3
Sep 29, 2025
f0ea23f
coalesce check in touch event
Sep 29, 2025
153385c
simplify logic props creation
Oct 1, 2025
765e326
Merge branch 'next' into @akwasniewski/logic-detector
Oct 1, 2025
fae6478
added delegate detector
Oct 3, 2025
f084754
extract common
Oct 3, 2025
c9b90f0
Merge branch 'next' into @akwasniewski/separate-logic-native-detector
akwasniewski Oct 3, 2025
3a994b0
Merge branch 'next' into @akwasniewski/separate-logic-native-detector
akwasniewski Oct 6, 2025
94d9079
merge could have been better
Oct 6, 2025
a9ab3a5
changed folder structure
Oct 6, 2025
743124e
extracted common
Oct 6, 2025
88d4290
renamed detectors folder
Oct 9, 2025
b7e9fcd
detector index
Oct 9, 2025
5ba432e
allowing delegate detector to not have a gesture specified
Oct 10, 2025
72f74a9
configuring relations in logic detector
Oct 10, 2025
113dc37
fix typo
Oct 10, 2025
95510d2
one detector to rule them all
Oct 15, 2025
2965de5
fix typo
Oct 15, 2025
90cf5d5
returning logic detector
Oct 15, 2025
359ca6c
Merge branch 'next' into @akwasniewski/separate-logic-native-detector
Oct 16, 2025
760d5ac
renamed delegate detector
Oct 16, 2025
103f778
renamed to intercepting gesture detector
Oct 28, 2025
eb6e762
not accessing context in v2
Oct 28, 2025
2c7df12
styling
Oct 28, 2025
0de96b3
export sczacher macher
Oct 28, 2025
f59defb
Merge branch 'next' into @akwasniewski/separate-logic-native-detector
Oct 28, 2025
8bcb96f
fix crashes on web
Oct 28, 2025
f7542cb
removed Provider
Oct 28, 2025
a6e6b58
fix ios
Oct 28, 2025
0dbbb2c
update comment
Oct 28, 2025
cc5ac64
finally fix ios
Oct 31, 2025
6b51db5
Merge branch 'next' into @akwasniewski/separate-logic-native-detector
akwasniewski Oct 31, 2025
466bef3
update example
Oct 31, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions apps/basic-example/src/NativeDetector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as React from 'react';
import { Animated, Button, useAnimatedValue } from 'react-native';
import {
GestureHandlerRootView,
NativeDetector,
GestureDetector,
usePan,
} from 'react-native-gesture-handler';

Expand Down Expand Up @@ -32,7 +32,7 @@ export default function App() {
/>

{visible && (
<NativeDetector gesture={gesture}>
<GestureDetector gesture={gesture}>
<Animated.View
style={[
{
Expand All @@ -48,7 +48,7 @@ export default function App() {
{ transform: [{ translateX: value }] },
]}
/>
</NativeDetector>
</GestureDetector>
)}
</GestureHandlerRootView>
);
Expand Down
15 changes: 7 additions & 8 deletions apps/basic-example/src/Text.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import { StyleSheet, Text, View } from 'react-native';
import {
Gesture,
GestureDetector,
LogicDetector,
NativeDetector,
InterceptingGestureDetector,
useTap,
} from 'react-native-gesture-handler';

Expand Down Expand Up @@ -37,24 +36,24 @@ function NativeDetectorExample() {
<Text style={styles.header}>
Native Detector example - this one should work
</Text>
<NativeDetector gesture={tapAll}>
<InterceptingGestureDetector gesture={tapAll}>
<Text style={{ fontSize: 18, textAlign: 'center' }}>
Some text example running with RNGH
<LogicDetector gesture={tapFirstPart}>
<GestureDetector gesture={tapFirstPart}>
<Text style={{ fontSize: 24, color: COLORS.NAVY }}>
{' '}
try tapping on this part
</Text>
</LogicDetector>
<LogicDetector gesture={tapSecondPart}>
</GestureDetector>
<GestureDetector gesture={tapSecondPart}>
<Text style={{ fontSize: 28, color: COLORS.KINDA_BLUE }}>
{' '}
or on this part
</Text>
</LogicDetector>
</GestureDetector>
this part is not special :(
</Text>
</NativeDetector>
</InterceptingGestureDetector>
</View>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { tagMessage } from '../utils';
import { useExclusive, useRace, useSimultaneous } from '../v3/hooks/relations';
import { useGesture } from '../v3/hooks/useGesture';
import { configureRelations } from '../v3/NativeDetector/utils';
import { configureRelations } from '../v3/detectors/utils';
import { SingleGesture, SingleGestureName } from '../v3/types';
import { renderHook } from '@testing-library/react-native';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ function propagateDetectorConfig(
}
}

interface GestureDetectorProps {
export interface GestureDetectorProps {
children?: React.ReactNode;
/**
* A gesture object containing the configuration and callbacks.
Expand Down
10 changes: 6 additions & 4 deletions packages/react-native-gesture-handler/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export { RotationGestureHandler } from './handlers/RotationGestureHandler';
export { FlingGestureHandler } from './handlers/FlingGestureHandler';
export { default as createNativeWrapper } from './handlers/createNativeWrapper';
export type { NativeViewGestureHandlerProps } from './handlers/NativeViewGestureHandler';
export { GestureDetector } from './handlers/gestures/GestureDetector';
export { GestureDetector as LegacyGestureDetector } from './handlers/gestures/GestureDetector';
export { GestureObjects as Gesture } from './handlers/gestures/gestureObjects';
export type { TapGestureType as LegacyTapGesture } from './handlers/gestures/tapGesture';
export type { PanGestureType as LegacyPanGesture } from './handlers/gestures/panGesture';
Expand Down Expand Up @@ -149,10 +149,12 @@ export type {
} from './components/Pressable';
export { default as Pressable } from './components/Pressable';

export type { NativeDetectorProps } from './v3/NativeDetector/NativeDetector';
export { NativeDetector } from './v3/NativeDetector/NativeDetector';
export {
GestureDetector,
InterceptingGestureDetector,
GestureDetectorProps,
} from './v3/detectors';

export { LogicDetector } from './v3/LogicDetector';
export * from './v3/hooks/useGesture';
export * from './v3/hooks/relations';

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { NativeDetectorProps } from './common';
import { BaseGesture } from '../../handlers/gestures/gesture';
import { NativeDetector } from './NativeDetector';
import { ComposedGesture } from '../../handlers/gestures/gestureComposition';
import {
GestureDetectorProps as LegacyGestureDetectorProps,
GestureDetector as LegacyGestureDetector,
} from '../../handlers/gestures/GestureDetector';
import { DetectorContext } from './LogicDetector/useDetectorContext';
import { LogicDetector } from './LogicDetector/LogicDetector';
import { use } from 'react';

export function GestureDetector<THandlerData, TConfig>(
Comment on lines +10 to +13

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import { LogicDetector } from './LogicDetector/LogicDetector';
export function GestureDetector<THandlerData, TConfig>(
import { LogicDetector } from './LogicDetector/LogicDetector';
export function GestureDetector<THandlerData, TConfig>(

Please 🙏

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

props: NativeDetectorProps<THandlerData, TConfig> | LegacyGestureDetectorProps
) {
if (
props.gesture instanceof ComposedGesture ||
props.gesture instanceof BaseGesture
) {
return <LegacyGestureDetector {...(props as LegacyGestureDetectorProps)} />;
}

const context = use(DetectorContext);
return context ? (
<LogicDetector {...(props as NativeDetectorProps<THandlerData, TConfig>)} />
) : (
<NativeDetector
{...(props as NativeDetectorProps<THandlerData, TConfig>)}
/>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@ const HostGestureDetector = (props: GestureHandlerDetectorProps) => {
);

props.logicChildren?.forEach((child) => {
if (child.viewRef.current == null) {
// We must check whether viewRef is not null as otherwise we get an error when intercepting gesture detector
// switches its component based on whether animated/reanimated events should run.
return;
}
Comment thread
j-piasecki marked this conversation as resolved.
Resolved
if (!attachedLogicHandlers.current.has(child.viewTag)) {
attachedLogicHandlers.current.set(child.viewTag, new Set());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,46 +1,55 @@
import React, { RefObject, useCallback, useRef, useState } from 'react';
import { Animated, StyleSheet } from 'react-native';
import HostGestureDetector from './HostGestureDetector';
import { tagMessage } from '../../utils';
import HostGestureDetector from '../HostGestureDetector';
import {
LogicChildren,
Gesture,
DetectorCallbacks,
GestureHandlerEvent,
} from '../types';
DetectorCallbacks,
} from '../../types';
import { DetectorContext } from './useDetectorContext';
import { Reanimated } from '../../handlers/gestures/reanimatedWrapper';
import { configureRelations } from './utils';
import { isComposedGesture } from '../hooks/utils/relationUtils';

export interface NativeDetectorProps<THandlerData, TConfig> {
children?: React.ReactNode;
gesture: Gesture<THandlerData, TConfig>;
}

const AnimatedNativeDetector =
Animated.createAnimatedComponent(HostGestureDetector);

const ReanimatedNativeDetector =
Reanimated?.default.createAnimatedComponent(HostGestureDetector);

export function NativeDetector<THandlerData, TConfig>({
import { Reanimated } from '../../../handlers/gestures/reanimatedWrapper';
import { configureRelations, ensureNativeDetectorComponent } from '../utils';
import { isComposedGesture } from '../../hooks/utils/relationUtils';
import {
AnimatedNativeDetector,
InterceptingGestureDetectorProps,
nativeDetectorStyles,
ReanimatedNativeDetector,
} from '../common';
import { tagMessage } from '../../../utils';

export function InterceptingGestureDetector<THandlerData, TConfig>({
gesture,
children,
}: NativeDetectorProps<THandlerData, TConfig>) {
}: InterceptingGestureDetectorProps<THandlerData, TConfig>) {
const [logicChildren, setLogicChildren] = useState<LogicChildren[]>([]);

const logicMethods = useRef<
Map<number, RefObject<DetectorCallbacks<unknown>>>
>(new Map());

const NativeDetectorComponent = gesture.config.dispatchesAnimatedEvents
const [shouldUseReanimated, setShouldUseReanimated] = useState(
gesture ? gesture.config.shouldUseReanimatedDetector : false
);
const [dispatchesAnimatedEvents, setDispatchesAnimatedEvents] = useState(
gesture ? gesture.config.dispatchesAnimatedEvents : false
);

const NativeDetectorComponent = dispatchesAnimatedEvents
? AnimatedNativeDetector
: gesture.config.shouldUseReanimatedDetector
: shouldUseReanimated
? ReanimatedNativeDetector
: HostGestureDetector;

const register = useCallback(
(child: LogicChildren, methods: RefObject<DetectorCallbacks<unknown>>) => {
(
child: LogicChildren,
methods: RefObject<DetectorCallbacks<unknown>>,
forReanimated: boolean | undefined,
forAnimated: boolean | undefined
) => {
setShouldUseReanimated(!!forReanimated);
setDispatchesAnimatedEvents(!!forAnimated);

setLogicChildren((prev) => {
const index = prev.findIndex((c) => c.viewTag === child.viewTag);
if (index !== -1) {
Expand Down Expand Up @@ -76,11 +85,9 @@ export function NativeDetector<THandlerData, TConfig>({
);
}

configureRelations(gesture);

const handleGestureEvent = (key: keyof DetectorCallbacks<THandlerData>) => {
return (e: GestureHandlerEvent<THandlerData>) => {
if (gesture.detectorCallbacks[key]) {
if (gesture?.detectorCallbacks[key]) {
gesture.detectorCallbacks[key](e);
}

Expand All @@ -97,7 +104,7 @@ export function NativeDetector<THandlerData, TConfig>({
(key: keyof DetectorCallbacks<unknown>) => {
const handlers: ((e: GestureHandlerEvent<THandlerData>) => void)[] = [];

if (gesture.detectorCallbacks[key]) {
if (gesture?.detectorCallbacks[key]) {
handlers.push(
gesture.detectorCallbacks[key] as (
e: GestureHandlerEvent<unknown>
Expand All @@ -116,21 +123,27 @@ export function NativeDetector<THandlerData, TConfig>({

return handlers;
},
[logicChildren, gesture.detectorCallbacks]
[logicChildren, gesture?.detectorCallbacks]
);

const reanimatedEventHandler = Reanimated?.useComposedEventHandler(
getHandlers('onReanimatedUpdateEvent')
);
const reanimedStateChangeHandler = Reanimated?.useComposedEventHandler(
const reanimatedStateChangeHandler = Reanimated?.useComposedEventHandler(
getHandlers('onReanimatedStateChange')
);
const reanimatedTouchEventHandler = Reanimated?.useComposedEventHandler(
getHandlers('onReanimatedTouchEvent')
);

ensureNativeDetectorComponent(NativeDetectorComponent);

if (gesture) {
configureRelations(gesture);
}

return (
<DetectorContext.Provider value={{ register, unregister }}>
<DetectorContext value={{ register, unregister }}>
<NativeDetectorComponent
// @ts-ignore This is a type mismatch between RNGH types and RN Codegen types
onGestureHandlerStateChange={handleGestureEvent(
Expand All @@ -140,32 +153,36 @@ export function NativeDetector<THandlerData, TConfig>({
onGestureHandlerEvent={handleGestureEvent('onGestureHandlerEvent')}
// @ts-ignore This is a type mismatch between RNGH types and RN Codegen types
onGestureHandlerAnimatedEvent={
gesture.detectorCallbacks.onGestureHandlerAnimatedEvent
gesture?.detectorCallbacks.onGestureHandlerAnimatedEvent
}
// @ts-ignore This is a type mismatch between RNGH types and RN Codegen types
onGestureHandlerTouchEvent={handleGestureEvent(
'onGestureHandlerTouchEvent'
)}
// @ts-ignore This is a type mismatch between RNGH types and RN Codegen types
onGestureHandlerReanimatedStateChange={reanimatedEventHandler}
onGestureHandlerReanimatedStateChange={
shouldUseReanimated ? reanimatedStateChangeHandler : undefined
}
// @ts-ignore This is a type mismatch between RNGH types and RN Codegen types
onGestureHandlerReanimatedEvent={reanimedStateChangeHandler}
onGestureHandlerReanimatedEvent={
shouldUseReanimated ? reanimatedEventHandler : undefined
}
// @ts-ignore This is a type mismatch between RNGH types and RN Codegen types
onGestureHandlerReanimatedTouchEvent={reanimatedTouchEventHandler}
onGestureHandlerReanimatedTouchEvent={
shouldUseReanimated ? reanimatedTouchEventHandler : undefined
}
moduleId={globalThis._RNGH_MODULE_ID}
handlerTags={isComposedGesture(gesture) ? gesture.tags : [gesture.tag]}
style={styles.detector}
handlerTags={
gesture
? isComposedGesture(gesture)
? gesture.tags
: [gesture.tag]
: []
}
style={nativeDetectorStyles.detector}
logicChildren={logicChildren}>
{children}
</NativeDetectorComponent>
</DetectorContext.Provider>
</DetectorContext>
);
}

const styles = StyleSheet.create({
detector: {
display: 'contents',
// TODO: remove, debug info only
backgroundColor: 'red',
},
});
Original file line number Diff line number Diff line change
@@ -1,19 +1,29 @@
import { RefObject, useCallback, useEffect, useRef, useState } from 'react';
import { Wrap } from '../handlers/gestures/GestureDetector/Wrap';
import { Wrap } from '../../../handlers/gestures/GestureDetector/Wrap';
import { findNodeHandle, Platform } from 'react-native';
import { useDetectorContext } from './NativeDetector/useDetectorContext';
import { NativeDetectorProps } from './NativeDetector/NativeDetector';
import { isComposedGesture } from './hooks/utils/relationUtils';
import { DetectorCallbacks } from './types';
import { useDetectorContext } from './useDetectorContext';
import { isComposedGesture } from '../../hooks/utils/relationUtils';
import { NativeDetectorProps } from '../common';
import { configureRelations } from '../utils';
import { tagMessage } from '../../../utils';
import { DetectorCallbacks } from '../../types';

export function LogicDetector<THandlerData, TConfig>(
props: NativeDetectorProps<THandlerData, TConfig>
) {
const { register, unregister } = useDetectorContext();
const context = useDetectorContext();
if (!context) {
throw new Error(
tagMessage(
'Logic detector must be a descendant of an InterceptingGestureDecector'
)
);
}
const { register, unregister } = context;

const viewRef = useRef(null);
const [viewTag, setViewTag] = useState<number>(-1);
const logicMethods = useRef(props.gesture.detectorCallbacks);

const handleRef = useCallback((node: any) => {
viewRef.current = node;
if (!node) {
Expand Down Expand Up @@ -52,12 +62,19 @@ export function LogicDetector<THandlerData, TConfig>(
Object.assign(logicProps, { viewRef });
}

register(logicProps, logicMethods as RefObject<DetectorCallbacks<unknown>>);
register(
logicProps,
logicMethods as RefObject<DetectorCallbacks<unknown>>,
props.gesture.config.shouldUseReanimatedDetector,
props.gesture.config.dispatchesAnimatedEvents
Comment thread
j-piasecki marked this conversation as resolved.
Resolved
);

return () => {
unregister(viewTag, handlerTags);
};
}, [viewTag, props.gesture, register, unregister]);

configureRelations(props.gesture);

return <Wrap ref={handleRef}>{props.children}</Wrap>;
}
Loading
Loading