Repository navigation
Commit bfe7941
committed
[macOS] Fix
On macOS, all of `Pan`'s custom activation criteria — `minDistance`,
`activeOffsetX/Y`, `minVelocity(X/Y)` — were silently ignored: the
handler activated the moment the mouse button went down.
Two things combined to cause this:
1. Both custom-activation code paths in `RNPanHandler.m` were compiled
out for macOS. The early-activation guard in `interactionsBegan` and the
`shouldActivateUnderCustomCriteria` check in `interactionsMoved` were
wrapped in `#if !TARGET_OS_TV && !TARGET_OS_OSX`, so the config values
were stored but never evaluated. (Only the fail criteria worked —
`shouldFailUnderCustomCriteria` runs unconditionally.)
2. `NSPanGestureRecognizer` begins on mouse-down. Unlike
`UIPanGestureRecognizer`, which has a built-in ~10 pt hysteresis, the
AppKit recognizer transitions to `Began` immediately, so without the
check there is nothing preventing a click from activating the handler.
The iOS implementation holds the recognizer back with the
`minimumNumberOfTouches = 20` trick, which has no AppKit equivalent.
Instead, on macOS the recognizer is held in the `Possible` state
explicitly:
- When a mouse-down arrives and custom activation criteria are
configured, a `_blockAutomaticActivation` flag is set.
- A `setState:` override swallows the superclass's `Began`/`Changed`
transitions while the flag is up. The superclass keeps receiving events,
so `translationInView:` / `velocityInView:` stay valid — which also
keeps `failOffsetX/Y` and the JS event payload working.
- `interactionsMoved` now evaluates `shouldActivateUnderCustomCriteria`
on macOS and, once it passes, clears the flag, sets `Began` and resets
the translation to zero — same semantics as iOS/Android (translation
counts from the activation point).
- A mouse-up before the criteria are met arrives at `setState:` as
`Ended` and is rewritten to `Failed`, so the gesture finalizes correctly
(`onFinalize` with `canceled=true`).
- The flag is cleared in `reset` and in `activateAfterLongPress` (which
already forces `minDistSq >= 100`, so the block is active while waiting
for the long-press timer).
<details>
<summary>Tested on the following code:</summary>
```tsx
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { GestureDetector, usePanGesture } from 'react-native-gesture-handler';
export default function EmptyExample() {
const minDistPan = usePanGesture({
minDistance: 50,
onBegin: () => console.log('[minDist] onBegin'),
onActivate: (e) =>
console.log(`[minDist] onActivate tx=${e.translationX.toFixed(1)} ty=${e.translationY.toFixed(1)}`),
onDeactivate: () => console.log('[minDist] onDeactivate'),
onFinalize: (e) => console.log(`[minDist] onFinalize canceled=${e.canceled}`),
});
const activeOffsetPan = usePanGesture({
activeOffsetX: [-50, 50],
onBegin: () => console.log('[activeOffsetX] onBegin'),
onActivate: (e) =>
console.log(`[activeOffsetX] onActivate tx=${e.translationX.toFixed(1)} ty=${e.translationY.toFixed(1)}`),
onDeactivate: () => console.log('[activeOffsetX] onDeactivate'),
onFinalize: (e) => console.log(`[activeOffsetX] onFinalize canceled=${e.canceled}`),
});
const minVelocityPan = usePanGesture({
minVelocity: 800,
onBegin: () => console.log('[minVelocity] onBegin'),
onActivate: (e) =>
console.log(`[minVelocity] onActivate tx=${e.translationX.toFixed(1)} ty=${e.translationY.toFixed(1)}`),
onDeactivate: () => console.log('[minVelocity] onDeactivate'),
onFinalize: (e) => console.log(`[minVelocity] onFinalize canceled=${e.canceled}`),
});
return (
<View style={styles.container}>
<Text style={styles.label}>minDistance: 50</Text>
<GestureDetector gesture={minDistPan}>
<View style={[styles.box, { backgroundColor: 'tomato' }]} />
</GestureDetector>
<Text style={styles.label}>activeOffsetX: [-50, 50]</Text>
<GestureDetector gesture={activeOffsetPan}>
<View style={[styles.box, { backgroundColor: 'mediumseagreen' }]} />
</GestureDetector>
<Text style={styles.label}>minVelocity: 800</Text>
<GestureDetector gesture={minVelocityPan}>
<View style={[styles.box, { backgroundColor: 'steelblue' }]} />
</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: 140, height: 140, borderRadius: 12 },
});
```
</details>Pan activation criteria being ignored (#4387)1 parent 33a876f commit bfe7941
1 file changed
Lines changed: 39 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
42 | 42 | | |
43 | 43 | | |
44 | 44 | | |
45 | | - | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
46 | 48 | | |
47 | 49 | | |
48 | 50 | | |
| |||
114 | 116 | | |
115 | 117 | | |
116 | 118 | | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
117 | 122 | | |
118 | 123 | | |
119 | 124 | | |
| |||
139 | 144 | | |
140 | 145 | | |
141 | 146 | | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
142 | 152 | | |
143 | 153 | | |
144 | 154 | | |
| |||
176 | 186 | | |
177 | 187 | | |
178 | 188 | | |
179 | | - | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
180 | 197 | | |
181 | 198 | | |
182 | 199 | | |
| |||
209 | 226 | | |
210 | 227 | | |
211 | 228 | | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
212 | 246 | | |
213 | 247 | | |
214 | 248 | | |
| |||
265 | 299 | | |
266 | 300 | | |
267 | 301 | | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
268 | 305 | | |
269 | 306 | | |
270 | 307 | | |
| |||
0 commit comments