Repository navigation
Commit 10235ad
authored
[macOS] Fix
## Description
`manualActivation: true` has never worked on macOS — the handler
activated on its own as if the flag wasn't set. Found while verifying a
review comment on #4387. Four stacked defects in
`RNManualActivationRecognizer`, all macOS-specific:
1. The failure requirement was never established. The blocking mechanism
relies on `shouldBeRequiredToFailByGestureRecognizer:`, which is a
`UIGestureRecognizer` *subclass* hook (`UIGestureRecognizerSubclass.h`).
`NSGestureRecognizer` has no such hook, so AppKit never called it and
the handler's recognizer never waited for the blocker — a pan activated
straight from mouse-down. AppKit only consults the *delegate*; since the
blocker is already its own `NSGestureRecognizerDelegate`, the two-arg
delegate callback now forwards to the shared logic.
2. AppKit does not reliably deny the dependent recognizer when the
blocker recognizes. On iOS, the blocker completing (`Began` → `Ended` in
its action handler) causes UIKit to fail the recognizer that required it
to fail — that's how a released-without-`activate()` gesture is
discarded. On AppKit, completing the blocker can instead *flush* the
dependent recognizer's buffered recognition: with the requirement in
place, releasing after a drag delivered the pan's entire withheld
sequence (`Began`/`Changed`/`Ended` with the full accumulated
translation) instead of discarding it, while a motionless press-release
was discarded correctly (traced with state-transition logging). The
blocker now explicitly fails the handler's recognizer before completing.
3. The release-without-activation cleanup was dead code. The original
macOS port (#2588) crossed the `touchesBegan`/`touchesEnded` bodies:
`mouseDown` incremented `_activePointers` and then checked `if
(_activePointers == 0)` — never true after an increment — while
`mouseUp` only decremented. The zero-check now lives in `mouseUp`,
mirroring `touchesEnded` on iOS.
4. Stale pointer count after a JS `activate()`. `stopActivationBlocker`
disables the blocker, so it misses the subsequent mouse-up; iOS recovers
in `touchesCancelled`, which has no AppKit equivalent. The count would
stay at 1 and the cleanup in (3) would never fire again. `reset` now
zeroes `_activePointers`.
## Test plan
<details>
<summary>Tested on the following code:</summary>
```tsx
import React, { useEffect } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import {
GestureDetector,
GestureStateManager,
usePanGesture,
} from 'react-native-gesture-handler';
import { useSharedValue } from 'react-native-reanimated';
export default function EmptyExample() {
// Box A: manualActivation, JS never calls activate().
// Expected: every press/drag/release cycle logs onBegin -> onFinalize canceled=true,
// never onActivate, and behaves identically on every repeat.
const neverActivatePan = usePanGesture({
manualActivation: true,
onBegin: () => console.log('[never-activate] onBegin'),
onActivate: () => console.log('[never-activate] onActivate (SHOULD NOT HAPPEN)'),
onDeactivate: () => console.log('[never-activate] onDeactivate'),
onFinalize: (e) => console.log(`[never-activate] onFinalize canceled=${e.canceled}`),
});
// Box B: manualActivation, activates itself from a timer while the button is held
// (onTouchesMove is not delivered on macOS — separate bug).
// Expected: hold past 300 ms -> onActivate (tx ~0), onUpdate while dragging,
// onDeactivate + onFinalize canceled=false on release. Release before 300 ms ->
// canceled=true and the late activate() is a no-op.
const selfTag = useSharedValue(-1);
const selfActivatePan = usePanGesture({
manualActivation: true,
onTouchesDown: () => {
const tag = selfTag.value;
setTimeout(() => {
console.log(`[self-activate] calling activate(${tag})`);
GestureStateManager.activate(tag);
}, 300);
},
onBegin: () => console.log('[self-activate] onBegin'),
onActivate: (e) =>
console.log(`[self-activate] onActivate tx=${e.translationX.toFixed(1)} ty=${e.translationY.toFixed(1)}`),
onUpdate: (e) =>
console.log(`[self-activate] onUpdate tx=${e.translationX.toFixed(1)} ty=${e.translationY.toFixed(1)}`),
onDeactivate: () => console.log('[self-activate] onDeactivate'),
onFinalize: (e) => console.log(`[self-activate] onFinalize canceled=${e.canceled}`),
});
useEffect(() => {
selfTag.value = selfActivatePan.handlerTag;
}, [selfActivatePan.handlerTag, selfTag]);
return (
<View style={styles.container}>
<Text style={styles.label}>A: manualActivation, never activated — click & drag, release</Text>
<GestureDetector gesture={neverActivatePan}>
<View style={[styles.box, { backgroundColor: 'darkorange' }]} />
</GestureDetector>
<Text style={styles.label}>B: manualActivation, self-activates 300 ms after press</Text>
<GestureDetector gesture={selfActivatePan}>
<View style={[styles.box, { backgroundColor: 'mediumpurple' }]} />
</GestureDetector>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', alignItems: 'center', gap: 8 },
label: { marginTop: 16, fontSize: 15, opacity: 0.6 },
box: { width: 150, height: 150, borderRadius: 12 },
});
```
</details>manualActivation never blocking gesture activation (#4389)1 parent 6819385 commit 10235ad
1 file changed
Lines changed: 25 additions & 6 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
22 | 22 | | |
23 | 23 | | |
24 | 24 | | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
25 | 32 | | |
26 | 33 | | |
27 | 34 | | |
28 | 35 | | |
29 | 36 | | |
30 | 37 | | |
31 | | - | |
| 38 | + | |
32 | 39 | | |
33 | | - | |
| 40 | + | |
34 | 41 | | |
35 | | - | |
| 42 | + | |
36 | 43 | | |
37 | 44 | | |
38 | | - | |
| 45 | + | |
39 | 46 | | |
40 | | - | |
| 47 | + | |
41 | 48 | | |
42 | | - | |
| 49 | + | |
43 | 50 | | |
44 | 51 | | |
45 | 52 | | |
| |||
80 | 87 | | |
81 | 88 | | |
82 | 89 | | |
| 90 | + | |
83 | 91 | | |
84 | 92 | | |
85 | 93 | | |
| |||
106 | 114 | | |
107 | 115 | | |
108 | 116 | | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
109 | 128 | | |
0 commit comments