Skip to content

Commit a724f16

Browse files
authored
Add runOnJS to config (#3743)
## Description This PR adds `runOnJS` to gesture config. ## Test plan <details> <summary>Tested on the following example:</summary> ```tsx import * as React from 'react'; import { Animated, Button, StyleSheet, View, Text } from 'react-native'; import { GestureHandlerRootView, NativeDetector, usePan, useSimultaneous, } from 'react-native-gesture-handler'; import { useSharedValue } from 'react-native-reanimated'; const runtimeKind = (_worklet: boolean) => { 'worklet'; return _worklet ? 'worklet' : 'JS'; }; function SingleExampleWithState() { const [js, setJs] = React.useState(false); const gesture = usePan({ onUpdate: () => { 'worklet'; console.log( `[SingleExampleWithState] I run on a ${runtimeKind(globalThis._WORKLET)} runtime!` ); }, runOnJS: js, }); return ( <View style={[styles.container, styles.center]}> <Button title="Change runtime" onPress={() => { setJs((v) => !v); }} /> <NativeDetector gesture={gesture}> <Animated.View style={[styles.box]} /> </NativeDetector> </View> ); } function SingleExampleWithSharedValue() { const js = useSharedValue(false); const gesture = usePan({ onUpdate: () => { 'worklet'; console.log( `[SingleExampleWithSharedValue] I run on a ${runtimeKind(globalThis._WORKLET)} runtime!` ); }, runOnJS: js, }); return ( <View style={[styles.container, styles.center]}> <Button title="Change runtime" onPress={() => { js.value = !js.value; }} /> <NativeDetector gesture={gesture}> <Animated.View style={[styles.box]} /> </NativeDetector> </View> ); } function ComposedExample() { const [pan1JS, setPan1JS] = React.useState(false); const pan2JS = useSharedValue(false); const pan1 = usePan({ onUpdate: () => { 'worklet'; console.log( `[ComposedExample | Pan1] I run on a ${runtimeKind(globalThis._WORKLET)} runtime!` ); }, runOnJS: pan1JS, }); const pan2 = usePan({ onUpdate: () => { 'worklet'; console.log( `[ComposedExample | Pan2] I run on a ${runtimeKind(globalThis._WORKLET)} runtime!` ); }, runOnJS: pan2JS, }); const gesture = useSimultaneous(pan1, pan2); return ( <View style={[styles.container, styles.center]}> <View style={[styles.center, { flexDirection: 'row', gap: 10 }]}> <Button title="Change Pan1 runtime" onPress={() => { setPan1JS((v) => !v); }} /> <Button title="Change Pan2 runtime" onPress={() => { pan2JS.value = !pan2JS.value; }} /> </View> <NativeDetector gesture={gesture}> <Animated.View style={[styles.box]} /> </NativeDetector> </View> ); } export default function App() { return ( <GestureHandlerRootView style={[{ flex: 1 }, styles.center]}> <SingleExampleWithState /> <SingleExampleWithSharedValue /> <ComposedExample /> </GestureHandlerRootView> ); } const styles = StyleSheet.create({ container: { gap: 10 }, center: { display: 'flex', justifyContent: 'space-around', alignItems: 'center', }, box: { width: 150, height: 150, borderRadius: 20, backgroundColor: 'pink', }, }); ``` </details>
1 parent 07edd7a commit a724f16

File tree

10 files changed

+139
-104
lines changed

10 files changed

+139
-104
lines changed

packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandler.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -902,8 +902,8 @@ open class GestureHandler {
902902
if (config.hasKey(KEY_DISPATCHES_ANIMATED_EVENTS)) {
903903
handler.dispatchesAnimatedEvents = config.getBoolean(KEY_DISPATCHES_ANIMATED_EVENTS)
904904
}
905-
if (config.hasKey(KEY_SHOULD_USE_REANIMATED)) {
906-
handler.dispatchesReanimatedEvents = config.getBoolean(KEY_SHOULD_USE_REANIMATED)
905+
if (config.hasKey(KEY_DISPATCHES_REANIMATED_EVENTS)) {
906+
handler.dispatchesReanimatedEvents = config.getBoolean(KEY_DISPATCHES_REANIMATED_EVENTS)
907907
}
908908
if (config.hasKey(KEY_MANUAL_ACTIVATION)) {
909909
handler.manualActivation = config.getBoolean(KEY_MANUAL_ACTIVATION)
@@ -920,7 +920,7 @@ open class GestureHandler {
920920
private const val KEY_ENABLED = "enabled"
921921
private const val KEY_NEEDS_POINTER_DATA = "needsPointerData"
922922
private const val KEY_DISPATCHES_ANIMATED_EVENTS = "dispatchesAnimatedEvents"
923-
private const val KEY_SHOULD_USE_REANIMATED = "shouldUseReanimated"
923+
private const val KEY_DISPATCHES_REANIMATED_EVENTS = "dispatchesReanimatedEvents"
924924
private const val KEY_MANUAL_ACTIVATION = "manualActivation"
925925
private const val KEY_MOUSE_BUTTON = "mouseButton"
926926
private const val KEY_HIT_SLOP = "hitSlop"

packages/react-native-gesture-handler/apple/RNGestureHandler.mm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ - (void)updateConfig:(NSDictionary *)config
147147
_dispatchesAnimatedEvents = [RCTConvert BOOL:prop];
148148
}
149149

150-
prop = config[@"shouldUseReanimated"];
150+
prop = config[@"dispatchesReanimatedEvents"];
151151
if (prop != nil) {
152152
_dispatchesReanimatedEvents = [RCTConvert BOOL:prop];
153153
}

packages/react-native-gesture-handler/src/v3/NativeDetector/NativeDetector.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export function NativeDetector<THandlerData, TConfig>({
3535

3636
const NativeDetectorComponent = gesture.config.dispatchesAnimatedEvents
3737
? AnimatedNativeDetector
38-
: gesture.config.shouldUseReanimated
38+
: gesture.config.shouldUseReanimatedDetector
3939
? ReanimatedNativeDetector
4040
: HostGestureDetector;
4141

packages/react-native-gesture-handler/src/v3/hooks/relations/useComposedGesture.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
ComposedGesture,
66
ComposedGestureName,
77
AnyGesture,
8+
ComposedGestureConfig,
89
} from '../../types';
910
import { tagMessage } from '../../../utils';
1011
import { Reanimated } from '../../../handlers/gestures/reanimatedWrapper';
@@ -27,16 +28,16 @@ export function useComposedGesture(
2728
);
2829
}
2930

30-
const config = {
31-
shouldUseReanimated: gestures.some(
32-
(gesture) => gesture.config.shouldUseReanimated
31+
const config: ComposedGestureConfig = {
32+
shouldUseReanimatedDetector: gestures.some(
33+
(gesture) => gesture.config.shouldUseReanimatedDetector
3334
),
3435
dispatchesAnimatedEvents: gestures.some(
3536
(gesture) => gesture.config.dispatchesAnimatedEvents
3637
),
3738
};
3839

39-
if (config.shouldUseReanimated && config.dispatchesAnimatedEvents) {
40+
if (config.shouldUseReanimatedDetector && config.dispatchesAnimatedEvents) {
4041
throw new Error(
4142
tagMessage(
4243
'Composed gestures cannot use both Reanimated and Animated events at the same time.'

packages/react-native-gesture-handler/src/v3/hooks/useGesture.ts

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,12 @@ import { useEffect, useMemo } from 'react';
22
import { getNextHandlerTag } from '../../handlers/getNextHandlerTag';
33
import RNGestureHandlerModule from '../../RNGestureHandlerModule';
44
import { useGestureCallbacks } from './useGestureCallbacks';
5-
import { Reanimated } from '../../handlers/gestures/reanimatedWrapper';
65
import {
76
prepareConfig,
8-
isAnimatedEvent,
9-
shouldHandleTouchEvents,
107
prepareRelations,
118
bindSharedValues,
12-
hasWorkletEventHandlers,
139
unbindSharedValues,
10+
prepareConfigForNativeSide,
1411
} from './utils';
1512
import { tagMessage } from '../../utils';
1613
import { BaseGestureConfig, SingleGesture, SingleGestureName } from '../types';
@@ -30,15 +27,10 @@ export function useGesture<THandlerData, TConfig>(
3027
);
3128
}
3229

33-
// This has to be done ASAP as other hooks depend `shouldUseReanimated`.
34-
config.shouldUseReanimated =
35-
!config.disableReanimated &&
36-
Reanimated !== undefined &&
37-
hasWorkletEventHandlers(config);
38-
config.needsPointerData = shouldHandleTouchEvents(config);
39-
config.dispatchesAnimatedEvents = isAnimatedEvent(config.onUpdate);
30+
// This has to be done ASAP as other hooks depend `shouldUseReanimatedDetector`.
31+
prepareConfig(config);
4032

41-
if (config.dispatchesAnimatedEvents && config.shouldUseReanimated) {
33+
if (config.dispatchesAnimatedEvents && config.shouldUseReanimatedDetector) {
4234
throw new Error(
4335
tagMessage(
4436
`${type}: You cannot use Animated.Event together with callbacks running on the UI thread. Either remove Animated.Event from onUpdate, or set runOnJS property to true on the gesture.`
@@ -69,7 +61,7 @@ export function useGesture<THandlerData, TConfig>(
6961
}
7062

7163
if (
72-
config.shouldUseReanimated &&
64+
config.shouldUseReanimatedDetector &&
7365
(!onReanimatedStateChange ||
7466
!onReanimatedUpdateEvent ||
7567
!onReanimatedTouchEvent)
@@ -92,7 +84,7 @@ export function useGesture<THandlerData, TConfig>(
9284
}, [type, tag]);
9385

9486
useEffect(() => {
95-
const preparedConfig = prepareConfig(type, config);
87+
const preparedConfig = prepareConfigForNativeSide(type, config);
9688
RNGestureHandlerModule.setGestureHandlerConfig(tag, preparedConfig);
9789
RNGestureHandlerModule.flushOperations();
9890

packages/react-native-gesture-handler/src/v3/hooks/utils/configUtils.ts

Lines changed: 23 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -2,83 +2,34 @@ import { Reanimated } from '../../../handlers/gestures/reanimatedWrapper';
22
import { tagMessage } from '../../../utils';
33
import {
44
BaseGestureConfig,
5-
CommonGestureConfig,
65
ExcludeInternalConfigProps,
7-
GestureCallbacks,
8-
HandlersPropsWhiteList,
9-
InternalConfigProps,
106
SingleGestureName,
117
} from '../../types';
12-
import { PanNativeProperties } from '../gestures/pan/PanProperties';
13-
import { FlingNativeProperties } from '../gestures/fling/FlingProperties';
14-
import { HoverNativeProperties } from '../gestures/hover/HoverProperties';
15-
import { LongPressNativeProperties } from '../gestures/longPress/LongPressProperties';
16-
import { NativeHandlerNativeProperties } from '../gestures/native/NativeProperties';
17-
import { TapNativeProperties } from '../gestures/tap/TapProperties';
18-
19-
const allowedNativeProps = new Set<
20-
keyof CommonGestureConfig | keyof InternalConfigProps<unknown>
21-
>([
22-
// CommonGestureConfig
23-
'disableReanimated',
24-
'enabled',
25-
'shouldCancelWhenOutside',
26-
'hitSlop',
27-
'userSelect',
28-
'activeCursor',
29-
'mouseButton',
30-
'enableContextMenu',
31-
'touchAction',
32-
33-
// InternalConfigProps
34-
'shouldUseReanimated',
35-
'dispatchesAnimatedEvents',
36-
'needsPointerData',
37-
'changeEventCalculator',
38-
]);
39-
40-
export const HandlerCallbacks = new Set<
41-
keyof Required<GestureCallbacks<unknown>>
42-
>([
43-
'onBegin',
44-
'onStart',
45-
'onUpdate',
46-
'onEnd',
47-
'onFinalize',
48-
'onTouchesDown',
49-
'onTouchesMove',
50-
'onTouchesUp',
51-
'onTouchesCancelled',
52-
]);
53-
54-
const PropsToFilter = new Set<BaseGestureConfig<unknown, unknown>>([
55-
...HandlerCallbacks,
56-
57-
// Config props
58-
'changeEventCalculator',
59-
'disableReanimated',
60-
61-
// Relations
62-
'simultaneousWithExternalGesture',
63-
'requireExternalGestureToFail',
64-
'blocksExternalGesture',
65-
]);
66-
67-
export const PropsWhiteLists = new Map<
68-
SingleGestureName,
69-
HandlersPropsWhiteList
70-
>([
71-
[SingleGestureName.Pan, PanNativeProperties],
72-
[SingleGestureName.Tap, TapNativeProperties],
73-
[SingleGestureName.Native, NativeHandlerNativeProperties],
74-
[SingleGestureName.Fling, FlingNativeProperties],
75-
[SingleGestureName.Hover, HoverNativeProperties],
76-
[SingleGestureName.LongPress, LongPressNativeProperties],
77-
]);
78-
79-
const EMPTY_WHITE_LIST = new Set<string>();
8+
import { hasWorkletEventHandlers, maybeUnpackValue } from './reanimatedUtils';
9+
import { isAnimatedEvent, shouldHandleTouchEvents } from './eventUtils';
10+
import {
11+
allowedNativeProps,
12+
EMPTY_WHITE_LIST,
13+
PropsToFilter,
14+
PropsWhiteLists,
15+
} from './propsWhiteList';
8016

8117
export function prepareConfig<THandlerData, TConfig extends object>(
18+
config: BaseGestureConfig<THandlerData, TConfig>
19+
) {
20+
const runOnJS = maybeUnpackValue(config.runOnJS);
21+
22+
config.shouldUseReanimatedDetector =
23+
!config.disableReanimated &&
24+
Reanimated !== undefined &&
25+
hasWorkletEventHandlers(config);
26+
config.needsPointerData = shouldHandleTouchEvents(config);
27+
config.dispatchesAnimatedEvents = isAnimatedEvent(config.onUpdate);
28+
config.dispatchesReanimatedEvents =
29+
config.shouldUseReanimatedDetector && !runOnJS;
30+
}
31+
32+
export function prepareConfigForNativeSide<THandlerData, TConfig>(
8233
handlerType: SingleGestureName,
8334
config: BaseGestureConfig<THandlerData, TConfig>
8435
) {

packages/react-native-gesture-handler/src/v3/hooks/utils/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ export * from './eventHandlersUtils';
33
export * from './eventUtils';
44
export * from './reanimatedUtils';
55
export * from './relationUtils';
6+
export * from './propsWhiteList';
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import {
2+
BaseGestureConfig,
3+
CommonGestureConfig,
4+
GestureCallbacks,
5+
HandlersPropsWhiteList,
6+
InternalConfigProps,
7+
SingleGestureName,
8+
} from '../../types';
9+
import { FlingNativeProperties } from '../gestures/fling/FlingProperties';
10+
import { HoverNativeProperties } from '../gestures/hover/HoverProperties';
11+
import { LongPressNativeProperties } from '../gestures/longPress/LongPressProperties';
12+
import { NativeHandlerNativeProperties } from '../gestures/native/NativeProperties';
13+
import { PanNativeProperties } from '../gestures/pan/PanProperties';
14+
import { TapNativeProperties } from '../gestures/tap/TapProperties';
15+
16+
export const allowedNativeProps = new Set<
17+
keyof CommonGestureConfig | keyof InternalConfigProps<unknown>
18+
>([
19+
// CommonGestureConfig
20+
'disableReanimated',
21+
'enabled',
22+
'shouldCancelWhenOutside',
23+
'hitSlop',
24+
'userSelect',
25+
'activeCursor',
26+
'mouseButton',
27+
'enableContextMenu',
28+
'touchAction',
29+
30+
// InternalConfigProps
31+
'dispatchesReanimatedEvents',
32+
'dispatchesAnimatedEvents',
33+
'needsPointerData',
34+
'changeEventCalculator',
35+
]);
36+
37+
export const HandlerCallbacks = new Set<
38+
keyof Required<GestureCallbacks<unknown>>
39+
>([
40+
'onBegin',
41+
'onStart',
42+
'onUpdate',
43+
'onEnd',
44+
'onFinalize',
45+
'onTouchesDown',
46+
'onTouchesMove',
47+
'onTouchesUp',
48+
'onTouchesCancelled',
49+
]);
50+
51+
export const PropsToFilter = new Set<BaseGestureConfig<unknown, unknown>>([
52+
...HandlerCallbacks,
53+
54+
// Config props
55+
'changeEventCalculator',
56+
'disableReanimated',
57+
58+
// Relations
59+
'simultaneousWithExternalGesture',
60+
'requireExternalGestureToFail',
61+
'blocksExternalGesture',
62+
]);
63+
64+
export const PropsWhiteLists = new Map<
65+
SingleGestureName,
66+
HandlersPropsWhiteList
67+
>([
68+
[SingleGestureName.Pan, PanNativeProperties],
69+
[SingleGestureName.Tap, TapNativeProperties],
70+
[SingleGestureName.Native, NativeHandlerNativeProperties],
71+
[SingleGestureName.Fling, FlingNativeProperties],
72+
[SingleGestureName.Hover, HoverNativeProperties],
73+
[SingleGestureName.LongPress, LongPressNativeProperties],
74+
]);
75+
76+
export const EMPTY_WHITE_LIST = new Set<string>();

packages/react-native-gesture-handler/src/v3/hooks/utils/reanimatedUtils.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import RNGestureHandlerModule from '../../../RNGestureHandlerModule';
22
import { Reanimated } from '../../../handlers/gestures/reanimatedWrapper';
33
import { BaseGestureConfig, SharedValue, SharedValueOrT } from '../../types';
4-
import { HandlerCallbacks } from './configUtils';
4+
import { HandlerCallbacks } from './propsWhiteList';
55

66
// Variant of djb2 hash function.
77
// Taken from https://gist.github.com/eplawless/52813b1d8ad9af510d85?permalink_comment_id=3367765#gistcomment-3367765
@@ -37,7 +37,16 @@ export function bindSharedValues<THandlerData, TConfig>(
3737
const listenerId = baseListenerId + keyHash;
3838

3939
sharedValue.addListener(listenerId, (value) => {
40-
updateGestureHandlerConfig(handlerTag, { [configKey]: value });
40+
if (configKey === 'runOnJS') {
41+
config.dispatchesReanimatedEvents =
42+
config.shouldUseReanimatedDetector && !value;
43+
44+
updateGestureHandlerConfig(handlerTag, {
45+
dispatchesReanimatedEvents: config.dispatchesReanimatedEvents,
46+
});
47+
} else {
48+
updateGestureHandlerConfig(handlerTag, { [configKey]: value });
49+
}
4150
flushOperations();
4251
});
4352
};

packages/react-native-gesture-handler/src/v3/types.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -122,13 +122,15 @@ export type SingleGesture<THandlerData, TConfig> = {
122122
gestureRelations: GestureRelations;
123123
};
124124

125+
export type ComposedGestureConfig = {
126+
shouldUseReanimatedDetector: boolean;
127+
dispatchesAnimatedEvents: boolean;
128+
};
129+
125130
export type ComposedGesture = {
126131
tags: number[];
127132
type: ComposedGestureName;
128-
config: {
129-
shouldUseReanimated: boolean;
130-
dispatchesAnimatedEvents: boolean;
131-
};
133+
config: ComposedGestureConfig;
132134
gestureEvents: GestureEvents<unknown>;
133135
externalSimultaneousHandlers: number[];
134136
gestures: Gesture[];
@@ -176,15 +178,18 @@ export interface GestureCallbacks<THandlerData> {
176178
}
177179

178180
export type InternalConfigProps<THandlerData> = {
179-
shouldUseReanimated?: boolean;
181+
shouldUseReanimatedDetector?: boolean;
182+
dispatchesReanimatedEvents?: boolean;
180183
dispatchesAnimatedEvents?: boolean;
181184
needsPointerData?: boolean;
182185
changeEventCalculator?: ChangeCalculatorType<THandlerData>;
183186
};
184187

185-
export type CommonGestureConfig = WithSharedValue<
188+
export type CommonGestureConfig = {
189+
disableReanimated?: boolean;
190+
} & WithSharedValue<
186191
{
187-
disableReanimated?: boolean;
192+
runOnJS?: boolean;
188193
enabled?: boolean;
189194
shouldCancelWhenOutside?: boolean;
190195
hitSlop?: HitSlop;

0 commit comments

Comments
 (0)