fix[LayoutableShadowNode::findNodeAtPoint]: consider layoutMetrics.overflowInset (#49701)

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

# Changelog:
[General] [Fixed] - findNodeAtPoint now considers overflow area of the parent node

This may happen when the child overflows the parent without resizing it. For example, when child has `position: 'absolute'` and absolute coordinates.

See
- https://fb.workplace.com/groups/rn.debugger.feedback/posts/1075015864052657/
- https://fb.workplace.com/groups/rn.debugger.feedback/permalink/1180445730176336/
- https://fb.workplace.com/groups/ford.eng.discussions/permalink/1961390260971262/

for more context

Reviewed By: NickGerleman

Differential Revision: D63328182

fbshipit-source-id: 9d5ed3ec8d88d8d65455aa3664588dc7d7273888
This commit is contained in:
Ruslan Lesiutin
2025-02-27 03:20:01 -08:00
committed by Facebook GitHub Bot
parent b95424d159
commit d8bc7c68c0
2 changed files with 46 additions and 5 deletions
@@ -261,15 +261,22 @@ ShadowNode::Shared LayoutableShadowNode::findNodeAtPoint(
return nullptr;
}
auto layoutMetrics = layoutableShadowNode->getLayoutMetrics();
auto transform = layoutableShadowNode->getTransform();
auto frame = layoutableShadowNode->getLayoutMetrics().frame;
auto transformedFrame = frame * transform;
auto transformedFrame = layoutMetrics.frame * transform;
auto isPointInside = transformedFrame.containsPoint(point);
if (!isPointInside) {
return nullptr;
} else if (!layoutableShadowNode->canChildrenBeTouchTarget()) {
if (isPointInside && !layoutableShadowNode->canChildrenBeTouchTarget()) {
return node;
} else if (!isPointInside) {
auto overflowFrame =
insetBy(layoutMetrics.frame, layoutMetrics.overflowInset);
auto transformedOverflowFrame = overflowFrame * transform;
// If child overflows parent, the touch may be intercepted by the child
// only, so we should continue recursing.
if (!transformedOverflowFrame.containsPoint(point)) {
return nullptr;
}
}
if (Transform::isVerticalInversion(transform) ||
@@ -415,3 +415,37 @@ TEST(FindNodeAtPointTest, invertedList) {
->getTag(),
2);
}
TEST(FindNodeAtPointTest, considersOverflowAreaOfTheParent) {
auto builder = simpleComponentBuilder();
auto element =
Element<ViewShadowNode>()
.tag(1)
.finalize([](ViewShadowNode& shadowNode) {
auto layoutMetrics = EmptyLayoutMetrics;
layoutMetrics.frame.size = {100, 100};
shadowNode.setLayoutMetrics(layoutMetrics);
})
.children({Element<ViewShadowNode>()
.tag(2)
.finalize([](ViewShadowNode& shadowNode) {
auto layoutMetrics = EmptyLayoutMetrics;
layoutMetrics.frame.size = {100, 0};
layoutMetrics.overflowInset = {0, 0, 0, -100};
shadowNode.setLayoutMetrics(layoutMetrics);
})
.children({Element<ViewShadowNode>().tag(3).finalize(
[](ViewShadowNode& shadowNode) {
auto layoutMetrics = EmptyLayoutMetrics;
layoutMetrics.frame.size = {100, 100};
shadowNode.setLayoutMetrics(layoutMetrics);
})})});
auto parentShadowNode = builder.build(element);
EXPECT_EQ(
LayoutableShadowNode::findNodeAtPoint(parentShadowNode, {1, 99})
->getTag(),
3);
}