[LON-CAPA-cvs] cvs: loncom /homework lonhomework.pm /interface loncommon.pm lonmenu.pm

raeburn raeburn at source.lon-capa.org
Wed Nov 26 14:43:21 EST 2025


raeburn		Wed Nov 26 19:43:21 2025 EDT

  Modified files:              
    /loncom/interface	loncommon.pm lonmenu.pm 
    /loncom/homework	lonhomework.pm 
  Log:
  - WCAG 2 compliance
    Access to items with nested hoverable drop-down lists in Problem Editing
    menu (Edit XML mode) by keyboard actions (enter/tab) with aria-expanded
    attribute updated accordingly.
  
  
-------------- next part --------------
Index: loncom/interface/loncommon.pm
diff -u loncom/interface/loncommon.pm:1.1484 loncom/interface/loncommon.pm:1.1485
--- loncom/interface/loncommon.pm:1.1484	Tue Nov 25 16:54:00 2025
+++ loncom/interface/loncommon.pm	Wed Nov 26 19:43:20 2025
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # a pile of common routines
 #
-# $Id: loncommon.pm,v 1.1484 2025/11/25 16:54:00 raeburn Exp $
+# $Id: loncommon.pm,v 1.1485 2025/11/26 19:43:20 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -7004,7 +7004,7 @@
     }
     my @hasdropdowns;
     unless ($args->{'no_primary_menu'}) {
-        push(@hasdropdowns,'#LC_nav_bar > ol >'); 
+        push(@hasdropdowns,'#LC_nav_bar > ol >');
     }
     unless ($args->{'no_secondary_menu'}) {
         push(@hasdropdowns,'ul#LC_secondary_menu');
@@ -7013,6 +7013,19 @@
         (!$env{'form.selectrole'})) {
         push(@hasdropdowns,'div#LC_breadcrumbs ol.LC_primary_menu');
     }
