From f0dcd8b86c9b92c750341e351cd0c204d34b3b6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Thu, 25 Jun 2026 11:02:48 +0200 Subject: [PATCH 01/30] test setup --- packages/rn-tester/RNTester/AppDelegate.mm | 4 + .../RNTNativeTouchReceiverComponentView.h | 25 +++ .../RNTNativeTouchReceiverComponentView.mm | 61 +++++++ .../RNTesterPods.xcodeproj/project.pbxproj | 20 ++- .../NativeTouchReceiverNativeComponent.js | 24 +++ .../js/examples/Pressable/PressableExample.js | 157 ++++++++++++++++++ packages/rn-tester/package.json | 3 + 7 files changed, 292 insertions(+), 2 deletions(-) create mode 100644 packages/rn-tester/RNTester/NativeExampleViews/RNTNativeTouchReceiverComponentView.h create mode 100644 packages/rn-tester/RNTester/NativeExampleViews/RNTNativeTouchReceiverComponentView.mm create mode 100644 packages/rn-tester/js/examples/Pressable/NativeTouchReceiverNativeComponent.js 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..c92e564ba611 --- /dev/null +++ b/packages/rn-tester/RNTester/NativeExampleViews/RNTNativeTouchReceiverComponentView.h @@ -0,0 +1,25 @@ +/* + * 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 whose touchesEnded: fires onNativeTouch. + * Used as the parent wrapper in the blockNativeResponder repro: + * + * ... + * + */ +@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..7bc3711a66f1 --- /dev/null +++ b/packages/rn-tester/RNTester/NativeExampleViews/RNTNativeTouchReceiverComponentView.mm @@ -0,0 +1,61 @@ +/* + * 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; +} + +/** + * touchesEnded: fires when UIKit delivers the touch-up event through the + * native responder chain. Even when a Pressable child is the JS responder, + * this still fires because RCTSurfaceTouchHandler sets cancelsTouchesInView=NO. + * That is the bug this component helps reproduce. + */ +- (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..4f4e0ee6ae00 100644 --- a/packages/rn-tester/RNTesterPods.xcodeproj/project.pbxproj +++ b/packages/rn-tester/RNTesterPods.xcodeproj/project.pbxproj @@ -22,6 +22,7 @@ CD10C7A5290BD4EB0033E1ED /* RCTEventEmitterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CD10C7A4290BD4EB0033E1ED /* RCTEventEmitterTests.m */; }; E62F11832A5C6580000BF1C8 /* FlexibleSizeExampleView.mm in Sources */ = {isa = PBXBuildFile; fileRef = 27F441E81BEBE5030039B79C /* FlexibleSizeExampleView.mm */; }; E62F11842A5C6584000BF1C8 /* UpdatePropertiesExampleView.mm in Sources */ = {isa = PBXBuildFile; fileRef = 272E6B3C1BEA849E001FCF37 /* UpdatePropertiesExampleView.mm */; }; + AA000003AABB0003CCDD0003 /* RNTNativeTouchReceiverComponentView.mm in Sources */ = {isa = PBXBuildFile; fileRef = AA000002AABB0002CCDD0002 /* RNTNativeTouchReceiverComponentView.mm */; }; E7C1241A22BEC44B00DA25C0 /* RNTesterIntegrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E7C1241922BEC44B00DA25C0 /* RNTesterIntegrationTests.m */; }; E7DB20D122B2BAA6005AC45F /* RCTBundleURLProviderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E7DB20A922B2BAA3005AC45F /* RCTBundleURLProviderTests.m */; }; E7DB20D222B2BAA6005AC45F /* RCTModuleInitNotificationRaceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E7DB20AA22B2BAA3005AC45F /* RCTModuleInitNotificationRaceTests.m */; }; @@ -81,6 +82,8 @@ 20B55D3C33B683598D2A4424 /* Pods-RNTesterIntegrationTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RNTesterIntegrationTests.debug.xcconfig"; path = "Target Support Files/Pods-RNTesterIntegrationTests/Pods-RNTesterIntegrationTests.debug.xcconfig"; sourceTree = ""; }; 272E6B3B1BEA849E001FCF37 /* UpdatePropertiesExampleView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = UpdatePropertiesExampleView.h; path = RNTester/NativeExampleViews/UpdatePropertiesExampleView.h; sourceTree = ""; }; 272E6B3C1BEA849E001FCF37 /* UpdatePropertiesExampleView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = UpdatePropertiesExampleView.mm; path = RNTester/NativeExampleViews/UpdatePropertiesExampleView.mm; 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 = ""; }; 27F441E81BEBE5030039B79C /* FlexibleSizeExampleView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = FlexibleSizeExampleView.mm; path = RNTester/NativeExampleViews/FlexibleSizeExampleView.mm; sourceTree = ""; }; 27F441EA1BEBE5030039B79C /* FlexibleSizeExampleView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FlexibleSizeExampleView.h; path = RNTester/NativeExampleViews/FlexibleSizeExampleView.h; sourceTree = ""; }; 2B312B0EEE90BA411618B015 /* libPods-RNTesterUnitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RNTesterUnitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -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 */, @@ -943,7 +949,10 @@ IPHONEOS_DEPLOYMENT_TARGET = 15.1; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; - OTHER_CFLAGS = "$(inherited)"; + OTHER_CFLAGS = ( + "$(inherited)", + "-DRCT_REMOVE_LEGACY_ARCH=1", + ); OTHER_CPLUSPLUSFLAGS = ( "$(OTHER_CFLAGS)", "-DFOLLY_NO_CONFIG", @@ -951,11 +960,13 @@ "-DFOLLY_USE_LIBCPP=1", "-DFOLLY_CFG_NO_COROUTINES=1", "-DFOLLY_HAVE_CLOCK_GETTIME=1", + "-DRCT_REMOVE_LEGACY_ARCH=1", ); OTHER_LDFLAGS = ( "-ObjC", "-lc++", ); + PODFILE_DIR = "$(SRCROOT)"; REACT_NATIVE_PATH = "${PODS_ROOT}/../../react-native"; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG"; @@ -1036,7 +1047,10 @@ ); IPHONEOS_DEPLOYMENT_TARGET = 15.1; MTL_ENABLE_DEBUG_INFO = NO; - OTHER_CFLAGS = "$(inherited)"; + OTHER_CFLAGS = ( + "$(inherited)", + "-DRCT_REMOVE_LEGACY_ARCH=1", + ); OTHER_CPLUSPLUSFLAGS = ( "$(OTHER_CFLAGS)", "-DFOLLY_NO_CONFIG", @@ -1044,11 +1058,13 @@ "-DFOLLY_USE_LIBCPP=1", "-DFOLLY_CFG_NO_COROUTINES=1", "-DFOLLY_HAVE_CLOCK_GETTIME=1", + "-DRCT_REMOVE_LEGACY_ARCH=1", ); OTHER_LDFLAGS = ( "-ObjC", "-lc++", ); + PODFILE_DIR = "$(SRCROOT)"; REACT_NATIVE_PATH = "${PODS_ROOT}/../../react-native"; SDKROOT = iphoneos; USE_HERMES = true; 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..f6a7f1f80557 100644 --- a/packages/rn-tester/js/examples/Pressable/PressableExample.js +++ b/packages/rn-tester/js/examples/Pressable/PressableExample.js @@ -11,6 +11,7 @@ import type {RNTesterModule} from '../../types/RNTesterTypes'; import * as PressableExampleFbInternal from './PressableExampleFbInternal'; +import RNTNativeTouchReceiver from './NativeTouchReceiverNativeComponent'; import * as React from 'react'; import { Alert, @@ -275,6 +276,146 @@ function PressableDisabled() { ); } +/** + * Repro for: Pressable does not consume native touches on iOS/iPadOS. + * + * Topology: NativeTouchReceiver (UIView with touchesEnded:) is the PARENT. + * Pressable is the CHILD and the UIKit hit-tested target. + * + * Bug: tapping the Pressable makes it the JS responder (onPress fires), but + * the touch also bubbles up the UIKit responder chain to NativeTouchReceiver + * because RCTSurfaceTouchHandler has cancelsTouchesInView=NO — so + * touchesEnded: is never cancelled on the ancestor, and onNativeTouch fires too. + * + * Expected after blockNativeResponder fix: + * blockNativeResponder=false → both onPress AND onNativeTouch fire (bug) + * blockNativeResponder=true → only onPress fires (fixed) + */ +function PressableBlockNativeResponderExample() { + const [log, setLog] = useState>([]); + + function emit(msg: string) { + setLog(prev => [msg, ...prev].slice(0, 10)); + } + + return ( + + + Tap the Pressable button inside each row.{'\n\n'} + [default] + {' — BUG: both Pressable.onPress (JS) and NativeTouchReceiver.onNativeTouch'} + {' (UIKit responder chain) fire for the same tap.\n'} + [blocked] + {' — FIXED: only Pressable.onPress fires.'} + + + + {log.length === 0 ? ( + + events will appear here + + ) : ( + log.map((line, i) => ( + + {line} + + )) + )} + + + + blockNativeResponder=undefined (default) + + + emit('[default] NativeTouchReceiver.touchesEnded: ← native leaked') + }> + emit('[default] Pressable.onPress ✓')}> + Tap me + + + + + blockNativeResponder=true + + + emit( + '[blocked] NativeTouchReceiver.touchesEnded: ← UNEXPECTED, fix not working', + ) + }> + emit('[blocked] Pressable.onPress ✓')}> + Tap me + + + + ); +} + +const blockNativeStyles = StyleSheet.create({ + description: { + fontSize: 13, + color: '#333', + marginBottom: 10, + lineHeight: 19, + }, + bold: { + fontWeight: '700', + }, + logBox: { + backgroundColor: '#1a1a1a', + borderRadius: 6, + padding: 10, + minHeight: 100, + marginBottom: 12, + }, + logPlaceholder: { + color: '#555', + fontSize: 12, + fontStyle: 'italic', + }, + logLine: { + color: '#b5f5a0', + fontSize: 12, + fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace', + lineHeight: 17, + }, + sectionHeader: { + fontSize: 13, + fontWeight: '600', + color: '#444', + marginTop: 10, + marginBottom: 4, + }, + receiver: { + padding: 16, + backgroundColor: '#fff3cd', + borderRadius: 8, + borderWidth: StyleSheet.hairlineWidth, + borderColor: '#e0bb5a', + marginBottom: 4, + alignItems: 'flex-start', + }, + pressable: { + backgroundColor: '#0a84ff', + borderRadius: 6, + paddingVertical: 10, + paddingHorizontal: 20, + }, + pressableText: { + color: '#fff', + fontWeight: '600', + fontSize: 15, + }, +}); + const styles = StyleSheet.create({ row: { justifyContent: 'center', @@ -661,6 +802,22 @@ const examples = [ ); }, }, + { + title: 'blockNativeResponder — press leaks to native parent (iOS repro)', + name: 'block-native-responder', + description: + ('Repro for: Pressable does not consume the native touch on iOS/iPadOS. ' + + 'A Pressable sits inside a NativeTouchReceiver (plain UIView with ' + + 'touchesEnded: overridden). Tapping the Pressable makes it the JS ' + + 'responder (onPress fires), but the touch also bubbles up the UIKit ' + + 'responder chain so the parent NativeTouchReceiver fires too. ' + + 'With blockNativeResponder={true} and the fix applied, only ' + + 'Pressable.onPress should fire.') as string, + platform: 'ios', + 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" } } } From 6ddc12092b026e1b4453d52e17c502c6b4c5692e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Thu, 25 Jun 2026 12:59:10 +0200 Subject: [PATCH 02/30] basic fix --- .../Components/Pressable/Pressable.js | 3 +- .../Components/View/ViewPropTypes.js | 7 ++++ .../NativeComponent/BaseViewConfig.ios.js | 1 + .../View/RCTViewComponentView.mm | 42 +++++++++++++++++++ .../Mounting/RCTComponentViewProtocol.h | 1 + .../Fabric/Mounting/RCTMountingManager.mm | 2 +- .../Mounting/UIView+ComponentViewProtocol.mm | 5 +++ .../components/view/BaseViewProps.cpp | 10 +++++ .../renderer/components/view/BaseViewProps.h | 1 + .../RNTNativeTouchReceiverComponentView.mm | 1 + 10 files changed, 71 insertions(+), 2 deletions(-) diff --git a/packages/react-native/Libraries/Components/Pressable/Pressable.js b/packages/react-native/Libraries/Components/Pressable/Pressable.js index 78d32ecbf5f1..a1499418c7d9 100644 --- a/packages/react-native/Libraries/Components/Pressable/Pressable.js +++ b/packages/react-native/Libraries/Components/Pressable/Pressable.js @@ -339,7 +339,8 @@ function Pressable({ {...eventHandlers} ref={mergedRef} style={typeof style === 'function' ? style({pressed}) : style} - collapsable={false}> + collapsable={false} + blockNativeResponder={blockNativeResponder}> {typeof children === 'function' ? children({pressed}) : children} {__DEV__ ? : null} diff --git a/packages/react-native/Libraries/Components/View/ViewPropTypes.js b/packages/react-native/Libraries/Components/View/ViewPropTypes.js index 6c4be3a025ca..8a9ae4de6997 100644 --- a/packages/react-native/Libraries/Components/View/ViewPropTypes.js +++ b/packages/react-native/Libraries/Components/View/ViewPropTypes.js @@ -502,6 +502,13 @@ type ViewBaseProps = Readonly<{ */ removeClippedSubviews?: ?boolean, + /** + * When true, prevents native ancestor views (UIKit responder chain) from + * receiving touch events when this view is the active JS responder. + * Requires that the Fabric gesture recognizer has already claimed the touch. + */ + blockNativeResponder?: ?boolean, + /** * Defines the order in which descendant elements receive accessibility focus. * The elements in the array represent nativeID values for the respective 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/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm index 5571b5350bff..afb2b178d867 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,48 @@ - (void)setIsJSResponder:(BOOL)isJSResponder _isJSResponder = isJSResponder; } +- (void)setIsJSResponder:(BOOL)isJSResponder blockNativeResponder:(BOOL)blockNativeResponder +{ + _isJSResponder = isJSResponder; +} + +- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event +{ + const auto &viewProps = static_cast(*_props); + NSLog(@"[BNR] touchesBegan %p [%@] blockNative=%d", self, NSStringFromClass([self class]), viewProps.blockNativeResponder); + 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/React/Fabric/Mounting/RCTComponentViewProtocol.h b/packages/react-native/React/Fabric/Mounting/RCTComponentViewProtocol.h index f3b35ecb36a9..8e78d70c6774 100644 --- a/packages/react-native/React/Fabric/Mounting/RCTComponentViewProtocol.h +++ b/packages/react-native/React/Fabric/Mounting/RCTComponentViewProtocol.h @@ -124,6 +124,7 @@ typedef NS_OPTIONS(NSInteger, RNComponentViewUpdateMask) { - (BOOL)isJSResponder; - (void)setIsJSResponder:(BOOL)isJSResponder; +- (void)setIsJSResponder:(BOOL)isJSResponder blockNativeResponder:(BOOL)blockNativeResponder; /* * This is broken. Do not use. diff --git a/packages/react-native/React/Fabric/Mounting/RCTMountingManager.mm b/packages/react-native/React/Fabric/Mounting/RCTMountingManager.mm index 68662f06d59b..70469a5ea45c 100644 --- a/packages/react-native/React/Fabric/Mounting/RCTMountingManager.mm +++ b/packages/react-native/React/Fabric/Mounting/RCTMountingManager.mm @@ -278,7 +278,7 @@ - (void)setIsJSResponder:(BOOL)isJSResponder ReactTag reactTag = shadowView.tag; RCTExecuteOnMainQueue(^{ UIView *componentView = [self->_componentViewRegistry findComponentViewWithTag:reactTag]; - [componentView setIsJSResponder:isJSResponder]; + [componentView setIsJSResponder:isJSResponder blockNativeResponder:blockNativeResponder]; }); } diff --git a/packages/react-native/React/Fabric/Mounting/UIView+ComponentViewProtocol.mm b/packages/react-native/React/Fabric/Mounting/UIView+ComponentViewProtocol.mm index 36e5f64f4b19..68d31c4d1417 100644 --- a/packages/react-native/React/Fabric/Mounting/UIView+ComponentViewProtocol.mm +++ b/packages/react-native/React/Fabric/Mounting/UIView+ComponentViewProtocol.mm @@ -151,6 +151,11 @@ - (void)setIsJSResponder:(BOOL)isJSResponder // Default implementation does nothing. } +- (void)setIsJSResponder:(BOOL)isJSResponder blockNativeResponder:(BOOL)blockNativeResponder +{ + // Default implementation does nothing. +} + - (void)setPropKeysManagedByAnimated_DO_NOT_USE_THIS_IS_BROKEN:(nullable NSSet *)propKeys { // Default implementation does nothing. 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/NativeExampleViews/RNTNativeTouchReceiverComponentView.mm b/packages/rn-tester/RNTester/NativeExampleViews/RNTNativeTouchReceiverComponentView.mm index 7bc3711a66f1..d2a6b9e86e9c 100644 --- a/packages/rn-tester/RNTester/NativeExampleViews/RNTNativeTouchReceiverComponentView.mm +++ b/packages/rn-tester/RNTester/NativeExampleViews/RNTNativeTouchReceiverComponentView.mm @@ -45,6 +45,7 @@ - (instancetype)initWithFrame:(CGRect)frame */ - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { + NSLog(@"[BNR] NativeTouchReceiver touchesEnded FIRED"); if (_eventEmitter) { auto const &emitter = *std::static_pointer_cast(_eventEmitter); From 2f5d043dd50cea9f65f47bf89aebb97277ea739a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Thu, 25 Jun 2026 13:14:00 +0200 Subject: [PATCH 03/30] simplify --- .../Mounting/ComponentViews/View/RCTViewComponentView.mm | 6 ------ .../React/Fabric/Mounting/RCTComponentViewProtocol.h | 1 - .../React/Fabric/Mounting/RCTMountingManager.mm | 2 +- .../React/Fabric/Mounting/UIView+ComponentViewProtocol.mm | 5 ----- .../RNTNativeTouchReceiverComponentView.mm | 1 - 5 files changed, 1 insertion(+), 14 deletions(-) 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 afb2b178d867..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,15 +672,9 @@ - (void)setIsJSResponder:(BOOL)isJSResponder _isJSResponder = isJSResponder; } -- (void)setIsJSResponder:(BOOL)isJSResponder blockNativeResponder:(BOOL)blockNativeResponder -{ - _isJSResponder = isJSResponder; -} - - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { const auto &viewProps = static_cast(*_props); - NSLog(@"[BNR] touchesBegan %p [%@] blockNative=%d", self, NSStringFromClass([self class]), viewProps.blockNativeResponder); if (viewProps.blockNativeResponder) { return; } diff --git a/packages/react-native/React/Fabric/Mounting/RCTComponentViewProtocol.h b/packages/react-native/React/Fabric/Mounting/RCTComponentViewProtocol.h index 8e78d70c6774..f3b35ecb36a9 100644 --- a/packages/react-native/React/Fabric/Mounting/RCTComponentViewProtocol.h +++ b/packages/react-native/React/Fabric/Mounting/RCTComponentViewProtocol.h @@ -124,7 +124,6 @@ typedef NS_OPTIONS(NSInteger, RNComponentViewUpdateMask) { - (BOOL)isJSResponder; - (void)setIsJSResponder:(BOOL)isJSResponder; -- (void)setIsJSResponder:(BOOL)isJSResponder blockNativeResponder:(BOOL)blockNativeResponder; /* * This is broken. Do not use. diff --git a/packages/react-native/React/Fabric/Mounting/RCTMountingManager.mm b/packages/react-native/React/Fabric/Mounting/RCTMountingManager.mm index 70469a5ea45c..68662f06d59b 100644 --- a/packages/react-native/React/Fabric/Mounting/RCTMountingManager.mm +++ b/packages/react-native/React/Fabric/Mounting/RCTMountingManager.mm @@ -278,7 +278,7 @@ - (void)setIsJSResponder:(BOOL)isJSResponder ReactTag reactTag = shadowView.tag; RCTExecuteOnMainQueue(^{ UIView *componentView = [self->_componentViewRegistry findComponentViewWithTag:reactTag]; - [componentView setIsJSResponder:isJSResponder blockNativeResponder:blockNativeResponder]; + [componentView setIsJSResponder:isJSResponder]; }); } diff --git a/packages/react-native/React/Fabric/Mounting/UIView+ComponentViewProtocol.mm b/packages/react-native/React/Fabric/Mounting/UIView+ComponentViewProtocol.mm index 68d31c4d1417..36e5f64f4b19 100644 --- a/packages/react-native/React/Fabric/Mounting/UIView+ComponentViewProtocol.mm +++ b/packages/react-native/React/Fabric/Mounting/UIView+ComponentViewProtocol.mm @@ -151,11 +151,6 @@ - (void)setIsJSResponder:(BOOL)isJSResponder // Default implementation does nothing. } -- (void)setIsJSResponder:(BOOL)isJSResponder blockNativeResponder:(BOOL)blockNativeResponder -{ - // Default implementation does nothing. -} - - (void)setPropKeysManagedByAnimated_DO_NOT_USE_THIS_IS_BROKEN:(nullable NSSet *)propKeys { // Default implementation does nothing. diff --git a/packages/rn-tester/RNTester/NativeExampleViews/RNTNativeTouchReceiverComponentView.mm b/packages/rn-tester/RNTester/NativeExampleViews/RNTNativeTouchReceiverComponentView.mm index d2a6b9e86e9c..7bc3711a66f1 100644 --- a/packages/rn-tester/RNTester/NativeExampleViews/RNTNativeTouchReceiverComponentView.mm +++ b/packages/rn-tester/RNTester/NativeExampleViews/RNTNativeTouchReceiverComponentView.mm @@ -45,7 +45,6 @@ - (instancetype)initWithFrame:(CGRect)frame */ - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { - NSLog(@"[BNR] NativeTouchReceiver touchesEnded FIRED"); if (_eventEmitter) { auto const &emitter = *std::static_pointer_cast(_eventEmitter); From 2954034f03494872d341ff605a984c5142bb2b97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Thu, 25 Jun 2026 14:20:44 +0200 Subject: [PATCH 04/30] rename to `preventNativePropagation` --- .../Libraries/Components/Pressable/Pressable.js | 10 +++++----- .../Libraries/Components/View/ViewPropTypes.js | 2 +- .../NativeComponent/BaseViewConfig.ios.js | 2 +- .../Libraries/Pressability/Pressability.js | 4 ++-- .../ComponentViews/View/RCTViewComponentView.mm | 8 ++++---- .../renderer/components/view/BaseViewProps.cpp | 10 +++++----- .../renderer/components/view/BaseViewProps.h | 2 +- .../RNTNativeTouchReceiverComponentView.h | 4 ++-- .../js/examples/Pressable/PressableExample.js | 16 ++++++++-------- 9 files changed, 29 insertions(+), 29 deletions(-) diff --git a/packages/react-native/Libraries/Components/Pressable/Pressable.js b/packages/react-native/Libraries/Components/Pressable/Pressable.js index a1499418c7d9..43b17b217795 100644 --- a/packages/react-native/Libraries/Components/Pressable/Pressable.js +++ b/packages/react-native/Libraries/Components/Pressable/Pressable.js @@ -123,7 +123,7 @@ type PressableBaseProps = Readonly<{ * Whether to prevent any other native components from becoming responder * while this pressable is responder. */ - blockNativeResponder?: ?boolean, + preventNativePropagation?: ?boolean, /** * Either view styles or a function that receives a boolean reflecting whether @@ -191,7 +191,7 @@ function Pressable({ 'aria-expanded': ariaExpanded, 'aria-label': ariaLabel, 'aria-selected': ariaSelected, - blockNativeResponder, + preventNativePropagation, cancelable, children, delayHoverIn, @@ -303,12 +303,12 @@ function Pressable({ onPressOut(event); } }, - blockNativeResponder, + preventNativePropagation, }), [ android_disableSound, android_rippleConfig, - blockNativeResponder, + preventNativePropagation, cancelable, delayHoverIn, delayHoverOut, @@ -340,7 +340,7 @@ function Pressable({ ref={mergedRef} style={typeof style === 'function' ? style({pressed}) : style} collapsable={false} - blockNativeResponder={blockNativeResponder}> + preventNativePropagation={preventNativePropagation}> {typeof children === 'function' ? children({pressed}) : children} {__DEV__ ? : null} diff --git a/packages/react-native/Libraries/Components/View/ViewPropTypes.js b/packages/react-native/Libraries/Components/View/ViewPropTypes.js index 8a9ae4de6997..dd1a81448727 100644 --- a/packages/react-native/Libraries/Components/View/ViewPropTypes.js +++ b/packages/react-native/Libraries/Components/View/ViewPropTypes.js @@ -507,7 +507,7 @@ type ViewBaseProps = Readonly<{ * receiving touch events when this view is the active JS responder. * Requires that the Fabric gesture recognizer has already claimed the touch. */ - blockNativeResponder?: ?boolean, + preventNativePropagation?: ?boolean, /** * Defines the order in which descendant elements receive accessibility focus. diff --git a/packages/react-native/Libraries/NativeComponent/BaseViewConfig.ios.js b/packages/react-native/Libraries/NativeComponent/BaseViewConfig.ios.js index 4e76214cb692..04a18f30b72d 100644 --- a/packages/react-native/Libraries/NativeComponent/BaseViewConfig.ios.js +++ b/packages/react-native/Libraries/NativeComponent/BaseViewConfig.ios.js @@ -247,7 +247,7 @@ const validAttributesForNonEventProps = { hitSlop: {diff: require('../Utilities/differ/insetsDiffer').default}, collapsable: true, collapsableChildren: true, - blockNativeResponder: true, + preventNativePropagation: 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..ecdf08176d48 100644 --- a/packages/react-native/Libraries/Pressability/Pressability.js +++ b/packages/react-native/Libraries/Pressability/Pressability.js @@ -134,7 +134,7 @@ export type PressabilityConfig = Readonly<{ * Whether to prevent any other native components from becoming responder * while this pressable is responder. */ - blockNativeResponder?: ?boolean, + preventNativePropagation?: ?boolean, }>; export type EventHandlers = Readonly<{ @@ -477,7 +477,7 @@ export default class Pressability { this._handleLongPress(event); }, delayLongPress + delayPressIn); - return this._config.blockNativeResponder === true; + return this._config.preventNativePropagation === true; }, onResponderMove: (event: GestureResponderEvent): void => { 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 6710d1d8afcc..bd435e984b6a 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -675,7 +675,7 @@ - (void)setIsJSResponder:(BOOL)isJSResponder - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { const auto &viewProps = static_cast(*_props); - if (viewProps.blockNativeResponder) { + if (viewProps.preventNativePropagation) { return; } [super touchesBegan:touches withEvent:event]; @@ -684,7 +684,7 @@ - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { const auto &viewProps = static_cast(*_props); - if (viewProps.blockNativeResponder) { + if (viewProps.preventNativePropagation) { return; } [super touchesMoved:touches withEvent:event]; @@ -693,7 +693,7 @@ - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { const auto &viewProps = static_cast(*_props); - if (viewProps.blockNativeResponder) { + if (viewProps.preventNativePropagation) { return; } [super touchesEnded:touches withEvent:event]; @@ -702,7 +702,7 @@ - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { const auto &viewProps = static_cast(*_props); - if (viewProps.blockNativeResponder) { + if (viewProps.preventNativePropagation) { return; } [super touchesCancelled:touches withEvent:event]; 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 f27d268222c7..14b3818258ad 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp @@ -425,14 +425,14 @@ BaseViewProps::BaseViewProps( "removeClippedSubviews", sourceProps.removeClippedSubviews, false)), - blockNativeResponder( + preventNativePropagation( ReactNativeFeatureFlags::enableCppPropsIteratorSetter() - ? sourceProps.blockNativeResponder + ? sourceProps.preventNativePropagation : convertRawProp( context, rawProps, - "blockNativeResponder", - sourceProps.blockNativeResponder, + "preventNativePropagation", + sourceProps.preventNativePropagation, false)) {} #define VIEW_EVENT_CASE(eventType) \ @@ -484,7 +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(preventNativePropagation); 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 978742beaf51..22858a949f36 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h @@ -108,7 +108,7 @@ class BaseViewProps : public YogaStylableProps, public AccessibilityProps { bool collapsableChildren{true}; bool removeClippedSubviews{false}; - bool blockNativeResponder{false}; + bool preventNativePropagation{false}; #pragma mark - Convenience Methods diff --git a/packages/rn-tester/RNTester/NativeExampleViews/RNTNativeTouchReceiverComponentView.h b/packages/rn-tester/RNTester/NativeExampleViews/RNTNativeTouchReceiverComponentView.h index c92e564ba611..9f1ee182f88c 100644 --- a/packages/rn-tester/RNTester/NativeExampleViews/RNTNativeTouchReceiverComponentView.h +++ b/packages/rn-tester/RNTester/NativeExampleViews/RNTNativeTouchReceiverComponentView.h @@ -14,9 +14,9 @@ NS_ASSUME_NONNULL_BEGIN /** * Fabric component view whose touchesEnded: fires onNativeTouch. - * Used as the parent wrapper in the blockNativeResponder repro: + * Used as the parent wrapper in the preventNativePropagation repro: * - * ... + * ... * */ @interface RNTNativeTouchReceiverComponentView : RCTViewComponentView diff --git a/packages/rn-tester/js/examples/Pressable/PressableExample.js b/packages/rn-tester/js/examples/Pressable/PressableExample.js index f6a7f1f80557..275a86a72f12 100644 --- a/packages/rn-tester/js/examples/Pressable/PressableExample.js +++ b/packages/rn-tester/js/examples/Pressable/PressableExample.js @@ -287,9 +287,9 @@ function PressableDisabled() { * because RCTSurfaceTouchHandler has cancelsTouchesInView=NO — so * touchesEnded: is never cancelled on the ancestor, and onNativeTouch fires too. * - * Expected after blockNativeResponder fix: - * blockNativeResponder=false → both onPress AND onNativeTouch fire (bug) - * blockNativeResponder=true → only onPress fires (fixed) + * Expected after preventNativePropagation fix: + * preventNativePropagation={false} → both onPress AND onNativeTouch fire (bug) + * preventNativePropagation={true} → only onPress fires (fixed) */ function PressableBlockNativeResponderExample() { const [log, setLog] = useState>([]); @@ -324,7 +324,7 @@ function PressableBlockNativeResponderExample() { - blockNativeResponder=undefined (default) + {'Default (preventNativePropagation={false})'} - blockNativeResponder=true + {'preventNativePropagation={true}'} emit('[blocked] Pressable.onPress ✓')}> Tap me @@ -803,7 +803,7 @@ const examples = [ }, }, { - title: 'blockNativeResponder — press leaks to native parent (iOS repro)', + title: 'preventNativePropagation — press leaks to native parent (iOS repro)', name: 'block-native-responder', description: ('Repro for: Pressable does not consume the native touch on iOS/iPadOS. ' + @@ -811,7 +811,7 @@ const examples = [ 'touchesEnded: overridden). Tapping the Pressable makes it the JS ' + 'responder (onPress fires), but the touch also bubbles up the UIKit ' + 'responder chain so the parent NativeTouchReceiver fires too. ' + - 'With blockNativeResponder={true} and the fix applied, only ' + + 'With preventNativePropagation={true} and the fix applied, only ' + 'Pressable.onPress should fire.') as string, platform: 'ios', render: function (): React.Node { From dee0df01ec041df6922a9991b0f5d4e311251a03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Thu, 25 Jun 2026 14:30:54 +0200 Subject: [PATCH 05/30] . --- packages/rn-tester/RNTesterPods.xcodeproj/project.pbxproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/rn-tester/RNTesterPods.xcodeproj/project.pbxproj b/packages/rn-tester/RNTesterPods.xcodeproj/project.pbxproj index 4f4e0ee6ae00..03f6310fd411 100644 --- a/packages/rn-tester/RNTesterPods.xcodeproj/project.pbxproj +++ b/packages/rn-tester/RNTesterPods.xcodeproj/project.pbxproj @@ -18,11 +18,11 @@ 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 */; }; E62F11842A5C6584000BF1C8 /* UpdatePropertiesExampleView.mm in Sources */ = {isa = PBXBuildFile; fileRef = 272E6B3C1BEA849E001FCF37 /* UpdatePropertiesExampleView.mm */; }; - AA000003AABB0003CCDD0003 /* RNTNativeTouchReceiverComponentView.mm in Sources */ = {isa = PBXBuildFile; fileRef = AA000002AABB0002CCDD0002 /* RNTNativeTouchReceiverComponentView.mm */; }; E7C1241A22BEC44B00DA25C0 /* RNTesterIntegrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E7C1241922BEC44B00DA25C0 /* RNTesterIntegrationTests.m */; }; E7DB20D122B2BAA6005AC45F /* RCTBundleURLProviderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E7DB20A922B2BAA3005AC45F /* RCTBundleURLProviderTests.m */; }; E7DB20D222B2BAA6005AC45F /* RCTModuleInitNotificationRaceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E7DB20AA22B2BAA3005AC45F /* RCTModuleInitNotificationRaceTests.m */; }; @@ -82,8 +82,6 @@ 20B55D3C33B683598D2A4424 /* Pods-RNTesterIntegrationTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RNTesterIntegrationTests.debug.xcconfig"; path = "Target Support Files/Pods-RNTesterIntegrationTests/Pods-RNTesterIntegrationTests.debug.xcconfig"; sourceTree = ""; }; 272E6B3B1BEA849E001FCF37 /* UpdatePropertiesExampleView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = UpdatePropertiesExampleView.h; path = RNTester/NativeExampleViews/UpdatePropertiesExampleView.h; sourceTree = ""; }; 272E6B3C1BEA849E001FCF37 /* UpdatePropertiesExampleView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = UpdatePropertiesExampleView.mm; path = RNTester/NativeExampleViews/UpdatePropertiesExampleView.mm; 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 = ""; }; 27F441E81BEBE5030039B79C /* FlexibleSizeExampleView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = FlexibleSizeExampleView.mm; path = RNTester/NativeExampleViews/FlexibleSizeExampleView.mm; sourceTree = ""; }; 27F441EA1BEBE5030039B79C /* FlexibleSizeExampleView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FlexibleSizeExampleView.h; path = RNTester/NativeExampleViews/FlexibleSizeExampleView.h; sourceTree = ""; }; 2B312B0EEE90BA411618B015 /* libPods-RNTesterUnitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RNTesterUnitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -98,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 = ""; }; From 5b007955dbdb61bf2473105ffd5469714210969a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Thu, 25 Jun 2026 15:29:39 +0200 Subject: [PATCH 06/30] cleanup --- .../js/examples/Pressable/PressableExample.js | 99 ++++++------------- 1 file changed, 30 insertions(+), 69 deletions(-) diff --git a/packages/rn-tester/js/examples/Pressable/PressableExample.js b/packages/rn-tester/js/examples/Pressable/PressableExample.js index 275a86a72f12..6716a3ab2d43 100644 --- a/packages/rn-tester/js/examples/Pressable/PressableExample.js +++ b/packages/rn-tester/js/examples/Pressable/PressableExample.js @@ -276,64 +276,23 @@ function PressableDisabled() { ); } -/** - * Repro for: Pressable does not consume native touches on iOS/iPadOS. - * - * Topology: NativeTouchReceiver (UIView with touchesEnded:) is the PARENT. - * Pressable is the CHILD and the UIKit hit-tested target. - * - * Bug: tapping the Pressable makes it the JS responder (onPress fires), but - * the touch also bubbles up the UIKit responder chain to NativeTouchReceiver - * because RCTSurfaceTouchHandler has cancelsTouchesInView=NO — so - * touchesEnded: is never cancelled on the ancestor, and onNativeTouch fires too. - * - * Expected after preventNativePropagation fix: - * preventNativePropagation={false} → both onPress AND onNativeTouch fire (bug) - * preventNativePropagation={true} → only onPress fires (fixed) - */ -function PressableBlockNativeResponderExample() { +function PressablePreventNativePropagationExample() { const [log, setLog] = useState>([]); - function emit(msg: string) { - setLog(prev => [msg, ...prev].slice(0, 10)); - } - return ( - - Tap the Pressable button inside each row.{'\n\n'} - [default] - {' — BUG: both Pressable.onPress (JS) and NativeTouchReceiver.onNativeTouch'} - {' (UIKit responder chain) fire for the same tap.\n'} - [blocked] - {' — FIXED: only Pressable.onPress fires.'} - - - - {log.length === 0 ? ( - - events will appear here - - ) : ( - log.map((line, i) => ( - - {line} - - )) - )} - - - {'Default (preventNativePropagation={false})'} + {'preventNativePropagation={false} (default)'} - emit('[default] NativeTouchReceiver.touchesEnded: ← native leaked') + setLog(prev => [...prev, 'NativeTouchReceiver.onNativeTouch']) }> emit('[default] Pressable.onPress ✓')}> + onPressIn={() => setLog([])} + onPress={() => setLog(prev => [...prev, 'Pressable.onPress'])}> Tap me @@ -344,31 +303,39 @@ function PressableBlockNativeResponderExample() { - emit( - '[blocked] NativeTouchReceiver.touchesEnded: ← UNEXPECTED, fix not working', - ) + setLog(prev => [...prev, 'NativeTouchReceiver.onNativeTouch']) }> emit('[blocked] Pressable.onPress ✓')}> + onPressIn={() => setLog([])} + onPress={() => setLog(prev => [...prev, 'Pressable.onPress'])}> Tap me + + + + ); +} + +function LogBox({lines}: {lines: Array}) { + return ( + + {lines.length === 0 ? ( + tap to see events + ) : ( + lines.map((line, i) => ( + + {line} + + )) + )} ); } const blockNativeStyles = StyleSheet.create({ - description: { - fontSize: 13, - color: '#333', - marginBottom: 10, - lineHeight: 19, - }, - bold: { - fontWeight: '700', - }, logBox: { backgroundColor: '#1a1a1a', borderRadius: 6, @@ -803,19 +770,13 @@ const examples = [ }, }, { - title: 'preventNativePropagation — press leaks to native parent (iOS repro)', - name: 'block-native-responder', + title: 'preventNativePropagation', + name: 'prevent-native-propagation', description: - ('Repro for: Pressable does not consume the native touch on iOS/iPadOS. ' + - 'A Pressable sits inside a NativeTouchReceiver (plain UIView with ' + - 'touchesEnded: overridden). Tapping the Pressable makes it the JS ' + - 'responder (onPress fires), but the touch also bubbles up the UIKit ' + - 'responder chain so the parent NativeTouchReceiver fires too. ' + - 'With preventNativePropagation={true} and the fix applied, only ' + - 'Pressable.onPress should fire.') as string, + 'Pressable inside a native UIView parent. Without preventNativePropagation the touch leaks up the UIKit responder chain to the parent.' as string, platform: 'ios', render: function (): React.Node { - return ; + return ; }, }, ...PressableExampleFbInternal.examples, From 4f4c0a2599664cd4aafe99ea7409ba6dfdc15382 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Thu, 25 Jun 2026 15:57:52 +0200 Subject: [PATCH 07/30] docs --- .../Libraries/Components/Pressable/Pressable.js | 6 ++++-- .../Libraries/Components/View/ViewPropTypes.js | 8 ++++++-- .../react-native/Libraries/Pressability/Pressability.js | 4 ++-- .../react/renderer/components/view/BaseViewProps.h | 2 +- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/react-native/Libraries/Components/Pressable/Pressable.js b/packages/react-native/Libraries/Components/Pressable/Pressable.js index 43b17b217795..ac4ad4778020 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 (UIKit responder chain) from + * receiving touch events when this Pressable handles a press. + * + * @platform ios */ preventNativePropagation?: ?boolean, diff --git a/packages/react-native/Libraries/Components/View/ViewPropTypes.js b/packages/react-native/Libraries/Components/View/ViewPropTypes.js index dd1a81448727..03138b4a5520 100644 --- a/packages/react-native/Libraries/Components/View/ViewPropTypes.js +++ b/packages/react-native/Libraries/Components/View/ViewPropTypes.js @@ -504,8 +504,12 @@ type ViewBaseProps = Readonly<{ /** * When true, prevents native ancestor views (UIKit responder chain) from - * receiving touch events when this view is the active JS responder. - * Requires that the Fabric gesture recognizer has already claimed the touch. + * receiving touch events when this view handles a press. Without this, UIKit + * delivers touches independently to every view that received touchesBegan:, + * so parent views fire touchesEnded: even when a child Pressable handles the + * press. + * + * @platform ios */ preventNativePropagation?: ?boolean, diff --git a/packages/react-native/Libraries/Pressability/Pressability.js b/packages/react-native/Libraries/Pressability/Pressability.js index ecdf08176d48..92d8f6e602ed 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. iOS only. */ preventNativePropagation?: ?boolean, }>; 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 22858a949f36..2f49f73de066 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h @@ -108,7 +108,7 @@ class BaseViewProps : public YogaStylableProps, public AccessibilityProps { bool collapsableChildren{true}; bool removeClippedSubviews{false}; - bool preventNativePropagation{false}; + bool preventNativePropagation{false}; // iOS only #pragma mark - Convenience Methods From 688200356bbb4d3dc22815f19cf62f914132dada Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Thu, 25 Jun 2026 16:14:48 +0200 Subject: [PATCH 08/30] revert the name change --- .../Libraries/Components/Pressable/Pressable.js | 10 +++++----- .../Libraries/Components/View/ViewPropTypes.js | 2 +- .../Libraries/NativeComponent/BaseViewConfig.ios.js | 2 +- .../Libraries/Pressability/Pressability.js | 4 ++-- .../ComponentViews/View/RCTViewComponentView.mm | 8 ++++---- .../react/renderer/components/view/BaseViewProps.cpp | 10 +++++----- .../react/renderer/components/view/BaseViewProps.h | 2 +- .../RNTNativeTouchReceiverComponentView.h | 4 ++-- 8 files changed, 21 insertions(+), 21 deletions(-) diff --git a/packages/react-native/Libraries/Components/Pressable/Pressable.js b/packages/react-native/Libraries/Components/Pressable/Pressable.js index ac4ad4778020..ef50130fb398 100644 --- a/packages/react-native/Libraries/Components/Pressable/Pressable.js +++ b/packages/react-native/Libraries/Components/Pressable/Pressable.js @@ -125,7 +125,7 @@ type PressableBaseProps = Readonly<{ * * @platform ios */ - preventNativePropagation?: ?boolean, + blockNativeResponder?: ?boolean, /** * Either view styles or a function that receives a boolean reflecting whether @@ -193,7 +193,7 @@ function Pressable({ 'aria-expanded': ariaExpanded, 'aria-label': ariaLabel, 'aria-selected': ariaSelected, - preventNativePropagation, + blockNativeResponder, cancelable, children, delayHoverIn, @@ -305,12 +305,12 @@ function Pressable({ onPressOut(event); } }, - preventNativePropagation, + blockNativeResponder, }), [ android_disableSound, android_rippleConfig, - preventNativePropagation, + blockNativeResponder, cancelable, delayHoverIn, delayHoverOut, @@ -342,7 +342,7 @@ function Pressable({ ref={mergedRef} style={typeof style === 'function' ? style({pressed}) : style} collapsable={false} - preventNativePropagation={preventNativePropagation}> + blockNativeResponder={blockNativeResponder}> {typeof children === 'function' ? children({pressed}) : children} {__DEV__ ? : null} diff --git a/packages/react-native/Libraries/Components/View/ViewPropTypes.js b/packages/react-native/Libraries/Components/View/ViewPropTypes.js index 03138b4a5520..559ec4e178c6 100644 --- a/packages/react-native/Libraries/Components/View/ViewPropTypes.js +++ b/packages/react-native/Libraries/Components/View/ViewPropTypes.js @@ -511,7 +511,7 @@ type ViewBaseProps = Readonly<{ * * @platform ios */ - preventNativePropagation?: ?boolean, + blockNativeResponder?: ?boolean, /** * Defines the order in which descendant elements receive accessibility focus. diff --git a/packages/react-native/Libraries/NativeComponent/BaseViewConfig.ios.js b/packages/react-native/Libraries/NativeComponent/BaseViewConfig.ios.js index 04a18f30b72d..4e76214cb692 100644 --- a/packages/react-native/Libraries/NativeComponent/BaseViewConfig.ios.js +++ b/packages/react-native/Libraries/NativeComponent/BaseViewConfig.ios.js @@ -247,7 +247,7 @@ const validAttributesForNonEventProps = { hitSlop: {diff: require('../Utilities/differ/insetsDiffer').default}, collapsable: true, collapsableChildren: true, - preventNativePropagation: 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 92d8f6e602ed..86ea5ef29305 100644 --- a/packages/react-native/Libraries/Pressability/Pressability.js +++ b/packages/react-native/Libraries/Pressability/Pressability.js @@ -134,7 +134,7 @@ export type PressabilityConfig = Readonly<{ * When true, prevents native ancestor views (UIKit responder chain) from * receiving touch events when this Pressable handles a press. iOS only. */ - preventNativePropagation?: ?boolean, + blockNativeResponder?: ?boolean, }>; export type EventHandlers = Readonly<{ @@ -477,7 +477,7 @@ export default class Pressability { this._handleLongPress(event); }, delayLongPress + delayPressIn); - return this._config.preventNativePropagation === true; + return this._config.blockNativeResponder === true; }, onResponderMove: (event: GestureResponderEvent): void => { 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 bd435e984b6a..6710d1d8afcc 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -675,7 +675,7 @@ - (void)setIsJSResponder:(BOOL)isJSResponder - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { const auto &viewProps = static_cast(*_props); - if (viewProps.preventNativePropagation) { + if (viewProps.blockNativeResponder) { return; } [super touchesBegan:touches withEvent:event]; @@ -684,7 +684,7 @@ - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { const auto &viewProps = static_cast(*_props); - if (viewProps.preventNativePropagation) { + if (viewProps.blockNativeResponder) { return; } [super touchesMoved:touches withEvent:event]; @@ -693,7 +693,7 @@ - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { const auto &viewProps = static_cast(*_props); - if (viewProps.preventNativePropagation) { + if (viewProps.blockNativeResponder) { return; } [super touchesEnded:touches withEvent:event]; @@ -702,7 +702,7 @@ - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { const auto &viewProps = static_cast(*_props); - if (viewProps.preventNativePropagation) { + if (viewProps.blockNativeResponder) { return; } [super touchesCancelled:touches withEvent:event]; 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 14b3818258ad..f27d268222c7 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp @@ -425,14 +425,14 @@ BaseViewProps::BaseViewProps( "removeClippedSubviews", sourceProps.removeClippedSubviews, false)), - preventNativePropagation( + blockNativeResponder( ReactNativeFeatureFlags::enableCppPropsIteratorSetter() - ? sourceProps.preventNativePropagation + ? sourceProps.blockNativeResponder : convertRawProp( context, rawProps, - "preventNativePropagation", - sourceProps.preventNativePropagation, + "blockNativeResponder", + sourceProps.blockNativeResponder, false)) {} #define VIEW_EVENT_CASE(eventType) \ @@ -484,7 +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(preventNativePropagation); + 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 2f49f73de066..7dd9d9d2092a 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h @@ -108,7 +108,7 @@ class BaseViewProps : public YogaStylableProps, public AccessibilityProps { bool collapsableChildren{true}; bool removeClippedSubviews{false}; - bool preventNativePropagation{false}; // iOS only + bool blockNativeResponder{false}; // iOS only #pragma mark - Convenience Methods diff --git a/packages/rn-tester/RNTester/NativeExampleViews/RNTNativeTouchReceiverComponentView.h b/packages/rn-tester/RNTester/NativeExampleViews/RNTNativeTouchReceiverComponentView.h index 9f1ee182f88c..c92e564ba611 100644 --- a/packages/rn-tester/RNTester/NativeExampleViews/RNTNativeTouchReceiverComponentView.h +++ b/packages/rn-tester/RNTester/NativeExampleViews/RNTNativeTouchReceiverComponentView.h @@ -14,9 +14,9 @@ NS_ASSUME_NONNULL_BEGIN /** * Fabric component view whose touchesEnded: fires onNativeTouch. - * Used as the parent wrapper in the preventNativePropagation repro: + * Used as the parent wrapper in the blockNativeResponder repro: * - * ... + * ... * */ @interface RNTNativeTouchReceiverComponentView : RCTViewComponentView From c40b1048c28a5df351b354b7a4dc3c41db022017 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Thu, 25 Jun 2026 16:31:06 +0200 Subject: [PATCH 09/30] naming --- .../js/examples/Pressable/PressableExample.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/rn-tester/js/examples/Pressable/PressableExample.js b/packages/rn-tester/js/examples/Pressable/PressableExample.js index 6716a3ab2d43..11007db2560c 100644 --- a/packages/rn-tester/js/examples/Pressable/PressableExample.js +++ b/packages/rn-tester/js/examples/Pressable/PressableExample.js @@ -282,7 +282,7 @@ function PressablePreventNativePropagationExample() { return ( - {'preventNativePropagation={false} (default)'} + {'blockNativeResponder={false} (default)'} - {'preventNativePropagation={true}'} + {'blockNativeResponder={true}'} setLog([])} onPress={() => setLog(prev => [...prev, 'Pressable.onPress'])}> Tap me @@ -770,10 +770,10 @@ const examples = [ }, }, { - title: 'preventNativePropagation', + title: 'blockNativeResponder', name: 'prevent-native-propagation', description: - 'Pressable inside a native UIView parent. Without preventNativePropagation the touch leaks up the UIKit responder chain to the parent.' as string, + 'Pressable inside a native UIView parent. Without blockNativeResponder the touch leaks up the UIKit responder chain to the parent.' as string, platform: 'ios', render: function (): React.Node { return ; From 5d9beef284f8005351ae9b2152a48a493b62b9e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Thu, 25 Jun 2026 16:35:39 +0200 Subject: [PATCH 10/30] revert unintended changes --- .../RNTesterPods.xcodeproj/project.pbxproj | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/packages/rn-tester/RNTesterPods.xcodeproj/project.pbxproj b/packages/rn-tester/RNTesterPods.xcodeproj/project.pbxproj index 03f6310fd411..cd086800567e 100644 --- a/packages/rn-tester/RNTesterPods.xcodeproj/project.pbxproj +++ b/packages/rn-tester/RNTesterPods.xcodeproj/project.pbxproj @@ -949,10 +949,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 15.1; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; - OTHER_CFLAGS = ( - "$(inherited)", - "-DRCT_REMOVE_LEGACY_ARCH=1", - ); + OTHER_CFLAGS = "$(inherited)"; OTHER_CPLUSPLUSFLAGS = ( "$(OTHER_CFLAGS)", "-DFOLLY_NO_CONFIG", @@ -960,13 +957,11 @@ "-DFOLLY_USE_LIBCPP=1", "-DFOLLY_CFG_NO_COROUTINES=1", "-DFOLLY_HAVE_CLOCK_GETTIME=1", - "-DRCT_REMOVE_LEGACY_ARCH=1", ); OTHER_LDFLAGS = ( "-ObjC", "-lc++", ); - PODFILE_DIR = "$(SRCROOT)"; REACT_NATIVE_PATH = "${PODS_ROOT}/../../react-native"; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG"; @@ -1047,10 +1042,7 @@ ); IPHONEOS_DEPLOYMENT_TARGET = 15.1; MTL_ENABLE_DEBUG_INFO = NO; - OTHER_CFLAGS = ( - "$(inherited)", - "-DRCT_REMOVE_LEGACY_ARCH=1", - ); + OTHER_CFLAGS = "$(inherited)"; OTHER_CPLUSPLUSFLAGS = ( "$(OTHER_CFLAGS)", "-DFOLLY_NO_CONFIG", @@ -1058,13 +1050,11 @@ "-DFOLLY_USE_LIBCPP=1", "-DFOLLY_CFG_NO_COROUTINES=1", "-DFOLLY_HAVE_CLOCK_GETTIME=1", - "-DRCT_REMOVE_LEGACY_ARCH=1", ); OTHER_LDFLAGS = ( "-ObjC", "-lc++", ); - PODFILE_DIR = "$(SRCROOT)"; REACT_NATIVE_PATH = "${PODS_ROOT}/../../react-native"; SDKROOT = iphoneos; USE_HERMES = true; From 5a4fa7c5751c6bd3e4ffc5c6eac9274d48478b83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Thu, 25 Jun 2026 16:41:40 +0200 Subject: [PATCH 11/30] simplify the example --- .../js/examples/Pressable/PressableExample.js | 173 ++++++++++-------- 1 file changed, 92 insertions(+), 81 deletions(-) diff --git a/packages/rn-tester/js/examples/Pressable/PressableExample.js b/packages/rn-tester/js/examples/Pressable/PressableExample.js index 11007db2560c..0bddfb3d3147 100644 --- a/packages/rn-tester/js/examples/Pressable/PressableExample.js +++ b/packages/rn-tester/js/examples/Pressable/PressableExample.js @@ -276,57 +276,116 @@ function PressableDisabled() { ); } -function PressablePreventNativePropagationExample() { - const [log, setLog] = useState>([]); - +const scenarioReceiverStyle = { + padding: 16, + backgroundColor: '#fff3cd', + borderRadius: 8, + borderWidth: StyleSheet.hairlineWidth, + borderColor: '#e0bb5a', + marginBottom: 4, + alignItems: 'flex-start' as const, +}; + +function Scenario({ + label, + blockNativeResponder, + onNativeTouch, + onPressIn, + onPress, +}: { + label: string, + blockNativeResponder?: boolean, + onNativeTouch: () => void, + onPressIn: () => void, + onPress: () => void, +}) { return ( - - - {'blockNativeResponder={false} (default)'} + <> + + {label} - setLog(prev => [...prev, 'NativeTouchReceiver.onNativeTouch']) - }> + style={scenarioReceiverStyle} + onNativeTouch={onNativeTouch}> setLog([])} - onPress={() => setLog(prev => [...prev, 'Pressable.onPress'])}> - Tap me + style={{ + backgroundColor: '#0a84ff', + borderRadius: 6, + paddingVertical: 10, + paddingHorizontal: 20, + }} + blockNativeResponder={blockNativeResponder} + onPressIn={onPressIn} + onPress={onPress}> + + Tap me + + + ); +} - - {'blockNativeResponder={true}'} - - - setLog(prev => [...prev, 'NativeTouchReceiver.onNativeTouch']) - }> - setLog([])} - onPress={() => setLog(prev => [...prev, 'Pressable.onPress'])}> - Tap me - - +function PressablePreventNativePropagationExample() { + const [log, setLog] = useState>([]); + const onNativeTouch = () => + setLog(prev => [...prev, 'NativeTouchReceiver.onNativeTouch']); + const onPressIn = () => setLog([]); + const onPress = () => setLog(prev => [...prev, 'Pressable.onPress']); + return ( + + + ); } +const monoFont = Platform.OS === 'ios' ? 'Menlo' : 'monospace'; + function LogBox({lines}: {lines: Array}) { return ( - + {lines.length === 0 ? ( - tap to see events + + tap to see events + ) : ( lines.map((line, i) => ( - + {line} )) @@ -335,54 +394,6 @@ function LogBox({lines}: {lines: Array}) { ); } -const blockNativeStyles = StyleSheet.create({ - logBox: { - backgroundColor: '#1a1a1a', - borderRadius: 6, - padding: 10, - minHeight: 100, - marginBottom: 12, - }, - logPlaceholder: { - color: '#555', - fontSize: 12, - fontStyle: 'italic', - }, - logLine: { - color: '#b5f5a0', - fontSize: 12, - fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace', - lineHeight: 17, - }, - sectionHeader: { - fontSize: 13, - fontWeight: '600', - color: '#444', - marginTop: 10, - marginBottom: 4, - }, - receiver: { - padding: 16, - backgroundColor: '#fff3cd', - borderRadius: 8, - borderWidth: StyleSheet.hairlineWidth, - borderColor: '#e0bb5a', - marginBottom: 4, - alignItems: 'flex-start', - }, - pressable: { - backgroundColor: '#0a84ff', - borderRadius: 6, - paddingVertical: 10, - paddingHorizontal: 20, - }, - pressableText: { - color: '#fff', - fontWeight: '600', - fontSize: 15, - }, -}); - const styles = StyleSheet.create({ row: { justifyContent: 'center', From b73f9389098cdf0162cbf10df2d0fac1a461c57d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Thu, 25 Jun 2026 16:46:50 +0200 Subject: [PATCH 12/30] simplify --- .../js/examples/Pressable/PressableExample.js | 88 +++++-------------- 1 file changed, 21 insertions(+), 67 deletions(-) diff --git a/packages/rn-tester/js/examples/Pressable/PressableExample.js b/packages/rn-tester/js/examples/Pressable/PressableExample.js index 0bddfb3d3147..cbc11ee91c91 100644 --- a/packages/rn-tester/js/examples/Pressable/PressableExample.js +++ b/packages/rn-tester/js/examples/Pressable/PressableExample.js @@ -21,6 +21,7 @@ import { PlatformColor, Pressable, StyleSheet, + Switch, Text, View, } from 'react-native'; @@ -286,31 +287,22 @@ const scenarioReceiverStyle = { alignItems: 'flex-start' as const, }; -function Scenario({ - label, - blockNativeResponder, - onNativeTouch, - onPressIn, - onPress, -}: { - label: string, - blockNativeResponder?: boolean, - onNativeTouch: () => void, - onPressIn: () => void, - onPress: () => void, -}) { +function PressableBlockNativeResponderExample() { + const [blockNativeResponder, setBlockNativeResponder] = useState(false); + const [log, setLog] = useState>([]); + const onNativeTouch = () => + setLog(prev => [...prev, 'NativeTouchReceiver.onNativeTouch']); + const onPressIn = () => setLog([]); + const onPress = () => setLog(prev => [...prev, 'Pressable.onPress']); + return ( - <> - - {label} - + + + + blockNativeResponder + + + @@ -325,36 +317,10 @@ function Scenario({ onPressIn={onPressIn} onPress={onPress}> - Tap me + Press Me - - ); -} - -function PressablePreventNativePropagationExample() { - const [log, setLog] = useState>([]); - const onNativeTouch = () => - setLog(prev => [...prev, 'NativeTouchReceiver.onNativeTouch']); - const onPressIn = () => setLog([]); - const onPress = () => setLog(prev => [...prev, 'Pressable.onPress']); - - return ( - - - ); @@ -364,28 +330,16 @@ const monoFont = Platform.OS === 'ios' ? 'Menlo' : 'monospace'; function LogBox({lines}: {lines: Array}) { return ( - + {lines.length === 0 ? ( - + tap to see events ) : ( lines.map((line, i) => ( + style={{fontSize: 12, fontFamily: monoFont, lineHeight: 17}}> {line} )) @@ -787,7 +741,7 @@ const examples = [ 'Pressable inside a native UIView parent. Without blockNativeResponder the touch leaks up the UIKit responder chain to the parent.' as string, platform: 'ios', render: function (): React.Node { - return ; + return ; }, }, ...PressableExampleFbInternal.examples, From fe7c25867b33ef0e23c32f75c7cb64c5264e7d6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Thu, 25 Jun 2026 16:50:38 +0200 Subject: [PATCH 13/30] fix format --- .../rn-tester/js/examples/Pressable/PressableExample.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/rn-tester/js/examples/Pressable/PressableExample.js b/packages/rn-tester/js/examples/Pressable/PressableExample.js index cbc11ee91c91..733babb7615c 100644 --- a/packages/rn-tester/js/examples/Pressable/PressableExample.js +++ b/packages/rn-tester/js/examples/Pressable/PressableExample.js @@ -297,11 +297,15 @@ function PressableBlockNativeResponderExample() { return ( - + blockNativeResponder - + Date: Thu, 25 Jun 2026 16:53:22 +0200 Subject: [PATCH 14/30] fix lint --- packages/rn-tester/js/examples/Pressable/PressableExample.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/rn-tester/js/examples/Pressable/PressableExample.js b/packages/rn-tester/js/examples/Pressable/PressableExample.js index 733babb7615c..8edf21fe31c4 100644 --- a/packages/rn-tester/js/examples/Pressable/PressableExample.js +++ b/packages/rn-tester/js/examples/Pressable/PressableExample.js @@ -10,8 +10,8 @@ import type {RNTesterModule} from '../../types/RNTesterTypes'; -import * as PressableExampleFbInternal from './PressableExampleFbInternal'; import RNTNativeTouchReceiver from './NativeTouchReceiverNativeComponent'; +import * as PressableExampleFbInternal from './PressableExampleFbInternal'; import * as React from 'react'; import { Alert, From a422fb86c7bb707a83500f2cd6c1636130ba5df3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Thu, 25 Jun 2026 17:17:52 +0200 Subject: [PATCH 15/30] fix edge case: disabled pressable --- .../Mounting/ComponentViews/View/RCTViewComponentView.mm | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 6710d1d8afcc..df43d76ec4dd 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -675,7 +675,7 @@ - (void)setIsJSResponder:(BOOL)isJSResponder - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { const auto &viewProps = static_cast(*_props); - if (viewProps.blockNativeResponder) { + if (viewProps.blockNativeResponder && _isJSResponder) { return; } [super touchesBegan:touches withEvent:event]; @@ -684,7 +684,7 @@ - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { const auto &viewProps = static_cast(*_props); - if (viewProps.blockNativeResponder) { + if (viewProps.blockNativeResponder && _isJSResponder) { return; } [super touchesMoved:touches withEvent:event]; @@ -693,7 +693,7 @@ - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { const auto &viewProps = static_cast(*_props); - if (viewProps.blockNativeResponder) { + if (viewProps.blockNativeResponder && _isJSResponder) { return; } [super touchesEnded:touches withEvent:event]; @@ -702,7 +702,7 @@ - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { const auto &viewProps = static_cast(*_props); - if (viewProps.blockNativeResponder) { + if (viewProps.blockNativeResponder && _isJSResponder) { return; } [super touchesCancelled:touches withEvent:event]; From 2574f0c356af93f8c906a16183bdf9e95cca27fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Thu, 25 Jun 2026 17:35:48 +0200 Subject: [PATCH 16/30] android support --- .../react/uiapp/RNTesterApplication.kt | 4 ++ .../RNTNativeTouchReceiverManager.kt | 35 +++++++++++ .../component/RNTNativeTouchReceiverView.kt | 59 +++++++++++++++++++ .../js/examples/Pressable/PressableExample.js | 3 +- 4 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 packages/rn-tester/android/app/src/main/java/com/facebook/react/uiapp/component/RNTNativeTouchReceiverManager.kt create mode 100644 packages/rn-tester/android/app/src/main/java/com/facebook/react/uiapp/component/RNTNativeTouchReceiverView.kt 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..1823bff40274 --- /dev/null +++ b/packages/rn-tester/android/app/src/main/java/com/facebook/react/uiapp/component/RNTNativeTouchReceiverView.kt @@ -0,0 +1,59 @@ +/* + * 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 + +/** + * A native ViewGroup that fires onNativeTouch via onInterceptTouchEvent. + * + * This simulates a native parent that receives touch events through Android's touch dispatch. + * When blockNativeResponder=false on a Pressable child, requestDisallowInterceptTouchEvent is NOT + * called, so onInterceptTouchEvent fires and onNativeTouch is dispatched. When + * blockNativeResponder=true, requestDisallowInterceptTouchEvent(true) is called on this view, + * suppressing onInterceptTouchEvent — and thus onNativeTouch — for that gesture. + */ +internal class RNTNativeTouchReceiverView(context: ThemedReactContext) : ReactViewGroup(context) { + + override fun onInterceptTouchEvent(ev: MotionEvent): Boolean { + val intercepted = super.onInterceptTouchEvent(ev) + if (ev.action == MotionEvent.ACTION_UP) { + emitNativeTouchEvent() + } + return intercepted + } + + override fun onTouchEvent(ev: MotionEvent): Boolean { + val handled = super.onTouchEvent(ev) + if (ev.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/PressableExample.js b/packages/rn-tester/js/examples/Pressable/PressableExample.js index 8edf21fe31c4..cf58e3e0e81b 100644 --- a/packages/rn-tester/js/examples/Pressable/PressableExample.js +++ b/packages/rn-tester/js/examples/Pressable/PressableExample.js @@ -742,8 +742,7 @@ const examples = [ title: 'blockNativeResponder', name: 'prevent-native-propagation', description: - 'Pressable inside a native UIView parent. Without blockNativeResponder the touch leaks up the UIKit responder chain to the parent.' as string, - platform: 'ios', + '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 ; }, From 2ccbd6adb23aba37f1b5f378a93ce5017ad6bcaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Thu, 25 Jun 2026 17:45:54 +0200 Subject: [PATCH 17/30] clean up ios only annotations --- .../react-native/Libraries/Components/Pressable/Pressable.js | 2 -- .../react-native/Libraries/Components/View/ViewPropTypes.js | 2 -- packages/react-native/Libraries/Pressability/Pressability.js | 2 +- .../ReactCommon/react/renderer/components/view/BaseViewProps.h | 2 +- 4 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/react-native/Libraries/Components/Pressable/Pressable.js b/packages/react-native/Libraries/Components/Pressable/Pressable.js index ef50130fb398..f882b048ca4c 100644 --- a/packages/react-native/Libraries/Components/Pressable/Pressable.js +++ b/packages/react-native/Libraries/Components/Pressable/Pressable.js @@ -122,8 +122,6 @@ type PressableBaseProps = Readonly<{ /** * When true, prevents native ancestor views (UIKit responder chain) from * receiving touch events when this Pressable handles a press. - * - * @platform ios */ blockNativeResponder?: ?boolean, diff --git a/packages/react-native/Libraries/Components/View/ViewPropTypes.js b/packages/react-native/Libraries/Components/View/ViewPropTypes.js index 559ec4e178c6..fe1fa8695813 100644 --- a/packages/react-native/Libraries/Components/View/ViewPropTypes.js +++ b/packages/react-native/Libraries/Components/View/ViewPropTypes.js @@ -508,8 +508,6 @@ type ViewBaseProps = Readonly<{ * delivers touches independently to every view that received touchesBegan:, * so parent views fire touchesEnded: even when a child Pressable handles the * press. - * - * @platform ios */ blockNativeResponder?: ?boolean, diff --git a/packages/react-native/Libraries/Pressability/Pressability.js b/packages/react-native/Libraries/Pressability/Pressability.js index 86ea5ef29305..4fd9dc84cd61 100644 --- a/packages/react-native/Libraries/Pressability/Pressability.js +++ b/packages/react-native/Libraries/Pressability/Pressability.js @@ -132,7 +132,7 @@ export type PressabilityConfig = Readonly<{ /** * When true, prevents native ancestor views (UIKit responder chain) from - * receiving touch events when this Pressable handles a press. iOS only. + * receiving touch events when this Pressable handles a press. */ blockNativeResponder?: ?boolean, }>; 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 7dd9d9d2092a..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,7 +108,7 @@ class BaseViewProps : public YogaStylableProps, public AccessibilityProps { bool collapsableChildren{true}; bool removeClippedSubviews{false}; - bool blockNativeResponder{false}; // iOS only + bool blockNativeResponder{false}; #pragma mark - Convenience Methods From 0f9691b404698299bf731ff3f5b5ed0cb8a9ad62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Thu, 25 Jun 2026 17:48:50 +0200 Subject: [PATCH 18/30] cleanup --- .../RNTNativeTouchReceiverComponentView.h | 6 +----- .../RNTNativeTouchReceiverComponentView.mm | 5 +---- .../react/uiapp/component/RNTNativeTouchReceiverView.kt | 9 ++------- 3 files changed, 4 insertions(+), 16 deletions(-) diff --git a/packages/rn-tester/RNTester/NativeExampleViews/RNTNativeTouchReceiverComponentView.h b/packages/rn-tester/RNTester/NativeExampleViews/RNTNativeTouchReceiverComponentView.h index c92e564ba611..9631c034e1c8 100644 --- a/packages/rn-tester/RNTester/NativeExampleViews/RNTNativeTouchReceiverComponentView.h +++ b/packages/rn-tester/RNTester/NativeExampleViews/RNTNativeTouchReceiverComponentView.h @@ -13,11 +13,7 @@ NS_ASSUME_NONNULL_BEGIN /** - * Fabric component view whose touchesEnded: fires onNativeTouch. - * Used as the parent wrapper in the blockNativeResponder repro: - * - * ... - * + * Fabric component view that receives native touch events and emits onNativeTouch. */ @interface RNTNativeTouchReceiverComponentView : RCTViewComponentView @end diff --git a/packages/rn-tester/RNTester/NativeExampleViews/RNTNativeTouchReceiverComponentView.mm b/packages/rn-tester/RNTester/NativeExampleViews/RNTNativeTouchReceiverComponentView.mm index 7bc3711a66f1..127f572a8274 100644 --- a/packages/rn-tester/RNTester/NativeExampleViews/RNTNativeTouchReceiverComponentView.mm +++ b/packages/rn-tester/RNTester/NativeExampleViews/RNTNativeTouchReceiverComponentView.mm @@ -38,10 +38,7 @@ - (instancetype)initWithFrame:(CGRect)frame } /** - * touchesEnded: fires when UIKit delivers the touch-up event through the - * native responder chain. Even when a Pressable child is the JS responder, - * this still fires because RCTSurfaceTouchHandler sets cancelsTouchesInView=NO. - * That is the bug this component helps reproduce. + * Emits onNativeTouch when UIKit delivers the touch-up event. */ - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { 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 index 1823bff40274..32e012a4189e 100644 --- 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 @@ -17,13 +17,8 @@ import com.facebook.react.uimanager.events.Event import com.facebook.react.views.view.ReactViewGroup /** - * A native ViewGroup that fires onNativeTouch via onInterceptTouchEvent. - * - * This simulates a native parent that receives touch events through Android's touch dispatch. - * When blockNativeResponder=false on a Pressable child, requestDisallowInterceptTouchEvent is NOT - * called, so onInterceptTouchEvent fires and onNativeTouch is dispatched. When - * blockNativeResponder=true, requestDisallowInterceptTouchEvent(true) is called on this view, - * suppressing onInterceptTouchEvent — and thus onNativeTouch — for that gesture. + * Native ViewGroup that receives touch events through Android's touch dispatch + * and emits onNativeTouch. */ internal class RNTNativeTouchReceiverView(context: ThemedReactContext) : ReactViewGroup(context) { From 8b2b1d81fa10a07535ea7437861c85d2989e6196 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Thu, 25 Jun 2026 18:25:22 +0200 Subject: [PATCH 19/30] fix yarn build-types --- packages/react-native/ReactNativeApi.d.ts | 89 ++++++++++++----------- 1 file changed, 45 insertions(+), 44 deletions(-) diff --git a/packages/react-native/ReactNativeApi.d.ts b/packages/react-native/ReactNativeApi.d.ts index 699c2fff455a..0d10264488f1 100644 --- a/packages/react-native/ReactNativeApi.d.ts +++ b/packages/react-native/ReactNativeApi.d.ts @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<42200de8ca10d30541e23b67547d9a13>> + * @generated SignedSource<<0ff5693800c50074fbf1c08e88c50093>> * * This file was generated by scripts/js-api/build-types/index.js. */ @@ -5659,6 +5659,7 @@ declare class ViewabilityHelper_default { } declare type ViewabilityHelperT = typeof ViewabilityHelper_default declare type ViewBaseProps = { + readonly blockNativeResponder?: boolean readonly children?: React.ReactNode readonly collapsable?: boolean readonly collapsableChildren?: boolean @@ -5911,16 +5912,16 @@ export { AccessibilityValue, // cf8bcb74 ActionSheetIOS, // b558559e ActionSheetIOSOptions, // 1756eb5a - ActivityIndicator, // 0f796809 + ActivityIndicator, // d7fc42fd ActivityIndicatorInstance, // a82dd4e7 - ActivityIndicatorProps, // a232e3ff + ActivityIndicatorProps, // 571c60c9 Alert, // 5bf12165 AlertButton, // bf1a3b60 AlertButtonStyle, // ec9fb242 AlertOptions, // a0cdac0f AlertType, // 5ab91217 AndroidKeyboardEvent, // e03becc8 - Animated, // 1ac00401 + Animated, // a8925b59 AppConfig, // 35c0ca70 AppRegistry, // 7ef8e53a AppState, // 12012be5 @@ -5952,9 +5953,9 @@ export { DimensionsPayload, // 653bc26c DisplayMetrics, // 1dc35cef DisplayMetricsAndroid, // 872e62eb - DrawerLayoutAndroid, // a8ced056 + DrawerLayoutAndroid, // 751bb72f DrawerLayoutAndroidInstance, // c0694352 - DrawerLayoutAndroidProps, // e9a67dd7 + DrawerLayoutAndroidProps, // 8ff2c62d DrawerSlideEvent, // c4ab8fba DropShadowValue, // e9df2606 DynamicColorIOS, // d96c228c @@ -5970,9 +5971,9 @@ export { EventSubscription, // b8d084aa ExtendedExceptionData, // 5a6ccf5a FilterFunction, // bf24c0e3 - FlatList, // b5cfde24 - FlatListInstance, // 8f76f3b2 - FlatListProps, // 3725656d + FlatList, // f3bcf76d + FlatListInstance, // e7a79099 + FlatListProps, // 48c58790 FocusEvent, // 850f1517 FontVariant, // 7c7558bb GestureResponderEvent, // 14d3e77a @@ -5983,17 +5984,17 @@ export { IEventEmitter, // fbef6131 IOSKeyboardEvent, // e67bfe3a IgnorePattern, // ec6f6ece - Image, // fba54a35 - ImageBackground, // 1543bae2 - ImageBackgroundInstance, // 610d9eed - ImageBackgroundProps, // ceb153c3 + Image, // b2561656 + ImageBackground, // ea7e7e08 + ImageBackgroundInstance, // 677f9154 + ImageBackgroundProps, // 7354b374 ImageErrorEvent, // 978933f4 ImageInstance, // 9a100753 ImageLoadEvent, // 77f0b718 ImageProgressEventIOS, // 445331a4 - ImageProps, // e8aecf89 + ImageProps, // e685c003 ImagePropsAndroid, // 9fd9bcbb - ImagePropsBase, // f5535cef + ImagePropsBase, // dabc9112 ImagePropsIOS, // 4a080668 ImageRequireSource, // 681d683b ImageResizeMode, // d51106e2 @@ -6011,9 +6012,9 @@ export { KeyEvent, // 20fa4267 KeyUpEvent, // 57f832c5 Keyboard, // 49414c97 - KeyboardAvoidingView, // cf0307d2 - KeyboardAvoidingViewInstance, // c4ca43a7 - KeyboardAvoidingViewProps, // cf148633 + KeyboardAvoidingView, // 8a6b2692 + KeyboardAvoidingViewInstance, // 85c50e87 + KeyboardAvoidingViewProps, // f721c5a8 KeyboardEvent, // c3f895d4 KeyboardEventEasing, // af4091c8 KeyboardEventName, // 59299ad6 @@ -6038,10 +6039,10 @@ export { MeasureInWindowOnSuccessCallback, // a285f598 MeasureLayoutOnSuccessCallback, // 3592502a MeasureOnSuccessCallback, // 82824e59 - Modal, // c8c01498 + Modal, // 325aa23a ModalBaseProps, // 71945951 ModalInstance, // d466ce77 - ModalProps, // 9f060027 + ModalProps, // bbf2cddf ModalPropsAndroid, // 515fb173 ModalPropsIOS, // 0e13cfcc ModeChangeEvent, // f64bf69d @@ -6076,15 +6077,15 @@ export { PointerEvent, // ff599afe PressabilityConfig, // faab5639 PressabilityEventHandlers, // 0b910091 - Pressable, // f4911021 + Pressable, // 699b380d PressableAndroidRippleConfig, // ee32eaca PressableInstance, // eebfe911 - PressableProps, // 95d57db2 + PressableProps, // ce74ed6d PressableStateCallbackType, // 9af36561 ProcessedColorValue, // 33f74304 - ProgressBarAndroid, // 44a46d48 + ProgressBarAndroid, // 54e0bdce ProgressBarAndroidInstance, // ab545ef1 - ProgressBarAndroidProps, // f20ebde5 + ProgressBarAndroidProps, // 3409491a PublicRootInstance, // 8040afd7 PublicTextInstance, // 6937c7bf PushNotificationEventName, // 84e7e150 @@ -6092,9 +6093,9 @@ export { PushNotificationPermissions, // c2e7ae4f Rationale, // 5df1b1c1 ReactNativeVersion, // abd76827 - RefreshControl, // 5b3465d2 - RefreshControlInstance, // 8b4d078b - RefreshControlProps, // f8883f9a + RefreshControl, // 9981464e + RefreshControlInstance, // a473cb11 + RefreshControlProps, // d1474044 RefreshControlPropsAndroid, // 99f64c97 RefreshControlPropsIOS, // 72a36381 Registry, // 6c39216d @@ -6106,24 +6107,24 @@ export { RootViewStyleProvider, // 8792d506 Runnable, // 594dd93a Runnables, // 4367c557 - SafeAreaView, // 47b1ede1 + SafeAreaView, // a133c7b4 SafeAreaViewInstance, // 21dba39c ScaledSize, // 07e417c7 ScrollEvent, // d7abdd0a - ScrollResponderType, // 079145bb + ScrollResponderType, // 41a19f80 ScrollToLocationParamsType, // d7ecdad1 - ScrollView, // 9c14c51c - ScrollViewImperativeMethods, // d3ff1532 - ScrollViewInstance, // 1ba64600 - ScrollViewProps, // c92ac7be + ScrollView, // 2339fb4f + ScrollViewImperativeMethods, // 7a01fc0d + ScrollViewInstance, // 51251f11 + ScrollViewProps, // b416f503 ScrollViewPropsAndroid, // 44210553 ScrollViewPropsIOS, // da991b9a ScrollViewScrollToOptions, // 3313411e SectionBase, // dca83594 - SectionList, // 626263b4 + SectionList, // 17c3ec79 SectionListData, // e0d79987 - SectionListInstance, // 019cdaef - SectionListProps, // 4cb9dfa9 + SectionListInstance, // cdbbc39f + SectionListProps, // c2d6578e SectionListRenderItem, // 466e3e87 SectionListRenderItemInfo, // d809238e Separators, // 6a45f7e3 @@ -6142,17 +6143,17 @@ export { StyleProp, // fa0e9b4a StyleSheet, // ebb07d46 SubmitBehavior, // c4ddf490 - Switch, // b004beeb + Switch, // f145ee86 SwitchChangeEvent, // 899635b1 SwitchInstance, // 3c50eec5 - SwitchProps, // ded81873 + SwitchProps, // f99eb847 Systrace, // 626d178c TVViewPropsIOS, // 330ce7b5 TargetedEvent, // 16e98910 TaskProvider, // 266dedf2 Text, // 90eee1c6 TextContentType, // 239b3ecc - TextInput, // d6b1ef70 + TextInput, // e104fe9a TextInputAndroidProps, // 3f09ce49 TextInputBlurEvent, // b77af40e TextInputChangeEvent, // f55eef98 @@ -6162,7 +6163,7 @@ export { TextInputIOSProps, // 0d05a855 TextInputInstance, // 5a0c0e0d TextInputKeyPressEvent, // 546c5d07 - TextInputProps, // d989bb55 + TextInputProps, // 9690cda6 TextInputSelectionChangeEvent, // e58f2abc TextInputSubmitEditingEvent, // 6bcb2aa5 TextInstance, // 05463a96 @@ -6188,19 +6189,19 @@ export { UIManager, // a1a7cc01 UTFSequence, // ad625158 Vibration, // 31e4bbf8 - View, // 443391a0 + View, // 89b0e520 ViewInstance, // ffde5573 - ViewProps, // 013135a2 + ViewProps, // 7e922820 ViewPropsAndroid, // 03c17367 ViewPropsIOS, // 58ee19bf ViewStyle, // e45056b1 VirtualViewMode, // 6be59722 VirtualizedList, // 68c7345e VirtualizedListInstance, // 423ee7c0 - VirtualizedListProps, // 29367911 + VirtualizedListProps, // 3596559d VirtualizedSectionList, // 9fd9cd61 VirtualizedSectionListInstance, // 12b706d5 - VirtualizedSectionListProps, // 6b9d9c05 + VirtualizedSectionListProps, // 0124d272 WrapperComponentProvider, // 4b8c7962 codegenNativeCommands, // 628a7c0a codegenNativeComponent, // 32a1bca6 From 2b6abcbae71552c1ddb58bacd674db1a5aea0061 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Thu, 25 Jun 2026 18:34:12 +0200 Subject: [PATCH 20/30] fix api snapshots --- packages/rn-tester/js/examples/Pressable/PressableExample.js | 2 +- scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api | 1 + scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api | 1 + scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api | 1 + scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api | 1 + scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api | 1 + scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api | 1 + scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api | 1 + scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api | 1 + scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api | 1 + 10 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/rn-tester/js/examples/Pressable/PressableExample.js b/packages/rn-tester/js/examples/Pressable/PressableExample.js index cf58e3e0e81b..18b475a3789a 100644 --- a/packages/rn-tester/js/examples/Pressable/PressableExample.js +++ b/packages/rn-tester/js/examples/Pressable/PressableExample.js @@ -739,7 +739,7 @@ const examples = [ }, }, { - title: 'blockNativeResponder', + 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, 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; From 884730f4807f85a1a57eb6c391437900a169a2b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Thu, 25 Jun 2026 19:29:01 +0200 Subject: [PATCH 21/30] fix ios: move disabled check to JS side to avoid setIsJSResponder timing race Co-Authored-By: Claude Sonnet 4.6 --- .../Libraries/Components/Pressable/Pressable.js | 2 +- .../Mounting/ComponentViews/View/RCTViewComponentView.mm | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/react-native/Libraries/Components/Pressable/Pressable.js b/packages/react-native/Libraries/Components/Pressable/Pressable.js index f882b048ca4c..1959a05ea6b0 100644 --- a/packages/react-native/Libraries/Components/Pressable/Pressable.js +++ b/packages/react-native/Libraries/Components/Pressable/Pressable.js @@ -340,7 +340,7 @@ function Pressable({ ref={mergedRef} style={typeof style === 'function' ? style({pressed}) : style} collapsable={false} - blockNativeResponder={blockNativeResponder}> + blockNativeResponder={disabled !== true && blockNativeResponder}> {typeof children === 'function' ? children({pressed}) : children} {__DEV__ ? : null} 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 df43d76ec4dd..6710d1d8afcc 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -675,7 +675,7 @@ - (void)setIsJSResponder:(BOOL)isJSResponder - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { const auto &viewProps = static_cast(*_props); - if (viewProps.blockNativeResponder && _isJSResponder) { + if (viewProps.blockNativeResponder) { return; } [super touchesBegan:touches withEvent:event]; @@ -684,7 +684,7 @@ - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { const auto &viewProps = static_cast(*_props); - if (viewProps.blockNativeResponder && _isJSResponder) { + if (viewProps.blockNativeResponder) { return; } [super touchesMoved:touches withEvent:event]; @@ -693,7 +693,7 @@ - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { const auto &viewProps = static_cast(*_props); - if (viewProps.blockNativeResponder && _isJSResponder) { + if (viewProps.blockNativeResponder) { return; } [super touchesEnded:touches withEvent:event]; @@ -702,7 +702,7 @@ - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { const auto &viewProps = static_cast(*_props); - if (viewProps.blockNativeResponder && _isJSResponder) { + if (viewProps.blockNativeResponder) { return; } [super touchesCancelled:touches withEvent:event]; From 67ffbf2e2bdf35082d75a8c046a74c138a738f7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Fri, 26 Jun 2026 10:17:20 +0200 Subject: [PATCH 22/30] add comment explaining blockNativeResponder disabled guard Co-Authored-By: Claude Sonnet 4.6 --- .../react-native/Libraries/Components/Pressable/Pressable.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/react-native/Libraries/Components/Pressable/Pressable.js b/packages/react-native/Libraries/Components/Pressable/Pressable.js index 1959a05ea6b0..360a6aa3b7d9 100644 --- a/packages/react-native/Libraries/Components/Pressable/Pressable.js +++ b/packages/react-native/Libraries/Components/Pressable/Pressable.js @@ -340,6 +340,10 @@ function Pressable({ ref={mergedRef} style={typeof style === 'function' ? style({pressed}) : style} collapsable={false} + // Disabled when `disabled` to allow touches to reach native ancestors. + // Cannot use `_isJSResponder` on the native side because it is set via + // RCTExecuteOnMainQueue (async), so it may not be true yet when + // touchesBegan fires, causing a race condition on fast taps. blockNativeResponder={disabled !== true && blockNativeResponder}> {typeof children === 'function' ? children({pressed}) : children} {__DEV__ ? : null} From 9fa06bf321fbd8174e09dcf8dbaf946ab5698679 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Fri, 26 Jun 2026 10:19:13 +0200 Subject: [PATCH 23/30] tighten comment on blockNativeResponder disabled guard Co-Authored-By: Claude Sonnet 4.6 --- .../Libraries/Components/Pressable/Pressable.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/react-native/Libraries/Components/Pressable/Pressable.js b/packages/react-native/Libraries/Components/Pressable/Pressable.js index 360a6aa3b7d9..394fc16925d0 100644 --- a/packages/react-native/Libraries/Components/Pressable/Pressable.js +++ b/packages/react-native/Libraries/Components/Pressable/Pressable.js @@ -340,10 +340,9 @@ function Pressable({ ref={mergedRef} style={typeof style === 'function' ? style({pressed}) : style} collapsable={false} - // Disabled when `disabled` to allow touches to reach native ancestors. - // Cannot use `_isJSResponder` on the native side because it is set via - // RCTExecuteOnMainQueue (async), so it may not be true yet when - // touchesBegan fires, causing a race condition on fast taps. + // 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} From b20805624725dea853a64eaddcb21da45830918e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Fri, 26 Jun 2026 11:01:40 +0200 Subject: [PATCH 24/30] reduce public type churn --- .../Components/Pressable/Pressable.js | 1 + .../Components/View/ViewPropTypes.js | 9 -- packages/react-native/ReactNativeApi.d.ts | 89 +++++++++---------- 3 files changed, 45 insertions(+), 54 deletions(-) diff --git a/packages/react-native/Libraries/Components/Pressable/Pressable.js b/packages/react-native/Libraries/Components/Pressable/Pressable.js index 394fc16925d0..de2f547e6c3f 100644 --- a/packages/react-native/Libraries/Components/Pressable/Pressable.js +++ b/packages/react-native/Libraries/Components/Pressable/Pressable.js @@ -343,6 +343,7 @@ function Pressable({ // 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. + // $FlowFixMe[prop-missing] internal prop, intentionally absent from public ViewProps blockNativeResponder={disabled !== true && blockNativeResponder}> {typeof children === 'function' ? children({pressed}) : children} {__DEV__ ? : null} diff --git a/packages/react-native/Libraries/Components/View/ViewPropTypes.js b/packages/react-native/Libraries/Components/View/ViewPropTypes.js index fe1fa8695813..6c4be3a025ca 100644 --- a/packages/react-native/Libraries/Components/View/ViewPropTypes.js +++ b/packages/react-native/Libraries/Components/View/ViewPropTypes.js @@ -502,15 +502,6 @@ type ViewBaseProps = Readonly<{ */ removeClippedSubviews?: ?boolean, - /** - * When true, prevents native ancestor views (UIKit responder chain) from - * receiving touch events when this view handles a press. Without this, UIKit - * delivers touches independently to every view that received touchesBegan:, - * so parent views fire touchesEnded: even when a child Pressable handles the - * press. - */ - blockNativeResponder?: ?boolean, - /** * Defines the order in which descendant elements receive accessibility focus. * The elements in the array represent nativeID values for the respective diff --git a/packages/react-native/ReactNativeApi.d.ts b/packages/react-native/ReactNativeApi.d.ts index 0d10264488f1..699c2fff455a 100644 --- a/packages/react-native/ReactNativeApi.d.ts +++ b/packages/react-native/ReactNativeApi.d.ts @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<0ff5693800c50074fbf1c08e88c50093>> + * @generated SignedSource<<42200de8ca10d30541e23b67547d9a13>> * * This file was generated by scripts/js-api/build-types/index.js. */ @@ -5659,7 +5659,6 @@ declare class ViewabilityHelper_default { } declare type ViewabilityHelperT = typeof ViewabilityHelper_default declare type ViewBaseProps = { - readonly blockNativeResponder?: boolean readonly children?: React.ReactNode readonly collapsable?: boolean readonly collapsableChildren?: boolean @@ -5912,16 +5911,16 @@ export { AccessibilityValue, // cf8bcb74 ActionSheetIOS, // b558559e ActionSheetIOSOptions, // 1756eb5a - ActivityIndicator, // d7fc42fd + ActivityIndicator, // 0f796809 ActivityIndicatorInstance, // a82dd4e7 - ActivityIndicatorProps, // 571c60c9 + ActivityIndicatorProps, // a232e3ff Alert, // 5bf12165 AlertButton, // bf1a3b60 AlertButtonStyle, // ec9fb242 AlertOptions, // a0cdac0f AlertType, // 5ab91217 AndroidKeyboardEvent, // e03becc8 - Animated, // a8925b59 + Animated, // 1ac00401 AppConfig, // 35c0ca70 AppRegistry, // 7ef8e53a AppState, // 12012be5 @@ -5953,9 +5952,9 @@ export { DimensionsPayload, // 653bc26c DisplayMetrics, // 1dc35cef DisplayMetricsAndroid, // 872e62eb - DrawerLayoutAndroid, // 751bb72f + DrawerLayoutAndroid, // a8ced056 DrawerLayoutAndroidInstance, // c0694352 - DrawerLayoutAndroidProps, // 8ff2c62d + DrawerLayoutAndroidProps, // e9a67dd7 DrawerSlideEvent, // c4ab8fba DropShadowValue, // e9df2606 DynamicColorIOS, // d96c228c @@ -5971,9 +5970,9 @@ export { EventSubscription, // b8d084aa ExtendedExceptionData, // 5a6ccf5a FilterFunction, // bf24c0e3 - FlatList, // f3bcf76d - FlatListInstance, // e7a79099 - FlatListProps, // 48c58790 + FlatList, // b5cfde24 + FlatListInstance, // 8f76f3b2 + FlatListProps, // 3725656d FocusEvent, // 850f1517 FontVariant, // 7c7558bb GestureResponderEvent, // 14d3e77a @@ -5984,17 +5983,17 @@ export { IEventEmitter, // fbef6131 IOSKeyboardEvent, // e67bfe3a IgnorePattern, // ec6f6ece - Image, // b2561656 - ImageBackground, // ea7e7e08 - ImageBackgroundInstance, // 677f9154 - ImageBackgroundProps, // 7354b374 + Image, // fba54a35 + ImageBackground, // 1543bae2 + ImageBackgroundInstance, // 610d9eed + ImageBackgroundProps, // ceb153c3 ImageErrorEvent, // 978933f4 ImageInstance, // 9a100753 ImageLoadEvent, // 77f0b718 ImageProgressEventIOS, // 445331a4 - ImageProps, // e685c003 + ImageProps, // e8aecf89 ImagePropsAndroid, // 9fd9bcbb - ImagePropsBase, // dabc9112 + ImagePropsBase, // f5535cef ImagePropsIOS, // 4a080668 ImageRequireSource, // 681d683b ImageResizeMode, // d51106e2 @@ -6012,9 +6011,9 @@ export { KeyEvent, // 20fa4267 KeyUpEvent, // 57f832c5 Keyboard, // 49414c97 - KeyboardAvoidingView, // 8a6b2692 - KeyboardAvoidingViewInstance, // 85c50e87 - KeyboardAvoidingViewProps, // f721c5a8 + KeyboardAvoidingView, // cf0307d2 + KeyboardAvoidingViewInstance, // c4ca43a7 + KeyboardAvoidingViewProps, // cf148633 KeyboardEvent, // c3f895d4 KeyboardEventEasing, // af4091c8 KeyboardEventName, // 59299ad6 @@ -6039,10 +6038,10 @@ export { MeasureInWindowOnSuccessCallback, // a285f598 MeasureLayoutOnSuccessCallback, // 3592502a MeasureOnSuccessCallback, // 82824e59 - Modal, // 325aa23a + Modal, // c8c01498 ModalBaseProps, // 71945951 ModalInstance, // d466ce77 - ModalProps, // bbf2cddf + ModalProps, // 9f060027 ModalPropsAndroid, // 515fb173 ModalPropsIOS, // 0e13cfcc ModeChangeEvent, // f64bf69d @@ -6077,15 +6076,15 @@ export { PointerEvent, // ff599afe PressabilityConfig, // faab5639 PressabilityEventHandlers, // 0b910091 - Pressable, // 699b380d + Pressable, // f4911021 PressableAndroidRippleConfig, // ee32eaca PressableInstance, // eebfe911 - PressableProps, // ce74ed6d + PressableProps, // 95d57db2 PressableStateCallbackType, // 9af36561 ProcessedColorValue, // 33f74304 - ProgressBarAndroid, // 54e0bdce + ProgressBarAndroid, // 44a46d48 ProgressBarAndroidInstance, // ab545ef1 - ProgressBarAndroidProps, // 3409491a + ProgressBarAndroidProps, // f20ebde5 PublicRootInstance, // 8040afd7 PublicTextInstance, // 6937c7bf PushNotificationEventName, // 84e7e150 @@ -6093,9 +6092,9 @@ export { PushNotificationPermissions, // c2e7ae4f Rationale, // 5df1b1c1 ReactNativeVersion, // abd76827 - RefreshControl, // 9981464e - RefreshControlInstance, // a473cb11 - RefreshControlProps, // d1474044 + RefreshControl, // 5b3465d2 + RefreshControlInstance, // 8b4d078b + RefreshControlProps, // f8883f9a RefreshControlPropsAndroid, // 99f64c97 RefreshControlPropsIOS, // 72a36381 Registry, // 6c39216d @@ -6107,24 +6106,24 @@ export { RootViewStyleProvider, // 8792d506 Runnable, // 594dd93a Runnables, // 4367c557 - SafeAreaView, // a133c7b4 + SafeAreaView, // 47b1ede1 SafeAreaViewInstance, // 21dba39c ScaledSize, // 07e417c7 ScrollEvent, // d7abdd0a - ScrollResponderType, // 41a19f80 + ScrollResponderType, // 079145bb ScrollToLocationParamsType, // d7ecdad1 - ScrollView, // 2339fb4f - ScrollViewImperativeMethods, // 7a01fc0d - ScrollViewInstance, // 51251f11 - ScrollViewProps, // b416f503 + ScrollView, // 9c14c51c + ScrollViewImperativeMethods, // d3ff1532 + ScrollViewInstance, // 1ba64600 + ScrollViewProps, // c92ac7be ScrollViewPropsAndroid, // 44210553 ScrollViewPropsIOS, // da991b9a ScrollViewScrollToOptions, // 3313411e SectionBase, // dca83594 - SectionList, // 17c3ec79 + SectionList, // 626263b4 SectionListData, // e0d79987 - SectionListInstance, // cdbbc39f - SectionListProps, // c2d6578e + SectionListInstance, // 019cdaef + SectionListProps, // 4cb9dfa9 SectionListRenderItem, // 466e3e87 SectionListRenderItemInfo, // d809238e Separators, // 6a45f7e3 @@ -6143,17 +6142,17 @@ export { StyleProp, // fa0e9b4a StyleSheet, // ebb07d46 SubmitBehavior, // c4ddf490 - Switch, // f145ee86 + Switch, // b004beeb SwitchChangeEvent, // 899635b1 SwitchInstance, // 3c50eec5 - SwitchProps, // f99eb847 + SwitchProps, // ded81873 Systrace, // 626d178c TVViewPropsIOS, // 330ce7b5 TargetedEvent, // 16e98910 TaskProvider, // 266dedf2 Text, // 90eee1c6 TextContentType, // 239b3ecc - TextInput, // e104fe9a + TextInput, // d6b1ef70 TextInputAndroidProps, // 3f09ce49 TextInputBlurEvent, // b77af40e TextInputChangeEvent, // f55eef98 @@ -6163,7 +6162,7 @@ export { TextInputIOSProps, // 0d05a855 TextInputInstance, // 5a0c0e0d TextInputKeyPressEvent, // 546c5d07 - TextInputProps, // 9690cda6 + TextInputProps, // d989bb55 TextInputSelectionChangeEvent, // e58f2abc TextInputSubmitEditingEvent, // 6bcb2aa5 TextInstance, // 05463a96 @@ -6189,19 +6188,19 @@ export { UIManager, // a1a7cc01 UTFSequence, // ad625158 Vibration, // 31e4bbf8 - View, // 89b0e520 + View, // 443391a0 ViewInstance, // ffde5573 - ViewProps, // 7e922820 + ViewProps, // 013135a2 ViewPropsAndroid, // 03c17367 ViewPropsIOS, // 58ee19bf ViewStyle, // e45056b1 VirtualViewMode, // 6be59722 VirtualizedList, // 68c7345e VirtualizedListInstance, // 423ee7c0 - VirtualizedListProps, // 3596559d + VirtualizedListProps, // 29367911 VirtualizedSectionList, // 9fd9cd61 VirtualizedSectionListInstance, // 12b706d5 - VirtualizedSectionListProps, // 0124d272 + VirtualizedSectionListProps, // 6b9d9c05 WrapperComponentProvider, // 4b8c7962 codegenNativeCommands, // 628a7c0a codegenNativeComponent, // 32a1bca6 From 07d001f750ef006ba06fa1254a2fe597cbc02f47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Fri, 26 Jun 2026 11:31:34 +0200 Subject: [PATCH 25/30] fix flow typecheck --- .../react-native/Libraries/Components/Pressable/Pressable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native/Libraries/Components/Pressable/Pressable.js b/packages/react-native/Libraries/Components/Pressable/Pressable.js index de2f547e6c3f..42f89237515c 100644 --- a/packages/react-native/Libraries/Components/Pressable/Pressable.js +++ b/packages/react-native/Libraries/Components/Pressable/Pressable.js @@ -343,7 +343,7 @@ function Pressable({ // 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. - // $FlowFixMe[prop-missing] internal prop, intentionally absent from public ViewProps + // $FlowFixMe[incompatible-type] internal prop, intentionally absent from public ViewProps blockNativeResponder={disabled !== true && blockNativeResponder}> {typeof children === 'function' ? children({pressed}) : children} {__DEV__ ? : null} From 44e89d900dd9600b35be74854cce76bfeee6136e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Fri, 26 Jun 2026 11:36:56 +0200 Subject: [PATCH 26/30] fix flow check --- .../react-native/Libraries/Components/Pressable/Pressable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native/Libraries/Components/Pressable/Pressable.js b/packages/react-native/Libraries/Components/Pressable/Pressable.js index 42f89237515c..8009d298cef1 100644 --- a/packages/react-native/Libraries/Components/Pressable/Pressable.js +++ b/packages/react-native/Libraries/Components/Pressable/Pressable.js @@ -334,6 +334,7 @@ function Pressable({ const eventHandlers = usePressability(config); return ( + // $FlowFixMe[incompatible-type] blockNativeResponder is an internal prop, intentionally absent from public ViewProps {typeof children === 'function' ? children({pressed}) : children} {__DEV__ ? : null} From b0ea2e13c946b497fae80b77d9256181a6179c5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Fri, 26 Jun 2026 12:01:29 +0200 Subject: [PATCH 27/30] tweaks --- .../rn-tester/js/examples/Pressable/PressableExample.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/rn-tester/js/examples/Pressable/PressableExample.js b/packages/rn-tester/js/examples/Pressable/PressableExample.js index 18b475a3789a..134d15171c7e 100644 --- a/packages/rn-tester/js/examples/Pressable/PressableExample.js +++ b/packages/rn-tester/js/examples/Pressable/PressableExample.js @@ -292,7 +292,6 @@ function PressableBlockNativeResponderExample() { const [log, setLog] = useState>([]); const onNativeTouch = () => setLog(prev => [...prev, 'NativeTouchReceiver.onNativeTouch']); - const onPressIn = () => setLog([]); const onPress = () => setLog(prev => [...prev, 'Pressable.onPress']); return ( @@ -318,13 +317,17 @@ function PressableBlockNativeResponderExample() { paddingHorizontal: 20, }} blockNativeResponder={blockNativeResponder} - onPressIn={onPressIn} onPress={onPress}> Press Me + + setLog([])}> + Clear log + + ); From f38462ec59e979d34ed0b4c853249ff0bc8dd5c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Fri, 26 Jun 2026 12:20:21 +0200 Subject: [PATCH 28/30] fix android build warnings --- .../uiapp/component/RNTNativeTouchReceiverView.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) 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 index 32e012a4189e..0f0c90bf5908 100644 --- 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 @@ -22,17 +22,17 @@ import com.facebook.react.views.view.ReactViewGroup */ internal class RNTNativeTouchReceiverView(context: ThemedReactContext) : ReactViewGroup(context) { - override fun onInterceptTouchEvent(ev: MotionEvent): Boolean { - val intercepted = super.onInterceptTouchEvent(ev) - if (ev.action == MotionEvent.ACTION_UP) { + override fun onInterceptTouchEvent(event: MotionEvent): Boolean { + val intercepted = super.onInterceptTouchEvent(event) + if (event.action == MotionEvent.ACTION_UP) { emitNativeTouchEvent() } return intercepted } - override fun onTouchEvent(ev: MotionEvent): Boolean { - val handled = super.onTouchEvent(ev) - if (ev.action == MotionEvent.ACTION_UP) { + override fun onTouchEvent(event: MotionEvent): Boolean { + val handled = super.onTouchEvent(event) + if (event.action == MotionEvent.ACTION_UP) { emitNativeTouchEvent() } return handled From 1c1602b056f2245bc6665dc4e476ad9331e66a96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Fri, 26 Jun 2026 12:47:24 +0200 Subject: [PATCH 29/30] improve comment --- .../Libraries/Components/Pressable/Pressable.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/react-native/Libraries/Components/Pressable/Pressable.js b/packages/react-native/Libraries/Components/Pressable/Pressable.js index 8009d298cef1..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, /** - * When true, prevents native ancestor views (UIKit responder chain) from - * receiving touch events when this Pressable handles a press. + * 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, From 9d3c17ed5258104201cf4d2098b96097254ff2b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Fri, 26 Jun 2026 13:10:28 +0200 Subject: [PATCH 30/30] fix prettier --- .../rn-tester/js/examples/Pressable/PressableExample.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/rn-tester/js/examples/Pressable/PressableExample.js b/packages/rn-tester/js/examples/Pressable/PressableExample.js index 134d15171c7e..84d713c3a00b 100644 --- a/packages/rn-tester/js/examples/Pressable/PressableExample.js +++ b/packages/rn-tester/js/examples/Pressable/PressableExample.js @@ -323,7 +323,12 @@ function PressableBlockNativeResponderExample() { - + setLog([])}> Clear log