implement Value.extractOffset in C++ Animated (#52049)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52049

changelog: [internal]

As title says, add implementation of [Value.extractOffset](https://reactnative.dev/docs/animatedvalue#extractoffset)

Reviewed By: zeyap

Differential Revision: D76735003

fbshipit-source-id: db09e2ed1c8b540d911970d2268fcd0374717e96
This commit is contained in:
Samuel Susla
2025-06-16 14:30:19 -07:00
committed by Facebook GitHub Bot
parent cb24596c33
commit 2528e48e93
6 changed files with 108 additions and 4 deletions
@@ -355,3 +355,91 @@ describe('Value.flattenOffset', () => {
Fantom.runWorkLoop();
});
});
describe('Value.extractOffset', () => {
it('sets the offset value to the base value and resets the base value to zero', () => {
const scrollViewRef = createRef<HostInstance>();
const viewRef = createRef<HostInstance>();
let _onScroll;
function PressableWithNativeDriver() {
_onScroll = useAnimatedValue(0);
return (
<View style={{flex: 1}}>
<Animated.View
ref={viewRef}
style={{
position: 'absolute',
width: 10,
height: 10,
transform: [{translateY: _onScroll}],
}}
/>
<Animated.ScrollView
ref={scrollViewRef}
onScroll={Animated.event(
[
{
nativeEvent: {
contentOffset: {
y: _onScroll,
},
},
},
],
{useNativeDriver: true},
)}>
<View style={{height: 1000, width: 100}} />
</Animated.ScrollView>
</View>
);
}
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(<PressableWithNativeDriver />);
});
const scrollViewelement = ensureInstance(
scrollViewRef.current,
ReactNativeElement,
);
const viewElement = ensureInstance(viewRef.current, ReactNativeElement);
Fantom.scrollTo(scrollViewelement, {
x: 0,
y: 10,
});
Fantom.runTask(() => {
_onScroll.setOffset(15);
// Sets offset to be 15 + 10 = 25 (this is not observable from JS).
_onScroll.extractOffset();
});
let transform =
// $FlowFixMe[incompatible-use]
Fantom.unstable_getDirectManipulationProps(viewElement).transform[0];
// Animated value is now 0 but offset is 25. The final value is 25.
expect(transform.translateY).toBeCloseTo(25, 0.001);
Fantom.runTask(() => {
// Sets offset 35, overriding the previous `setOffset`.
// Due to `extractOffset`, base value was restarted to 0.
_onScroll.setOffset(35);
});
transform =
// $FlowFixMe[incompatible-use]
Fantom.unstable_getDirectManipulationProps(viewElement).transform[0];
// `extractOffset` resets value back to 0.
// Previously we set offset to 35. The final value is 35.
expect(transform.translateY).toBeCloseTo(35, 0.001);
// TODO: this shouldn't be neccessary.
Fantom.runWorkLoop();
});
});
@@ -137,8 +137,8 @@ void AnimatedModule::flattenAnimatedNodeOffset(
void AnimatedModule::extractAnimatedNodeOffset(
jsi::Runtime& /*rt*/,
Tag /*nodeTag*/) {
// TODO(T196513004): missing implementation
Tag nodeTag) {
operations_.push_back(ExtractAnimatedNodeOffsetOp({.nodeTag = nodeTag}));
}
void AnimatedModule::connectAnimatedNodeToView(
@@ -253,6 +253,8 @@ void AnimatedModule::executeOperation(const Operation& operation) {
nodesManager_->setAnimatedNodeOffset(op.nodeTag, op.offset);
} else if constexpr (std::is_same_v<T, FlattenAnimatedNodeOffsetOp>) {
nodesManager_->flattenAnimatedNodeOffset(op.nodeTag);
} else if constexpr (std::is_same_v<T, ExtractAnimatedNodeOffsetOp>) {
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<
@@ -75,6 +75,10 @@ class AnimatedModule : public NativeAnimatedModuleCxxSpec<AnimatedModule>,
Tag nodeTag{};
};
struct ExtractAnimatedNodeOffsetOp {
Tag nodeTag{};
};
struct ConnectAnimatedNodeToViewOp {
Tag nodeTag{};
Tag viewTag{};
@@ -120,6 +124,7 @@ class AnimatedModule : public NativeAnimatedModuleCxxSpec<AnimatedModule>,
DisconnectAnimatedNodeFromViewOp,
RestoreDefaultValuesOp,
FlattenAnimatedNodeOffsetOp,
ExtractAnimatedNodeOffsetOp,
DropAnimatedNodeOp,
AddAnimatedEventToViewOp,
RemoveAnimatedEventFromViewOp>;
@@ -260,6 +260,12 @@ void NativeAnimatedNodesManager::flattenAnimatedNodeOffset(Tag tag) {
}
}
void NativeAnimatedNodesManager::extractAnimatedNodeOffsetOp(Tag tag) {
if (auto node = getAnimatedNode<ValueAnimatedNode>(tag)) {
node->extractOffset();
}
}
void NativeAnimatedNodesManager::stopAnimationsForNode(Tag nodeTag) {
std::vector<int> discardedAnimIds{};
@@ -98,6 +98,8 @@ class NativeAnimatedNodesManager {
void flattenAnimatedNodeOffset(Tag tag);
void extractAnimatedNodeOffsetOp(Tag tag);
void setAnimatedNodeOffset(Tag tag, double offset);
// drivers
@@ -71,8 +71,9 @@ void ValueAnimatedNode::flattenOffset() noexcept {
}
void ValueAnimatedNode::extractOffset() noexcept {
setOffset(value_ + getOffset());
setRawValue(0.0f);
offset_ += value_;
value_ = 0;
onValueUpdate();
}
void ValueAnimatedNode::onValueUpdate() noexcept {