Skip to content

Commit a116ba8

Browse files
authored
[Web] Don't cancel buttons when pointer moves inside (#4100)
## Description Prevents the buttons from being canceled when the pointer is moved inside the button. Note that this only works with a mouse, since the browser will cancel any events with no `touchAction` during any drag gesture. ## Test plan |Before|After| |-|-| |<video src="https://github.com/user-attachments/assets/08b342f8-24cc-43e9-a240-844641f97552" />|<video src="https://github.com/user-attachments/assets/5cdc15bf-2518-4602-a180-ffd4ae48745e" />| <details> <summary>Expand</summary> ```jsx import React, { useState } from 'react'; import { StyleSheet, Text, View } from 'react-native'; import { Touchable } from 'react-native-gesture-handler'; export default function EmptyExample() { const [isActive, setIsActive] = useState(false); return ( <View style={styles.container}> <Touchable style={[styles.button, isActive && styles.buttonActive]} onPress={() => console.log('pressed')} onPressIn={() => setIsActive(true)} onPressOut={() => setIsActive(false)} activeScale={1.2}> <Text style={styles.label}>{isActive ? 'Highlighted' : 'Press me'}</Text> </Touchable> <Text style={styles.hint}> Press the button, then slowly drag your pointer away from it. </Text> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 24, gap: 24, }, button: { backgroundColor: '#ddd', paddingHorizontal: 32, paddingVertical: 16, borderRadius: 8, }, buttonActive: { backgroundColor: '#f97316', }, label: { fontSize: 20, fontWeight: '600', }, hint: { textAlign: 'center', opacity: 0.6, fontSize: 14, }, }); ``` </details>
1 parent 2154bfd commit a116ba8

2 files changed

Lines changed: 18 additions & 6 deletions

File tree

packages/react-native-gesture-handler/src/web/handlers/NativeViewGestureHandler.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,12 @@ export default class NativeViewGestureHandler extends GestureHandler {
109109
const dy = this.startY - lastCoords.y;
110110
const distSq = dx * dx + dy * dy;
111111

112-
if (distSq >= this.minDistSq) {
113-
if (this.buttonRole && this.state === State.ACTIVE) {
114-
this.cancel();
115-
} else if (!this.buttonRole && this.state === State.BEGAN) {
116-
this.activate();
117-
}
112+
if (
113+
distSq >= this.minDistSq &&
114+
!this.buttonRole &&
115+
this.state === State.BEGAN
116+
) {
117+
this.activate();
118118
}
119119
}
120120

packages/react-native-gesture-handler/src/web/tools/PointerEventManager.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,18 @@ export default class PointerEventManager extends EventManager<HTMLElement> {
162162
const adaptedEvent: AdaptedEvent = this.mapEvent(event, EventTypes.LEAVE);
163163

164164
this.onPointerMoveOut(adaptedEvent);
165+
166+
// When the view is not capturing the pointer (e.g. when `role="button"`),
167+
// `pointermove` stops firing once the pointer leaves the view's bounds, so
168+
// the out-of-bounds detection in `pointerMoveCallback` never runs. Fall back
169+
// to the DOM `pointerleave` event for any tracked, in-bounds pointer.
170+
if (
171+
this.trackedPointers.has(event.pointerId) &&
172+
this.pointersInBounds.indexOf(event.pointerId) >= 0
173+
) {
174+
this.onPointerLeave(adaptedEvent);
175+
this.markAsOutOfBounds(event.pointerId);
176+
}
165177
};
166178

167179
private lostPointerCaptureCallback = (event: PointerEvent) => {

0 commit comments

Comments
 (0)