diff --git a/packages/react-native/Libraries/Animated/__tests__/Animated-itest.js b/packages/react-native/Libraries/Animated/__tests__/Animated-itest.js
index a53ad8b8100..1a01d2fe500 100644
--- a/packages/react-native/Libraries/Animated/__tests__/Animated-itest.js
+++ b/packages/react-native/Libraries/Animated/__tests__/Animated-itest.js
@@ -197,3 +197,69 @@ test('animated opacity', () => {
,
);
});
+
+test('moving box by 50 points with offset 10', () => {
+ let _translateX;
+ const viewRef = createRef();
+
+ function MyApp() {
+ const translateX = useAnimatedValue(0);
+ _translateX = translateX;
+ return (
+
+ );
+ }
+
+ const root = Fantom.createRoot();
+
+ Fantom.runTask(() => {
+ root.render();
+ });
+
+ const viewElement = ensureInstance(viewRef.current, ReactNativeElement);
+
+ expect(viewElement.getBoundingClientRect().x).toBe(0);
+
+ Fantom.runTask(() => {
+ Animated.timing(_translateX, {
+ toValue: 50,
+ duration: 1000, // 1 second
+ useNativeDriver: true,
+ }).start();
+ });
+
+ Fantom.runTask(() => {
+ _translateX.setOffset(10);
+ });
+
+ Fantom.unstable_produceFramesForDuration(500);
+
+ // shadow tree is not synchronised yet, position X is still 0.
+ expect(viewElement.getBoundingClientRect().x).toBe(0);
+
+ expect(
+ // $FlowFixMe[incompatible-use]
+ Fantom.unstable_getDirectManipulationProps(viewElement).transform[0]
+ .translateX,
+ ).toBeCloseTo(35, 0.001);
+
+ Fantom.unstable_produceFramesForDuration(500);
+
+ expect(
+ // $FlowFixMe[incompatible-use]
+ Fantom.unstable_getDirectManipulationProps(viewElement).transform[0]
+ .translateX,
+ ).toBeCloseTo(60, 0.001);
+
+ // TODO: this shouldn't be neccessary but C++ Animated still schedules a React state update.
+ Fantom.runWorkLoop();
+});
diff --git a/packages/react-native/ReactCxxPlatform/react/renderer/animated/AnimatedModule.cpp b/packages/react-native/ReactCxxPlatform/react/renderer/animated/AnimatedModule.cpp
index b2535bfd2a2..ec5dda3f049 100644
--- a/packages/react-native/ReactCxxPlatform/react/renderer/animated/AnimatedModule.cpp
+++ b/packages/react-native/ReactCxxPlatform/react/renderer/animated/AnimatedModule.cpp
@@ -123,9 +123,10 @@ void AnimatedModule::setAnimatedNodeValue(
void AnimatedModule::setAnimatedNodeOffset(
jsi::Runtime& /*rt*/,
- Tag /*nodeTag*/,
- double /*offset*/) {
- // TODO(T196512946): missing implementation
+ Tag nodeTag,
+ double offset) {
+ operations_.emplace_back(
+ SetAnimatedNodeOffsetOp{.nodeTag = nodeTag, .offset = offset});
}
void AnimatedModule::flattenAnimatedNodeOffset(
@@ -245,6 +246,8 @@ void AnimatedModule::executeOperation(const Operation& operation) {
nodesManager_->stopAnimation(op.animationId, false);
} else if constexpr (std::is_same_v) {
nodesManager_->setAnimatedNodeValue(op.nodeTag, op.value);
+ } else if constexpr (std::is_same_v) {
+ nodesManager_->setAnimatedNodeOffset(op.nodeTag, op.offset);
} else if constexpr (std::is_same_v) {
nodesManager_->connectAnimatedNodeToView(op.nodeTag, op.viewTag);
} else if constexpr (std::is_same_v<
diff --git a/packages/react-native/ReactCxxPlatform/react/renderer/animated/AnimatedModule.h b/packages/react-native/ReactCxxPlatform/react/renderer/animated/AnimatedModule.h
index a164c59d2ee..b736365c87c 100644
--- a/packages/react-native/ReactCxxPlatform/react/renderer/animated/AnimatedModule.h
+++ b/packages/react-native/ReactCxxPlatform/react/renderer/animated/AnimatedModule.h
@@ -66,6 +66,11 @@ class AnimatedModule : public NativeAnimatedModuleCxxSpec,
double value{};
};
+ struct SetAnimatedNodeOffsetOp {
+ Tag nodeTag{};
+ double offset{};
+ };
+
struct ConnectAnimatedNodeToViewOp {
Tag nodeTag{};
Tag viewTag{};
@@ -105,6 +110,7 @@ class AnimatedModule : public NativeAnimatedModuleCxxSpec,
DisconnectAnimatedNodesOp,
StartAnimatingNodeOp,
StopAnimationOp,
+ SetAnimatedNodeOffsetOp,
SetAnimatedNodeValueOp,
ConnectAnimatedNodeToViewOp,
DisconnectAnimatedNodeFromViewOp,
diff --git a/packages/react-native/ReactCxxPlatform/react/renderer/animated/NativeAnimatedNodesManager.cpp b/packages/react-native/ReactCxxPlatform/react/renderer/animated/NativeAnimatedNodesManager.cpp
index 4c5eb9b66ea..e4f2f40d315 100644
--- a/packages/react-native/ReactCxxPlatform/react/renderer/animated/NativeAnimatedNodesManager.cpp
+++ b/packages/react-native/ReactCxxPlatform/react/renderer/animated/NativeAnimatedNodesManager.cpp
@@ -246,6 +246,14 @@ void NativeAnimatedNodesManager::setAnimatedNodeValue(Tag tag, double value) {
}
}
+void NativeAnimatedNodesManager::setAnimatedNodeOffset(Tag tag, double offset) {
+ if (auto node = getAnimatedNode(tag)) {
+ if (node->setOffset(offset)) {
+ updatedNodeTags_.insert(node->tag());
+ }
+ }
+}
+
void NativeAnimatedNodesManager::stopAnimationsForNode(Tag nodeTag) {
std::vector discardedAnimIds{};
diff --git a/packages/react-native/ReactCxxPlatform/react/renderer/animated/NativeAnimatedNodesManager.h b/packages/react-native/ReactCxxPlatform/react/renderer/animated/NativeAnimatedNodesManager.h
index bcc40e7ed56..be83cf76aae 100644
--- a/packages/react-native/ReactCxxPlatform/react/renderer/animated/NativeAnimatedNodesManager.h
+++ b/packages/react-native/ReactCxxPlatform/react/renderer/animated/NativeAnimatedNodesManager.h
@@ -96,6 +96,8 @@ class NativeAnimatedNodesManager {
void setAnimatedNodeValue(Tag tag, double value);
+ void setAnimatedNodeOffset(Tag tag, double offset);
+
// drivers
void startAnimatingNode(