be67cde22f4d9af8323ef62560754b5ef925d020
chmalee
  Mon Mar 9 16:19:25 2026 -0700
Fix two regression in tooltip code from commit ddfed922f22d47: not clearing tooltips on mouse scroll and not clearing tooltips when showing a wiggle mouseover, refs #37205

diff --git src/hg/js/utils.js src/hg/js/utils.js
index f2d07f87b39..ffd7a62f74d 100644
--- src/hg/js/utils.js
+++ src/hg/js/utils.js
@@ -4201,30 +4201,47 @@
 
 function addMouseover(ele1, text = null, ele2 = null) {
     /* Adds wrapper elements to control various mouseover events using mouseenter/mouseleave. */
     if (!mouseoverContainer) {
         mouseoverContainer = document.createElement("div");
         mouseoverContainer.className = "tooltip";
         mouseoverContainer.style.position = "fixed";
         mouseoverContainer.style.display = "inline-block";
         mouseoverContainer.style.visibility = "hidden";
         mouseoverContainer.style.opacity = "0";
         mouseoverContainer.id = "mouseoverContainer";
         let tooltipTextSize = localStorage.getItem("tooltipTextSize");
         if (tooltipTextSize === null) {tooltipTextSize = window.browserTextSize !== null ? window.browserTextSize : 12;}
         mouseoverContainer.style.fontSize =  tooltipTextSize + "px";
         document.body.append(mouseoverContainer);
+
+        // Tooltip mouseenter/mouseleave
+        mouseoverContainer.addEventListener("mouseenter", function() {
+            mouseoverContainer._isMouseOver = true;
+            // Clear any hide timeout when entering tooltip
+            if (mouseoverContainer._hideTimeout) {
+                clearTimeout(mouseoverContainer._hideTimeout);
+                mouseoverContainer._hideTimeout = null;
+            }
+        });
+        mouseoverContainer.addEventListener("mouseleave", function() {
+            mouseoverContainer._isMouseOver = false;
+            // Hide after a short delay to allow for quick mouse movements
+            mouseoverContainer._hideTimeout = setTimeout(function() {
+                hideMouseoverText(mouseoverContainer);
+            }, 100);
+        });
     }
 
     if (ele1) {
         ele1.setAttribute("mouseoverText", text);
         // Remove title attribute to prevent default browser tooltip
         if (ele1.title || ele1.dataset.tooltip) {
             ele1.setAttribute("originalTitle", ele1.title);
             ele1.title = "";
         }
         // Remove previous listeners if any
         ele1.removeEventListener("mouseenter", ele1._mouseenterHandler);
         ele1.removeEventListener("mouseleave", ele1._mouseleaveHandler);
         // Show tooltip on mouseenter with delay
         ele1._mouseenterHandler = function(e) {
             // Clear any existing hide timeout
@@ -4243,64 +4260,48 @@
             // Clear show timeout if mouse leaves before delay
             if (ele1._tooltipShowTimeout) {
                 clearTimeout(ele1._tooltipShowTimeout);
                 ele1._tooltipShowTimeout = null;
             }
             // Use a grace period to allow moving to the tooltip itself
             ele1._tooltipHideTimeout = setTimeout(function() {
                 if (!mouseoverContainer._isMouseOver) {
                     hideMouseoverText(mouseoverContainer);
                 }
             }, 500); // 500ms grace period
         };
         ele1.addEventListener("mouseenter", ele1._mouseenterHandler);
         ele1.addEventListener("mouseleave", ele1._mouseleaveHandler);
     }
-
-    // Tooltip mouseenter/mouseleave
-    mouseoverContainer.addEventListener("mouseenter", function() {
-        mouseoverContainer._isMouseOver = true;
-        // Clear any hide timeout when entering tooltip
-        if (mouseoverContainer._hideTimeout) {
-            clearTimeout(mouseoverContainer._hideTimeout);
-            mouseoverContainer._hideTimeout = null;
-        }
-    });
-    mouseoverContainer.addEventListener("mouseleave", function() {
-        mouseoverContainer._isMouseOver = false;
-        // Hide after a short delay to allow for quick mouse movements
-        mouseoverContainer._hideTimeout = setTimeout(function() {
-            hideMouseoverText(mouseoverContainer);
-        }, 100);
-    });
 }
 
 function showTooltipForElement(ele, ev) {
     // Show the tooltip for the given element
     if (suppressTooltips) {return;}
+    // wiggle mouseovers have special code, don't use these tooltips for those:
+    if (typeof mouseOver !== "undefined" && mouseOver.visible) {return;}
     let text = ele.getAttribute("mouseoverText");
     if (!text) return;
     mouseoverContainer.replaceChildren();
     let newEl = document.createElement("span");
     newEl.style = "max-width: 400px";
     newEl.innerHTML = text;
     mouseoverContainer.appendChild(newEl);
     positionMouseover(ev, ele, mouseoverContainer, ev.pageX, ev.pageY);
     mouseoverContainer.classList.add("isShown");
     mouseoverContainer.style.opacity = "1";
     mouseoverContainer.style.visibility = "visible";
-    mouseoverContainer.setAttribute("origItemMouseoverId", ele.getAttribute("mouseoverid"));
 }
 
 function hideMouseoverText(ele) {
     /* Actually hides the tooltip text */
     ele.classList.remove("isShown");
     ele.style.opacity = "0";
     ele.style.visibility = "hidden";
 }
 
 function titleTagToMouseover(mapEl) {
     /* for a given area tag, extract the title text into a div that can be positioned
     * like a standard tooltip mouseover next to the item */
     if (mapEl.dataset.tooltip)
         addMouseover(mapEl, mapEl.dataset.tooltip);
     else
@@ -4316,67 +4317,80 @@
         }
         if (a.title !== undefined &&
                 (a.title.startsWith("click & drag to scroll") || a.title.startsWith("drag select or click to zoom")))
             a.title = "";
         else if (a.title !== undefined && a.title.length > 0) {
             if (a.title.startsWith("Click to alter the display density")) {
                 // these tooltips have a longer delay:
                 a.setAttribute("tooltipDelay", "delayed");
             }
             titleTagToMouseover(a);
         }
     });
 
     /* Mouseover should clear if you leave the document window altogether */
     document.body.addEventListener("mouseleave", (ev) => {
+        if (mouseoverContainer) {
+            hideMouseoverText(mouseoverContainer);
+        }
+    });
+
+    /* Mouseover should clear on scroll */
+    document.addEventListener("scroll", (ev) => {
+        if (mouseoverContainer) {
             hideMouseoverText(mouseoverContainer);
+        }
     });
 
     function hideTooltips() {
+        if (mouseoverContainer) {
             hideMouseoverText(mouseoverContainer);
         }
+    }
 
     /* make the mouseovers go away if we are in an input */
     const inps = document.getElementsByTagName("input");
     for (let inp of inps) {
         if (!(inp.type == "hidden" || inp.type == "HIDDEN")) {
             if (inp.type !== "submit") {
                 inp.addEventListener("focus", hideTooltips);
             } else {
                 // the buttons are inputs that don't blur right away (or ever? I can't tell), so
                 // be sure to restore the tooltips when they are clicked
                 inp.addEventListener("click", hideTooltips);
             }
         }
     }
     /* on a select, we can hide the tooltip on focus, but don't disable them
      * altogether, because it's easy to click out of a select without actually
      * losing focus, and we can't detect that because the web browser handles
      * that click separately */
     const sels = document.getElementsByTagName("select");
     for (let sel of sels) {
         sel.addEventListener("focus", hideTooltips);
         for (let opt of sel.options) {
             opt.addEventListener("click", hideTooltips);
         }
     }
 
     /* Make the ESC key hide tooltips */
     document.body.addEventListener("keyup", (ev) => {
         if (ev.keyCode === 27) {
+            if (mouseoverContainer) {
                 hideMouseoverText(mouseoverContainer);
             }
+        }
     });
 }
 
 function parseUrl(url) {
     // turn a url into some of it's components like server, query-string, etc
     let protocol, serverName, pathInfo, queryString, queryArgs = {};
     let temp;
     temp = url.split("?");
     if (temp.length > 1)
         queryString = temp.slice(1).join("?");
     temp = temp[0].split("/");
     protocol = temp[0]; // "https:"
     serverName = temp[2]; // "genome-test.gi.ucsc.edu"
     pathInfo = temp.slice(3).join("/"); // "cgi-bin/hgTracks"
     cgi = pathInfo.startsWith("cgi-bin") ? pathInfo.split('/')[1] : "";