diff --git a/packages/react-native/Libraries/Animated/__tests__/Animated-itest.js b/packages/react-native/Libraries/Animated/__tests__/Animated-itest.js index d124c865264..7a36361c941 100644 --- a/packages/react-native/Libraries/Animated/__tests__/Animated-itest.js +++ b/packages/react-native/Libraries/Animated/__tests__/Animated-itest.js @@ -17,6 +17,7 @@ import ensureInstance from '../../../src/private/__tests__/utilities/ensureInsta import * as Fantom from '@react-native/fantom'; import {createRef} from 'react'; import {Animated, View, useAnimatedValue} from 'react-native'; +import {allowStyleProp} from 'react-native/Libraries/Animated/NativeAnimatedAllowlist'; import * as ReactNativeFeatureFlags from 'react-native/src/private/featureflags/ReactNativeFeatureFlags'; import ReactNativeElement from 'react-native/src/private/webapis/dom/nodes/ReactNativeElement'; @@ -152,6 +153,7 @@ test('animation driven by onScroll event', () => { test('animated opacity', () => { let _opacity; + let _opacityAnimation; const viewRef = createRef(); function MyApp() { @@ -182,7 +184,7 @@ test('animated opacity', () => { expect(viewElement.getBoundingClientRect().x).toBe(0); Fantom.runTask(() => { - Animated.timing(_opacity, { + _opacityAnimation = Animated.timing(_opacity, { toValue: 0, duration: 30, useNativeDriver: true, @@ -194,8 +196,10 @@ test('animated opacity', () => { 0, ); - // TODO(T223344928): this shouldn't be neccessary with cxxNativeAnimatedRemoveJsSync:true - Fantom.runWorkLoop(); + // TODO: this shouldn't be neccessary since animation should be stopped after duration + Fantom.runTask(() => { + _opacityAnimation?.stop(); + }); expect(root.getRenderedOutput({props: ['opacity']}).toJSX()).toEqual( , @@ -475,3 +479,61 @@ describe('Value.extractOffset', () => { Fantom.runWorkLoop(); }); }); + +test('animate layout props', () => { + const viewRef = createRef(); + allowStyleProp('height'); + + let _animatedHeight; + let _heightAnimation; + + function MyApp() { + const animatedHeight = useAnimatedValue(0); + _animatedHeight = animatedHeight; + return ( + + ); + } + + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render(); + }); + + const viewElement = ensureInstance(viewRef.current, ReactNativeElement); + + Fantom.runTask(() => { + _heightAnimation = Animated.timing(_animatedHeight, { + toValue: 100, + duration: 10, + useNativeDriver: true, + }).start(); + }); + + Fantom.unstable_produceFramesForDuration(10); + + // TODO: this shouldn't be neccessary since animation should be stopped after duration + Fantom.runTask(() => { + _heightAnimation?.stop(); + }); + + // $FlowFixMe[incompatible-use] + expect(Fantom.unstable_getDirectManipulationProps(viewElement).height).toBe( + 100, + ); + + expect(Fantom.unstable_getFabricUpdateProps(viewElement).height).toBe(100); + + expect(root.getRenderedOutput({props: ['height']}).toJSX()).toEqual( + , + ); +}); diff --git a/packages/react-native/ReactCxxPlatform/react/renderer/animated/nodes/PropsAnimatedNode.cpp b/packages/react-native/ReactCxxPlatform/react/renderer/animated/nodes/PropsAnimatedNode.cpp index 4e88608e678..05ccc4ebf4e 100644 --- a/packages/react-native/ReactCxxPlatform/react/renderer/animated/nodes/PropsAnimatedNode.cpp +++ b/packages/react-native/ReactCxxPlatform/react/renderer/animated/nodes/PropsAnimatedNode.cpp @@ -12,7 +12,6 @@ #include "PropsAnimatedNode.h" #include -#include #include #include #include @@ -31,12 +30,8 @@ bool isLayoutStyleUpdated( if (node->type() == AnimatedNodeType::Style) { if (const auto& styleNode = manager.getAnimatedNode(nodeTag)) { - auto& styleNodeProps = styleNode->getProps(); - for (const auto& styleNodeProp : styleNodeProps.items()) { - if (getDirectManipulationAllowlist().count( - styleNodeProp.first.asString()) == 0u) { - return true; - } + if (styleNode->isLayoutStyleUpdated()) { + return true; } } } @@ -53,9 +48,7 @@ PropsAnimatedNode::PropsAnimatedNode( const folly::dynamic& config, NativeAnimatedNodesManager& manager) : AnimatedNode(tag, config, manager, AnimatedNodeType::Props), - props_(folly::dynamic::object()), - layoutStyleUpdated_(isLayoutStyleUpdated(getConfig()["props"], manager)) { -} + props_(folly::dynamic::object()) {} void PropsAnimatedNode::connectToView(Tag viewTag) { react_native_assert( @@ -145,6 +138,8 @@ void PropsAnimatedNode::update(bool forceFabricCommit) { } } + layoutStyleUpdated_ = isLayoutStyleUpdated(getConfig()["props"], *manager_); + manager_->schedulePropsCommit( connectedViewTag_, props_, layoutStyleUpdated_, forceFabricCommit); } diff --git a/packages/react-native/ReactCxxPlatform/react/renderer/animated/nodes/PropsAnimatedNode.h b/packages/react-native/ReactCxxPlatform/react/renderer/animated/nodes/PropsAnimatedNode.h index c1fde9deeee..da6eb4c4a9a 100644 --- a/packages/react-native/ReactCxxPlatform/react/renderer/animated/nodes/PropsAnimatedNode.h +++ b/packages/react-native/ReactCxxPlatform/react/renderer/animated/nodes/PropsAnimatedNode.h @@ -43,7 +43,7 @@ class PropsAnimatedNode final : public AnimatedNode { private: std::mutex propsMutex_; folly::dynamic props_; - const bool layoutStyleUpdated_; + bool layoutStyleUpdated_{false}; Tag connectedViewTag_{animated::undefinedAnimatedNodeIdentifier}; }; diff --git a/packages/react-native/ReactCxxPlatform/react/renderer/animated/nodes/StyleAnimatedNode.cpp b/packages/react-native/ReactCxxPlatform/react/renderer/animated/nodes/StyleAnimatedNode.cpp index ab18189a5df..a54378ca7a1 100644 --- a/packages/react-native/ReactCxxPlatform/react/renderer/animated/nodes/StyleAnimatedNode.cpp +++ b/packages/react-native/ReactCxxPlatform/react/renderer/animated/nodes/StyleAnimatedNode.cpp @@ -11,12 +11,29 @@ #include "StyleAnimatedNode.h" +#include #include #include #include #include namespace facebook::react { + +namespace { + +bool isLayoutPropsUpdated(const folly::dynamic& props) { + for (const auto& styleNodeProp : props.items()) { + if (getDirectManipulationAllowlist().count( + styleNodeProp.first.asString()) == 0u) { + return true; + } + } + + return false; +} + +} // namespace + StyleAnimatedNode::StyleAnimatedNode( Tag tag, const folly::dynamic& config, @@ -77,5 +94,8 @@ void StyleAnimatedNode::update() { } } } + + layoutStyleUpdated_ = isLayoutPropsUpdated(props_); } + } // namespace facebook::react diff --git a/packages/react-native/ReactCxxPlatform/react/renderer/animated/nodes/StyleAnimatedNode.h b/packages/react-native/ReactCxxPlatform/react/renderer/animated/nodes/StyleAnimatedNode.h index ebb64c2fe5e..53ddd3346a5 100644 --- a/packages/react-native/ReactCxxPlatform/react/renderer/animated/nodes/StyleAnimatedNode.h +++ b/packages/react-native/ReactCxxPlatform/react/renderer/animated/nodes/StyleAnimatedNode.h @@ -28,7 +28,12 @@ class StyleAnimatedNode final : public AnimatedNode { return props_; } + bool isLayoutStyleUpdated() const noexcept { + return layoutStyleUpdated_; + } + private: folly::dynamic props_; + bool layoutStyleUpdated_; }; } // namespace facebook::react