Fix tooltip positioning for ContentIsland and dismiss on scroll #15644
+76
−4
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Description
Fixes tooltip positioning being incorrect (trimmed/offset) when scrolling in Fabric ContentIsland-hosted apps, and ensures tooltips are dismissed immediately when the user scrolls.
Why
In the RNW Gallery app (and any Fabric Composition app), hovering over a component with a
tooltipprop and then scrolling causes:Wrong tooltip position — The tooltip appears at an incorrect screen location because the coordinate conversion used
ClientToScreen(parentHwnd), which assumes HWND client coordinates. In ContentIsland hosting,m_pos(fromPointerPoint::Position()) is in island-local DIP coordinates, not HWND client coordinates.ClientToScreenproduces the wrong screen position.Stale tooltip on scroll — After scrolling, the tooltip remains visible at the old screen position while the component underneath has moved, making the tooltip appear detached or trimmed.
What
Files Changed
TooltipService.cppClientToScreenwithLocalToScreenfor correct coordinate conversion; extractClampTooltipToMonitorhelper; addDismissActiveTooltipimplementationTooltipService.hDismissActiveTooltip()toTooltipTracker; addDismissAllTooltips()toTooltipServiceScrollViewComponentView.cppTooltipService::DismissAllTooltips()on scroll position changeChanges in Detail
TooltipService.cpp— Coordinate fix (core fix)POINT pt = {m_pos.X * scaleFactor, m_pos.Y * scaleFactor}; ClientToScreen(parentHwnd, &pt);auto screenPt = selfView->LocalToScreen({m_pos.X, m_pos.Y}); POINT pt = {static_cast<LONG>(screenPt.X), static_cast<LONG>(screenPt.Y)};LocalToScreenfollows the correct Fabric conversion chain:ComponentView::LocalToScreen()→RootComponentView::ConvertLocalToScreen()→ReactNativeIsland::ConvertLocalToScreen()which usesm_island.CoordinateConverter().ConvertLocalToScreen()for ContentIsland hosting, or falls back toClientToScreen(m_hwnd)for legacy HWND hosting.TooltipService.cpp— Monitor edge clamping (extracted helper)ClampTooltipToMonitor()— clamps tooltip X/Y to the nearest monitor's work area and flips tooltip below cursor if it would go above the screen.TooltipService.h/.cpp— Dismiss APITooltipTracker::DismissActiveTooltip()— destroys any active timer and tooltip window (both have null guards, so calling on inactive trackers is a no-op).TooltipService::DismissAllTooltips()— iterates all trackers and callsDismissActiveTooltip().ScrollViewComponentView.cpp— Scroll dismiss integrationTooltipService::GetCurrent(m_reactContext.Properties())->DismissAllTooltips()at the top of theScrollPositionChangedcallback. This covers all scroll types: mouse wheel, touch drag, keyboard navigation, and programmatic scroll.How
Root Cause Analysis
The
LocalToScreencall properly traverses the Fabric view hierarchy:This handles both hosting models correctly without hardcoding either one.
Scroll Dismissal
ScrollPositionChangedon theIScrollVisualfires for all scroll types (wheel, touch, keyboard, programmatic). CallingDismissAllTooltips()here is the single integration point. Each tracker'sDismissActiveTooltip()delegates toDestroyTimer()+DestroyTooltip(), both of which haveif (m_timer)/if (m_hwndTip)null guards — so inactive trackers cost just two pointer comparisons.Testing
tooltipprop until the tooltip appearsScreenshots
Before
failedtooltip.mp4
After
fixedtooltip.mp4
Microsoft Reviewers: Open in CodeFlow