ade21ee4e0109c70e29860d5393306e48e6a44d3
lrnassar
  Mon Mar 16 16:38:58 2026 -0700
Adding keyboard navigation accessibility to the Genome Browser menu bar and all main CGI pages. Converts non-focusable menu bar span elements to keyboard-accessible buttons using the W3C disclosure navigation pattern, adds aria-expanded state for screen readers, skip navigation link, and main content landmark. Also adds a Keyboard Navigation section to the accessibility page with screenshots. refs #37252

diff --git src/hg/js/jquery.plugins.js src/hg/js/jquery.plugins.js
index 621528324ed..fcbeb81d373 100644
--- src/hg/js/jquery.plugins.js
+++ src/hg/js/jquery.plugins.js
@@ -148,15 +148,114 @@
     $('ul.nice-menu').superfish({
       // Apply a generic hover class.
       hoverClass: 'over',
       // Disable generation of arrow mark-up.
       autoArrows: false,
       // Disable drop shadows.
       dropShadows: false,
       // Mouse delay.
       delay: 800,
       // Animation speed.
       speed: 1
     });
     $('ul.nice-menu ul').css('display', 'none');
   });
 })(jQuery);
+
+
+// Keyboard accessibility for menu bar disclosure buttons (WCAG 2.1)
+(function ($) {
+  $(document).ready(function() {
+    var $menu = $('ul.nice-menu');
+    if (!$menu.length) return;
+    var suppressFocus = false; // prevent focus handler from interfering with click/escape
+
+    // Track mouse clicks so focus handler can distinguish Tab from click
+    $menu.on('mousedown', 'li.menuparent > button', function() {
+      suppressFocus = true;
+    });
+
+    // Toggle dropdown when a disclosure button is clicked (Enter/Space/click)
+    $menu.on('click', 'li.menuparent > button', function(e) {
+      e.preventDefault();
+      suppressFocus = false;
+      var $li = $(this).parent();
+      var isOpen = $li.hasClass('over');
+      // Close all open menus first
+      $menu.find('li.menuparent').removeClass('over')
+        .find('> ul').hide().css('visibility', 'hidden');
+      $menu.find('li.menuparent > button, li.menuparent > a')
+        .attr('aria-expanded', 'false');
+      if (!isOpen) {
+        $li.addClass('over').find('> ul').show().css('visibility', 'visible');
+        $(this).attr('aria-expanded', 'true');
+      }
+    });
+
+    // Focus on a button shows its dropdown (Tab navigation only, not mouse click)
+    $menu.on('focus', 'li.menuparent > button', function() {
+      if (suppressFocus) { suppressFocus = false; return; }
+      var $li = $(this).parent();
+      $li.showSuperfishUl().siblings().hideSuperfishUl();
+      $(this).attr('aria-expanded', 'true');
+      $li.siblings().find('> button, > a').attr('aria-expanded', 'false');
+    });
+
+    $menu.on('blur', 'li.menuparent > button', function() {
+      var $li = $(this).parent();
+      var o = $.fn.superfish.op;
+      var delay = (o && o.delay) ? o.delay : 800;
+      var menu = $menu[0];
+      clearTimeout(menu.sfTimer);
+      var $btn = $(this);
+      menu.sfTimer = setTimeout(function() {
+        $li.hideSuperfishUl();
+        $btn.attr('aria-expanded', 'false');
+      }, delay);
+    });
+
+    // Sync aria-expanded for <a>-based parent items (Genomes, Genome Browser)
+    $menu.on('focus', 'li.menuparent > a[aria-expanded]', function() {
+      $(this).attr('aria-expanded', 'true');
+    });
+
+    $menu.on('blur', 'li.menuparent > a[aria-expanded]', function() {
+      var $a = $(this);
+      setTimeout(function() {
+        if (!$a.parent().hasClass('over')) {
+          $a.attr('aria-expanded', 'false');
+        }
+      }, 900);
+    });
+
+    // Sync aria-expanded on mouse hover for all parent items
+    $menu.on('mouseenter', 'li.menuparent', function() {
+      $(this).find('> button, > a[aria-expanded]').attr('aria-expanded', 'true');
+    });
+
+    $menu.on('mouseleave', 'li.menuparent', function() {
+      var $trigger = $(this).find('> button, > a[aria-expanded]');
+      var o = $.fn.superfish.op;
+      var delay = (o && o.delay) ? o.delay : 800;
+      setTimeout(function() {
+        if (!$trigger.parent().hasClass('over')) {
+          $trigger.attr('aria-expanded', 'false');
+        }
+      }, delay + 100);
+    });
+
+    // Escape key closes any open dropdown and returns focus to its trigger
+    $(document).on('keydown', function(e) {
+      if (e.key === 'Escape' || e.keyCode === 27) {
+        var $openLi = $menu.find('li.menuparent.over');
+        if ($openLi.length) {
+          $openLi.removeClass('over').find('> ul').hide().css('visibility', 'hidden');
+          var $trigger = $openLi.find('> button, > a').first();
+          $trigger.attr('aria-expanded', 'false');
+          suppressFocus = true; // prevent focus handler from re-opening
+          $trigger.focus();
+          e.stopPropagation();
+        }
+      }
+    });
+  });
+})(jQuery);