@@ -161,6 +161,39 @@ void RegisterTooltipWndClass() noexcept {
161161 registered = true ;
162162}
163163
164+ // Clamp tooltip position so it stays within the nearest monitor's work area.
165+ // Flips the tooltip below the cursor if it would go above the work area.
166+ void ClampTooltipToMonitor (POINT cursorScreenPt, int tooltipWidth, int tooltipHeight, float scaleFactor, int &x, int &y) noexcept {
167+ HMONITOR hMonitor = MonitorFromPoint (cursorScreenPt, MONITOR_DEFAULTTONEAREST);
168+ if (!hMonitor)
169+ return ;
170+
171+ MONITORINFO mi = {};
172+ mi.cbSize = sizeof (mi);
173+ if (!GetMonitorInfo (hMonitor, &mi))
174+ return ;
175+
176+ const RECT &workArea = mi.rcWork ;
177+
178+ // Clamp horizontally
179+ if (x + tooltipWidth > workArea.right ) {
180+ x = workArea.right - tooltipWidth;
181+ }
182+ if (x < workArea.left ) {
183+ x = workArea.left ;
184+ }
185+
186+ // If tooltip goes above the work area, flip it below the cursor
187+ if (y < workArea.top ) {
188+ y = static_cast <int >(cursorScreenPt.y + (toolTipPlacementMargin * scaleFactor));
189+ }
190+
191+ // If tooltip goes below the work area (after flip), clamp to bottom
192+ if (y + tooltipHeight > workArea.bottom ) {
193+ y = workArea.bottom - tooltipHeight;
194+ }
195+ }
196+
164197TooltipTracker::TooltipTracker (
165198 const winrt::Microsoft::ReactNative::ComponentView &view,
166199 const winrt::Microsoft::ReactNative::ReactPropertyBag &properties,
@@ -227,6 +260,11 @@ void TooltipTracker::OnPointerExited(
227260 DestroyTooltip ();
228261}
229262
263+ void TooltipTracker::DismissActiveTooltip () noexcept {
264+ DestroyTimer ();
265+ DestroyTooltip ();
266+ }
267+
230268void TooltipTracker::OnUnmounted (
231269 const winrt::Windows::Foundation::IInspectable &,
232270 const winrt::Microsoft::ReactNative::ComponentView &) noexcept {
@@ -267,17 +305,24 @@ void TooltipTracker::ShowTooltip(const winrt::Microsoft::ReactNative::ComponentV
267305 static_cast <int >((tm.width + tooltipHorizontalPadding + tooltipHorizontalPadding) * scaleFactor);
268306 tooltipData->height = static_cast <int >((tm.height + tooltipTopPadding + tooltipBottomPadding) * scaleFactor);
269307
270- POINT pt = {static_cast <LONG>(m_pos.X * scaleFactor), static_cast <LONG>(m_pos.Y * scaleFactor)};
271- ClientToScreen (parentHwnd, &pt);
308+ // Convert island-local DIP coordinates to screen pixel coordinates.
309+ // Use LocalToScreen which properly handles both ContentIsland and HWND hosting.
310+ auto screenPt = selfView->LocalToScreen ({m_pos.X , m_pos.Y });
311+ POINT pt = {static_cast <LONG>(screenPt.X ), static_cast <LONG>(screenPt.Y )};
312+
313+ // Calculate initial desired tooltip position and clamp to monitor work area
314+ int tooltipX = pt.x - tooltipData->width / 2 ;
315+ int tooltipY = static_cast <int >(pt.y - tooltipData->height - (toolTipPlacementMargin * scaleFactor));
316+ ClampTooltipToMonitor (pt, tooltipData->width , tooltipData->height , scaleFactor, tooltipX, tooltipY);
272317
273318 RegisterTooltipWndClass ();
274319 HINSTANCE hInstance = GetModuleHandle (NULL );
275320 m_hwndTip = CreateWindow (
276321 c_tooltipWindowClassName,
277322 L" Tooltip" ,
278323 WS_POPUP,
279- pt. x - tooltipData-> width / 2 ,
280- static_cast < int >(pt. y - tooltipData-> height - (toolTipPlacementMargin * scaleFactor)) ,
324+ tooltipX ,
325+ tooltipY ,
281326 tooltipData->width ,
282327 tooltipData->height ,
283328 parentHwnd,
@@ -326,6 +371,12 @@ void TooltipService::StopTracking(const winrt::Microsoft::ReactNative::Component
326371 }
327372}
328373
374+ void TooltipService::DismissAllTooltips () noexcept {
375+ for (auto &tracker : m_trackers) {
376+ tracker->DismissActiveTooltip ();
377+ }
378+ }
379+
329380static const ReactPropertyId<winrt::Microsoft::ReactNative::ReactNonAbiValue<std::shared_ptr<TooltipService>>>
330381 &TooltipServicePropertyId () noexcept {
331382 static const ReactPropertyId<winrt::Microsoft::ReactNative::ReactNonAbiValue<std::shared_ptr<TooltipService>>> prop{
0 commit comments