Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* 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
* @fantom_flags enableFlexboxAutoMinSizeInStrictMode:*
* @format
*/

import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment';

import type {HostInstance} from 'react-native';

import * as Fantom from '@react-native/fantom';
import nullthrows from 'nullthrows';
import * as React from 'react';
import {createRef} from 'react';
import {
View,
experimental_LayoutConformance as LayoutConformance,
} from 'react-native';

type Mode = 'strict' | 'compatibility';

function renderRowWidth(mode: Mode, outerMode?: Mode): number {
const root = Fantom.createRoot();
const rowRef = createRef<HostInstance>();

const content = (
<LayoutConformance mode={mode}>
<View
collapsable={false}
style={{
width: 60,
height: 60,
flexDirection: 'row',
alignItems: 'center',
}}>
{/* With YGErrataStretchFlexBasis this row grows to its parent's
width, even though its content takes no space. */}
<View collapsable={false} ref={rowRef} style={{flexDirection: 'row'}}>
<View collapsable={false} style={{height: 30, flexGrow: 1}} />
</View>
</View>
</LayoutConformance>
);

Fantom.runTask(() => {
root.render(
outerMode != null ? (
<LayoutConformance mode={outerMode}>{content}</LayoutConformance>
) : (
content
),
);
});

const width = nullthrows(rowRef.current).getBoundingClientRect().width;
root.destroy();
return width;
}

test('strict mode lays out its subtree without the stretch flex-basis errata', () => {
expect(renderRowWidth('strict')).toBe(0);
});

