[LON-CAPA-cvs] cvs: loncom /interface loncommon.pm lonmenu.pm
raeburn
raeburn at source.lon-capa.org
Thu Nov 20 12:06:26 EST 2025
raeburn Thu Nov 20 17:06:26 2025 EDT
Modified files:
/loncom/interface loncommon.pm lonmenu.pm
Log:
- WCAG 2 compliance.
Access to primary and secondary menu items with hoverable drop-down lists
by keyboard actions with aria-expanded attribute updated accordingly.
-------------- next part --------------
Index: loncom/interface/loncommon.pm
diff -u loncom/interface/loncommon.pm:1.1482 loncom/interface/loncommon.pm:1.1483
--- loncom/interface/loncommon.pm:1.1482 Sun Nov 2 01:42:00 2025
+++ loncom/interface/loncommon.pm Thu Nov 20 17:06:26 2025
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# a pile of common routines
#
-# $Id: loncommon.pm,v 1.1482 2025/11/02 01:42:00 raeburn Exp $
+# $Id: loncommon.pm,v 1.1483 2025/11/20 17:06:26 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -7000,6 +7000,138 @@
if ($public) {
undef($role);
+ } else {
+ my $dropdownjs = <<ENDJS;
+<script type="text/javascript">
+// <![CDATA[
+
+document.addEventListener("DOMContentLoaded", function() {
+ var menuItems1 = document.querySelectorAll('#LC_nav_bar > ol > li.LC_hoverable');
+ var hovertimer, tabtimer;
+
+ Array.prototype.forEach.call(menuItems1, function(el, i){
+ el.addEventListener("mouseover", function(event){
+ this.className = "LC_hoverable LC_open";
+ clearTimeout(hovertimer);
+ });
+ el.addEventListener("mouseout", function(event){
+ hovertimer = setTimeout(function(event) {
+ var opennav = document.querySelector("#LC_nav_bar .LC_hoverable.LC_open");
+ if (opennav) {
+ opennav.className = "LC_hoverable";
+ opennav.querySelector('a').setAttribute('aria-expanded', "false");
+ }
+ }, 1000);
+ });
+ el.querySelector('a').addEventListener("click", function(event) {
+ if (this.parentNode.className == "LC_hoverable") {
+ this.parentNode.className = "LC_hoverable LC_open";
+ this.setAttribute('aria-expanded', "true");
+ } else {
+ this.parentNode.className = "LC_hoverable";
+ this.setAttribute('aria-expanded', "false");
+ }
+ event.preventDefault();
+ });
+ var links = el.querySelectorAll('a');
+ Array.prototype.forEach.call(links, function(el, i) {
+ el.addEventListener("focus", function(event) {
+ if (tabtimer) {
+ clearTimeout(tabtimer);
+ tabtimer = null;
+ }
+ });
+ el.addEventListener("blur", function(event) {
+ tabtimer = setTimeout(function() {
+ var opennav = document.querySelector("#LC_nav_bar .LC_hoverable.LC_open");
+ if (opennav) {
+ opennav.className = "LC_hoverable";
+ opennav.querySelector('a').setAttribute('aria-expanded', "false");
+ }
+ }, 10);
+ });
+ });
+ });
+ var menuItems2 = document.querySelectorAll("ul#LC_secondary_menu li.LC_hoverable");
+ let hoverTimeoutIds = [];
+ let tabTimeoutIds = [];
+
+ Array.prototype.forEach.call(menuItems2, function(el, i){
+ el.addEventListener("mouseover", function(event){
+ while (hoverTimeoutIds.length > 0) {
+ const timeoutId = hoverTimeoutIds.pop();
+ clearTimeout(timeoutId);
+ }
+ document.querySelectorAll("ul#LC_secondary_menu li.LC_hoverable.LC_open").forEach(element => {
+ element.className = "LC_hoverable";
+ });
+ this.className = "LC_hoverable LC_open";
+ });
+ el.addEventListener("mouseout", function(event) {
+ const timeoutId = setTimeout(function() {
+ var opennav = document.querySelector("ul#LC_secondary_menu li.LC_hoverable.LC_open");
+ if (opennav) {
+ opennav.className = "LC_hoverable";
+ opennav.querySelector('a').setAttribute('aria-expanded', "false");
+ }
+ }, 500);
+ hoverTimeoutIds.push(timeoutId);
+ });
+ el.querySelector('a').addEventListener("click", function(event){
+ if (tabTimeoutIds.length) {
+ while (tabTimeoutIds.length > 0) {
+ const timeoutId = tabTimeoutIds.pop();
+ clearTimeout(timeoutId);
+ }
+ }
+ document.querySelectorAll("ul#LC_secondary_menu li.LC_hoverable.LC_open").forEach(element => {
+ if (element !== this.parentNode) {
+ element.className = "LC_hoverable";
+ }
+ });
+ if (this.parentNode.className == "LC_hoverable") {
+ this.parentNode.className = "LC_hoverable LC_open";
+ this.setAttribute('aria-expanded', "true");
+ } else {
+ this.parentNode.className = "LC_hoverable";
+ this.setAttribute('aria-expanded', "false");
+ }
+ event.preventDefault();
+ });
+
+ var links = el.querySelectorAll('ul#LC_secondary_menu li a');
+ var numlinks = links.length;
+ Array.prototype.forEach.call(links, function(el, i){
+ el.addEventListener("focus", function(event) {
+ if (tabTimeoutIds.length) {
+ while (tabTimeoutIds.length > 0) {
+ const timeoutId = tabTimeoutIds.pop();
+ clearTimeout(timeoutId);
+ }
+ }
+ document.querySelectorAll("ul#LC_secondary_menu li.LC_hoverable.LC_open").forEach(element => {
+ if (element !== event.target.closest('li.LC_hoverable.LC_open')) {
+ element.className = "LC_hoverable";
+ }
+ });
+ });
+ el.addEventListener("blur", function(event) {
+ const timeoutId = setTimeout(function () {
+ var opennav = document.querySelector("ul#LC_secondary_menu li.LC_hoverable.LC_open");
+ if (opennav) {
+ opennav.className = "LC_hoverable";
+ opennav.querySelector('a').setAttribute('aria-expanded',"false");
+ }
+ }, 10);
+ tabTimeoutIds.push(timeoutId);
+ });
+ });
+ });
+});
+// ]]>
+</script>
+ENDJS
+ $bodytag .= "\n$dropdownjs\n";
}
my $showcrstitle = 1;
@@ -8880,7 +9012,7 @@
top: 0;
}
-ol.LC_primary_menu li:hover > ul, ol.LC_primary_menu li.hover > 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;
@@ -8888,7 +9020,7 @@
z-index: 2;
}
-ol.LC_primary_menu li:hover li, ol.LC_primary_menu li.hover li {
+ol.LC_primary_menu li:hover li, ol.LC_primary_menu li.hover li, ol.LC_primary_menu li.LC_open li {
/* First Submenu -> size should be smaller than the menu title of the whole menu */
font-size: 90%;
vertical-align: top;
@@ -8912,7 +9044,7 @@
}
/* Font-size equal to the size of the predecessors*/
-ol.LC_primary_menu li:hover li li {
+ol.LC_primary_menu li:hover li li, ol.LC_primary_menu li.LC_open li li {
font-size: 100%;
}
@@ -8975,7 +9107,7 @@
float: left;
}
-ul#LC_secondary_menu li.LC_hoverable:hover, ul#LC_secondary_menu li.hover {
+ul#LC_secondary_menu li.LC_hoverable:hover, ul#LC_secondary_menu li.hover, ul#LC_secondary_menu li.LC_open {
background-color: $data_table_light;
}
@@ -8987,7 +9119,7 @@
display: none;
}
-ul#LC_secondary_menu li:hover ul, ul#LC_secondary_menu li.hover ul {
+ul#LC_secondary_menu li:hover ul, ul#LC_secondary_menu li.hover ul, ul#LC_secondary_menu li.LC_open ul {
display: block;
position: absolute;
margin: 0;
@@ -9009,7 +9141,7 @@
float: none;
}
-ul#LC_secondary_menu li ul li:hover, ul#LC_secondary_menu li ul li.hover {
+ul#LC_secondary_menu li ul li:hover, ul#LC_secondary_menu li ul li.hover, ul#LC_secondary_menu li ul li.LC_open {
background-color: $data_table_dark;
}
Index: loncom/interface/lonmenu.pm
diff -u loncom/interface/lonmenu.pm:1.564 loncom/interface/lonmenu.pm:1.565
--- loncom/interface/lonmenu.pm:1.564 Sun Sep 7 04:21:13 2025
+++ loncom/interface/lonmenu.pm Thu Nov 20 17:06:26 2025
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# Routines to control the menu
#
-# $Id: lonmenu.pm,v 1.564 2025/09/07 04:21:13 raeburn Exp $
+# $Id: lonmenu.pm,v 1.565 2025/11/20 17:06:26 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -814,7 +814,9 @@
if (($target ne '') && ($link ne '#')) {
$targetattr = ' target="'.$target.'"';
}
- my $menu = '<li class="LC_hoverable '.$addclass.'">'.
+ my $cssclass = "LC_hoverable $addclass";
+ $cssclass =~ s/\s+$//;
+ my $menu = '<li class="'.$cssclass.'" aria-expanded="false">'.
'<a href="'.$link.'"'.$targetattr.'>'.
'<span class="LC_nobreak">'.$title.
'<span class="LC_fontsize_medium" style="font-weight:normal;">'.
More information about the LON-CAPA-cvs
mailing list