ffea7ccf62ba088c64b5f5c3a8e3d56865e4b297
chmalee
  Fri Dec 8 09:33:52 2023 -0800
More tooltip fixups, hide/show the tooltip when leaving/re-entering the window, hide them upon opening a dropdown, fix hiding/showing them on right clicks and when entering the search bar, refs #32697

diff --git src/hg/js/utils.js src/hg/js/utils.js
index ad3634f..7243f04 100644
--- src/hg/js/utils.js
+++ src/hg/js/utils.js
@@ -3963,31 +3963,31 @@
             mouseY >= (origTargetBox.top - fudgeFactor) && mouseY <= (origTargetBox.bottom + fudgeFactor)) ) {
         return true;
     }
     return false;
 }
 
 function mousemoveTimerHelper(e) {
     /* user has moved the mouse and then stopped moving for long enough. */
     clearTimeout(mousemoveTimer);
     canShowNewMouseover = true;
     mousemoveTimer = undefined;
 }
 
 function mousemoveHelper(e) {
     /* Helper function for deciding whether to keep a tooltip visible upon a mousemove event */
-    if (mousemoveTimer === undefined) {
+    if (mousemoveTimer === undefined && canShowNewMouseover) {
         // if we are over another mouseable element we want to show that one instead
         // use this timer to do so
         let callback = mousemoveTimerHelper.bind(mouseoverContainer);
         mousemoveTimer = setTimeout(callback, 300, e);
     }
 
     if (mousedNewItem && canShowNewMouseover && !mouseIsOverPopup(e, this, 0)) {
         // the !mouseIsOverPopup() check catches the corner case where the mouse
         // was moved slowly enough to the popup to set off the mousemoveTimer, but
         // is now over the pop up itself
         hideMouseoverText(this);
         mousemoveController.abort();
     } else {
         if (mouseIsOverPopup(e, this) || mouseIsOverItem(e, this)) {
             // the mouse is in the general area of the popup/item
@@ -4048,32 +4048,35 @@
     }
 }
 
 function showMouseover(e) {
     /* Helper function for showing a mouseover. Uses a timeout function to allow
      * user to not immediately see all available tooltips. */
     e.preventDefault();
     // if a tooltip is currently visible, we need to wait for its mousemove
     // event to clear it before we can show this one, ie a user "hovers"
     // this element on their way to mousing over the shown mouseover
     mousedNewItem = true;
     if (mouseoverTimer) {
         // user is moving their mouse around, make sure where they stop is what we show
         clearTimeout(mouseoverTimer);
     }
+    // prevent attaching a timer if something has focus
+    if (canShowNewMouseover) {
         mouseoverTimer = setTimeout(showMouseoverText, 300, e);
     }
+}
 
 function addMouseover(ele1, text = null, ele2 = null) {
     /* Adds wrapper elements to control various mouseover events */
     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;}
         mouseoverContainer.style.fontSize =  tooltipTextSize + "px";
         document.body.append(mouseoverContainer);
@@ -4118,100 +4121,92 @@
     * like a standard tooltip mouseover next to the item */
     addMouseover(mapEl, mapEl.title);
 }
 
 function convertTitleTagsToMouseovers() {
     /* make all the title tags in the document have mouseovers */
     $("[[title]").each(function(i, a) {
         if (a.title !== undefined && a.title.length > 0) {
             titleTagToMouseover(a);
         }
     });
 
     /* Mouseover should clear if you leave the document window altogether */
     document.body.addEventListener("mouseleave", (ev) => {
         clearTimeout(mouseoverTimer);
-        mousemoveController.abort();
+        if (mousemoveController) { mousemoveController.abort(); }
         hideMouseoverText(mouseoverContainer);
         canShowNewMouseover = false;
         // let mouseovers show up again upon moving back in to the window
         // but only need the event once
-        document.body.addEventListener("mouseenter", (ev) => {
-            canShowNewMousoever = true;
-        }, {once: true});
+        // use capture: true to force this event to happen
+        // before the regular mouseover event
+        document.body.addEventListener("mouseover", (evt) => {
+            canShowNewMouseover = true;
+        }, {capture: true, once: true});
     });
 
-    /* make the above mouseovers go away if we are in an input or select menu */
+    /* make the mouseovers go away if we are in an input */
     const inps = document.getElementsByTagName("input");
-    const sels = document.getElementsByTagName("select");
     for (let inp of inps) {
         if (!(inp.type == "hidden" || inp.type == "HIDDEN")) {
+            if (inp.type !== "submit") {
                 inp.addEventListener("focus", (ev) => {
+                    if (mousemoveController) {mousemoveController.abort();}
                     clearTimeout(mouseoverTimer);
-                mousemoveController.abort();
+                    clearTimeout(mousemoveTimer);
                     hideMouseoverText(mouseoverContainer);
                     canShowNewMouseover = false;
-            });
                     inp.addEventListener("blur", (evt) => {
                         canShowNewMouseover = true;
+                    }, {once: true});
+                });
+            } 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", (ev) => {
+                    if (mousemoveController) {mousemoveController.abort();}
+                    clearTimeout(mouseoverTimer);
+                    clearTimeout(mousemoveTimer);
+                    hideMouseoverText(mouseoverContainer);
+                    canShowNewMouseover = true;
                 });
             }
         }
+    }
+    /* 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", (ev) => {
+            if (mousemoveController) {mousemoveController.abort();}
             clearTimeout(mouseoverTimer);
-            mousemoveController.abort();
             hideMouseoverText(mouseoverContainer);
-            canShowNewMouseover = false;
-        });
-        sel.addEventListener("blur", (evt) => {
             canShowNewMouseover = true;
         });
-    }
-
-    /* for hgTracks specifically, we also need to deal with the special contextmenu */
-    let imgTbl = document.getElementById("imgTbl");
-    if (imgTbl) {
-        imgTbl.addEventListener("contextmenu", function(e) {
+        for (let opt of sel.options) {
+            opt.addEventListener("click", (evt) => {
+                if (mousemoveController) {mousemoveController.abort();}
                 clearTimeout(mouseoverTimer);
-            mousemoveController.abort();
                 hideMouseoverText(mouseoverContainer);
-            canShowNewMouseover = false;
-            // right-click menu doesn't capture focus so manually catch it
-            document.addEventListener("click", function(ev) {
-                // there is a race condition where the close happens after an inputs
-                // focus happens, which means mouseovers get re-enabled when the focus
-                // on the input should prevent them, catch that here:
-                if (!(document.activeElement.tagName === "INPUT" ||
-                        document.activeElement === "SELECT")) {
                 canShowNewMouseover = true;
-                }
-            }, {once: true});
-            document.addEventListener("keyup", function(ev) {
-                if (ev.keyCode == 27) {
-                    // there is a race condition where the close happens after an inputs
-                    // focus happens, which means mouseovers get re-enabled when the focus
-                    // on the input should prevent them, catch that here:
-                    if (!(document.activeElement.tagName === "INPUT" ||
-                            document.activeElement === "SELECT")) {
-                        canShowNewMouseover = true;
-                    }
-                }
-            }, {once: true});
             });
         }
     }
+}
 
 function newTooltips() {
     /* For server side printed tooltip texts, make them work as pop ups.
      * Please note that the Tooltiptext node can have any arbitrary html in it, like
      * line breaks or links*/
     $("[title]").each(function(i, n) {
         tooltiptext = n.getAttribute("title");
         if (tooltiptext !== null) {
             addMouseover(n, tooltiptext);
         }
     });
 }
 
 function parseUrl(url) {
     // turn a url into some of it's components like server, query-string, etc