@@ -169,12 +169,22 @@ TooltipTracker::TooltipTracker(
169169 view.PointerEntered ({this , &TooltipTracker::OnPointerEntered});
170170 view.PointerExited ({this , &TooltipTracker::OnPointerExited});
171171 view.PointerMoved ({this , &TooltipTracker::OnPointerMoved});
172+ view.GotFocus ({this , &TooltipTracker::OnGotFocus});
173+ view.LostFocus ({this , &TooltipTracker::OnLostFocus});
172174 view.Unmounted ({this , &TooltipTracker::OnUnmounted});
173175}
174176
175177TooltipTracker::~TooltipTracker () {
176178 DestroyTimer ();
177179 DestroyTooltip ();
180+ m_outer->NotifyDismiss (this );
181+ }
182+
183+ void TooltipTracker::DismissForExternalRequest () noexcept {
184+ // Service is already updating its active slot; do not call back into it.
185+ m_focusTooltip = false ;
186+ DestroyTimer ();
187+ DestroyTooltip ();
178188}
179189
180190facebook::react::Tag TooltipTracker::Tag () const noexcept {
@@ -192,6 +202,9 @@ void TooltipTracker::OnPointerEntered(
192202 auto pp = args.GetCurrentPoint (-1 );
193203 m_pos = pp.Position ();
194204
205+ // Claim the single tooltip slot, dismissing any other tracker's pending or visible tooltip.
206+ m_outer->NotifyShow (this );
207+
195208 m_timer = winrt::Microsoft::ReactNative::Timer::Create (m_properties.Handle ());
196209 m_timer.Interval (std::chrono::milliseconds (toolTipTimeToShowMs));
197210 m_timer.Tick ({this , &TooltipTracker::OnTick});
@@ -225,13 +238,64 @@ void TooltipTracker::OnPointerExited(
225238 return ;
226239 DestroyTimer ();
227240 DestroyTooltip ();
241+ m_outer->NotifyDismiss (this );
242+ }
243+
244+ void TooltipTracker::OnGotFocus (
245+ const winrt::Windows::Foundation::IInspectable & /* sender*/ ,
246+ const winrt::Microsoft::ReactNative::Composition::Input::RoutedEventArgs & /* args*/ ) noexcept {
247+ // Skip if a mouse-driven tooltip or its dwell timer is already in flight on this view.
248+ if (m_hwndTip || m_timer) {
249+ return ;
250+ }
251+
252+ auto view = m_view.view ();
253+ if (!view) {
254+ return ;
255+ }
256+
257+ auto viewCompView = view.try_as <winrt::Microsoft::ReactNative::Composition::ViewComponentView>();
258+ if (!viewCompView) {
259+ return ;
260+ }
261+ auto selfView =
262+ winrt::get_self<winrt::Microsoft::ReactNative::Composition::implementation::ViewComponentView>(viewCompView);
263+ RECT rc = selfView->getClientRect ();
264+ auto scaleFactor = view.LayoutMetrics ().PointScaleFactor ;
265+ if (scaleFactor <= 0 ) {
266+ return ;
267+ }
268+
269+ // Anchor in DIPs at the horizontal center of the view's top edge; ShowTooltip re-applies scaleFactor.
270+ m_pos = {static_cast <float >(rc.left + rc.right ) / 2 .0f / scaleFactor, static_cast <float >(rc.top ) / scaleFactor};
271+
272+ m_focusTooltip = true ;
273+ // Claim the single tooltip slot, dismissing any other tracker's pending or visible tooltip.
274+ m_outer->NotifyShow (this );
275+ m_timer = winrt::Microsoft::ReactNative::Timer::Create (m_properties.Handle ());
276+ m_timer.Interval (std::chrono::milliseconds (toolTipTimeToShowMs));
277+ m_timer.Tick ({this , &TooltipTracker::OnTick});
278+ m_timer.Start ();
279+ }
280+
281+ void TooltipTracker::OnLostFocus (
282+ const winrt::Windows::Foundation::IInspectable & /* sender*/ ,
283+ const winrt::Microsoft::ReactNative::Composition::Input::RoutedEventArgs & /* args*/ ) noexcept {
284+ if (!m_focusTooltip) {
285+ return ;
286+ }
287+ m_focusTooltip = false ;
288+ DestroyTimer ();
289+ DestroyTooltip ();
290+ m_outer->NotifyDismiss (this );
228291}
229292
230293void TooltipTracker::OnUnmounted (
231294 const winrt::Windows::Foundation::IInspectable &,
232295 const winrt::Microsoft::ReactNative::ComponentView &) noexcept {
233296 DestroyTimer ();
234297 DestroyTooltip ();
298+ m_outer->NotifyDismiss (this );
235299}
236300
237301void TooltipTracker::ShowTooltip (const winrt::Microsoft::ReactNative::ComponentView &view) noexcept {
@@ -326,6 +390,22 @@ void TooltipService::StopTracking(const winrt::Microsoft::ReactNative::Component
326390 }
327391}
328392
393+ void TooltipService::NotifyShow (TooltipTracker *tracker) noexcept {
394+ if (m_activeTracker == tracker) {
395+ return ;
396+ }
397+ if (m_activeTracker) {
398+ m_activeTracker->DismissForExternalRequest ();
399+ }
400+ m_activeTracker = tracker;
401+ }
402+
403+ void TooltipService::NotifyDismiss (TooltipTracker *tracker) noexcept {
404+ if (m_activeTracker == tracker) {
405+ m_activeTracker = nullptr ;
406+ }
407+ }
408+
329409static const ReactPropertyId<winrt::Microsoft::ReactNative::ReactNonAbiValue<std::shared_ptr<TooltipService>>>
330410 &TooltipServicePropertyId () noexcept {
331411 static const ReactPropertyId<winrt::Microsoft::ReactNative::ReactNonAbiValue<std::shared_ptr<TooltipService>>> prop{
0 commit comments