From a1b49ec3164887cd9012c360f423397a55e28385 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20=C5=BBelawski?= Date: Tue, 14 Jul 2026 14:10:02 +0200 Subject: [PATCH 1/4] fix: prevent programmatic scrolls from cancelling active touches on iOS --- .../ComponentViews/ScrollView/RCTScrollViewComponentView.mm | 1 + .../react/renderer/components/scrollview/ScrollEvent.cpp | 4 +++- .../react/renderer/components/scrollview/ScrollEvent.h | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm index 548987ff291d..cd96841fa611 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm @@ -599,6 +599,7 @@ - (BOOL)_shouldDisableScrollInteraction metrics.containerSize = RCTSizeFromCGSize(_scrollView.bounds.size); metrics.zoomScale = _scrollView.zoomScale; metrics.timestamp = CACurrentMediaTime(); + metrics.responderIgnoreScroll = !_isUserTriggeredScrolling; if (_layoutMetrics.layoutDirection == LayoutDirection::RightToLeft) { metrics.contentOffset.x = metrics.contentSize.width - metrics.containerSize.width - metrics.contentOffset.x; diff --git a/packages/react-native/ReactCommon/react/renderer/components/scrollview/ScrollEvent.cpp b/packages/react-native/ReactCommon/react/renderer/components/scrollview/ScrollEvent.cpp index 7adacc575272..5307df67ff62 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/scrollview/ScrollEvent.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/scrollview/ScrollEvent.cpp @@ -44,6 +44,7 @@ jsi::Value ScrollEvent::asJSIValue(jsi::Runtime& runtime) const { payload.setProperty(runtime, "zoomScale", zoomScale); payload.setProperty(runtime, "timestamp", timestamp * 1000); + payload.setProperty(runtime, "responderIgnoreScroll", responderIgnoreScroll); return payload; } @@ -67,7 +68,8 @@ folly::dynamic ScrollEvent::asDynamic() const { "contentInset", std::move(contentInsetObj))( "contentSize", std::move(contentSizeObj))( "layoutMeasurement", std::move(containerSizeObj))( - "zoomScale", zoomScale)("timestamp", timestamp * 1000); + "zoomScale", zoomScale)("timestamp", timestamp * 1000)( + "responderIgnoreScroll", responderIgnoreScroll); return metrics; }; diff --git a/packages/react-native/ReactCommon/react/renderer/components/scrollview/ScrollEvent.h b/packages/react-native/ReactCommon/react/renderer/components/scrollview/ScrollEvent.h index fb2cd8b44fd5..c3fd9583828e 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/scrollview/ScrollEvent.h +++ b/packages/react-native/ReactCommon/react/renderer/components/scrollview/ScrollEvent.h @@ -21,6 +21,7 @@ struct ScrollEvent : public EventPayload { EdgeInsets contentInset; Size containerSize; Float zoomScale{}; + bool responderIgnoreScroll{false}; /* * The time in seconds when the touch occurred or when it was last mutated. From 8723cea7857ffe54117143bc8ae221a84cc8a1c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20=C5=BBelawski?= Date: Wed, 15 Jul 2026 09:42:43 +0200 Subject: [PATCH 2/4] chore: cxx api snapshots --- 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 + 9 files changed, 9 insertions(+) diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api index 0aaf2efc5caa..9ea34a485d14 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api @@ -7858,6 +7858,7 @@ struct facebook::react::ScrollEndDragEvent : public facebook::react::ScrollEvent struct facebook::react::ScrollEvent : public facebook::react::EventPayload { public ScrollEvent() = default; + public bool responderIgnoreScroll; public facebook::react::EdgeInsets contentInset; public facebook::react::Float timestamp; public facebook::react::Float zoomScale; diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api index be2958293dad..922137b650b5 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api @@ -7619,6 +7619,7 @@ struct facebook::react::ScrollEndDragEvent : public facebook::react::ScrollEvent struct facebook::react::ScrollEvent : public facebook::react::EventPayload { public ScrollEvent() = default; + public bool responderIgnoreScroll; public facebook::react::EdgeInsets contentInset; public facebook::react::Float timestamp; public facebook::react::Float zoomScale; diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api index faf7b0b6db37..3528eea0b92b 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api @@ -7849,6 +7849,7 @@ struct facebook::react::ScrollEndDragEvent : public facebook::react::ScrollEvent struct facebook::react::ScrollEvent : public facebook::react::EventPayload { public ScrollEvent() = default; + public bool responderIgnoreScroll; public facebook::react::EdgeInsets contentInset; public facebook::react::Float timestamp; public facebook::react::Float zoomScale; diff --git a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api index 2ec052291919..051f7374c5a4 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api @@ -9891,6 +9891,7 @@ struct facebook::react::ScrollEndDragEvent : public facebook::react::ScrollEvent struct facebook::react::ScrollEvent : public facebook::react::EventPayload { public ScrollEvent() = default; + public bool responderIgnoreScroll; public facebook::react::EdgeInsets contentInset; public facebook::react::Float timestamp; public facebook::react::Float zoomScale; diff --git a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api index 319fba1e0d52..743b41ed91a8 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api @@ -9704,6 +9704,7 @@ struct facebook::react::ScrollEndDragEvent : public facebook::react::ScrollEvent struct facebook::react::ScrollEvent : public facebook::react::EventPayload { public ScrollEvent() = default; + public bool responderIgnoreScroll; public facebook::react::EdgeInsets contentInset; public facebook::react::Float timestamp; public facebook::react::Float zoomScale; diff --git a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api index 3ec823148f22..8f16c9a2fd76 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api @@ -9882,6 +9882,7 @@ struct facebook::react::ScrollEndDragEvent : public facebook::react::ScrollEvent struct facebook::react::ScrollEvent : public facebook::react::EventPayload { public ScrollEvent() = default; + public bool responderIgnoreScroll; public facebook::react::EdgeInsets contentInset; public facebook::react::Float timestamp; public facebook::react::Float zoomScale; diff --git a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api index 64301436eeda..bf1b6a6a4874 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api @@ -6004,6 +6004,7 @@ struct facebook::react::ScrollEvent : public facebook::react::EventPayload { public Float timestamp; public Float zoomScale; public ScrollEvent() = default; + public bool responderIgnoreScroll; public facebook::react::EdgeInsets contentInset; public facebook::react::Point contentOffset; public facebook::react::Size containerSize; diff --git a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api index eda51abd3696..d80736b3aa89 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api @@ -5829,6 +5829,7 @@ struct facebook::react::ScrollEvent : public facebook::react::EventPayload { public Float timestamp; public Float zoomScale; public ScrollEvent() = default; + public bool responderIgnoreScroll; public facebook::react::EdgeInsets contentInset; public facebook::react::Point contentOffset; public facebook::react::Size containerSize; diff --git a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api index 3c9c5e8ab249..2e011217998d 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api @@ -5995,6 +5995,7 @@ struct facebook::react::ScrollEvent : public facebook::react::EventPayload { public Float timestamp; public Float zoomScale; public ScrollEvent() = default; + public bool responderIgnoreScroll; public facebook::react::EdgeInsets contentInset; public facebook::react::Point contentOffset; public facebook::react::Size containerSize; From ef05bac834d34e6759ab9409fb78577612ec6298 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20=C5=BBelawski?= Date: Tue, 18 Aug 2026 14:50:15 +0200 Subject: [PATCH 3/4] fix: other scroll events --- .../Libraries/Components/TextInput/TextInput.d.ts | 1 + .../ComponentViews/TextInput/RCTTextInputComponentView.mm | 4 +++- .../renderer/components/textinput/TextInputEventEmitter.cpp | 3 +++ .../renderer/components/textinput/TextInputEventEmitter.h | 1 + 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 + 13 files changed, 17 insertions(+), 1 deletion(-) diff --git a/packages/react-native/Libraries/Components/TextInput/TextInput.d.ts b/packages/react-native/Libraries/Components/TextInput/TextInput.d.ts index 9cb01c24c646..8bab82d8ea4f 100644 --- a/packages/react-native/Libraries/Components/TextInput/TextInput.d.ts +++ b/packages/react-native/Libraries/Components/TextInput/TextInput.d.ts @@ -440,6 +440,7 @@ export type TextInputFocusEvent = NativeSyntheticEvent; */ export interface TextInputScrollEventData { contentOffset: {x: number; y: number}; + responderIgnoreScroll?: boolean; } /** diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm index c0e56acd2a0f..4281264bc893 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -530,7 +530,9 @@ - (void)textInputDidChangeSelection - (void)scrollViewDidScroll:(UIScrollView *)scrollView { if (_eventEmitter) { - static_cast(*_eventEmitter).onScroll([self _textInputMetrics]); + auto metrics = [self _textInputMetrics]; + metrics.responderIgnoreScroll = !(scrollView.isDragging || scrollView.isDecelerating); + static_cast(*_eventEmitter).onScroll(metrics); } } diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/TextInputEventEmitter.cpp b/packages/react-native/ReactCommon/react/renderer/components/textinput/TextInputEventEmitter.cpp index 2982b12fe014..7da2622926dc 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/textinput/TextInputEventEmitter.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/TextInputEventEmitter.cpp @@ -86,6 +86,9 @@ static jsi::Value textInputMetricsScrollPayload( "zoomScale", textInputMetrics.zoomScale != 0.0f ? textInputMetrics.zoomScale : 1); + payload.setProperty( + runtime, "responderIgnoreScroll", textInputMetrics.responderIgnoreScroll); + return payload; }; diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/TextInputEventEmitter.h b/packages/react-native/ReactCommon/react/renderer/components/textinput/TextInputEventEmitter.h index a3f2e01c130c..78f9e7d4f4bb 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/textinput/TextInputEventEmitter.h +++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/TextInputEventEmitter.h @@ -27,6 +27,7 @@ class TextInputEventEmitter : public ViewEventEmitter { int eventCount; Size layoutMeasurement; Float zoomScale; + bool responderIgnoreScroll{false}; Tag target; }; diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api index 9ea34a485d14..69d8cbc5cc57 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api @@ -5031,6 +5031,7 @@ struct facebook::react::TextInputEventEmitter::KeyPressMetrics { } struct facebook::react::TextInputEventEmitter::Metrics { + public bool responderIgnoreScroll; public facebook::react::AttributedString::Range selectionRange; public facebook::react::EdgeInsets contentInset; public facebook::react::Float zoomScale; diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api index 922137b650b5..1bd4f57c4b45 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api @@ -4842,6 +4842,7 @@ struct facebook::react::TextInputEventEmitter::KeyPressMetrics { } struct facebook::react::TextInputEventEmitter::Metrics { + public bool responderIgnoreScroll; public facebook::react::AttributedString::Range selectionRange; public facebook::react::EdgeInsets contentInset; public facebook::react::Float zoomScale; diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api index 3528eea0b92b..073796a6badc 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api @@ -5022,6 +5022,7 @@ struct facebook::react::TextInputEventEmitter::KeyPressMetrics { } struct facebook::react::TextInputEventEmitter::Metrics { + public bool responderIgnoreScroll; public facebook::react::AttributedString::Range selectionRange; public facebook::react::EdgeInsets contentInset; public facebook::react::Float zoomScale; diff --git a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api index 051f7374c5a4..01555a7656d0 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api @@ -7217,6 +7217,7 @@ struct facebook::react::TextInputEventEmitter::KeyPressMetrics { } struct facebook::react::TextInputEventEmitter::Metrics { + public bool responderIgnoreScroll; public facebook::react::AttributedString::Range selectionRange; public facebook::react::EdgeInsets contentInset; public facebook::react::Float zoomScale; diff --git a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api index 743b41ed91a8..0378e4daec7b 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api @@ -7056,6 +7056,7 @@ struct facebook::react::TextInputEventEmitter::KeyPressMetrics { } struct facebook::react::TextInputEventEmitter::Metrics { + public bool responderIgnoreScroll; public facebook::react::AttributedString::Range selectionRange; public facebook::react::EdgeInsets contentInset; public facebook::react::Float zoomScale; diff --git a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api index 8f16c9a2fd76..e780af2a36c6 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api @@ -7208,6 +7208,7 @@ struct facebook::react::TextInputEventEmitter::KeyPressMetrics { } struct facebook::react::TextInputEventEmitter::Metrics { + public bool responderIgnoreScroll; public facebook::react::AttributedString::Range selectionRange; public facebook::react::EdgeInsets contentInset; public facebook::react::Float zoomScale; diff --git a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api index bf1b6a6a4874..bb9c152a3263 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api @@ -3545,6 +3545,7 @@ struct facebook::react::TextInputEventEmitter::KeyPressMetrics { struct facebook::react::TextInputEventEmitter::Metrics { public Float zoomScale; + public bool responderIgnoreScroll; public facebook::react::AttributedString::Range selectionRange; public facebook::react::EdgeInsets contentInset; public facebook::react::Point contentOffset; diff --git a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api index d80736b3aa89..51aac2eeeac7 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api @@ -3396,6 +3396,7 @@ struct facebook::react::TextInputEventEmitter::KeyPressMetrics { struct facebook::react::TextInputEventEmitter::Metrics { public Float zoomScale; + public bool responderIgnoreScroll; public facebook::react::AttributedString::Range selectionRange; public facebook::react::EdgeInsets contentInset; public facebook::react::Point contentOffset; diff --git a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api index 2e011217998d..14598f4f0de6 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api @@ -3536,6 +3536,7 @@ struct facebook::react::TextInputEventEmitter::KeyPressMetrics { struct facebook::react::TextInputEventEmitter::Metrics { public Float zoomScale; + public bool responderIgnoreScroll; public facebook::react::AttributedString::Range selectionRange; public facebook::react::EdgeInsets contentInset; public facebook::react::Point contentOffset; From 66b4a87f10fd9e5885e0bd6e8ff5b299ee04ad89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20=C5=BBelawski?= Date: Tue, 18 Aug 2026 15:42:15 +0200 Subject: [PATCH 4/4] fix: type --- .../Libraries/Components/TextInput/TextInput.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native/types_DEPRECATED/Libraries/Components/TextInput/TextInput.d.ts b/packages/react-native/types_DEPRECATED/Libraries/Components/TextInput/TextInput.d.ts index cd6902370fa3..21bb5c60658a 100644 --- a/packages/react-native/types_DEPRECATED/Libraries/Components/TextInput/TextInput.d.ts +++ b/packages/react-native/types_DEPRECATED/Libraries/Components/TextInput/TextInput.d.ts @@ -440,7 +440,7 @@ export type TextInputFocusEvent = NativeSyntheticEvent; */ export interface TextInputScrollEventData { contentOffset: {x: number; y: number}; - responderIgnoreScroll?: boolean; + responderIgnoreScroll?: boolean | undefined; } /**