From 13372dce104a750c22836b0db1cc11b662852fdf Mon Sep 17 00:00:00 2001 From: Zeya Peng Date: Fri, 17 Apr 2026 06:44:13 -0700 Subject: [PATCH] support multiple old pseudo elements for same name but different source node (#56479) Summary: ## Changelog: [General] [Fixed] - support multiple old pseudo elements for same name but different source node We will need to track multiple old pseudo elements for same name but different source node when doing a series of shared transitions: 1. go from component A to component B (A stays hidden with Activity but not unmounted) 2. go back to A (B unmounted) 3. hide A and show B again A will lose its old pseudo element node, because at #1, B's old pseudo element node overrides A's (since they have the same vt name), and at #2, B's old node gets cleaned up. At #3, since createViewTransitionInstance won't be called for A again (react reconciler assumes the instance is only created once until a component is unmounted), there's no valid old node for A anymore. Reviewed By: sammy-SC Differential Revision: D101237889 --- .../viewtransition/ViewTransitionModule.cpp | 25 ++++++++++++++++--- .../viewtransition/ViewTransitionModule.h | 3 ++- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/packages/react-native/ReactCommon/react/renderer/viewtransition/ViewTransitionModule.cpp b/packages/react-native/ReactCommon/react/renderer/viewtransition/ViewTransitionModule.cpp index 5892e345131..f070bfce3bf 100644 --- a/packages/react-native/ReactCommon/react/renderer/viewtransition/ViewTransitionModule.cpp +++ b/packages/react-native/ReactCommon/react/renderer/viewtransition/ViewTransitionModule.cpp @@ -7,6 +7,7 @@ #include "ViewTransitionModule.h" +#include #include #include #include @@ -93,7 +94,20 @@ void ViewTransitionModule::applyViewTransitionName( if (auto it = oldPseudoElementNodesRepository_.find(name); it != oldPseudoElementNodesRepository_.end()) { - oldPseudoElementNodes_[name] = it->second.node; + // Find the pseudo element created from this specific source tag + auto& pseudoElementsBySourceTag = it->second; + auto innerIt = pseudoElementsBySourceTag.find(tag); + if (innerIt != pseudoElementsBySourceTag.end()) { + oldPseudoElementNodes_[name] = innerIt->second.node; + } else if (!pseudoElementsBySourceTag.empty()) { + // Fallback to first available entry for this name + oldPseudoElementNodes_[name] = + pseudoElementsBySourceTag.begin()->second.node; + } + } else { + LOG(WARNING) + << "applyViewTransitionName: old pseudo element shadow node doesn't exist for name " + << name; } } else { @@ -158,7 +172,7 @@ void ViewTransitionModule::createViewTransitionInstance( if (!forNextTransition) { oldPseudoElementNodes_[name] = pseudoElementNode; } - oldPseudoElementNodesRepository_[name] = InactivePseudoElement{ + oldPseudoElementNodesRepository_[name][view.tag] = InactivePseudoElement{ .node = pseudoElementNode, .sourceTag = view.tag}; } } @@ -222,7 +236,12 @@ std::optional ViewTransitionModule::pullTransaction( auto tag = mutation.oldChildShadowView.tag; for (auto it = oldPseudoElementNodesRepository_.begin(); it != oldPseudoElementNodesRepository_.end();) { - if (it->second.sourceTag == tag) { + auto& pseudoElementsBySourceTag = it->second; + if (auto innerIt = pseudoElementsBySourceTag.find(tag); + innerIt != pseudoElementsBySourceTag.end()) { + pseudoElementsBySourceTag.erase(innerIt); + } + if (pseudoElementsBySourceTag.empty()) { it = oldPseudoElementNodesRepository_.erase(it); } else { ++it; diff --git a/packages/react-native/ReactCommon/react/renderer/viewtransition/ViewTransitionModule.h b/packages/react-native/ReactCommon/react/renderer/viewtransition/ViewTransitionModule.h index 44878c24b68..ad28dce6a69 100644 --- a/packages/react-native/ReactCommon/react/renderer/viewtransition/ViewTransitionModule.h +++ b/packages/react-native/ReactCommon/react/renderer/viewtransition/ViewTransitionModule.h @@ -124,7 +124,8 @@ class ViewTransitionModule : public UIManagerViewTransitionDelegate, // pseudo-element nodes created for entering nodes, to be copied into // oldPseudoElementNodes_ during the next applyViewTransitionName call. // Mutable because pullTransaction (const) needs to erase unmounted entries. - mutable std::unordered_map oldPseudoElementNodesRepository_{}; + mutable std::unordered_map> + oldPseudoElementNodesRepository_{}; LayoutMetrics captureLayoutMetricsFromRoot(const ShadowNode &shadowNode);