diff --git a/packages/react-native/Libraries/Components/Pressable/Pressable.js b/packages/react-native/Libraries/Components/Pressable/Pressable.js
index 78d32ecbf5f1..4a7d44112cf1 100644
--- a/packages/react-native/Libraries/Components/Pressable/Pressable.js
+++ b/packages/react-native/Libraries/Components/Pressable/Pressable.js
@@ -120,8 +120,10 @@ type PressableBaseProps = Readonly<{
onPressOut?: ?(event: GestureResponderEvent) => unknown,
/**
- * Whether to prevent any other native components from becoming responder
- * while this pressable is responder.
+ * When true, prevents native ancestor views from receiving any touch events
+ * that begin on this Pressable, including pan gestures. Suppression is
+ * unconditional (not gated on JS responder state) to avoid timing races on
+ * fast taps. Does not affect UIGestureRecognizer-based interactions.
*/
blockNativeResponder?: ?boolean,
@@ -334,12 +336,17 @@ function Pressable({
const eventHandlers = usePressability(config);
return (
+ // $FlowFixMe[incompatible-type] blockNativeResponder is an internal prop, intentionally absent from public ViewProps
+ collapsable={false}
+ // Must be false when disabled so touches reach native ancestors. Cannot
+ // guard this on the native side via _isJSResponder — it's set async and
+ // races with touchesBegan on fast taps.
+ blockNativeResponder={disabled !== true && blockNativeResponder}>
{typeof children === 'function' ? children({pressed}) : children}
{__DEV__ ? : null}
diff --git a/packages/react-native/Libraries/NativeComponent/BaseViewConfig.ios.js b/packages/react-native/Libraries/NativeComponent/BaseViewConfig.ios.js
index d22a68642194..4e76214cb692 100644
--- a/packages/react-native/Libraries/NativeComponent/BaseViewConfig.ios.js
+++ b/packages/react-native/Libraries/NativeComponent/BaseViewConfig.ios.js
@@ -247,6 +247,7 @@ const validAttributesForNonEventProps = {
hitSlop: {diff: require('../Utilities/differ/insetsDiffer').default},
collapsable: true,
collapsableChildren: true,
+ blockNativeResponder: true,
filter: filterAttribute,
boxShadow: boxShadowAttribute,
mixBlendMode: true,
diff --git a/packages/react-native/Libraries/Pressability/Pressability.js b/packages/react-native/Libraries/Pressability/Pressability.js
index 0be465bec546..4fd9dc84cd61 100644
--- a/packages/react-native/Libraries/Pressability/Pressability.js
+++ b/packages/react-native/Libraries/Pressability/Pressability.js
@@ -131,8 +131,8 @@ export type PressabilityConfig = Readonly<{
onPressOut?: ?(event: GestureResponderEvent) => unknown,
/**
- * Whether to prevent any other native components from becoming responder
- * while this pressable is responder.
+ * When true, prevents native ancestor views (UIKit responder chain) from
+ * receiving touch events when this Pressable handles a press.
*/
blockNativeResponder?: ?boolean,
}>;
diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm
index 5571b5350bff..6710d1d8afcc 100644
--- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm
+++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm
@@ -672,6 +672,42 @@ - (void)setIsJSResponder:(BOOL)isJSResponder
_isJSResponder = isJSResponder;
}
+- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
+{
+ const auto &viewProps = static_cast(*_props);
+ if (viewProps.blockNativeResponder) {
+ return;
+ }
+ [super touchesBegan:touches withEvent:event];
+}
+
+- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
+{
+ const auto &viewProps = static_cast(*_props);
+ if (viewProps.blockNativeResponder) {
+ return;
+ }
+ [super touchesMoved:touches withEvent:event];
+}
+
+- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
+{
+ const auto &viewProps = static_cast(*_props);
+ if (viewProps.blockNativeResponder) {
+ return;
+ }
+ [super touchesEnded:touches withEvent:event];
+}
+
+- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
+{
+ const auto &viewProps = static_cast(*_props);
+ if (viewProps.blockNativeResponder) {
+ return;
+ }
+ [super touchesCancelled:touches withEvent:event];
+}
+
- (void)finalizeUpdates:(RNComponentViewUpdateMask)updateMask
{
[super finalizeUpdates:updateMask];
diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp
index d3bae3163c09..f27d268222c7 100644
--- a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp
+++ b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp
@@ -424,6 +424,15 @@ BaseViewProps::BaseViewProps(
rawProps,
"removeClippedSubviews",
sourceProps.removeClippedSubviews,
+ false)),
+ blockNativeResponder(
+ ReactNativeFeatureFlags::enableCppPropsIteratorSetter()
+ ? sourceProps.blockNativeResponder
+ : convertRawProp(
+ context,
+ rawProps,
+ "blockNativeResponder",
+ sourceProps.blockNativeResponder,
false)) {}
#define VIEW_EVENT_CASE(eventType) \
@@ -475,6 +484,7 @@ void BaseViewProps::setProp(
RAW_SET_PROP_SWITCH_CASE_BASIC(collapsable);
RAW_SET_PROP_SWITCH_CASE_BASIC(collapsableChildren);
RAW_SET_PROP_SWITCH_CASE_BASIC(removeClippedSubviews);
+ RAW_SET_PROP_SWITCH_CASE_BASIC(blockNativeResponder);
RAW_SET_PROP_SWITCH_CASE_BASIC(cursor);
RAW_SET_PROP_SWITCH_CASE_BASIC(outlineColor);
RAW_SET_PROP_SWITCH_CASE_BASIC(outlineOffset);
diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h
index 7554ba7cad86..978742beaf51 100644
--- a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h
+++ b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h
@@ -108,6 +108,7 @@ class BaseViewProps : public YogaStylableProps, public AccessibilityProps {
bool collapsableChildren{true};
bool removeClippedSubviews{false};
+ bool blockNativeResponder{false};
#pragma mark - Convenience Methods
diff --git a/packages/rn-tester/RNTester/AppDelegate.mm b/packages/rn-tester/RNTester/AppDelegate.mm
index 3f1ea7209bac..c12a5ec6b9ee 100644
--- a/packages/rn-tester/RNTester/AppDelegate.mm
+++ b/packages/rn-tester/RNTester/AppDelegate.mm
@@ -25,6 +25,7 @@
#ifndef RN_DISABLE_OSS_PLUGIN_HEADER
#import
#endif
+#import "NativeExampleViews/RNTNativeTouchReceiverComponentView.h"
#if __has_include()
#define USE_OSS_CODEGEN 1
@@ -171,6 +172,9 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center
if (!dict[@"SampleNativeComponent"]) {
dict[@"SampleNativeComponent"] = NSClassFromString(@"RCTSampleNativeComponentComponentView");
}
+ if (!dict[@"RNTNativeTouchReceiver"]) {
+ dict[@"RNTNativeTouchReceiver"] = [RNTNativeTouchReceiverComponentView class];
+ }
return dict;
}
#endif
diff --git a/packages/rn-tester/RNTester/NativeExampleViews/RNTNativeTouchReceiverComponentView.h b/packages/rn-tester/RNTester/NativeExampleViews/RNTNativeTouchReceiverComponentView.h
new file mode 100644
index 000000000000..9631c034e1c8
--- /dev/null
+++ b/packages/rn-tester/RNTester/NativeExampleViews/RNTNativeTouchReceiverComponentView.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+#pragma once
+
+#import
+#import
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * Fabric component view that receives native touch events and emits onNativeTouch.
+ */
+@interface RNTNativeTouchReceiverComponentView : RCTViewComponentView
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/packages/rn-tester/RNTester/NativeExampleViews/RNTNativeTouchReceiverComponentView.mm b/packages/rn-tester/RNTester/NativeExampleViews/RNTNativeTouchReceiverComponentView.mm
new file mode 100644
index 000000000000..127f572a8274
--- /dev/null
+++ b/packages/rn-tester/RNTester/NativeExampleViews/RNTNativeTouchReceiverComponentView.mm
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+#import "RNTNativeTouchReceiverComponentView.h"
+
+#import
+#import
+#import
+#import
+
+#import
+
+using namespace facebook::react;
+
+@implementation RNTNativeTouchReceiverComponentView
+
++ (ComponentDescriptorProvider)componentDescriptorProvider
+{
+ return concreteComponentDescriptorProvider();
+}
+
++ (void)load
+{
+ [super load];
+}
+
+- (instancetype)initWithFrame:(CGRect)frame
+{
+ if (self = [super initWithFrame:frame]) {
+ static const auto defaultProps = std::make_shared();
+ _props = defaultProps;
+ }
+ return self;
+}
+
+/**
+ * Emits onNativeTouch when UIKit delivers the touch-up event.
+ */
+- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
+{
+ if (_eventEmitter) {
+ auto const &emitter =
+ *std::static_pointer_cast(_eventEmitter);
+ emitter.onNativeTouch({});
+ }
+ [super touchesEnded:touches withEvent:event];
+}
+
+@end
+
+Class RNTNativeTouchReceiverCls(void)
+{
+ return RNTNativeTouchReceiverComponentView.class;
+}
diff --git a/packages/rn-tester/RNTesterPods.xcodeproj/project.pbxproj b/packages/rn-tester/RNTesterPods.xcodeproj/project.pbxproj
index 898cd14e1ca5..cd086800567e 100644
--- a/packages/rn-tester/RNTesterPods.xcodeproj/project.pbxproj
+++ b/packages/rn-tester/RNTesterPods.xcodeproj/project.pbxproj
@@ -18,6 +18,7 @@
8145AE06241172D900A3F8DA /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8145AE05241172D900A3F8DA /* LaunchScreen.storyboard */; };
832F45BB2A8A6E1F0097B4E6 /* SwiftTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 832F45BA2A8A6E1F0097B4E6 /* SwiftTest.swift */; };
A975CA6C2C05EADF0043F72A /* RCTNetworkTaskTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A975CA6B2C05EADE0043F72A /* RCTNetworkTaskTests.m */; };
+ AA000003AABB0003CCDD0003 /* RNTNativeTouchReceiverComponentView.mm in Sources */ = {isa = PBXBuildFile; fileRef = AA000002AABB0002CCDD0002 /* RNTNativeTouchReceiverComponentView.mm */; };
C175B6D9ED9336FB66637943 /* libPods-RNTester.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C706D402EE4AF9BE838CBA9 /* libPods-RNTester.a */; };
CD10C7A5290BD4EB0033E1ED /* RCTEventEmitterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CD10C7A4290BD4EB0033E1ED /* RCTEventEmitterTests.m */; };
E62F11832A5C6580000BF1C8 /* FlexibleSizeExampleView.mm in Sources */ = {isa = PBXBuildFile; fileRef = 27F441E81BEBE5030039B79C /* FlexibleSizeExampleView.mm */; };
@@ -95,6 +96,8 @@
832F45BA2A8A6E1F0097B4E6 /* SwiftTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SwiftTest.swift; path = RNTester/SwiftTest.swift; sourceTree = ""; };
93A243F0D4D5C54911E811C4 /* libPods-RNTesterIntegrationTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RNTesterIntegrationTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
A975CA6B2C05EADE0043F72A /* RCTNetworkTaskTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RCTNetworkTaskTests.m; sourceTree = ""; };
+ AA000001AABB0001CCDD0001 /* RNTNativeTouchReceiverComponentView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNTNativeTouchReceiverComponentView.h; path = RNTester/NativeExampleViews/RNTNativeTouchReceiverComponentView.h; sourceTree = ""; };
+ AA000002AABB0002CCDD0002 /* RNTNativeTouchReceiverComponentView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = RNTNativeTouchReceiverComponentView.mm; path = RNTester/NativeExampleViews/RNTNativeTouchReceiverComponentView.mm; sourceTree = ""; };
AC474BFB29BBD4A1002BDAED /* RNTester.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; name = RNTester.xctestplan; path = RNTester/RNTester.xctestplan; sourceTree = ""; };
B0E70A8A05E03E868F8703FE /* Pods-RNTesterIntegrationTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RNTesterIntegrationTests.release.xcconfig"; path = "Target Support Files/Pods-RNTesterIntegrationTests/Pods-RNTesterIntegrationTests.release.xcconfig"; sourceTree = ""; };
CA59C9994B1822826D8983F0 /* Pods-RNTester.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RNTester.debug.xcconfig"; path = "Target Support Files/Pods-RNTester/Pods-RNTester.debug.xcconfig"; sourceTree = ""; };
@@ -224,6 +227,8 @@
27F441EA1BEBE5030039B79C /* FlexibleSizeExampleView.h */,
272E6B3B1BEA849E001FCF37 /* UpdatePropertiesExampleView.h */,
272E6B3C1BEA849E001FCF37 /* UpdatePropertiesExampleView.mm */,
+ AA000001AABB0001CCDD0001 /* RNTNativeTouchReceiverComponentView.h */,
+ AA000002AABB0002CCDD0002 /* RNTNativeTouchReceiverComponentView.mm */,
);
name = NativeExampleViews;
sourceTree = "";
@@ -727,6 +732,7 @@
files = (
E62F11842A5C6584000BF1C8 /* UpdatePropertiesExampleView.mm in Sources */,
E62F11832A5C6580000BF1C8 /* FlexibleSizeExampleView.mm in Sources */,
+ AA000003AABB0003CCDD0003 /* RNTNativeTouchReceiverComponentView.mm in Sources */,
832F45BB2A8A6E1F0097B4E6 /* SwiftTest.swift in Sources */,
5C60EB1C226440DB0018C04F /* AppDelegate.mm in Sources */,
13B07FC11A68108700A75B9A /* main.m in Sources */,
diff --git a/packages/rn-tester/android/app/src/main/java/com/facebook/react/uiapp/RNTesterApplication.kt b/packages/rn-tester/android/app/src/main/java/com/facebook/react/uiapp/RNTesterApplication.kt
index 2a162c4c2493..bd8d28178afb 100644
--- a/packages/rn-tester/android/app/src/main/java/com/facebook/react/uiapp/RNTesterApplication.kt
+++ b/packages/rn-tester/android/app/src/main/java/com/facebook/react/uiapp/RNTesterApplication.kt
@@ -27,6 +27,7 @@ import com.facebook.react.module.model.ReactModuleInfo
import com.facebook.react.module.model.ReactModuleInfoProvider
import com.facebook.react.uiapp.component.MyLegacyViewManager
import com.facebook.react.uiapp.component.MyNativeViewManager
+import com.facebook.react.uiapp.component.RNTNativeTouchReceiverManager
import com.facebook.react.uiapp.component.ReportFullyDrawnViewManager
import com.facebook.react.uimanager.ReactShadowNode
import com.facebook.react.uimanager.ViewManager
@@ -83,6 +84,7 @@ internal class RNTesterApplication : Application(), ReactApplication {
"RNTMyNativeView",
"RNTMyLegacyNativeView",
"RNTReportFullyDrawnView",
+ "RNTNativeTouchReceiver",
)
override fun createViewManagers(
@@ -92,6 +94,7 @@ internal class RNTesterApplication : Application(), ReactApplication {
MyNativeViewManager(),
MyLegacyViewManager(reactContext),
ReportFullyDrawnViewManager(),
+ RNTNativeTouchReceiverManager(),
)
override fun createViewManager(
@@ -102,6 +105,7 @@ internal class RNTesterApplication : Application(), ReactApplication {
"RNTMyNativeView" -> MyNativeViewManager()
"RNTMyLegacyNativeView" -> MyLegacyViewManager(reactContext)
"RNTReportFullyDrawnView" -> ReportFullyDrawnViewManager()
+ "RNTNativeTouchReceiver" -> RNTNativeTouchReceiverManager()
else -> null
}
}
diff --git a/packages/rn-tester/android/app/src/main/java/com/facebook/react/uiapp/component/RNTNativeTouchReceiverManager.kt b/packages/rn-tester/android/app/src/main/java/com/facebook/react/uiapp/component/RNTNativeTouchReceiverManager.kt
new file mode 100644
index 000000000000..590b0ec30419
--- /dev/null
+++ b/packages/rn-tester/android/app/src/main/java/com/facebook/react/uiapp/component/RNTNativeTouchReceiverManager.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+package com.facebook.react.uiapp.component
+
+import com.facebook.react.module.annotations.ReactModule
+import com.facebook.react.uimanager.ThemedReactContext
+import com.facebook.react.uimanager.ViewGroupManager
+import com.facebook.react.uimanager.ViewManagerDelegate
+import com.facebook.react.viewmanagers.RNTNativeTouchReceiverManagerDelegate
+import com.facebook.react.viewmanagers.RNTNativeTouchReceiverManagerInterface
+
+@ReactModule(name = RNTNativeTouchReceiverManager.REACT_CLASS)
+internal class RNTNativeTouchReceiverManager :
+ ViewGroupManager(),
+ RNTNativeTouchReceiverManagerInterface {
+
+ companion object {
+ const val REACT_CLASS = "RNTNativeTouchReceiver"
+ }
+
+ private val delegate: ViewManagerDelegate =
+ RNTNativeTouchReceiverManagerDelegate(this)
+
+ override fun getDelegate(): ViewManagerDelegate = delegate
+
+ override fun getName(): String = REACT_CLASS
+
+ override fun createViewInstance(reactContext: ThemedReactContext): RNTNativeTouchReceiverView =
+ RNTNativeTouchReceiverView(reactContext)
+}
diff --git a/packages/rn-tester/android/app/src/main/java/com/facebook/react/uiapp/component/RNTNativeTouchReceiverView.kt b/packages/rn-tester/android/app/src/main/java/com/facebook/react/uiapp/component/RNTNativeTouchReceiverView.kt
new file mode 100644
index 000000000000..0f0c90bf5908
--- /dev/null
+++ b/packages/rn-tester/android/app/src/main/java/com/facebook/react/uiapp/component/RNTNativeTouchReceiverView.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+package com.facebook.react.uiapp.component
+
+import android.view.MotionEvent
+import com.facebook.react.bridge.Arguments
+import com.facebook.react.bridge.ReactContext
+import com.facebook.react.bridge.WritableMap
+import com.facebook.react.uimanager.ThemedReactContext
+import com.facebook.react.uimanager.UIManagerHelper
+import com.facebook.react.uimanager.events.Event
+import com.facebook.react.views.view.ReactViewGroup
+
+/**
+ * Native ViewGroup that receives touch events through Android's touch dispatch
+ * and emits onNativeTouch.
+ */
+internal class RNTNativeTouchReceiverView(context: ThemedReactContext) : ReactViewGroup(context) {
+
+ override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
+ val intercepted = super.onInterceptTouchEvent(event)
+ if (event.action == MotionEvent.ACTION_UP) {
+ emitNativeTouchEvent()
+ }
+ return intercepted
+ }
+
+ override fun onTouchEvent(event: MotionEvent): Boolean {
+ val handled = super.onTouchEvent(event)
+ if (event.action == MotionEvent.ACTION_UP) {
+ emitNativeTouchEvent()
+ }
+ return handled
+ }
+
+ private fun emitNativeTouchEvent() {
+ val reactContext = context as ReactContext
+ val surfaceId = UIManagerHelper.getSurfaceId(reactContext)
+ val eventDispatcher = UIManagerHelper.getEventDispatcher(reactContext)
+ eventDispatcher?.dispatchEvent(OnNativeTouchEvent(surfaceId, id))
+ }
+
+ private inner class OnNativeTouchEvent(surfaceId: Int, viewId: Int) :
+ Event(surfaceId, viewId) {
+ override fun getEventName(): String = "topNativeTouch"
+
+ override fun getEventData(): WritableMap = Arguments.createMap()
+ }
+}
diff --git a/packages/rn-tester/js/examples/Pressable/NativeTouchReceiverNativeComponent.js b/packages/rn-tester/js/examples/Pressable/NativeTouchReceiverNativeComponent.js
new file mode 100644
index 000000000000..f4b22e8e9f52
--- /dev/null
+++ b/packages/rn-tester/js/examples/Pressable/NativeTouchReceiverNativeComponent.js
@@ -0,0 +1,24 @@
+/**
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow strict-local
+ * @format
+ */
+
+import type {CodegenTypes, HostComponent, ViewProps} from 'react-native';
+
+import {codegenNativeComponent} from 'react-native';
+
+type NativeProps = Readonly<{
+ ...ViewProps,
+ onNativeTouch?: ?CodegenTypes.DirectEventHandler>,
+}>;
+
+export type NativeTouchReceiverType = HostComponent;
+
+export default codegenNativeComponent(
+ 'RNTNativeTouchReceiver',
+) as NativeTouchReceiverType;
diff --git a/packages/rn-tester/js/examples/Pressable/PressableExample.js b/packages/rn-tester/js/examples/Pressable/PressableExample.js
index b52f9f9ba053..84d713c3a00b 100644
--- a/packages/rn-tester/js/examples/Pressable/PressableExample.js
+++ b/packages/rn-tester/js/examples/Pressable/PressableExample.js
@@ -10,6 +10,7 @@
import type {RNTesterModule} from '../../types/RNTesterTypes';
+import RNTNativeTouchReceiver from './NativeTouchReceiverNativeComponent';
import * as PressableExampleFbInternal from './PressableExampleFbInternal';
import * as React from 'react';
import {
@@ -20,6 +21,7 @@ import {
PlatformColor,
Pressable,
StyleSheet,
+ Switch,
Text,
View,
} from 'react-native';
@@ -275,6 +277,89 @@ function PressableDisabled() {
);
}
+const scenarioReceiverStyle = {
+ padding: 16,
+ backgroundColor: '#fff3cd',
+ borderRadius: 8,
+ borderWidth: StyleSheet.hairlineWidth,
+ borderColor: '#e0bb5a',
+ marginBottom: 4,
+ alignItems: 'flex-start' as const,
+};
+
+function PressableBlockNativeResponderExample() {
+ const [blockNativeResponder, setBlockNativeResponder] = useState(false);
+ const [log, setLog] = useState>([]);
+ const onNativeTouch = () =>
+ setLog(prev => [...prev, 'NativeTouchReceiver.onNativeTouch']);
+ const onPress = () => setLog(prev => [...prev, 'Pressable.onPress']);
+
+ return (
+
+
+
+ blockNativeResponder
+
+
+
+
+
+
+ Press Me
+
+
+
+
+ setLog([])}>
+ Clear log
+
+
+
+
+ );
+}
+
+const monoFont = Platform.OS === 'ios' ? 'Menlo' : 'monospace';
+
+function LogBox({lines}: {lines: Array}) {
+ return (
+
+ {lines.length === 0 ? (
+
+ tap to see events
+
+ ) : (
+ lines.map((line, i) => (
+
+ {line}
+
+ ))
+ )}
+
+ );
+}
+
const styles = StyleSheet.create({
row: {
justifyContent: 'center',
@@ -661,6 +746,15 @@ const examples = [
);
},
},
+ {
+ title: 'Pressable with blockNativeResponder',
+ name: 'prevent-native-propagation',
+ description:
+ 'Pressable inside a native parent view. Without blockNativeResponder the touch leaks up the native touch event system to the parent.' as string,
+ render: function (): React.Node {
+ return ;
+ },
+ },
...PressableExampleFbInternal.examples,
];
diff --git a/packages/rn-tester/package.json b/packages/rn-tester/package.json
index 8056b295df1c..8a5517c9e52d 100644
--- a/packages/rn-tester/package.json
+++ b/packages/rn-tester/package.json
@@ -47,6 +47,9 @@
"components": {
"RNTMyNativeView": {
"className": "RNTMyNativeViewComponentView"
+ },
+ "RNTNativeTouchReceiver": {
+ "className": "RNTNativeTouchReceiverComponentView"
}
}
}
diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api
index 205202f898f6..69d0a0abf2a2 100644
--- a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api
+++ b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api
@@ -1804,6 +1804,7 @@ class facebook::react::BaseViewEventEmitter : public facebook::react::TouchEvent
class facebook::react::BaseViewProps : public facebook::react::YogaStylableProps, public facebook::react::AccessibilityProps {
public BaseViewProps() = default;
public BaseViewProps(const facebook::react::PropsParserContext& context, const facebook::react::BaseViewProps& sourceProps, const facebook::react::RawProps& rawProps, const std::function& filterObjectKeys = nullptr);
+ public bool blockNativeResponder;
public bool collapsable;
public bool collapsableChildren;
public bool getClipsContentToBounds() const;
diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api
index 59443fbd585d..ea9953eb1dd7 100644
--- a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api
+++ b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api
@@ -1798,6 +1798,7 @@ class facebook::react::BaseViewEventEmitter : public facebook::react::TouchEvent
class facebook::react::BaseViewProps : public facebook::react::YogaStylableProps, public facebook::react::AccessibilityProps {
public BaseViewProps() = default;
public BaseViewProps(const facebook::react::PropsParserContext& context, const facebook::react::BaseViewProps& sourceProps, const facebook::react::RawProps& rawProps, const std::function& filterObjectKeys = nullptr);
+ public bool blockNativeResponder;
public bool collapsable;
public bool collapsableChildren;
public bool getClipsContentToBounds() const;
diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api
index 0f5f39b20abf..b030139446bd 100644
--- a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api
+++ b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api
@@ -1802,6 +1802,7 @@ class facebook::react::BaseViewEventEmitter : public facebook::react::TouchEvent
class facebook::react::BaseViewProps : public facebook::react::YogaStylableProps, public facebook::react::AccessibilityProps {
public BaseViewProps() = default;
public BaseViewProps(const facebook::react::PropsParserContext& context, const facebook::react::BaseViewProps& sourceProps, const facebook::react::RawProps& rawProps, const std::function& filterObjectKeys = nullptr);
+ public bool blockNativeResponder;
public bool collapsable;
public bool collapsableChildren;
public bool getClipsContentToBounds() const;
diff --git a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api
index cc3c6c0e38da..dfc72cdac8aa 100644
--- a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api
+++ b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api
@@ -4391,6 +4391,7 @@ class facebook::react::BaseViewEventEmitter : public facebook::react::TouchEvent
class facebook::react::BaseViewProps : public facebook::react::YogaStylableProps, public facebook::react::AccessibilityProps {
public BaseViewProps() = default;
public BaseViewProps(const facebook::react::PropsParserContext& context, const facebook::react::BaseViewProps& sourceProps, const facebook::react::RawProps& rawProps, const std::function& filterObjectKeys = nullptr);
+ public bool blockNativeResponder;
public bool collapsable;
public bool collapsableChildren;
public bool getClipsContentToBounds() const;
diff --git a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api
index bcd1ef1969f3..4bef41ed4f17 100644
--- a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api
+++ b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api
@@ -4374,6 +4374,7 @@ class facebook::react::BaseViewEventEmitter : public facebook::react::TouchEvent
class facebook::react::BaseViewProps : public facebook::react::YogaStylableProps, public facebook::react::AccessibilityProps {
public BaseViewProps() = default;
public BaseViewProps(const facebook::react::PropsParserContext& context, const facebook::react::BaseViewProps& sourceProps, const facebook::react::RawProps& rawProps, const std::function& filterObjectKeys = nullptr);
+ public bool blockNativeResponder;
public bool collapsable;
public bool collapsableChildren;
public bool getClipsContentToBounds() const;
diff --git a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api
index baa00077d2b5..5196f1d6a153 100644
--- a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api
+++ b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api
@@ -4389,6 +4389,7 @@ class facebook::react::BaseViewEventEmitter : public facebook::react::TouchEvent
class facebook::react::BaseViewProps : public facebook::react::YogaStylableProps, public facebook::react::AccessibilityProps {
public BaseViewProps() = default;
public BaseViewProps(const facebook::react::PropsParserContext& context, const facebook::react::BaseViewProps& sourceProps, const facebook::react::RawProps& rawProps, const std::function& filterObjectKeys = nullptr);
+ public bool blockNativeResponder;
public bool collapsable;
public bool collapsableChildren;
public bool getClipsContentToBounds() const;
diff --git a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api
index 575079ae0c35..3281b715918b 100644
--- a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api
+++ b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api
@@ -1133,6 +1133,7 @@ class facebook::react::BaseViewProps : public facebook::react::YogaStylableProps
public Float outlineWidth;
public Float shadowOpacity;
public Float shadowRadius;
+ public bool blockNativeResponder;
public bool collapsable;
public bool collapsableChildren;
public bool getClipsContentToBounds() const;
diff --git a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api
index 81906b49e192..39e6a4cea6bb 100644
--- a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api
+++ b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api
@@ -1128,6 +1128,7 @@ class facebook::react::BaseViewProps : public facebook::react::YogaStylableProps
public Float outlineWidth;
public Float shadowOpacity;
public Float shadowRadius;
+ public bool blockNativeResponder;
public bool collapsable;
public bool collapsableChildren;
public bool getClipsContentToBounds() const;
diff --git a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api
index ccc6128c0ef7..aebec6f73246 100644
--- a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api
+++ b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api
@@ -1131,6 +1131,7 @@ class facebook::react::BaseViewProps : public facebook::react::YogaStylableProps
public Float outlineWidth;
public Float shadowOpacity;
public Float shadowRadius;
+ public bool blockNativeResponder;
public bool collapsable;
public bool collapsableChildren;
public bool getClipsContentToBounds() const;