Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@

import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment';

import type {HostInstance} from 'react-native';

Check warning on line 14 in packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js

View workflow job for this annotation

GitHub Actions / test_js (24.4.1)

Requires should be sorted alphabetically, with at least one line between imports/requires and code

Check warning on line 14 in packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js

View workflow job for this annotation

GitHub Actions / test_js (22)

Requires should be sorted alphabetically, with at least one line between imports/requires and code

Check warning on line 14 in packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js

View workflow job for this annotation

GitHub Actions / test_js (20.19.4)

Requires should be sorted alphabetically, with at least one line between imports/requires and code

import ensureInstance from '../../../src/private/__tests__/utilities/ensureInstance';
import * as Fantom from '@react-native/fantom';
import {createRef} from 'react';
import {createRef, useState} from 'react';
import {Animated, useAnimatedValue} from 'react-native';
import ReactNativeElement from 'react-native/src/private/webapis/dom/nodes/ReactNativeElement';
import {allowStyleProp} from 'react-native/Libraries/Animated/NativeAnimatedAllowlist';

test('animated opacity', () => {
let _opacity;
Expand Down Expand Up @@ -73,3 +74,119 @@
<rn-view opacity="0" />,
);
});

test('animate layout props', () => {
const viewRef = createRef<HostInstance>();
allowStyleProp('height');

let _animatedHeight;
let _heightAnimation;

function MyApp() {
const animatedHeight = useAnimatedValue(0);
_animatedHeight = animatedHeight;
return (
<Animated.View
ref={viewRef}
style={[
{
width: 100,
height: animatedHeight,
},
]}
/>
);
}

const root = Fantom.createRoot();

Fantom.runTask(() => {
root.render(<MyApp />);
});

const viewElement = ensureInstance(viewRef.current, ReactNativeElement);

Check warning on line 107 in packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js

View workflow job for this annotation

GitHub Actions / test_js (24.4.1)

'viewElement' is assigned a value but never used

Check warning on line 107 in packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js

View workflow job for this annotation

GitHub Actions / test_js (22)

'viewElement' is assigned a value but never used

Check warning on line 107 in packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js

View workflow job for this annotation

GitHub Actions / test_js (20.19.4)

'viewElement' is assigned a value but never used

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();
});

// TODO: getFabricUpdateProps is not working with the cloneMutliple method
// expect(Fantom.unstable_getFabricUpdateProps(viewElement).height).toBe(100);
expect(root.getRenderedOutput({props: ['height']}).toJSX()).toEqual(
<rn-view height="100.000000" />,
);
});

test('animate layout props and rerender', () => {
const viewRef = createRef<HostInstance>();
allowStyleProp('height');

let _animatedHeight;
let _heightAnimation;
let _setWidth;

function MyApp() {
const animatedHeight = useAnimatedValue(0);
const [width, setWidth] = useState(100);
_animatedHeight = animatedHeight;
_setWidth = setWidth;
return (
<Animated.View
ref={viewRef}
style={[
{
width: width,
height: animatedHeight,
},
]}
/>
);
}

const root = Fantom.createRoot();

Fantom.runTask(() => {
root.render(<MyApp />);
});

const viewElement = ensureInstance(viewRef.current, ReactNativeElement);

Check warning on line 163 in packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js

View workflow job for this annotation

GitHub Actions / test_js (24.4.1)

'viewElement' is assigned a value but never used

Check warning on line 163 in packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js

View workflow job for this annotation

GitHub Actions / test_js (22)

'viewElement' is assigned a value but never used

Check warning on line 163 in packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js

View workflow job for this annotation

GitHub Actions / test_js (20.19.4)

'viewElement' is assigned a value but never used

Fantom.runTask(() => {
_heightAnimation = Animated.timing(_animatedHeight, {
toValue: 100,
duration: 1000,
useNativeDriver: true,
}).start();
});

Fantom.unstable_produceFramesForDuration(500);
expect(root.getRenderedOutput({props: ['height', 'width']}).toJSX()).toEqual(
<rn-view height="50.000000" width="100.000000" />,
);

Fantom.runTask(() => {
_setWidth(200);
});

// TODO: this shouldn't be neccessary since animation should be stopped after duration
Fantom.runTask(() => {
_heightAnimation?.stop();
});

// TODO: getFabricUpdateProps is not working with the cloneMutliple method
// expect(Fantom.unstable_getFabricUpdateProps(viewElement).height).toBe(50);
expect(root.getRenderedOutput({props: ['height', 'width']}).toJSX()).toEqual(
<rn-view height="50.000000" width="200.000000" />,
);
});
14 changes: 14 additions & 0 deletions packages/react-native/Libraries/Animated/nodes/AnimatedProps.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type {AnimatedStyleAllowlist} from './AnimatedStyle';

