diff --git a/packages/react-native/ReactCommon/react/renderer/core/LayoutableShadowNode.cpp b/packages/react-native/ReactCommon/react/renderer/core/LayoutableShadowNode.cpp index 86e78064cb6..565c1a2e0f5 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/LayoutableShadowNode.cpp +++ b/packages/react-native/ReactCommon/react/renderer/core/LayoutableShadowNode.cpp @@ -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) || diff --git a/packages/react-native/ReactCommon/react/renderer/core/tests/FindNodeAtPointTest.cpp b/packages/react-native/ReactCommon/react/renderer/core/tests/FindNodeAtPointTest.cpp index c167f86591a..0776acf8983 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/tests/FindNodeAtPointTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/core/tests/FindNodeAtPointTest.cpp @@ -415,3 +415,37 @@ TEST(FindNodeAtPointTest, invertedList) { ->getTag(), 2); } + +TEST(FindNodeAtPointTest, considersOverflowAreaOfTheParent) { + auto builder = simpleComponentBuilder(); + + auto element = + Element() + .tag(1) + .finalize([](ViewShadowNode& shadowNode) { + auto layoutMetrics = EmptyLayoutMetrics; + layoutMetrics.frame.size = {100, 100}; + shadowNode.setLayoutMetrics(layoutMetrics); + }) + .children({Element() + .tag(2) + .finalize([](ViewShadowNode& shadowNode) { + auto layoutMetrics = EmptyLayoutMetrics; + layoutMetrics.frame.size = {100, 0}; + layoutMetrics.overflowInset = {0, 0, 0, -100}; + + shadowNode.setLayoutMetrics(layoutMetrics); + }) + .children({Element().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); +}