mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Add experimental_layoutConformance prop (#38746)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/38746 This adds a view prop which controls the layout conformance of it and its transitive children (including non-view ShadowNodes). The implementation here is to traverse down before layout, updating any context-specific configuration. Normally at the time of layout, unchanged child ShadowNodes are already sealed. These must be mutated already if layout results change, and we do this today via Yoga node clone callback, where Yoga clones nodes where it will do layout work (but not all of them). We down-propagate configuration, but can terminate if we observe an already sealed node whose values match those of our current context, since any nodes under must also follow the same contexts (or any nested contexts of the sealed node). This means that calling `layoutTree()` on a mostly clean ShadowTree will cause configuration to not traverse most nodes. `Errata` and `PointScaleFactor` (DPI) are threaded to each Node's per-node config. The same values are retained when Yoga nodes are cloned (by cloning the ShadowNode). Since these are set before the first layout, config setting should only ever dirty layout [when layout-effecting values change](https://www.internalfb.com/code/fbsource/[1c95e981c740]/xplat/yoga/yoga/YGConfig.cpp?lines=13). The prop is prefixed with `experimental` because we are likely to make more breaking conformance fixes in the strict mode, and I'm not sure this is the final API yet. I was previously looking at a context-like component which is more challenging to implement. Changelog: [Internal] bypass-github-export-checks Reviewed By: rozele, sammy-SC Differential Revision: D47940100 fbshipit-source-id: f72e1c73f537e0312c2bba19ae8c2e882c82aced
This commit is contained in:
committed by
Facebook GitHub Bot
parent
0e69050612
commit
f965da97f3
@@ -98,6 +98,7 @@ export const __INTERNAL_VIEW_CONFIG: PartialViewConfig =
|
||||
focusable: true,
|
||||
overflow: true,
|
||||
backfaceVisibility: true,
|
||||
experimental_layoutConformance: true,
|
||||
},
|
||||
}
|
||||
: {
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -273,6 +273,8 @@ const validAttributesForNonEventProps = {
|
||||
position: true,
|
||||
|
||||
style: ReactNativeStyleAttributes,
|
||||
|
||||
experimental_layoutConformance: true,
|
||||
};
|
||||
|
||||
// Props for bubbling and direct events
|
||||
|
||||
@@ -335,6 +335,8 @@ const validAttributesForNonEventProps = {
|
||||
direction: true,
|
||||
|
||||
style: ReactNativeStyleAttributes,
|
||||
|
||||
experimental_layoutConformance: true,
|
||||
};
|
||||
|
||||
// Props for bubbling and direct events
|
||||
|
||||
@@ -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) \
|
||||
{ \
|
||||
|
||||
+6
@@ -151,6 +151,12 @@ public class ReactViewManager extends ReactClippingViewManager<ReactViewGroup> {
|
||||
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()) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -70,6 +70,8 @@ class BaseViewProps : public YogaStylableProps, public AccessibilityProps {
|
||||
|
||||
bool removeClippedSubviews{false};
|
||||
|
||||
LayoutConformance experimental_layoutConformance{};
|
||||
|
||||
Float elevation{}; /* Android-only */
|
||||
|
||||
#pragma mark - Convenience Methods
|
||||
|
||||
+121
-62
@@ -115,9 +115,11 @@ YogaLayoutableShadowNode::YogaLayoutableShadowNode(
|
||||
.yogaNode_.isDirty() == yogaNode_.isDirty() &&
|
||||
"Yoga node must inherit dirty flag.");
|
||||
|
||||
for (auto &child : getChildren()) {
|
||||
if (auto layoutableChild = traitCast<YogaLayoutableShadowNode>(child)) {
|
||||
yogaLayoutableChildren_.push_back(layoutableChild);
|
||||
if (!getTraits().check(ShadowNodeTraits::Trait::LeafYogaNode)) {
|
||||
for (auto &child : getChildren()) {
|
||||
if (auto layoutableChild = traitCast<YogaLayoutableShadowNode>(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<YogaLayoutableShadowNode const &>(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<YGConfigRef>(&child.yogaConfig_));
|
||||
|
||||
if (child.yogaTreeHasBeenConfigured_ &&
|
||||
childLayoutMetrics.pointScaleFactor == pointScaleFactor &&
|
||||
childLayoutMetrics.wasLeftAndRightSwapped == swapLeftAndRight &&
|
||||
childErrata == child.resolveErrata(errata)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (doesOwn(child)) {
|
||||
auto &mutableChild = const_cast<YogaLayoutableShadowNode &>(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<ViewShadowNode const *>(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<YogaLayoutableShadowNode &>(*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<YogaLayoutableShadowNode &>(*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 {
|
||||
|
||||
+28
-4
@@ -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
|
||||
|
||||
@@ -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<std::string>());
|
||||
if (!value.hasType<std::string>()) {
|
||||
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<float, yoga::enums::count<YGDimension>()> &dimensions) {
|
||||
return "{" + folly::to<std::string>(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
|
||||
|
||||
@@ -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 <typename T>
|
||||
struct CascadedRectangleEdges {
|
||||
using Counterpart = RectangleEdges<T>;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,6 +342,51 @@ class FlexGapExample extends React.Component<$ReadOnly<{|testID?: ?string|}>> {
|
||||
}
|
||||
}
|
||||
|
||||
function LayoutConformanceExample(): React.Node {
|
||||
return (
|
||||
<View style={{flexDirection: 'row', gap: 10}}>
|
||||
<View>
|
||||
<Text>Unset</Text>
|
||||
<LayoutConformanceBox />
|
||||
</View>
|
||||
<View experimental_layoutConformance="classic">
|
||||
<Text>Classic</Text>
|
||||
<LayoutConformanceBox />
|
||||
</View>
|
||||
<View experimental_layoutConformance="strict">
|
||||
<Text>Strict</Text>
|
||||
<LayoutConformanceBox />
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
function LayoutConformanceBox(): React.Node {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: 'blue',
|
||||
width: 100,
|
||||
height: 100,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
}}>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
}}>
|
||||
<View
|
||||
style={{
|
||||
height: 50,
|
||||
backgroundColor: 'red',
|
||||
flexGrow: 1,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user