Skip to content
Open
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
583 changes: 583 additions & 0 deletions apps/src/tests/issue-tests/Test4777.tsx

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions apps/src/tests/issue-tests/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ export { default as Test4618 } from './Test4618'; // [E2E skipped]: tvOS Menu ke
export { default as Test4709 } from './Test4709';
export { default as Test4725 } from './Test4725';
export { default as Test4757 } from './Test4757';
export { default as Test4777 } from './Test4777';
export { default as Test4872 } from './Test4872';
export { default as TestScreenAnimation } from './TestScreenAnimation';
// The following test was meant to demo the "go back" gesture using Reanimated
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,13 @@ class RNSFormSheetHostComponentDescriptor final
auto &concreteShadowNode =
static_cast<RNSFormSheetHostShadowNode &>(shadowNode);

react_native_assert(
dynamic_cast<YogaLayoutableShadowNode *>(&concreteShadowNode));
auto &layoutableShadowNode =
static_cast<YogaLayoutableShadowNode &>(concreteShadowNode);

auto state = std::static_pointer_cast<
const RNSFormSheetHostShadowNode::ConcreteState>(shadowNode.getState());

auto stateData = state->getData();

if (stateData.frameSize.width > 0 && stateData.frameSize.height > 0) {
layoutableShadowNode.setSize(
concreteShadowNode.setSizeIfChanged(
Size{stateData.frameSize.width, stateData.frameSize.height});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,20 @@
#include <react/renderer/components/rnscreens/Props.h>
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
#include "RNSFormSheetHostState.h"
#include "utils/LayoutFromState.h"

namespace facebook::react {

JSI_EXPORT extern const char RNSFormSheetHostComponentName[];

class JSI_EXPORT RNSFormSheetHostShadowNode final
: public ConcreteViewShadowNode<
: public rnscreens::SizeFromStateShadowNode<ConcreteViewShadowNode<
RNSFormSheetHostComponentName,
RNSFormSheetHostProps,
RNSFormSheetHostEventEmitter,
RNSFormSheetHostState> {
RNSFormSheetHostState>> {
public:
using ConcreteViewShadowNode::ConcreteViewShadowNode;
using SizeFromStateShadowNode::SizeFromStateShadowNode;

static ShadowNodeTraits BaseTraits() {
auto traits = ConcreteViewShadowNode::BaseTraits();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,13 @@ class RNSSplitScreenComponentDescriptor final
auto &splitScreenShadowNode =
static_cast<RNSSplitScreenShadowNode &>(shadowNode);

react_native_assert(
dynamic_cast<YogaLayoutableShadowNode *>(&splitScreenShadowNode));
auto &layoutableShadowNode =
static_cast<YogaLayoutableShadowNode &>(splitScreenShadowNode);

auto state =
std::static_pointer_cast<const RNSSplitScreenShadowNode::ConcreteState>(
shadowNode.getState());
auto stateData = state->getData();

if (stateData.frameSize.width != 0 && stateData.frameSize.height != 0) {
layoutableShadowNode.setSize(stateData.frameSize);
splitScreenShadowNode.setSizeIfChanged(stateData.frameSize);
}

ConcreteComponentDescriptor::adopt(shadowNode);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
#include <react/renderer/core/LayoutContext.h>
#include "RNSSplitScreenState.h"
#include "utils/LayoutFromState.h"

namespace facebook::react {

Expand All @@ -18,9 +19,10 @@ using ConcreteViewShadowNodeSuperType = ConcreteViewShadowNode<
RNSSplitScreenState>;

class JSI_EXPORT RNSSplitScreenShadowNode final
: public ConcreteViewShadowNodeSuperType {
: public rnscreens::SizeFromStateShadowNode<
ConcreteViewShadowNodeSuperType> {
public:
using ConcreteViewShadowNode::ConcreteViewShadowNode;
using SizeFromStateShadowNode::SizeFromStateShadowNode;
using StateData = ConcreteViewShadowNode::ConcreteStateData;

#pragma mark - ShadowNode overrides
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,22 @@ class RNSStackHeaderConfigComponentDescriptor final
dynamic_cast<RNSStackHeaderConfigShadowNode *>(&shadowNode));
auto &configShadowNode =
static_cast<RNSStackHeaderConfigShadowNode &>(shadowNode);
react_native_assert(
dynamic_cast<YogaLayoutableShadowNode *>(&configShadowNode));
auto &layoutableShadowNode =
static_cast<YogaLayoutableShadowNode &>(configShadowNode);

auto state = std::static_pointer_cast<
const RNSStackHeaderConfigShadowNode::ConcreteState>(
shadowNode.getState());
auto stateData = state->getData();

if (stateData.frameSize.width != 0 && stateData.frameSize.height != 0) {
layoutableShadowNode.setSize(
configShadowNode.setSizeIfChanged(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[correctness, high] v5 iOS header config and header item origins go stale.

RNSStackHeaderConfigShadowNode::layout() and RNSStackHeaderItemShadowNode::layout() (iOS) both set the origin from stateData.contentOffset. Items are children of the config, so their corrections relied on this setSize dirtying the config on every clone.

Scenario: an item moves within the bar but keeps its size (a neighbour item is added or removed, the title changes, iOS 26 regroups items). RNSStackHeaderItemShadowStateProxy sends a new origin, no node is dirty, layout is skipped, and measure / press-rect checks are off by the move distance. The same applies to the config itself when only the navbar origin changes.


Generated by Claude Code

Size{stateData.frameSize.width, stateData.frameSize.height});
}

#if !defined(ANDROID)
// `layout()` takes the frame origin from state, so re-run it when that
// origin changes.
rnscreens::dirtyLayoutIfFrameOriginStale(configShadowNode);

std::weak_ptr<void> imageLoader =
contextContainer_->at<std::shared_ptr<void>>("RCTImageLoader");
configShadowNode.setImageLoader(imageLoader);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@ void RNSStackHeaderConfigShadowNode::layout(LayoutContext layoutContext) {
applyFrameCorrections();
}

Point RNSStackHeaderConfigShadowNode::correctFrameOrigin(
Point /*origin*/) const {
return getStateData().contentOffset;
}

void RNSStackHeaderConfigShadowNode::applyFrameCorrections() {
ensureUnsealed();

const auto &stateData = getStateData();
layoutMetrics_.frame.origin.x = stateData.contentOffset.x;
layoutMetrics_.frame.origin.y = stateData.contentOffset.y;
layoutMetrics_.frame.origin = correctFrameOrigin(layoutMetrics_.frame.origin);
}

void RNSStackHeaderConfigShadowNode::setImageLoader(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
#include <react/renderer/core/LayoutContext.h>
#include "RNSStackHeaderConfigState.h"
#include "utils/LayoutFromState.h"

namespace facebook::react {

JSI_EXPORT extern const char RNSStackHeaderConfigComponentName[];

class JSI_EXPORT RNSStackHeaderConfigShadowNode final
: public ConcreteViewShadowNode<
: public rnscreens::SizeFromStateShadowNode<ConcreteViewShadowNode<
RNSStackHeaderConfigComponentName,
#if !defined(ANDROID)
RNSStackHeaderConfigIOSProps,
Expand All @@ -21,16 +22,22 @@ class JSI_EXPORT RNSStackHeaderConfigShadowNode final
RNSStackHeaderConfigAndroidProps,
RNSStackHeaderConfigAndroidEventEmitter,
#endif // !defined(ANDROID)
RNSStackHeaderConfigState> {
RNSStackHeaderConfigState>> {
public:
using ConcreteViewShadowNode::ConcreteViewShadowNode;
using SizeFromStateShadowNode::SizeFromStateShadowNode;
using StateData = ConcreteViewShadowNode::ConcreteStateData;

#ifdef ANDROID
Point getContentOriginOffset(bool includeTransform) const override;
#else // ANDROID
void layout(LayoutContext layoutContext) override;

/**
* The frame origin `layout()` sets, given the one computed by Yoga. The
* component descriptor compares it with the current layout metrics.
*/
Point correctFrameOrigin(Point origin) const;

void setImageLoader(std::weak_ptr<void> imageLoader);

private:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,27 @@
#include <react/renderer/components/rnscreens/Props.h>
#include <react/renderer/core/ConcreteComponentDescriptor.h>
#include "RNSStackHeaderItemShadowNode.h"
#include "utils/LayoutFromState.h"

namespace facebook::react {

class RNSStackHeaderItemComponentDescriptor final
: public ConcreteComponentDescriptor<RNSStackHeaderItemShadowNode> {
public:
using ConcreteComponentDescriptor::ConcreteComponentDescriptor;

void adopt(ShadowNode &shadowNode) const override {
react_native_assert(
dynamic_cast<RNSStackHeaderItemShadowNode *>(&shadowNode));
auto &itemShadowNode =
static_cast<RNSStackHeaderItemShadowNode &>(shadowNode);

// `layout()` takes the frame origin from state, so re-run it when that
// origin changes.
rnscreens::dirtyLayoutIfFrameOriginStale(itemShadowNode);

ConcreteComponentDescriptor::adopt(shadowNode);
}
};

} // namespace facebook::react
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ void RNSStackHeaderItemShadowNode::layout(LayoutContext layoutContext) {
applyFrameCorrections();
}

Point RNSStackHeaderItemShadowNode::correctFrameOrigin(Point /*origin*/) const {
return getStateData().contentOffset;
}

void RNSStackHeaderItemShadowNode::applyFrameCorrections() {
ensureUnsealed();

const auto &stateData = getStateData();
layoutMetrics_.frame.origin.x = stateData.contentOffset.x;
layoutMetrics_.frame.origin.y = stateData.contentOffset.y;
layoutMetrics_.frame.origin = correctFrameOrigin(layoutMetrics_.frame.origin);
}

} // namespace facebook::react
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ class JSI_EXPORT RNSStackHeaderItemShadowNode final

void layout(LayoutContext layoutContext) override;

/**
* The frame origin `layout()` sets, given the one computed by Yoga: native
* positions the item in the navigation bar and reports where. The component
* descriptor compares it with the current layout metrics.
*/
Point correctFrameOrigin(Point origin) const;

private:
void applyFrameCorrections();
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,14 @@ class RNSStackScreenComponentDescriptor final
react_native_assert(dynamic_cast<RNSStackScreenShadowNode *>(&shadowNode));
auto &screenShadowNode =
static_cast<RNSStackScreenShadowNode &>(shadowNode);
react_native_assert(
dynamic_cast<YogaLayoutableShadowNode *>(&screenShadowNode));
auto &layoutableShadowNode =
static_cast<YogaLayoutableShadowNode &>(screenShadowNode);

auto state =
std::static_pointer_cast<const RNSStackScreenShadowNode::ConcreteState>(
shadowNode.getState());
auto stateData = state->getData();

if (stateData.frameSize.width != 0 && stateData.frameSize.height != 0) {
layoutableShadowNode.setSize(
screenShadowNode.setSizeIfChanged(
Size{stateData.frameSize.width, stateData.frameSize.height});
}
#endif // ANDROID
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,20 @@
#include <react/renderer/components/rnscreens/Props.h>
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
#include "RNSStackScreenState.h"
#include "utils/LayoutFromState.h"

namespace facebook::react {

JSI_EXPORT extern const char RNSStackScreenComponentName[];

class JSI_EXPORT RNSStackScreenShadowNode final
: public ConcreteViewShadowNode<
: public rnscreens::SizeFromStateShadowNode<ConcreteViewShadowNode<
RNSStackScreenComponentName,
RNSStackScreenProps,
RNSStackScreenEventEmitter,
RNSStackScreenState> {
RNSStackScreenState>> {
public:
using ConcreteViewShadowNode::ConcreteViewShadowNode;
using SizeFromStateShadowNode::SizeFromStateShadowNode;
using StateData = ConcreteViewShadowNode::ConcreteStateData;

#pragma mark - ShadowNode overrides
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class RNSTabsBottomAccessoryComponentDescriptor final
auto stateData = state->getData();

if (stateData.frameSize.width != 0 && stateData.frameSize.height != 0) {
tabsBottomAccessoryShadowNode.setSize(stateData.frameSize);
tabsBottomAccessoryShadowNode.setSizeIfChanged(stateData.frameSize);
}

ConcreteComponentDescriptor::adopt(shadowNode);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,20 @@
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
#include <react/renderer/core/LayoutContext.h>
#include "RNSTabsBottomAccessoryState.h"
#include "utils/LayoutFromState.h"

namespace facebook::react {

JSI_EXPORT extern const char RNSTabsBottomAccessoryComponentName[];

class JSI_EXPORT RNSTabsBottomAccessoryShadowNode final
: public ConcreteViewShadowNode<
: public rnscreens::SizeFromStateShadowNode<ConcreteViewShadowNode<
RNSTabsBottomAccessoryComponentName,
RNSTabsBottomAccessoryProps,
RNSTabsBottomAccessoryEventEmitter,
RNSTabsBottomAccessoryState> {
RNSTabsBottomAccessoryState>> {
public:
using ConcreteViewShadowNode::ConcreteViewShadowNode;
using SizeFromStateShadowNode::SizeFromStateShadowNode;
using StateData = ConcreteViewShadowNode::ConcreteStateData;

#pragma mark - ShadowNode overrides
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,13 @@ class RNSModalScreenComponentDescriptor final
auto &screenShadowNode =
static_cast<RNSModalScreenShadowNode &>(shadowNode);

react_native_assert(
dynamic_cast<YogaLayoutableShadowNode *>(&screenShadowNode));
auto &layoutableShadowNode =
dynamic_cast<YogaLayoutableShadowNode &>(screenShadowNode);

auto state =
std::static_pointer_cast<const RNSModalScreenShadowNode::ConcreteState>(
shadowNode.getState());
auto stateData = state->getData();

if (stateData.frameSize.width != 0 && stateData.frameSize.height != 0) {
layoutableShadowNode.setSize(
screenShadowNode.setSizeIfChanged(
Size{stateData.frameSize.width, stateData.frameSize.height});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <react/renderer/components/rnscreens/EventEmitters.h>
#include <react/renderer/components/rnscreens/Props.h>
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
#include "../utils/LayoutFromState.h"
#include "RNSScreenState.h"

namespace facebook {
Expand All @@ -12,13 +13,13 @@ namespace react {
JSI_EXPORT extern const char RNSModalScreenComponentName[];

class JSI_EXPORT RNSModalScreenShadowNode final
: public ConcreteViewShadowNode<
: public rnscreens::SizeFromStateShadowNode<ConcreteViewShadowNode<
RNSModalScreenComponentName,
RNSScreenProps,
RNSScreenEventEmitter,
RNSScreenState> {
RNSScreenState>> {
public:
using ConcreteViewShadowNode::ConcreteViewShadowNode;
using SizeFromStateShadowNode::SizeFromStateShadowNode;

Point getContentOriginOffset(bool includeTransform) const override;
static ShadowNodeTraits BaseTraits() {
Expand Down
Loading