🐛 Fix tooltip appearing two times when nested elements (#9031)

This commit is contained in:
Eva Marco 2026-04-22 11:33:43 +02:00 committed by GitHub
parent c02f0a2bc9
commit 09fca1c820
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -17,8 +17,52 @@
(def ^:private ^:const overlay-offset 32) (def ^:private ^:const overlay-offset 32)
;; Global state for tooltip coordination
(defonce active-tooltip (atom nil)) (defonce active-tooltip (atom nil))
;; Registry of visible tooltips to detect nested tooltips
;; Map: tooltip-id -> trigger-element
(defonce ^:private tooltip-registry (atom {}))
;; Track tooltips that are "about to show" - used to prevent race conditions
;; when both parent and child schedule their show at the same time.
;; Map: tooltip-id -> trigger-element
(defonce ^:private pending-tooltips (atom {}))
(defn- mark-pending
"Mark a tooltip as pending (scheduled to show soon).
Used to detect potential nested tooltips during race condition window."
[tooltip-id trigger-el]
(swap! pending-tooltips assoc tooltip-id trigger-el))
(defn- clear-pending
"Clear the pending state (tooltip showed or cancelled)."
[tooltip-id]
(swap! pending-tooltips dissoc tooltip-id))
(defn- register-tooltip
"Register this tooltip in the global registry when it becomes visible.
Used to detect nested tooltips."
[tooltip-id trigger-el]
(swap! tooltip-registry assoc tooltip-id trigger-el)
(clear-pending tooltip-id))
(defn- unregister-tooltip
"Unregister this tooltip from the global registry when it hides."
[tooltip-id]
(swap! tooltip-registry dissoc tooltip-id)
(clear-pending tooltip-id))
(defn- has-descendant-tooltip?
"Check if there's a registered or pending tooltip that is a descendant of trigger-el.
If so, we should NOT show the parent tooltip."
[trigger-el]
(let [all-tooltips (merge @tooltip-registry @pending-tooltips)]
(some (fn [[_ entry-el]]
(when (some? entry-el)
(dom/child? entry-el trigger-el)))
all-tooltips)))
(defn- clear-schedule (defn- clear-schedule
[ref] [ref]
(when-let [schedule (mf/ref-val ref)] (when-let [schedule (mf/ref-val ref)]
@ -191,15 +235,27 @@
(when-not (.-hidden js/document) (when-not (.-hidden js/document)
(let [trigger-el (mf/ref-val trigger-ref)] (let [trigger-el (mf/ref-val trigger-ref)]
(clear-schedule schedule-ref) (clear-schedule schedule-ref)
(add-schedule schedule-ref (d/nilv delay 300)
(fn [] ;; Check if there's a registered or pending tooltip that is a descendant of our trigger.
(when-let [active @active-tooltip] ;; If so, skip showing this tooltip and let the innermost one show instead.
(when (not= (:id active) tooltip-id) (when-not (has-descendant-tooltip? trigger-el)
(when-let [tooltip-el (dom/get-element (:id active))] ;; Mark as pending BEFORE scheduling (helps prevent race conditions)
(dom/set-css-property! tooltip-el "display" "none")) (mark-pending tooltip-id trigger-el)
(reset! active-tooltip nil)))
(reset! active-tooltip {:id tooltip-id :trigger trigger-el}) (add-schedule schedule-ref (d/nilv delay 300)
(reset! visible* true))))))) (fn []
;; Double-check: don't show if another tooltip is now visible
(when-let [active @active-tooltip]
(when (not= (:id active) tooltip-id)
(when-let [tooltip-el (dom/get-element (:id active))]
(dom/set-css-property! tooltip-el "display" "none"))
(reset! active-tooltip nil)))
;; Register this tooltip as visible
(register-tooltip tooltip-id trigger-el)
(reset! active-tooltip {:id tooltip-id :trigger trigger-el})
(reset! visible* true))))))))
on-show-focus on-show-focus
(mf/use-fn (mf/use-fn
@ -215,6 +271,10 @@
(fn [] (fn []
(clear-schedule schedule-ref) (clear-schedule schedule-ref)
(reset! visible* false) (reset! visible* false)
;; Unregister from the global registry
(unregister-tooltip tooltip-id)
(when (= (:id @active-tooltip) tooltip-id) (when (= (:id @active-tooltip) tooltip-id)
(reset! active-tooltip nil)))) (reset! active-tooltip nil))))