diff --git a/packages/react-native/Libraries/Components/View/ViewNativeComponent.js b/packages/react-native/Libraries/Components/View/ViewNativeComponent.js index d8052fb9889..df119f6c5ed 100644 --- a/packages/react-native/Libraries/Components/View/ViewNativeComponent.js +++ b/packages/react-native/Libraries/Components/View/ViewNativeComponent.js @@ -98,6 +98,7 @@ export const __INTERNAL_VIEW_CONFIG: PartialViewConfig = focusable: true, overflow: true, backfaceVisibility: true, + experimental_layoutConformance: true, }, } : { diff --git a/packages/react-native/Libraries/Components/View/ViewPropTypes.js b/packages/react-native/Libraries/Components/View/ViewPropTypes.js index 501eb311a7c..1ead9ba053a 100644 --- a/packages/react-native/Libraries/Components/View/ViewPropTypes.js +++ b/packages/react-native/Libraries/Components/View/ViewPropTypes.js @@ -568,6 +568,15 @@ export type ViewProps = $ReadOnly<{| */ collapsable?: ?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 f0dfd8d6628..249e81199a5 100644 --- a/packages/react-native/Libraries/NativeComponent/BaseViewConfig.android.js +++ b/packages/react-native/Libraries/NativeComponent/BaseViewConfig.android.js @@ -273,6 +273,8 @@ const validAttributesForNonEventProps = { position: true, style: ReactNativeStyleAttributes, + + experimental_layoutConformance: true, }; // Props for bubbling and direct events diff --git a/packages/react-native/Libraries/NativeComponent/BaseViewConfig.ios.js b/packages/react-native/Libraries/NativeComponent/BaseViewConfig.ios.js index 92b2959aad1..61628802a85 100644 --- a/packages/react-native/Libraries/NativeComponent/BaseViewConfig.ios.js +++ b/packages/react-native/Libraries/NativeComponent/BaseViewConfig.ios.js @@ -335,6 +335,8 @@ const validAttributesForNonEventProps = { direction: true, style: ReactNativeStyleAttributes, + + experimental_layoutConformance: true, }; // Props for bubbling and direct events diff --git a/packages/react-native/React/Views/RCTViewManager.m b/packages/react-native/React/Views/RCTViewManager.m index eb3ff9c00d8..a88064337cf 100644 --- a/packages/react-native/React/Views/RCTViewManager.m +++ b/packages/react-native/React/Views/RCTViewManager.m @@ -398,6 +398,13 @@ RCT_CUSTOM_VIEW_PROPERTY(collapsable, BOOL, RCTView) // 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. +} + #define RCT_VIEW_BORDER_PROPERTY(SIDE) \ RCT_CUSTOM_VIEW_PROPERTY(border##SIDE##Width, float, RCTView) \ { \ diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java index 4b3d624907d..a30e31d0f79 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java @@ -151,6 +151,12 @@ public class ReactViewManager extends ReactClippingViewManager { view.setBorderStyle(borderStyle); } + // This is unused by the view manager, and not wired to be sent to Java, but + // must be present for the prop to show up in the view config. + @ReactProp(name = "experimental_layoutConformance") + public void setexperimental_layoutConformance( + ReactViewGroup view, @Nullable String layoutConformance) {} + @ReactProp(name = "hitSlop") public void setHitSlop(final ReactViewGroup view, Dynamic hitSlop) { switch (hitSlop.getType()) { 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 2f0f22023b2..97f3caf2b89 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp @@ -190,7 +190,16 @@ BaseViewProps::BaseViewProps( rawProps, "removeClippedSubviews", sourceProps.removeClippedSubviews, - false)) {} + false)), + experimental_layoutConformance( + CoreFeatures::enablePropIteratorSetter + ? sourceProps.experimental_layoutConformance + : convertRawProp( + context, + rawProps, + "experimental_layoutConformance", + sourceProps.experimental_layoutConformance, + {})) {} #define VIEW_EVENT_CASE(eventType) \ case CONSTEXPR_RAW_PROPS_KEY_HASH("on" #eventType): { \ @@ -233,6 +242,7 @@ void BaseViewProps::setProp( RAW_SET_PROP_SWITCH_CASE_BASIC(onLayout); RAW_SET_PROP_SWITCH_CASE_BASIC(collapsable); RAW_SET_PROP_SWITCH_CASE_BASIC(removeClippedSubviews); + RAW_SET_PROP_SWITCH_CASE_BASIC(experimental_layoutConformance); // events field VIEW_EVENT_CASE(PointerEnter); VIEW_EVENT_CASE(PointerEnterCapture); 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 706620f7b5a..9d2530e7061 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h @@ -70,6 +70,8 @@ class BaseViewProps : public YogaStylableProps, public AccessibilityProps { bool removeClippedSubviews{false}; + LayoutConformance experimental_layoutConformance{}; + Float elevation{}; /* Android-only */ #pragma mark - Convenience Methods 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 12c8ebd1255..6d0a2f4807a 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.cpp @@ -115,9 +115,11 @@ YogaLayoutableShadowNode::YogaLayoutableShadowNode( .yogaNode_.isDirty() == yogaNode_.isDirty() && "Yoga node must inherit dirty flag."); - for (auto &child : getChildren()) { - if (auto layoutableChild = traitCast(child)) { - yogaLayoutableChildren_.push_back(layoutableChild); + if (!getTraits().check(ShadowNodeTraits::Trait::LeafYogaNode)) { + for (auto &child : getChildren()) { + if (auto layoutableChild = traitCast(child)) { + yogaLayoutableChildren_.push_back(layoutableChild); + } } } @@ -138,6 +140,15 @@ YogaLayoutableShadowNode::YogaLayoutableShadowNode( yogaNode_.setDirty(true); } + // We do not need to reconfigure this subtree before the next layout pass if + // the previous node with the same props and children has already been + // configured. + if (!fragment.props && !fragment.children) { + yogaTreeHasBeenConfigured_ = + static_cast(sourceShadowNode) + .yogaTreeHasBeenConfigured_; + } + if (fragment.props) { updateYogaProps(); } @@ -436,6 +447,85 @@ void YogaLayoutableShadowNode::updateYogaProps() { return result; } +void YogaLayoutableShadowNode::configureYogaTree( + float pointScaleFactor, + YGErrata defaultErrata, + bool swapLeftAndRight) { + ensureUnsealed(); + + // Set state on our own Yoga node + YGErrata errata = resolveErrata(defaultErrata); + YGConfigSetErrata(&yogaConfig_, errata); + YGConfigSetPointScaleFactor(&yogaConfig_, pointScaleFactor); + + // TODO: `swapLeftAndRight` modified backing props and cannot be undone + if (swapLeftAndRight) { + swapStyleLeftAndRight(); + } + + yogaTreeHasBeenConfigured_ = true; + + // Recursively propagate the configuration to child nodes. If a child was + // already configured as part of a previous ShadowTree generation, we only + // need to reconfigure it if the context values passed to the Node have + // changed. + for (size_t i = 0; i < yogaLayoutableChildren_.size(); i++) { + const auto &child = *yogaLayoutableChildren_[i]; + auto childLayoutMetrics = child.getLayoutMetrics(); + auto childErrata = + YGConfigGetErrata(const_cast(&child.yogaConfig_)); + + if (child.yogaTreeHasBeenConfigured_ && + childLayoutMetrics.pointScaleFactor == pointScaleFactor && + childLayoutMetrics.wasLeftAndRightSwapped == swapLeftAndRight && + childErrata == child.resolveErrata(errata)) { + continue; + } + + if (doesOwn(child)) { + auto &mutableChild = const_cast(child); + mutableChild.configureYogaTree( + pointScaleFactor, child.resolveErrata(errata), swapLeftAndRight); + } else { + cloneChildInPlace(i).configureYogaTree( + pointScaleFactor, errata, swapLeftAndRight); + } + } +} + +YGErrata YogaLayoutableShadowNode::resolveErrata(YGErrata defaultErrata) const { + if (auto viewShadowNode = traitCast(this)) { + const auto &props = viewShadowNode->getConcreteProps(); + switch (props.experimental_layoutConformance) { + case LayoutConformance::Classic: + return YGErrataAll; + case LayoutConformance::Strict: + return YGErrataNone; + case LayoutConformance::Undefined: + return defaultErrata; + } + } + + return defaultErrata; +} + +YogaLayoutableShadowNode &YogaLayoutableShadowNode::cloneChildInPlace( + int32_t layoutableChildIndex) { + ensureUnsealed(); + + const auto &childNode = *yogaLayoutableChildren_[layoutableChildIndex]; + + // TODO: Why does this not use `ShadowNodeFragment::statePlaceholder()` like + // `adoptYogaChild()`? + auto clonedChildNode = childNode.clone( + {ShadowNodeFragment::propsPlaceholder(), + ShadowNodeFragment::childrenPlaceholder(), + childNode.getState()}); + + replaceChild(childNode, clonedChildNode, layoutableChildIndex); + return static_cast(*clonedChildNode); +} + void YogaLayoutableShadowNode::setSize(Size size) const { ensureUnsealed(); @@ -484,16 +574,19 @@ void YogaLayoutableShadowNode::layoutTree( LayoutConstraints layoutConstraints) { ensureUnsealed(); - /* - * In Yoga, every single Yoga Node has to have a (non-null) pointer to - * Yoga Config (this config can be shared between many nodes), - * so every node can be individually configured. This does *not* mean - * however that Yoga consults with every single Yoga Node Config for every - * config parameter. Especially in case of `pointScaleFactor`, - * the only value in the config of the root node is taken into account - * (and this is by design). - */ - YGConfigSetPointScaleFactor(&yogaConfig_, layoutContext.pointScaleFactor); + SystraceSection s1("YogaLayoutableShadowNode::layoutTree"); + + bool swapLeftAndRight = layoutContext.swapLeftAndRightInRTL && + (layoutConstraints.layoutDirection == LayoutDirection::RightToLeft || + !CoreFeatures::doNotSwapLeftAndRightOnAndroidInLTR); + + { + SystraceSection s2("YogaLayoutableShadowNode::configureYogaTree"); + configureYogaTree( + layoutContext.pointScaleFactor, + YGErrataAll /*defaultErrata*/, + swapLeftAndRight); + } auto minimumSize = layoutConstraints.minimumSize; auto maximumSize = layoutConstraints.maximumSize; @@ -551,25 +644,17 @@ void YogaLayoutableShadowNode::layoutTree( threadLocalLayoutContext = layoutContext; - if (CoreFeatures::doNotSwapLeftAndRightOnAndroidInLTR) { - if (layoutConstraints.layoutDirection == LayoutDirection::RightToLeft && - layoutContext.swapLeftAndRightInRTL) { - swapLeftAndRightInTree(*this); - } - } else { - if (layoutContext.swapLeftAndRightInRTL) { - swapLeftAndRightInTree(*this); - } - } - { - SystraceSection s("YogaLayoutableShadowNode::YGNodeCalculateLayout"); + SystraceSection s3("YogaLayoutableShadowNode::YGNodeCalculateLayout"); YGNodeCalculateLayout(&yogaNode_, ownerWidth, ownerHeight, direction); } + // Update layout metrics for root node. Updated for children in + // YogaLayoutableShadowNode::layout if (yogaNode_.getHasNewLayout()) { auto layoutMetrics = layoutMetricsFromYogaNode(yogaNode_); layoutMetrics.pointScaleFactor = layoutContext.pointScaleFactor; + layoutMetrics.wasLeftAndRightSwapped = swapLeftAndRight; setLayoutMetrics(layoutMetrics); yogaNode_.setHasNewLayout(false); } @@ -617,6 +702,10 @@ void YogaLayoutableShadowNode::layout(LayoutContext layoutContext) { auto newLayoutMetrics = layoutMetricsFromYogaNode(*childYogaNode); newLayoutMetrics.pointScaleFactor = layoutContext.pointScaleFactor; + newLayoutMetrics.wasLeftAndRightSwapped = + layoutContext.swapLeftAndRightInRTL && + (newLayoutMetrics.layoutDirection == LayoutDirection::RightToLeft || + !CoreFeatures::doNotSwapLeftAndRightOnAndroidInLTR); // Child node's layout has changed. When a node is added to // `affectedNodes`, onLayout event is called on the component. Comparing @@ -683,22 +772,13 @@ void YogaLayoutableShadowNode::layout(LayoutContext layoutContext) { #pragma mark - Yoga Connectors YGNode *YogaLayoutableShadowNode::yogaNodeCloneCallbackConnector( - YGNode *oldYogaNode, + YGNode * /*oldYogaNode*/, YGNode *parentYogaNode, int childIndex) { SystraceSection s("YogaLayoutableShadowNode::yogaNodeCloneCallbackConnector"); - // At this point it is guaranteed that all shadow nodes associated with yoga - // nodes are `YogaLayoutableShadowNode` subclasses. auto &parentNode = shadowNodeFromContext(parentYogaNode); - auto &oldNode = shadowNodeFromContext(oldYogaNode); - - auto clonedNode = oldNode.clone( - {ShadowNodeFragment::propsPlaceholder(), - ShadowNodeFragment::childrenPlaceholder(), - oldNode.getState()}); - parentNode.replaceChild(oldNode, clonedNode, childIndex); - return &traitCast(*clonedNode).yogaNode_; + return &parentNode.cloneChildInPlace(childIndex).yogaNode_; } YGSize YogaLayoutableShadowNode::yogaNodeMeasureCallbackConnector( @@ -759,10 +839,10 @@ YGConfig &YogaLayoutableShadowNode::initializeYogaConfig( const YGConfigRef previousConfig) { YGConfigSetCloneNodeFunc( &config, YogaLayoutableShadowNode::yogaNodeCloneCallbackConnector); - YGConfigSetErrata(&config, YGErrataAll); if (previousConfig != nullptr) { YGConfigSetPointScaleFactor( &config, YGConfigGetPointScaleFactor(previousConfig)); + YGConfigSetErrata(&config, YGConfigGetErrata(previousConfig)); } #ifdef RN_DEBUG_YOGA_LOGGER @@ -773,16 +853,11 @@ YGConfig &YogaLayoutableShadowNode::initializeYogaConfig( #pragma mark - RTL left and right swapping -void YogaLayoutableShadowNode::swapLeftAndRightInTree( - YogaLayoutableShadowNode const &shadowNode) { - swapLeftAndRightInYogaStyleProps(shadowNode); - swapLeftAndRightInViewProps(shadowNode); +void YogaLayoutableShadowNode::swapStyleLeftAndRight() { + ensureUnsealed(); - for (auto &child : shadowNode.yogaLayoutableChildren_) { - if (!child->doesOwn(shadowNode)) { - swapLeftAndRightInTree(*child); - } - } + swapLeftAndRightInYogaStyleProps(*this); + swapLeftAndRightInViewProps(*this); } void YogaLayoutableShadowNode::swapLeftAndRightInYogaStyleProps( @@ -893,22 +968,6 @@ void YogaLayoutableShadowNode::swapLeftAndRightInViewProps( void YogaLayoutableShadowNode::ensureConsistency() const { ensureYogaChildrenLookFine(); ensureYogaChildrenAlignment(); - ensureYogaChildrenOwnersConsistency(); -} - -void YogaLayoutableShadowNode::ensureYogaChildrenOwnersConsistency() const { -#ifdef REACT_NATIVE_DEBUG - // Checking that all Yoga node children have the same `owner`. - // The owner might be not equal to the `yogaNode_` though. - auto &yogaChildren = yogaNode_.getChildren(); - - if (!yogaChildren.empty()) { - auto owner = yogaChildren.at(0)->getOwner(); - for (auto const &child : yogaChildren) { - react_native_assert(child->getOwner() == owner); - } - } -#endif } void YogaLayoutableShadowNode::ensureYogaChildrenLookFine() const { diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.h b/packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.h index d59e515487b..fee4c635810 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.h @@ -132,6 +132,27 @@ class YogaLayoutableShadowNode : public LayoutableShadowNode { */ void adoptYogaChild(size_t index); + /** + * Applies contextual values to the ShadowNode's Yoga tree after the + * ShadowTree has been constructed, but before it has been is laid out or + * committed. + */ + void configureYogaTree( + float pointScaleFactor, + YGErrata defaultErrata, + bool swapLeftAndRight); + + /** + * Return an errata based on a `layoutConformance` prop if given, otherwise + * the passed default + */ + YGErrata resolveErrata(YGErrata defaultErrata) const; + + /** + * Replcaes a child with a mutable clone of itself, returning the clone. + */ + YogaLayoutableShadowNode &cloneChildInPlace(int32_t layoutableChildIndex); + static YGConfig &initializeYogaConfig( YGConfig &config, YGConfigRef previousConfig = nullptr); @@ -150,7 +171,7 @@ class YogaLayoutableShadowNode : public LayoutableShadowNode { #pragma mark - RTL Legacy Autoflip /* - * Walks though shadow node hierarchy and reassign following values: + * Reassigns the following values: * - (left|right) → (start|end) * - margin(Left|Right) → margin(Start|End) * - padding(Left|Right) → padding(Start|End) @@ -161,8 +182,7 @@ class YogaLayoutableShadowNode : public LayoutableShadowNode { * This is neccesarry to be backwards compatible with old renderer, it swaps * the values as well in https://fburl.com/diffusion/kl7bjr3h */ - static void swapLeftAndRightInTree( - YogaLayoutableShadowNode const &shadowNode); + void swapStyleLeftAndRight(); /* * In shadow node passed as argument, reassigns following values * - borderTop(Left|Right)Radius → borderTop(Start|End)Radius @@ -193,7 +213,6 @@ class YogaLayoutableShadowNode : public LayoutableShadowNode { void ensureConsistency() const; void ensureYogaChildrenAlignment() const; - void ensureYogaChildrenOwnersConsistency() const; void ensureYogaChildrenLookFine() const; #pragma mark - Private member variables @@ -201,6 +220,11 @@ class YogaLayoutableShadowNode : public LayoutableShadowNode { * List of children which derive from YogaLayoutableShadowNode */ ListOfShared yogaLayoutableChildren_; + + /* + * Whether the full Yoga subtree of this Node has been configured. + */ + bool yogaTreeHasBeenConfigured_{false}; }; } // namespace facebook::react 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 85191d84373..7d3d9d0586f 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/conversions.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/conversions.h @@ -660,6 +660,28 @@ inline void fromRawValue( react_native_expect(false); } +inline void fromRawValue( + const PropsParserContext & /*context*/, + const RawValue &value, + LayoutConformance &result) { + result = LayoutConformance::Classic; + react_native_expect(value.hasType()); + if (!value.hasType()) { + return; + } + auto stringValue = (std::string)value; + if (stringValue == "classic") { + result = LayoutConformance::Classic; + return; + } + if (stringValue == "strict") { + result = LayoutConformance::Strict; + return; + } + LOG(ERROR) << "Could not parse LayoutConformance:" << stringValue; + react_native_expect(false); +} + inline std::string toString( const std::array()> &dimensions) { return "{" + folly::to(dimensions[0]) + ", " + @@ -838,4 +860,15 @@ inline std::string toString(const YGStyle::Edges &value) { return "{" + result + "}"; } +inline std::string toString(const LayoutConformance &value) { + switch (value) { + case LayoutConformance::Undefined: + return "undefined"; + case LayoutConformance::Classic: + return "classic"; + case LayoutConformance::Strict: + return "strict"; + } +} + } // namespace facebook::react 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 9e7889a7612..e3591f7c41f 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/primitives.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/primitives.h @@ -87,6 +87,8 @@ enum class BorderCurve : uint8_t { Circular, Continuous }; enum class BorderStyle : uint8_t { Solid, Dotted, Dashed }; +enum class LayoutConformance : uint8_t { Undefined, Classic, Strict }; + template struct CascadedRectangleEdges { using Counterpart = RectangleEdges; diff --git a/packages/react-native/ReactCommon/react/renderer/core/LayoutMetrics.h b/packages/react-native/ReactCommon/react/renderer/core/LayoutMetrics.h index c4f4b82c592..714b37f43b4 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/LayoutMetrics.h +++ b/packages/react-native/ReactCommon/react/renderer/core/LayoutMetrics.h @@ -31,6 +31,10 @@ struct LayoutMetrics { DisplayType displayType{DisplayType::Flex}; // See `LayoutDirection` for all possible options. LayoutDirection layoutDirection{LayoutDirection::Undefined}; + // Whether React Native treated cardinal directions as flow-relative + // directions due to being laid out in RTL with `doLeftAndRightSwapInRTL` + // enabled. + bool wasLeftAndRightSwapped{false}; // Pixel density. Number of device pixels per density-independent pixel. Float pointScaleFactor{1.0}; // How much the children of the node actually overflow in each direction. diff --git a/packages/react-native/types/experimental.d.ts b/packages/react-native/types/experimental.d.ts index c58c9ffa10a..2e9f69d0c84 100644 --- a/packages/react-native/types/experimental.d.ts +++ b/packages/react-native/types/experimental.d.ts @@ -133,4 +133,13 @@ declare module '.' { */ paddingInlineStart?: DimensionValue | undefined; } + + export interface ViewProps { + /** + * 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/rn-tester/js/examples/View/ViewExample.js b/packages/rn-tester/js/examples/View/ViewExample.js index b9fe52ef7ba..bd278ccd7ed 100644 --- a/packages/rn-tester/js/examples/View/ViewExample.js +++ b/packages/rn-tester/js/examples/View/ViewExample.js @@ -342,6 +342,51 @@ class FlexGapExample extends React.Component<$ReadOnly<{|testID?: ?string|}>> { } } +function LayoutConformanceExample(): React.Node { + return ( + + + Unset + + + + Classic + + + + Strict + + + + ); +} + +function LayoutConformanceBox(): React.Node { + return ( + + + + + + ); +} + export default ({ title: 'View', documentationURL: 'https://reactnative.dev/docs/view', @@ -847,5 +892,10 @@ export default ({ ); }, }, + { + title: 'Layout conformance', + name: 'layout-conformance', + render: LayoutConformanceExample, + }, ], }: RNTesterModule);