+    if ($env{'request.state'} eq "construct") {
+        &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},
+                                                ['problemmode']);
+        if (($env{'form.problemmode'} eq 'editxml') ||
+            ($env{'form.problemmode'} eq 'saveeditxml') ||
+            ($env{'form.problemmode'} eq 'saveviewxml') ||
+            ($env{'form.problemmode'} eq 'undoxml')) {
+            my %editors = &Apache::loncommon::permitted_editors();
+            if (($editors{'xml'}) || ($env{'form.problemmode'} eq 'saveviewxml') || ($env{'form.problemmode'} eq 'undoxml')) {
+                push(@hasdropdowns,'ol#LC_editXML');
+            }
+        }
+    }
     if (@hasdropdowns) {
         my $jsarray = '["'.join('","', at hasdropdowns).'"]';
         $dropdownjs = <<ENDJS;
@@ -7024,24 +7037,26 @@
     let tabTimeoutIds = [];
 
     Array.prototype.forEach.call(menuItems, function(el, i){
-      el.addEventListener("mouseover", function(event){
+      el.addEventListener("mouseenter", function(event){
         while (hoverTimeoutIds.length > 0) {
           const timeoutId = hoverTimeoutIds.pop();
           clearTimeout(timeoutId);
         }
         document.querySelectorAll(anc+" li.LC_hoverable.LC_open").forEach(element => {
           element.classList.remove("LC_open");
+          element.querySelector('a').setAttribute('aria-expanded',"false");
         });
         if (!hasClass(this,"LC_open")) {
           this.classList.add("LC_open");
+          this.querySelector('a').setAttribute('aria-expanded',"true");
         }
       });
-      el.addEventListener("mouseout", function(event) {
+      el.addEventListener("mouseleave", function(event) {
         const timeoutId = setTimeout(function() {
           var opennav = document.querySelector(anc+" li.LC_hoverable.LC_open");
           if (opennav) {
             opennav.classList.remove("LC_open");
-            opennav.querySelector('a').setAttribute('aria-expanded', "false");
+            opennav.querySelector('a').setAttribute('aria-expanded',"false");
           }
         }, 500);
         hoverTimeoutIds.push(timeoutId);
@@ -7053,19 +7068,19 @@
             clearTimeout(timeoutId);
           }
         }
+        const anclist = getAllAncestorsWithClass(this,'LC_hoverable');
         document.querySelectorAll(anc+" li.LC_hoverable.LC_open").forEach(element => {
-          if (element !== this.parentNode) {
+          if (anclist.indexOf(element) == -1) {
             element.classList.remove("LC_open");
+            element.querySelector('a').setAttribute('aria-expanded',"false");
           }
         });
-        if (hasClass(this.parentNode,"LC_hoverable")) {
-          if (!hasClass(this.parentNode,"LC_open")) {
-            this.parentNode.classList.add("LC_open");
-          }
-          this.setAttribute('aria-expanded', "true");
+        if (!hasClass(this.parentNode,"LC_open")) {
+          this.parentNode.classList.add("LC_open");
+          this.setAttribute('aria-expanded',"true");
         } else {
-          this.parentNode.add("LC_hoverable"); 
-          this.setAttribute('aria-expanded', "false");
+          this.parentNode.classList.remove("LC_open");
+          this.setAttribute('aria-expanded',"false");
         }
         event.preventDefault();
       });
@@ -7080,9 +7095,11 @@
               clearTimeout(timeoutId);
             }
           }
+          const anclist = getAllAncestorsWithClass(event.target,'LC_hoverable');
           document.querySelectorAll(anc+" li.LC_hoverable.LC_open").forEach(element => {
-            if (element !== event.target.closest('li.LC_hoverable.LC_open')) {
+            if (anclist.indexOf(element) == -1) {
               element.classList.remove("LC_open");
+              element.querySelector('a').setAttribute('aria-expanded',"false");
             }
           });
         });
@@ -7118,6 +7135,20 @@
     return new RegExp('(^| )' + className + '( |$)', 'gi').test(el.className);
   }
 }
+
+function getAllAncestorsWithClass(element, className) {
+  const ancestors = [];
+  let currentElement = element.parentElement; // Start with the immediate parent
+
+  while (currentElement) {
+    if (currentElement.classList.contains(className)) {
+      ancestors.push(currentElement);
+    }
+    currentElement = currentElement.parentElement; // Move up to the next parent
+  }
+  return ancestors;
+}
+
 // ]]>
 </script>
 
@@ -8967,21 +8998,20 @@
   line-height: 1.5em;
 }
 
-ol.LC_primary_menu li a,
-ol.LC_primary_menu li p {
+ol.LC_primary_menu li a {
   display: block;
   margin: 0;
   padding: 0 5px 0 10px;
   text-decoration: none;
 }
 
-ol.LC_primary_menu li p span.LC_primary_menu_innertitle {
+ol.LC_primary_menu li a span.LC_primary_menu_innertitle {
   display: inline-block;
   width: 95%;
   text-align: left;
 }
 
-ol.LC_primary_menu li p span.LC_primary_menu_innerarrow {
+ol.LC_primary_menu li a span.LC_primary_menu_innerarrow {
   display: inline-block;	
   width: 5%;
   float: right;
@@ -9002,7 +9032,7 @@
   top: 0;
 }
 
-ol.LC_primary_menu li:hover > ul, ol.LC_primary_menu li.hover > ul, ol.LC_primary_menu li.LC_open > ul { 
+ol.LC_primary_menu li:hover > ul, ol.LC_primary_menu li.hover > ul, ol.LC_primary_menu li.LC_open > ul {
   display: block;
   position: absolute;
   margin: 0;
@@ -9022,12 +9052,6 @@
   border-bottom: 1px solid $data_table_dark; 
 }
 
-ol.LC_primary_menu li li p:hover {
-  color:$button_hover;
-  text-decoration:none;
-  background-color:$data_table_dark;
-}
-
 ol.LC_primary_menu li li a:hover {
    color:$button_hover;
    background-color:$data_table_dark;
Index: loncom/interface/lonmenu.pm
diff -u loncom/interface/lonmenu.pm:1.566 loncom/interface/lonmenu.pm:1.567
--- loncom/interface/lonmenu.pm:1.566	Tue Nov 25 16:54:00 2025
+++ loncom/interface/lonmenu.pm	Wed Nov 26 19:43:20 2025
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # Routines to control the menu
 #
-# $Id: lonmenu.pm,v 1.566 2025/11/25 16:54:00 raeburn Exp $
+# $Id: lonmenu.pm,v 1.567 2025/11/26 19:43:20 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -871,9 +871,10 @@
 
             # href is a reference to another submenu
             if (ref($href) eq 'ARRAY') {
-                $menu .= '<li style="margin:0;padding:0;'.$bordertop . $borderbot . '">';
-                $menu .= '<p><span class="LC_primary_menu_innertitle">'
-					. $title . '</span><span class="LC_primary_menu_innerarrow">▶</span></p>';
+                $menu .= '<li style="margin:0;padding:0;'.$bordertop . $borderbot . '" class="LC_hoverable">';
+                $menu .= '<a href="#" aria-expanded="false">'
+                        .'<span class="LC_primary_menu_innertitle">'
+                        . $title . '</span><span class="LC_primary_menu_innerarrow">▶</span></a>';
                 $menu .= '<ul>';
                 $menu .= &build_submenu($target, $href, $translate);
                 $menu .= '</ul>';
Index: loncom/homework/lonhomework.pm
diff -u loncom/homework/lonhomework.pm:1.400 loncom/homework/lonhomework.pm:1.401
--- loncom/homework/lonhomework.pm:1.400	Thu Sep 11 02:06:01 2025
+++ loncom/homework/lonhomework.pm	Wed Nov 26 19:43:21 2025
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # The LON-CAPA Homework handler
 #
-# $Id: lonhomework.pm,v 1.400 2025/09/11 02:06:01 raeburn Exp $
+# $Id: lonhomework.pm,v 1.401 2025/11/26 19:43:21 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -1336,7 +1336,7 @@
                   &Apache::structuretags::problem_edit_buttons('editxml');
     $result.='<div>';
 
-    $result .= '<ol class="LC_primary_menu" style="display:inline-block;font-size:90%;vertical-align:middle;">';
+    $result .= '<ol id="LC_editXML" class="LC_primary_menu" style="display:inline-block;font-size:90%;vertical-align:middle;">';
 
     my $nocodemirror = &Apache::loncommon::nocodemirror();
     unless ($nocodemirror) {


More information about the LON-CAPA-cvs mailing list