diff --git a/lib/outputcomponents.php b/lib/outputcomponents.php
index db091bf2525..613ef61dbab 100644
--- a/lib/outputcomponents.php
+++ b/lib/outputcomponents.php
@@ -4340,6 +4340,7 @@ class action_menu implements renderable, templatable {
public function add_primary_action($action) {
if ($action instanceof action_link || $action instanceof pix_icon) {
$action->attributes['role'] = 'menuitem';
+ $action->attributes['tabindex'] = '-1';
if ($action instanceof action_menu_link) {
$action->actionmenu = $this;
}
@@ -4355,6 +4356,7 @@ class action_menu implements renderable, templatable {
public function add_secondary_action($action) {
if ($action instanceof action_link || $action instanceof pix_icon) {
$action->attributes['role'] = 'menuitem';
+ $action->attributes['tabindex'] = '-1';
if ($action instanceof action_menu_link) {
$action->actionmenu = $this;
}
@@ -4412,7 +4414,8 @@ class action_menu implements renderable, templatable {
'title' => $title,
'aria-label' => $label,
'id' => 'action-menu-toggle-'.$this->instance,
- 'role' => 'menuitem'
+ 'role' => 'menuitem',
+ 'tabindex' => '-1',
);
$link = html_writer::link('#', $string . $this->menutrigger . $pixicon, $attributes);
if ($this->prioritise) {
diff --git a/lib/templates/user_action_menu_items.mustache b/lib/templates/user_action_menu_items.mustache
index ff8942af6fd..29a177973f3 100644
--- a/lib/templates/user_action_menu_items.mustache
+++ b/lib/templates/user_action_menu_items.mustache
@@ -59,7 +59,7 @@
}}
{{#items}}
{{#link}}
-
+
{{#pixicon}}
{{#pix}}{{pixicon}}{{/pix}}
{{/pixicon}}
@@ -70,7 +70,7 @@
{{/link}}
{{#submenulink}}
-
+
{{#pixicon}}
{{#pix}}{{pixicon}}{{/pix}}
{{/pixicon}}
diff --git a/lib/templates/user_action_menu_submenu_items.mustache b/lib/templates/user_action_menu_submenu_items.mustache
index 18896c4b900..605ab867116 100644
--- a/lib/templates/user_action_menu_submenu_items.mustache
+++ b/lib/templates/user_action_menu_submenu_items.mustache
@@ -41,7 +41,7 @@
}}
{{#items}}
{{#link}}
-
+
{{text}}
{{/link}}
diff --git a/theme/boost/amd/build/aria.min.js b/theme/boost/amd/build/aria.min.js
index fd7209e5b94..ce1aae25109 100644
--- a/theme/boost/amd/build/aria.min.js
+++ b/theme/boost/amd/build/aria.min.js
@@ -5,6 +5,6 @@ define("theme_boost/aria",["exports","jquery","core/pending"],(function(_exports
* @module theme_boost/aria
* @copyright 2018 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_jquery=_interopRequireDefault(_jquery),_pending=_interopRequireDefault(_pending);const dropdownFix=()=>{let focusEnd=!1;document.addEventListener("keydown",(e=>{if(e.target.matches('[data-toggle="dropdown"]')){const trigger=e.key;"ArrowUp"==trigger&&(focusEnd=!0)," "!=trigger&&"Enter"!=trigger||(e.preventDefault(),e.target.click())}}));const shiftFocus=element=>{setTimeout((pendingPromise=>{element.focus(),pendingPromise.resolve()}),50,new _pending.default("core/aria:delayed-focus"))};(0,_jquery.default)(".dropdown").on("shown.bs.dropdown",(e=>{const menu=e.target.querySelector('[role="menu"]');let menuItems=!1,foundMenuItem=!1;menu&&(menuItems=menu.querySelectorAll('[role="menuitem"]')),menuItems&&menuItems.length>0&&(foundMenuItem=(()=>{const result=focusEnd;return focusEnd=!1,result})()?menuItems[menuItems.length-1]:menuItems[0]),foundMenuItem&&shiftFocus(foundMenuItem)})),document.addEventListener("keypress",(e=>{if(e.target.matches('.dropdown [role="menu"] [role="menuitem"]')){const menu=e.target.closest('[role="menu"]');if(!menu)return;const menuItems=menu.querySelectorAll('[role="menuitem"]');if(!menuItems)return;const trigger=e.key.toLowerCase();for(let i=0;i{if(e.target.matches('.dropdown [role="menu"] [role="menuitem"]')){const trigger=e.key;let next=!1;const menu=e.target.closest('[role="menu"]');if(!menu)return;const menuItems=menu.querySelectorAll('[role="menuitem"]');if(!menuItems)return;if("ArrowDown"==trigger){for(let i=0;i{const trigger=e.target.querySelector('[data-toggle="dropdown"]');trigger&&shiftFocus(trigger)}))},tabElementFix=()=>{document.addEventListener("keydown",(e=>{["ArrowUp","ArrowDown","ArrowLeft","ArrowRight","Home","End","Enter"," "].includes(e.key)&&e.target.matches('[role="tablist"] [role="tab"]')&&(e=>{const tabList=e.target.closest('[role="tablist"]'),vertical="vertical"==tabList.getAttribute("aria-orientation"),rtl=window.right_to_left(),arrowNext=vertical?"ArrowDown":rtl?"ArrowLeft":"ArrowRight",arrowPrevious=vertical?"ArrowUp":rtl?"ArrowRight":"ArrowLeft",tabs=Array.prototype.filter.call(tabList.querySelectorAll('[role="tab"]'),(tab=>"none"!==getComputedStyle(tab).display));for(let i=0;i{tab.tabIndex=-1})),e.target.tabIndex=0}})(e)})),document.addEventListener("click",(e=>{if(e.target.matches('[role="tablist"] [role="tab"]')){const tabs=e.target.closest('[role="tablist"]').querySelectorAll('[role="tab"]');e.preventDefault(),(0,_jquery.default)(e.target).tab("show"),tabs.forEach((tab=>{tab.tabIndex=-1})),e.target.tabIndex=0}}))};_exports.init=()=>{dropdownFix(),window.addEventListener("load",(()=>{const alerts=document.querySelectorAll('[data-aria-autofocus="true"][role="alert"]');Array.prototype.forEach.call(alerts,(autofocusElement=>{autofocusElement.innerHTML+=" ",autofocusElement.removeAttribute("data-aria-autofocus")}))})),tabElementFix()}}));
+ */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_jquery=_interopRequireDefault(_jquery),_pending=_interopRequireDefault(_pending);const dropdownFix=()=>{let focusEnd=!1,focusBackToTrigger=!0;const setFocusEnd=function(){let end=!(arguments.length>0&&void 0!==arguments[0])||arguments[0];focusEnd=end},shiftFocus=element=>{setTimeout((pendingPromise=>{element.focus(),pendingPromise.resolve()}),50,new _pending.default("core/aria:delayed-focus"))},handleMenuButton=e=>{const trigger=e.key;let fixFocus=!1;if(" "!==trigger&&"Enter"!==trigger||(fixFocus=!0,e.preventDefault(),e.target.click()),"ArrowUp"!==trigger&&"ArrowDown"!==trigger||(fixFocus=!0),"Tab"===trigger&&(focusBackToTrigger=!1),!fixFocus)return;const menu=e.target.parentElement.querySelector('[role="menu"]');let menuItems=!1,foundMenuItem=!1;menu&&(menuItems=menu.querySelectorAll('[role="menuitem"]')),menuItems&&menuItems.length>0&&("ArrowUp"===trigger?setFocusEnd():setFocusEnd(!1),foundMenuItem=(()=>{const result=focusEnd;return focusEnd=!1,result})()?menuItems[menuItems.length-1]:menuItems[0]),foundMenuItem&&shiftFocus(foundMenuItem)};document.addEventListener("keypress",(e=>{if(e.target.matches('.dropdown [role="menu"] [role="menuitem"]')){const menu=e.target.closest('[role="menu"]');if(!menu)return;const menuItems=menu.querySelectorAll('[role="menuitem"]');if(!menuItems)return;const trigger=e.key.toLowerCase();for(let i=0;i{if(e.target.matches('[data-toggle="dropdown"]')&&handleMenuButton(e),e.target.matches('.dropdown [role="menu"] [role="menuitem"]')){const trigger=e.key;let next=!1;const menu=e.target.closest('[role="menu"]');if(!menu)return;const menuItems=menu.querySelectorAll('[role="menuitem"]');if(!menuItems)return;if("ArrowDown"==trigger){for(let i=0;i{const trigger=e.target.querySelector('[data-toggle="dropdown"]');trigger&&focusBackToTrigger&&shiftFocus(trigger),focusBackToTrigger=!0}))},tabElementFix=()=>{document.addEventListener("keydown",(e=>{["ArrowUp","ArrowDown","ArrowLeft","ArrowRight","Home","End","Enter"," "].includes(e.key)&&e.target.matches('[role="tablist"] [role="tab"]')&&(e=>{const tabList=e.target.closest('[role="tablist"]'),vertical="vertical"==tabList.getAttribute("aria-orientation"),rtl=window.right_to_left(),arrowNext=vertical?"ArrowDown":rtl?"ArrowLeft":"ArrowRight",arrowPrevious=vertical?"ArrowUp":rtl?"ArrowRight":"ArrowLeft",tabs=Array.prototype.filter.call(tabList.querySelectorAll('[role="tab"]'),(tab=>"none"!==getComputedStyle(tab).display));for(let i=0;i{tab.tabIndex=-1})),e.target.tabIndex=0}})(e)})),document.addEventListener("click",(e=>{if(e.target.matches('[role="tablist"] [role="tab"]')){const tabs=e.target.closest('[role="tablist"]').querySelectorAll('[role="tab"]');e.preventDefault(),(0,_jquery.default)(e.target).tab("show"),tabs.forEach((tab=>{tab.tabIndex=-1})),e.target.tabIndex=0}}))};_exports.init=()=>{dropdownFix(),window.addEventListener("load",(()=>{const alerts=document.querySelectorAll('[data-aria-autofocus="true"][role="alert"]');Array.prototype.forEach.call(alerts,(autofocusElement=>{autofocusElement.innerHTML+=" ",autofocusElement.removeAttribute("data-aria-autofocus")}))})),tabElementFix()}}));
//# sourceMappingURL=aria.min.js.map
\ No newline at end of file
diff --git a/theme/boost/amd/build/aria.min.js.map b/theme/boost/amd/build/aria.min.js.map
index bde28a14b4f..cfb45545c7c 100644
--- a/theme/boost/amd/build/aria.min.js.map
+++ b/theme/boost/amd/build/aria.min.js.map
@@ -1 +1 @@
-{"version":3,"file":"aria.min.js","sources":["../src/aria.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Enhancements to Bootstrap components for accessibility.\n *\n * @module theme_boost/aria\n * @copyright 2018 Damyon Wiese \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport Pending from 'core/pending';\n\n/**\n * Drop downs from bootstrap don't support keyboard accessibility by default.\n */\nconst dropdownFix = () => {\n let focusEnd = false;\n const setFocusEnd = () => {\n focusEnd = true;\n };\n const getFocusEnd = () => {\n const result = focusEnd;\n focusEnd = false;\n return result;\n };\n\n // Special handling for \"up\" keyboard control.\n document.addEventListener('keydown', e => {\n if (e.target.matches('[data-toggle=\"dropdown\"]')) {\n const trigger = e.key;\n\n // Up key opens the menu at the end.\n if (trigger == 'ArrowUp') {\n // Focus the end of the menu, not the beginning.\n setFocusEnd();\n }\n\n // Space key or Enter key opens the menu.\n if (trigger == ' ' || trigger == 'Enter') {\n // Cancel random scroll.\n e.preventDefault();\n // Open the menu instead.\n e.target.click();\n }\n }\n });\n\n // Special handling for navigation keys when menu is open.\n const shiftFocus = element => {\n const delayedFocus = pendingPromise => {\n element.focus();\n pendingPromise.resolve();\n };\n setTimeout(delayedFocus, 50, new Pending('core/aria:delayed-focus'));\n };\n\n $('.dropdown').on('shown.bs.dropdown', e => {\n // We need to focus on the first menuitem.\n const menu = e.target.querySelector('[role=\"menu\"]');\n let menuItems = false;\n let foundMenuItem = false;\n\n if (menu) {\n menuItems = menu.querySelectorAll('[role=\"menuitem\"]');\n }\n if (menuItems && menuItems.length > 0) {\n if (getFocusEnd()) {\n foundMenuItem = menuItems[menuItems.length - 1];\n } else {\n // The first menu entry, pretty reasonable.\n foundMenuItem = menuItems[0];\n }\n }\n if (foundMenuItem) {\n shiftFocus(foundMenuItem);\n }\n });\n // Search for menu items by finding the first item that has\n // text starting with the typed character (case insensitive).\n document.addEventListener('keypress', e => {\n if (e.target.matches('.dropdown [role=\"menu\"] [role=\"menuitem\"]')) {\n const menu = e.target.closest('[role=\"menu\"]');\n if (!menu) {\n return;\n }\n const menuItems = menu.querySelectorAll('[role=\"menuitem\"]');\n if (!menuItems) {\n return;\n }\n\n const trigger = e.key.toLowerCase();\n\n for (let i = 0; i < menuItems.length; i++) {\n const item = menuItems[i];\n const itemText = item.text.trim().toLowerCase();\n if (itemText.indexOf(trigger) == 0) {\n shiftFocus(item);\n break;\n }\n }\n }\n });\n\n // Keyboard navigation for arrow keys, home and end keys.\n document.addEventListener('keydown', e => {\n if (e.target.matches('.dropdown [role=\"menu\"] [role=\"menuitem\"]')) {\n const trigger = e.key;\n let next = false;\n const menu = e.target.closest('[role=\"menu\"]');\n\n if (!menu) {\n return;\n }\n const menuItems = menu.querySelectorAll('[role=\"menuitem\"]');\n if (!menuItems) {\n return;\n }\n // Down key.\n if (trigger == 'ArrowDown') {\n for (let i = 0; i < menuItems.length - 1; i++) {\n if (menuItems[i] == e.target) {\n next = menuItems[i + 1];\n break;\n }\n }\n if (!next) {\n // Wrap to first item.\n next = menuItems[0];\n }\n\n } else if (trigger == 'ArrowUp') {\n // Up key.\n for (let i = 1; i < menuItems.length; i++) {\n if (menuItems[i] == e.target) {\n next = menuItems[i - 1];\n break;\n }\n }\n if (!next) {\n // Wrap to last item.\n next = menuItems[menuItems.length - 1];\n }\n\n } else if (trigger == 'Home') {\n // Home key.\n next = menuItems[0];\n\n } else if (trigger == 'End') {\n // End key.\n next = menuItems[menuItems.length - 1];\n }\n // Variable next is set if we do want to act on the keypress.\n if (next) {\n e.preventDefault();\n shiftFocus(next);\n }\n return;\n }\n });\n\n $('.dropdown').on('hidden.bs.dropdown', e => {\n // We need to focus on the menu trigger.\n const trigger = e.target.querySelector('[data-toggle=\"dropdown\"]');\n if (trigger) {\n shiftFocus(trigger);\n }\n });\n};\n\n/**\n * After page load, focus on any element with special autofocus attribute.\n */\nconst autoFocus = () => {\n window.addEventListener(\"load\", () => {\n const alerts = document.querySelectorAll('[data-aria-autofocus=\"true\"][role=\"alert\"]');\n Array.prototype.forEach.call(alerts, autofocusElement => {\n // According to the specification an role=\"alert\" region is only read out on change to the content\n // of that region.\n autofocusElement.innerHTML += ' ';\n autofocusElement.removeAttribute('data-aria-autofocus');\n });\n });\n};\n\n/**\n * Changes the focus to the correct tab based on the key that is pressed.\n * @param {KeyboardEvent} e\n */\nconst updateTabFocus = e => {\n const tabList = e.target.closest('[role=\"tablist\"]');\n const vertical = tabList.getAttribute('aria-orientation') == 'vertical';\n const rtl = window.right_to_left();\n const arrowNext = vertical ? 'ArrowDown' : (rtl ? 'ArrowLeft' : 'ArrowRight');\n const arrowPrevious = vertical ? 'ArrowUp' : (rtl ? 'ArrowRight' : 'ArrowLeft');\n const tabs = Array.prototype.filter.call(\n tabList.querySelectorAll('[role=\"tab\"]'),\n tab => getComputedStyle(tab).display !== 'none'); // We only work with the visible tabs.\n\n for (let i = 0; i < tabs.length; i++) {\n tabs[i].index = i;\n }\n\n switch (e.key) {\n case arrowNext:\n e.preventDefault();\n if (e.target.index !== undefined && tabs[e.target.index + 1]) {\n tabs[e.target.index + 1].focus();\n } else {\n tabs[0].focus();\n }\n break;\n case arrowPrevious:\n e.preventDefault();\n if (e.target.index !== undefined && tabs[e.target.index - 1]) {\n tabs[e.target.index - 1].focus();\n } else {\n tabs[tabs.length - 1].focus();\n }\n break;\n case 'Home':\n e.preventDefault();\n tabs[0].focus();\n break;\n case 'End':\n e.preventDefault();\n tabs[tabs.length - 1].focus();\n break;\n case 'Enter':\n case ' ':\n e.preventDefault();\n $(e.target).tab('show');\n tabs.forEach(tab => {\n tab.tabIndex = -1;\n });\n e.target.tabIndex = 0;\n }\n};\n\n/**\n * Fix accessibility issues regarding tab elements focus and their tab order in Bootstrap navs.\n */\nconst tabElementFix = () => {\n document.addEventListener('keydown', e => {\n if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Home', 'End', 'Enter', ' '].includes(e.key)) {\n if (e.target.matches('[role=\"tablist\"] [role=\"tab\"]')) {\n updateTabFocus(e);\n }\n }\n });\n\n document.addEventListener('click', e => {\n if (e.target.matches('[role=\"tablist\"] [role=\"tab\"]')) {\n const tabs = e.target.closest('[role=\"tablist\"]').querySelectorAll('[role=\"tab\"]');\n e.preventDefault();\n $(e.target).tab('show');\n tabs.forEach(tab => {\n tab.tabIndex = -1;\n });\n e.target.tabIndex = 0;\n }\n });\n};\n\nexport const init = () => {\n dropdownFix();\n autoFocus();\n tabElementFix();\n};\n"],"names":["dropdownFix","focusEnd","document","addEventListener","e","target","matches","trigger","key","preventDefault","click","shiftFocus","element","setTimeout","pendingPromise","focus","resolve","Pending","on","menu","querySelector","menuItems","foundMenuItem","querySelectorAll","length","result","getFocusEnd","closest","toLowerCase","i","item","text","trim","indexOf","next","tabElementFix","includes","tabList","vertical","getAttribute","rtl","window","right_to_left","arrowNext","arrowPrevious","tabs","Array","prototype","filter","call","tab","getComputedStyle","display","index","undefined","forEach","tabIndex","updateTabFocus","alerts","autofocusElement","innerHTML","removeAttribute"],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"aria.min.js","sources":["../src/aria.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Enhancements to Bootstrap components for accessibility.\n *\n * @module theme_boost/aria\n * @copyright 2018 Damyon Wiese \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport Pending from 'core/pending';\n\n/**\n * Drop downs from bootstrap don't support keyboard accessibility by default.\n */\nconst dropdownFix = () => {\n let focusEnd = false;\n let focusBackToTrigger = true;\n const setFocusEnd = (end = true) => {\n focusEnd = end;\n };\n const getFocusEnd = () => {\n const result = focusEnd;\n focusEnd = false;\n return result;\n };\n\n // Special handling for navigation keys when menu is open.\n const shiftFocus = element => {\n const delayedFocus = pendingPromise => {\n element.focus();\n pendingPromise.resolve();\n };\n setTimeout(delayedFocus, 50, new Pending('core/aria:delayed-focus'));\n };\n\n // Event handling for the dropdown menu button.\n const handleMenuButton = e => {\n const trigger = e.key;\n let fixFocus = false;\n\n // Space key or Enter key opens the menu.\n if (trigger === ' ' || trigger === 'Enter') {\n fixFocus = true;\n // Cancel random scroll.\n e.preventDefault();\n // Open the menu instead.\n e.target.click();\n }\n\n // Up and Down keys also open the menu.\n if (trigger === 'ArrowUp' || trigger === 'ArrowDown') {\n fixFocus = true;\n }\n\n // Pressing tab on the menu button should focus on the next element in the DOM and not back to the menu trigger.\n if (trigger === 'Tab') {\n focusBackToTrigger = false;\n }\n\n if (!fixFocus) {\n // No need to fix the focus. Return early.\n return;\n }\n\n // Fix the focus on the menu items when the menu is opened.\n const menu = e.target.parentElement.querySelector('[role=\"menu\"]');\n let menuItems = false;\n let foundMenuItem = false;\n\n if (menu) {\n menuItems = menu.querySelectorAll('[role=\"menuitem\"]');\n }\n if (menuItems && menuItems.length > 0) {\n // Up key opens the menu at the end.\n if (trigger === 'ArrowUp') {\n setFocusEnd();\n } else {\n setFocusEnd(false);\n }\n\n if (getFocusEnd()) {\n foundMenuItem = menuItems[menuItems.length - 1];\n } else {\n // The first menu entry, pretty reasonable.\n foundMenuItem = menuItems[0];\n }\n }\n\n if (foundMenuItem) {\n shiftFocus(foundMenuItem);\n }\n };\n\n // Search for menu items by finding the first item that has\n // text starting with the typed character (case insensitive).\n document.addEventListener('keypress', e => {\n if (e.target.matches('.dropdown [role=\"menu\"] [role=\"menuitem\"]')) {\n const menu = e.target.closest('[role=\"menu\"]');\n if (!menu) {\n return;\n }\n const menuItems = menu.querySelectorAll('[role=\"menuitem\"]');\n if (!menuItems) {\n return;\n }\n\n const trigger = e.key.toLowerCase();\n\n for (let i = 0; i < menuItems.length; i++) {\n const item = menuItems[i];\n const itemText = item.text.trim().toLowerCase();\n if (itemText.indexOf(trigger) == 0) {\n shiftFocus(item);\n break;\n }\n }\n }\n });\n\n // Keyboard navigation for arrow keys, home and end keys.\n document.addEventListener('keydown', e => {\n\n // We only want to set focus when users access the dropdown via keyboard as per\n // guidelines defined in w3 aria practices 1.1 menu-button.\n if (e.target.matches('[data-toggle=\"dropdown\"]')) {\n handleMenuButton(e);\n }\n\n if (e.target.matches('.dropdown [role=\"menu\"] [role=\"menuitem\"]')) {\n const trigger = e.key;\n let next = false;\n const menu = e.target.closest('[role=\"menu\"]');\n\n if (!menu) {\n return;\n }\n const menuItems = menu.querySelectorAll('[role=\"menuitem\"]');\n if (!menuItems) {\n return;\n }\n // Down key.\n if (trigger == 'ArrowDown') {\n for (let i = 0; i < menuItems.length - 1; i++) {\n if (menuItems[i] == e.target) {\n next = menuItems[i + 1];\n break;\n }\n }\n if (!next) {\n // Wrap to first item.\n next = menuItems[0];\n }\n\n } else if (trigger == 'ArrowUp') {\n // Up key.\n for (let i = 1; i < menuItems.length; i++) {\n if (menuItems[i] == e.target) {\n next = menuItems[i - 1];\n break;\n }\n }\n if (!next) {\n // Wrap to last item.\n next = menuItems[menuItems.length - 1];\n }\n\n } else if (trigger == 'Home') {\n // Home key.\n next = menuItems[0];\n\n } else if (trigger == 'End') {\n // End key.\n next = menuItems[menuItems.length - 1];\n } else if (trigger == 'Tab') {\n // Pressing tab in the menu should focus on the next element in the DOM and not back to the menu trigger.\n focusBackToTrigger = false;\n }\n\n // Variable next is set if we do want to act on the keypress.\n if (next) {\n e.preventDefault();\n shiftFocus(next);\n }\n return;\n }\n });\n\n $('.dropdown').on('hidden.bs.dropdown', e => {\n // We need to focus on the menu trigger.\n const trigger = e.target.querySelector('[data-toggle=\"dropdown\"]');\n if (trigger && focusBackToTrigger) {\n shiftFocus(trigger);\n }\n // Reset flag to focus back to the menu trigger.\n focusBackToTrigger = true;\n });\n};\n\n/**\n * After page load, focus on any element with special autofocus attribute.\n */\nconst autoFocus = () => {\n window.addEventListener(\"load\", () => {\n const alerts = document.querySelectorAll('[data-aria-autofocus=\"true\"][role=\"alert\"]');\n Array.prototype.forEach.call(alerts, autofocusElement => {\n // According to the specification an role=\"alert\" region is only read out on change to the content\n // of that region.\n autofocusElement.innerHTML += ' ';\n autofocusElement.removeAttribute('data-aria-autofocus');\n });\n });\n};\n\n/**\n * Changes the focus to the correct tab based on the key that is pressed.\n * @param {KeyboardEvent} e\n */\nconst updateTabFocus = e => {\n const tabList = e.target.closest('[role=\"tablist\"]');\n const vertical = tabList.getAttribute('aria-orientation') == 'vertical';\n const rtl = window.right_to_left();\n const arrowNext = vertical ? 'ArrowDown' : (rtl ? 'ArrowLeft' : 'ArrowRight');\n const arrowPrevious = vertical ? 'ArrowUp' : (rtl ? 'ArrowRight' : 'ArrowLeft');\n const tabs = Array.prototype.filter.call(\n tabList.querySelectorAll('[role=\"tab\"]'),\n tab => getComputedStyle(tab).display !== 'none'); // We only work with the visible tabs.\n\n for (let i = 0; i < tabs.length; i++) {\n tabs[i].index = i;\n }\n\n switch (e.key) {\n case arrowNext:\n e.preventDefault();\n if (e.target.index !== undefined && tabs[e.target.index + 1]) {\n tabs[e.target.index + 1].focus();\n } else {\n tabs[0].focus();\n }\n break;\n case arrowPrevious:\n e.preventDefault();\n if (e.target.index !== undefined && tabs[e.target.index - 1]) {\n tabs[e.target.index - 1].focus();\n } else {\n tabs[tabs.length - 1].focus();\n }\n break;\n case 'Home':\n e.preventDefault();\n tabs[0].focus();\n break;\n case 'End':\n e.preventDefault();\n tabs[tabs.length - 1].focus();\n break;\n case 'Enter':\n case ' ':\n e.preventDefault();\n $(e.target).tab('show');\n tabs.forEach(tab => {\n tab.tabIndex = -1;\n });\n e.target.tabIndex = 0;\n }\n};\n\n/**\n * Fix accessibility issues regarding tab elements focus and their tab order in Bootstrap navs.\n */\nconst tabElementFix = () => {\n document.addEventListener('keydown', e => {\n if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Home', 'End', 'Enter', ' '].includes(e.key)) {\n if (e.target.matches('[role=\"tablist\"] [role=\"tab\"]')) {\n updateTabFocus(e);\n }\n }\n });\n\n document.addEventListener('click', e => {\n if (e.target.matches('[role=\"tablist\"] [role=\"tab\"]')) {\n const tabs = e.target.closest('[role=\"tablist\"]').querySelectorAll('[role=\"tab\"]');\n e.preventDefault();\n $(e.target).tab('show');\n tabs.forEach(tab => {\n tab.tabIndex = -1;\n });\n e.target.tabIndex = 0;\n }\n });\n};\n\nexport const init = () => {\n dropdownFix();\n autoFocus();\n tabElementFix();\n};\n"],"names":["dropdownFix","focusEnd","focusBackToTrigger","setFocusEnd","end","shiftFocus","element","setTimeout","pendingPromise","focus","resolve","Pending","handleMenuButton","e","trigger","key","fixFocus","preventDefault","target","click","menu","parentElement","querySelector","menuItems","foundMenuItem","querySelectorAll","length","result","getFocusEnd","document","addEventListener","matches","closest","toLowerCase","i","item","text","trim","indexOf","next","on","tabElementFix","includes","tabList","vertical","getAttribute","rtl","window","right_to_left","arrowNext","arrowPrevious","tabs","Array","prototype","filter","call","tab","getComputedStyle","display","index","undefined","forEach","tabIndex","updateTabFocus","alerts","autofocusElement","innerHTML","removeAttribute"],"mappings":"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"}
\ No newline at end of file
diff --git a/theme/boost/amd/src/aria.js b/theme/boost/amd/src/aria.js
index 1acf8673aef..1f8724d8315 100644
--- a/theme/boost/amd/src/aria.js
+++ b/theme/boost/amd/src/aria.js
@@ -29,8 +29,9 @@ import Pending from 'core/pending';
*/
const dropdownFix = () => {
let focusEnd = false;
- const setFocusEnd = () => {
- focusEnd = true;
+ let focusBackToTrigger = true;
+ const setFocusEnd = (end = true) => {
+ focusEnd = end;
};
const getFocusEnd = () => {
const result = focusEnd;
@@ -38,27 +39,6 @@ const dropdownFix = () => {
return result;
};
- // Special handling for "up" keyboard control.
- document.addEventListener('keydown', e => {
- if (e.target.matches('[data-toggle="dropdown"]')) {
- const trigger = e.key;
-
- // Up key opens the menu at the end.
- if (trigger == 'ArrowUp') {
- // Focus the end of the menu, not the beginning.
- setFocusEnd();
- }
-
- // Space key or Enter key opens the menu.
- if (trigger == ' ' || trigger == 'Enter') {
- // Cancel random scroll.
- e.preventDefault();
- // Open the menu instead.
- e.target.click();
- }
- }
- });
-
// Special handling for navigation keys when menu is open.
const shiftFocus = element => {
const delayedFocus = pendingPromise => {
@@ -68,9 +48,37 @@ const dropdownFix = () => {
setTimeout(delayedFocus, 50, new Pending('core/aria:delayed-focus'));
};
- $('.dropdown').on('shown.bs.dropdown', e => {
- // We need to focus on the first menuitem.
- const menu = e.target.querySelector('[role="menu"]');
+ // Event handling for the dropdown menu button.
+ const handleMenuButton = e => {
+ const trigger = e.key;
+ let fixFocus = false;
+
+ // Space key or Enter key opens the menu.
+ if (trigger === ' ' || trigger === 'Enter') {
+ fixFocus = true;
+ // Cancel random scroll.
+ e.preventDefault();
+ // Open the menu instead.
+ e.target.click();
+ }
+
+ // Up and Down keys also open the menu.
+ if (trigger === 'ArrowUp' || trigger === 'ArrowDown') {
+ fixFocus = true;
+ }
+
+ // Pressing tab on the menu button should focus on the next element in the DOM and not back to the menu trigger.
+ if (trigger === 'Tab') {
+ focusBackToTrigger = false;
+ }
+
+ if (!fixFocus) {
+ // No need to fix the focus. Return early.
+ return;
+ }
+
+ // Fix the focus on the menu items when the menu is opened.
+ const menu = e.target.parentElement.querySelector('[role="menu"]');
let menuItems = false;
let foundMenuItem = false;
@@ -78,6 +86,13 @@ const dropdownFix = () => {
menuItems = menu.querySelectorAll('[role="menuitem"]');
}
if (menuItems && menuItems.length > 0) {
+ // Up key opens the menu at the end.
+ if (trigger === 'ArrowUp') {
+ setFocusEnd();
+ } else {
+ setFocusEnd(false);
+ }
+
if (getFocusEnd()) {
foundMenuItem = menuItems[menuItems.length - 1];
} else {
@@ -85,10 +100,12 @@ const dropdownFix = () => {
foundMenuItem = menuItems[0];
}
}
+
if (foundMenuItem) {
shiftFocus(foundMenuItem);
}
- });
+ };
+
// Search for menu items by finding the first item that has
// text starting with the typed character (case insensitive).
document.addEventListener('keypress', e => {
@@ -117,6 +134,13 @@ const dropdownFix = () => {
// Keyboard navigation for arrow keys, home and end keys.
document.addEventListener('keydown', e => {
+
+ // We only want to set focus when users access the dropdown via keyboard as per
+ // guidelines defined in w3 aria practices 1.1 menu-button.
+ if (e.target.matches('[data-toggle="dropdown"]')) {
+ handleMenuButton(e);
+ }
+
if (e.target.matches('.dropdown [role="menu"] [role="menuitem"]')) {
const trigger = e.key;
let next = false;
@@ -162,7 +186,11 @@ const dropdownFix = () => {
} else if (trigger == 'End') {
// End key.
next = menuItems[menuItems.length - 1];
+ } else if (trigger == 'Tab') {
+ // Pressing tab in the menu should focus on the next element in the DOM and not back to the menu trigger.
+ focusBackToTrigger = false;
}
+
// Variable next is set if we do want to act on the keypress.
if (next) {
e.preventDefault();
@@ -175,9 +203,11 @@ const dropdownFix = () => {
$('.dropdown').on('hidden.bs.dropdown', e => {
// We need to focus on the menu trigger.
const trigger = e.target.querySelector('[data-toggle="dropdown"]');
- if (trigger) {
+ if (trigger && focusBackToTrigger) {
shiftFocus(trigger);
}
+ // Reset flag to focus back to the menu trigger.
+ focusBackToTrigger = true;
});
};
diff --git a/theme/boost/scss/moodle/moremenu.scss b/theme/boost/scss/moodle/moremenu.scss
index b52aef471b4..01511b93060 100644
--- a/theme/boost/scss/moodle/moremenu.scss
+++ b/theme/boost/scss/moodle/moremenu.scss
@@ -91,7 +91,8 @@
.dropdown-item.active {
background-color: transparent;
color: $dropdown-link-color;
- &:focus-within {
+ &:focus-within,
+ &:hover {
background-color: $dropdown-link-active-bg;
color: $dropdown-link-active-color;
a {
diff --git a/theme/boost/style/moodle.css b/theme/boost/style/moodle.css
index ca19dcc6d8c..b9ffcd2abda 100644
--- a/theme/boost/style/moodle.css
+++ b/theme/boost/style/moodle.css
@@ -21515,12 +21515,14 @@ div.editor_atto_toolbar button .icon {
.moremenu .dropdown-item.active {
background-color: transparent;
color: #1d2125; }
- .moremenu .dropdown-item[aria-current="true"]:focus-within,
- .moremenu .dropdown-item.active:focus-within {
+ .moremenu .dropdown-item[aria-current="true"]:focus-within, .moremenu .dropdown-item[aria-current="true"]:hover,
+ .moremenu .dropdown-item.active:focus-within,
+ .moremenu .dropdown-item.active:hover {
background-color: #0f6cbf;
color: #fff; }
- .moremenu .dropdown-item[aria-current="true"]:focus-within a,
- .moremenu .dropdown-item.active:focus-within a {
+ .moremenu .dropdown-item[aria-current="true"]:focus-within a, .moremenu .dropdown-item[aria-current="true"]:hover a,
+ .moremenu .dropdown-item.active:focus-within a,
+ .moremenu .dropdown-item.active:hover a {
color: #fff; }
.moremenu .dropdown-item[aria-current="true"]:before,
.moremenu .dropdown-item.active:before {
diff --git a/theme/classic/style/moodle.css b/theme/classic/style/moodle.css
index b88d5da67be..0b074281110 100644
--- a/theme/classic/style/moodle.css
+++ b/theme/classic/style/moodle.css
@@ -21461,12 +21461,14 @@ div.editor_atto_toolbar button .icon {
.moremenu .dropdown-item.active {
background-color: transparent;
color: #1d2125; }
- .moremenu .dropdown-item[aria-current="true"]:focus-within,
- .moremenu .dropdown-item.active:focus-within {
+ .moremenu .dropdown-item[aria-current="true"]:focus-within, .moremenu .dropdown-item[aria-current="true"]:hover,
+ .moremenu .dropdown-item.active:focus-within,
+ .moremenu .dropdown-item.active:hover {
background-color: #0f6cbf;
color: #fff; }
- .moremenu .dropdown-item[aria-current="true"]:focus-within a,
- .moremenu .dropdown-item.active:focus-within a {
+ .moremenu .dropdown-item[aria-current="true"]:focus-within a, .moremenu .dropdown-item[aria-current="true"]:hover a,
+ .moremenu .dropdown-item.active:focus-within a,
+ .moremenu .dropdown-item.active:hover a {
color: #fff; }
.moremenu .dropdown-item[aria-current="true"]:before,
.moremenu .dropdown-item.active:before {