Fix XAML popup positioning and light dismiss in ScrollView (#15557)#15564
Conversation
…#15557) - Add SetXamlRoot() API on ContentIslandComponentView for 3rd party XAML components - Add DismissPopups() using VisualTreeHelper.GetOpenPopupsForXamlRoot() - Fire LayoutMetricsChanged on scroll to update popup positions - Dismiss child ContentIsland popups when scroll begins - Add ComboBox sample component demonstrating the pattern - Add xamlPopupBug test sample for playground-composition
sundaramramaswamy
left a comment
There was a problem hiding this comment.
Please address comments.
…#15557) - Set LocalToParentTransformMatrix synchronously before Connect() to fix initial position - Fire LayoutMetricsChanged on scroll to update position after scrolling - Add DismissPopupsRequest event for 3P components to implement light dismiss - Update ComboBox sample to use event-based approach (core remains XAML-agnostic)
Issue microsoft#15557: Fix popup positioning for XAML controls in ScrollView Bug 1 (Position Fix): - Convert getClientRect() physical pixels to DIPs by dividing by pointScaleFactor - LocalToParentTransformMatrix expects logical pixels (DIPs), not physical pixels - Update transform synchronously instead of async to avoid race conditions - Set initial transform in OnMounted() before Connect() Bug 2 (Light Dismiss Fix): - Add DismissPopupsRequest event for 3P components to handle popup dismissal - ScrollView fires event when scroll begins via DismissChildContentIslandPopups() - Core remains XAML-agnostic - 3P components handle their own popups
sundaramramaswamy
left a comment
There was a problem hiding this comment.
Please revert formatting changes.
f0d7eb5 to
e8cfe99
Compare
vnext/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.cpp
Show resolved
Hide resolved
vnext/Microsoft.ReactNative/Fabric/Composition/ScrollViewComponentView.cpp
Outdated
Show resolved
Hide resolved
|
Assuming LocalToParentTransformMatrix is pretty cheap, the LocalToParentTransformMatrix changes look good. The light dismiss logic seems more wrong. This looks like we need additional hosting APIs from ContentIslands. ContentIslands should provide APIs to handle this kind of communication, and Xaml / RNW should talk to the new API. Xaml content islands should not have to know about RNW APIs - it breaks the ContentIsland encapsulation. |
vnext/Microsoft.ReactNative/Fabric/Composition/ScrollViewComponentView.cpp
Outdated
Show resolved
Hide resolved
vnext/Microsoft.ReactNative/Fabric/Composition/ScrollViewComponentView.cpp
Outdated
Show resolved
Hide resolved
|
@acoates-ms I have addressed all comments, please review it. |
* docs: fix broken markdown links * Address reviewer feedback * Remove obsolete docs and clean conflict markers * Delete managedCodeGen.md --------- Co-authored-by: Protik Biswas <protikbiswas@microsoft.com> Co-authored-by: Vineeth <66076509+vineethkuttan@users.noreply.github.com>
* Use automationoption as frameworkbased for childsite * Change files * remove tokens associated with fragment based
LocalToParentTransformMatrix: Yes, it's a cheap call - just sets a 4x4 matrix value. Confirmed working correctly. Light dismiss architecture: I agree this isn't ideal from an encapsulation perspective. I've implemented an event-based approach that keeps RNW core XAML-agnostic:
The 3rd party XAML component subscribes to You're right that ideally ContentIsland platform should provide this natively - perhaps via For now, this workaround unblocks the immediate customer scenario while maintaining XAML-agnostic core code. |
505103b to
db01f95
Compare
vnext/Microsoft.ReactNative/Fabric/Composition/ScrollViewComponentView.cpp
Outdated
Show resolved
Hide resolved
…ent, move light dismiss to 3P component Addresses acoates-ms review comments: 1. Replace FireLayoutMetricsChangedForScrollPositionChange with proper ViewChanged event on ScrollViewComponentView (similar to XAML ScrollViewer.ViewChanged) 2. Remove DismissPopupsRequest from ContentIslandComponentView - light dismiss logic moved to ComboBoxComponentView which walks parent tree on mount 3. ContentIslandComponentView subscribes to ViewChanged for transform updates 4. 3P component (ComboBox) subscribes to ViewChanged for popup dismiss
…#15557) (microsoft#15564) * Fix XAML popup positioning and light dismiss in ScrollView (microsoft#15557) - Add SetXamlRoot() API on ContentIslandComponentView for 3rd party XAML components - Add DismissPopups() using VisualTreeHelper.GetOpenPopupsForXamlRoot() - Fire LayoutMetricsChanged on scroll to update popup positions - Dismiss child ContentIsland popups when scroll begins - Add ComboBox sample component demonstrating the pattern - Add xamlPopupBug test sample for playground-composition * Change files * Fix XAML popup positioning and light dismiss in ScrollView (microsoft#15557) - Set LocalToParentTransformMatrix synchronously before Connect() to fix initial position - Fire LayoutMetricsChanged on scroll to update position after scrolling - Add DismissPopupsRequest event for 3P components to implement light dismiss - Update ComboBox sample to use event-based approach (core remains XAML-agnostic) * Fix XAML popup positioning - convert to DIPs and update synchronously Issue microsoft#15557: Fix popup positioning for XAML controls in ScrollView Bug 1 (Position Fix): - Convert getClientRect() physical pixels to DIPs by dividing by pointScaleFactor - LocalToParentTransformMatrix expects logical pixels (DIPs), not physical pixels - Update transform synchronously instead of async to avoid race conditions - Set initial transform in OnMounted() before Connect() Bug 2 (Light Dismiss Fix): - Add DismissPopupsRequest event for 3P components to handle popup dismissal - ScrollView fires event when scroll begins via DismissChildContentIslandPopups() - Core remains XAML-agnostic - 3P components handle their own popups * Apply clang-format * Revert IDL formatting, keep only DismissPopupsRequest event * Change files * Optimize light dismiss: register during mount instead of tree walk on scroll * docs: fix broken markdown links in pipeline (microsoft#15594) * docs: fix broken markdown links * Address reviewer feedback * Remove obsolete docs and clean conflict markers * Delete managedCodeGen.md --------- Co-authored-by: Protik Biswas <protikbiswas@microsoft.com> Co-authored-by: Vineeth <66076509+vineethkuttan@users.noreply.github.com> * Bring Narrator focus to XAML island (microsoft#15611) * Use automationoption as frameworkbased for childsite * Change files * remove tokens associated with fragment based * Fix IDL: only add events without formatting changes * Address review: replace fake LayoutMetricsChanged with ViewChanged event, move light dismiss to 3P component Addresses acoates-ms review comments: 1. Replace FireLayoutMetricsChangedForScrollPositionChange with proper ViewChanged event on ScrollViewComponentView (similar to XAML ScrollViewer.ViewChanged) 2. Remove DismissPopupsRequest from ContentIslandComponentView - light dismiss logic moved to ComboBoxComponentView which walks parent tree on mount 3. ContentIslandComponentView subscribes to ViewChanged for transform updates 4. 3P component (ComboBox) subscribes to ViewChanged for popup dismiss * Apply formatting fixes and add ComboBox codegen * Fix syntax error: use correct codegen type ComboBoxEventEmitter::OnSelectionChanged --------- Co-authored-by: Nitin Chaudhary <nitchaudhary@microsoft.com> Co-authored-by: Suraj Raykar <168889106+ssuraj2504@users.noreply.github.com> Co-authored-by: Protik Biswas <protikbiswas@microsoft.com> Co-authored-by: Vineeth <66076509+vineethkuttan@users.noreply.github.com>
…#15557) (microsoft#15564) * Fix XAML popup positioning and light dismiss in ScrollView (microsoft#15557) - Add SetXamlRoot() API on ContentIslandComponentView for 3rd party XAML components - Add DismissPopups() using VisualTreeHelper.GetOpenPopupsForXamlRoot() - Fire LayoutMetricsChanged on scroll to update popup positions - Dismiss child ContentIsland popups when scroll begins - Add ComboBox sample component demonstrating the pattern - Add xamlPopupBug test sample for playground-composition * Change files * Fix XAML popup positioning and light dismiss in ScrollView (microsoft#15557) - Set LocalToParentTransformMatrix synchronously before Connect() to fix initial position - Fire LayoutMetricsChanged on scroll to update position after scrolling - Add DismissPopupsRequest event for 3P components to implement light dismiss - Update ComboBox sample to use event-based approach (core remains XAML-agnostic) * Fix XAML popup positioning - convert to DIPs and update synchronously Issue microsoft#15557: Fix popup positioning for XAML controls in ScrollView Bug 1 (Position Fix): - Convert getClientRect() physical pixels to DIPs by dividing by pointScaleFactor - LocalToParentTransformMatrix expects logical pixels (DIPs), not physical pixels - Update transform synchronously instead of async to avoid race conditions - Set initial transform in OnMounted() before Connect() Bug 2 (Light Dismiss Fix): - Add DismissPopupsRequest event for 3P components to handle popup dismissal - ScrollView fires event when scroll begins via DismissChildContentIslandPopups() - Core remains XAML-agnostic - 3P components handle their own popups * Apply clang-format * Revert IDL formatting, keep only DismissPopupsRequest event * Change files * Optimize light dismiss: register during mount instead of tree walk on scroll * docs: fix broken markdown links in pipeline (microsoft#15594) * docs: fix broken markdown links * Address reviewer feedback * Remove obsolete docs and clean conflict markers * Delete managedCodeGen.md --------- Co-authored-by: Protik Biswas <protikbiswas@microsoft.com> Co-authored-by: Vineeth <66076509+vineethkuttan@users.noreply.github.com> * Bring Narrator focus to XAML island (microsoft#15611) * Use automationoption as frameworkbased for childsite * Change files * remove tokens associated with fragment based * Fix IDL: only add events without formatting changes * Address review: replace fake LayoutMetricsChanged with ViewChanged event, move light dismiss to 3P component Addresses acoates-ms review comments: 1. Replace FireLayoutMetricsChangedForScrollPositionChange with proper ViewChanged event on ScrollViewComponentView (similar to XAML ScrollViewer.ViewChanged) 2. Remove DismissPopupsRequest from ContentIslandComponentView - light dismiss logic moved to ComboBoxComponentView which walks parent tree on mount 3. ContentIslandComponentView subscribes to ViewChanged for transform updates 4. 3P component (ComboBox) subscribes to ViewChanged for popup dismiss * Apply formatting fixes and add ComboBox codegen * Fix syntax error: use correct codegen type ComboBoxEventEmitter::OnSelectionChanged --------- Co-authored-by: Nitin Chaudhary <nitchaudhary@microsoft.com> Co-authored-by: Suraj Raykar <168889106+ssuraj2504@users.noreply.github.com> Co-authored-by: Protik Biswas <protikbiswas@microsoft.com> Co-authored-by: Vineeth <66076509+vineethkuttan@users.noreply.github.com>
…557,15338 (#15663) * Overflow implementation in Fabric as per Parity to Paper (#15338) * Implement overflow property support for Fabric architecture * Change files * Subject: ETW Provider Registration - Route to Existing 1DS Aria Tenant Hi OSG Instrumentation Team, I'm working on React Native Windows telemetry. We currently have: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ EXISTING (working): - JavaScript CLI telemetry via 1DS SDK - Instrumentation Key: 49ff6d3ef12f4578a7b75a2573d9dba8-026332b2-2d50-452f-ad0d-50f921c97a9d-7145 - Data flows to: Aria → Kusto - Kusto cluster: [YOU NEED TO TELL ME THIS] NEW (want to add): - Native C++ telemetry via ETW/TraceLogging - Provider Name: Microsoft.ReactNativeWindows.Telemetry - Provider GUID: [WILL GENERATE] ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ QUESTION: Can you register the new ETW provider GUID and route it to the SAME Aria tenant as our existing 1DS instrumentation key? Goal: Have both JavaScript (1DS) and C++ (ETW) telemetry appear in the same Kusto database for unified querying. Is this possible? If yes, what info do you need from me? Thanks, Harini Malothu React Native Windows Team * Remove overflowTest.tsx - will attach separately in PR * Address review comment: make variables const * Ensure visual is created before accessing m_contentVisual * Fix formatting issues * Fix E2E crash: use try_as instead of as for IVisualInterop * Implement overflow:hidden with lazy m_contentVisual creation - Only create m_contentVisual when overflow:hidden is set (performance optimization) - Reparent children when m_contentVisual is created/removed - Use try_as instead of as for IVisualInterop to prevent crashes - Properly handle border radii for clipping * Fix crash: use try_as for IVisualInterop * Implement overflow clipping with m_contentVisual and try_as fix * Fix crash: use try_as in ComponentView::updateClippingPath * Implement lazy m_contentVisual creation for memory efficiency * Fix crash: skip m_contentVisual creation for custom builder visuals * Add null check for m_visual in applyShadowProps to prevent crash * Fix build: Use Visual() instead of m_visual in ComponentView::applyShadowProps * Fix E2E crash: Remove assert in Visual() that crashes before initialization * Fix E2E: Make m_contentVisual truly lazy - only create for overflow:hidden - Remove ensureContentVisual() calls from VisualToMountChildrenInto() and MountChildComponentView() - m_contentVisual is now only created in updateLayoutMetrics when getClipsContentToBounds() is true - Add null check in onThemeChanged() before accessing Visual() - Restore original ensureVisual() call in MountChildComponentView() * feat: Implement overflow:hidden for Fabric using on-demand children container Implement proper overflow:hidden clipping for the Fabric Composition renderer, matching iOS/Android behavior. Instead of clipping m_visual directly (which also clips borders and background), a dedicated m_childrenContainer visual is created on-demand as a child of m_visual, and only children are clipped. Visual Tree (with overflow:hidden): m_outerVisual m_visual (background, opacity, transform, border-radius clip) Border Visuals x N m_childrenContainer (created on demand, clipped to bounds) <children> Key design decisions: - m_childrenContainer is created lazily only when overflow:hidden is set - Existing children are moved from m_visual into m_childrenContainer - Once created, m_childrenContainer is never destroyed (clip is just removed if overflow changes back to visible) - Clipping uses outer border radii via D2D path geometry, matching iOS - ScrollView overrides updateChildrenClippingPath as no-op since it manages its own visual tree via m_scrollVisual and inherently clips content Also cleans up: - Removed unnecessary null-checks on Visual() (ensureVisual guarantees it) - Removed shadow mask workaround code (not related to overflow) - Removed InsetClip-based overflow handling from updateProps - Added assert(m_visual) in Visual() accessor - Simplified updateClippingPath to only handle border-radius clipping * Move shadow to OuterVisual, use RelativeSizeWithOffset for children container * lint,format fixes * update e2e snapshots for overflow changes * Revert "update e2e snapshots for overflow changes" This reverts commit 5e07f75. * update e2e snapshots for overflow changes --------- Co-authored-by: Nitin Chaudhary <nitchaudhary@microsoft.com> Co-authored-by: Abhijeet Jha <74712637+iamAbhi-916@users.noreply.github.com> * Fix XAML popup positioning and light dismiss in ScrollView (#15557) (#15564) * Fix XAML popup positioning and light dismiss in ScrollView (#15557) - Add SetXamlRoot() API on ContentIslandComponentView for 3rd party XAML components - Add DismissPopups() using VisualTreeHelper.GetOpenPopupsForXamlRoot() - Fire LayoutMetricsChanged on scroll to update popup positions - Dismiss child ContentIsland popups when scroll begins - Add ComboBox sample component demonstrating the pattern - Add xamlPopupBug test sample for playground-composition * Change files * Fix XAML popup positioning and light dismiss in ScrollView (#15557) - Set LocalToParentTransformMatrix synchronously before Connect() to fix initial position - Fire LayoutMetricsChanged on scroll to update position after scrolling - Add DismissPopupsRequest event for 3P components to implement light dismiss - Update ComboBox sample to use event-based approach (core remains XAML-agnostic) * Fix XAML popup positioning - convert to DIPs and update synchronously Issue #15557: Fix popup positioning for XAML controls in ScrollView Bug 1 (Position Fix): - Convert getClientRect() physical pixels to DIPs by dividing by pointScaleFactor - LocalToParentTransformMatrix expects logical pixels (DIPs), not physical pixels - Update transform synchronously instead of async to avoid race conditions - Set initial transform in OnMounted() before Connect() Bug 2 (Light Dismiss Fix): - Add DismissPopupsRequest event for 3P components to handle popup dismissal - ScrollView fires event when scroll begins via DismissChildContentIslandPopups() - Core remains XAML-agnostic - 3P components handle their own popups * Apply clang-format * Revert IDL formatting, keep only DismissPopupsRequest event * Change files * Optimize light dismiss: register during mount instead of tree walk on scroll * docs: fix broken markdown links in pipeline (#15594) * docs: fix broken markdown links * Address reviewer feedback * Remove obsolete docs and clean conflict markers * Delete managedCodeGen.md --------- Co-authored-by: Protik Biswas <protikbiswas@microsoft.com> Co-authored-by: Vineeth <66076509+vineethkuttan@users.noreply.github.com> * Bring Narrator focus to XAML island (#15611) * Use automationoption as frameworkbased for childsite * Change files * remove tokens associated with fragment based * Fix IDL: only add events without formatting changes * Address review: replace fake LayoutMetricsChanged with ViewChanged event, move light dismiss to 3P component Addresses acoates-ms review comments: 1. Replace FireLayoutMetricsChangedForScrollPositionChange with proper ViewChanged event on ScrollViewComponentView (similar to XAML ScrollViewer.ViewChanged) 2. Remove DismissPopupsRequest from ContentIslandComponentView - light dismiss logic moved to ComboBoxComponentView which walks parent tree on mount 3. ContentIslandComponentView subscribes to ViewChanged for transform updates 4. 3P component (ComboBox) subscribes to ViewChanged for popup dismiss * Apply formatting fixes and add ComboBox codegen * Fix syntax error: use correct codegen type ComboBoxEventEmitter::OnSelectionChanged --------- Co-authored-by: Nitin Chaudhary <nitchaudhary@microsoft.com> Co-authored-by: Suraj Raykar <168889106+ssuraj2504@users.noreply.github.com> Co-authored-by: Protik Biswas <protikbiswas@microsoft.com> Co-authored-by: Vineeth <66076509+vineethkuttan@users.noreply.github.com> * Apply clang-format --------- Co-authored-by: Nitin Chaudhary <nitchaudhary@microsoft.com> Co-authored-by: Abhijeet Jha <74712637+iamAbhi-916@users.noreply.github.com> Co-authored-by: Suraj Raykar <168889106+ssuraj2504@users.noreply.github.com> Co-authored-by: Protik Biswas <protikbiswas@microsoft.com> Co-authored-by: Vineeth <66076509+vineethkuttan@users.noreply.github.com>
Fix XAML popup positioning and light dismiss in ScrollView (#15557)
Description
This PR fixes Issue #15557 - "Pop-ups of Xaml controls need positioning and dismissal"
When XAML controls with popups (like ComboBox, DatePicker, TimePicker) are hosted inside a React Native ScrollView via
ContentIslandComponentView, two bugs occur:Root Cause Analysis
Bug 1: Popup Position
ContentIslandComponentViewusesChildSiteLink.LocalToParentTransformMatrixfor popup positioninggetClientRect()returns values in physical pixels (scaled bypointScaleFactor), butLocalToParentTransformMatrixexpects logical pixels (DIPs)UIDispatcher().Post(), causing race conditions where popup opened with stale transform valuesConnect(), causing wrong position even without scrollingBug 2: Light Dismiss on Scroll
Solution
Bug 1 Fix: Popup Position After Scroll
getClientRect()values bypointScaleFactorbefore settingLocalToParentTransformMatrixUIDispatcher().Post()wrapper - update transform immediatelyLocalToParentTransformMatrixinOnMounted()beforeConnect()ScrollViewComponentViewfiresLayoutMetricsChangedevent when scroll position changesBug 2 Fix: Light Dismiss on Scroll (Optimized)
DismissPopupsRequestevent toContentIslandComponentViewScrollBeginDragevent toScrollViewComponentViewOnMounted(),ContentIslandComponentViewwalks up the tree once and registers forScrollBeginDragon all parentScrollViewComponentViewinstancesScrollViewComponentViewfires itsScrollBeginDragevent, which notifies only the registeredContentIslandComponentViewinstancesDismissPopupsRequestand dismiss their own popupsFiles Changed
Core Fix (vnext/Microsoft.ReactNative/)
Fabric/Composition/ContentIslandComponentView.cpp- DIP conversion, sync updates, register for parent ScrollView events during mountFabric/Composition/ContentIslandComponentView.h- Store scroll subscriptionsFabric/Composition/ScrollViewComponentView.cpp- Fire LayoutMetricsChanged on scroll, expose ScrollBeginDrag eventFabric/Composition/ScrollViewComponentView.h- ScrollBeginDrag eventCompositionComponentView.idl- Added DismissPopupsRequest event to ContentIslandComponentView, ScrollBeginDrag event to ScrollViewComponentViewTesting
XamlPopupReprosample in Playground##Screenshot
testingFix.mp4