diff --git a/admin/search.php b/admin/search.php index 1862f3928db..648822e60f1 100644 --- a/admin/search.php +++ b/admin/search.php @@ -11,7 +11,6 @@ $query = trim(optional_param('query', '', PARAM_NOTAGS)); // Search string $context = context_system::instance(); $PAGE->set_context($context); -$PAGE->has_secondary_navigation(); $hassiteconfig = has_capability('moodle/site:config', $context); @@ -91,7 +90,7 @@ if ($hassiteconfig) { if ($showsettingslinks) { $node = $PAGE->settingsnav->find('root', navigation_node::TYPE_SITE_ADMIN); if ($node) { - $moremenu = new \core\navigation\output\more_menu($PAGE->secondarynav, 'nav-tabs', true); + $moremenu = new \core\navigation\output\more_menu($PAGE->secondarynav, 'nav-tabs'); $secondarynavigation = $moremenu->export_for_template($OUTPUT); echo $OUTPUT->render_from_template('core/settings_link_page', ['node' => $node, 'secondarynavigation' => $secondarynavigation]); diff --git a/admin/tool/behat/tests/behat/keyboard.feature b/admin/tool/behat/tests/behat/keyboard.feature index ca9a74f02f3..ca2912ce791 100644 --- a/admin/tool/behat/tests/behat/keyboard.feature +++ b/admin/tool/behat/tests/behat/keyboard.feature @@ -34,7 +34,7 @@ Feature: Verify that keyboard steps work as expected | username | email | firstname | lastname | | saffronr | saffron.rutledge@example.com | Saffron | Rutledge | And I log in as "saffronr" - And I click on "Saffron Rutledge" "link" in the ".usermenu" "css_element" + And I click on "Saffron Rutledge" "button" in the ".usermenu" "css_element" When I press the up key Then the focused element is "Log out" "link" diff --git a/lib/amd/build/keyboard_navigation.min.js b/lib/amd/build/keyboard_navigation.min.js deleted file mode 100644 index ab2be201c68..00000000000 --- a/lib/amd/build/keyboard_navigation.min.js +++ /dev/null @@ -1,2 +0,0 @@ -define ("core/keyboard_navigation",["exports","core/key_codes"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;var c={menuitem:"[role=\"menuitem\"]",menu:"[role=\"menu\"]"},d=null,e=function(a,b){if(null!==a){return a}else{return b}},f=function(a){var e=a.srcElement,f=a.currentTarget.firstElementChild,k=j(a.currentTarget);if(e.classList.contains("dropdown-item")){if(a.keyCode===b.arrowRight||a.keyCode===b.arrowLeft){a.preventDefault();if(null!==d){d.parentElement.click()}}if(a.keyCode===b.space||a.keyCode===b.enter){a.preventDefault();Array.prototype.forEach.call(e.closest(".dropdown-menu").children,function(a){a.querySelector(c.menuitem).classList.remove("active")});if(!e.parentElement.classList.contains("dropdown")){e.click()}}}else{if(a.keyCode===b.arrowRight){a.preventDefault();g(e,f)}if(a.keyCode===b.arrowLeft){a.preventDefault();h(e,k)}if(a.keyCode===b.arrowUp||a.keyCode===b.arrowDown){d=e;a.preventDefault()}if(a.keyCode===b.home){a.preventDefault();i(f)}if(a.keyCode===b.end){a.preventDefault();i(k)}if(a.keyCode===b.space||a.keyCode===b.enter){a.preventDefault();if(!e.parentElement.classList.contains("dropdown")){e.click()}}}};a.default=function(a){a.removeEventListener("keydown",f);a.addEventListener("keydown",f)};var g=function(a,b){var d=a.parentElement.nextElementSibling,f=e(d,b),g=f.querySelector(c.menuitem);g.focus()},h=function(a,b){var d=a.parentElement.previousElementSibling,f=e(d,b),g=f.querySelector(c.menuitem);g.focus()},i=function(a){a.querySelector(c.menuitem).focus()},j=function(a){var b=a.lastElementChild;if(!b.classList.contains("d-none")){return a.lastElementChild}else{var c=Array.prototype.map.call(a.children,function(a){return a}).reverse(),d=c.filter(function(a){if(!a.classList.contains("d-none")){return a}});if(0!==d.length){return d[0]}else{return a.firstElementChild}}};return a.default}); -//# sourceMappingURL=keyboard_navigation.min.js.map diff --git a/lib/amd/build/keyboard_navigation.min.js.map b/lib/amd/build/keyboard_navigation.min.js.map deleted file mode 100644 index 6e90f833675..00000000000 --- a/lib/amd/build/keyboard_navigation.min.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["../src/keyboard_navigation.js"],"names":["SELECTORS","openDropdownNode","clickErrorHandler","item","fallback","listenerEvents","e","src","srcElement","firstNode","currentTarget","firstElementChild","lastNode","findUsableLastNode","classList","contains","keyCode","arrowRight","arrowLeft","preventDefault","parentElement","click","space","enter","Array","prototype","forEach","call","closest","children","node","querySelector","menuitem","remove","setFocusNext","setFocusPrev","arrowUp","arrowDown","home","setFocusHomeEnd","end","elementRoot","removeEventListener","addEventListener","currentNode","nextListItem","nextElementSibling","nodeToSelect","menuItem","focus","previousElementSibling","lastElementChild","extractedNodes","map","reverse","nodesToUse","filter","length"],"mappings":"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","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 * Keyboard initialization for a given html node.\n *\n * @module core/keyboard_navigation\n * @package core\n * @copyright 2021 Moodle\n * @author Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {space, enter, arrowRight, arrowLeft, arrowDown, arrowUp, home, end} from 'core/key_codes';\n\nconst SELECTORS = {\n 'menuitem': '[role=\"menuitem\"]',\n 'menu': '[role=\"menu\"]'\n};\n\nlet openDropdownNode = null;\n\n/**\n * Small helper function to check if a given node is null or not.\n *\n * @param {HTMLElement|null} item The node that we want to compare.\n * @param {HTMLElement} fallback Either the first node or final node that can be focused on.\n * @return {HTMLElement}\n */\nconst clickErrorHandler = (item, fallback) => {\n if (item !== null) {\n return item;\n } else {\n return fallback;\n }\n};\n\n/**\n * Defined event handling so we can remove listeners on nodes on resize etc.\n *\n * @param {event} e The triggering element and key presses etc.\n */\nconst listenerEvents = e => {\n const src = e.srcElement;\n const firstNode = e.currentTarget.firstElementChild;\n const lastNode = findUsableLastNode(e.currentTarget);\n\n // Handling for dropdown escapes.\n // A bulk of the handling is already done by aria.js just add polish.\n if (src.classList.contains('dropdown-item')) {\n if (e.keyCode === arrowRight ||\n e.keyCode === arrowLeft) {\n e.preventDefault();\n if (openDropdownNode !== null) {\n openDropdownNode.parentElement.click();\n }\n }\n if (e.keyCode === space ||\n e.keyCode === enter) {\n e.preventDefault();\n\n // Remove active class from any other dropdown elements.\n Array.prototype.forEach.call(src.closest('.dropdown-menu').children, node => {\n node.querySelector(SELECTORS.menuitem).classList.remove('active');\n });\n\n if (!src.parentElement.classList.contains('dropdown')) {\n src.click();\n }\n }\n } else {\n if (e.keyCode === arrowRight) {\n e.preventDefault();\n setFocusNext(src, firstNode);\n }\n if (e.keyCode === arrowLeft) {\n e.preventDefault();\n setFocusPrev(src, lastNode);\n }\n // Let aria.js handle the dropdowns.\n if (e.keyCode === arrowUp ||\n e.keyCode === arrowDown) {\n openDropdownNode = src;\n e.preventDefault();\n }\n if (e.keyCode === home) {\n e.preventDefault();\n setFocusHomeEnd(firstNode);\n }\n if (e.keyCode === end) {\n e.preventDefault();\n setFocusHomeEnd(lastNode);\n }\n if (e.keyCode === space ||\n e.keyCode === enter) {\n e.preventDefault();\n // Aria.js handles dropdowns etc.\n if (!src.parentElement.classList.contains('dropdown')) {\n src.click();\n }\n }\n }\n};\n\n/**\n * The initial entry point that a given module can pass a HTMLElement.\n *\n * @param {HTMLElement} elementRoot The menu to add handlers upon.\n */\nexport default elementRoot => {\n elementRoot.removeEventListener('keydown', listenerEvents);\n elementRoot.addEventListener('keydown', listenerEvents);\n};\n\n/**\n * Handle the focusing to the next element in the dropdown.\n *\n * @param {HTMLElement|null} currentNode The node that we want to take action on.\n * @param {HTMLElement} firstNode The backup node to focus as a last resort.\n */\nconst setFocusNext = (currentNode, firstNode) => {\n const nextListItem = currentNode.parentElement.nextElementSibling;\n const nodeToSelect = clickErrorHandler(nextListItem, firstNode);\n const menuItem = nodeToSelect.querySelector(SELECTORS.menuitem);\n menuItem.focus();\n};\n\n/**\n * Handle the focusing to the previous element in the dropdown.\n *\n * @param {HTMLElement|null} currentNode The node that we want to take action on.\n * @param {HTMLElement} lastNode The backup node to focus as a last resort.\n */\nconst setFocusPrev = (currentNode, lastNode) => {\n const nextListItem = currentNode.parentElement.previousElementSibling;\n const nodeToSelect = clickErrorHandler(nextListItem, lastNode);\n const menuItem = nodeToSelect.querySelector(SELECTORS.menuitem);\n menuItem.focus();\n};\n\n/**\n * Focus on either the start or end of a nav list.\n *\n * @param {HTMLElement} node The element to focus on.\n */\nconst setFocusHomeEnd = node => {\n node.querySelector(SELECTORS.menuitem).focus();\n};\n\n/**\n * We need to look within the menu to find a last node we can add focus to.\n *\n * @param {HTMLElement} elementRoot Menu to find a final child node within.\n * @return {HTMLElement}\n */\nconst findUsableLastNode = elementRoot => {\n const lastNode = elementRoot.lastElementChild;\n\n // An example is the more menu existing but hidden on the page for the time being.\n if (!lastNode.classList.contains('d-none')) {\n return elementRoot.lastElementChild;\n } else {\n // Cast the HTMLCollection & reverse it.\n const extractedNodes = Array.prototype.map.call(elementRoot.children, node => {\n return node;\n }).reverse();\n\n // Get rid of any nodes we can not set focus on.\n const nodesToUse = extractedNodes.filter((node => {\n if (!node.classList.contains('d-none')) {\n return node;\n }\n }));\n\n // If we find no elements we can set focus on, fall back to the absolute first element.\n if (nodesToUse.length !== 0) {\n return nodesToUse[0];\n } else {\n return elementRoot.firstElementChild;\n }\n }\n};\n"],"file":"keyboard_navigation.min.js"} \ No newline at end of file diff --git a/lib/amd/build/menu_navigation.min.js b/lib/amd/build/menu_navigation.min.js new file mode 100644 index 00000000000..a6cb660bcf6 --- /dev/null +++ b/lib/amd/build/menu_navigation.min.js @@ -0,0 +1,2 @@ +define ("core/menu_navigation",["exports","core/key_codes"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;var c={menuitem:"[role=\"menuitem\"]",menu:"[role=\"menu\"]"},d=null,e=function(a,b){if(null!==a){return a}else{return b}},f=function(a){var e=a.srcElement,f=a.currentTarget.firstElementChild,k=j(a.currentTarget);if(e.classList.contains("dropdown-item")){if(a.keyCode===b.arrowRight||a.keyCode===b.arrowLeft){a.preventDefault();if(null!==d){d.parentElement.click()}}if(a.keyCode===b.space||a.keyCode===b.enter){a.preventDefault();Array.prototype.forEach.call(e.closest(".dropdown-menu").children,function(a){a.querySelector(c.menuitem).classList.remove("active");a.setAttribute("aria-current","false")});if(!e.parentElement.classList.contains("dropdown")){e.click()}}}else{if(a.keyCode===b.arrowRight){a.preventDefault();g(e,f)}if(a.keyCode===b.arrowLeft){a.preventDefault();h(e,k)}if(a.keyCode===b.arrowUp||a.keyCode===b.arrowDown){d=e;a.preventDefault()}if(a.keyCode===b.home){a.preventDefault();e.setAttribute("aria-current","false");i(f)}if(a.keyCode===b.end){a.preventDefault();e.currentNode.setAttribute("aria-current","false");i(k)}if(a.keyCode===b.space||a.keyCode===b.enter){a.preventDefault();if(!e.parentElement.classList.contains("dropdown")){e.click()}}}};a.default=function(a){a.removeEventListener("keydown",f);a.addEventListener("keydown",f)};var g=function(a,b){var d=a.parentElement.nextElementSibling,f=e(d,b),g=f.querySelector(c.menuitem);a.setAttribute("aria-current","false");g.setAttribute("aria-current","true");g.focus()},h=function(a,b){var d=a.parentElement.previousElementSibling,f=e(d,b),g=f.querySelector(c.menuitem);a.setAttribute("aria-current","false");g.setAttribute("aria-current","true");g.focus()},i=function(a){a.querySelector(c.menuitem).focus();a.querySelector(c.menuitem).setAttribute("aria-current","true")},j=function(a){var b=a.lastElementChild;if(!b.classList.contains("d-none")){return a.lastElementChild}else{var c=Array.prototype.map.call(a.children,function(a){return a}).reverse(),d=c.filter(function(a){if(!a.classList.contains("d-none")){return a}});if(0!==d.length){return d[0]}else{return a.firstElementChild}}};return a.default}); +//# sourceMappingURL=menu_navigation.min.js.map diff --git a/lib/amd/build/menu_navigation.min.js.map b/lib/amd/build/menu_navigation.min.js.map new file mode 100644 index 00000000000..2fc7010a405 --- /dev/null +++ b/lib/amd/build/menu_navigation.min.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../src/menu_navigation.js"],"names":["SELECTORS","openDropdownNode","clickErrorHandler","item","fallback","listenerEvents","e","src","srcElement","firstNode","currentTarget","firstElementChild","lastNode","findUsableLastNode","classList","contains","keyCode","arrowRight","arrowLeft","preventDefault","parentElement","click","space","enter","Array","prototype","forEach","call","closest","children","node","querySelector","menuitem","remove","setAttribute","setFocusNext","setFocusPrev","arrowUp","arrowDown","home","setFocusHomeEnd","end","currentNode","elementRoot","removeEventListener","addEventListener","nextListItem","nextElementSibling","nodeToSelect","menuItem","focus","previousElementSibling","lastElementChild","extractedNodes","map","reverse","nodesToUse","filter","length"],"mappings":"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","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 * Keyboard initialization for a given html node.\n *\n * @module core/keyboard_navigation\n * @copyright 2021 Moodle\n * @author Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {space, enter, arrowRight, arrowLeft, arrowDown, arrowUp, home, end} from 'core/key_codes';\n\nconst SELECTORS = {\n 'menuitem': '[role=\"menuitem\"]',\n 'menu': '[role=\"menu\"]'\n};\n\nlet openDropdownNode = null;\n\n/**\n * Small helper function to check if a given node is null or not.\n *\n * @param {HTMLElement|null} item The node that we want to compare.\n * @param {HTMLElement} fallback Either the first node or final node that can be focused on.\n * @return {HTMLElement}\n */\nconst clickErrorHandler = (item, fallback) => {\n if (item !== null) {\n return item;\n } else {\n return fallback;\n }\n};\n\n/**\n * Defined event handling so we can remove listeners on nodes on resize etc.\n *\n * @param {event} e The triggering element and key presses etc.\n */\nconst listenerEvents = e => {\n const src = e.srcElement;\n const firstNode = e.currentTarget.firstElementChild;\n const lastNode = findUsableLastNode(e.currentTarget);\n\n // Handling for dropdown escapes.\n // A bulk of the handling is already done by aria.js just add polish.\n if (src.classList.contains('dropdown-item')) {\n if (e.keyCode === arrowRight ||\n e.keyCode === arrowLeft) {\n e.preventDefault();\n if (openDropdownNode !== null) {\n openDropdownNode.parentElement.click();\n }\n }\n if (e.keyCode === space ||\n e.keyCode === enter) {\n e.preventDefault();\n\n // Remove active class from any other dropdown elements.\n Array.prototype.forEach.call(src.closest('.dropdown-menu').children, node => {\n node.querySelector(SELECTORS.menuitem).classList.remove('active');\n node.setAttribute('aria-current', 'false');\n });\n\n if (!src.parentElement.classList.contains('dropdown')) {\n src.click();\n }\n }\n } else {\n if (e.keyCode === arrowRight) {\n e.preventDefault();\n setFocusNext(src, firstNode);\n }\n if (e.keyCode === arrowLeft) {\n e.preventDefault();\n setFocusPrev(src, lastNode);\n }\n // Let aria.js handle the dropdowns.\n if (e.keyCode === arrowUp ||\n e.keyCode === arrowDown) {\n openDropdownNode = src;\n e.preventDefault();\n }\n if (e.keyCode === home) {\n e.preventDefault();\n src.setAttribute('aria-current', 'false');\n setFocusHomeEnd(firstNode);\n }\n if (e.keyCode === end) {\n e.preventDefault();\n src.currentNode.setAttribute('aria-current', 'false');\n setFocusHomeEnd(lastNode);\n }\n if (e.keyCode === space ||\n e.keyCode === enter) {\n e.preventDefault();\n // Aria.js handles dropdowns etc.\n if (!src.parentElement.classList.contains('dropdown')) {\n src.click();\n }\n }\n }\n};\n\n/**\n * The initial entry point that a given module can pass a HTMLElement.\n *\n * @param {HTMLElement} elementRoot The menu to add handlers upon.\n */\nexport default elementRoot => {\n elementRoot.removeEventListener('keydown', listenerEvents);\n elementRoot.addEventListener('keydown', listenerEvents);\n};\n\n/**\n * Handle the focusing to the next element in the dropdown.\n *\n * @param {HTMLElement|null} currentNode The node that we want to take action on.\n * @param {HTMLElement} firstNode The backup node to focus as a last resort.\n */\nconst setFocusNext = (currentNode, firstNode) => {\n const nextListItem = currentNode.parentElement.nextElementSibling;\n const nodeToSelect = clickErrorHandler(nextListItem, firstNode);\n const menuItem = nodeToSelect.querySelector(SELECTORS.menuitem);\n currentNode.setAttribute('aria-current', 'false');\n menuItem.setAttribute('aria-current', 'true');\n menuItem.focus();\n};\n\n/**\n * Handle the focusing to the previous element in the dropdown.\n *\n * @param {HTMLElement|null} currentNode The node that we want to take action on.\n * @param {HTMLElement} lastNode The backup node to focus as a last resort.\n */\nconst setFocusPrev = (currentNode, lastNode) => {\n const nextListItem = currentNode.parentElement.previousElementSibling;\n const nodeToSelect = clickErrorHandler(nextListItem, lastNode);\n const menuItem = nodeToSelect.querySelector(SELECTORS.menuitem);\n currentNode.setAttribute('aria-current', 'false');\n menuItem.setAttribute('aria-current', 'true');\n menuItem.focus();\n};\n\n/**\n * Focus on either the start or end of a nav list.\n *\n * @param {HTMLElement} node The element to focus on.\n */\nconst setFocusHomeEnd = node => {\n node.querySelector(SELECTORS.menuitem).focus();\n node.querySelector(SELECTORS.menuitem).setAttribute('aria-current', 'true');\n};\n\n/**\n * We need to look within the menu to find a last node we can add focus to.\n *\n * @param {HTMLElement} elementRoot Menu to find a final child node within.\n * @return {HTMLElement}\n */\nconst findUsableLastNode = elementRoot => {\n const lastNode = elementRoot.lastElementChild;\n\n // An example is the more menu existing but hidden on the page for the time being.\n if (!lastNode.classList.contains('d-none')) {\n return elementRoot.lastElementChild;\n } else {\n // Cast the HTMLCollection & reverse it.\n const extractedNodes = Array.prototype.map.call(elementRoot.children, node => {\n return node;\n }).reverse();\n\n // Get rid of any nodes we can not set focus on.\n const nodesToUse = extractedNodes.filter((node => {\n if (!node.classList.contains('d-none')) {\n return node;\n }\n }));\n\n // If we find no elements we can set focus on, fall back to the absolute first element.\n if (nodesToUse.length !== 0) {\n return nodesToUse[0];\n } else {\n return elementRoot.firstElementChild;\n }\n }\n};\n"],"file":"menu_navigation.min.js"} \ No newline at end of file diff --git a/lib/amd/build/moremenu.min.js b/lib/amd/build/moremenu.min.js index a06f4480f04..feecc3dd193 100644 --- a/lib/amd/build/moremenu.min.js +++ b/lib/amd/build/moremenu.min.js @@ -1,2 +1,2 @@ -define ("core/moremenu",["exports","jquery","core/keyboard_navigation"],function(a,b,c){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=d(b);c=d(c);function d(a){return a&&a.__esModule?a:{default:a}}var f={regions:{moredropdown:"[data-region=\"moredropdown\"]",morebutton:"[data-region=\"morebutton\"]"},classes:{dropdownitem:"dropdown-item",dropdownmoremenu:"dropdownmoremenu",dropdowntoggle:"dropdown-toggle",hidden:"d-none",active:"active",nav:"nav",navlink:"nav-link",observed:"observed"},attributes:{menu:"[role=\"menu\"]"}},g=function(a){var b=a.parentNode.offsetHeight+1,c=a.querySelector(f.regions.moredropdown),d=a.querySelector(f.regions.morebutton);if(a.offsetHeight>b){d.classList.remove(f.classes.hidden);var e=Array.from(a.children).reverse();e.forEach(function(c){if(!c.classList.contains(f.classes.dropdownmoremenu)){if(a.offsetHeight>b){var d=a.removeChild(c);h(a,d,!0)}}})}else{if("children"in c){var j=Array.from(c.children);j.forEach(function(d){if(a.offsetHeightb){g(a)}}a.parentNode.classList.add(f.classes.observed)},h=function(a,b){var c=2b){d.classList.remove(f.classes.hidden);var e=Array.from(a.children).reverse();e.forEach(function(c){if(!c.classList.contains(f.classes.dropdownmoremenu)){if(a.offsetHeight>b){var d=a.removeChild(c);h(a,d,!0)}}})}else{if("children"in c){var j=Array.from(c.children);j.forEach(function(d){if(a.offsetHeightb){g(a)}}a.parentNode.classList.add(f.classes.observed)},h=function(a,b){var c=2.\n\n/**\n * Moves wrapping navigation items into a more menu.\n *\n * @module core/moremenu\n * @package core\n * @copyright 2021 Moodle\n * @author Bas Brands \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport keyboard_navigation from \"core/keyboard_navigation\";\n/**\n * Moremenu selectors.\n */\nconst Selectors = {\n regions: {\n moredropdown: '[data-region=\"moredropdown\"]',\n morebutton: '[data-region=\"morebutton\"]'\n },\n classes: {\n dropdownitem: 'dropdown-item',\n dropdownmoremenu: 'dropdownmoremenu',\n dropdowntoggle: 'dropdown-toggle',\n hidden: 'd-none',\n active: 'active',\n nav: 'nav',\n navlink: 'nav-link',\n observed: 'observed',\n },\n attributes: {\n menu: '[role=\"menu\"]'\n }\n};\n\n/**\n * Auto Collapse navigation items that wrap into a dropdown menu.\n *\n * @param {HTMLElement} menu The navbar container.\n */\nconst autoCollapse = menu => {\n\n const maxHeight = menu.parentNode.offsetHeight + 1;\n\n const moreDropdown = menu.querySelector(Selectors.regions.moredropdown);\n const moreButton = menu.querySelector(Selectors.regions.morebutton);\n\n // If the menu items wrap and the menu height is larger than the height of the\n // parent then start pushing navlinks into the moreDropdown.\n if (menu.offsetHeight > maxHeight) {\n\n moreButton.classList.remove(Selectors.classes.hidden);\n\n const menuNodes = Array.from(menu.children).reverse();\n menuNodes.forEach(item => {\n if (!item.classList.contains(Selectors.classes.dropdownmoremenu)) {\n // After moving the menu items into the moreDropdown check again\n // if the menu height is still larger then the height of the parent.\n if (menu.offsetHeight > maxHeight) {\n const lastNode = menu.removeChild(item);\n // Move this node into the more dropdown menu.\n moveIntoMoreDropdown(menu, lastNode, true);\n }\n }\n });\n } else {\n // If the menu height is smaller than the height of the parent, then try returning navlinks to the menu.\n\n if ('children' in moreDropdown) {\n const menuNodes = Array.from(moreDropdown.children);\n menuNodes.forEach(item => {\n // Don't move the node to the more menu if it is explicitly defined that\n // this node should be displayed in the more dropdown menu at all times.\n if (menu.offsetHeight < maxHeight && item.dataset.forceintomoremenu !== 'true') {\n const lastNode = moreDropdown.removeChild(item);\n // Move this node from the more dropdown menu into the main section of the menu.\n moveOutOfMoreDropdown(menu, lastNode);\n }\n });\n\n // If there are no more menuNodes in the dropdown we can hide the moreButton.\n if (menuNodes.length === 0) {\n moreButton.classList.add(Selectors.classes.hidden);\n }\n }\n\n if (menu.offsetHeight > maxHeight) {\n autoCollapse(menu);\n }\n }\n menu.parentNode.classList.add(Selectors.classes.observed);\n};\n\n/**\n * Move a node into the \"more\" dropdown menu.\n *\n * This method forces a given navigation node to be added and displayed within the \"more\" dropdown menu.\n *\n * @param {HTMLElement} menu The navbar moremenu.\n * @param {HTMLElement} navNode The navigation node.\n * @param {boolean} prepend Whether to prepend or append the node to the content in the more dropdown menu.\n */\nconst moveIntoMoreDropdown = (menu, navNode, prepend = false) => {\n const moreDropdown = menu.querySelector(Selectors.regions.moredropdown);\n const dropdownToggle = menu.querySelector('.' + Selectors.classes.dropdowntoggle);\n\n const navLink = navNode.querySelector('.' + Selectors.classes.navlink);\n navNode.setAttribute('prev-role', navNode.getAttribute('role'));\n navNode.setAttribute('role', 'menuitem');\n\n // If there are navLinks that contain an active link in the moreDropdown\n // make the dropdownToggle in the moreButton active.\n if (navLink.classList.contains(Selectors.classes.active)) {\n dropdownToggle.classList.add(Selectors.classes.active);\n }\n\n // Change the styling of the navLink to a dropdownitem and push it into\n // the moreDropdown.\n navLink.classList.remove(Selectors.classes.navlink);\n navLink.classList.add(Selectors.classes.dropdownitem);\n if (prepend) {\n moreDropdown.prepend(navNode);\n } else {\n moreDropdown.append(navNode);\n }\n};\n\n/**\n * Move a node out of the \"more\" dropdown menu.\n *\n * This method forces a given node from the \"more\" dropdown menu to be displayed in the main section of the menu.\n *\n * @param {HTMLElement} menu The navbar moremenu.\n * @param {HTMLElement} navNode The navigation node.\n */\nconst moveOutOfMoreDropdown = (menu, navNode) => {\n const moreButton = menu.querySelector(Selectors.regions.morebutton);\n const dropdownToggle = menu.querySelector('.' + Selectors.classes.dropdowntoggle);\n const navLink = navNode.querySelector('.' + Selectors.classes.dropdownitem);\n\n navNode.setAttribute('role', navNode.getAttribute('prev-role'));\n if (navLink) {\n const currentAttribute = navLink.getAttribute('role');\n if (currentAttribute === 'menuitem') {\n navLink.removeAttribute('role');\n }\n }\n\n // Stop displaying the active state on the dropdownToggle if\n // the active navlink is removed.\n if (navLink.classList.contains(Selectors.classes.active)) {\n dropdownToggle.classList.remove(Selectors.classes.active);\n }\n navLink.classList.remove(Selectors.classes.dropdownitem);\n navLink.classList.add(Selectors.classes.navlink);\n menu.insertBefore(navNode, moreButton);\n};\n\n/**\n * Initialise the more menus.\n *\n * @param {HTMLElement} menu The navbar moremenu.\n */\nexport default menu => {\n menu.firstElementChild.querySelector('[role=\"menuitem\"]').setAttribute('tabindex', '0');\n // Pre-populate the \"more\" dropdown menu with navigation nodes which are set to be displayed in this menu\n // by default at all times.\n if ('children' in menu) {\n const moreButton = menu.querySelector(Selectors.regions.morebutton);\n const menuNodes = Array.from(menu.children);\n menuNodes.forEach((item) => {\n if (!item.classList.contains(Selectors.classes.dropdownmoremenu) &&\n item.dataset.forceintomoremenu === 'true') {\n // Append this node into the more dropdown menu.\n moveIntoMoreDropdown(menu, item, false);\n // After adding the node into the more dropdown menu, make sure that the more dropdown menu button\n // is displayed.\n if (moreButton.classList.contains(Selectors.classes.hidden)) {\n moreButton.classList.remove(Selectors.classes.hidden);\n }\n }\n });\n }\n // Populate the more dropdown menu with additional nodes if necessary, depending on the current screen size.\n autoCollapse(menu);\n keyboard_navigation(menu);\n\n // When the screen size changes make sure the menu still fits.\n window.addEventListener('resize', () => {\n autoCollapse(menu);\n keyboard_navigation(menu);\n });\n\n const toggledropdown = e => {\n const innerMenu = e.target.parentNode.querySelector(Selectors.attributes.menu);\n if (innerMenu) {\n innerMenu.classList.toggle('show');\n }\n e.stopPropagation();\n };\n\n // If there are dropdowns in the MoreMenu, add a new\n // event listener to show the contents on click and prevent the\n // moreMenu from closing.\n $('.' + Selectors.classes.dropdownmoremenu).on('show.bs.dropdown', function() {\n const moreDropdown = menu.querySelector(Selectors.regions.moredropdown);\n moreDropdown.querySelectorAll('.dropdown').forEach((dropdown) => {\n dropdown.removeEventListener('click', toggledropdown, true);\n dropdown.addEventListener('click', toggledropdown, true);\n });\n });\n};\n"],"file":"moremenu.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/moremenu.js"],"names":["Selectors","regions","moredropdown","morebutton","classes","dropdownitem","dropdownmoremenu","dropdowntoggle","hidden","active","nav","navlink","observed","attributes","menu","autoCollapse","maxHeight","parentNode","offsetHeight","moreDropdown","querySelector","moreButton","classList","remove","menuNodes","Array","from","children","reverse","forEach","item","contains","lastNode","removeChild","moveIntoMoreDropdown","dataset","forceintomoremenu","moveOutOfMoreDropdown","length","add","navNode","prepend","dropdownToggle","navLink","append","insertBefore","firstElementChild","setAttribute","window","addEventListener","toggledropdown","e","innerMenu","target","toggle","stopPropagation","on","querySelectorAll","dropdown","removeEventListener"],"mappings":"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","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 * Moves wrapping navigation items into a more menu.\n *\n * @module core/moremenu\n * @copyright 2021 Moodle\n * @author Bas Brands \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport menu_navigation from \"core/menu_navigation\";\n/**\n * Moremenu selectors.\n */\nconst Selectors = {\n regions: {\n moredropdown: '[data-region=\"moredropdown\"]',\n morebutton: '[data-region=\"morebutton\"]'\n },\n classes: {\n dropdownitem: 'dropdown-item',\n dropdownmoremenu: 'dropdownmoremenu',\n dropdowntoggle: 'dropdown-toggle',\n hidden: 'd-none',\n active: 'active',\n nav: 'nav',\n navlink: 'nav-link',\n observed: 'observed',\n },\n attributes: {\n menu: '[role=\"menu\"]'\n }\n};\n\n/**\n * Auto Collapse navigation items that wrap into a dropdown menu.\n *\n * @param {HTMLElement} menu The navbar container.\n */\nconst autoCollapse = menu => {\n\n const maxHeight = menu.parentNode.offsetHeight + 1;\n\n const moreDropdown = menu.querySelector(Selectors.regions.moredropdown);\n const moreButton = menu.querySelector(Selectors.regions.morebutton);\n\n // If the menu items wrap and the menu height is larger than the height of the\n // parent then start pushing navlinks into the moreDropdown.\n if (menu.offsetHeight > maxHeight) {\n\n moreButton.classList.remove(Selectors.classes.hidden);\n\n const menuNodes = Array.from(menu.children).reverse();\n menuNodes.forEach(item => {\n if (!item.classList.contains(Selectors.classes.dropdownmoremenu)) {\n // After moving the menu items into the moreDropdown check again\n // if the menu height is still larger then the height of the parent.\n if (menu.offsetHeight > maxHeight) {\n const lastNode = menu.removeChild(item);\n // Move this node into the more dropdown menu.\n moveIntoMoreDropdown(menu, lastNode, true);\n }\n }\n });\n } else {\n // If the menu height is smaller than the height of the parent, then try returning navlinks to the menu.\n\n if ('children' in moreDropdown) {\n const menuNodes = Array.from(moreDropdown.children);\n menuNodes.forEach(item => {\n // Don't move the node to the more menu if it is explicitly defined that\n // this node should be displayed in the more dropdown menu at all times.\n if (menu.offsetHeight < maxHeight && item.dataset.forceintomoremenu !== 'true') {\n const lastNode = moreDropdown.removeChild(item);\n // Move this node from the more dropdown menu into the main section of the menu.\n moveOutOfMoreDropdown(menu, lastNode);\n }\n });\n\n // If there are no more menuNodes in the dropdown we can hide the moreButton.\n if (menuNodes.length === 0) {\n moreButton.classList.add(Selectors.classes.hidden);\n }\n }\n\n if (menu.offsetHeight > maxHeight) {\n autoCollapse(menu);\n }\n }\n menu.parentNode.classList.add(Selectors.classes.observed);\n};\n\n/**\n * Move a node into the \"more\" dropdown menu.\n *\n * This method forces a given navigation node to be added and displayed within the \"more\" dropdown menu.\n *\n * @param {HTMLElement} menu The navbar moremenu.\n * @param {HTMLElement} navNode The navigation node.\n * @param {boolean} prepend Whether to prepend or append the node to the content in the more dropdown menu.\n */\nconst moveIntoMoreDropdown = (menu, navNode, prepend = false) => {\n const moreDropdown = menu.querySelector(Selectors.regions.moredropdown);\n const dropdownToggle = menu.querySelector('.' + Selectors.classes.dropdowntoggle);\n\n const navLink = navNode.querySelector('.' + Selectors.classes.navlink);\n // If there are navLinks that contain an active link in the moreDropdown\n // make the dropdownToggle in the moreButton active.\n if (navLink.classList.contains(Selectors.classes.active)) {\n dropdownToggle.classList.add(Selectors.classes.active);\n }\n\n // Change the styling of the navLink to a dropdownitem and push it into\n // the moreDropdown.\n navLink.classList.remove(Selectors.classes.navlink);\n navLink.classList.add(Selectors.classes.dropdownitem);\n if (prepend) {\n moreDropdown.prepend(navNode);\n } else {\n moreDropdown.append(navNode);\n }\n};\n\n/**\n * Move a node out of the \"more\" dropdown menu.\n *\n * This method forces a given node from the \"more\" dropdown menu to be displayed in the main section of the menu.\n *\n * @param {HTMLElement} menu The navbar moremenu.\n * @param {HTMLElement} navNode The navigation node.\n */\nconst moveOutOfMoreDropdown = (menu, navNode) => {\n const moreButton = menu.querySelector(Selectors.regions.morebutton);\n const dropdownToggle = menu.querySelector('.' + Selectors.classes.dropdowntoggle);\n const navLink = navNode.querySelector('.' + Selectors.classes.dropdownitem);\n\n // Stop displaying the active state on the dropdownToggle if\n // the active navlink is removed.\n if (navLink.classList.contains(Selectors.classes.active)) {\n dropdownToggle.classList.remove(Selectors.classes.active);\n }\n navLink.classList.remove(Selectors.classes.dropdownitem);\n navLink.classList.add(Selectors.classes.navlink);\n menu.insertBefore(navNode, moreButton);\n};\n\n/**\n * Initialise the more menus.\n *\n * @param {HTMLElement} menu The navbar moremenu.\n */\nexport default menu => {\n menu.firstElementChild.querySelector('[role=\"menuitem\"]').setAttribute('tabindex', '0');\n // Pre-populate the \"more\" dropdown menu with navigation nodes which are set to be displayed in this menu\n // by default at all times.\n if ('children' in menu) {\n const moreButton = menu.querySelector(Selectors.regions.morebutton);\n const menuNodes = Array.from(menu.children);\n menuNodes.forEach((item) => {\n if (!item.classList.contains(Selectors.classes.dropdownmoremenu) &&\n item.dataset.forceintomoremenu === 'true') {\n // Append this node into the more dropdown menu.\n moveIntoMoreDropdown(menu, item, false);\n // After adding the node into the more dropdown menu, make sure that the more dropdown menu button\n // is displayed.\n if (moreButton.classList.contains(Selectors.classes.hidden)) {\n moreButton.classList.remove(Selectors.classes.hidden);\n }\n }\n });\n }\n // Populate the more dropdown menu with additional nodes if necessary, depending on the current screen size.\n autoCollapse(menu);\n menu_navigation(menu);\n\n // When the screen size changes make sure the menu still fits.\n window.addEventListener('resize', () => {\n autoCollapse(menu);\n menu_navigation(menu);\n });\n\n const toggledropdown = e => {\n const innerMenu = e.target.parentNode.querySelector(Selectors.attributes.menu);\n if (innerMenu) {\n innerMenu.classList.toggle('show');\n }\n e.stopPropagation();\n };\n\n // If there are dropdowns in the MoreMenu, add a new\n // event listener to show the contents on click and prevent the\n // moreMenu from closing.\n $('.' + Selectors.classes.dropdownmoremenu).on('show.bs.dropdown', function() {\n const moreDropdown = menu.querySelector(Selectors.regions.moredropdown);\n moreDropdown.querySelectorAll('.dropdown').forEach((dropdown) => {\n dropdown.removeEventListener('click', toggledropdown, true);\n dropdown.addEventListener('click', toggledropdown, true);\n });\n });\n};\n"],"file":"moremenu.min.js"} \ No newline at end of file diff --git a/lib/amd/build/usermenu.min.js.map b/lib/amd/build/usermenu.min.js.map index 6dfd818f289..fda9bf8d7f3 100644 --- a/lib/amd/build/usermenu.min.js.map +++ b/lib/amd/build/usermenu.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/usermenu.js"],"names":["selectors","userMenu","userMenuCarousel","userMenuCarouselItem","userMenuCarouselItemActive","userMenuCarouselNavigationLink","registerEventListeners","document","querySelector","on","activeCarouselItem","focus","querySelectorAll","forEach","element","classList","contains","style","width","offsetWidth","height","offsetHeight","addEventListener","e","target","matches","carouselManagement","keyCode","space","enter","preventDefault","stopPropagation","targetedCarouselItemId","dataset","carouselTargetId","targetedCarouselItem","index","Array","from","parentNode","children","indexOf","carousel","init"],"mappings":"6JAyBA,uD,GAMMA,CAAAA,CAAS,CAAG,CACdC,QAAQ,CAAE,WADI,CAEdC,gBAAgB,CAAE,8BAFJ,CAGdC,oBAAoB,CAAE,6CAHR,CAIdC,0BAA0B,CAAE,oDAJd,CAKdC,8BAA8B,CAAE,wDALlB,C,CAWZC,CAAsB,CAAG,UAAM,CACjC,GAAML,CAAAA,CAAQ,CAAGM,QAAQ,CAACC,aAAT,CAAuBR,CAAS,CAACC,QAAjC,CAAjB,CAGA,cAAED,CAAS,CAACC,QAAZ,EAAsBQ,EAAtB,CAAyB,mBAAzB,CAA8C,UAAM,CAChD,GAAMC,CAAAA,CAAkB,CAAGH,QAAQ,CAACC,aAAT,CAAuBR,CAAS,CAACI,0BAAjC,CAA3B,CAEAM,CAAkB,CAACC,KAAnB,GAEAV,CAAQ,CAACW,gBAAT,CAA0BZ,CAAS,CAACG,oBAApC,EAA0DU,OAA1D,CAAkE,SAAAC,CAAO,CAAI,CAKzE,GAAI,CAACA,CAAO,CAACC,SAAR,CAAkBC,QAAlB,CAA2B,QAA3B,CAAL,CAA2C,CACvCF,CAAO,CAACG,KAAR,CAAcC,KAAd,CAAsBR,CAAkB,CAACS,WAAnB,CAAiC,IAAvD,CACAL,CAAO,CAACG,KAAR,CAAcG,MAAd,CAAuBV,CAAkB,CAACW,YAAnB,CAAkC,IAC5D,CACJ,CATD,CAUH,CAfD,EAkBApB,CAAQ,CAACqB,gBAAT,CAA0B,OAA1B,CAAmC,SAACC,CAAD,CAAO,CAGtC,GAAIA,CAAC,CAACC,MAAF,CAASC,OAAT,CAAiBzB,CAAS,CAACK,8BAA3B,CAAJ,CAAgE,CAC5DqB,CAAkB,CAACH,CAAD,CACrB,CACJ,CAND,EAQAtB,CAAQ,CAACqB,gBAAT,CAA0B,SAA1B,CAAqC,SAAAC,CAAC,CAAI,CAEtC,GAAI,CAACA,CAAC,CAACI,OAAF,GAAcC,OAAd,EACDL,CAAC,CAACI,OAAF,GAAcE,OADd,GAEAN,CAAC,CAACC,MAAF,CAASC,OAAT,CAAiBzB,CAAS,CAACK,8BAA3B,CAFJ,CAEgE,CAC5DkB,CAAC,CAACO,cAAF,GACAJ,CAAkB,CAACH,CAAD,CACrB,CACJ,CARD,EAeA,GAAMG,CAAAA,CAAkB,CAAG,SAAAH,CAAC,CAAI,CAK5BA,CAAC,CAACQ,eAAF,GAL4B,GAOtBC,CAAAA,CAAsB,CAAGT,CAAC,CAACC,MAAF,CAASS,OAAT,CAAiBC,gBAPpB,CAQtBC,CAAoB,CAAGlC,CAAQ,CAACO,aAAT,CAAuB,IAAMwB,CAA7B,CARD,CAUtBI,CAAK,CAAGC,KAAK,CAACC,IAAN,CAAWH,CAAoB,CAACI,UAArB,CAAgCC,QAA3C,EAAqDC,OAArD,CAA6DN,CAA7D,CAVc,CAY5B,cAAEnC,CAAS,CAACE,gBAAZ,EAA8BwC,QAA9B,CAAuCN,CAAvC,CAEH,CAdD,CAiBA,cAAEpC,CAAS,CAACC,QAAZ,EAAsBQ,EAAtB,CAAyB,kBAAzB,CAA6C,UAAM,CAG/C,cAAET,CAAS,CAACE,gBAAZ,EAA8BwC,QAA9B,CAAuC,CAAvC,CACH,CAJD,EAOA,cAAE1C,CAAS,CAACE,gBAAZ,EAA8BO,EAA9B,CAAiC,kBAAjC,CAAqD,UAAM,CACvD,GAAMC,CAAAA,CAAkB,CAAGT,CAAQ,CAACO,aAAT,CAAuBR,CAAS,CAACI,0BAAjC,CAA3B,CAEAM,CAAkB,CAACC,KAAnB,EACH,CAJD,CAKH,C,WASc,CACXgC,IAAI,CALK,QAAPA,CAAAA,IAAO,EAAM,CACfrC,CAAsB,EACzB,CAEc,C","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 * Initializes and handles events in the user menu.\n *\n * @module core/usermenu\n * @package core\n * @copyright 2021 Moodle\n * @author Mihail Geshoski \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport {space, enter} from 'core/key_codes';\n\n/**\n * User menu constants.\n */\nconst selectors = {\n userMenu: '.usermenu',\n userMenuCarousel: '.usermenu #usermenu-carousel',\n userMenuCarouselItem: '.usermenu #usermenu-carousel .carousel-item',\n userMenuCarouselItemActive: '.usermenu #usermenu-carousel .carousel-item.active',\n userMenuCarouselNavigationLink: '.usermenu #usermenu-carousel .carousel-navigation-link',\n};\n\n/**\n * Register event listeners.\n */\nconst registerEventListeners = () => {\n const userMenu = document.querySelector(selectors.userMenu);\n\n // Handle the 'shown.bs.dropdown' event (Fired when the dropdown menu is fully displayed).\n $(selectors.userMenu).on('shown.bs.dropdown', () => {\n const activeCarouselItem = document.querySelector(selectors.userMenuCarouselItemActive);\n // Set the focus on the active carousel item.\n activeCarouselItem.focus();\n\n userMenu.querySelectorAll(selectors.userMenuCarouselItem).forEach(element => {\n // Resize all non-active carousel items to match the height and width of the current active (main)\n // carousel item to avoid sizing inconsistencies. This has to be done once the dropdown menu is fully\n // displayed ('shown.bs.dropdown') as the offsetWidth and offsetHeight cannot be obtained when the\n // element is hidden.\n if (!element.classList.contains('active')) {\n element.style.width = activeCarouselItem.offsetWidth + 'px';\n element.style.height = activeCarouselItem.offsetHeight + 'px';\n }\n });\n });\n\n // Handle click events in the user menu.\n userMenu.addEventListener('click', (e) => {\n\n // Handle click event on the carousel navigation (control) links in the user menu.\n if (e.target.matches(selectors.userMenuCarouselNavigationLink)) {\n carouselManagement(e);\n }\n });\n\n userMenu.addEventListener('keydown', e => {\n // Handle keydown event on the carousel navigation (control) links in the user menu.\n if ((e.keyCode === space ||\n e.keyCode === enter) &&\n e.target.matches(selectors.userMenuCarouselNavigationLink)) {\n e.preventDefault();\n carouselManagement(e);\n }\n });\n\n /**\n * We do the same actions here even if the caller was a click or button press.\n *\n * @param {Event} e The triggering element and key presses etc.\n */\n const carouselManagement = e => {\n // By default the user menu dropdown element closes on a click event. This behaviour is not desirable\n // as we need to be able to navigate through the carousel items (submenus of the user menu) within the\n // user menu. Therefore, we need to prevent the propagation of this event and then manually call the\n // carousel transition.\n e.stopPropagation();\n // The id of the targeted carousel item.\n const targetedCarouselItemId = e.target.dataset.carouselTargetId;\n const targetedCarouselItem = userMenu.querySelector('#' + targetedCarouselItemId);\n // Get the position (index) of the targeted carousel item within the parent container element.\n const index = Array.from(targetedCarouselItem.parentNode.children).indexOf(targetedCarouselItem);\n // Navigate to the targeted carousel item.\n $(selectors.userMenuCarousel).carousel(index);\n\n };\n\n // Handle the 'hide.bs.dropdown' event (Fired when the dropdown menu is being closed).\n $(selectors.userMenu).on('hide.bs.dropdown', () => {\n // Reset the state once the user menu dropdown is closed and return back to the first (main) carousel item\n // if necessary.\n $(selectors.userMenuCarousel).carousel(0);\n });\n\n // Handle the 'slid.bs.carousel' event (Fired when the carousel has completed its slide transition).\n $(selectors.userMenuCarousel).on('slid.bs.carousel', () => {\n const activeCarouselItem = userMenu.querySelector(selectors.userMenuCarouselItemActive);\n // Set the focus on the newly activated carousel item.\n activeCarouselItem.focus();\n });\n};\n\n/**\n * Initialize the user menu.\n */\nconst init = () => {\n registerEventListeners();\n};\n\nexport default {\n init: init,\n};\n"],"file":"usermenu.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/usermenu.js"],"names":["selectors","userMenu","userMenuCarousel","userMenuCarouselItem","userMenuCarouselItemActive","userMenuCarouselNavigationLink","registerEventListeners","document","querySelector","on","activeCarouselItem","focus","querySelectorAll","forEach","element","classList","contains","style","width","offsetWidth","height","offsetHeight","addEventListener","e","target","matches","carouselManagement","keyCode","space","enter","preventDefault","stopPropagation","targetedCarouselItemId","dataset","carouselTargetId","targetedCarouselItem","index","Array","from","parentNode","children","indexOf","carousel","init"],"mappings":"6JAwBA,uD,GAMMA,CAAAA,CAAS,CAAG,CACdC,QAAQ,CAAE,WADI,CAEdC,gBAAgB,CAAE,8BAFJ,CAGdC,oBAAoB,CAAE,6CAHR,CAIdC,0BAA0B,CAAE,oDAJd,CAKdC,8BAA8B,CAAE,wDALlB,C,CAWZC,CAAsB,CAAG,UAAM,CACjC,GAAML,CAAAA,CAAQ,CAAGM,QAAQ,CAACC,aAAT,CAAuBR,CAAS,CAACC,QAAjC,CAAjB,CAGA,cAAED,CAAS,CAACC,QAAZ,EAAsBQ,EAAtB,CAAyB,mBAAzB,CAA8C,UAAM,CAChD,GAAMC,CAAAA,CAAkB,CAAGH,QAAQ,CAACC,aAAT,CAAuBR,CAAS,CAACI,0BAAjC,CAA3B,CAEAM,CAAkB,CAACC,KAAnB,GAEAV,CAAQ,CAACW,gBAAT,CAA0BZ,CAAS,CAACG,oBAApC,EAA0DU,OAA1D,CAAkE,SAAAC,CAAO,CAAI,CAKzE,GAAI,CAACA,CAAO,CAACC,SAAR,CAAkBC,QAAlB,CAA2B,QAA3B,CAAL,CAA2C,CACvCF,CAAO,CAACG,KAAR,CAAcC,KAAd,CAAsBR,CAAkB,CAACS,WAAnB,CAAiC,IAAvD,CACAL,CAAO,CAACG,KAAR,CAAcG,MAAd,CAAuBV,CAAkB,CAACW,YAAnB,CAAkC,IAC5D,CACJ,CATD,CAUH,CAfD,EAkBApB,CAAQ,CAACqB,gBAAT,CAA0B,OAA1B,CAAmC,SAACC,CAAD,CAAO,CAGtC,GAAIA,CAAC,CAACC,MAAF,CAASC,OAAT,CAAiBzB,CAAS,CAACK,8BAA3B,CAAJ,CAAgE,CAC5DqB,CAAkB,CAACH,CAAD,CACrB,CACJ,CAND,EAQAtB,CAAQ,CAACqB,gBAAT,CAA0B,SAA1B,CAAqC,SAAAC,CAAC,CAAI,CAEtC,GAAI,CAACA,CAAC,CAACI,OAAF,GAAcC,OAAd,EACDL,CAAC,CAACI,OAAF,GAAcE,OADd,GAEAN,CAAC,CAACC,MAAF,CAASC,OAAT,CAAiBzB,CAAS,CAACK,8BAA3B,CAFJ,CAEgE,CAC5DkB,CAAC,CAACO,cAAF,GACAJ,CAAkB,CAACH,CAAD,CACrB,CACJ,CARD,EAeA,GAAMG,CAAAA,CAAkB,CAAG,SAAAH,CAAC,CAAI,CAK5BA,CAAC,CAACQ,eAAF,GAL4B,GAOtBC,CAAAA,CAAsB,CAAGT,CAAC,CAACC,MAAF,CAASS,OAAT,CAAiBC,gBAPpB,CAQtBC,CAAoB,CAAGlC,CAAQ,CAACO,aAAT,CAAuB,IAAMwB,CAA7B,CARD,CAUtBI,CAAK,CAAGC,KAAK,CAACC,IAAN,CAAWH,CAAoB,CAACI,UAArB,CAAgCC,QAA3C,EAAqDC,OAArD,CAA6DN,CAA7D,CAVc,CAY5B,cAAEnC,CAAS,CAACE,gBAAZ,EAA8BwC,QAA9B,CAAuCN,CAAvC,CAEH,CAdD,CAiBA,cAAEpC,CAAS,CAACC,QAAZ,EAAsBQ,EAAtB,CAAyB,kBAAzB,CAA6C,UAAM,CAG/C,cAAET,CAAS,CAACE,gBAAZ,EAA8BwC,QAA9B,CAAuC,CAAvC,CACH,CAJD,EAOA,cAAE1C,CAAS,CAACE,gBAAZ,EAA8BO,EAA9B,CAAiC,kBAAjC,CAAqD,UAAM,CACvD,GAAMC,CAAAA,CAAkB,CAAGT,CAAQ,CAACO,aAAT,CAAuBR,CAAS,CAACI,0BAAjC,CAA3B,CAEAM,CAAkB,CAACC,KAAnB,EACH,CAJD,CAKH,C,WASc,CACXgC,IAAI,CALK,QAAPA,CAAAA,IAAO,EAAM,CACfrC,CAAsB,EACzB,CAEc,C","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 * Initializes and handles events in the user menu.\n *\n * @module core/usermenu\n * @copyright 2021 Moodle\n * @author Mihail Geshoski \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport {space, enter} from 'core/key_codes';\n\n/**\n * User menu constants.\n */\nconst selectors = {\n userMenu: '.usermenu',\n userMenuCarousel: '.usermenu #usermenu-carousel',\n userMenuCarouselItem: '.usermenu #usermenu-carousel .carousel-item',\n userMenuCarouselItemActive: '.usermenu #usermenu-carousel .carousel-item.active',\n userMenuCarouselNavigationLink: '.usermenu #usermenu-carousel .carousel-navigation-link',\n};\n\n/**\n * Register event listeners.\n */\nconst registerEventListeners = () => {\n const userMenu = document.querySelector(selectors.userMenu);\n\n // Handle the 'shown.bs.dropdown' event (Fired when the dropdown menu is fully displayed).\n $(selectors.userMenu).on('shown.bs.dropdown', () => {\n const activeCarouselItem = document.querySelector(selectors.userMenuCarouselItemActive);\n // Set the focus on the active carousel item.\n activeCarouselItem.focus();\n\n userMenu.querySelectorAll(selectors.userMenuCarouselItem).forEach(element => {\n // Resize all non-active carousel items to match the height and width of the current active (main)\n // carousel item to avoid sizing inconsistencies. This has to be done once the dropdown menu is fully\n // displayed ('shown.bs.dropdown') as the offsetWidth and offsetHeight cannot be obtained when the\n // element is hidden.\n if (!element.classList.contains('active')) {\n element.style.width = activeCarouselItem.offsetWidth + 'px';\n element.style.height = activeCarouselItem.offsetHeight + 'px';\n }\n });\n });\n\n // Handle click events in the user menu.\n userMenu.addEventListener('click', (e) => {\n\n // Handle click event on the carousel navigation (control) links in the user menu.\n if (e.target.matches(selectors.userMenuCarouselNavigationLink)) {\n carouselManagement(e);\n }\n });\n\n userMenu.addEventListener('keydown', e => {\n // Handle keydown event on the carousel navigation (control) links in the user menu.\n if ((e.keyCode === space ||\n e.keyCode === enter) &&\n e.target.matches(selectors.userMenuCarouselNavigationLink)) {\n e.preventDefault();\n carouselManagement(e);\n }\n });\n\n /**\n * We do the same actions here even if the caller was a click or button press.\n *\n * @param {Event} e The triggering element and key presses etc.\n */\n const carouselManagement = e => {\n // By default the user menu dropdown element closes on a click event. This behaviour is not desirable\n // as we need to be able to navigate through the carousel items (submenus of the user menu) within the\n // user menu. Therefore, we need to prevent the propagation of this event and then manually call the\n // carousel transition.\n e.stopPropagation();\n // The id of the targeted carousel item.\n const targetedCarouselItemId = e.target.dataset.carouselTargetId;\n const targetedCarouselItem = userMenu.querySelector('#' + targetedCarouselItemId);\n // Get the position (index) of the targeted carousel item within the parent container element.\n const index = Array.from(targetedCarouselItem.parentNode.children).indexOf(targetedCarouselItem);\n // Navigate to the targeted carousel item.\n $(selectors.userMenuCarousel).carousel(index);\n\n };\n\n // Handle the 'hide.bs.dropdown' event (Fired when the dropdown menu is being closed).\n $(selectors.userMenu).on('hide.bs.dropdown', () => {\n // Reset the state once the user menu dropdown is closed and return back to the first (main) carousel item\n // if necessary.\n $(selectors.userMenuCarousel).carousel(0);\n });\n\n // Handle the 'slid.bs.carousel' event (Fired when the carousel has completed its slide transition).\n $(selectors.userMenuCarousel).on('slid.bs.carousel', () => {\n const activeCarouselItem = userMenu.querySelector(selectors.userMenuCarouselItemActive);\n // Set the focus on the newly activated carousel item.\n activeCarouselItem.focus();\n });\n};\n\n/**\n * Initialize the user menu.\n */\nconst init = () => {\n registerEventListeners();\n};\n\nexport default {\n init: init,\n};\n"],"file":"usermenu.min.js"} \ No newline at end of file diff --git a/lib/amd/src/keyboard_navigation.js b/lib/amd/src/menu_navigation.js similarity index 93% rename from lib/amd/src/keyboard_navigation.js rename to lib/amd/src/menu_navigation.js index 85817bba569..a6f49f5d84e 100644 --- a/lib/amd/src/keyboard_navigation.js +++ b/lib/amd/src/menu_navigation.js @@ -17,7 +17,6 @@ * Keyboard initialization for a given html node. * * @module core/keyboard_navigation - * @package core * @copyright 2021 Moodle * @author Mathew May * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later @@ -74,6 +73,7 @@ const listenerEvents = e => { // Remove active class from any other dropdown elements. Array.prototype.forEach.call(src.closest('.dropdown-menu').children, node => { node.querySelector(SELECTORS.menuitem).classList.remove('active'); + node.setAttribute('aria-current', 'false'); }); if (!src.parentElement.classList.contains('dropdown')) { @@ -97,10 +97,12 @@ const listenerEvents = e => { } if (e.keyCode === home) { e.preventDefault(); + src.setAttribute('aria-current', 'false'); setFocusHomeEnd(firstNode); } if (e.keyCode === end) { e.preventDefault(); + src.currentNode.setAttribute('aria-current', 'false'); setFocusHomeEnd(lastNode); } if (e.keyCode === space || @@ -134,6 +136,8 @@ const setFocusNext = (currentNode, firstNode) => { const nextListItem = currentNode.parentElement.nextElementSibling; const nodeToSelect = clickErrorHandler(nextListItem, firstNode); const menuItem = nodeToSelect.querySelector(SELECTORS.menuitem); + currentNode.setAttribute('aria-current', 'false'); + menuItem.setAttribute('aria-current', 'true'); menuItem.focus(); }; @@ -147,6 +151,8 @@ const setFocusPrev = (currentNode, lastNode) => { const nextListItem = currentNode.parentElement.previousElementSibling; const nodeToSelect = clickErrorHandler(nextListItem, lastNode); const menuItem = nodeToSelect.querySelector(SELECTORS.menuitem); + currentNode.setAttribute('aria-current', 'false'); + menuItem.setAttribute('aria-current', 'true'); menuItem.focus(); }; @@ -157,6 +163,7 @@ const setFocusPrev = (currentNode, lastNode) => { */ const setFocusHomeEnd = node => { node.querySelector(SELECTORS.menuitem).focus(); + node.querySelector(SELECTORS.menuitem).setAttribute('aria-current', 'true'); }; /** diff --git a/lib/amd/src/moremenu.js b/lib/amd/src/moremenu.js index 02557cd9847..425a01fe826 100644 --- a/lib/amd/src/moremenu.js +++ b/lib/amd/src/moremenu.js @@ -17,14 +17,13 @@ * Moves wrapping navigation items into a more menu. * * @module core/moremenu - * @package core * @copyright 2021 Moodle * @author Bas Brands * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ import $ from 'jquery'; -import keyboard_navigation from "core/keyboard_navigation"; +import menu_navigation from "core/menu_navigation"; /** * Moremenu selectors. */ @@ -120,9 +119,6 @@ const moveIntoMoreDropdown = (menu, navNode, prepend = false) => { const dropdownToggle = menu.querySelector('.' + Selectors.classes.dropdowntoggle); const navLink = navNode.querySelector('.' + Selectors.classes.navlink); - navNode.setAttribute('prev-role', navNode.getAttribute('role')); - navNode.setAttribute('role', 'menuitem'); - // If there are navLinks that contain an active link in the moreDropdown // make the dropdownToggle in the moreButton active. if (navLink.classList.contains(Selectors.classes.active)) { @@ -153,14 +149,6 @@ const moveOutOfMoreDropdown = (menu, navNode) => { const dropdownToggle = menu.querySelector('.' + Selectors.classes.dropdowntoggle); const navLink = navNode.querySelector('.' + Selectors.classes.dropdownitem); - navNode.setAttribute('role', navNode.getAttribute('prev-role')); - if (navLink) { - const currentAttribute = navLink.getAttribute('role'); - if (currentAttribute === 'menuitem') { - navLink.removeAttribute('role'); - } - } - // Stop displaying the active state on the dropdownToggle if // the active navlink is removed. if (navLink.classList.contains(Selectors.classes.active)) { @@ -198,12 +186,12 @@ export default menu => { } // Populate the more dropdown menu with additional nodes if necessary, depending on the current screen size. autoCollapse(menu); - keyboard_navigation(menu); + menu_navigation(menu); // When the screen size changes make sure the menu still fits. window.addEventListener('resize', () => { autoCollapse(menu); - keyboard_navigation(menu); + menu_navigation(menu); }); const toggledropdown = e => { diff --git a/lib/amd/src/usermenu.js b/lib/amd/src/usermenu.js index de8104cdbd1..25d05ca2f4d 100644 --- a/lib/amd/src/usermenu.js +++ b/lib/amd/src/usermenu.js @@ -17,7 +17,6 @@ * Initializes and handles events in the user menu. * * @module core/usermenu - * @package core * @copyright 2021 Moodle * @author Mihail Geshoski * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later diff --git a/lib/classes/navigation/output/more_menu.php b/lib/classes/navigation/output/more_menu.php index 6d6d356baaa..7947c88766e 100644 --- a/lib/classes/navigation/output/more_menu.php +++ b/lib/classes/navigation/output/more_menu.php @@ -33,7 +33,6 @@ class more_menu implements renderable, templatable { protected $content; protected $navbarstyle; - protected $hastabs; protected $haschildren; /** @@ -41,13 +40,11 @@ class more_menu implements renderable, templatable { * * @param object $content Navigation objects. * @param string $navbarstyle class name. - * @param bool $hastabs If tabs are being used. * @param bool $haschildren The content has children. */ - public function __construct(object $content, string $navbarstyle, bool $hastabs = false, bool $haschildren = true) { + public function __construct(object $content, string $navbarstyle, bool $haschildren = true) { $this->content = $content; $this->navbarstyle = $navbarstyle; - $this->hastabs = $hastabs; $this->haschildren = $haschildren; } @@ -58,7 +55,7 @@ class more_menu implements renderable, templatable { * @return array Data for rendering a template */ public function export_for_template(renderer_base $output): array { - $data = ['navbarstyle' => $this->navbarstyle, 'tabs' => $this->hastabs]; + $data = ['navbarstyle' => $this->navbarstyle]; if ($this->haschildren) { if (!isset($this->content->children) || count($this->content->children) == 0) { $data = []; diff --git a/lib/classes/navigation/output/primary.php b/lib/classes/navigation/output/primary.php index 2bcc751b193..f8103a6c184 100644 --- a/lib/classes/navigation/output/primary.php +++ b/lib/classes/navigation/output/primary.php @@ -56,7 +56,7 @@ class primary implements renderable, templatable { } $menudata = (object) array_merge($this->get_primary_nav(), $this->get_custom_menu($output)); - $moremenu = new \core\navigation\output\more_menu($menudata, 'navbar-nav', false, false); + $moremenu = new \core\navigation\output\more_menu($menudata, 'navbar-nav', false); return [ 'moremenu' => $moremenu->export_for_template($output), diff --git a/lib/templates/moremenu_children.mustache b/lib/templates/moremenu_children.mustache index ce86853b9c6..e404bf975bf 100644 --- a/lib/templates/moremenu_children.mustache +++ b/lib/templates/moremenu_children.mustache @@ -50,7 +50,7 @@ {{^haschildren}}