// Compatibility is also the default, so check it under a strict subtree.
test('compatibility mode inside strict mode brings the stretch flex-basis errata back', () => {
expect(renderRowWidth('compatibility', 'strict')).toBe(60);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* 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.
*/

#include "LayoutConformanceShadowNode.h"

namespace facebook::react {

// NOLINTNEXTLINE(facebook-hte-CArray,modernize-avoid-c-arrays)
const char LayoutConformanceShadowNodeComponentName[] = "LayoutConformance";

} // namespace facebook::react
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
namespace facebook::react {

// NOLINTNEXTLINE(facebook-hte-CArray,modernize-avoid-c-arrays)
constexpr const char LayoutConformanceShadowNodeComponentName[] = "LayoutConformance";
extern const char LayoutConformanceShadowNodeComponentName[];

class LayoutConformanceShadowNode final : public ConcreteShadowNode<
LayoutConformanceShadowNodeComponentName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -560,9 +560,15 @@ void YogaLayoutableShadowNode::configureYogaTree(
}

YGErrata YogaLayoutableShadowNode::resolveErrata(YGErrata defaultErrata) const {
if (auto layoutConformanceNode =
dynamic_cast<const LayoutConformanceShadowNode*>(this)) {
switch (layoutConformanceNode->getConcreteProps().mode) {
// This runs for every child on every layout, and almost no node is a
// LayoutConformance node, so a failing `dynamic_cast` would be the common
// case. The class is final and its component name is defined once, in
// LayoutConformanceShadowNode.cpp, so comparing component handles is
// equivalent.
if (getComponentHandle() == LayoutConformanceShadowNode::Handle()) {
const auto& layoutConformanceNode =
static_cast<const LayoutConformanceShadowNode&>(*this);
switch (layoutConformanceNode.getConcreteProps().mode) {
case LayoutConformance::Strict:
// CSS Flexbox §4.5 automatic minimum sizing is the spec-compliant
// behaviour, so strict conformance should adopt it — but its probe
Expand Down Expand Up @@ -829,7 +835,12 @@ Rect YogaLayoutableShadowNode::getContentBounds() const {

auto layoutMetricsWithOverflowInset = childNode.getLayoutMetrics();
if (layoutMetricsWithOverflowInset.displayType != DisplayType::None) {
auto viewChildNode = dynamic_cast<const ViewShadowNode*>(&childNode);
// ViewShadowNode is final, so comparing component handles is equivalent
// to a `dynamic_cast`, without its cost for every non-View child.
auto viewChildNode =
childNode.getComponentHandle() == ViewShadowNode::Handle()
? static_cast<const ViewShadowNode*>(&childNode)
: nullptr;
auto hitSlop = viewChildNode != nullptr
? viewChildNode->getConcreteProps().hitSlop
: EdgeInsets{};
Expand Down Expand Up @@ -951,8 +962,8 @@ float YogaLayoutableShadowNode::yogaNodeBaselineCallbackConnector(

YogaLayoutableShadowNode& YogaLayoutableShadowNode::shadowNodeFromContext(
YGNodeConstRef yogaNode) {
return dynamic_cast<YogaLayoutableShadowNode&>(
*static_cast<ShadowNode*>(YGNodeGetContext(yogaNode)));
// The context is only set by the constructors of this class, to `this`.
return *static_cast<YogaLayoutableShadowNode*>(YGNodeGetContext(yogaNode));
}

yoga::Config& YogaLayoutableShadowNode::initializeYogaConfig(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include <react/renderer/components/text/RawTextComponentDescriptor.h>
#include <react/renderer/components/text/SelectableParagraphComponentDescriptor.h>
#include <react/renderer/components/text/TextComponentDescriptor.h>
#include <react/renderer/components/view/LayoutConformanceComponentDescriptor.h>
#include <react/renderer/components/view/ViewComponentDescriptor.h>

namespace facebook::react {
Expand All @@ -27,6 +28,7 @@ inline ComponentRegistryFactory getDefaultComponentRegistryFactory()
static auto providerRegistry = []() {
auto providerRegistry = std::make_shared<ComponentDescriptorProviderRegistry>();
providerRegistry->add(concreteComponentDescriptorProvider<ImageComponentDescriptor>());
providerRegistry->add(concreteComponentDescriptorProvider<LayoutConformanceComponentDescriptor>());
providerRegistry->add(concreteComponentDescriptorProvider<ParagraphComponentDescriptor>());
providerRegistry->add(concreteComponentDescriptorProvider<ScrollViewComponentDescriptor>());
providerRegistry->add(concreteComponentDescriptorProvider<RawTextComponentDescriptor>());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,7 @@ struct facebook::hermes::ISetEventLoopControl : public facebook::jsi::ICast {
const char facebook::react::AndroidTextInputComponentName[];
const char facebook::react::ImageComponentName[];
const char facebook::react::ImageManagerKey[];
const char facebook::react::LayoutConformanceShadowNodeComponentName[];
const char facebook::react::ModalHostViewComponentName[];
const char facebook::react::ParagraphComponentName[];
const char facebook::react::RawTextComponentName[];
Expand All @@ -386,7 +387,6 @@ const char facebook::react::TextComponentName[];
const char facebook::react::ViewComponentName[];
const facebook::react::EventTag facebook::react::EMPTY_EVENT_TAG;
constexpr auto facebook::react::kSimpleThreadSafeCacheSizeCap;
constexpr char facebook::react::LayoutConformanceShadowNodeComponentName[];
constexpr char* const facebook::react::TextLayoutManagerKey;
constexpr facebook::react::ReactNativeVersionType facebook::react::ReactNativeVersion;
constexpr facebook::react::Tag facebook::react::kNoTag;
Expand Down
2 changes: 1 addition & 1 deletion scripts/cxx-api/api-snapshots/ReactAppleFrameworksCxx.api
Original file line number Diff line number Diff line change
Expand Up @@ -3221,6 +3221,7 @@ struct facebook::hermes::ISetEventLoopControl : public facebook::jsi::ICast {

const char facebook::react::ImageComponentName[];
const char facebook::react::ImageManagerKey[];
const char facebook::react::LayoutConformanceShadowNodeComponentName[];
const char facebook::react::ModalHostViewComponentName[];
const char facebook::react::ParagraphComponentName[];
const char facebook::react::RawTextComponentName[];
Expand All @@ -3232,7 +3233,6 @@ const char facebook::react::TextInputComponentName[];
const char facebook::react::ViewComponentName[];
const facebook::react::EventTag facebook::react::EMPTY_EVENT_TAG;
constexpr auto facebook::react::kSimpleThreadSafeCacheSizeCap;
constexpr char facebook::react::LayoutConformanceShadowNodeComponentName[];
constexpr char* const facebook::react::TextLayoutManagerKey;
constexpr facebook::react::ReactNativeVersionType facebook::react::ReactNativeVersion;
constexpr facebook::react::Tag facebook::react::kNoTag;
Expand Down
2 changes: 1 addition & 1 deletion scripts/cxx-api/api-snapshots/ReactCommonFrameworksCxx.api
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ struct facebook::hermes::ISetEventLoopControl : public facebook::jsi::ICast {

const char facebook::react::ImageComponentName[];
const char facebook::react::ImageManagerKey[];
const char facebook::react::LayoutConformanceShadowNodeComponentName[];
const char facebook::react::ModalHostViewComponentName[];
const char facebook::react::ParagraphComponentName[];
const char facebook::react::RawTextComponentName[];
Expand All @@ -83,7 +84,6 @@ const char facebook::react::TextComponentName[];
const char facebook::react::ViewComponentName[];
const facebook::react::EventTag facebook::react::EMPTY_EVENT_TAG;
constexpr auto facebook::react::kSimpleThreadSafeCacheSizeCap;
constexpr char facebook::react::LayoutConformanceShadowNodeComponentName[];
constexpr char* const facebook::react::TextLayoutManagerKey;
constexpr facebook::react::ReactNativeVersionType facebook::react::ReactNativeVersion;
constexpr facebook::react::Tag facebook::react::kNoTag;
Expand Down
Loading