26c8e4e2290ebd613b3aad5d5d2b6a12bdfff251
braney
  Mon Aug 10 17:16:54 2026 -0700
hgTracks: apply the color a drag picks in the highlight picker, refs #37987

The Drag-and-select dialog's buttons read the hex text box beside the swatch, not
the picker itself.  Clicking a palette square makes spectrum treat the color as
chosen, which updates that box.  Dragging in the gradient square only repaints the
picker: spectrum does not consider the color chosen until "choose" is clicked, so
the box kept its old value and "Save Color" saved the old color.

A move handler now writes the dragged color into the box as the drag happens, and
a hide handler resyncs the box so a cancelled drag does not leave the dragged
color behind.

Also open the picker above the swatch when opening downwards would cover the
dialog's row of buttons, which otherwise cannot be clicked at all.

Reported on the mailing list in #37983.

diff --git src/hg/js/hui.js src/hg/js/hui.js
index 86481c5e954..c105a14362f 100644
--- src/hg/js/hui.js
+++ src/hg/js/hui.js
@@ -1582,41 +1582,72 @@
     inpResetLink.href = "#";
     inpResetLink.id = cartVar + "Reset";
     inpResetLink.textContent = "Reset";
     colorPickerContainer.appendChild(inpText);
     colorPickerContainer.append(" ");
     colorPickerContainer.appendChild(inpSpec);
     colorPickerContainer.append(" ");
     colorPickerContainer.appendChild(inpResetLink);
 
     if (typeof parentEl !== undefined) {
         parentEl.appendChild(colorPickerContainer);
     } else {
         alert("Must supply parentNode to append color picker");
         throw new Error();
     }
+    let keepPickerOffButtons = function() {
+        // The picker opens downwards, covering whatever sits below the swatch. In a jQuery UI
+        // dialog that is the row of buttons ("Save Color", "Add Highlight", ...), which then
+        // cannot be clicked at all, so open above the swatch instead when that would happen.
+        let container = $(inpSpec).spectrum("container");
+        let buttons = $(inpSpec).closest(".ui-dialog").find(".ui-dialog-buttonpane");
+        let swatch = $(colorPickerContainer).find(".sp-replacer");
+        if (container.length === 0 || buttons.length === 0 || swatch.length === 0)
+            return;
+        let cRect = container[0].getBoundingClientRect();
+        let bRect = buttons[0].getBoundingClientRect();
+        if (cRect.bottom <= bRect.top || cRect.top >= bRect.bottom)
+            return;  // does not cover the buttons, leave it where spectrum put it
+        if (swatch[0].getBoundingClientRect().top - cRect.height < 0)
+            return;  // no room above either, moving it would push it off the page
+        container.css("top", (swatch.offset().top - cRect.height) + "px");
+    };
+
     let opt = {
         hideAfterPaletteSelect: true,
         color: $(inpSpec).val(),
         showPalette: true,
         showInput: true,
         showSelectionPalette: true,
         showInitial: true,
         preferredFormat: "hex",
         localStorageKey: "genomebrowser",
+        move: function(color) {
+            // Dragging in the picker only repaints the picker itself, spectrum does not
+            // consider the color chosen until 'choose' is clicked. Show it in the text box
+            // anyway: that box is what the buttons of the drag select dialog read, so
+            // otherwise they act on the color that was showing before the drag.
+            $(inpText).val(color.toHexString());
+        },
+        hide: function(color) {
+            // Resync with the picker, so a cancelled drag does not leave the dragged
+            // color behind in the text box.
+            $(inpText).val(color.toHexString());
+        },
+        show: keepPickerOffButtons,
         change: function() {
-            let color = $(inpSpec).spectrum("get");
+            let color = $(inpSpec).spectrum("get").toHexString();
             $(inpText).val(color);
             saveHlColor(color, trackName);
         },
     };
     $(inpSpec).spectrum(opt);
 
     // update the color picker if you change the input box
     $(inpText).on("change", function() {
         $(inpSpec).spectrum("set", $(inpText).val());
         saveHlColor($(inpText).val(), trackName);
     });
 
     // Restore the default on Reset link click
     $(inpResetLink).on("click", function() {
         let hlDefault = hlColorDefault;