import NativeAnimatedHelper from '../../../src/private/animated/NativeAnimatedHelper';
import {findNodeHandle} from '../../ReactNative/RendererProxy';
import {getInternalInstanceHandleFromPublicInstance} from '../../ReactPrivate/ReactNativePrivateInterface';
import flattenStyle from '../../StyleSheet/flattenStyle';
import {AnimatedEvent} from '../AnimatedEvent';
import AnimatedNode from './AnimatedNode';
Expand Down Expand Up @@ -248,6 +249,7 @@ export default class AnimatedProps extends AnimatedNode {

if (this._target != null) {
this.#connectAnimatedView(this._target);
this.#connectShadowNode(this._target);
}
}
}
Expand All @@ -259,6 +261,7 @@ export default class AnimatedProps extends AnimatedNode {
this._target = {instance, connectedViewTag: null};
if (this.__isNative) {
this.#connectAnimatedView(this._target);
this.#connectShadowNode(this._target);
}
}

Expand All @@ -279,6 +282,17 @@ export default class AnimatedProps extends AnimatedNode {
target.connectedViewTag = viewTag;
}

#connectShadowNode(target: TargetView): void {
invariant(this.__isNative, 'Expected node to be marked as "native"');
let shadowNode = getInternalInstanceHandleFromPublicInstance(
target.instance,
).stateNode.node;
NativeAnimatedHelper.API.connectAnimatedNodeToShadowNode(
this.__getNativeTag(),
shadowNode,
);
}

#disconnectAnimatedView(target: TargetView): void {
invariant(this.__isNative, 'Expected node to be marked as "native"');
const viewTag = target.connectedViewTag;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,11 @@ - (void)setSurfacePresenter:(id<RCTSurfacePresenterStub>)surfacePresenter
}];
}

RCT_EXPORT_METHOD(connectAnimatedNodeToShadowNode:(double)nodeTag shadowNode:(NSDictionary *)shadowNode)
{

}

