react-native-reanimated 4.4.1 → 4.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/Common/cpp/reanimated/CSS/interpolation/transforms/TransformOperationInterpolator.cpp +12 -5
  2. package/Common/cpp/reanimated/CSS/misc/ViewStylesRepository.cpp +63 -36
  3. package/Common/cpp/reanimated/CSS/misc/ViewStylesRepository.h +6 -8
  4. package/Common/cpp/reanimated/CSS/svg/values/SVGStrokeDashArray.cpp +5 -0
  5. package/Common/cpp/reanimated/Fabric/ReanimatedMountHook.cpp +25 -12
  6. package/Common/cpp/reanimated/Fabric/ReanimatedMountHook.h +3 -0
  7. package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxyCommon.cpp +6 -2
  8. package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxyCommon.h +44 -1
  9. package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp +125 -43
  10. package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Legacy.cpp +189 -102
  11. package/Common/cpp/reanimated/LayoutAnimations/SharedTransitions.cpp +8 -3
  12. package/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp +4 -16
  13. package/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.h +0 -16
  14. package/android/CMakeLists.txt +6 -0
  15. package/android/build.gradle.kts +12 -1
  16. package/android/src/main/java/com/swmansion/reanimated/NativeProxy.kt +46 -2
  17. package/android/src/main/java/com/swmansion/reanimated/keyboard/KeyboardAnimationCallback.kt +8 -1
  18. package/apple/reanimated/apple/ReanimatedModule.mm +12 -1
  19. package/compatibility.json +1 -1
  20. package/lib/module/common/constants/platform.js +4 -4
  21. package/lib/module/common/constants/platform.js.map +1 -1
  22. package/lib/module/createAnimatedComponent/getViewInfo.js +3 -1
  23. package/lib/module/createAnimatedComponent/getViewInfo.js.map +1 -1
  24. package/lib/module/css/component/AnimatedComponent.js +0 -2
  25. package/lib/module/css/component/AnimatedComponent.js.map +1 -1
  26. package/lib/module/css/native/managers/CSSManager.js +5 -5
  27. package/lib/module/css/native/managers/CSSManager.js.map +1 -1
  28. package/lib/module/css/native/managers/CSSTransitionsManager.js +8 -1
  29. package/lib/module/css/native/managers/CSSTransitionsManager.js.map +1 -1
  30. package/lib/module/css/svg/native/processors/stroke.js +9 -5
  31. package/lib/module/css/svg/native/processors/stroke.js.map +1 -1
  32. package/lib/module/layoutReanimation/animationBuilder/ComplexAnimationBuilder.js +6 -3
  33. package/lib/module/layoutReanimation/animationBuilder/ComplexAnimationBuilder.js.map +1 -1
  34. package/lib/module/layoutReanimation/animationsManager.js +31 -6
  35. package/lib/module/layoutReanimation/animationsManager.js.map +1 -1
  36. package/lib/module/mappers.js +1 -1
  37. package/lib/module/mappers.js.map +1 -1
  38. package/lib/module/platform-specific/findHostInstance.js +8 -5
  39. package/lib/module/platform-specific/findHostInstance.js.map +1 -1
  40. package/lib/module/platform-specific/jsVersion.js +1 -1
  41. package/lib/module/updateProps/updateProps.js +3 -2
  42. package/lib/module/updateProps/updateProps.js.map +1 -1
  43. package/lib/module/valueSetter.js +6 -7
  44. package/lib/module/valueSetter.js.map +1 -1
  45. package/lib/typescript/common/constants/platform.d.ts.map +1 -1
  46. package/lib/typescript/createAnimatedComponent/commonTypes.d.ts +0 -1
  47. package/lib/typescript/createAnimatedComponent/commonTypes.d.ts.map +1 -1
  48. package/lib/typescript/createAnimatedComponent/getViewInfo.d.ts.map +1 -1
  49. package/lib/typescript/css/component/AnimatedComponent.d.ts +0 -1
  50. package/lib/typescript/css/component/AnimatedComponent.d.ts.map +1 -1
  51. package/lib/typescript/css/native/managers/CSSTransitionsManager.d.ts +5 -1
  52. package/lib/typescript/css/native/managers/CSSTransitionsManager.d.ts.map +1 -1
  53. package/lib/typescript/css/svg/native/processors/stroke.d.ts.map +1 -1
  54. package/lib/typescript/layoutReanimation/animationBuilder/ComplexAnimationBuilder.d.ts +17 -14
  55. package/lib/typescript/layoutReanimation/animationBuilder/ComplexAnimationBuilder.d.ts.map +1 -1
  56. package/lib/typescript/layoutReanimation/animationsManager.d.ts.map +1 -1
  57. package/lib/typescript/platform-specific/findHostInstance.d.ts.map +1 -1
  58. package/lib/typescript/platform-specific/jsVersion.d.ts +1 -1
  59. package/lib/typescript/platform-specific/types.d.ts +1 -0
  60. package/lib/typescript/platform-specific/types.d.ts.map +1 -1
  61. package/lib/typescript/updateProps/updateProps.d.ts.map +1 -1
  62. package/package.json +2 -2
  63. package/src/common/constants/platform.ts +4 -4
  64. package/src/createAnimatedComponent/commonTypes.ts +0 -1
  65. package/src/createAnimatedComponent/getViewInfo.ts +3 -1
  66. package/src/css/component/AnimatedComponent.tsx +0 -2
  67. package/src/css/native/managers/CSSManager.ts +8 -8
  68. package/src/css/native/managers/CSSTransitionsManager.ts +9 -2
  69. package/src/css/svg/native/processors/stroke.ts +9 -6
  70. package/src/layoutReanimation/animationBuilder/ComplexAnimationBuilder.ts +39 -36
  71. package/src/layoutReanimation/animationsManager.ts +33 -5
  72. package/src/mappers.ts +1 -1
  73. package/src/platform-specific/findHostInstance.ts +7 -7
  74. package/src/platform-specific/jsVersion.ts +1 -1
  75. package/src/platform-specific/types.ts +2 -0
  76. package/src/updateProps/updateProps.ts +3 -2
  77. package/src/valueSetter.ts +7 -7
