Skip to content

Commit 348e37e

Browse files
Merge branch 'main' into rolkrado/apply-rnrepo
2 parents 192b5bf + 9d256ae commit 348e37e

24 files changed

Lines changed: 217 additions & 143 deletions

apps/common-app/App.tsx

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
/* eslint-disable @eslint-react/no-nested-component-definitions */
2+
/* eslint-disable @eslint-react/no-nested-components */
13
import AsyncStorage from '@react-native-async-storage/async-storage';
24
import type { ParamListBase } from '@react-navigation/native';
35
import { NavigationContainer } from '@react-navigation/native';
@@ -11,8 +13,8 @@ import { useEffect, useState } from 'react';
1113
import { Dimensions, Platform, StyleSheet, Text, View } from 'react-native';
1214
import {
1315
GestureHandlerRootView,
14-
RectButton,
1516
Switch,
17+
Touchable,
1618
} from 'react-native-gesture-handler';
1719
import { useSafeAreaInsets } from 'react-native-safe-area-context';
1820

@@ -123,7 +125,7 @@ export default function App() {
123125
<MainScreenItem
124126
name={item.name}
125127
onPressItem={(name) => navigate(navigation, name)}
126-
enabled={!item.unsupportedPlatforms?.has(Platform.OS)}
128+
disabled={!!item.unsupportedPlatforms?.has(Platform.OS)}
127129
/>
128130
)}
129131
renderSectionHeader={({ section: { sectionTitle } }) => (
@@ -156,7 +158,9 @@ export default function App() {
156158
}
157159
return (
158160
<View style={styles.settings}>
159-
<RectButton
161+
<Touchable
162+
androidRipple={{}}
163+
activeUnderlayOpacity={Platform.OS !== 'android' ? 0.1 : 0}
160164
style={styles.settingsButton}
161165
onPress={() => {
162166
updateKeepSetting(!openLastExample);
@@ -175,8 +179,10 @@ export default function App() {
175179
}}
176180
/>
177181
</View>
178-
</RectButton>
179-
<RectButton
182+
</Touchable>
183+
<Touchable
184+
androidRipple={{}}
185+
activeUnderlayOpacity={Platform.OS !== 'android' ? 0.1 : 0}
180186
style={styles.settingsButton}
181187
onPress={() => {
182188
updateVersionSetting(!showLegacyVersion);
@@ -195,28 +201,34 @@ export default function App() {
195201
}}
196202
/>
197203
</View>
198-
</RectButton>
204+
</Touchable>
199205
</View>
200206
);
201207
}
202208

203209
interface MainScreenItemProps {
204210
name: string;
205211
onPressItem: (name: string) => void;
206-
enabled: boolean;
212+
disabled: boolean;
207213
}
208214

209-
function MainScreenItem({ name, onPressItem, enabled }: MainScreenItemProps) {
215+
function MainScreenItem({
216+
name,
217+
onPressItem,
218+
disabled,
219+
}: MainScreenItemProps) {
210220
return (
211-
<RectButton
212-
enabled={enabled}
213-
style={[styles.button, !enabled && styles.unavailableExample]}
221+
<Touchable
222+
disabled={disabled}
223+
style={[styles.button, disabled && styles.unavailableExample]}
224+
androidRipple={{}}
225+
activeUnderlayOpacity={Platform.OS !== 'android' ? 0.1 : 0}
214226
onPress={() => onPressItem(name)}>
215227
<Text style={styles.text}>{name}</Text>
216-
{Platform.OS !== 'macos' && enabled && (
228+
{Platform.OS !== 'macos' && !disabled && (
217229
<Icon name="chevron-small-right" size={24} color="#bbb" />
218230
)}
219-
</RectButton>
231+
</Touchable>
220232
);
221233
}
222234
}

packages/docs-gesture-handler/docs/gestures/use-native-gesture.mdx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,14 @@ disallowInterruption: boolean | SharedValue<boolean>;
115115

116116
When `true`, this handler cancels all other gesture handlers when it activates.
117117

118+
### yieldsToNativeGestures
119+
120+
```ts
121+
yieldsToNativeGestures: boolean | SharedValue<boolean>;
122+
```
123+
124+
Composes with [`disallowInterruption`](#disallowinterruption). When both are `true`, this handler still cancels other gestures (such as `Pan` or `Tap`) on activation but allows other native gestures — for example a wrapping `ScrollView` — to interrupt it. No-op when `disallowInterruption` is `false`. Defaults to `false`.
125+
118126
<BaseGestureConfig />
119127

120128
## Callbacks

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

Lines changed: 28 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)) {
@@ -872,7 +862,7 @@ open class GestureHandler {
872862
* This method modifies and transforms the received point.
873863
*/
874864
protected fun transformPoint(point: PointF): PointF =
875-
orchestrator?.transformPointToViewCoords(this.view, point) ?: run {
865+
orchestrator?.transformPointToViewCoords(coordinateView, point) ?: run {
876866
point.x = Float.NaN
877867
point.y = Float.NaN
878868
point

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

Lines changed: 1 addition & 1 deletion
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)

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

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import com.facebook.react.views.text.ReactTextView
1515
import com.facebook.react.views.textinput.ReactEditText
1616
import com.facebook.react.views.view.ReactViewGroup
1717
import com.swmansion.gesturehandler.react.RNGestureHandlerButtonViewManager
18+
import com.swmansion.gesturehandler.react.RNGestureHandlerRootHelper
1819
import com.swmansion.gesturehandler.react.events.eventbuilders.NativeGestureHandlerEventDataBuilder
1920
import com.swmansion.gesturehandler.react.isScreenReaderOn
2021

@@ -29,6 +30,14 @@ class NativeViewGestureHandler : GestureHandler() {
2930
var disallowInterruption = false
3031
private set
3132

33+
/**
34+
* Composes with [disallowInterruption]. When both are `true`, the handler still resists
35+
* generic gesture peers (Pan, Tap, etc.) but yields to other [NativeViewGestureHandler] peers
36+
* such as a wrapping ScrollView. No-op when [disallowInterruption] is `false`.
37+
*/
38+
var yieldsToNativeGestures = false
39+
private set
40+
3241
private var hook: NativeViewGestureHandlerHook = defaultHook
3342

3443
private data class ActiveUpdateSnapshot(val pointerInside: Boolean, val numberOfPointers: Int, val pointerType: Int)
@@ -43,6 +52,7 @@ class NativeViewGestureHandler : GestureHandler() {
4352
super.resetConfig()
4453
shouldActivateOnStart = DEFAULT_SHOULD_ACTIVATE_ON_START
4554
disallowInterruption = DEFAULT_DISALLOW_INTERRUPTION
55+
yieldsToNativeGestures = DEFAULT_YIELDS_TO_NATIVE_GESTURES
4656
shouldCancelWhenOutside = DEFAULT_SHOULD_CANCEL_WHEN_OUTSIDE
4757
}
4858

@@ -61,12 +71,15 @@ class NativeViewGestureHandler : GestureHandler() {
6171
// For the `disallowInterruption` to work correctly we need to check the property when
6272
// accessed as a peer, because simultaneous recognizers can be set on either side of the
6373
// connection.
64-
if (handler.state == STATE_ACTIVE && handler.disallowInterruption) {
74+
if (handler.state == STATE_ACTIVE &&
75+
handler.disallowInterruption &&
76+
!handler.yieldsToNativeGestures
77+
) {
6578
// other handler is active and it disallows interruption, we don't want to get into its way
6679
return false
6780
}
6881
}
69-
val canBeInterrupted = !disallowInterruption
82+
val canBeInterrupted = canBeInterruptedBy(handler)
7083
val otherState = handler.state
7184
return if (state == STATE_ACTIVE && otherState == STATE_ACTIVE && canBeInterrupted) {
7285
// if both handlers are active and the current handler can be interrupted it we return `false`
@@ -81,7 +94,17 @@ class NativeViewGestureHandler : GestureHandler() {
8194
// otherwise we can only return `true` if already in an active state
8295
}
8396

84-
override fun shouldBeCancelledBy(handler: GestureHandler): Boolean = !disallowInterruption
97+
override fun shouldBeCancelledBy(handler: GestureHandler): Boolean = canBeInterruptedBy(handler)
98+
99+
/**
100+
* Whether this handler permits [other] to take over the touch stream, given its
101+
* `disallowInterruption` and `yieldsToNativeGestures` configuration.
102+
*/
103+
fun canBeInterruptedBy(other: GestureHandler): Boolean = !disallowInterruption ||
104+
(
105+
yieldsToNativeGestures &&
106+
(other is NativeViewGestureHandler || other is RNGestureHandlerRootHelper.RootViewGestureHandler)
107+
)
85108

86109
override fun shouldBeginWithRecordedHandlers(recorded: List<GestureHandler>): Boolean =
87110
hook.shouldBeginWithRecordedHandlers(recorded, this)
@@ -203,20 +226,25 @@ class NativeViewGestureHandler : GestureHandler() {
203226
if (config.hasKey(KEY_DISALLOW_INTERRUPTION)) {
204227
handler.disallowInterruption = config.getBoolean(KEY_DISALLOW_INTERRUPTION)
205228
}
229+
if (config.hasKey(KEY_YIELDS_TO_NATIVE_GESTURES)) {
230+
handler.yieldsToNativeGestures = config.getBoolean(KEY_YIELDS_TO_NATIVE_GESTURES)
231+
}
206232
}
207233

208234
override fun createEventBuilder(handler: NativeViewGestureHandler) = NativeGestureHandlerEventDataBuilder(handler)
209235

210236
companion object {
211237
private const val KEY_SHOULD_ACTIVATE_ON_START = "shouldActivateOnStart"
212238
private const val KEY_DISALLOW_INTERRUPTION = "disallowInterruption"
239+
private const val KEY_YIELDS_TO_NATIVE_GESTURES = "yieldsToNativeGestures"
213240
}
214241
}
215242

216243
companion object {
217244
private const val DEFAULT_SHOULD_CANCEL_WHEN_OUTSIDE = true
218245
private const val DEFAULT_SHOULD_ACTIVATE_ON_START = false
219246
private const val DEFAULT_DISALLOW_INTERRUPTION = false
247+
private const val DEFAULT_YIELDS_TO_NATIVE_GESTURES = false
220248

221249
private fun tryIntercept(view: View, event: MotionEvent) = view is ViewGroup && view.onInterceptTouchEvent(event)
222250

@@ -317,11 +345,10 @@ class NativeViewGestureHandler : GestureHandler() {
317345
}
318346
}
319347

320-
// recognize alongside every handler besides RootViewGestureHandler, which is a private inner class
321-
// of RNGestureHandlerRootHelper so no explicit type checks, but its tag is always negative
348+
// recognize alongside every handler besides RootViewGestureHandler;
322349
// also if other handler is NativeViewGestureHandler then don't override the default implementation
323350
override fun shouldRecognizeSimultaneously(handler: GestureHandler) =
324-
handler.tag > 0 && handler !is NativeViewGestureHandler
351+
handler !is RNGestureHandlerRootHelper.RootViewGestureHandler && handler !is NativeViewGestureHandler
325352

326353
override fun wantsToHandleEventBeforeActivation() = true
327354

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ class RNGestureHandlerInteractionManager : GestureHandlerInteractionController {
5050

5151
override fun shouldHandlerBeCancelledBy(handler: GestureHandler, otherHandler: GestureHandler): Boolean {
5252
if (otherHandler is NativeViewGestureHandler) {
53-
return otherHandler.disallowInterruption
53+
return !otherHandler.canBeInterruptedBy(handler)
5454
}
5555

5656
if (otherHandler is RNGestureHandlerRootHelper.RootViewGestureHandler) {

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 & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -56,16 +56,6 @@ - (void)handleGesture:(UIHoverGestureRecognizer *)recognizer
5656
[_gestureHandler handleGesture:self];
5757
}
5858

59-
- (void)triggerAction
60-
{
61-
[_gestureHandler handleGesture:self fromReset:NO fromManualStateChange:NO];
62-
}
63-
64-
- (void)cancel
65-
{
66-
self.enabled = NO;
67-
}
68-
6959
- (void)reset
7060
{
7161
[super reset];
@@ -177,7 +167,7 @@ - (void)setCurrentPointerType:(RNGestureHandlerPointerType)pointerType
177167

178168
- (RNGestureHandlerEventExtraData *)eventExtraData:(UIGestureRecognizer *)recognizer
179169
{
180-
return [RNGestureHandlerEventExtraData forPosition:[recognizer locationInView:recognizer.view]
170+
return [RNGestureHandlerEventExtraData forPosition:[recognizer locationInView:self.coordinateView]
181171
withAbsolutePosition:[recognizer locationInView:recognizer.view.window]
182172
withPointerType:_pointerType];
183173
}

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]

0 commit comments

Comments
 (0)