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 66c05902b4ee..40be92f74290 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<> + * @generated SignedSource<<89627a52825452c8f6c0dd3725f61175>> */ /** @@ -546,6 +546,12 @@ public object ReactNativeFeatureFlags { @JvmStatic public fun useTraitHiddenOnAndroid(): Boolean = accessor.useTraitHiddenOnAndroid() + /** + * Use Trait::hidden slice-skip on iOS. When false, Hidden subtrees stay mounted and hide via UIView.hidden = YES (the path in UIView+ComponentViewProtocol since 2018) instead of REMOVE + DELETE. + */ + @JvmStatic + public fun useTraitHiddenOnIOS(): Boolean = accessor.useTraitHiddenOnIOS() + /** * In Bridgeless mode, should legacy NativeModules use the TurboModule system? */ 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 f5c895d05ab6..8f5d574f3f3c 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<<4f9f5c1c46217ed6802abd5f786aac19>> + * @generated SignedSource<> */ /** @@ -106,6 +106,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces private var useNestedScrollViewAndroidCache: Boolean? = null private var useSharedAnimatedBackendCache: Boolean? = null private var useTraitHiddenOnAndroidCache: Boolean? = null + private var useTraitHiddenOnIOSCache: Boolean? = null private var useTurboModuleInteropCache: Boolean? = null private var useTurboModulesCache: Boolean? = null private var useUnorderedMapInDifferentiatorCache: Boolean? = null @@ -887,6 +888,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces return cached } + override fun useTraitHiddenOnIOS(): Boolean { + var cached = useTraitHiddenOnIOSCache + if (cached == null) { + cached = ReactNativeFeatureFlagsCxxInterop.useTraitHiddenOnIOS() + useTraitHiddenOnIOSCache = cached + } + return cached + } + override fun useTurboModuleInterop(): Boolean { var cached = useTurboModuleInteropCache 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 028421437717..1c45536b619d 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<<8916e9f4a938a69ff175c806db9835d4>> + * @generated SignedSource<<9a216ce4f720f39e338ba9eb40e3503d>> */ /** @@ -200,6 +200,8 @@ public object ReactNativeFeatureFlagsCxxInterop { @DoNotStrip @JvmStatic public external fun useTraitHiddenOnAndroid(): Boolean + @DoNotStrip @JvmStatic public external fun useTraitHiddenOnIOS(): Boolean + @DoNotStrip @JvmStatic public external fun useTurboModuleInterop(): Boolean @DoNotStrip @JvmStatic public external fun useTurboModules(): 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 a9e1a1e0cb74..669e3d991168 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<> */ /** @@ -195,6 +195,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi override fun useTraitHiddenOnAndroid(): Boolean = false + override fun useTraitHiddenOnIOS(): Boolean = true + override fun useTurboModuleInterop(): Boolean = false override fun useTurboModules(): 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 698723ec7ef8..eda86f609801 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<> + * @generated SignedSource<> */ /** @@ -110,6 +110,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc private var useNestedScrollViewAndroidCache: Boolean? = null private var useSharedAnimatedBackendCache: Boolean? = null private var useTraitHiddenOnAndroidCache: Boolean? = null + private var useTraitHiddenOnIOSCache: Boolean? = null private var useTurboModuleInteropCache: Boolean? = null private var useTurboModulesCache: Boolean? = null private var useUnorderedMapInDifferentiatorCache: Boolean? = null @@ -977,6 +978,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc return cached } + override fun useTraitHiddenOnIOS(): Boolean { + var cached = useTraitHiddenOnIOSCache + if (cached == null) { + cached = currentProvider.useTraitHiddenOnIOS() + accessedFeatureFlags.add("useTraitHiddenOnIOS") + useTraitHiddenOnIOSCache = cached + } + return cached + } + override fun useTurboModuleInterop(): Boolean { var cached = useTurboModuleInteropCache 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 3bcc2f82ed22..f3891df7765d 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<<536a5156deea17740dd24782bf79feb4>> + * @generated SignedSource<<4ba890681ce63a02ddcd443223515323>> */ /** @@ -195,6 +195,8 @@ public interface ReactNativeFeatureFlagsProvider { @DoNotStrip public fun useTraitHiddenOnAndroid(): Boolean + @DoNotStrip public fun useTraitHiddenOnIOS(): Boolean + @DoNotStrip public fun useTurboModuleInterop(): Boolean @DoNotStrip public fun useTurboModules(): Boolean 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 67a3144ccd0f..7a02891b1a7f 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<> + * @generated SignedSource<<592d2f00a3b2ff81533cf74f27068819>> */ /** @@ -555,6 +555,12 @@ class ReactNativeFeatureFlagsJavaProvider return method(javaProvider_); } + bool useTraitHiddenOnIOS() override { + static const auto method = + getReactNativeFeatureFlagsProviderJavaClass()->getMethod("useTraitHiddenOnIOS"); + return method(javaProvider_); + } + bool useTurboModuleInterop() override { static const auto method = getReactNativeFeatureFlagsProviderJavaClass()->getMethod("useTurboModuleInterop"); @@ -1025,6 +1031,11 @@ bool JReactNativeFeatureFlagsCxxInterop::useTraitHiddenOnAndroid( return ReactNativeFeatureFlags::useTraitHiddenOnAndroid(); } +bool JReactNativeFeatureFlagsCxxInterop::useTraitHiddenOnIOS( + facebook::jni::alias_ref /*unused*/) { + return ReactNativeFeatureFlags::useTraitHiddenOnIOS(); +} + bool JReactNativeFeatureFlagsCxxInterop::useTurboModuleInterop( facebook::jni::alias_ref /*unused*/) { return ReactNativeFeatureFlags::useTurboModuleInterop(); @@ -1344,6 +1355,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() { makeNativeMethod( "useTraitHiddenOnAndroid", JReactNativeFeatureFlagsCxxInterop::useTraitHiddenOnAndroid), + makeNativeMethod( + "useTraitHiddenOnIOS", + JReactNativeFeatureFlagsCxxInterop::useTraitHiddenOnIOS), makeNativeMethod( "useTurboModuleInterop", JReactNativeFeatureFlagsCxxInterop::useTurboModuleInterop), 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 aff905ed7d73..e83d035bf369 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<<4be32bad403baeca1a28f19ad181c42c>> + * @generated SignedSource<<23eef56b233a38a30476600370621264>> */ /** @@ -288,6 +288,9 @@ class JReactNativeFeatureFlagsCxxInterop static bool useTraitHiddenOnAndroid( facebook::jni::alias_ref); + static bool useTraitHiddenOnIOS( + facebook::jni::alias_ref); + static bool useTurboModuleInterop( 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 c2a07d570235..c70d02d9b36e 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<<05ffdb0ed4ce2b2f2eed475127ff170a>> */ /** @@ -370,6 +370,10 @@ bool ReactNativeFeatureFlags::useTraitHiddenOnAndroid() { return getAccessor().useTraitHiddenOnAndroid(); } +bool ReactNativeFeatureFlags::useTraitHiddenOnIOS() { + return getAccessor().useTraitHiddenOnIOS(); +} + bool ReactNativeFeatureFlags::useTurboModuleInterop() { return getAccessor().useTurboModuleInterop(); } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h index 423e77658798..ee0396b00e57 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<<8e9b09843bf4a0312b254559a975f612>> + * @generated SignedSource<<21d1871f9323c4adc17695dcc141a60d>> */ /** @@ -469,6 +469,11 @@ class ReactNativeFeatureFlags { */ RN_EXPORT static bool useTraitHiddenOnAndroid(); + /** + * Use Trait::hidden slice-skip on iOS. When false, Hidden subtrees stay mounted and hide via UIView.hidden = YES (the path in UIView+ComponentViewProtocol since 2018) instead of REMOVE + DELETE. + */ + RN_EXPORT static bool useTraitHiddenOnIOS(); + /** * In Bridgeless mode, should legacy NativeModules use the TurboModule system? */ diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp index 948a6241e535..00f330985e1a 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<<6c5ecdf9466d74d3a4c66a7bacfc7043>> */ /** @@ -1577,6 +1577,24 @@ bool ReactNativeFeatureFlagsAccessor::useTraitHiddenOnAndroid() { return flagValue.value(); } +bool ReactNativeFeatureFlagsAccessor::useTraitHiddenOnIOS() { + auto flagValue = useTraitHiddenOnIOS_.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(86, "useTraitHiddenOnIOS"); + + flagValue = currentProvider_->useTraitHiddenOnIOS(); + useTraitHiddenOnIOS_ = flagValue; + } + + return flagValue.value(); +} + bool ReactNativeFeatureFlagsAccessor::useTurboModuleInterop() { auto flagValue = useTurboModuleInterop_.load(); @@ -1586,7 +1604,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(86, "useTurboModuleInterop"); + markFlagAsAccessed(87, "useTurboModuleInterop"); flagValue = currentProvider_->useTurboModuleInterop(); useTurboModuleInterop_ = flagValue; @@ -1604,7 +1622,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(87, "useTurboModules"); + markFlagAsAccessed(88, "useTurboModules"); flagValue = currentProvider_->useTurboModules(); useTurboModules_ = flagValue; @@ -1622,7 +1640,7 @@ bool ReactNativeFeatureFlagsAccessor::useUnorderedMapInDifferentiator() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(88, "useUnorderedMapInDifferentiator"); + markFlagAsAccessed(89, "useUnorderedMapInDifferentiator"); flagValue = currentProvider_->useUnorderedMapInDifferentiator(); useUnorderedMapInDifferentiator_ = flagValue; @@ -1640,7 +1658,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(89, "viewCullingOutsetRatio"); + markFlagAsAccessed(90, "viewCullingOutsetRatio"); flagValue = currentProvider_->viewCullingOutsetRatio(); viewCullingOutsetRatio_ = flagValue; @@ -1658,7 +1676,7 @@ bool ReactNativeFeatureFlagsAccessor::viewTransitionEnabled() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(90, "viewTransitionEnabled"); + markFlagAsAccessed(91, "viewTransitionEnabled"); flagValue = currentProvider_->viewTransitionEnabled(); viewTransitionEnabled_ = flagValue; @@ -1676,7 +1694,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(91, "virtualViewPrerenderRatio"); + markFlagAsAccessed(92, "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 cf2ca1f872e8..4c1915c0ba99 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<<9334675799ea378311b8c675ed419b1d>> + * @generated SignedSource<> */ /** @@ -118,6 +118,7 @@ class ReactNativeFeatureFlagsAccessor { bool useNestedScrollViewAndroid(); bool useSharedAnimatedBackend(); bool useTraitHiddenOnAndroid(); + bool useTraitHiddenOnIOS(); bool useTurboModuleInterop(); bool useTurboModules(); bool useUnorderedMapInDifferentiator(); @@ -135,7 +136,7 @@ class ReactNativeFeatureFlagsAccessor { std::unique_ptr currentProvider_; bool wasOverridden_; - std::array, 92> accessedFeatureFlags_; + std::array, 93> accessedFeatureFlags_; std::atomic> commonTestFlag_; std::atomic> cdpInteractionMetricsEnabled_; @@ -223,6 +224,7 @@ class ReactNativeFeatureFlagsAccessor { std::atomic> useNestedScrollViewAndroid_; std::atomic> useSharedAnimatedBackend_; std::atomic> useTraitHiddenOnAndroid_; + std::atomic> useTraitHiddenOnIOS_; std::atomic> useTurboModuleInterop_; std::atomic> useTurboModules_; std::atomic> useUnorderedMapInDifferentiator_; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h index bfbe407374af..077d8211f0d3 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<<4a2fd61cbcdb28042f09ccb03c970674>> + * @generated SignedSource<<7861725f5c1354b87caba6b7c0e406a7>> */ /** @@ -371,6 +371,10 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider { return false; } + bool useTraitHiddenOnIOS() override { + return true; + } + bool useTurboModuleInterop() override { return false; } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h index 1f9a7c1307f6..ef3e53947119 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<<7853bedb8a11b20a633eb6579257b4bc>> + * @generated SignedSource<> */ /** @@ -819,6 +819,15 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef return ReactNativeFeatureFlagsDefaults::useTraitHiddenOnAndroid(); } + bool useTraitHiddenOnIOS() override { + auto value = values_["useTraitHiddenOnIOS"]; + if (!value.isNull()) { + return value.getBool(); + } + + return ReactNativeFeatureFlagsDefaults::useTraitHiddenOnIOS(); + } + bool useTurboModuleInterop() override { auto value = values_["useTurboModuleInterop"]; if (!value.isNull()) { diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h index 2acdef336041..2ca2bb8e21b2 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<> */ /** @@ -111,6 +111,7 @@ class ReactNativeFeatureFlagsProvider { virtual bool useNestedScrollViewAndroid() = 0; virtual bool useSharedAnimatedBackend() = 0; virtual bool useTraitHiddenOnAndroid() = 0; + virtual bool useTraitHiddenOnIOS() = 0; virtual bool useTurboModuleInterop() = 0; virtual bool useTurboModules() = 0; virtual bool useUnorderedMapInDifferentiator() = 0; diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp index 816c333b1415..6a943b166980 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<> + * @generated SignedSource<> */ /** @@ -474,6 +474,11 @@ bool NativeReactNativeFeatureFlags::useTraitHiddenOnAndroid( return ReactNativeFeatureFlags::useTraitHiddenOnAndroid(); } +bool NativeReactNativeFeatureFlags::useTraitHiddenOnIOS( + jsi::Runtime& /*runtime*/) { + return ReactNativeFeatureFlags::useTraitHiddenOnIOS(); +} + bool NativeReactNativeFeatureFlags::useTurboModuleInterop( jsi::Runtime& /*runtime*/) { return ReactNativeFeatureFlags::useTurboModuleInterop(); diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h index 9097a2a90c49..0b544fccd0f4 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<<3f44b628a681f0d005827f51f4ad885e>> + * @generated SignedSource<<85c7f4be002fb6edd73b11d3876abbd6>> */ /** @@ -208,6 +208,8 @@ class NativeReactNativeFeatureFlags bool useTraitHiddenOnAndroid(jsi::Runtime& runtime); + bool useTraitHiddenOnIOS(jsi::Runtime& runtime); + bool useTurboModuleInterop(jsi::Runtime& runtime); bool useTurboModules(jsi::Runtime& runtime); diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/internal/sliceChildShadowNodeViewPairs.cpp b/packages/react-native/ReactCommon/react/renderer/mounting/internal/sliceChildShadowNodeViewPairs.cpp index 644d4e4d88a4..c1d6c671f47f 100644 --- a/packages/react-native/ReactCommon/react/renderer/mounting/internal/sliceChildShadowNodeViewPairs.cpp +++ b/packages/react-native/ReactCommon/react/renderer/mounting/internal/sliceChildShadowNodeViewPairs.cpp @@ -58,9 +58,20 @@ static void sliceChildShadowNodeViewPairsRecursively( auto& childShadowNode = *sharedChildShadowNode; // T153547836: Disabled on Android because the mounting infrastructure // is not fully ready yet. + // + // On iOS, the Trait::Hidden slice-skip destroys host views via REMOVE + + // DELETE on every `display: none` toggle. Apps that mutate native state + // imperatively via Fabric Commands lose that state silently, because + // Commands have no prop-shape for React to replay onto the new instance. + // `useTraitHiddenOnIOS` lets such apps opt out and fall back to the + // hide-via-`UIView.hidden` path that has been in + // `UIView+ComponentViewProtocol.updateLayoutMetrics:` since 2018 + // (D8460108). Defaults to `true` so existing behaviour is preserved. if ( #ifdef ANDROID ReactNativeFeatureFlags::useTraitHiddenOnAndroid() && +#elif defined(__APPLE__) + ReactNativeFeatureFlags::useTraitHiddenOnIOS() && #endif childShadowNode.getTraits().check(ShadowNodeTraits::Trait::Hidden)) { continue; diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/tests/StackingContextTest.cpp b/packages/react-native/ReactCommon/react/renderer/mounting/tests/StackingContextTest.cpp index 9d1651057864..1dcba402b31d 100644 --- a/packages/react-native/ReactCommon/react/renderer/mounting/tests/StackingContextTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/mounting/tests/StackingContextTest.cpp @@ -21,6 +21,9 @@ #include #include +#include +#include + namespace facebook::react { class StackingContextTest : public ::testing::Test { @@ -975,4 +978,63 @@ TEST_F(StackingContextTest, zIndexAndFlattenedNodes) { #endif }); } + +namespace { + +// Override that flips the iOS slice-skip flag off so that Hidden subtrees +// stay in the mount slice and are hidden via UIView.hidden = YES (the path +// in UIView+ComponentViewProtocol since 2018) instead of being torn down +// via REMOVE + DELETE. +class UseTraitHiddenOnIOSDisabledOverride + : public ReactNativeFeatureFlagsDefaults { + public: + bool useTraitHiddenOnIOS() override { + return false; + } +}; + +} // namespace + +// Companion to the `display: none` assertions above. The default-flag +// case (current iOS behaviour: views removed) is already covered there. +// This test pins the opt-out: with `useTraitHiddenOnIOS = false`, an iOS +// `display: none` subtree must stay in the mount slice — i.e. the view +// tree should match what Android already produces with +// `useTraitHiddenOnAndroid = false` (its default). +// +// Skipped on Android: the `useTraitHiddenOnIOS` gate is unreachable +// there because the surrounding block is `#ifdef ANDROID && useTraitHiddenOnAndroid()`. +TEST_F( + StackingContextTest, + displayNoneRespectsUseTraitHiddenOnIOSOptOut) { +#ifdef ANDROID + GTEST_SKIP() << "useTraitHiddenOnIOS gate is unreachable on Android"; +#else + ReactNativeFeatureFlags::override( + std::make_unique()); + + mutateViewShadowNodeProps_(nodeBB_, [](ViewProps& props) { + auto& yogaStyle = props.yogaStyle; + yogaStyle.setDisplay(yoga::Display::None); + }); + + testViewTree_([](const StubViewTree& viewTree) { + // With useTraitHiddenOnIOS = false, the Hidden subtree stays in the + // mount slice. These expectations mirror the existing `#ifdef ANDROID` + // assertion in `zIndexAndFlattenedNodes` for the same display:none + // mutation. + EXPECT_EQ(viewTree.size(), 8); + + // nodeBB_ (tag 6) forms a stacking context and is present. + EXPECT_EQ(viewTree.getRootStubView().children.size(), 5); + + // The root view subviews are [6, 10, 9, 5, 3]. + EXPECT_EQ(viewTree.getRootStubView().children.at(0)->tag, 6); + EXPECT_EQ(viewTree.getRootStubView().children.at(1)->tag, 10); + EXPECT_EQ(viewTree.getRootStubView().children.at(2)->tag, 9); + EXPECT_EQ(viewTree.getRootStubView().children.at(3)->tag, 5); + EXPECT_EQ(viewTree.getRootStubView().children.at(4)->tag, 3); + }); +#endif +} } // namespace facebook::react diff --git a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js index 8f5856661e98..fe3f9349233e 100644 --- a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js +++ b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js @@ -965,6 +965,17 @@ const definitions: FeatureFlagDefinitions = { }, ossReleaseStage: 'none', }, + useTraitHiddenOnIOS: { + defaultValue: true, + metadata: { + dateAdded: '2026-04-30', + description: + 'Use Trait::hidden slice-skip on iOS. When false, Hidden subtrees stay mounted and hide via UIView.hidden = YES (the path in UIView+ComponentViewProtocol since 2018) instead of REMOVE + DELETE.', + expectedReleaseValue: true, + purpose: 'experimentation', + }, + ossReleaseStage: 'none', + }, useTurboModuleInterop: { defaultValue: false, metadata: { diff --git a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js index ad14fb3138a6..40ed3b1c8852 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<<133adf1c55824bb238caf569f94829ca>> * @flow strict * @noformat */ @@ -133,6 +133,7 @@ export type ReactNativeFeatureFlags = $ReadOnly<{ useNestedScrollViewAndroid: Getter, useSharedAnimatedBackend: Getter, useTraitHiddenOnAndroid: Getter, + useTraitHiddenOnIOS: Getter, useTurboModuleInterop: Getter, useTurboModules: Getter, useUnorderedMapInDifferentiator: Getter, @@ -549,6 +550,10 @@ export const useSharedAnimatedBackend: Getter = createNativeFlagGetter( * Use Trait::hidden on Android */ export const useTraitHiddenOnAndroid: Getter = createNativeFlagGetter('useTraitHiddenOnAndroid', false); +/** + * Use Trait::hidden slice-skip on iOS. When false, Hidden subtrees stay mounted and hide via UIView.hidden = YES (the path in UIView+ComponentViewProtocol since 2018) instead of REMOVE + DELETE. + */ +export const useTraitHiddenOnIOS: Getter = createNativeFlagGetter('useTraitHiddenOnIOS', true); /** * In Bridgeless mode, should legacy NativeModules use the TurboModule system? */ diff --git a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js index c9116e01e890..d7d58c11ba8c 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<<5ab72b9af228bc7e591bc0addaf6150e>> + * @generated SignedSource<<4832bd4ef65113e677d266f15200ec37>> * @flow strict * @noformat */ @@ -111,6 +111,7 @@ export interface Spec extends TurboModule { +useNestedScrollViewAndroid?: () => boolean; +useSharedAnimatedBackend?: () => boolean; +useTraitHiddenOnAndroid?: () => boolean; + +useTraitHiddenOnIOS?: () => boolean; +useTurboModuleInterop?: () => boolean; +useTurboModules?: () => boolean; +useUnorderedMapInDifferentiator?: () => boolean;