@@ -5,6 +5,8 @@
5
5
  #include <reanimated/CSS/interpolation/transforms/operations/skew.h>
6
6
  #include <reanimated/CSS/interpolation/transforms/operations/translate.h>
7
7
 
8
+ #include <cmath>
9
+ #include <limits>
8
10
  #include <utility>
9
11
 
10
12
  namespace reanimated::css {
@@ -31,14 +33,19 @@ std::unique_ptr<StyleOperation> TransformOperationInterpolator<PerspectiveOperat
31
33
  const std::shared_ptr<StyleOperation> &from,
32
34
  const std::shared_ptr<StyleOperation> &to,
33
35
  const StyleOperationsInterpolationContext & /* context */) const {
34
- // TODO - check if this implementation is correct
35
36
  const auto &fromValue = std::static_pointer_cast<PerspectiveOperation>(from)->value;
36
37
  const auto &toValue = std::static_pointer_cast<PerspectiveOperation>(to)->value;
37
38
 
38
- if (fromValue.value == 0)
39
- return std::make_unique<PerspectiveOperation>(toValue);
40
- if (toValue.value == 0)
41
- return std::make_unique<PerspectiveOperation>(fromValue);
39
+ // The default "no perspective" is infinity, so interpolating the distance
40
+ // directly gives inf + t*(d - inf) = NaN. Interpolate in reciprocal space
41
+ // instead (1/inf = 0, matching how perspective enters the matrix as -1/d).
42
+ if (std::isinf(fromValue.value) || std::isinf(toValue.value)) {
43
+ const double fromReciprocal = 1.0 / fromValue.value;
44
+ const double toReciprocal = 1.0 / toValue.value;
45
+ const double reciprocal = fromReciprocal + progress * (toReciprocal - fromReciprocal);
46
+ return std::make_unique<PerspectiveOperation>(
47
+ reciprocal == 0.0 ? std::numeric_limits<double>::infinity() : 1.0 / reciprocal);
48
+ }
42
49
 
43
50
  return std::make_unique<PerspectiveOperation>(fromValue.interpolate(progress, toValue));
44
51
  }
@@ -1,4 +1,5 @@
1
1
  #include <reanimated/CSS/misc/ViewStylesRepository.h>
2
+ #include <reanimated/Tools/FeatureFlags.h>
2
3
 
3
4
  #include <memory>
