diff --git a/packages/react-native/Libraries/Components/LayoutConformance/LayoutConformance.d.ts b/packages/react-native/Libraries/Components/LayoutConformance/LayoutConformance.d.ts new file mode 100644 index 00000000000000..20ef6f66178b68 --- /dev/null +++ b/packages/react-native/Libraries/Components/LayoutConformance/LayoutConformance.d.ts @@ -0,0 +1,30 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +import type * as React from 'react'; +import {HostInstance} from '../../../types/public/ReactNativeTypes'; +import {Constructor} from '../../../types/private/Utilities'; + +type LayoutConformanceProps = { + /** + * strict: Layout in accordance with W3C spec, even when breaking + * compatibility: Layout with the same behavior as previous versions of React Native + */ + mode: 'strict' | 'compatibility'; + children: React.ReactNode; +}; + +declare class LayoutConformanceComponent extends React.Component {} +declare const LayoutConformanceBase: Constructor & + typeof LayoutConformanceComponent; + +/** + * Allows controlling the layout conformance/correctness behaviors of the subtree under this component. + */ +export class experimental_LayoutConformance extends LayoutConformanceBase {} diff --git a/packages/react-native/Libraries/Components/LayoutConformance/LayoutConformance.js b/packages/react-native/Libraries/Components/LayoutConformance/LayoutConformance.js new file mode 100644 index 00000000000000..69bcaa7509aff0 --- /dev/null +++ b/packages/react-native/Libraries/Components/LayoutConformance/LayoutConformance.js @@ -0,0 +1,58 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + * @oncall react_native + */ + +import StyleSheet from '../../StyleSheet/StyleSheet'; +import Text from '../../Text/Text'; +import UnimplementedView from '../UnimplementedViews/UnimplementedView'; +import LayoutConformanceNativeComponent from './LayoutConformanceNativeComponent'; +import * as React from 'react'; + +type Props = $ReadOnly<{ + /** + * strict: Layout in accordance with W3C spec, even when breaking + * compatibility: Layout with the same behavior as previous versions of React Native + */ + mode: 'strict' | 'compatibility', + + children: React.Node, +}>; + +// We want a graceful fallback for apps using legacy arch, but need to know +// ahead of time whether the component is available, so we test for global. +// This does not correctly handle mixed arch apps (which is okay, since we just +// degrade the error experience). +const isFabricUIManagerInstalled = global?.nativeFabricUIManager != null; + +/** + * Allows controlling the layout conformance/correctness behaviors of the subtree under this component. + */ +export default function LayoutConformance(props: Props): React.Node { + if (!isFabricUIManagerInstalled) { + return ( + + + The LayoutConformance component is only supported in the New + Architecture + + + ); + } + + return ( + + ); +} + +const styles = StyleSheet.create({ + container: { + display: 'contents', + }, +}); diff --git a/packages/react-native/Libraries/Components/LayoutConformance/LayoutConformanceNativeComponent.js b/packages/react-native/Libraries/Components/LayoutConformance/LayoutConformanceNativeComponent.js new file mode 100644 index 00000000000000..8c1cf66970316b --- /dev/null +++ b/packages/react-native/Libraries/Components/LayoutConformance/LayoutConformanceNativeComponent.js @@ -0,0 +1,29 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +import type {HostComponent} from '../../Renderer/shims/ReactNativeTypes'; +import type {ViewProps} from '../View/ViewPropTypes'; + +import * as NativeComponentRegistry from '../../NativeComponent/NativeComponentRegistry'; + +type Props = $ReadOnly<{ + mode: 'strict' | 'compatibility', + ...ViewProps, +}>; + +const LayoutConformanceNativeComponent: HostComponent = + NativeComponentRegistry.get('LayoutConformance', () => ({ + uiViewClassName: 'LayoutConformance', + validAttributes: { + mode: true, + }, + })); + +export default LayoutConformanceNativeComponent; diff --git a/packages/react-native/Libraries/Components/View/ViewPropTypes.d.ts b/packages/react-native/Libraries/Components/View/ViewPropTypes.d.ts index 9210d64aa836a8..a1aa196ccbd6ee 100644 --- a/packages/react-native/Libraries/Components/View/ViewPropTypes.d.ts +++ b/packages/react-native/Libraries/Components/View/ViewPropTypes.d.ts @@ -211,11 +211,4 @@ export interface ViewProps * Used to reference react managed views from native code. */ nativeID?: string | undefined; - - /** - * Contols whether this view, and its transitive children, are laid in a way - * consistent with web browsers ('strict'), or consistent with existing - * React Native code which may rely on incorrect behavior ('classic'). - */ - experimental_layoutConformance?: 'strict' | 'classic' | undefined; } diff --git a/packages/react-native/Libraries/Components/View/ViewPropTypes.js b/packages/react-native/Libraries/Components/View/ViewPropTypes.js index a0c2d5953ca5d3..f56e28d86e0c4b 100644 --- a/packages/react-native/Libraries/Components/View/ViewPropTypes.js +++ b/packages/react-native/Libraries/Components/View/ViewPropTypes.js @@ -578,15 +578,6 @@ export type ViewProps = $ReadOnly<{| */ collapsableChildren?: ?boolean, - /** - * Contols whether this view, and its transitive children, are laid in a way - * consistent with web browsers ('strict'), or consistent with existing - * React Native code which may rely on incorrect behavior ('classic'). - * - * This prop only works when using Fabric. - */ - experimental_layoutConformance?: ?('strict' | 'classic'), - /** * Used to locate this view from native classes. Has precedence over `nativeID` prop. * diff --git a/packages/react-native/Libraries/NativeComponent/BaseViewConfig.android.js b/packages/react-native/Libraries/NativeComponent/BaseViewConfig.android.js index 0fc6e8dc2d71c5..407943c4c9dc9c 100644 --- a/packages/react-native/Libraries/NativeComponent/BaseViewConfig.android.js +++ b/packages/react-native/Libraries/NativeComponent/BaseViewConfig.android.js @@ -293,8 +293,6 @@ const validAttributesForNonEventProps = { style: ReactNativeStyleAttributes, - experimental_layoutConformance: true, - // ReactClippingViewManager @ReactProps removeClippedSubviews: true, diff --git a/packages/react-native/Libraries/NativeComponent/BaseViewConfig.ios.js b/packages/react-native/Libraries/NativeComponent/BaseViewConfig.ios.js index 40f1b1bed35176..2cd155760db666 100644 --- a/packages/react-native/Libraries/NativeComponent/BaseViewConfig.ios.js +++ b/packages/react-native/Libraries/NativeComponent/BaseViewConfig.ios.js @@ -357,8 +357,6 @@ const validAttributesForNonEventProps = { direction: true, style: ReactNativeStyleAttributes, - - experimental_layoutConformance: true, }; // Props for bubbling and direct events diff --git a/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap b/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap index ad43dce3894403..7f69116dc1210c 100644 --- a/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap +++ b/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap @@ -1887,6 +1887,25 @@ declare export default typeof NativeKeyboardObserver; " `; +exports[`public API should not change unintentionally Libraries/Components/LayoutConformance/LayoutConformance.js 1`] = ` +"type Props = $ReadOnly<{ + mode: \\"strict\\" | \\"compatibility\\", + children: React.Node, +}>; +declare export default function LayoutConformance(props: Props): React.Node; +" +`; + +exports[`public API should not change unintentionally Libraries/Components/LayoutConformance/LayoutConformanceNativeComponent.js 1`] = ` +"type Props = $ReadOnly<{ + mode: \\"strict\\" | \\"compatibility\\", + ...ViewProps, +}>; +declare const LayoutConformanceNativeComponent: HostComponent; +declare export default typeof LayoutConformanceNativeComponent; +" +`; + exports[`public API should not change unintentionally Libraries/Components/Pressable/Pressable.js 1`] = ` "type ViewStyleProp = $ElementType, \\"style\\">; export type StateCallbackType = $ReadOnly<{| @@ -4268,7 +4287,6 @@ export type ViewProps = $ReadOnly<{| \\"aria-hidden\\"?: ?boolean, collapsable?: ?boolean, collapsableChildren?: ?boolean, - experimental_layoutConformance?: ?(\\"strict\\" | \\"classic\\"), id?: string, testID?: ?string, nativeID?: ?string, @@ -9623,6 +9641,7 @@ declare module.exports: { get Image(): Image, get ImageBackground(): ImageBackground, get InputAccessoryView(): InputAccessoryView, + get experimental_LayoutConformance(): LayoutConformance, get KeyboardAvoidingView(): KeyboardAvoidingView, get Modal(): Modal, get Pressable(): Pressable, diff --git a/packages/react-native/React/Views/RCTViewManager.m b/packages/react-native/React/Views/RCTViewManager.m index 12d854a7997f91..f017cb87caaeeb 100644 --- a/packages/react-native/React/Views/RCTViewManager.m +++ b/packages/react-native/React/Views/RCTViewManager.m @@ -424,13 +424,6 @@ - (void)updateAccessibilityTraitsForRole:(RCTView *)view withDefaultView:(RCTVie // filtered by view configs. } -RCT_CUSTOM_VIEW_PROPERTY(experimental_layoutConformance, NSString *, RCTView) -{ - // Property is only to be used in the new renderer. - // It is necessary to add it here, otherwise it gets - // filtered by view configs. -} - typedef NSArray *FilterArray; // Custom type to make the StaticViewConfigValidator Happy RCT_CUSTOM_VIEW_PROPERTY(filter, FilterArray, RCTView) { diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index bfc684e1623dca..1b5a13a73d3e0e 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -4175,7 +4175,6 @@ public class com/facebook/react/uimanager/LayoutShadowNode : com/facebook/react/ public fun setInsetBlock (ILcom/facebook/react/bridge/Dynamic;)V public fun setInsetInline (ILcom/facebook/react/bridge/Dynamic;)V public fun setJustifyContent (Ljava/lang/String;)V - public fun setLayoutConformance (Ljava/lang/String;)V public fun setMarginBlock (ILcom/facebook/react/bridge/Dynamic;)V public fun setMarginInline (ILcom/facebook/react/bridge/Dynamic;)V public fun setMargins (ILcom/facebook/react/bridge/Dynamic;)V diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/LayoutShadowNode.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/LayoutShadowNode.java index d1fbd2479aa5c2..d62808ae4ff096 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/LayoutShadowNode.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/LayoutShadowNode.java @@ -972,9 +972,4 @@ public void setShouldNotifyPointerLeave(boolean value) { public void setShouldNotifyPointerMove(boolean value) { // Do Nothing: Align with static ViewConfigs } - - @ReactProp(name = "experimental_layoutConformance") - public void setLayoutConformance(String value) { - // Do Nothing: Align with static ViewConfigs - } } diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/CoreComponentsRegistry.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/CoreComponentsRegistry.cpp index aa2cd5ffbe0a0e..935d48ca0241e8 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/CoreComponentsRegistry.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/CoreComponentsRegistry.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include namespace facebook::react::CoreComponentsRegistry { @@ -66,6 +67,8 @@ sharedProviderRegistry() { AndroidDrawerLayoutComponentDescriptor>()); providerRegistry->add(concreteComponentDescriptorProvider< DebuggingOverlayComponentDescriptor>()); + providerRegistry->add(concreteComponentDescriptorProvider< + LayoutConformanceComponentDescriptor>()); return providerRegistry; }(); diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp index cd785ced9bc812..bfa341f6568089 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp @@ -342,16 +342,7 @@ BaseViewProps::BaseViewProps( rawProps, "removeClippedSubviews", sourceProps.removeClippedSubviews, - false)), - experimental_layoutConformance( - ReactNativeFeatureFlags::enableCppPropsIteratorSetter() - ? sourceProps.experimental_layoutConformance - : convertRawProp( - context, - rawProps, - "experimental_layoutConformance", - sourceProps.experimental_layoutConformance, - {})) {} + false)) {} #define VIEW_EVENT_CASE(eventType) \ case CONSTEXPR_RAW_PROPS_KEY_HASH("on" #eventType): { \ @@ -397,7 +388,6 @@ void BaseViewProps::setProp( RAW_SET_PROP_SWITCH_CASE_BASIC(collapsable); RAW_SET_PROP_SWITCH_CASE_BASIC(collapsableChildren); RAW_SET_PROP_SWITCH_CASE_BASIC(removeClippedSubviews); - RAW_SET_PROP_SWITCH_CASE_BASIC(experimental_layoutConformance); RAW_SET_PROP_SWITCH_CASE_BASIC(cursor); RAW_SET_PROP_SWITCH_CASE_BASIC(outlineColor); RAW_SET_PROP_SWITCH_CASE_BASIC(outlineOffset); diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h index f33bcf265eed60..cbd7beb492d860 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h @@ -106,8 +106,6 @@ class BaseViewProps : public YogaStylableProps, public AccessibilityProps { bool removeClippedSubviews{false}; - LayoutConformance experimental_layoutConformance{}; - #pragma mark - Convenience Methods CascadedBorderWidths getBorderWidths() const; diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/LayoutConformanceComponentDescriptor.h b/packages/react-native/ReactCommon/react/renderer/components/view/LayoutConformanceComponentDescriptor.h new file mode 100644 index 00000000000000..fdeae8854e7695 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/components/view/LayoutConformanceComponentDescriptor.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include + +namespace facebook::react { + +using LayoutConformanceComponentDescriptor = + ConcreteComponentDescriptor; + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/LayoutConformanceProps.h b/packages/react-native/ReactCommon/react/renderer/components/view/LayoutConformanceProps.h new file mode 100644 index 00000000000000..7425ece1123e0e --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/components/view/LayoutConformanceProps.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include + +namespace facebook::react { + +struct LayoutConformanceProps final : public YogaStylableProps { + /** + * Whether to layout the subtree with strict conformance to W3C standard + * (YGErrataNone) or for compatibility with legacy RN bugs (YGErrataAll) + */ + LayoutConformance mode{LayoutConformance::Strict}; + + LayoutConformanceProps() = default; + LayoutConformanceProps( + const PropsParserContext& context, + const LayoutConformanceProps& sourceProps, + const RawProps& rawProps) + : YogaStylableProps(context, sourceProps, rawProps), + mode{convertRawProp( + context, + rawProps, + "mode", + mode, + LayoutConformance::Strict)} {} +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/LayoutConformanceShadowNode.h b/packages/react-native/ReactCommon/react/renderer/components/view/LayoutConformanceShadowNode.h new file mode 100644 index 00000000000000..a98acb21f0aaa2 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/components/view/LayoutConformanceShadowNode.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include + +namespace facebook::react { + +constexpr const char LayoutConformanceShadowNodeComponentName[] = + "LayoutConformance"; + +using LayoutConformanceShadowNode = ConcreteShadowNode< + LayoutConformanceShadowNodeComponentName, + YogaLayoutableShadowNode, + LayoutConformanceProps>; + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.cpp index 89b16b71b617d1..055e4bd907d180 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -501,15 +502,13 @@ void YogaLayoutableShadowNode::configureYogaTree( } YGErrata YogaLayoutableShadowNode::resolveErrata(YGErrata defaultErrata) const { - if (auto viewShadowNode = dynamic_cast(this)) { - const auto& props = viewShadowNode->getConcreteProps(); - switch (props.experimental_layoutConformance) { - case LayoutConformance::Classic: - return YGErrataAll; + if (auto layoutConformanceNode = + dynamic_cast(this)) { + switch (layoutConformanceNode->getConcreteProps().mode) { case LayoutConformance::Strict: return YGErrataNone; - case LayoutConformance::Undefined: - return defaultErrata; + case LayoutConformance::Compatibility: + return YGErrataAll; } } diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/conversions.h b/packages/react-native/ReactCommon/react/renderer/components/view/conversions.h index 66fdd78acc6fdb..ef4202ce9580b8 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/conversions.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/conversions.h @@ -1018,22 +1018,21 @@ inline void fromRawValue( const PropsParserContext& /*context*/, const RawValue& value, LayoutConformance& result) { - result = LayoutConformance::Classic; react_native_expect(value.hasType()); + result = LayoutConformance::Strict; if (!value.hasType()) { return; } + auto stringValue = (std::string)value; - if (stringValue == "classic") { - result = LayoutConformance::Classic; - return; - } if (stringValue == "strict") { result = LayoutConformance::Strict; - return; + } else if (stringValue == "compatibility") { + result = LayoutConformance::Compatibility; + } else { + LOG(ERROR) << "Unexpected LayoutConformance value:" << stringValue; + react_native_expect(false); } - LOG(ERROR) << "Could not parse LayoutConformance:" << stringValue; - react_native_expect(false); } inline void fromRawValue( @@ -1457,12 +1456,10 @@ inline std::string toString(const yoga::FloatOptional& value) { inline std::string toString(const LayoutConformance& value) { switch (value) { - case LayoutConformance::Undefined: - return "undefined"; - case LayoutConformance::Classic: - return "classic"; case LayoutConformance::Strict: return "strict"; + case LayoutConformance::Compatibility: + return "compatibility"; } } diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/primitives.h b/packages/react-native/ReactCommon/react/renderer/components/view/primitives.h index feb396a0535584..96d379b6c4bf2e 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/primitives.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/primitives.h @@ -140,7 +140,7 @@ enum class Cursor : uint8_t { ZoomOut, }; -enum class LayoutConformance : uint8_t { Undefined, Classic, Strict }; +enum class LayoutConformance : uint8_t { Strict, Compatibility }; template struct CascadedRectangleEdges { diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.cpp b/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.cpp index 09c67c66b573e9..0728ee811824be 100644 --- a/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.cpp +++ b/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.cpp @@ -397,6 +397,9 @@ void ShadowTree::emitLayoutEvents( for (const auto* layoutableNode : affectedLayoutableNodes) { // Only instances of `ViewShadowNode` (and subclasses) are supported. + if (dynamic_cast(layoutableNode) == nullptr) { + continue; + } const auto& viewEventEmitter = static_cast( *layoutableNode->getEventEmitter()); diff --git a/packages/react-native/index.js b/packages/react-native/index.js index 99a5a5ee17bda2..31f568698f6f2a 100644 --- a/packages/react-native/index.js +++ b/packages/react-native/index.js @@ -28,6 +28,7 @@ import typeof Clipboard from './Libraries/Components/Clipboard/Clipboard'; import typeof DrawerLayoutAndroid from './Libraries/Components/DrawerAndroid/DrawerLayoutAndroid'; import typeof Keyboard from './Libraries/Components/Keyboard/Keyboard'; import typeof KeyboardAvoidingView from './Libraries/Components/Keyboard/KeyboardAvoidingView'; +import typeof LayoutConformance from './Libraries/Components/LayoutConformance/LayoutConformance'; import typeof Pressable from './Libraries/Components/Pressable/Pressable'; import typeof ProgressBarAndroid from './Libraries/Components/ProgressBarAndroid/ProgressBarAndroid'; import typeof RefreshControl from './Libraries/Components/RefreshControl/RefreshControl'; @@ -136,6 +137,10 @@ module.exports = { return require('./Libraries/Components/TextInput/InputAccessoryView') .default; }, + get experimental_LayoutConformance(): LayoutConformance { + return require('./Libraries/Components/LayoutConformance/LayoutConformance') + .default; + }, get KeyboardAvoidingView(): KeyboardAvoidingView { return require('./Libraries/Components/Keyboard/KeyboardAvoidingView') .default; diff --git a/packages/react-native/types/__typetests__/index.tsx b/packages/react-native/types/__typetests__/index.tsx index 67476ab6c544c8..b03e7c744c8274 100644 --- a/packages/react-native/types/__typetests__/index.tsx +++ b/packages/react-native/types/__typetests__/index.tsx @@ -121,6 +121,7 @@ import { ToastAndroid, Touchable, LayoutAnimation, + experimental_LayoutConformance as LayoutConformance, } from 'react-native'; declare module 'react-native' { @@ -2212,3 +2213,15 @@ const ActionSheetIOSTest = () => { // test dismissActionSheet method ActionSheetIOS.dismissActionSheet(); }; + +function LayoutConformanceView() { + const ref = React.useRef(null); + + ref.current?.measureInWindow(() => {}); + + return ( + + + + ); +} diff --git a/packages/react-native/types/index.d.ts b/packages/react-native/types/index.d.ts index 3df2d2738ea4a2..63fc7e3096dac8 100644 --- a/packages/react-native/types/index.d.ts +++ b/packages/react-native/types/index.d.ts @@ -84,6 +84,7 @@ export * from '../Libraries/Components/Clipboard/Clipboard'; export * from '../Libraries/Components/DrawerAndroid/DrawerLayoutAndroid'; export * from '../Libraries/Components/Keyboard/Keyboard'; export * from '../Libraries/Components/Keyboard/KeyboardAvoidingView'; +export * from '../Libraries/Components/LayoutConformance/LayoutConformance'; export * from '../Libraries/Components/Pressable/Pressable'; export * from '../Libraries/Components/ProgressBarAndroid/ProgressBarAndroid'; export * from '../Libraries/Components/RefreshControl/RefreshControl'; diff --git a/packages/rn-tester/js/examples/LayoutConformance/LayoutConformanceExample.js b/packages/rn-tester/js/examples/LayoutConformance/LayoutConformanceExample.js new file mode 100644 index 00000000000000..8bd8b07e7a299c --- /dev/null +++ b/packages/rn-tester/js/examples/LayoutConformance/LayoutConformanceExample.js @@ -0,0 +1,106 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + * @flow strict-local + */ + +'use strict'; + +import type {RNTesterModule} from '../../types/RNTesterTypes'; + +import RNTesterText from '../../components/RNTesterText'; +import * as React from 'react'; +import { + View, + experimental_LayoutConformance as LayoutConformance, +} from 'react-native'; + +function LayoutConformanceExample({ + testID, +}: $ReadOnly<{testID: ?string}>): React.Node { + return ( + + + Unset + + + + + Compat + + + + + + Strict + + + + + ); +} + +function LayoutConformanceBox(): React.Node { + return ( + + {/*Yoga with YGErrataStretchFlexBasis will let this node grow to + parent width, even though it shouldn't take any space*/} + + + + + ); +} + +export default ({ + title: 'LayoutConformance', + description: + 'Examples laid out in compatibility mode will show a red bar, not present in examples with conformant layout.', + examples: [ + { + title: 'LayoutConformance (No outer context)', + name: 'layout-conformance-no-outer-context', + render: () => ( + + ), + }, + { + title: 'LayoutConformance (Under compatibility context)', + name: 'layout-conformance-under-compat', + render: () => ( + + + + ), + }, + { + title: 'LayoutConformance (Under strict context)', + name: 'layout-conformance-under-strict', + render: () => ( + + + + ), + }, + ], +}: RNTesterModule); diff --git a/packages/rn-tester/js/examples/View/ViewExample.js b/packages/rn-tester/js/examples/View/ViewExample.js index f25662a3f402a9..f5280ba3f9d2c8 100644 --- a/packages/rn-tester/js/examples/View/ViewExample.js +++ b/packages/rn-tester/js/examples/View/ViewExample.js @@ -410,55 +410,6 @@ class FlexGapExample extends React.Component<$ReadOnly<{|testID?: ?string|}>> { } } -function LayoutConformanceExample({ - testID, -}: $ReadOnly<{testID: ?string}>): React.Node { - return ( - - - Unset - - - - Classic - - - - Strict - - - - ); -} - -function LayoutConformanceBox(): React.Node { - return ( - - - - - - ); -} - function BoxShadowExample(): React.Node { const defaultStyleSize = {width: 75, height: 75}; @@ -1355,11 +1306,6 @@ export default ({ ); }, }, - { - title: 'Layout conformance', - name: 'layout-conformance', - render: LayoutConformanceExample, - }, { title: 'Box Shadow', name: 'box-shadow', diff --git a/packages/rn-tester/js/utils/RNTesterList.android.js b/packages/rn-tester/js/utils/RNTesterList.android.js index 5f531c5ded228b..ced32824302678 100644 --- a/packages/rn-tester/js/utils/RNTesterList.android.js +++ b/packages/rn-tester/js/utils/RNTesterList.android.js @@ -45,6 +45,11 @@ const Components: Array = [ category: 'Basic', module: require('../examples/Image/ImageExample'), }, + { + key: 'LayoutConformanceExample', + module: require('../examples/LayoutConformance/LayoutConformanceExample') + .default, + }, { key: 'JSResponderHandlerExample', module: require('../examples/JSResponderHandlerExample/JSResponderHandlerExample'), diff --git a/packages/rn-tester/js/utils/RNTesterList.ios.js b/packages/rn-tester/js/utils/RNTesterList.ios.js index cf06c62fa775af..5349e8888ffa14 100644 --- a/packages/rn-tester/js/utils/RNTesterList.ios.js +++ b/packages/rn-tester/js/utils/RNTesterList.ios.js @@ -47,6 +47,11 @@ const Components: Array = [ key: 'KeyboardAvoidingViewExample', module: require('../examples/KeyboardAvoidingView/KeyboardAvoidingViewExample'), }, + { + key: 'LayoutConformanceExample', + module: require('../examples/LayoutConformance/LayoutConformanceExample') + .default, + }, { key: 'LayoutEventsExample', module: require('../examples/Layout/LayoutEventsExample'),