Skip to content

Commit c8ee392

Browse files
committed
Merge branch 'main' into @mbert/native-types
2 parents bbb0720 + f1de825 commit c8ee392

29 files changed

Lines changed: 331 additions & 101 deletions

apps/common-app/src/new_api/tests/nestedTouchables/index.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export default function NestedTouchablesExample() {
1212
const [log, setLog] = useState<string[]>([]);
1313

1414
const pushLog = (message: string) => {
15+
console.log(message);
1516
setLog((prev) =>
1617
[...prev, `[${new Date().toLocaleTimeString()}] ${message}`].slice(-6)
1718
);
@@ -20,11 +21,13 @@ export default function NestedTouchablesExample() {
2021
const outerTap = useTapGesture({
2122
runOnJS: true,
2223
onActivate: () => pushLog('outer tap gesture'),
24+
testID: 'outer-tap',
2325
});
2426

2527
const innerTap = useTapGesture({
2628
runOnJS: true,
2729
onActivate: () => pushLog('inner tap gesture'),
30+
testID: 'inner-tap',
2831
});
2932

3033
return (
@@ -40,6 +43,11 @@ export default function NestedTouchablesExample() {
4043

4144
<Touchable
4245
style={[styles.layer, styles.outerTouchable]}
46+
testID="outer-touchable"
47+
activeUnderlayOpacity={0.3}
48+
onPressIn={() => pushLog('outer press in')}
49+
onPressOut={() => pushLog('outer press out')}
50+
onLongPress={() => pushLog('outer long press')}
4351
onPress={() => pushLog('outer Touchable')}>
4452
<Text style={styles.layerLabel}>Outer Touchable</Text>
4553

@@ -49,6 +57,11 @@ export default function NestedTouchablesExample() {
4957

5058
<Touchable
5159
style={[styles.layer, styles.innerTouchable]}
60+
testID="inner-touchable"
61+
activeUnderlayOpacity={0.3}
62+
onPressIn={() => pushLog('inner press in')}
63+
onPressOut={() => pushLog('inner press out')}
64+
onLongPress={() => pushLog('inner long press')}
5265
onPress={() => pushLog('inner Touchable')}>
5366
<Text style={styles.layerLabel}>Inner Touchable</Text>
5467
</Touchable>

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

Lines changed: 30 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import android.view.MotionEvent
99
import android.view.MotionEvent.PointerCoords
1010
import android.view.MotionEvent.PointerProperties
1111
import android.view.View
12-
import androidx.core.view.isNotEmpty
1312
import com.facebook.react.bridge.Arguments
1413
import com.facebook.react.bridge.ReactContext
1514
import com.facebook.react.bridge.ReadableMap
@@ -48,6 +47,26 @@ open class GestureHandler {
4847
}
4948
}
5049

50+
/**
51+
* The view whose coordinate space should be used when reporting event positions to JS.
52+
*
53+
* Handlers attached via the V3 NativeDetector are registered against the DetectorView wrapper,
54+
* which never carries user-applied transforms — those live on its child. When the detector has
55+
* exactly one child we descend into it so reported coordinates match the visible (transformed)
56+
* view, the same coordinate space V2 and the V3 VirtualGestureDetector report in. With
57+
* multiple children there is no JS-side way to disambiguate which child caught the pointer,
58+
* so we keep the detector itself as the reference frame.
59+
*/
60+
val coordinateView: View?
61+
get() {
62+
val v = view
63+
return if (v is RNGestureHandlerDetectorView && v.childCount == 1) {
64+
v.getChildAt(0)
65+
} else {
66+
v
67+
}
68+
}
69+
5170
var state = STATE_UNDETERMINED
5271
private set
5372
var x = 0f
@@ -387,42 +406,13 @@ open class GestureHandler {
387406

388407
numberOfPointers = adaptedTransformedEvent.pointerCount
389408

390-
// TODO: this is likely wrong, and the transformed event itself should be
391-
// in the coordinate system of the child view, but I'm not sure of the
392-
// consequences
393-
val detectorView = hostDetectorView
394-
if (detectorView != null && view == detectorView && detectorView.isNotEmpty()) {
395-
val outPoint = PointF()
396-
var foundChild = false
397-
398-
for (i in 0 until detectorView.childCount) {
399-
val child = detectorView.getChildAt(i)
400-
GestureHandlerOrchestrator.transformPointToChildViewCoords(
401-
adaptedTransformedEvent.x,
402-
adaptedTransformedEvent.y,
403-
detectorView,
404-
child,
405-
outPoint,
406-
)
407-
if (isWithinBounds(child, outPoint.x, outPoint.y)) {
408-
x = outPoint.x
409-
y = outPoint.y
410-
isWithinBounds = true
411-
foundChild = true
412-
break
413-
}
414-
}
415-
416-
if (!foundChild) {
417-
x = adaptedTransformedEvent.x
418-
y = adaptedTransformedEvent.y
419-
isWithinBounds = false
420-
}
421-
} else {
422-
x = adaptedTransformedEvent.x
423-
y = adaptedTransformedEvent.y
424-
isWithinBounds = isWithinBounds(view, x, y)
425-
}
409+
x = adaptedTransformedEvent.x
410+
y = adaptedTransformedEvent.y
411+
// The orchestrator transforms incoming events into the coordinate space of the detector's
412+
// child (when the handler is attached to a NativeDetector wrapper), so bounds-checking must
413+
// also use that child rather than the wrapper, otherwise hit-testing would ignore the user's
414+
// transforms applied to the visible view.
415+
isWithinBounds = isWithinBounds(coordinateView, x, y)
426416

427417
if (shouldCancelWhenOutside) {
428418
if (!isWithinBounds && (state == STATE_ACTIVE || state == STATE_BEGAN)) {
@@ -693,6 +683,8 @@ open class GestureHandler {
693683
return interactionController?.shouldHandlerBeCancelledBy(this, handler) ?: false
694684
}
695685

686+
open fun shouldBeginWithRecordedHandlers(recorded: List<GestureHandler>): Boolean = true
687+
696688
fun isWithinBounds(view: View?, posX: Float, posY: Float): Boolean {
697689
if (RNSVGHitTester.isSvgElement(view!!)) {
698690
return RNSVGHitTester.hitTest(view, posX, posY)
@@ -870,7 +862,7 @@ open class GestureHandler {
870862
* This method modifies and transforms the received point.
871863
*/
872864
protected fun transformPoint(point: PointF): PointF =
873-
orchestrator?.transformPointToViewCoords(this.view, point) ?: run {
865+
orchestrator?.transformPointToViewCoords(coordinateView, point) ?: run {
874866
point.x = Float.NaN
875867
point.y = Float.NaN
876868
point

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ class GestureHandlerOrchestrator(
297297
}
298298

299299
val action = sourceEvent.actionMasked
300-
val event = transformEventToViewCoords(handler.view, MotionEvent.obtain(sourceEvent))
300+
val event = transformEventToViewCoords(handler.coordinateView, MotionEvent.obtain(sourceEvent))
301301

302302
if (handler.needsPointerData) {
303303
handler.updatePointerData(event, sourceEvent)
@@ -448,11 +448,16 @@ class GestureHandlerOrchestrator(
448448
return
449449
}
450450

451-
gestureHandlers.add(handler)
452451
handler.isActive = false
453452
handler.isAwaiting = false
454453
handler.activationIndex = Int.MAX_VALUE
455454
handler.prepare(view, this)
455+
456+
if (!handler.shouldBeginWithRecordedHandlers(gestureHandlers)) {
457+
handler.cancel()
458+
}
459+
460+
gestureHandlers.add(handler)
456461
}
457462

458463
private fun isViewOverflowingParent(view: View): Boolean {

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ class NativeViewGestureHandler : GestureHandler() {
8383

8484
override fun shouldBeCancelledBy(handler: GestureHandler): Boolean = !disallowInterruption
8585

86+
override fun shouldBeginWithRecordedHandlers(recorded: List<GestureHandler>): Boolean =
87+
hook.shouldBeginWithRecordedHandlers(recorded, this)
88+
8689
override fun onPrepare() {
8790
when (val view = view) {
8891
is NativeViewGestureHandlerHook -> this.hook = view
@@ -271,6 +274,16 @@ class NativeViewGestureHandler : GestureHandler() {
271274
*/
272275
fun shouldCancelRootViewGestureHandlerIfNecessary() = false
273276

277+
/**
278+
* Called when the handler is being recorded by the orchestrator, before any pointer events
279+
* are delivered. Returning `false` cancels the handler immediately.
280+
*
281+
* @param recorded handlers already recorded for the current touch
282+
* @param handler the handler being recorded
283+
*/
284+
fun shouldBeginWithRecordedHandlers(recorded: List<GestureHandler>, handler: NativeViewGestureHandler): Boolean =
285+
true
286+
274287
/**
275288
* Passes the event down to the underlying view using the correct method.
276289
*/

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ import com.facebook.react.uimanager.style.BorderStyle
4242
import com.facebook.react.uimanager.style.LogicalEdge
4343
import com.facebook.react.viewmanagers.RNGestureHandlerButtonManagerDelegate
4444
import com.facebook.react.viewmanagers.RNGestureHandlerButtonManagerInterface
45+
import com.swmansion.gesturehandler.core.GestureHandler
46+
import com.swmansion.gesturehandler.core.HoverGestureHandler
4547
import com.swmansion.gesturehandler.core.NativeViewGestureHandler
4648
import com.swmansion.gesturehandler.react.RNGestureHandlerButtonViewManager.ButtonViewGroup
4749

@@ -738,6 +740,16 @@ class RNGestureHandlerButtonViewManager :
738740
isTouched = false
739741
}
740742

743+
override fun shouldBeginWithRecordedHandlers(
744+
recorded: List<GestureHandler>,
745+
handler: NativeViewGestureHandler,
746+
): Boolean = recorded.all {
747+
it.shouldRecognizeSimultaneously(handler) ||
748+
handler.shouldRecognizeSimultaneously(it) ||
749+
it.view == this ||
750+
it is HoverGestureHandler
751+
}
752+
741753
private fun tryFreeingResponder() {
742754
if (touchResponder === this) {
743755
touchResponder = null

packages/react-native-gesture-handler/apple/Handlers/RNForceTouchHandler.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ - (void)updateConfig:(NSDictionary *)config
170170
- (RNGestureHandlerEventExtraData *)eventExtraData:(RNForceTouchGestureRecognizer *)recognizer
171171
{
172172
return [RNGestureHandlerEventExtraData forForce:recognizer.force
173-
forPosition:[recognizer locationInView:recognizer.view]
173+
forPosition:[recognizer locationInView:self.coordinateView]
174174
withAbsolutePosition:[recognizer locationInView:recognizer.view.window]
175175
withNumberOfTouches:recognizer.numberOfTouches
176176
withPointerType:_pointerType];

packages/react-native-gesture-handler/apple/Handlers/RNHoverHandler.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ - (void)setCurrentPointerType:(RNGestureHandlerPointerType)pointerType
177177

178178
- (RNGestureHandlerEventExtraData *)eventExtraData:(UIGestureRecognizer *)recognizer
179179
{
180-
return [RNGestureHandlerEventExtraData forPosition:[recognizer locationInView:recognizer.view]
180+
return [RNGestureHandlerEventExtraData forPosition:[recognizer locationInView:self.coordinateView]
181181
withAbsolutePosition:[recognizer locationInView:recognizer.view.window]
182182
withPointerType:_pointerType];
183183
}

packages/react-native-gesture-handler/apple/Handlers/RNLongPressHandler.m

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
287287

288288
- (RNGestureHandlerEventExtraData *)eventExtraData:(UIGestureRecognizer *)recognizer
289289
{
290-
return [RNGestureHandlerEventExtraData forPosition:[recognizer locationInView:recognizer.view]
290+
return [RNGestureHandlerEventExtraData forPosition:[recognizer locationInView:self.coordinateView]
291291
withAbsolutePosition:[recognizer locationInView:recognizer.view.window]
292292
withNumberOfTouches:recognizer.numberOfTouches
293293
withDuration:[(RNBetterLongPressGestureRecognizer *)recognizer getDuration]
@@ -298,7 +298,7 @@ - (RNGestureHandlerEventExtraData *)eventExtraData:(UIGestureRecognizer *)recogn
298298

299299
- (RNGestureHandlerEventExtraData *)eventExtraData:(NSGestureRecognizer *)recognizer
300300
{
301-
return [RNGestureHandlerEventExtraData forPosition:[recognizer locationInView:recognizer.view]
301+
return [RNGestureHandlerEventExtraData forPosition:[recognizer locationInView:self.coordinateView]
302302
withAbsolutePosition:[recognizer locationInView:recognizer.view.window.contentView]
303303
withNumberOfTouches:1
304304
withDuration:[(RNBetterLongPressGestureRecognizer *)recognizer getDuration]

packages/react-native-gesture-handler/apple/Handlers/RNNativeViewHandler.mm

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -253,11 +253,6 @@ - (void)handleTouchDown:(UIView *)sender forEvent:(UIEvent *)event
253253
withExtraData:[RNGestureHandlerEventExtraData forPointerInside:YES
254254
withNumberOfTouches:event.allTouches.count
255255
withPointerType:_pointerType]];
256-
257-
[self sendActiveStateEventIfChangedForView:sender
258-
extraData:[RNGestureHandlerEventExtraData forPointerInside:YES
259-
withNumberOfTouches:event.allTouches.count
260-
withPointerType:_pointerType]];
261256
}
262257

263258
- (void)handleTouchUpOutside:(UIView *)sender forEvent:(UIEvent *)event
@@ -275,11 +270,12 @@ - (void)handleTouchUpOutside:(UIView *)sender forEvent:(UIEvent *)event
275270

276271
- (void)handleTouchUpInside:(UIView *)sender forEvent:(UIEvent *)event
277272
{
278-
[self sendEventsInState:RNGestureHandlerStateEnd
279-
forViewWithTag:sender.reactTag
280-
withExtraData:[RNGestureHandlerEventExtraData forPointerInside:YES
281-
withNumberOfTouches:event.allTouches.count
282-
withPointerType:_pointerType]];
273+
RNGestureHandlerEventExtraData *extraData = [RNGestureHandlerEventExtraData forPointerInside:YES
274+
withNumberOfTouches:event.allTouches.count
275+
withPointerType:_pointerType];
276+
277+
[self sendActiveStateEventIfChangedForView:sender extraData:extraData];
278+
[self sendEventsInState:RNGestureHandlerStateEnd forViewWithTag:sender.reactTag withExtraData:extraData];
283279
}
284280

285281
- (void)handleDragExit:(UIView *)sender forEvent:(UIEvent *)event

packages/react-native-gesture-handler/apple/Handlers/RNPanHandler.m

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,7 @@ - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
452452
#if TARGET_OS_OSX
453453
- (RNGestureHandlerEventExtraData *)eventExtraData:(NSPanGestureRecognizer *)recognizer
454454
{
455-
return [RNGestureHandlerEventExtraData forPan:[recognizer locationInView:recognizer.view]
455+
return [RNGestureHandlerEventExtraData forPan:[recognizer locationInView:self.coordinateView]
456456
withAbsolutePosition:[recognizer locationInView:recognizer.view.window.contentView]
457457
withTranslation:[recognizer translationInView:recognizer.view.window.contentView]
458458
withVelocity:[recognizer velocityInView:recognizer.view.window.contentView]
@@ -466,7 +466,7 @@ - (RNGestureHandlerEventExtraData *)eventExtraData:(UIPanGestureRecognizer *)rec
466466
RNBetterPanGestureRecognizer *panRecognizer = (RNBetterPanGestureRecognizer *)recognizer;
467467

468468
return [RNGestureHandlerEventExtraData
469-
forPan:[recognizer locationInView:recognizer.view]
469+
forPan:[recognizer locationInView:self.coordinateView]
470470
withAbsolutePosition:[recognizer locationInView:recognizer.view.window]
471471
withTranslation:[recognizer translationInView:recognizer.view.window]
472472
withVelocity:[recognizer velocityInView:recognizer.view.window]

0 commit comments

Comments
 (0)