4
5
  #include <string>
@@ -13,13 +14,11 @@ ViewStylesRepository::ViewStylesRepository(
13
14
  jsi::Value ViewStylesRepository::getNodeProp(
14
15
  const std::shared_ptr<const ShadowNode> &shadowNode,
15
16
  const std::string &propName) {
16
- int tag = shadowNode->getTag();
17
-
18
- auto &cachedNode = shadowNodeCache_[tag];
19
- updateCacheIfNeeded(cachedNode, shadowNode);
17
+ const auto resolvedNode = getNewestNode(shadowNode);
18
+ const auto *layoutableShadowNode = dynamic_cast<const LayoutableShadowNode *>(resolvedNode.get());
20
19
 
21
20
  if (propName == "width" || propName == "height" || propName == "top" || propName == "left") {
22
- const auto &layoutMetrics = cachedNode.layoutMetrics;
21
+ const auto layoutMetrics = layoutableShadowNode ? layoutableShadowNode->layoutMetrics_ : LayoutMetrics{};
23
22
 
24
23
  if (propName == "width") {
25
24
  return {layoutMetrics.frame.size.width};
@@ -31,7 +30,13 @@ jsi::Value ViewStylesRepository::getNodeProp(
31
30
  return {layoutMetrics.frame.origin.x};
32
31
  }
33
32
  } else {
34
- const auto &viewProps = cachedNode.viewProps;
33
+ if (!layoutableShadowNode) {
34
+ return jsi::Value::undefined();
35
+ }
36
+ const auto viewProps = std::static_pointer_cast<const ViewProps>(resolvedNode->getProps());
37
+ if (!viewProps) {
38
+ return jsi::Value::undefined();
39
+ }
35
40
 
36
41
  if (propName == "opacity") {
37
42
  return {viewProps->opacity};
@@ -48,15 +53,7 @@ jsi::Value ViewStylesRepository::getNodeProp(
48
53
  jsi::Value ViewStylesRepository::getParentNodeProp(
49
54
  const std::shared_ptr<const ShadowNode> &shadowNode,
50
55
  const std::string &propName) {
51
- const auto surfaceId = shadowNode->getSurfaceId();
52
- const auto &shadowTreeRegistry = uiManager_->getShadowTreeRegistry();
53
-
54
- std::shared_ptr<const ShadowNode> parentNode = nullptr;
55
-
56
- shadowTreeRegistry.visit(surfaceId, [&](ShadowTree const &shadowTree) {
57
- auto currentRevision = shadowTree.getCurrentRevision();
58
- parentNode = dom::getParentNode(currentRevision.rootShadowNode, *shadowNode);
59
- });
56
+ const auto parentNode = getParentNode(shadowNode);
60
57
 
61
58
  if (!parentNode) {
62
59
  return jsi::Value::undefined();
@@ -65,37 +62,67 @@ jsi::Value ViewStylesRepository::getParentNodeProp(
65
62
  return getNodeProp(parentNode, propName);
66
63
  }
67
64
 
68
- folly::dynamic ViewStylesRepository::getStyleProp(const Tag tag, const PropertyPath &propertyPath) {
69
- auto animatedValue = getPropertyValue(animatedPropsRegistry_->get(tag), propertyPath);
70
- if (!animatedValue.isNull()) {
71
- return animatedValue;
65
+ std::shared_ptr<const ShadowNode> ViewStylesRepository::getNewestNode(
66
+ const std::shared_ptr<const ShadowNode> &shadowNode) const {
67
+ if constexpr (StaticFeatureFlags::getFlag("USE_ANIMATION_BACKEND")) {
68
+ // Backend mode has no commit hook (it returns before taking the updates-registry
69
+ // lock), so reading the live tree here cannot deadlock and needs no snapshot.
70
+ const auto newestNode = uiManager_->getNewestCloneOfShadowNode(*shadowNode);
71
+ return newestNode ? newestNode : shadowNode;
72
72
  }
73
73
 
74
- return getPropertyValue(staticPropsRegistry_->get(tag), propertyPath);
75
- }
74
+ // Resolve shadowNode against the last mounted root without the ShadowTree lock (the
75
+ // deadlock) that getNewestCloneOfShadowNode takes, falling back to the passed node.
76
+ // Mirrors RN's getShadowNodeInSubtree:
77
+ // https://github.com/facebook/react-native/blob/v0.86.0-rc.3/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp#L339
78
+ const auto it = lastMountedRootBySurface_.find(shadowNode->getSurfaceId());
79
+ if (it == lastMountedRootBySurface_.end()) {
80
+ return shadowNode;
81
+ }
82
+ const auto &root = it->second;
76
83
 
77
- void ViewStylesRepository::clearNodesCache() {
78
- shadowNodeCache_.clear();
84
+ if (ShadowNode::sameFamily(*root, *shadowNode)) {
85
+ return root;
86
+ }
87
+
88
+ const auto ancestors = shadowNode->getFamily().getAncestors(*root);
89
+ if (ancestors.empty()) {
90
+ return shadowNode;
91
+ }
92
+
93
+ const auto &deepest = ancestors.back();
94
+ return deepest.first.get().getChildren().at(deepest.second);
79
95
  }
80
96
 
81
- void ViewStylesRepository::updateCacheIfNeeded(
82
- CachedShadowNode &cachedNode,
83
- const std::shared_ptr<const ShadowNode> &shadowNode) {
84
- auto newestCloneOfShadowNode = uiManager_->getNewestCloneOfShadowNode(*shadowNode);
97
+ std::shared_ptr<const ShadowNode> ViewStylesRepository::getParentNode(
98
+ const std::shared_ptr<const ShadowNode> &shadowNode) const {
99
+ if constexpr (StaticFeatureFlags::getFlag("USE_ANIMATION_BACKEND")) {
100
+ // Backend mode resolves against the live tree (see getNewestNode).
101
+ std::shared_ptr<const ShadowNode> parentNode = nullptr;
102
+ uiManager_->getShadowTreeRegistry().visit(shadowNode->getSurfaceId(), [&](const ShadowTree &shadowTree) {
103
+ parentNode = dom::getParentNode(shadowTree.getCurrentRevision().rootShadowNode, *shadowNode);
104
+ });
105
+ return parentNode;
106
+ }
85
107
 
86
- // Check if newestCloneOfShadowNode is valid (is already mounted / not
87
- // yet unmounted)
88
- if (!newestCloneOfShadowNode) {
89
- return;
108
+ const auto it = lastMountedRootBySurface_.find(shadowNode->getSurfaceId());
109
+ if (it == lastMountedRootBySurface_.end()) {
110
+ return nullptr;
90
111
  }
112
+ return dom::getParentNode(it->second, *shadowNode);
113
+ }
114
+
115
+ void ViewStylesRepository::setLastMountedRoot(const RootShadowNode::Shared &rootShadowNode) {
116
+ lastMountedRootBySurface_[rootShadowNode->getSurfaceId()] = rootShadowNode;
117
+ }
91
118
 
92
- auto layoutableShadowNode = dynamic_cast<const LayoutableShadowNode *>(newestCloneOfShadowNode.get());
93
- if (!layoutableShadowNode) {
94
- return;
119
+ folly::dynamic ViewStylesRepository::getStyleProp(const Tag tag, const PropertyPath &propertyPath) {
120
+ auto animatedValue = getPropertyValue(animatedPropsRegistry_->get(tag), propertyPath);
121
+ if (!animatedValue.isNull()) {
122
+ return animatedValue;
95
123
  }
96
124
 
97
- cachedNode.layoutMetrics = layoutableShadowNode->layoutMetrics_;
98
- cachedNode.viewProps = std::static_pointer_cast<const ViewProps>(newestCloneOfShadowNode->getProps());
125
+ return getPropertyValue(staticPropsRegistry_->get(tag), propertyPath);
99
126
  }
100
127
 
101
128
  folly::dynamic ViewStylesRepository::getPropertyValue(const folly::dynamic &value, const PropertyPath &propertyPath) {
@@ -4,9 +4,11 @@
4
4
  #include <reanimated/CSS/registries/StaticPropsRegistry.h>
5
5
  #include <reanimated/Fabric/updates/AnimatedPropsRegistry.h>
6
6
 
7
+ #include <react/renderer/components/root/RootShadowNode.h>
7
8
  #include <react/renderer/components/view/ViewProps.h>
8
9
  #include <react/renderer/core/LayoutableShadowNode.h>
9
10
  #include <react/renderer/dom/DOM.h>
11
+ #include <react/renderer/uimanager/UIManager.h>
10
12
 
11
13
  #include <memory>
12
14
  #include <string>
@@ -17,11 +19,6 @@ namespace reanimated::css {
17
19
  using namespace facebook;
18
20
  using namespace react;
19
21
 
20
- struct CachedShadowNode {
21
- LayoutMetrics layoutMetrics;
22
- std::shared_ptr<const ViewProps> viewProps;
23
- };
24
-
25
22
  class ViewStylesRepository {
26
23
  public:
27
24
  ViewStylesRepository(
@@ -36,16 +33,17 @@ class ViewStylesRepository {
36
33
  jsi::Value getParentNodeProp(const std::shared_ptr<const ShadowNode> &shadowNode, const std::string &propName);
37
34
  folly::dynamic getStyleProp(Tag tag, const PropertyPath &propertyPath);
38
35
 
39
- void clearNodesCache();
36
+ void setLastMountedRoot(const RootShadowNode::Shared &rootShadowNode);
40
37
 
41
38
  private:
42
39
  std::shared_ptr<UIManager> uiManager_;
43
40
  std::shared_ptr<StaticPropsRegistry> staticPropsRegistry_;
44
41
  std::shared_ptr<AnimatedPropsRegistry> animatedPropsRegistry_;
45
42
 
46
- std::unordered_map<int, CachedShadowNode> shadowNodeCache_;
43
+ std::unordered_map<SurfaceId, RootShadowNode::Shared> lastMountedRootBySurface_;
47
44
 
48
- void updateCacheIfNeeded(CachedShadowNode &cachedNode, const std::shared_ptr<const ShadowNode> &shadowNode);
45
+ std::shared_ptr<const ShadowNode> getNewestNode(const std::shared_ptr<const ShadowNode> &shadowNode) const;
46
+ std::shared_ptr<const ShadowNode> getParentNode(const std::shared_ptr<const ShadowNode> &shadowNode) const;
49
47
 
50
48
  static folly::dynamic getPropertyValue(const folly::dynamic &value, const PropertyPath &propertyPath);
51
49
  };
@@ -43,6 +43,11 @@ bool SVGStrokeDashArray::canConstruct(const folly::dynamic &value) {
43
43
  }
44
44
 
45
45
  folly::dynamic SVGStrokeDashArray::toDynamic() const {
46
+ // Empty means "no dashing" - emit null instead of [], which react-native-svg
47
+ // would feed to Android's DashPathEffect that requires at least 2 intervals.
48
+ if (values.empty()) {
49
+ return nullptr;
50
+ }
46
51
  folly::dynamic array = folly::dynamic::array;
47
52
  array.reserve(values.size());
48
53
  for (const auto &value : values) {
@@ -9,8 +9,12 @@ namespace reanimated {
9
9
  ReanimatedMountHook::ReanimatedMountHook(
10
10
  const std::shared_ptr<UIManager> &uiManager,
11
11
  const std::shared_ptr<UpdatesRegistryManager> &updatesRegistryManager,
12
+ const std::shared_ptr<css::ViewStylesRepository> &viewStylesRepository,
12
13
  const std::function<void()> &requestFlush)
13
- : uiManager_(uiManager), updatesRegistryManager_(updatesRegistryManager), requestFlush_(requestFlush) {
14
+ : uiManager_(uiManager),
15
+ updatesRegistryManager_(updatesRegistryManager),
16
+ viewStylesRepository_(viewStylesRepository),
17
+ requestFlush_(requestFlush) {
14
18
  uiManager_->registerMountHook(*this);
15
19
  }
16
20
 
@@ -26,24 +30,33 @@ void ReanimatedMountHook::shadowTreeDidMount(
26
30
  auto reaShadowNode = std::reinterpret_pointer_cast<ReanimatedCommitShadowNode>(
27
31
  std::const_pointer_cast<RootShadowNode>(rootShadowNode));
28
32
 
29
- if (reaShadowNode->hasReanimatedMountTrait()) {
30
- // We mark reanimated commits with ReanimatedMountTrait. We don't want other
31
- // shadow nodes to use this trait, but since this rootShadowNode is Shared,
32
- // we don't have that guarantee. That's why we also unset this trait in the
33
- // commit hook. We remove it here mainly for the sake of cleanliness.
33
+ // We mark reanimated commits with ReanimatedMountTrait. We don't want other
34
+ // shadow nodes to use this trait, but since this rootShadowNode is Shared,
35
+ // we don't have that guarantee. That's why we also unset this trait in the
36
+ // commit hook. We remove it here mainly for the sake of cleanliness.
37
+ const bool isReanimatedMount = reaShadowNode->hasReanimatedMountTrait();
38
+ if (isReanimatedMount) {
34
39
  reaShadowNode->unsetReanimatedMountTrait();
35
- return;
36
40
  }
37
41
 
38
42
  {
39
43
  auto lock = updatesRegistryManager_->lock();
44
+ // Record the mounted tree for relative-length resolution.
45
+ viewStylesRepository_->setLastMountedRoot(rootShadowNode);
46
+
47
+ // Always drain removable nodes, even on Reanimated's own commits. While CSS
48
+ // animations run every mount carries the mount trait, so returning early here
49
+ // would skip removals for the whole animation and leak unmounted nodes if the
50
+ // tree is torn down mid-animation.
40
51
  updatesRegistryManager_->handleNodeRemovals(*rootShadowNode);
41
52
 
42
- // When commit from React Native has finished, we reset the skip commit flag
43
- // in order to allow Reanimated to commit its tree
44
- updatesRegistryManager_->unpauseReanimatedCommits();
45
- if (updatesRegistryManager_->shouldCommitAfterPause()) {
46
- requestFlush_();
53
+ if (!isReanimatedMount) {
54
+ // When a commit from React Native has finished, we reset the skip commit
55
+ // flag in order to allow Reanimated to commit its tree.
56
+ updatesRegistryManager_->unpauseReanimatedCommits();
57
+ if (updatesRegistryManager_->shouldCommitAfterPause()) {
58
+ requestFlush_();
59
+ }
47
60
  }
48
61
  }
49
62
  }
@@ -1,5 +1,6 @@
1
1
  #pragma once
2
2
 
3
+ #include <reanimated/CSS/misc/ViewStylesRepository.h>
3
4
  #include <reanimated/Fabric/ShadowTreeCloner.h>
4
5
  #include <reanimated/Fabric/updates/UpdatesRegistryManager.h>
5
6
 
@@ -16,6 +17,7 @@ class ReanimatedMountHook : public UIManagerMountHook {
16
17
  ReanimatedMountHook(
17
18
  const std::shared_ptr<UIManager> &uiManager,
18
19
  const std::shared_ptr<UpdatesRegistryManager> &updatesRegistryManager,
20
+ const std::shared_ptr<css::ViewStylesRepository> &viewStylesRepository,
19
21
  const std::function<void()> &requestFlush);
20
22
  ~ReanimatedMountHook() noexcept override;
21
23
 
@@ -24,6 +26,7 @@ class ReanimatedMountHook : public UIManagerMountHook {
24
26
  private:
25
27
  const std::shared_ptr<UIManager> uiManager_;
26
28
  const std::shared_ptr<UpdatesRegistryManager> updatesRegistryManager_;
29
+ const std::shared_ptr<css::ViewStylesRepository> viewStylesRepository_;
27
30
  const std::function<void()> requestFlush_;
28
31
  };
29
32
 
@@ -37,8 +37,12 @@ const facebook::react::ShadowNode *findInShadowTreeByTag(const facebook::react::
37
37
 
38
38
  void LayoutAnimationsProxyCommon::restoreOpacityInCaseOfFlakyEnteringAnimation(SurfaceId surfaceId) const {
39
39
  std::vector<std::pair<double, Tag>> opacityToRestore;
40
- for (const auto tag : finishedAnimationTags_) {
41
- const auto &opacity = layoutAnimations_[tag].opacity;
40
+ for (const auto tag : maybeSettledAnimationTags_) {
41
+ const auto layoutAnimationIt = layoutAnimations_.find(tag);
42
+ if (layoutAnimationIt == layoutAnimations_.end() || !layoutAnimationIt->second.isSettled()) {
43
+ continue;
44
+ }
45
+ const auto &opacity = layoutAnimationIt->second.opacity;
42
46
  if (opacity.has_value()) {
43
47
  opacityToRestore.emplace_back(std::pair<double, Tag>{opacity.value(), tag});
44
48
  }
@@ -1,6 +1,7 @@
1
1
  #pragma once
2
2
 
3
3
  #include <jsi/jsi.h>
4
+ #include <react/debug/react_native_assert.h>
4
5
  #include <react/renderer/componentregistry/ComponentDescriptorFactory.h>
5
6
  #include <react/renderer/mounting/MountingOverrideDelegate.h>
6
7
  #include <react/renderer/uimanager/UIManager.h>
@@ -11,6 +12,7 @@
11
12
  #include <memory>
12
13
  #include <optional>
13
14
  #include <unordered_map>
15
+ #include <unordered_set>
14
16
  #include <vector>
15
17
 
16
18
  namespace reanimated {
@@ -22,8 +24,37 @@ struct LayoutAnimation {
22
24
  bool isViewAlreadyMounted = false;
23
25
  int count = 1;
24
26
  LayoutAnimation &operator=(const LayoutAnimation &other) = default;
27
+
28
+ bool isSettled() const {
29
+ return count == 0;
30
+ }
31
+ };
32
+
33
+ #ifdef ANDROID
34
+ // Bookkeeping for animation starts that were scheduled onto the UI thread but
35
+ // haven't run yet — see `pendingStarts_` below.
36
+ struct PendingStart {
37
+ int count = 0;
38
+ uint64_t handle = 0;
25
39
  };
26
40
 
41
+ // Removes one pending start for the given tag and returns whether it was
42
+ // cancelled since it was scheduled (i.e. its handle is no longer current).
43
+ // Call under the proxy mutex.
44
+ inline bool
45
+ consumeIsCancelled(std::unordered_map<Tag, PendingStart> &pendingStarts, const Tag tag, const uint64_t handle) {
46
+ const auto it = pendingStarts.find(tag);
47
+ // every scheduled start keeps its entry alive until it is consumed —
48
+ // cancellations only bump the handle, they never erase
49
+ react_native_assert(it != pendingStarts.end() && "PendingStart not found");
50
+ const bool isCancelled = it->second.handle != handle;
51
+ if (--it->second.count == 0) {
52
+ pendingStarts.erase(it);
53
+ }
54
+ return isCancelled;
55
+ }
56
+ #endif
57
+
27
58
  class LayoutAnimationsProxyCommon : public facebook::react::MountingOverrideDelegate {
28
59
  public:
29
60
  LayoutAnimationsProxyCommon(
@@ -60,7 +91,7 @@ class LayoutAnimationsProxyCommon : public facebook::react::MountingOverrideDele
60
91
  virtual void startSurface(const SurfaceId surfaceId);
61
92
 
62
93
  protected:
63
- mutable std::vector<Tag> finishedAnimationTags_;
94
+ mutable std::unordered_set<Tag> maybeSettledAnimationTags_;
64
95
  mutable std::unordered_map<Tag, LayoutAnimation> layoutAnimations_;
65
96
  std::shared_ptr<LayoutAnimationsManager> layoutAnimationsManager_;
66
97
  std::shared_ptr<const ContextContainer> contextContainer_;
@@ -75,6 +106,18 @@ class LayoutAnimationsProxyCommon : public facebook::react::MountingOverrideDele
75
106
 
76
107
  void restoreOpacityInCaseOfFlakyEnteringAnimation(SurfaceId surfaceId) const;
77
108
 
109
+ // On Android pullTransaction can run on the JS thread, so animation starts
110
+ // are scheduled onto the UI thread. If `maybeCancelAnimation` is called between the
111
+ // start was scheduled and the lambda runs, it
112
+ // finds no `layoutAnimations_` entry to erase (the start lambda hasn't created it
113
+ // yet) and the cancellation is lost — the stale start would later
114
+ // "resurrect" the animation for a view whose Remove+Delete are already on
115
+ // their way to the mounting layer
116
+ // (https://github.com/software-mansion/react-native-reanimated/issues/7493).
117
+
118
+ // To work around this, we keep a separate `pendingStarts_` map that tracks scheduled starts by tag,
119
+ // with a generation counter to detect cancellations.
120
+ mutable std::unordered_map<Tag, PendingStart> pendingStarts_;
78
121
  #endif
79
122
  };
80
123