Skip to content

Commit 8b625ed

Browse files
committed
fix: perform operations inside shadowTreeWillCommit hook
1 parent 9c48c75 commit 8b625ed

File tree

9 files changed

+258
-32
lines changed

9 files changed

+258
-32
lines changed

common/cpp/react/renderer/components/TeleportViewSpec/PortalShadowRegistry.cpp

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,51 @@
22

33
#include <string>
44

5-
namespace facebook::react {
5+
namespace facebook::react
6+
{
67

78
void PortalShadowRegistry::registerHost(
89
const std::string &name,
9-
const LayoutableShadowNode *host) {
10+
const LayoutableShadowNode *host)
11+
{
1012
std::lock_guard<std::mutex> lock(mutex_);
1113
hosts_[name] = host;
1214
}
1315

14-
void PortalShadowRegistry::unregisterHost(const std::string &name) {
16+
void PortalShadowRegistry::unregisterHost(const std::string &name)
17+
{
1518
std::lock_guard<std::mutex> lock(mutex_);
1619
hosts_.erase(name);
1720
}
1821

1922
const LayoutableShadowNode *PortalShadowRegistry::getHost(
20-
const std::string &name) const {
23+
const std::string &name) const
24+
{
2125
std::lock_guard<std::mutex> lock(mutex_);
2226
auto it = hosts_.find(name);
23-
if (it != hosts_.end()) {
27+
if (it != hosts_.end())
28+
{
2429
return it->second;
2530
}
2631
return nullptr;
2732
}
2833

34+
void PortalShadowRegistry::registerPortal(const ShadowNodeFamily *family)
35+
{
36+
std::lock_guard<std::mutex> lock(mutex_);
37+
portalFamilies_.insert(family);
38+
}
39+
40+
void PortalShadowRegistry::unregisterPortal(const ShadowNodeFamily *family)
41+
{
42+
std::lock_guard<std::mutex> lock(mutex_);
43+
portalFamilies_.erase(family);
44+
}
45+
46+
std::unordered_set<const ShadowNodeFamily*> PortalShadowRegistry::getPortalFamilies() const
47+
{
48+
std::lock_guard<std::mutex> lock(mutex_);
49+
return portalFamilies_;
50+
}
51+
2952
} // namespace facebook::react

common/cpp/react/renderer/components/TeleportViewSpec/PortalShadowRegistry.h

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,18 @@
44

55
#include <string>
66
#include <unordered_map>
7+
#include <unordered_set>
78
#include <mutex>
89
#include <memory>
910

10-
namespace facebook::react {
11+
namespace facebook::react
12+
{
1113

12-
class PortalShadowRegistry {
13-
public:
14-
static PortalShadowRegistry &getInstance() {
14+
class PortalShadowRegistry
15+
{
16+
public:
17+
static PortalShadowRegistry &getInstance()
18+
{
1519
static PortalShadowRegistry instance;
1620
return instance;
1721
}
@@ -20,10 +24,16 @@ namespace facebook::react {
2024
void unregisterHost(const std::string &name);
2125
const LayoutableShadowNode *getHost(const std::string &name) const;
2226

23-
private:
27+
// Portal node registration (using ShadowNodeFamily for stable identity)
28+
void registerPortal(const ShadowNodeFamily *family);
29+
void unregisterPortal(const ShadowNodeFamily *family);
30+
std::unordered_set<const ShadowNodeFamily *> getPortalFamilies() const;
31+
32+
private:
2433
PortalShadowRegistry() = default;
2534
mutable std::mutex mutex_;
2635
std::unordered_map<std::string, const LayoutableShadowNode *> hosts_;
36+
std::unordered_set<const ShadowNodeFamily *> portalFamilies_;
2737
};
2838

2939
} // namespace facebook::react

common/cpp/react/renderer/components/TeleportViewSpec/RNTPortalViewComponentDescriptor.h

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,37 +8,34 @@
88
#include <react/renderer/core/ConcreteComponentDescriptor.h>
99
#include <react/renderer/components/view/YogaLayoutableShadowNode.h>
1010

11-
namespace facebook::react {
11+
namespace facebook::react
12+
{
1213

1314
class PortalViewComponentDescriptor final
14-
: public ConcreteComponentDescriptor<PortalViewShadowNode> {
15-
public:
15+
: public ConcreteComponentDescriptor<PortalViewShadowNode>
16+
{
17+
public:
1618
using ConcreteComponentDescriptor::ConcreteComponentDescriptor;
1719

18-
void adopt(ShadowNode &shadowNode) const override {
20+
void adopt(ShadowNode &shadowNode) const override
21+
{
1922
react_native_assert(dynamic_cast<PortalViewShadowNode *>(&shadowNode));
2023

2124
auto &portalViewShadowNode = static_cast<PortalViewShadowNode &>(shadowNode);
2225
auto &props = static_cast<const PortalViewProps &>(*portalViewShadowNode.getProps());
2326

24-
// If this portal is teleported to a host, apply the host's layout constraints
25-
if (!props.hostName.empty()) {
26-
const LayoutableShadowNode *host =
27-
PortalShadowRegistry::getInstance().getHost(props.hostName);
28-
29-
if (host) {
30-
auto &yogaPortal = static_cast<YogaLayoutableShadowNode &>(portalViewShadowNode);
31-
32-
// Set position to absolute because:
33-
// - when the view is teleported, we need to "free" its original space
34-
// - we need to stretch the view beyond the parent layout constraints
35-
yogaPortal.setPositionType(YGPositionTypeAbsolute);
36-
37-
auto hostLayoutMetrics = host->getLayoutMetrics();
38-
39-
// Update view dimensions
40-
portalViewShadowNode.setDimensionsFromHost(hostLayoutMetrics.frame.size);
41-
}
27+
// Register this portal in the registry if it has a hostName
28+
// The actual size/position adjustments will be applied in the commit hook
29+
if (!props.hostName.empty())
30+
{
31+
PortalShadowRegistry::getInstance().registerPortal(
32+
&portalViewShadowNode.getFamily());
33+
}
34+
else
35+
{
36+
// Unregister if hostName is empty (portal is not teleported)
37+
PortalShadowRegistry::getInstance().unregisterPortal(
38+
&portalViewShadowNode.getFamily());
4239
}
4340

4441
ConcreteComponentDescriptor::adopt(shadowNode);
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
//
2+
// TeleportCommitHook.cpp
3+
// Pods
4+
//
5+
// Created by Kiryl Ziusko on 07/10/2025.
6+
//
7+
8+
#include "TeleportCommitHook.h"
9+
#include "RNTPortalViewShadowNode.h"
10+
#include <react/renderer/components/view/YogaLayoutableShadowNode.h>
11+
12+
using namespace facebook::react;
13+
14+
namespace teleport
15+
{
16+
17+
TeleportCommitHook::TeleportCommitHook(const std::shared_ptr<UIManager> &uiManager) : uiManager_(uiManager)
18+
{
19+
uiManager_->registerCommitHook(*this);
20+
}
21+
22+
TeleportCommitHook::~TeleportCommitHook() noexcept
23+
{
24+
uiManager_->unregisterCommitHook(*this);
25+
}
26+
27+
RootShadowNode::Unshared TeleportCommitHook::shadowTreeWillCommit(
28+
ShadowTree const &,
29+
RootShadowNode::Shared const &oldRootShadowNode,
30+
RootShadowNode::Unshared const &newRootShadowNode) noexcept
31+
{
32+
// Start with the new root
33+
auto rootNode = newRootShadowNode;
34+
35+
// Get all registered portal families
36+
auto portalFamilies = PortalShadowRegistry::getInstance().getPortalFamilies();
37+
38+
// Iterate over each portal that needs size/position adjustment
39+
for (const auto family : portalFamilies)
40+
{
41+
auto newRoot = rootNode->cloneTree(
42+
*family,
43+
[](ShadowNode const &oldShadowNode)
44+
{
45+
// Clone the node
46+
auto clone = oldShadowNode.clone({});
47+
48+
// Cast to PortalViewShadowNode to access props
49+
auto portalNode = std::dynamic_pointer_cast<PortalViewShadowNode>(clone);
50+
if (portalNode)
51+
{
52+
auto &props = static_cast<const PortalViewProps &>(*portalNode->getProps());
53+
54+
// If this portal has a hostName, apply size and position
55+
if (!props.hostName.empty())
56+
{
57+
auto host = PortalShadowRegistry::getInstance().getHost(props.hostName);
58+
59+
if (host)
60+
{
61+
auto hostLayoutMetrics = host->getLayoutMetrics();
62+
auto hostSize = hostLayoutMetrics.frame.size;
63+
64+
// Only apply if host has valid (non-zero) size
65+
if (hostSize.width > 0 && hostSize.height > 0)
66+
{
67+
auto layoutableNode = std::dynamic_pointer_cast<YogaLayoutableShadowNode>(clone);
68+
if (layoutableNode)
69+
{
70+
// Set position to absolute to free the original space
71+
layoutableNode->setPositionType(YGPositionTypeAbsolute);
72+
// Set size to match the host
73+
layoutableNode->setSize(hostSize);
74+
}
75+
}
76+
}
77+
}
78+
}
79+
80+
return clone;
81+
});
82+
83+
if (newRoot)
84+
{
85+
rootNode = std::static_pointer_cast<RootShadowNode>(newRoot);
86+
}
87+
}
88+
89+
return std::const_pointer_cast<RootShadowNode>(rootNode);
90+
}
91+
92+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//
2+
// TeleportCommitHook.h
3+
// Pods
4+
//
5+
// Created by Kiryl Ziusko on 07/10/2025.
6+
//
7+
8+
#pragma once
9+
10+
#include "PortalShadowRegistry.h"
11+
#include <react/renderer/uimanager/UIManager.h>
12+
#include <react/renderer/uimanager/UIManagerCommitHook.h>
13+
14+
using namespace facebook::react;
15+
16+
namespace teleport {
17+
18+
class TeleportCommitHook : public UIManagerCommitHook {
19+
public:
20+
TeleportCommitHook(const std::shared_ptr<UIManager> &uiManager);
21+
22+
~TeleportCommitHook() noexcept override;
23+
24+
void commitHookWasRegistered(UIManager const &) noexcept override {}
25+
26+
void commitHookWasUnregistered(UIManager const &) noexcept override {}
27+
28+
RootShadowNode::Unshared shadowTreeWillCommit(
29+
ShadowTree const &shadowTree,
30+
RootShadowNode::Shared const &oldRootShadowNode,
31+
RootShadowNode::Unshared const &newRootShadowNode)
32+
noexcept override;
33+
34+
private:
35+
std::shared_ptr<UIManager> uiManager_;
36+
};
37+
38+
}

ios/Teleport.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//
2+
// Teleport.h
3+
// Pods
4+
//
5+
// Created by Kiryl Ziusko on 07/10/2025.
6+
//
7+
8+
#import <TeleportViewSpec/TeleportViewSpec.h>
9+
10+
@interface Teleport : NSObject <NativeTeleportSpec>
11+
12+
@end

ios/Teleport.mm

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//
2+
// Teleport.mm
3+
// Pods
4+
//
5+
// Created by Kiryl Ziusko on 07/10/2025.
6+
//
7+
8+
#import "Teleport.h"
9+
10+
#import <React/RCTScheduler.h>
11+
#import <React/RCTSurfacePresenter.h>
12+
#import <React/RCTSurfacePresenterStub.h>
13+
#import <react/renderer/components/TeleportViewSpec/TeleportCommitHook.h>
14+
15+
@implementation Teleport {
16+
__weak RCTSurfacePresenter* _surfacePresenter;
17+
std::shared_ptr<teleport::TeleportCommitHook> commitHook_;
18+
}
19+
20+
- (void)install
21+
{
22+
commitHook_ = std::make_shared<teleport::TeleportCommitHook>(_surfacePresenter.scheduler.uiManager);
23+
}
24+
25+
- (void)setSurfacePresenter:(id<RCTSurfacePresenterStub>)surfacePresenter
26+
{
27+
_surfacePresenter = surfacePresenter;
28+
}
29+
30+
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
31+
(const facebook::react::ObjCTurboModule::InitParams &)params
32+
{
33+
return std::make_shared<facebook::react::NativeTeleportSpecJSI>(params);
34+
}
35+
36+
+ (NSString *)moduleName
37+
{
38+
return @"Teleport";
39+
}
40+
41+
@end

src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import TeleportModule from "./specs/NativeTeleport";
2+
3+
TeleportModule?.install();
4+
15
export { default as PortalHost } from "./components/PortalHost";
26
export { default as Portal } from "./components/Portal";
37
export { default as PortalProvider } from "./PortalProvider";

src/specs/NativeTeleport.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { TurboModuleRegistry } from "react-native";
2+
3+
import type { TurboModule } from "react-native";
4+
5+
export interface Spec extends TurboModule {
6+
install(): void;
7+
}
8+
9+
export default TurboModuleRegistry.get<Spec>("Teleport");

0 commit comments

Comments
 (0)