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