RCT_EXPORT_METHOD(disconnectAnimatedNodeFromView : (double)nodeTag viewTag : (double)viewTag)
{
[self queueOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,17 @@ void AnimatedModule::connectAnimatedNodeToView(
ConnectAnimatedNodeToViewOp{.nodeTag = nodeTag, .viewTag = viewTag});
}

void AnimatedModule::connectAnimatedNodeToShadowNode(
jsi::Runtime& rt,
Tag nodeTag,
jsi::Object shadowNode) {
auto nativeState = shadowNode.getNativeState(rt);
auto shadowNodeWrapper =
std::dynamic_pointer_cast<ShadowNodeWrapper>(nativeState);
operations_.emplace_back(ConnectAnimatedNodeToShadowNodeOp{
.nodeTag = nodeTag, .shadowNode = shadowNodeWrapper->shadowNode});
}

void AnimatedModule::disconnectAnimatedNodeFromView(
jsi::Runtime& /*rt*/,
Tag nodeTag,
Expand Down Expand Up @@ -270,6 +281,11 @@ void AnimatedModule::executeOperation(const Operation& operation) {
nodesManager_->extractAnimatedNodeOffsetOp(op.nodeTag);
} else if constexpr (std::is_same_v<T, ConnectAnimatedNodeToViewOp>) {
nodesManager_->connectAnimatedNodeToView(op.nodeTag, op.viewTag);
} else if constexpr (std::is_same_v<
T,
ConnectAnimatedNodeToShadowNodeOp>) {
nodesManager_->connectAnimatedNodeToShadowNode(
op.nodeTag, op.shadowNode);
} else if constexpr (std::is_same_v<
T,
DisconnectAnimatedNodeFromViewOp>) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ class AnimatedModule : public NativeAnimatedModuleCxxSpec<AnimatedModule>,
Tag viewTag{};
};

struct ConnectAnimatedNodeToShadowNodeOp {
Tag nodeTag{};
ShadowNode::Shared shadowNode{};
};

struct DisconnectAnimatedNodeFromViewOp {
Tag nodeTag{};
Tag viewTag{};
Expand Down Expand Up @@ -125,6 +130,7 @@ class AnimatedModule : public NativeAnimatedModuleCxxSpec<AnimatedModule>,
SetAnimatedNodeOffsetOp,
SetAnimatedNodeValueOp,
ConnectAnimatedNodeToViewOp,
ConnectAnimatedNodeToShadowNodeOp,
DisconnectAnimatedNodeFromViewOp,
RestoreDefaultValuesOp,
FlattenAnimatedNodeOffsetOp,
Expand Down Expand Up @@ -178,6 +184,11 @@ class AnimatedModule : public NativeAnimatedModuleCxxSpec<AnimatedModule>,

void connectAnimatedNodeToView(jsi::Runtime& rt, Tag nodeTag, Tag viewTag);

void connectAnimatedNodeToShadowNode(
jsi::Runtime& rt,
Tag nodeTag,
jsi::Object shadowNode);

void
disconnectAnimatedNodeFromView(jsi::Runtime& rt, Tag nodeTag, Tag viewTag);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,20 @@ void NativeAnimatedNodesManager::connectAnimatedNodeToView(
}
}

void NativeAnimatedNodesManager::connectAnimatedNodeToShadowNode(
Tag propsNodeTag,
ShadowNode::Shared shadowNode) noexcept {
react_native_assert(propsNodeTag);
auto node = getAnimatedNode<PropsAnimatedNode>(propsNodeTag);
if (node != nullptr) {
node->connectToShadowNode(shadowNode);
updatedNodeTags_.insert(node->tag());
} else {
LOG(WARNING)
<< "Cannot ConnectAnimatedNodeToShadowNode, animated node has to be props type";
}
}

void NativeAnimatedNodesManager::disconnectAnimatedNodeFromView(
Tag propsNodeTag,
Tag viewTag) noexcept {
Expand Down Expand Up @@ -861,10 +875,14 @@ void NativeAnimatedNodesManager::schedulePropsCommit(
Tag viewTag,
const folly::dynamic& props,
bool layoutStyleUpdated,
bool forceFabricCommit) noexcept {
bool forceFabricCommit,
ShadowNode::Shared shadowNode) noexcept {
if (ReactNativeFeatureFlags::useSharedAnimatedBackend()) {
if (layoutStyleUpdated) {
mergeObjects(updateViewProps_[viewTag], props);
if (shadowNode) {
tagToShadowNode_[viewTag] = std::move(shadowNode);
}
} else {
mergeObjects(updateViewPropsDirect_[viewTag], props);
}
Expand Down Expand Up @@ -975,6 +993,28 @@ AnimationMutations NativeAnimatedNodesManager::pullAnimationMutations() {
AnimationMutation{tag, nullptr, propsBuilder.get()});
containsChange = true;
}
for (auto& [tag, props] : updateViewProps_) {
auto node = tagToShadowNode_.find(tag);
if (node != tagToShadowNode_.end()) {
if (props.find("width") != props.items().end()) {
propsBuilder.setWidth(
yoga::Style::SizeLength::points(props["width"].asDouble()));
}
if (props.find("height") != props.items().end()) {
propsBuilder.setHeight(
yoga::Style::SizeLength::points(props["height"].asDouble()));
}
// propsBuilder.storeDynamic(props);
mutations.push_back(AnimationMutation{
tag, &node->second->getFamily(), propsBuilder.get()});
}
containsChange = true;
}
if (containsChange) {
updateViewPropsDirect_.clear();
updateViewProps_.clear();
tagToShadowNode_.clear();
}
}

if (!containsChange) {
Expand All @@ -994,7 +1034,8 @@ AnimationMutations NativeAnimatedNodesManager::pullAnimationMutations() {
}
}

// Step 2: update all nodes that are connected to the finished animations.
// Step 2: update all nodes that are connected to the finished
// animations.
updateNodes(finishedAnimationValueNodes);

isEventAnimationInProgress_ = false;
Expand All @@ -1005,6 +1046,14 @@ AnimationMutations NativeAnimatedNodesManager::pullAnimationMutations() {
mutations.push_back(
AnimationMutation{tag, nullptr, propsBuilder.get()});
}
for (auto& [tag, props] : updateViewProps_) {
auto node = tagToShadowNode_.find(tag);
if (node != tagToShadowNode_.end()) {
propsBuilder.storeDynamic(props);
mutations.push_back(AnimationMutation{
tag, &node->second->getFamily(), propsBuilder.get()});
}
}
}
} else {
// There is no active animation. Stop the render callback.
Expand Down Expand Up @@ -1073,7 +1122,8 @@ void NativeAnimatedNodesManager::onRender() {
}
}

// Step 2: update all nodes that are connected to the finished animations.
// Step 2: update all nodes that are connected to the finished
// animations.
updateNodes(finishedAnimationValueNodes);

isEventAnimationInProgress_ = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ class NativeAnimatedNodesManager {

void connectAnimatedNodeToView(Tag propsNodeTag, Tag viewTag) noexcept;

void connectAnimatedNodeToShadowNode(
Tag propsNodeTag,
ShadowNode::Shared node) noexcept;

void disconnectAnimatedNodes(Tag parentTag, Tag childTag) noexcept;

void disconnectAnimatedNodeFromView(Tag propsNodeTag, Tag viewTag) noexcept;
Expand Down Expand Up @@ -158,7 +162,8 @@ class NativeAnimatedNodesManager {
Tag viewTag,
const folly::dynamic& props,
bool layoutStyleUpdated,
bool forceFabricCommit) noexcept;
bool forceFabricCommit,
ShadowNode::Shared shadowNode = nullptr) noexcept;

/**
* Commits all pending animated property updates to their respective views.
Expand Down Expand Up @@ -266,6 +271,7 @@ class NativeAnimatedNodesManager {

std::unordered_map<Tag, folly::dynamic> updateViewProps_{};
std::unordered_map<Tag, folly::dynamic> updateViewPropsDirect_{};
std::unordered_map<Tag, ShadowNode::Shared> tagToShadowNode_{};

/*
* Sometimes a view is not longer connected to a PropsAnimatedNode, but
Expand Down
Loading
Loading