Skip to content

Commit 6f45895

Browse files
authored
Default shouldCancelWhenOutside to true in useTapGesture (#4475)
## Description `useTapGesture` never sets `shouldCancelWhenOutside`, so a v3 `Tap` keeps recognizing after the finger leaves the view on Apple and web. Both older APIs send `true` for `Tap`: * v1: `src/handlers/TapGestureHandler.ts:94` sets `config: { shouldCancelWhenOutside: true }` * v2: `src/handlers/gestures/tapGesture.ts:16` calls `this.shouldCancelWhenOutside(true)` in the constructor The docs describe the same contract for the v3 hooks, in `packages/docs-gesture-handler/docs/gestures/_shared/base-gesture-config.mdx:22`: > Most handlers' `shouldCancelWhenOutside` property defaults to `false` except for the [`LongPress`](/docs/gestures/use-long-press-gesture), [`Tap`](/docs/gestures/use-tap-gesture) and [`Native`](/docs/gestures/use-native-gesture) (on Android and web), which default to `true`. The value has to come from JS, because only Android defaults it to `true` on its own: * Android: `TapGestureHandler.kt:33-35` sets it in `init`, and `resetConfig` restores a `true` `DEFAULT_SHOULD_CANCEL_WHEN_OUTSIDE` (`TapGestureHandler.kt:193`), shadowing the `false` one in `GestureHandler.kt:1004` * Apple: `RNGestureHandler.mm:120` sets `_shouldCancelWhenOutside = NO` in `resetConfig`, `setConfig` calls `resetConfig` and then only assigns the property when the key is present (`RNGestureHandler.mm:149-152`), and `RNTapHandler` does not override it * Web: `src/web/handlers/GestureHandler.ts:45` initializes `_shouldCancelWhenOutside = false`, and the web `TapGestureHandler` does not override it `useLongPressGesture` already applies its own default through a props transformer (`useLongPressGesture.ts:21-29`); this applies the `Tap` default the same way, so an explicitly configured value still wins. ## Test plan Two tests added to `src/__tests__/api_v3.test.tsx`: * `useTapGesture()` and `useLongPressGesture()` both end up with `shouldCancelWhenOutside: true` * `useTapGesture({ shouldCancelWhenOutside: false })` keeps `false` The first one fails on `main` with `Expected: true, Received: undefined`, and passes with the change. I also reverted just the two source files with the test kept, and confirmed it goes red for that reason, then restored them. Commands run in this checkout: * `yarn workspace react-native-gesture-handler test` -> 19 suites, 161 tests passing (159 before this change) * `yarn ts-check` from the repository root -> clean * `yarn workspace react-native-gesture-handler lint-js` -> 0 errors, prettier clean Not verified on a device or simulator: the change is confined to the JS config that the hook hands to the native side, so the check above is the config assertion rather than an on-device gesture run.
1 parent b6ff243 commit 6f45895

3 files changed

Lines changed: 41 additions & 2 deletions

File tree

‎packages/react-native-gesture-handler/src/__tests__/api_v3.test.tsx‎

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ import { State } from '../State';
1313
import { Pressable, RectButton, ScrollView, Touchable } from '../v3/components';
1414
import { GestureDetector } from '../v3/detectors';
1515
import { useSimultaneousGestures } from '../v3/hooks';
16-
import { usePanGesture, useTapGesture } from '../v3/hooks/gestures';
16+
import {
17+
useLongPressGesture,
18+
usePanGesture,
19+
useTapGesture,
20+
} from '../v3/hooks/gestures';
1721
import {
1822
isKeyboardDismissingTap,
1923
type JSResponderContextValue,
@@ -152,6 +156,25 @@ describe('[API v3] Hooks', () => {
152156
expect.objectContaining({ changeX: 10, changeY: 5 })
153157
);
154158
});
159+
160+
// Apple and web leave `shouldCancelWhenOutside` off unless the config carries
161+
// it, so the default has to come from here to match the documented behavior.
162+
test('Tap and long press gestures cancel when outside by default', () => {
163+
const tapGesture = renderHook(() => useTapGesture()).result.current;
164+
const longPressGesture = renderHook(() => useLongPressGesture()).result
165+
.current;
166+
167+
expect(tapGesture.config.shouldCancelWhenOutside).toBe(true);
168+
expect(longPressGesture.config.shouldCancelWhenOutside).toBe(true);
169+
});
170+
171+
test('Tap gesture keeps an explicitly configured shouldCancelWhenOutside', () => {
172+
const tapGesture = renderHook(() =>
173+
useTapGesture({ shouldCancelWhenOutside: false })
174+
).result.current;
175+
176+
expect(tapGesture.config.shouldCancelWhenOutside).toBe(false);
177+
});
155178
});
156179

157180
describe('[API v3] Components', () => {

‎packages/react-native-gesture-handler/src/v3/hooks/gestures/tap/TapTypes.ts‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,11 @@ export type TapGestureConfig = ExcludeInternalConfigProps<
9191
BaseDiscreteGestureConfig<TapGestureProperties, TapHandlerData>
9292
>;
9393

94+
export type TapGestureInternalConfig = BaseDiscreteGestureConfig<
95+
TapGestureInternalProperties,
96+
TapHandlerData
97+
>;
98+
9499
export type TapGestureEvent = GestureEvent<TapHandlerData>;
95100
export type TapGestureActiveEvent = TapGestureEvent;
96101

‎packages/react-native-gesture-handler/src/v3/hooks/gestures/tap/useTapGesture.ts‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { useClonedAndRemappedConfig } from '../../utils';
44
import type {
55
TapGesture,
66
TapGestureConfig,
7+
TapGestureInternalConfig,
78
TapGestureInternalProperties,
89
TapGestureProperties,
910
TapHandlerData,
@@ -18,6 +19,16 @@ const TapPropsMapping = new Map<
1819
['maxDelay', 'maxDelayMs'],
1920
]);
2021

22+
function transformTapProps(
23+
config: TapGestureConfig & TapGestureInternalConfig
24+
) {
25+
if (config.shouldCancelWhenOutside === undefined) {
26+
config.shouldCancelWhenOutside = true;
27+
}
28+
29+
return config;
30+
}
31+
2132
const EMPTY_TAP_CONFIG: TapGestureConfig = {};
2233

2334
export function useTapGesture(
@@ -27,7 +38,7 @@ export function useTapGesture(
2738
TapGestureProperties,
2839
TapHandlerData,
2940
TapGestureInternalProperties
30-
>(config, TapPropsMapping);
41+
>(config, TapPropsMapping, transformTapProps);
3142

3243
return useGesture<TapGestureInternalProperties, TapHandlerData>(
3344
SingleGestureName.Tap,

0 commit comments

Comments
 (0)