diff --git a/change/react-native-windows-8d24c2d8-08f9-4612-a46d-8bf25b0f020d.json b/change/react-native-windows-8d24c2d8-08f9-4612-a46d-8bf25b0f020d.json new file mode 100644 index 00000000000..da6dd0cddc0 --- /dev/null +++ b/change/react-native-windows-8d24c2d8-08f9-4612-a46d-8bf25b0f020d.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "Implement overflow property support for Fabric architecture", + "packageName": "react-native-windows", + "email": "nitchaudhary@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp index 03f4bb0cb2c..db8ff2ca6b6 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp @@ -1070,6 +1070,12 @@ void ViewComponentView::ensureVisual() noexcept { } OuterVisual().InsertAt(m_visual, 0); } + + // Create m_contentVisual as a child of m_visual if not already created + if (!m_contentVisual) { + m_contentVisual = m_compContext.CreateSpriteVisual(); + m_visual.InsertAt(m_contentVisual, 0); // Insert at index 0 so it's below border visuals + } } winrt::Microsoft::ReactNative::ComponentView ViewComponentView::Create( @@ -1084,7 +1090,8 @@ winrt::Microsoft::ReactNative::Composition::Experimental::IVisual ViewComponentView::VisualToMountChildrenInto() noexcept { if (m_builder && m_builder->VisualToMountChildrenIntoHandler()) return m_builder->VisualToMountChildrenIntoHandler()(*this); - return Visual(); + // Mount children into m_contentVisual + return m_contentVisual ? m_contentVisual : Visual(); } void ViewComponentView::MountChildComponentView( @@ -1140,6 +1147,21 @@ void ViewComponentView::updateProps( // update BaseComponentView props updateAccessibilityProps(oldViewProps, newViewProps); updateTransformProps(oldViewProps, newViewProps, Visual()); + + // Handle overflow property changes + if (oldViewProps.yogaStyle.overflow() != newViewProps.yogaStyle.overflow()) { + auto compVisual = + winrt::Microsoft::ReactNative::Composition::Experimental::MicrosoftCompositionContextHelper::InnerVisual( + Visual()); + if (compVisual) { + if (newViewProps.yogaStyle.overflow() == facebook::yoga::Overflow::Hidden) { + compVisual.Clip(Compositor().CreateInsetClip(0.0f, 0.0f, 0.0f, 0.0f)); + } else { + compVisual.Clip(nullptr); + } + } + } + base_type::updateProps(props, oldProps); m_props = std::static_pointer_cast(props); @@ -1317,6 +1339,48 @@ void ViewComponentView::updateLayoutMetrics( Visual().Size( {layoutMetrics.frame.size.width * layoutMetrics.pointScaleFactor, layoutMetrics.frame.size.height * layoutMetrics.pointScaleFactor}); + + // Size and offset m_contentVisual to match the content area, excluding borders + if (m_contentVisual && m_props) { + auto borderMetrics = BorderPrimitive::resolveAndAlignBorderMetrics(layoutMetrics, *m_props); + float borderLeft = borderMetrics.borderWidths.left; + float borderTop = borderMetrics.borderWidths.top; + float borderRight = borderMetrics.borderWidths.right; + float borderBottom = borderMetrics.borderWidths.bottom; + float scale = layoutMetrics.pointScaleFactor; + float contentWidth = layoutMetrics.frame.size.width * scale - borderLeft - borderRight; + float contentHeight = layoutMetrics.frame.size.height * scale - borderTop - borderBottom; + m_contentVisual.Offset({borderLeft, borderTop, 0}); + m_contentVisual.Size({std::max(0.f, contentWidth), std::max(0.f, contentHeight)}); + + // Apply clipping to m_contentVisual for overflow: hidden + if (m_props->getClipsContentToBounds()) { + // Calculate inner border radii for clipping (0 for rectangular clip) + facebook::react::RectangleCorners innerRadii; + innerRadii.topLeft = { + std::max(0.f, borderMetrics.borderRadii.topLeft.horizontal - borderLeft), + std::max(0.f, borderMetrics.borderRadii.topLeft.vertical - borderTop)}; + innerRadii.topRight = { + std::max(0.f, borderMetrics.borderRadii.topRight.horizontal - borderRight), + std::max(0.f, borderMetrics.borderRadii.topRight.vertical - borderTop)}; + innerRadii.bottomLeft = { + std::max(0.f, borderMetrics.borderRadii.bottomLeft.horizontal - borderLeft), + std::max(0.f, borderMetrics.borderRadii.bottomLeft.vertical - borderBottom)}; + innerRadii.bottomRight = { + std::max(0.f, borderMetrics.borderRadii.bottomRight.horizontal - borderRight), + std::max(0.f, borderMetrics.borderRadii.bottomRight.vertical - borderBottom)}; + + winrt::com_ptr pathGeometry = BorderPrimitive::GenerateRoundedRectPathGeometry( + m_compContext, innerRadii, {0, 0, 0, 0}, {0, 0, std::max(0.f, contentWidth), std::max(0.f, contentHeight)}); + + m_contentVisual.as<::Microsoft::ReactNative::Composition::Experimental::IVisualInterop>()->SetClippingPath( + pathGeometry.get()); + } else { + // Clear any existing clip + m_contentVisual.as<::Microsoft::ReactNative::Composition::Experimental::IVisualInterop>()->SetClippingPath( + nullptr); + } + } } void ViewComponentView::prepareForRecycle() noexcept {} diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.h b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.h index 588af7ad463..5de28793a03 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.h +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.h @@ -231,6 +231,7 @@ struct ViewComponentView : public ViewComponentViewT< bool m_hasNonVisualChildren{false}; facebook::react::SharedViewProps m_props; winrt::Microsoft::ReactNative::Composition::Experimental::IVisual m_visual{nullptr}; + winrt::Microsoft::ReactNative::Composition::Experimental::IVisual m_contentVisual{nullptr}; winrt::Microsoft::ReactNative::Composition::Experimental::CreateInternalVisualDelegate m_createInternalVisualHandler{ nullptr}; };