Skip to content

Commit e5bebbc

Browse files
zeyapfacebook-github-bot
authored andcommitted
Add UIManagerViewTransitionDelegate interface and View Transition APIs (facebook#55742)
Summary: Changelog: [General] [Added] - Add UIManagerViewTransitionDelegate interface and View Transition APIs Adds `UIManagerViewTransitionDelegate` interface and View Transition APIs to UIManager, enabling integration with React reconciler's `<ViewTransition/>` component. Exposes JSI bindings in UIManagerBinding, which will be consumed by react fabric renderer (facebook/react#35764) - `measureInstance` - returns layout metrics and calls `captureLayoutMetricsFromRoot` to capture snapshot - `applyViewTransitionName` / `cancelViewTransitionName` / `restoreViewTransitionName` - manage transition name registration - `startViewTransition` - orchestrates transition lifecycle with mutation/ready/complete callbacks The delegate methods are gated by the `viewTransitionEnabled` feature flag. Reviewed By: sammy-SC Differential Revision: D92537193
1 parent 45de710 commit e5bebbc

File tree

4 files changed

+306
-0
lines changed

4 files changed

+306
-0
lines changed

packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,10 @@ void UIManager::sendAccessibilityEvent(
488488
}
489489
}
490490

491+
UIManagerViewTransitionDelegate* UIManager::getViewTransitionDelegate() const {
492+
return viewTransitionDelegate_;
493+
}
494+
491495
void UIManager::configureNextLayoutAnimation(
492496
jsi::Runtime& runtime,
493497
const RawValue& config,
@@ -708,6 +712,13 @@ void UIManager::setNativeAnimatedDelegate(
708712
nativeAnimatedDelegate_ = delegate;
709713
}
710714

715+
void UIManager::setViewTransitionDelegate(
716+
UIManagerViewTransitionDelegate* delegate) {
717+
if (ReactNativeFeatureFlags::viewTransitionEnabled()) {
718+
viewTransitionDelegate_ = delegate;
719+
}
720+
}
721+
711722
void UIManager::unstable_setAnimationBackend(
712723
std::shared_ptr<UIManagerAnimationBackend> animationBackend) {
713724
animationBackend_ = animationBackend;

packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include <react/renderer/uimanager/UIManagerAnimationDelegate.h>
2828
#include <react/renderer/uimanager/UIManagerDelegate.h>
2929
#include <react/renderer/uimanager/UIManagerNativeAnimatedDelegate.h>
30+
#include <react/renderer/uimanager/UIManagerViewTransitionDelegate.h>
3031
#include <react/renderer/uimanager/consistency/LazyShadowTreeRevisionConsistencyManager.h>
3132
#include <react/renderer/uimanager/consistency/ShadowTreeRevisionProvider.h>
3233
#include <react/renderer/uimanager/primitives.h>
@@ -74,6 +75,12 @@ class UIManager final : public ShadowTreeDelegate {
7475

7576
void setNativeAnimatedDelegate(std::weak_ptr<UIManagerNativeAnimatedDelegate> delegate);
7677

78+
/**
79+
* Sets and gets UIManager's ViewTransition API delegate.
80+
*/
81+
void setViewTransitionDelegate(UIManagerViewTransitionDelegate *delegate);
82+
UIManagerViewTransitionDelegate *getViewTransitionDelegate() const;
83+
7784
void animationTick() const;
7885

7986
void synchronouslyUpdateViewOnUIThread(Tag tag, const folly::dynamic &props);
@@ -242,6 +249,7 @@ class UIManager final : public ShadowTreeDelegate {
242249
UIManagerDelegate *delegate_{};
243250
UIManagerAnimationDelegate *animationDelegate_{nullptr};
244251
std::weak_ptr<UIManagerNativeAnimatedDelegate> nativeAnimatedDelegate_;
252+
UIManagerViewTransitionDelegate *viewTransitionDelegate_{nullptr};
245253

246254
const RuntimeExecutor runtimeExecutor_{};
247255
ShadowTreeRegistry shadowTreeRegistry_{};

packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -876,6 +876,256 @@ jsi::Value UIManagerBinding::get(
876876
});
877877
}
878878

879+
if (methodName == "measureInstance") {
880+
auto paramCount = 1;
881+
return jsi::Function::createFromHostFunction(
882+
runtime,
883+
name,
884+
paramCount,
885+
[uiManager, methodName, paramCount](
886+
jsi::Runtime& runtime,
887+
const jsi::Value& /*thisValue*/,
888+
const jsi::Value* arguments,
889+
size_t count) -> jsi::Value {
890+
validateArgumentCount(runtime, methodName, paramCount, count);
891+
892+
if (!arguments[0].isObject()) {
893+
auto result = jsi::Object(runtime);
894+
result.setProperty(runtime, "x", 0);
895+
result.setProperty(runtime, "y", 0);
896+
result.setProperty(runtime, "width", 0);
897+
result.setProperty(runtime, "height", 0);
898+
return result;
899+
}
900+
901+
auto shadowNode = Bridging<std::shared_ptr<const ShadowNode>>::fromJs(
902+
runtime, arguments[0]);
903+
904+
auto currentRevision =
905+
uiManager->getShadowTreeRevisionProvider()->getCurrentRevision(
906+
shadowNode->getSurfaceId());
907+
908+
if (currentRevision == nullptr) {
909+
auto result = jsi::Object(runtime);
910+
result.setProperty(runtime, "x", 0);
911+
result.setProperty(runtime, "y", 0);
912+
result.setProperty(runtime, "width", 0);
913+
result.setProperty(runtime, "height", 0);
914+
return result;
915+
}
916+
917+
auto domRect = dom::getBoundingClientRect(
918+
currentRevision, *shadowNode, true /* includeTransform */);
919+
920+
auto result = jsi::Object(runtime);
921+
result.setProperty(runtime, "x", domRect.x);
922+
result.setProperty(runtime, "y", domRect.y);
923+
result.setProperty(runtime, "width", domRect.width);
924+
result.setProperty(runtime, "height", domRect.height);
925+
926+
auto* viewTransitionDelegate = uiManager->getViewTransitionDelegate();
927+
if (viewTransitionDelegate != nullptr) {
928+
viewTransitionDelegate->captureLayoutMetricsFromRoot(shadowNode);
929+
}
930+
931+
return result;
932+
});
933+
}
934+
935+
if (methodName == "applyViewTransitionName") {
936+
auto paramCount = 3;
937+
return jsi::Function::createFromHostFunction(
938+
runtime,
939+
name,
940+
paramCount,
941+
[uiManager, methodName, paramCount](
942+
jsi::Runtime& runtime,
943+
const jsi::Value& /*thisValue*/,
944+
const jsi::Value* arguments,
945+
size_t count) -> jsi::Value {
946+
validateArgumentCount(runtime, methodName, paramCount, count);
947+
948+
if (arguments[0].isObject()) {
949+
auto shadowNode =
950+
Bridging<std::shared_ptr<const ShadowNode>>::fromJs(
951+
runtime, arguments[0]);
952+
auto transitionName = arguments[1].isString()
953+
? stringFromValue(runtime, arguments[1])
954+
: "";
955+
auto className = arguments[2].isString()
956+
? stringFromValue(runtime, arguments[2])
957+
: "";
958+
if (!transitionName.empty()) {
959+
auto* viewTransitionDelegate =
960+
uiManager->getViewTransitionDelegate();
961+
if (viewTransitionDelegate != nullptr) {
962+
viewTransitionDelegate->applyViewTransitionName(
963+
shadowNode, transitionName, className);
964+
}
965+
}
966+
}
967+
968+
return jsi::Value::undefined();
969+
});
970+
}
971+
972+
if (methodName == "cancelViewTransitionName") {
973+
auto paramCount = 2;
974+
return jsi::Function::createFromHostFunction(
975+
runtime,
976+
name,
977+
paramCount,
978+
[uiManager, methodName, paramCount](
979+
jsi::Runtime& runtime,
980+
const jsi::Value& /*thisValue*/,
981+
const jsi::Value* arguments,
982+
size_t count) -> jsi::Value {
983+
validateArgumentCount(runtime, methodName, paramCount, count);
984+
985+
if (arguments[0].isObject()) {
986+
auto shadowNode =
987+
Bridging<std::shared_ptr<const ShadowNode>>::fromJs(
988+
runtime, arguments[0]);
989+
auto transitionName = arguments[1].isString()
990+
? stringFromValue(runtime, arguments[1])
991+
: "";
992+
if (!transitionName.empty()) {
993+
auto* viewTransitionDelegate =
994+
uiManager->getViewTransitionDelegate();
995+
if (viewTransitionDelegate != nullptr) {
996+
viewTransitionDelegate->cancelViewTransitionName(
997+
shadowNode, transitionName);
998+
}
999+
}
1000+
}
1001+
1002+
return jsi::Value::undefined();
1003+
});
1004+
}
1005+
1006+
if (methodName == "restoreViewTransitionName") {
1007+
auto paramCount = 1;
1008+
return jsi::Function::createFromHostFunction(
1009+
runtime,
1010+
name,
1011+
paramCount,
1012+
[uiManager, methodName, paramCount](
1013+
jsi::Runtime& runtime,
1014+
const jsi::Value& /*thisValue*/,
1015+
const jsi::Value* arguments,
1016+
size_t count) -> jsi::Value {
1017+
validateArgumentCount(runtime, methodName, paramCount, count);
1018+
1019+
if (arguments[0].isObject()) {
1020+
auto shadowNode =
1021+
Bridging<std::shared_ptr<const ShadowNode>>::fromJs(
1022+
runtime, arguments[0]);
1023+
auto* viewTransitionDelegate =
1024+
uiManager->getViewTransitionDelegate();
1025+
if (viewTransitionDelegate != nullptr) {
1026+
viewTransitionDelegate->restoreViewTransitionName(shadowNode);
1027+
}
1028+
}
1029+
1030+
return jsi::Value::undefined();
1031+
});
1032+
}
1033+
1034+
if (methodName == "startViewTransition") {
1035+
auto paramCount = 1;
1036+
return jsi::Function::createFromHostFunction(
1037+
runtime,
1038+
name,
1039+
paramCount,
1040+
[uiManager, methodName, paramCount](
1041+
jsi::Runtime& runtime,
1042+
const jsi::Value& /*thisValue*/,
1043+
const jsi::Value* arguments,
1044+
size_t count) -> jsi::Value {
1045+
validateArgumentCount(runtime, methodName, paramCount, count);
1046+
1047+
auto* viewTransitionDelegate = uiManager->getViewTransitionDelegate();
1048+
if (viewTransitionDelegate == nullptr) {
1049+
return jsi::Value::undefined();
1050+
}
1051+
1052+
auto promiseConstructor =
1053+
runtime.global().getPropertyAsFunction(runtime, "Promise");
1054+
1055+
auto readyResolveFunc =
1056+
std::make_shared<std::shared_ptr<jsi::Function>>();
1057+
auto finishedResolveFunc =
1058+
std::make_shared<std::shared_ptr<jsi::Function>>();
1059+
1060+
auto mutationFunc = std::make_shared<jsi::Function>(
1061+
arguments[0].asObject(runtime).asFunction(runtime));
1062+
1063+
auto readyPromise = promiseConstructor.callAsConstructor(
1064+
runtime,
1065+
jsi::Function::createFromHostFunction(
1066+
runtime,
1067+
jsi::PropNameID::forAscii(runtime, "readyExecutor"),
1068+
2,
1069+
[readyResolveFunc](
1070+
jsi::Runtime& runtime,
1071+
const jsi::Value& /*thisValue*/,
1072+
const jsi::Value* args,
1073+
size_t /*count*/) -> jsi::Value {
1074+
auto onReadyFunc = std::make_shared<jsi::Function>(
1075+
args[0].asObject(runtime).asFunction(runtime));
1076+
*readyResolveFunc = onReadyFunc;
1077+
return jsi::Value::undefined();
1078+
}));
1079+
1080+
auto finishedPromise = promiseConstructor.callAsConstructor(
1081+
runtime,
1082+
jsi::Function::createFromHostFunction(
1083+
runtime,
1084+
jsi::PropNameID::forAscii(runtime, "finishedExecutor"),
1085+
2,
1086+
[finishedResolveFunc](
1087+
jsi::Runtime& rt,
1088+
const jsi::Value& /*thisValue*/,
1089+
const jsi::Value* args,
1090+
size_t /*count*/) -> jsi::Value {
1091+
auto onCompleteFunc = std::make_shared<jsi::Function>(
1092+
args[0].asObject(rt).asFunction(rt));
1093+
*finishedResolveFunc = onCompleteFunc;
1094+
return jsi::Value::undefined();
1095+
}));
1096+
1097+
auto result = jsi::Object(runtime);
1098+
result.setProperty(runtime, "ready", std::move(readyPromise));
1099+
result.setProperty(runtime, "finished", std::move(finishedPromise));
1100+
1101+
viewTransitionDelegate->startViewTransition(
1102+
[&runtime, mutationFunc = std::move(mutationFunc)]() {
1103+
mutationFunc->call(runtime);
1104+
},
1105+
[readyResolveFunc = std::move(readyResolveFunc), uiManager]() {
1106+
uiManager->runtimeExecutor_(
1107+
[readyResolveFunc = std::move(readyResolveFunc)](
1108+
jsi::Runtime& rt) mutable {
1109+
if (*readyResolveFunc) {
1110+
(*readyResolveFunc)->call(rt);
1111+
}
1112+
});
1113+
},
1114+
[finishedResolveFunc = std::move(finishedResolveFunc),
1115+
uiManager]() {
1116+
uiManager->runtimeExecutor_(
1117+
[finishedResolveFunc = std::move(finishedResolveFunc)](
1118+
jsi::Runtime& rt) mutable {
1119+
if (*finishedResolveFunc) {
1120+
(*finishedResolveFunc)->call(rt);
1121+
}
1122+
});
1123+
});
1124+
1125+
return jsi::Value(runtime, result);
1126+
});
1127+
}
1128+
8791129
return jsi::Value::undefined();
8801130
}
8811131

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#pragma once
9+
10+
#include <react/renderer/core/ShadowNode.h>
11+
#include <functional>
12+
13+
namespace facebook::react {
14+
15+
class UIManagerViewTransitionDelegate {
16+
public:
17+
virtual ~UIManagerViewTransitionDelegate() = default;
18+
19+
virtual void applyViewTransitionName(
20+
const std::shared_ptr<const ShadowNode> &shadowNode,
21+
const std::string &name,
22+
const std::string &className) {};
23+
24+
virtual void cancelViewTransitionName(const std::shared_ptr<const ShadowNode> &shadowNode, const std::string &name) {
25+
};
26+
27+
virtual void restoreViewTransitionName(const std::shared_ptr<const ShadowNode> &shadowNode) {};
28+
29+
virtual void captureLayoutMetricsFromRoot(const std::shared_ptr<const ShadowNode> &shadowNode) {};
30+
31+
virtual void startViewTransition(
32+
std::function<void()> mutationCallback,
33+
std::function<void()> onReadyCallback,
34+
std::function<void()> onCompleteCallback) {};
35+
};
36+
37+
} // namespace facebook::react

0 commit comments

Comments
 (0)