diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index aa31d38cc437..8e29b69bf890 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -6025,6 +6025,7 @@ public abstract interface class com/facebook/react/views/scroll/VirtualView { public final class com/facebook/react/views/swiperefresh/ReactSwipeRefreshLayout : androidx/swiperefreshlayout/widget/SwipeRefreshLayout { public fun (Lcom/facebook/react/bridge/ReactContext;)V public fun canChildScrollUp ()Z + public fun dispatchGenericMotionEvent (Landroid/view/MotionEvent;)Z public fun onInterceptTouchEvent (Landroid/view/MotionEvent;)Z public fun onLayout (ZIIII)V public fun onTouchEvent (Landroid/view/MotionEvent;)Z diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt index 468ebfe32d13..b54cceccd290 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt @@ -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<<7a9f9d29e1d5f01df33a0893e143a6db>> + * @generated SignedSource<<8cca42a9160a8a0e307a00e53cad46e8>> */ /** @@ -384,6 +384,12 @@ public object ReactNativeFeatureFlags { @JvmStatic public fun overrideBySynchronousMountPropsAtMountingAndroid(): Boolean = accessor.overrideBySynchronousMountPropsAtMountingAndroid() + /** + * When enabled, ReactSwipeRefreshLayout will forward ACTION_SCROLL events to its child for proper handling. + */ + @JvmStatic + public fun passScrollToSwipeRefreshChild(): Boolean = accessor.passScrollToSwipeRefreshChild() + /** * Enable reporting Performance Issues (`detail.devtools.performanceIssue`). Displayed in the V2 Performance Monitor and the "Performance Issues" sub-panel in DevTools. */ diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt index 476f2e30daf2..8a5fcef41ab2 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt @@ -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<> + * @generated SignedSource<<2adb48ad6907058ba092af9703994a57>> */ /** @@ -79,6 +79,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces private var fuseboxNetworkInspectionEnabledCache: Boolean? = null private var hideOffscreenVirtualViewsOnIOSCache: Boolean? = null private var overrideBySynchronousMountPropsAtMountingAndroidCache: Boolean? = null + private var passScrollToSwipeRefreshChildCache: Boolean? = null private var perfIssuesEnabledCache: Boolean? = null private var perfMonitorV2EnabledCache: Boolean? = null private var preparedTextCacheSizeCache: Double? = null @@ -630,6 +631,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces return cached } + override fun passScrollToSwipeRefreshChild(): Boolean { + var cached = passScrollToSwipeRefreshChildCache + if (cached == null) { + cached = ReactNativeFeatureFlagsCxxInterop.passScrollToSwipeRefreshChild() + passScrollToSwipeRefreshChildCache = cached + } + return cached + } + override fun perfIssuesEnabled(): Boolean { var cached = perfIssuesEnabledCache if (cached == null) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt index 525fb9a86b1e..b19e73006c9b 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt @@ -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<> + * @generated SignedSource<<54ca4d2bf514ce152b2f566205716aa6>> */ /** @@ -146,6 +146,8 @@ public object ReactNativeFeatureFlagsCxxInterop { @DoNotStrip @JvmStatic public external fun overrideBySynchronousMountPropsAtMountingAndroid(): Boolean + @DoNotStrip @JvmStatic public external fun passScrollToSwipeRefreshChild(): Boolean + @DoNotStrip @JvmStatic public external fun perfIssuesEnabled(): Boolean @DoNotStrip @JvmStatic public external fun perfMonitorV2Enabled(): Boolean diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt index 4a9e42bbe3d3..f5ea1b146701 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt @@ -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<> + * @generated SignedSource<<8f895394bc6263275f42c0bfa10674b4>> */ /** @@ -141,6 +141,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi override fun overrideBySynchronousMountPropsAtMountingAndroid(): Boolean = false + override fun passScrollToSwipeRefreshChild(): Boolean = false + override fun perfIssuesEnabled(): Boolean = false override fun perfMonitorV2Enabled(): Boolean = false diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt index 4b6b0bd24b9d..f6be5b19e94a 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt @@ -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<<44f52bff0ecd0f26c2173581b09dccce>> + * @generated SignedSource<<45b7473c077eb6fd4c2b46712962ddbd>> */ /** @@ -83,6 +83,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc private var fuseboxNetworkInspectionEnabledCache: Boolean? = null private var hideOffscreenVirtualViewsOnIOSCache: Boolean? = null private var overrideBySynchronousMountPropsAtMountingAndroidCache: Boolean? = null + private var passScrollToSwipeRefreshChildCache: Boolean? = null private var perfIssuesEnabledCache: Boolean? = null private var perfMonitorV2EnabledCache: Boolean? = null private var preparedTextCacheSizeCache: Double? = null @@ -693,6 +694,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc return cached } + override fun passScrollToSwipeRefreshChild(): Boolean { + var cached = passScrollToSwipeRefreshChildCache + if (cached == null) { + cached = currentProvider.passScrollToSwipeRefreshChild() + accessedFeatureFlags.add("passScrollToSwipeRefreshChild") + passScrollToSwipeRefreshChildCache = cached + } + return cached + } + override fun perfIssuesEnabled(): Boolean { var cached = perfIssuesEnabledCache if (cached == null) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt index 69eae3cdac01..a11fde174d37 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt @@ -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<<2bc1b7c78ced990301722e1c1dcc2dcf>> + * @generated SignedSource<> */ /** @@ -141,6 +141,8 @@ public interface ReactNativeFeatureFlagsProvider { @DoNotStrip public fun overrideBySynchronousMountPropsAtMountingAndroid(): Boolean + @DoNotStrip public fun passScrollToSwipeRefreshChild(): Boolean + @DoNotStrip public fun perfIssuesEnabled(): Boolean @DoNotStrip public fun perfMonitorV2Enabled(): Boolean diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/ReactSwipeRefreshLayout.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/ReactSwipeRefreshLayout.kt index 7574af951511..4733a1bb15a1 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/ReactSwipeRefreshLayout.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/swiperefresh/ReactSwipeRefreshLayout.kt @@ -12,11 +12,18 @@ import android.view.ViewConfiguration import android.view.ViewGroup import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import com.facebook.react.bridge.ReactContext +import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags import com.facebook.react.uimanager.PixelUtil import com.facebook.react.uimanager.events.NativeGestureUtil import kotlin.math.abs -/** Basic extension of [SwipeRefreshLayout] with ReactNative-specific functionality. */ +/** + * Basic extension of [SwipeRefreshLayout] with ReactNative-specific functionality. + * + * This component wraps a scrollable child (typically a ScrollView or RecyclerView) and provides + * pull-to-refresh functionality. It handles touch event interception for the refresh gesture while + * properly forwarding other events to its children. + */ public class ReactSwipeRefreshLayout(reactContext: ReactContext) : SwipeRefreshLayout(reactContext) { @@ -127,6 +134,31 @@ public class ReactSwipeRefreshLayout(reactContext: ReactContext) : return true } + /** + * Dispatches generic motion events to children. + * + * This override ensures that [MotionEvent.ACTION_SCROLL] events (from joystick, scrollwheel, or + * other pointing devices) are properly forwarded to child views. + */ + public override fun dispatchGenericMotionEvent(ev: MotionEvent): Boolean { + // For ACTION_SCROLL events, dispatch to child for handling + // The child ScrollView will use nested scrolling APIs to communicate with this + // SwipeRefreshLayout + if ( + ReactNativeFeatureFlags.passScrollToSwipeRefreshChild() && + ev.actionMasked == MotionEvent.ACTION_SCROLL + ) { + val child = getChildAt(0) + if (child != null) { + val handled = child.dispatchGenericMotionEvent(ev) + if (handled) { + return true + } + } + } + return super.dispatchGenericMotionEvent(ev) + } + private companion object { private const val DEFAULT_CIRCLE_TARGET = 64f } diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp index 14679e254340..9597f3342e98 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp @@ -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<<89d63e717ae8634f32613243bcbe2c40>> + * @generated SignedSource<<77d79172d377c62d4d26ddd16dab56ec>> */ /** @@ -393,6 +393,12 @@ class ReactNativeFeatureFlagsJavaProvider return method(javaProvider_); } + bool passScrollToSwipeRefreshChild() override { + static const auto method = + getReactNativeFeatureFlagsProviderJavaClass()->getMethod("passScrollToSwipeRefreshChild"); + return method(javaProvider_); + } + bool perfIssuesEnabled() override { static const auto method = getReactNativeFeatureFlagsProviderJavaClass()->getMethod("perfIssuesEnabled"); @@ -806,6 +812,11 @@ bool JReactNativeFeatureFlagsCxxInterop::overrideBySynchronousMountPropsAtMounti return ReactNativeFeatureFlags::overrideBySynchronousMountPropsAtMountingAndroid(); } +bool JReactNativeFeatureFlagsCxxInterop::passScrollToSwipeRefreshChild( + facebook::jni::alias_ref /*unused*/) { + return ReactNativeFeatureFlags::passScrollToSwipeRefreshChild(); +} + bool JReactNativeFeatureFlagsCxxInterop::perfIssuesEnabled( facebook::jni::alias_ref /*unused*/) { return ReactNativeFeatureFlags::perfIssuesEnabled(); @@ -1109,6 +1120,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() { makeNativeMethod( "overrideBySynchronousMountPropsAtMountingAndroid", JReactNativeFeatureFlagsCxxInterop::overrideBySynchronousMountPropsAtMountingAndroid), + makeNativeMethod( + "passScrollToSwipeRefreshChild", + JReactNativeFeatureFlagsCxxInterop::passScrollToSwipeRefreshChild), makeNativeMethod( "perfIssuesEnabled", JReactNativeFeatureFlagsCxxInterop::perfIssuesEnabled), diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h index 2e92ce6725d8..4bbd8bb84acf 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h @@ -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<<1cf1c6d1d2a98a495db315e0e7edcc32>> + * @generated SignedSource<<35b3b4933e2c337c4d924c2cc545eac6>> */ /** @@ -207,6 +207,9 @@ class JReactNativeFeatureFlagsCxxInterop static bool overrideBySynchronousMountPropsAtMountingAndroid( facebook::jni::alias_ref); + static bool passScrollToSwipeRefreshChild( + facebook::jni::alias_ref); + static bool perfIssuesEnabled( facebook::jni::alias_ref); diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp index 1ac8889482e2..6bc5e0487bb1 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp @@ -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<> + * @generated SignedSource<> */ /** @@ -262,6 +262,10 @@ bool ReactNativeFeatureFlags::overrideBySynchronousMountPropsAtMountingAndroid() return getAccessor().overrideBySynchronousMountPropsAtMountingAndroid(); } +bool ReactNativeFeatureFlags::passScrollToSwipeRefreshChild() { + return getAccessor().passScrollToSwipeRefreshChild(); +} + bool ReactNativeFeatureFlags::perfIssuesEnabled() { return getAccessor().perfIssuesEnabled(); } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h index b61f3b259d85..770bfe78c90a 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h @@ -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<<0741bbc608bc7d7fdd242d33197057b7>> + * @generated SignedSource<<94420e56d4980f03465c949a42cb6062>> */ /** @@ -334,6 +334,11 @@ class ReactNativeFeatureFlags { */ RN_EXPORT static bool overrideBySynchronousMountPropsAtMountingAndroid(); + /** + * When enabled, ReactSwipeRefreshLayout will forward ACTION_SCROLL events to its child for proper handling. + */ + RN_EXPORT static bool passScrollToSwipeRefreshChild(); + /** * Enable reporting Performance Issues (`detail.devtools.performanceIssue`). Displayed in the V2 Performance Monitor and the "Performance Issues" sub-panel in DevTools. */ diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp index d967c9511d36..6bba61432639 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp @@ -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<> + * @generated SignedSource<> */ /** @@ -1091,6 +1091,24 @@ bool ReactNativeFeatureFlagsAccessor::overrideBySynchronousMountPropsAtMountingA return flagValue.value(); } +bool ReactNativeFeatureFlagsAccessor::passScrollToSwipeRefreshChild() { + auto flagValue = passScrollToSwipeRefreshChild_.load(); + + if (!flagValue.has_value()) { + // This block is not exclusive but it is not necessary. + // If multiple threads try to initialize the feature flag, we would only + // be accessing the provider multiple times but the end state of this + // instance and the returned flag value would be the same. + + markFlagAsAccessed(59, "passScrollToSwipeRefreshChild"); + + flagValue = currentProvider_->passScrollToSwipeRefreshChild(); + passScrollToSwipeRefreshChild_ = flagValue; + } + + return flagValue.value(); +} + bool ReactNativeFeatureFlagsAccessor::perfIssuesEnabled() { auto flagValue = perfIssuesEnabled_.load(); @@ -1100,7 +1118,7 @@ bool ReactNativeFeatureFlagsAccessor::perfIssuesEnabled() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(59, "perfIssuesEnabled"); + markFlagAsAccessed(60, "perfIssuesEnabled"); flagValue = currentProvider_->perfIssuesEnabled(); perfIssuesEnabled_ = flagValue; @@ -1118,7 +1136,7 @@ bool ReactNativeFeatureFlagsAccessor::perfMonitorV2Enabled() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(60, "perfMonitorV2Enabled"); + markFlagAsAccessed(61, "perfMonitorV2Enabled"); flagValue = currentProvider_->perfMonitorV2Enabled(); perfMonitorV2Enabled_ = flagValue; @@ -1136,7 +1154,7 @@ double ReactNativeFeatureFlagsAccessor::preparedTextCacheSize() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(61, "preparedTextCacheSize"); + markFlagAsAccessed(62, "preparedTextCacheSize"); flagValue = currentProvider_->preparedTextCacheSize(); preparedTextCacheSize_ = flagValue; @@ -1154,7 +1172,7 @@ bool ReactNativeFeatureFlagsAccessor::preventShadowTreeCommitExhaustion() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(62, "preventShadowTreeCommitExhaustion"); + markFlagAsAccessed(63, "preventShadowTreeCommitExhaustion"); flagValue = currentProvider_->preventShadowTreeCommitExhaustion(); preventShadowTreeCommitExhaustion_ = flagValue; @@ -1172,7 +1190,7 @@ bool ReactNativeFeatureFlagsAccessor::shouldPressibilityUseW3CPointerEventsForHo // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(63, "shouldPressibilityUseW3CPointerEventsForHover"); + markFlagAsAccessed(64, "shouldPressibilityUseW3CPointerEventsForHover"); flagValue = currentProvider_->shouldPressibilityUseW3CPointerEventsForHover(); shouldPressibilityUseW3CPointerEventsForHover_ = flagValue; @@ -1190,7 +1208,7 @@ bool ReactNativeFeatureFlagsAccessor::shouldTriggerResponderTransferOnScrollAndr // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(64, "shouldTriggerResponderTransferOnScrollAndroid"); + markFlagAsAccessed(65, "shouldTriggerResponderTransferOnScrollAndroid"); flagValue = currentProvider_->shouldTriggerResponderTransferOnScrollAndroid(); shouldTriggerResponderTransferOnScrollAndroid_ = flagValue; @@ -1208,7 +1226,7 @@ bool ReactNativeFeatureFlagsAccessor::skipActivityIdentityAssertionOnHostPause() // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(65, "skipActivityIdentityAssertionOnHostPause"); + markFlagAsAccessed(66, "skipActivityIdentityAssertionOnHostPause"); flagValue = currentProvider_->skipActivityIdentityAssertionOnHostPause(); skipActivityIdentityAssertionOnHostPause_ = flagValue; @@ -1226,7 +1244,7 @@ bool ReactNativeFeatureFlagsAccessor::traceTurboModulePromiseRejectionsOnAndroid // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(66, "traceTurboModulePromiseRejectionsOnAndroid"); + markFlagAsAccessed(67, "traceTurboModulePromiseRejectionsOnAndroid"); flagValue = currentProvider_->traceTurboModulePromiseRejectionsOnAndroid(); traceTurboModulePromiseRejectionsOnAndroid_ = flagValue; @@ -1244,7 +1262,7 @@ bool ReactNativeFeatureFlagsAccessor::updateRuntimeShadowNodeReferencesOnCommit( // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(67, "updateRuntimeShadowNodeReferencesOnCommit"); + markFlagAsAccessed(68, "updateRuntimeShadowNodeReferencesOnCommit"); flagValue = currentProvider_->updateRuntimeShadowNodeReferencesOnCommit(); updateRuntimeShadowNodeReferencesOnCommit_ = flagValue; @@ -1262,7 +1280,7 @@ bool ReactNativeFeatureFlagsAccessor::useAlwaysAvailableJSErrorHandling() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(68, "useAlwaysAvailableJSErrorHandling"); + markFlagAsAccessed(69, "useAlwaysAvailableJSErrorHandling"); flagValue = currentProvider_->useAlwaysAvailableJSErrorHandling(); useAlwaysAvailableJSErrorHandling_ = flagValue; @@ -1280,7 +1298,7 @@ bool ReactNativeFeatureFlagsAccessor::useFabricInterop() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(69, "useFabricInterop"); + markFlagAsAccessed(70, "useFabricInterop"); flagValue = currentProvider_->useFabricInterop(); useFabricInterop_ = flagValue; @@ -1298,7 +1316,7 @@ bool ReactNativeFeatureFlagsAccessor::useNativeViewConfigsInBridgelessMode() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(70, "useNativeViewConfigsInBridgelessMode"); + markFlagAsAccessed(71, "useNativeViewConfigsInBridgelessMode"); flagValue = currentProvider_->useNativeViewConfigsInBridgelessMode(); useNativeViewConfigsInBridgelessMode_ = flagValue; @@ -1316,7 +1334,7 @@ bool ReactNativeFeatureFlagsAccessor::useShadowNodeStateOnClone() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(71, "useShadowNodeStateOnClone"); + markFlagAsAccessed(72, "useShadowNodeStateOnClone"); flagValue = currentProvider_->useShadowNodeStateOnClone(); useShadowNodeStateOnClone_ = flagValue; @@ -1334,7 +1352,7 @@ bool ReactNativeFeatureFlagsAccessor::useSharedAnimatedBackend() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(72, "useSharedAnimatedBackend"); + markFlagAsAccessed(73, "useSharedAnimatedBackend"); flagValue = currentProvider_->useSharedAnimatedBackend(); useSharedAnimatedBackend_ = flagValue; @@ -1352,7 +1370,7 @@ bool ReactNativeFeatureFlagsAccessor::useTraitHiddenOnAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(73, "useTraitHiddenOnAndroid"); + markFlagAsAccessed(74, "useTraitHiddenOnAndroid"); flagValue = currentProvider_->useTraitHiddenOnAndroid(); useTraitHiddenOnAndroid_ = flagValue; @@ -1370,7 +1388,7 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModuleInterop() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(74, "useTurboModuleInterop"); + markFlagAsAccessed(75, "useTurboModuleInterop"); flagValue = currentProvider_->useTurboModuleInterop(); useTurboModuleInterop_ = flagValue; @@ -1388,7 +1406,7 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModules() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(75, "useTurboModules"); + markFlagAsAccessed(76, "useTurboModules"); flagValue = currentProvider_->useTurboModules(); useTurboModules_ = flagValue; @@ -1406,7 +1424,7 @@ double ReactNativeFeatureFlagsAccessor::viewCullingOutsetRatio() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(76, "viewCullingOutsetRatio"); + markFlagAsAccessed(77, "viewCullingOutsetRatio"); flagValue = currentProvider_->viewCullingOutsetRatio(); viewCullingOutsetRatio_ = flagValue; @@ -1424,7 +1442,7 @@ double ReactNativeFeatureFlagsAccessor::virtualViewPrerenderRatio() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(77, "virtualViewPrerenderRatio"); + markFlagAsAccessed(78, "virtualViewPrerenderRatio"); flagValue = currentProvider_->virtualViewPrerenderRatio(); virtualViewPrerenderRatio_ = flagValue; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h index 277146234d1f..34e28c434ca3 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h @@ -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<<0a3237b70685886405ea47d3a8c83280>> + * @generated SignedSource<> */ /** @@ -91,6 +91,7 @@ class ReactNativeFeatureFlagsAccessor { bool fuseboxNetworkInspectionEnabled(); bool hideOffscreenVirtualViewsOnIOS(); bool overrideBySynchronousMountPropsAtMountingAndroid(); + bool passScrollToSwipeRefreshChild(); bool perfIssuesEnabled(); bool perfMonitorV2Enabled(); double preparedTextCacheSize(); @@ -121,7 +122,7 @@ class ReactNativeFeatureFlagsAccessor { std::unique_ptr currentProvider_; bool wasOverridden_; - std::array, 78> accessedFeatureFlags_; + std::array, 79> accessedFeatureFlags_; std::atomic> commonTestFlag_; std::atomic> cdpInteractionMetricsEnabled_; @@ -182,6 +183,7 @@ class ReactNativeFeatureFlagsAccessor { std::atomic> fuseboxNetworkInspectionEnabled_; std::atomic> hideOffscreenVirtualViewsOnIOS_; std::atomic> overrideBySynchronousMountPropsAtMountingAndroid_; + std::atomic> passScrollToSwipeRefreshChild_; std::atomic> perfIssuesEnabled_; std::atomic> perfMonitorV2Enabled_; std::atomic> preparedTextCacheSize_; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h index a657551f6227..47d3ec76be76 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h @@ -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<<4d8ad520eb6442e7d68b739889cc57ce>> + * @generated SignedSource<<9ac153a95e55474403a3d4c47ff7c484>> */ /** @@ -263,6 +263,10 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider { return false; } + bool passScrollToSwipeRefreshChild() override { + return false; + } + bool perfIssuesEnabled() override { return false; } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h index b811f17a2b24..ca43073b077a 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h @@ -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<<5d268976bbf89c50ed0fac1913925803>> + * @generated SignedSource<<0984597008f53b23ba0f5ee0fcad0996>> */ /** @@ -576,6 +576,15 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef return ReactNativeFeatureFlagsDefaults::overrideBySynchronousMountPropsAtMountingAndroid(); } + bool passScrollToSwipeRefreshChild() override { + auto value = values_["passScrollToSwipeRefreshChild"]; + if (!value.isNull()) { + return value.getBool(); + } + + return ReactNativeFeatureFlagsDefaults::passScrollToSwipeRefreshChild(); + } + bool perfIssuesEnabled() override { auto value = values_["perfIssuesEnabled"]; if (!value.isNull()) { diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h index 41c360d3e10c..66ae29caa4f4 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h @@ -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<> + * @generated SignedSource<> */ /** @@ -84,6 +84,7 @@ class ReactNativeFeatureFlagsProvider { virtual bool fuseboxNetworkInspectionEnabled() = 0; virtual bool hideOffscreenVirtualViewsOnIOS() = 0; virtual bool overrideBySynchronousMountPropsAtMountingAndroid() = 0; + virtual bool passScrollToSwipeRefreshChild() = 0; virtual bool perfIssuesEnabled() = 0; virtual bool perfMonitorV2Enabled() = 0; virtual double preparedTextCacheSize() = 0; diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp index 89413bab54e2..ca7564c39183 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp @@ -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<<3d7f32a7be7944a149b788ffe38f89d1>> + * @generated SignedSource<<33102350b43b6c51283a5480dcac28ca>> */ /** @@ -339,6 +339,11 @@ bool NativeReactNativeFeatureFlags::overrideBySynchronousMountPropsAtMountingAnd return ReactNativeFeatureFlags::overrideBySynchronousMountPropsAtMountingAndroid(); } +bool NativeReactNativeFeatureFlags::passScrollToSwipeRefreshChild( + jsi::Runtime& /*runtime*/) { + return ReactNativeFeatureFlags::passScrollToSwipeRefreshChild(); +} + bool NativeReactNativeFeatureFlags::perfIssuesEnabled( jsi::Runtime& /*runtime*/) { return ReactNativeFeatureFlags::perfIssuesEnabled(); diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h index e75ea83757b6..a4df97695b13 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h +++ b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h @@ -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<<0fdc53b52abd242240617cabe47477b3>> + * @generated SignedSource<<773acdacdd2ef30df0ebda67d1c455ab>> */ /** @@ -154,6 +154,8 @@ class NativeReactNativeFeatureFlags bool overrideBySynchronousMountPropsAtMountingAndroid(jsi::Runtime& runtime); + bool passScrollToSwipeRefreshChild(jsi::Runtime& runtime); + bool perfIssuesEnabled(jsi::Runtime& runtime); bool perfMonitorV2Enabled(jsi::Runtime& runtime); diff --git a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js index eadb8d2a0adc..163ffaf05d58 100644 --- a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js +++ b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js @@ -679,6 +679,17 @@ const definitions: FeatureFlagDefinitions = { }, ossReleaseStage: 'none', }, + passScrollToSwipeRefreshChild: { + defaultValue: false, + metadata: { + dateAdded: '2026-01-13', + description: + 'When enabled, ReactSwipeRefreshLayout will forward ACTION_SCROLL events to its child for proper handling.', + expectedReleaseValue: true, + purpose: 'experimentation', + }, + ossReleaseStage: 'none', + }, perfIssuesEnabled: { defaultValue: false, metadata: { diff --git a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js index 5ef19ea424b7..4c8b5db5f3e7 100644 --- a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js +++ b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js @@ -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<> + * @generated SignedSource<> * @flow strict * @noformat */ @@ -107,6 +107,7 @@ export type ReactNativeFeatureFlags = $ReadOnly<{ fuseboxNetworkInspectionEnabled: Getter, hideOffscreenVirtualViewsOnIOS: Getter, overrideBySynchronousMountPropsAtMountingAndroid: Getter, + passScrollToSwipeRefreshChild: Getter, perfIssuesEnabled: Getter, perfMonitorV2Enabled: Getter, preparedTextCacheSize: Getter, @@ -433,6 +434,10 @@ export const hideOffscreenVirtualViewsOnIOS: Getter = createNativeFlagG * Override props at mounting with synchronously mounted (i.e. direct manipulation) props from Native Animated. */ export const overrideBySynchronousMountPropsAtMountingAndroid: Getter = createNativeFlagGetter('overrideBySynchronousMountPropsAtMountingAndroid', false); +/** + * When enabled, ReactSwipeRefreshLayout will forward ACTION_SCROLL events to its child for proper handling. + */ +export const passScrollToSwipeRefreshChild: Getter = createNativeFlagGetter('passScrollToSwipeRefreshChild', false); /** * Enable reporting Performance Issues (`detail.devtools.performanceIssue`). Displayed in the V2 Performance Monitor and the "Performance Issues" sub-panel in DevTools. */ diff --git a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js index b38207f4a883..e28b4b807b2b 100644 --- a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js +++ b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js @@ -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<<3540142c0e0e90aaffeb7ea02812e7b1>> + * @generated SignedSource<> * @flow strict * @noformat */ @@ -84,6 +84,7 @@ export interface Spec extends TurboModule { +fuseboxNetworkInspectionEnabled?: () => boolean; +hideOffscreenVirtualViewsOnIOS?: () => boolean; +overrideBySynchronousMountPropsAtMountingAndroid?: () => boolean; + +passScrollToSwipeRefreshChild?: () => boolean; +perfIssuesEnabled?: () => boolean; +perfMonitorV2Enabled?: () => boolean; +preparedTextCacheSize?: () => number;