diff --git a/packages/react-native/Libraries/Modal/Modal.d.ts b/packages/react-native/Libraries/Modal/Modal.d.ts index 9c9e1ea57228ac..2093d2ba11a341 100644 --- a/packages/react-native/Libraries/Modal/Modal.d.ts +++ b/packages/react-native/Libraries/Modal/Modal.d.ts @@ -63,6 +63,15 @@ export interface ModalPropsIOS { | 'overFullScreen' | undefined; + /** + * The `detents` determines the height for modal, + * based on the detents value. + * It can be a combination of `large`, `medium` or + * any number. + * for eg: ['140', 'medium', 'large'] + */ + detents?: Array | undefined; + /** * The `supportedOrientations` prop allows the modal to be rotated to any of the specified orientations. * On iOS, the modal is still restricted by what's specified in your app's Info.plist's UISupportedInterfaceOrientations field. diff --git a/packages/react-native/Libraries/Modal/Modal.js b/packages/react-native/Libraries/Modal/Modal.js index eb0e1a925413b0..3f99409ff76d39 100644 --- a/packages/react-native/Libraries/Modal/Modal.js +++ b/packages/react-native/Libraries/Modal/Modal.js @@ -121,6 +121,15 @@ export type ModalPropsIOS = { | 'overFullScreen' ), + /** + * The `detents` determines the height for modal, + * based on the detents value. + * It can be a combination of `large`, `medium` or + * any number. + * for eg: ['140', 'medium', 'large'] + */ + detents?: ?$ReadOnlyArray, + /** * The `supportedOrientations` prop allows the modal to be rotated to any of the specified orientations. * On iOS, the modal is still restricted by what's specified in your app's Info.plist's UISupportedInterfaceOrientations field. @@ -208,6 +217,27 @@ function confirmProps(props: ModalProps) { 'Modal requires the onRequestClose prop when used with `allowSwipeDismissal`. This is necessary to prevent state corruption.', ); } + + if ( + Platform.OS === 'ios' && + props.detents && + !props.detents.every(detent => typeof detent === 'string') + ) { + console.warn( + 'Modal detents should be a combination of `large`, `medium` or any number represented as a string.', + ); + } + + if ( + Platform.OS === 'ios' && + (props.detents?.length ?? 0) > 0 && + props.presentationStyle !== 'pageSheet' && + props.presentationStyle !== 'formSheet' + ) { + console.warn( + 'Modal detents are only supported with `pageSheet` or `formSheet` presentation styles.', + ); + } } } @@ -344,6 +374,7 @@ class Modal extends React.Component { supportedOrientations={this.props.supportedOrientations} onOrientationChange={this.props.onOrientationChange} allowSwipeDismissal={this.props.allowSwipeDismissal} + detents={this.props.detents} testID={this.props.testID}> diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/Modal/RCTModalHostViewComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/Modal/RCTModalHostViewComponentView.mm index bbba4cd45ee60e..7f21a6dbb50926 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/Modal/RCTModalHostViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/Modal/RCTModalHostViewComponentView.mm @@ -14,6 +14,7 @@ #import #import #import +#import #import "RCTConversions.h" @@ -107,6 +108,7 @@ @implementation RCTModalHostViewComponentView { BOOL _shouldPresent; BOOL _isPresented; BOOL _modalInPresentation; + NSArray* _detents; } - (instancetype)initWithFrame:(CGRect)frame @@ -156,6 +158,13 @@ - (void)ensurePresentedOnlyIfNeeded self.viewController.presentationController.delegate = self; self.viewController.modalInPresentation = _modalInPresentation; + UISheetPresentationController *sheetPresentationController = + _viewController.sheetPresentationController; + + if (sheetPresentationController) { + sheetPresentationController.detents = _detents; + } + _isPresented = YES; [self presentViewController:self.viewController animated:_shouldAnimatePresentation @@ -243,7 +252,10 @@ - (void)boundsDidChange:(CGRect)newBounds if (_state != nullptr) { auto newState = ModalHostViewState{RCTSizeFromCGSize(newBounds.size)}; - _state->updateState(std::move(newState)); + BOOL enableImmediateUpdateForModalDetents = + ReactNativeFeatureFlags::enableImmediateUpdateForModalDetents(); + + _state->updateState(std::move(newState), enableImmediateUpdateForModalDetents ? EventQueue::UpdateMode::unstable_Immediate : EventQueue::UpdateMode::Asynchronous); } } @@ -282,6 +294,13 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & _modalInPresentation = !newProps.allowSwipeDismissal; self.viewController.modalInPresentation = _modalInPresentation; } + + if (oldViewProps.detents != newProps.detents) { + _detents = [self calculateDetents:newProps.detents]; + if (_viewController.sheetPresentationController) { + _viewController.sheetPresentationController.detents = _detents; + } + } _shouldPresent = newProps.visible; [self ensurePresentedOnlyIfNeeded]; @@ -325,6 +344,41 @@ - (void)presentationControllerDidDismiss:(UIPresentationController *)presentatio } } +#pragma mark - Helpers + +- (NSArray *)calculateDetents:(const std::vector &)detents { + if (@available(iOS 16.0, *)) { + NSMutableArray *detentsArray = [NSMutableArray new]; + + for (const auto &detent : detents) { + NSString *detentString = [NSString stringWithUTF8String:detent.c_str()]; + + if ([detentString isEqualToString:@"medium"]) { + [detentsArray addObject:[UISheetPresentationControllerDetent mediumDetent]]; + } else if ([detentString isEqualToString:@"large"]) { + [detentsArray addObject:[UISheetPresentationControllerDetent largeDetent]]; + } else { + // Try to parse as a number (custom detent) + NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init]; + NSNumber *number = [formatter numberFromString:detentString]; + + if (number != nil) { + float value = [number floatValue]; + [detentsArray addObject:[UISheetPresentationControllerDetent + customDetentWithIdentifier:nil + resolver:^CGFloat(id context) { + return value; + }]]; + } + } + } + + return detentsArray; + } + + return [NSMutableArray new]; +} + @end #ifdef __cplusplus 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 8dca0e86d35ecb..80a2ddf550824a 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<<3ea1ee77358d99334a7c40bed44f2d90>> + * @generated SignedSource<> */ /** @@ -192,6 +192,12 @@ public object ReactNativeFeatureFlags { @JvmStatic public fun enableImagePrefetchingOnUiThreadAndroid(): Boolean = accessor.enableImagePrefetchingOnUiThreadAndroid() + /** + * When enabled, updates to modal detents will be applied immediately instead of being deferred to the next layout pass. + */ + @JvmStatic + public fun enableImmediateUpdateForModalDetents(): Boolean = accessor.enableImmediateUpdateForModalDetents() + /** * Dispatches state updates for content offset changes synchronously on the main thread. */ 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 4b4caaed0bdba3..aee552c800a9ae 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<<007a5a1235a999716b382ffd4ca23158>> + * @generated SignedSource<<2df72858c4ff2678f885dc8c6cffda14>> */ /** @@ -47,6 +47,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces private var enableIOSViewClipToPaddingBoxCache: Boolean? = null private var enableImagePrefetchingAndroidCache: Boolean? = null private var enableImagePrefetchingOnUiThreadAndroidCache: Boolean? = null + private var enableImmediateUpdateForModalDetentsCache: Boolean? = null private var enableImmediateUpdateModeForContentOffsetChangesCache: Boolean? = null private var enableImperativeFocusCache: Boolean? = null private var enableInteropViewManagerClassLookUpOptimizationIOSCache: Boolean? = null @@ -345,6 +346,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces return cached } + override fun enableImmediateUpdateForModalDetents(): Boolean { + var cached = enableImmediateUpdateForModalDetentsCache + if (cached == null) { + cached = ReactNativeFeatureFlagsCxxInterop.enableImmediateUpdateForModalDetents() + enableImmediateUpdateForModalDetentsCache = cached + } + return cached + } + override fun enableImmediateUpdateModeForContentOffsetChanges(): Boolean { var cached = enableImmediateUpdateModeForContentOffsetChangesCache 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 2263fe960e7671..3b981654f15801 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<<8a775646d455cdb6d017fd08aee3e807>> + * @generated SignedSource<<86eaaf389de7ba9e424eb5797b2fcb8a>> */ /** @@ -82,6 +82,8 @@ public object ReactNativeFeatureFlagsCxxInterop { @DoNotStrip @JvmStatic public external fun enableImagePrefetchingOnUiThreadAndroid(): Boolean + @DoNotStrip @JvmStatic public external fun enableImmediateUpdateForModalDetents(): Boolean + @DoNotStrip @JvmStatic public external fun enableImmediateUpdateModeForContentOffsetChanges(): Boolean @DoNotStrip @JvmStatic public external fun enableImperativeFocus(): 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 bd29add65d3559..a694671435fb60 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<> */ /** @@ -77,6 +77,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi override fun enableImagePrefetchingOnUiThreadAndroid(): Boolean = false + override fun enableImmediateUpdateForModalDetents(): Boolean = false + override fun enableImmediateUpdateModeForContentOffsetChanges(): Boolean = false override fun enableImperativeFocus(): 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 5d2b38954e17a0..a82bfbde1b78fd 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<> */ /** @@ -51,6 +51,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc private var enableIOSViewClipToPaddingBoxCache: Boolean? = null private var enableImagePrefetchingAndroidCache: Boolean? = null private var enableImagePrefetchingOnUiThreadAndroidCache: Boolean? = null + private var enableImmediateUpdateForModalDetentsCache: Boolean? = null private var enableImmediateUpdateModeForContentOffsetChangesCache: Boolean? = null private var enableImperativeFocusCache: Boolean? = null private var enableInteropViewManagerClassLookUpOptimizationIOSCache: Boolean? = null @@ -376,6 +377,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc return cached } + override fun enableImmediateUpdateForModalDetents(): Boolean { + var cached = enableImmediateUpdateForModalDetentsCache + if (cached == null) { + cached = currentProvider.enableImmediateUpdateForModalDetents() + accessedFeatureFlags.add("enableImmediateUpdateForModalDetents") + enableImmediateUpdateForModalDetentsCache = cached + } + return cached + } + override fun enableImmediateUpdateModeForContentOffsetChanges(): Boolean { var cached = enableImmediateUpdateModeForContentOffsetChangesCache if (cached == null) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsOverrides_RNOSS_Experimental_Android.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsOverrides_RNOSS_Experimental_Android.kt index 49fa0434b93692..d559d6ff8e0d0d 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsOverrides_RNOSS_Experimental_Android.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsOverrides_RNOSS_Experimental_Android.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<<144aa9984009c2c26681ccbb23129927>> */ /** @@ -25,6 +25,8 @@ public open class ReactNativeFeatureFlagsOverrides_RNOSS_Experimental_Android : override fun enableAccessibilityOrder(): Boolean = true + override fun enableImmediateUpdateForModalDetents(): Boolean = true + override fun enableSwiftUIBasedFilters(): Boolean = true override fun preventShadowTreeCommitExhaustion(): Boolean = true 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 b083f6900f8575..a6c886e523961e 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<<1c4ea60d119996f37742542976fedcc8>> + * @generated SignedSource<> */ /** @@ -77,6 +77,8 @@ public interface ReactNativeFeatureFlagsProvider { @DoNotStrip public fun enableImagePrefetchingOnUiThreadAndroid(): Boolean + @DoNotStrip public fun enableImmediateUpdateForModalDetents(): Boolean + @DoNotStrip public fun enableImmediateUpdateModeForContentOffsetChanges(): Boolean @DoNotStrip public fun enableImperativeFocus(): Boolean diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostManager.kt index 0b928ca820cffd..ee398e83cb367e 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostManager.kt @@ -69,6 +69,11 @@ internal class ReactModalHostManager : // iOS only } + @ReactProp(name = "detent") + override fun setDetent(view: ReactModalHostView, value: String?) { + // iOS only + } + @ReactProp(name = "presentationStyle") override fun setPresentationStyle(view: ReactModalHostView, value: String?): Unit = Unit 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 d2f758eace887e..ec40edb4c16c4d 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<<9d6ca4ab85f104654c347ed949eb669e>> */ /** @@ -201,6 +201,12 @@ class ReactNativeFeatureFlagsJavaProvider return method(javaProvider_); } + bool enableImmediateUpdateForModalDetents() override { + static const auto method = + getReactNativeFeatureFlagsProviderJavaClass()->getMethod("enableImmediateUpdateForModalDetents"); + return method(javaProvider_); + } + bool enableImmediateUpdateModeForContentOffsetChanges() override { static const auto method = getReactNativeFeatureFlagsProviderJavaClass()->getMethod("enableImmediateUpdateModeForContentOffsetChanges"); @@ -664,6 +670,11 @@ bool JReactNativeFeatureFlagsCxxInterop::enableImagePrefetchingOnUiThreadAndroid return ReactNativeFeatureFlags::enableImagePrefetchingOnUiThreadAndroid(); } +bool JReactNativeFeatureFlagsCxxInterop::enableImmediateUpdateForModalDetents( + facebook::jni::alias_ref /*unused*/) { + return ReactNativeFeatureFlags::enableImmediateUpdateForModalDetents(); +} + bool JReactNativeFeatureFlagsCxxInterop::enableImmediateUpdateModeForContentOffsetChanges( facebook::jni::alias_ref /*unused*/) { return ReactNativeFeatureFlags::enableImmediateUpdateModeForContentOffsetChanges(); @@ -1046,6 +1057,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() { makeNativeMethod( "enableImagePrefetchingOnUiThreadAndroid", JReactNativeFeatureFlagsCxxInterop::enableImagePrefetchingOnUiThreadAndroid), + makeNativeMethod( + "enableImmediateUpdateForModalDetents", + JReactNativeFeatureFlagsCxxInterop::enableImmediateUpdateForModalDetents), makeNativeMethod( "enableImmediateUpdateModeForContentOffsetChanges", JReactNativeFeatureFlagsCxxInterop::enableImmediateUpdateModeForContentOffsetChanges), 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 b74c0868250bcd..edbd57eb1e2dad 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<<7679a91daba1f8941b78a4be4baea6ef>> + * @generated SignedSource<> */ /** @@ -111,6 +111,9 @@ class JReactNativeFeatureFlagsCxxInterop static bool enableImagePrefetchingOnUiThreadAndroid( facebook::jni::alias_ref); + static bool enableImmediateUpdateForModalDetents( + facebook::jni::alias_ref); + static bool enableImmediateUpdateModeForContentOffsetChanges( 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 ef3c0fc3e5bb4f..4e7a3e9693e7cb 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<<303e8208cbdf0b9b9041942822c5597d>> + * @generated SignedSource<<64506a81e2fbf0a559c6a24146a1b958>> */ /** @@ -134,6 +134,10 @@ bool ReactNativeFeatureFlags::enableImagePrefetchingOnUiThreadAndroid() { return getAccessor().enableImagePrefetchingOnUiThreadAndroid(); } +bool ReactNativeFeatureFlags::enableImmediateUpdateForModalDetents() { + return getAccessor().enableImmediateUpdateForModalDetents(); +} + bool ReactNativeFeatureFlags::enableImmediateUpdateModeForContentOffsetChanges() { return getAccessor().enableImmediateUpdateModeForContentOffsetChanges(); } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h index 331533669f4c9c..c16ce995ca2c88 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<<07b665ad842faafaf7520051f636d805>> + * @generated SignedSource<<87135fff5b48d1d07a0c86f5a2a99cda>> */ /** @@ -174,6 +174,11 @@ class ReactNativeFeatureFlags { */ RN_EXPORT static bool enableImagePrefetchingOnUiThreadAndroid(); + /** + * When enabled, updates to modal detents will be applied immediately instead of being deferred to the next layout pass. + */ + RN_EXPORT static bool enableImmediateUpdateForModalDetents(); + /** * Dispatches state updates for content offset changes synchronously on the main thread. */ diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp index 426f44a7edd7d0..525bd3ae318465 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<> */ /** @@ -515,6 +515,24 @@ bool ReactNativeFeatureFlagsAccessor::enableImagePrefetchingOnUiThreadAndroid() return flagValue.value(); } +bool ReactNativeFeatureFlagsAccessor::enableImmediateUpdateForModalDetents() { + auto flagValue = enableImmediateUpdateForModalDetents_.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(27, "enableImmediateUpdateForModalDetents"); + + flagValue = currentProvider_->enableImmediateUpdateForModalDetents(); + enableImmediateUpdateForModalDetents_ = flagValue; + } + + return flagValue.value(); +} + bool ReactNativeFeatureFlagsAccessor::enableImmediateUpdateModeForContentOffsetChanges() { auto flagValue = enableImmediateUpdateModeForContentOffsetChanges_.load(); @@ -524,7 +542,7 @@ bool ReactNativeFeatureFlagsAccessor::enableImmediateUpdateModeForContentOffsetC // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(27, "enableImmediateUpdateModeForContentOffsetChanges"); + markFlagAsAccessed(28, "enableImmediateUpdateModeForContentOffsetChanges"); flagValue = currentProvider_->enableImmediateUpdateModeForContentOffsetChanges(); enableImmediateUpdateModeForContentOffsetChanges_ = flagValue; @@ -542,7 +560,7 @@ bool ReactNativeFeatureFlagsAccessor::enableImperativeFocus() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(28, "enableImperativeFocus"); + markFlagAsAccessed(29, "enableImperativeFocus"); flagValue = currentProvider_->enableImperativeFocus(); enableImperativeFocus_ = flagValue; @@ -560,7 +578,7 @@ bool ReactNativeFeatureFlagsAccessor::enableInteropViewManagerClassLookUpOptimiz // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(29, "enableInteropViewManagerClassLookUpOptimizationIOS"); + markFlagAsAccessed(30, "enableInteropViewManagerClassLookUpOptimizationIOS"); flagValue = currentProvider_->enableInteropViewManagerClassLookUpOptimizationIOS(); enableInteropViewManagerClassLookUpOptimizationIOS_ = flagValue; @@ -578,7 +596,7 @@ bool ReactNativeFeatureFlagsAccessor::enableKeyEvents() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(30, "enableKeyEvents"); + markFlagAsAccessed(31, "enableKeyEvents"); flagValue = currentProvider_->enableKeyEvents(); enableKeyEvents_ = flagValue; @@ -596,7 +614,7 @@ bool ReactNativeFeatureFlagsAccessor::enableLayoutAnimationsOnAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(31, "enableLayoutAnimationsOnAndroid"); + markFlagAsAccessed(32, "enableLayoutAnimationsOnAndroid"); flagValue = currentProvider_->enableLayoutAnimationsOnAndroid(); enableLayoutAnimationsOnAndroid_ = flagValue; @@ -614,7 +632,7 @@ bool ReactNativeFeatureFlagsAccessor::enableLayoutAnimationsOnIOS() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(32, "enableLayoutAnimationsOnIOS"); + markFlagAsAccessed(33, "enableLayoutAnimationsOnIOS"); flagValue = currentProvider_->enableLayoutAnimationsOnIOS(); enableLayoutAnimationsOnIOS_ = flagValue; @@ -632,7 +650,7 @@ bool ReactNativeFeatureFlagsAccessor::enableMainQueueCoordinatorOnIOS() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(33, "enableMainQueueCoordinatorOnIOS"); + markFlagAsAccessed(34, "enableMainQueueCoordinatorOnIOS"); flagValue = currentProvider_->enableMainQueueCoordinatorOnIOS(); enableMainQueueCoordinatorOnIOS_ = flagValue; @@ -650,7 +668,7 @@ bool ReactNativeFeatureFlagsAccessor::enableModuleArgumentNSNullConversionIOS() // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(34, "enableModuleArgumentNSNullConversionIOS"); + markFlagAsAccessed(35, "enableModuleArgumentNSNullConversionIOS"); flagValue = currentProvider_->enableModuleArgumentNSNullConversionIOS(); enableModuleArgumentNSNullConversionIOS_ = flagValue; @@ -668,7 +686,7 @@ bool ReactNativeFeatureFlagsAccessor::enableNativeCSSParsing() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(35, "enableNativeCSSParsing"); + markFlagAsAccessed(36, "enableNativeCSSParsing"); flagValue = currentProvider_->enableNativeCSSParsing(); enableNativeCSSParsing_ = flagValue; @@ -686,7 +704,7 @@ bool ReactNativeFeatureFlagsAccessor::enableNetworkEventReporting() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(36, "enableNetworkEventReporting"); + markFlagAsAccessed(37, "enableNetworkEventReporting"); flagValue = currentProvider_->enableNetworkEventReporting(); enableNetworkEventReporting_ = flagValue; @@ -704,7 +722,7 @@ bool ReactNativeFeatureFlagsAccessor::enablePreparedTextLayout() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(37, "enablePreparedTextLayout"); + markFlagAsAccessed(38, "enablePreparedTextLayout"); flagValue = currentProvider_->enablePreparedTextLayout(); enablePreparedTextLayout_ = flagValue; @@ -722,7 +740,7 @@ bool ReactNativeFeatureFlagsAccessor::enablePropsUpdateReconciliationAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(38, "enablePropsUpdateReconciliationAndroid"); + markFlagAsAccessed(39, "enablePropsUpdateReconciliationAndroid"); flagValue = currentProvider_->enablePropsUpdateReconciliationAndroid(); enablePropsUpdateReconciliationAndroid_ = flagValue; @@ -740,7 +758,7 @@ bool ReactNativeFeatureFlagsAccessor::enableResourceTimingAPI() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(39, "enableResourceTimingAPI"); + markFlagAsAccessed(40, "enableResourceTimingAPI"); flagValue = currentProvider_->enableResourceTimingAPI(); enableResourceTimingAPI_ = flagValue; @@ -758,7 +776,7 @@ bool ReactNativeFeatureFlagsAccessor::enableSwiftUIBasedFilters() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(40, "enableSwiftUIBasedFilters"); + markFlagAsAccessed(41, "enableSwiftUIBasedFilters"); flagValue = currentProvider_->enableSwiftUIBasedFilters(); enableSwiftUIBasedFilters_ = flagValue; @@ -776,7 +794,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewCulling() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(41, "enableViewCulling"); + markFlagAsAccessed(42, "enableViewCulling"); flagValue = currentProvider_->enableViewCulling(); enableViewCulling_ = flagValue; @@ -794,7 +812,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecycling() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(42, "enableViewRecycling"); + markFlagAsAccessed(43, "enableViewRecycling"); flagValue = currentProvider_->enableViewRecycling(); enableViewRecycling_ = flagValue; @@ -812,7 +830,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecyclingForImage() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(43, "enableViewRecyclingForImage"); + markFlagAsAccessed(44, "enableViewRecyclingForImage"); flagValue = currentProvider_->enableViewRecyclingForImage(); enableViewRecyclingForImage_ = flagValue; @@ -830,7 +848,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecyclingForScrollView() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(44, "enableViewRecyclingForScrollView"); + markFlagAsAccessed(45, "enableViewRecyclingForScrollView"); flagValue = currentProvider_->enableViewRecyclingForScrollView(); enableViewRecyclingForScrollView_ = flagValue; @@ -848,7 +866,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecyclingForText() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(45, "enableViewRecyclingForText"); + markFlagAsAccessed(46, "enableViewRecyclingForText"); flagValue = currentProvider_->enableViewRecyclingForText(); enableViewRecyclingForText_ = flagValue; @@ -866,7 +884,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecyclingForView() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(46, "enableViewRecyclingForView"); + markFlagAsAccessed(47, "enableViewRecyclingForView"); flagValue = currentProvider_->enableViewRecyclingForView(); enableViewRecyclingForView_ = flagValue; @@ -884,7 +902,7 @@ bool ReactNativeFeatureFlagsAccessor::enableVirtualViewContainerStateExperimenta // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(47, "enableVirtualViewContainerStateExperimental"); + markFlagAsAccessed(48, "enableVirtualViewContainerStateExperimental"); flagValue = currentProvider_->enableVirtualViewContainerStateExperimental(); enableVirtualViewContainerStateExperimental_ = flagValue; @@ -902,7 +920,7 @@ bool ReactNativeFeatureFlagsAccessor::enableVirtualViewDebugFeatures() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(48, "enableVirtualViewDebugFeatures"); + markFlagAsAccessed(49, "enableVirtualViewDebugFeatures"); flagValue = currentProvider_->enableVirtualViewDebugFeatures(); enableVirtualViewDebugFeatures_ = flagValue; @@ -920,7 +938,7 @@ bool ReactNativeFeatureFlagsAccessor::enableVirtualViewRenderState() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(49, "enableVirtualViewRenderState"); + markFlagAsAccessed(50, "enableVirtualViewRenderState"); flagValue = currentProvider_->enableVirtualViewRenderState(); enableVirtualViewRenderState_ = flagValue; @@ -938,7 +956,7 @@ bool ReactNativeFeatureFlagsAccessor::enableVirtualViewWindowFocusDetection() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(50, "enableVirtualViewWindowFocusDetection"); + markFlagAsAccessed(51, "enableVirtualViewWindowFocusDetection"); flagValue = currentProvider_->enableVirtualViewWindowFocusDetection(); enableVirtualViewWindowFocusDetection_ = flagValue; @@ -956,7 +974,7 @@ bool ReactNativeFeatureFlagsAccessor::enableWebPerformanceAPIsByDefault() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(51, "enableWebPerformanceAPIsByDefault"); + markFlagAsAccessed(52, "enableWebPerformanceAPIsByDefault"); flagValue = currentProvider_->enableWebPerformanceAPIsByDefault(); enableWebPerformanceAPIsByDefault_ = flagValue; @@ -974,7 +992,7 @@ bool ReactNativeFeatureFlagsAccessor::fixMappingOfEventPrioritiesBetweenFabricAn // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(52, "fixMappingOfEventPrioritiesBetweenFabricAndReact"); + markFlagAsAccessed(53, "fixMappingOfEventPrioritiesBetweenFabricAndReact"); flagValue = currentProvider_->fixMappingOfEventPrioritiesBetweenFabricAndReact(); fixMappingOfEventPrioritiesBetweenFabricAndReact_ = flagValue; @@ -992,7 +1010,7 @@ bool ReactNativeFeatureFlagsAccessor::fuseboxEnabledRelease() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(53, "fuseboxEnabledRelease"); + markFlagAsAccessed(54, "fuseboxEnabledRelease"); flagValue = currentProvider_->fuseboxEnabledRelease(); fuseboxEnabledRelease_ = flagValue; @@ -1010,7 +1028,7 @@ bool ReactNativeFeatureFlagsAccessor::fuseboxNetworkInspectionEnabled() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(54, "fuseboxNetworkInspectionEnabled"); + markFlagAsAccessed(55, "fuseboxNetworkInspectionEnabled"); flagValue = currentProvider_->fuseboxNetworkInspectionEnabled(); fuseboxNetworkInspectionEnabled_ = flagValue; @@ -1028,7 +1046,7 @@ bool ReactNativeFeatureFlagsAccessor::hideOffscreenVirtualViewsOnIOS() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(55, "hideOffscreenVirtualViewsOnIOS"); + markFlagAsAccessed(56, "hideOffscreenVirtualViewsOnIOS"); flagValue = currentProvider_->hideOffscreenVirtualViewsOnIOS(); hideOffscreenVirtualViewsOnIOS_ = flagValue; @@ -1046,7 +1064,7 @@ bool ReactNativeFeatureFlagsAccessor::overrideBySynchronousMountPropsAtMountingA // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(56, "overrideBySynchronousMountPropsAtMountingAndroid"); + markFlagAsAccessed(57, "overrideBySynchronousMountPropsAtMountingAndroid"); flagValue = currentProvider_->overrideBySynchronousMountPropsAtMountingAndroid(); overrideBySynchronousMountPropsAtMountingAndroid_ = flagValue; @@ -1064,7 +1082,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(57, "perfMonitorV2Enabled"); + markFlagAsAccessed(58, "perfMonitorV2Enabled"); flagValue = currentProvider_->perfMonitorV2Enabled(); perfMonitorV2Enabled_ = flagValue; @@ -1082,7 +1100,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(58, "preparedTextCacheSize"); + markFlagAsAccessed(59, "preparedTextCacheSize"); flagValue = currentProvider_->preparedTextCacheSize(); preparedTextCacheSize_ = flagValue; @@ -1100,7 +1118,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(59, "preventShadowTreeCommitExhaustion"); + markFlagAsAccessed(60, "preventShadowTreeCommitExhaustion"); flagValue = currentProvider_->preventShadowTreeCommitExhaustion(); preventShadowTreeCommitExhaustion_ = flagValue; @@ -1118,7 +1136,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(60, "shouldPressibilityUseW3CPointerEventsForHover"); + markFlagAsAccessed(61, "shouldPressibilityUseW3CPointerEventsForHover"); flagValue = currentProvider_->shouldPressibilityUseW3CPointerEventsForHover(); shouldPressibilityUseW3CPointerEventsForHover_ = flagValue; @@ -1136,7 +1154,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(61, "shouldTriggerResponderTransferOnScrollAndroid"); + markFlagAsAccessed(62, "shouldTriggerResponderTransferOnScrollAndroid"); flagValue = currentProvider_->shouldTriggerResponderTransferOnScrollAndroid(); shouldTriggerResponderTransferOnScrollAndroid_ = flagValue; @@ -1154,7 +1172,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(62, "skipActivityIdentityAssertionOnHostPause"); + markFlagAsAccessed(63, "skipActivityIdentityAssertionOnHostPause"); flagValue = currentProvider_->skipActivityIdentityAssertionOnHostPause(); skipActivityIdentityAssertionOnHostPause_ = flagValue; @@ -1172,7 +1190,7 @@ bool ReactNativeFeatureFlagsAccessor::sweepActiveTouchOnChildNativeGesturesAndro // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(63, "sweepActiveTouchOnChildNativeGesturesAndroid"); + markFlagAsAccessed(64, "sweepActiveTouchOnChildNativeGesturesAndroid"); flagValue = currentProvider_->sweepActiveTouchOnChildNativeGesturesAndroid(); sweepActiveTouchOnChildNativeGesturesAndroid_ = flagValue; @@ -1190,7 +1208,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(64, "traceTurboModulePromiseRejectionsOnAndroid"); + markFlagAsAccessed(65, "traceTurboModulePromiseRejectionsOnAndroid"); flagValue = currentProvider_->traceTurboModulePromiseRejectionsOnAndroid(); traceTurboModulePromiseRejectionsOnAndroid_ = flagValue; @@ -1208,7 +1226,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(65, "updateRuntimeShadowNodeReferencesOnCommit"); + markFlagAsAccessed(66, "updateRuntimeShadowNodeReferencesOnCommit"); flagValue = currentProvider_->updateRuntimeShadowNodeReferencesOnCommit(); updateRuntimeShadowNodeReferencesOnCommit_ = flagValue; @@ -1226,7 +1244,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(66, "useAlwaysAvailableJSErrorHandling"); + markFlagAsAccessed(67, "useAlwaysAvailableJSErrorHandling"); flagValue = currentProvider_->useAlwaysAvailableJSErrorHandling(); useAlwaysAvailableJSErrorHandling_ = flagValue; @@ -1244,7 +1262,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(67, "useFabricInterop"); + markFlagAsAccessed(68, "useFabricInterop"); flagValue = currentProvider_->useFabricInterop(); useFabricInterop_ = flagValue; @@ -1262,7 +1280,7 @@ bool ReactNativeFeatureFlagsAccessor::useNativeEqualsInNativeReadableArrayAndroi // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(68, "useNativeEqualsInNativeReadableArrayAndroid"); + markFlagAsAccessed(69, "useNativeEqualsInNativeReadableArrayAndroid"); flagValue = currentProvider_->useNativeEqualsInNativeReadableArrayAndroid(); useNativeEqualsInNativeReadableArrayAndroid_ = flagValue; @@ -1280,7 +1298,7 @@ bool ReactNativeFeatureFlagsAccessor::useNativeTransformHelperAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(69, "useNativeTransformHelperAndroid"); + markFlagAsAccessed(70, "useNativeTransformHelperAndroid"); flagValue = currentProvider_->useNativeTransformHelperAndroid(); useNativeTransformHelperAndroid_ = 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::useOptimizedEventBatchingOnAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(71, "useOptimizedEventBatchingOnAndroid"); + markFlagAsAccessed(72, "useOptimizedEventBatchingOnAndroid"); flagValue = currentProvider_->useOptimizedEventBatchingOnAndroid(); useOptimizedEventBatchingOnAndroid_ = flagValue; @@ -1334,7 +1352,7 @@ bool ReactNativeFeatureFlagsAccessor::useRawPropsJsiValue() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(72, "useRawPropsJsiValue"); + markFlagAsAccessed(73, "useRawPropsJsiValue"); flagValue = currentProvider_->useRawPropsJsiValue(); useRawPropsJsiValue_ = flagValue; @@ -1352,7 +1370,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(73, "useShadowNodeStateOnClone"); + markFlagAsAccessed(74, "useShadowNodeStateOnClone"); flagValue = currentProvider_->useShadowNodeStateOnClone(); useShadowNodeStateOnClone_ = flagValue; @@ -1370,7 +1388,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(74, "useSharedAnimatedBackend"); + markFlagAsAccessed(75, "useSharedAnimatedBackend"); flagValue = currentProvider_->useSharedAnimatedBackend(); useSharedAnimatedBackend_ = flagValue; @@ -1388,7 +1406,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(75, "useTraitHiddenOnAndroid"); + markFlagAsAccessed(76, "useTraitHiddenOnAndroid"); flagValue = currentProvider_->useTraitHiddenOnAndroid(); useTraitHiddenOnAndroid_ = flagValue; @@ -1406,7 +1424,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(76, "useTurboModuleInterop"); + markFlagAsAccessed(77, "useTurboModuleInterop"); flagValue = currentProvider_->useTurboModuleInterop(); useTurboModuleInterop_ = flagValue; @@ -1424,7 +1442,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(77, "useTurboModules"); + markFlagAsAccessed(78, "useTurboModules"); flagValue = currentProvider_->useTurboModules(); useTurboModules_ = flagValue; @@ -1442,7 +1460,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(78, "viewCullingOutsetRatio"); + markFlagAsAccessed(79, "viewCullingOutsetRatio"); flagValue = currentProvider_->viewCullingOutsetRatio(); viewCullingOutsetRatio_ = flagValue; @@ -1460,7 +1478,7 @@ double ReactNativeFeatureFlagsAccessor::virtualViewHysteresisRatio() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(79, "virtualViewHysteresisRatio"); + markFlagAsAccessed(80, "virtualViewHysteresisRatio"); flagValue = currentProvider_->virtualViewHysteresisRatio(); virtualViewHysteresisRatio_ = flagValue; @@ -1478,7 +1496,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(80, "virtualViewPrerenderRatio"); + markFlagAsAccessed(81, "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 5b374f2479db77..12db375723b584 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<<352f96a011a58b975766f1e6f49aeb48>> + * @generated SignedSource<<0c72ce4aacd56aabf0abc15f366b3ebc>> */ /** @@ -59,6 +59,7 @@ class ReactNativeFeatureFlagsAccessor { bool enableIOSViewClipToPaddingBox(); bool enableImagePrefetchingAndroid(); bool enableImagePrefetchingOnUiThreadAndroid(); + bool enableImmediateUpdateForModalDetents(); bool enableImmediateUpdateModeForContentOffsetChanges(); bool enableImperativeFocus(); bool enableInteropViewManagerClassLookUpOptimizationIOS(); @@ -124,7 +125,7 @@ class ReactNativeFeatureFlagsAccessor { std::unique_ptr currentProvider_; bool wasOverridden_; - std::array, 81> accessedFeatureFlags_; + std::array, 82> accessedFeatureFlags_; std::atomic> commonTestFlag_; std::atomic> cdpInteractionMetricsEnabled_; @@ -153,6 +154,7 @@ class ReactNativeFeatureFlagsAccessor { std::atomic> enableIOSViewClipToPaddingBox_; std::atomic> enableImagePrefetchingAndroid_; std::atomic> enableImagePrefetchingOnUiThreadAndroid_; + std::atomic> enableImmediateUpdateForModalDetents_; std::atomic> enableImmediateUpdateModeForContentOffsetChanges_; std::atomic> enableImperativeFocus_; std::atomic> enableInteropViewManagerClassLookUpOptimizationIOS_; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h index f9ec7cb7b5868e..8991cd626eb240 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<> + * @generated SignedSource<<19895108ea6959905741a69b18f9e000>> */ /** @@ -135,6 +135,10 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider { return false; } + bool enableImmediateUpdateForModalDetents() override { + return false; + } + bool enableImmediateUpdateModeForContentOffsetChanges() override { return false; } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h index 7ebd8d5db5fd09..8a7483b3194853 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<> + * @generated SignedSource<> */ /** @@ -288,6 +288,15 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef return ReactNativeFeatureFlagsDefaults::enableImagePrefetchingOnUiThreadAndroid(); } + bool enableImmediateUpdateForModalDetents() override { + auto value = values_["enableImmediateUpdateForModalDetents"]; + if (!value.isNull()) { + return value.getBool(); + } + + return ReactNativeFeatureFlagsDefaults::enableImmediateUpdateForModalDetents(); + } + bool enableImmediateUpdateModeForContentOffsetChanges() override { auto value = values_["enableImmediateUpdateModeForContentOffsetChanges"]; if (!value.isNull()) { diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsOverridesOSSExperimental.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsOverridesOSSExperimental.h index 68289afb76ef12..e028f71ee50ec4 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsOverridesOSSExperimental.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsOverridesOSSExperimental.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<> */ /** @@ -31,6 +31,10 @@ class ReactNativeFeatureFlagsOverridesOSSExperimental : public ReactNativeFeatur return true; } + bool enableImmediateUpdateForModalDetents() override { + return true; + } + bool enableSwiftUIBasedFilters() override { return true; } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h index d980b650b7508b..50f1bed3f3a90c 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<<6b88f3c9d5688f9d1844f7cd4936a1e4>> + * @generated SignedSource<<943e59ffc1df45cc2b317e5c16c38177>> */ /** @@ -52,6 +52,7 @@ class ReactNativeFeatureFlagsProvider { virtual bool enableIOSViewClipToPaddingBox() = 0; virtual bool enableImagePrefetchingAndroid() = 0; virtual bool enableImagePrefetchingOnUiThreadAndroid() = 0; + virtual bool enableImmediateUpdateForModalDetents() = 0; virtual bool enableImmediateUpdateModeForContentOffsetChanges() = 0; virtual bool enableImperativeFocus() = 0; virtual bool enableInteropViewManagerClassLookUpOptimizationIOS() = 0; diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp index 3657c1eab01450..447ef55702ed0f 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<<90c47eabce1b8648c630ba8d9479c603>> + * @generated SignedSource<<32164d3462e3bc3f36e6652c9d569b5b>> */ /** @@ -179,6 +179,11 @@ bool NativeReactNativeFeatureFlags::enableImagePrefetchingOnUiThreadAndroid( return ReactNativeFeatureFlags::enableImagePrefetchingOnUiThreadAndroid(); } +bool NativeReactNativeFeatureFlags::enableImmediateUpdateForModalDetents( + jsi::Runtime& /*runtime*/) { + return ReactNativeFeatureFlags::enableImmediateUpdateForModalDetents(); +} + bool NativeReactNativeFeatureFlags::enableImmediateUpdateModeForContentOffsetChanges( jsi::Runtime& /*runtime*/) { return ReactNativeFeatureFlags::enableImmediateUpdateModeForContentOffsetChanges(); diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h index 8b0976e5263a42..59a7c25769a8a6 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<> + * @generated SignedSource<> */ /** @@ -90,6 +90,8 @@ class NativeReactNativeFeatureFlags bool enableImagePrefetchingOnUiThreadAndroid(jsi::Runtime& runtime); + bool enableImmediateUpdateForModalDetents(jsi::Runtime& runtime); + bool enableImmediateUpdateModeForContentOffsetChanges(jsi::Runtime& runtime); bool enableImperativeFocus(jsi::Runtime& runtime); diff --git a/packages/react-native/ReactNativeApi.d.ts b/packages/react-native/ReactNativeApi.d.ts index 2ac82083cbfec3..bc41994108e743 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<<4e0d1f9ebc86fb237989b7099bf6f9df>> + * @generated SignedSource<> * * This file was generated by scripts/js-api/build-types/index.js. */ @@ -3167,6 +3167,7 @@ declare type ModalPropsAndroid = { } declare type ModalPropsIOS = { allowSwipeDismissal?: boolean + detents?: ReadonlyArray onDismiss?: () => void onOrientationChange?: DirectEventHandler presentationStyle?: @@ -6035,9 +6036,9 @@ export { MeasureOnSuccessCallback, // 82824e59 Modal, // 78e8a79d ModalBaseProps, // 0c81c9b1 - ModalProps, // 270223fa + ModalProps, // 964d14a7 ModalPropsAndroid, // 515fb173 - ModalPropsIOS, // 4fbcedf6 + ModalPropsIOS, // 569b6e71 ModeChangeEvent, // b889a7ce MouseEvent, // 53ede3db NativeAppEventEmitter, // b4d20c1d diff --git a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js index 71cec3c55a4329..4e9977ea5da3e4 100644 --- a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js +++ b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js @@ -335,6 +335,17 @@ const definitions: FeatureFlagDefinitions = { }, ossReleaseStage: 'none', }, + enableImmediateUpdateForModalDetents: { + defaultValue: false, + metadata: { + dateAdded: '2025-10-30', + description: + 'When enabled, updates to modal detents will be applied immediately instead of being deferred to the next layout pass.', + expectedReleaseValue: true, + purpose: 'experimentation', + }, + ossReleaseStage: 'experimental', + }, enableImmediateUpdateModeForContentOffsetChanges: { defaultValue: false, metadata: { diff --git a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js index 10db4909c195af..2c75b4c121fa84 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 */ @@ -77,6 +77,7 @@ export type ReactNativeFeatureFlags = $ReadOnly<{ enableIOSViewClipToPaddingBox: Getter, enableImagePrefetchingAndroid: Getter, enableImagePrefetchingOnUiThreadAndroid: Getter, + enableImmediateUpdateForModalDetents: Getter, enableImmediateUpdateModeForContentOffsetChanges: Getter, enableImperativeFocus: Getter, enableInteropViewManagerClassLookUpOptimizationIOS: Getter, @@ -320,6 +321,10 @@ export const enableImagePrefetchingAndroid: Getter = createNativeFlagGe * When enabled, Android will initiate image prefetch requested on ImageShadowNode::layout on the UI thread */ export const enableImagePrefetchingOnUiThreadAndroid: Getter = createNativeFlagGetter('enableImagePrefetchingOnUiThreadAndroid', false); +/** + * When enabled, updates to modal detents will be applied immediately instead of being deferred to the next layout pass. + */ +export const enableImmediateUpdateForModalDetents: Getter = createNativeFlagGetter('enableImmediateUpdateForModalDetents', false); /** * Dispatches state updates for content offset changes synchronously on the main thread. */ diff --git a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js index b700761075e652..db481935780fc0 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<> + * @generated SignedSource<<80c5404cf16ee58131844e037ededc18>> * @flow strict * @noformat */ @@ -52,6 +52,7 @@ export interface Spec extends TurboModule { +enableIOSViewClipToPaddingBox?: () => boolean; +enableImagePrefetchingAndroid?: () => boolean; +enableImagePrefetchingOnUiThreadAndroid?: () => boolean; + +enableImmediateUpdateForModalDetents?: () => boolean; +enableImmediateUpdateModeForContentOffsetChanges?: () => boolean; +enableImperativeFocus?: () => boolean; +enableInteropViewManagerClassLookUpOptimizationIOS?: () => boolean; diff --git a/packages/react-native/src/private/specs_DEPRECATED/components/RCTModalHostViewNativeComponent.js b/packages/react-native/src/private/specs_DEPRECATED/components/RCTModalHostViewNativeComponent.js index b5aa18adf207ed..dc0905a5d84a7e 100644 --- a/packages/react-native/src/private/specs_DEPRECATED/components/RCTModalHostViewNativeComponent.js +++ b/packages/react-native/src/private/specs_DEPRECATED/components/RCTModalHostViewNativeComponent.js @@ -42,6 +42,15 @@ type RCTModalHostViewNativeProps = $ReadOnly<{ 'fullScreen', >, + /** + * The `detents` determines the height for modal, + * based on the detents value. + * It can be a combination of `large`, `medium` or + * any number. + * for eg: ['140', 'medium', 'large'] + */ + detents?: ?$ReadOnlyArray, + /** * The `transparent` prop determines whether your modal will fill the * entire view. diff --git a/packages/rn-tester/js/examples/Modal/ModalPresentation.js b/packages/rn-tester/js/examples/Modal/ModalPresentation.js index 5ac8e416fedd99..4bbc1b3fa16b52 100644 --- a/packages/rn-tester/js/examples/Modal/ModalPresentation.js +++ b/packages/rn-tester/js/examples/Modal/ModalPresentation.js @@ -19,7 +19,15 @@ import {RNTesterThemeContext} from '../../components/RNTesterTheme'; import RNTOption from '../../components/RNTOption'; import * as React from 'react'; import {useCallback, useContext, useState} from 'react'; -import {Modal, Platform, StyleSheet, Switch, Text, View} from 'react-native'; +import { + Modal, + Platform, + ScrollView, + StyleSheet, + Switch, + Text, + View, +} from 'react-native'; const animationTypes = ['slide', 'none', 'fade'] as const; const presentationStyles = [ @@ -37,6 +45,7 @@ const supportedOrientations = [ ] as const; const backdropColors = ['red', 'blue', undefined]; +const detents = ['200', '500', 'large']; function ModalPresentation() { const onDismiss = useCallback(() => { @@ -72,6 +81,7 @@ function ModalPresentation() { visible: false, backdropColor: undefined, }); + const [useDetents, setUseDetents] = useState(false); const presentationStyle = props.presentationStyle; const hardwareAccelerated = props.hardwareAccelerated; const statusBarTranslucent = props.statusBarTranslucent; @@ -149,6 +159,13 @@ function ModalPresentation() { } /> + + Use Detents ⚫️ + setUseDetents(enabled)} + /> + Presentation Style ⚫️ @@ -295,9 +312,12 @@ function ModalPresentation() { - + This modal was presented with animationType: ' @@ -314,6 +334,9 @@ function ModalPresentation() { {controls} + + + Footer position: absolute @@ -367,7 +390,6 @@ const styles = StyleSheet.create({ }, modalContainer: { flex: 1, - justifyContent: 'center', padding: 20, }, modalInnerContainer: { @@ -379,6 +401,17 @@ const styles = StyleSheet.create({ fontSize: 12, color: 'red', }, + footer: { + height: 60, + width: '100%', + position: 'absolute', + bottom: 0, + left: 0, + right: 0, + backgroundColor: 'rgba(0, 0, 0, 0.5)', + justifyContent: 'center', + alignItems: 'center', + }, }); export default ({