mirror of
https://github.com/moodle/moodle.git
synced 2025-04-21 16:32:18 +02:00
MDL-69588 accessibility: Address review points
This commit is contained in:
parent
1ce2bb0007
commit
1f6bcd7eae
@ -35,6 +35,7 @@ Feature: Display badges
|
||||
And I set the field "potentialrecipients[]" to "Student 1 (student1@example.com)"
|
||||
And I press "Award badge"
|
||||
# Check badge details are displayed.
|
||||
And I navigate to "Badges > Manage badges" in site administration
|
||||
And I follow "Testing system badge"
|
||||
And I follow "Recipients (1)"
|
||||
When I click on "View issued badge" "link" in the "Student 1" "table_row"
|
||||
@ -59,6 +60,7 @@ Feature: Display badges
|
||||
And I set the field "potentialrecipients[]" to "Student 1 (student1@example.com)"
|
||||
And I press "Award badge"
|
||||
# Check badge details are displayed.
|
||||
And I navigate to "Badges > Manage badges" in site administration
|
||||
And I follow "Testing system badge"
|
||||
And I follow "Recipients (1)"
|
||||
When I click on "View issued badge" "link" in the "Student 1" "table_row"
|
||||
@ -107,6 +109,7 @@ Feature: Display badges
|
||||
And I set the field "potentialrecipients[]" to "Student 1 (student1@example.com)"
|
||||
And I press "Award badge"
|
||||
# Check "Expires" date is displayed.
|
||||
And I navigate to "Badges > Manage badges" in site administration
|
||||
And I follow "Testing system badge"
|
||||
And I follow "Recipients (1)"
|
||||
And I click on "View issued badge" "link" in the "Student 1" "table_row"
|
||||
@ -130,6 +133,7 @@ Feature: Display badges
|
||||
# Wait 1 second to guarantee the badge is expired.
|
||||
And I wait "1" seconds
|
||||
# Check "Expired" date is displayed.
|
||||
And I navigate to "Badges > Manage badges" in site administration
|
||||
And I follow "Testing system badge"
|
||||
And I follow "Recipients (1)"
|
||||
And I click on "View issued badge" "link" in the "Student 1" "table_row"
|
||||
|
@ -1739,6 +1739,7 @@ $string['rename'] = 'Rename';
|
||||
$string['renamefileto'] = 'Rename <b>{$a}</b> to';
|
||||
$string['report'] = 'Report';
|
||||
$string['reports'] = 'Reports';
|
||||
$string['reporttype'] = 'Report type';
|
||||
$string['repositories'] = 'Repositories';
|
||||
$string['requestcourse'] = 'Request a course';
|
||||
$string['requestedby'] = 'Requested by';
|
||||
|
2
lib/amd/build/keyboard_navigation.min.js
vendored
Normal file
2
lib/amd/build/keyboard_navigation.min.js
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
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
|
1
lib/amd/build/keyboard_navigation.min.js.map
Normal file
1
lib/amd/build/keyboard_navigation.min.js.map
Normal file
File diff suppressed because one or more lines are too long
2
lib/amd/build/moremenu.min.js
vendored
2
lib/amd/build/moremenu.min.js
vendored
@ -1,2 +1,2 @@
|
||||
define ("core/moremenu",["exports","jquery"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);var c={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\"]"}},d=function(a){var b=a.parentNode.offsetHeight+1,g=a.querySelector(c.regions.moredropdown),h=a.querySelector(c.regions.morebutton);if(a.offsetHeight>b){h.classList.remove(c.classes.hidden);var i=Array.from(a.children).reverse();i.forEach(function(d){if(!d.classList.contains(c.classes.dropdownmoremenu)){if(a.offsetHeight>b){var f=a.removeChild(d);e(a,f,!0)}}})}else{if("children"in g){var j=Array.from(g.children);j.forEach(function(c){if(a.offsetHeight<b&&"true"!==c.dataset.forceintomoremenu){var d=g.removeChild(c);f(a,d)}});if(0===j.length){h.classList.add(c.classes.hidden)}}if(a.offsetHeight>b){d(a)}}a.parentNode.classList.add(c.classes.observed)},e=function(a,b){var d=2<arguments.length&&arguments[2]!==void 0?arguments[2]:!1,e=a.querySelector(c.regions.moredropdown),f=a.querySelector("."+c.classes.dropdowntoggle),g=b.querySelector("."+c.classes.navlink);b.setAttribute("prev-role",b.getAttribute("role"));b.setAttribute("role","menuitem");if(g.classList.contains(c.classes.active)){f.classList.add(c.classes.active)}g.classList.remove(c.classes.navlink);g.classList.add(c.classes.dropdownitem);if(d){e.prepend(b)}else{e.append(b)}},f=function(a,b){var d=a.querySelector(c.regions.morebutton),e=a.querySelector("."+c.classes.dropdowntoggle),f=b.querySelector("."+c.classes.dropdownitem);b.setAttribute("role",b.getAttribute("prev-role"));if(f){var g=f.getAttribute("role");if("menuitem"===g){f.removeAttribute("role")}}if(f.classList.contains(c.classes.active)){e.classList.remove(c.classes.active)}f.classList.remove(c.classes.dropdownitem);f.classList.add(c.classes.navlink);a.insertBefore(b,d)},g=function(a){if("children"in a){var f=a.querySelector(c.regions.morebutton),g=Array.from(a.children);g.forEach(function(b){if(!b.classList.contains(c.classes.dropdownmoremenu)&&"true"===b.dataset.forceintomoremenu){e(a,b,!1);if(f.classList.contains(c.classes.hidden)){f.classList.remove(c.classes.hidden)}}})}d(a);window.addEventListener("resize",function(){d(a)});var h=function(a){var b=a.target.parentNode.querySelector(c.attributes.menu);if(b){b.classList.toggle("show")}a.stopPropagation()};(0,b.default)("."+c.classes.dropdownmoremenu).on("show.bs.dropdown",function(){var b=a.querySelector(c.regions.moredropdown);b.querySelectorAll(".dropdown").forEach(function(a){a.removeEventListener("click",h,!0);a.addEventListener("click",h,!0)})})};a.default=g;return a.default});
|
||||
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.offsetHeight<b&&"true"!==d.dataset.forceintomoremenu){var e=c.removeChild(d);i(a,e)}});if(0===j.length){d.classList.add(f.classes.hidden)}}if(a.offsetHeight>b){g(a)}}a.parentNode.classList.add(f.classes.observed)},h=function(a,b){var c=2<arguments.length&&arguments[2]!==void 0?arguments[2]:!1,d=a.querySelector(f.regions.moredropdown),e=a.querySelector("."+f.classes.dropdowntoggle),g=b.querySelector("."+f.classes.navlink);b.setAttribute("prev-role",b.getAttribute("role"));b.setAttribute("role","menuitem");if(g.classList.contains(f.classes.active)){e.classList.add(f.classes.active)}g.classList.remove(f.classes.navlink);g.classList.add(f.classes.dropdownitem);if(c){d.prepend(b)}else{d.append(b)}},i=function(a,b){var c=a.querySelector(f.regions.morebutton),d=a.querySelector("."+f.classes.dropdowntoggle),e=b.querySelector("."+f.classes.dropdownitem);b.setAttribute("role",b.getAttribute("prev-role"));if(e){var g=e.getAttribute("role");if("menuitem"===g){e.removeAttribute("role")}}if(e.classList.contains(f.classes.active)){d.classList.remove(f.classes.active)}e.classList.remove(f.classes.dropdownitem);e.classList.add(f.classes.navlink);a.insertBefore(b,c)},j=function(a){a.firstElementChild.querySelector("[role=\"menuitem\"]").setAttribute("tabindex","0");if("children"in a){var d=a.querySelector(f.regions.morebutton),e=Array.from(a.children);e.forEach(function(b){if(!b.classList.contains(f.classes.dropdownmoremenu)&&"true"===b.dataset.forceintomoremenu){h(a,b,!1);if(d.classList.contains(f.classes.hidden)){d.classList.remove(f.classes.hidden)}}})}g(a);(0,c.default)(a);window.addEventListener("resize",function(){g(a);(0,c.default)(a)});var i=function(a){var b=a.target.parentNode.querySelector(f.attributes.menu);if(b){b.classList.toggle("show")}a.stopPropagation()};(0,b.default)("."+f.classes.dropdownmoremenu).on("show.bs.dropdown",function(){var b=a.querySelector(f.regions.moredropdown);b.querySelectorAll(".dropdown").forEach(function(a){a.removeEventListener("click",i,!0);a.addEventListener("click",i,!0)})})};a.default=j;return a.default});
|
||||
//# sourceMappingURL=moremenu.min.js.map
|
||||
|
File diff suppressed because one or more lines are too long
2
lib/amd/build/usermenu.min.js
vendored
2
lib/amd/build/usermenu.min.js
vendored
@ -1,2 +1,2 @@
|
||||
define ("core/usermenu",["exports","jquery"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);var c={userMenu:".usermenu",userMenuCarousel:".usermenu #usermenu-carousel",userMenuCarouselItem:".usermenu #usermenu-carousel .carousel-item",userMenuCarouselItemActive:".usermenu #usermenu-carousel .carousel-item.active",userMenuCarouselNavigationLink:".usermenu #usermenu-carousel .carousel-navigation-link"},d=function(){var a=document.querySelector(c.userMenu);(0,b.default)(c.userMenu).on("shown.bs.dropdown",function(){var b=document.querySelector(c.userMenuCarouselItemActive);b.focus();a.querySelectorAll(c.userMenuCarouselItem).forEach(function(a){if(!a.classList.contains("active")){a.style.width=b.offsetWidth+"px";a.style.height=b.offsetHeight+"px"}})});a.addEventListener("click",function(d){if(d.target.matches(c.userMenuCarouselNavigationLink)){d.stopPropagation();var e=d.target.dataset.carouselTargetId,f=a.querySelector("#"+e),g=Array.from(f.parentNode.children).indexOf(f);(0,b.default)(c.userMenuCarousel).carousel(g)}});(0,b.default)(c.userMenu).on("hide.bs.dropdown",function(){(0,b.default)(c.userMenuCarousel).carousel(0)});(0,b.default)(c.userMenuCarousel).on("slid.bs.carousel",function(){var b=a.querySelector(c.userMenuCarouselItemActive);b.focus()})};a.default={init:function init(){d()}};return a.default});
|
||||
define ("core/usermenu",["exports","jquery","core/key_codes"],function(a,b,c){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);var d={userMenu:".usermenu",userMenuCarousel:".usermenu #usermenu-carousel",userMenuCarouselItem:".usermenu #usermenu-carousel .carousel-item",userMenuCarouselItemActive:".usermenu #usermenu-carousel .carousel-item.active",userMenuCarouselNavigationLink:".usermenu #usermenu-carousel .carousel-navigation-link"},e=function(){var a=document.querySelector(d.userMenu);(0,b.default)(d.userMenu).on("shown.bs.dropdown",function(){var b=document.querySelector(d.userMenuCarouselItemActive);b.focus();a.querySelectorAll(d.userMenuCarouselItem).forEach(function(a){if(!a.classList.contains("active")){a.style.width=b.offsetWidth+"px";a.style.height=b.offsetHeight+"px"}})});a.addEventListener("click",function(a){if(a.target.matches(d.userMenuCarouselNavigationLink)){f(a)}});a.addEventListener("keydown",function(a){if((a.keyCode===c.space||a.keyCode===c.enter)&&a.target.matches(d.userMenuCarouselNavigationLink)){a.preventDefault();f(a)}});var f=function(c){c.stopPropagation();var e=c.target.dataset.carouselTargetId,f=a.querySelector("#"+e),g=Array.from(f.parentNode.children).indexOf(f);(0,b.default)(d.userMenuCarousel).carousel(g)};(0,b.default)(d.userMenu).on("hide.bs.dropdown",function(){(0,b.default)(d.userMenuCarousel).carousel(0)});(0,b.default)(d.userMenuCarousel).on("slid.bs.carousel",function(){var b=a.querySelector(d.userMenuCarouselItemActive);b.focus()})};a.default={init:function init(){e()}};return a.default});
|
||||
//# sourceMappingURL=usermenu.min.js.map
|
||||
|
File diff suppressed because one or more lines are too long
194
lib/amd/src/keyboard_navigation.js
Normal file
194
lib/amd/src/keyboard_navigation.js
Normal file
@ -0,0 +1,194 @@
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Keyboard initialization for a given html node.
|
||||
*
|
||||
* @module core/keyboard_navigation
|
||||
* @package core
|
||||
* @copyright 2021 Moodle
|
||||
* @author Mathew May <mathew.solutions>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
import {space, enter, arrowRight, arrowLeft, arrowDown, arrowUp, home, end} from 'core/key_codes';
|
||||
|
||||
const SELECTORS = {
|
||||
'menuitem': '[role="menuitem"]',
|
||||
'menu': '[role="menu"]'
|
||||
};
|
||||
|
||||
let openDropdownNode = null;
|
||||
|
||||
/**
|
||||
* Small helper function to check if a given node is null or not.
|
||||
*
|
||||
* @param {HTMLElement|null} item The node that we want to compare.
|
||||
* @param {HTMLElement} fallback Either the first node or final node that can be focused on.
|
||||
* @return {HTMLElement}
|
||||
*/
|
||||
const clickErrorHandler = (item, fallback) => {
|
||||
if (item !== null) {
|
||||
return item;
|
||||
} else {
|
||||
return fallback;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Defined event handling so we can remove listeners on nodes on resize etc.
|
||||
*
|
||||
* @param {event} e The triggering element and key presses etc.
|
||||
*/
|
||||
const listenerEvents = e => {
|
||||
const src = e.srcElement;
|
||||
const firstNode = e.currentTarget.firstElementChild;
|
||||
const lastNode = findUsableLastNode(e.currentTarget);
|
||||
|
||||
// Handling for dropdown escapes.
|
||||
// A bulk of the handling is already done by aria.js just add polish.
|
||||
if (src.classList.contains('dropdown-item')) {
|
||||
if (e.keyCode === arrowRight ||
|
||||
e.keyCode === arrowLeft) {
|
||||
e.preventDefault();
|
||||
if (openDropdownNode !== null) {
|
||||
openDropdownNode.parentElement.click();
|
||||
}
|
||||
}
|
||||
if (e.keyCode === space ||
|
||||
e.keyCode === enter) {
|
||||
e.preventDefault();
|
||||
|
||||
// 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');
|
||||
});
|
||||
|
||||
if (!src.parentElement.classList.contains('dropdown')) {
|
||||
src.click();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (e.keyCode === arrowRight) {
|
||||
e.preventDefault();
|
||||
setFocusNext(src, firstNode);
|
||||
}
|
||||
if (e.keyCode === arrowLeft) {
|
||||
e.preventDefault();
|
||||
setFocusPrev(src, lastNode);
|
||||
}
|
||||
// Let aria.js handle the dropdowns.
|
||||
if (e.keyCode === arrowUp ||
|
||||
e.keyCode === arrowDown) {
|
||||
openDropdownNode = src;
|
||||
e.preventDefault();
|
||||
}
|
||||
if (e.keyCode === home) {
|
||||
e.preventDefault();
|
||||
setFocusHomeEnd(firstNode);
|
||||
}
|
||||
if (e.keyCode === end) {
|
||||
e.preventDefault();
|
||||
setFocusHomeEnd(lastNode);
|
||||
}
|
||||
if (e.keyCode === space ||
|
||||
e.keyCode === enter) {
|
||||
e.preventDefault();
|
||||
// Aria.js handles dropdowns etc.
|
||||
if (!src.parentElement.classList.contains('dropdown')) {
|
||||
src.click();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The initial entry point that a given module can pass a HTMLElement.
|
||||
*
|
||||
* @param {HTMLElement} elementRoot The menu to add handlers upon.
|
||||
*/
|
||||
export default elementRoot => {
|
||||
elementRoot.removeEventListener('keydown', listenerEvents);
|
||||
elementRoot.addEventListener('keydown', listenerEvents);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle the focusing to the next element in the dropdown.
|
||||
*
|
||||
* @param {HTMLElement|null} currentNode The node that we want to take action on.
|
||||
* @param {HTMLElement} firstNode The backup node to focus as a last resort.
|
||||
*/
|
||||
const setFocusNext = (currentNode, firstNode) => {
|
||||
const nextListItem = currentNode.parentElement.nextElementSibling;
|
||||
const nodeToSelect = clickErrorHandler(nextListItem, firstNode);
|
||||
const menuItem = nodeToSelect.querySelector(SELECTORS.menuitem);
|
||||
menuItem.focus();
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle the focusing to the previous element in the dropdown.
|
||||
*
|
||||
* @param {HTMLElement|null} currentNode The node that we want to take action on.
|
||||
* @param {HTMLElement} lastNode The backup node to focus as a last resort.
|
||||
*/
|
||||
const setFocusPrev = (currentNode, lastNode) => {
|
||||
const nextListItem = currentNode.parentElement.previousElementSibling;
|
||||
const nodeToSelect = clickErrorHandler(nextListItem, lastNode);
|
||||
const menuItem = nodeToSelect.querySelector(SELECTORS.menuitem);
|
||||
menuItem.focus();
|
||||
};
|
||||
|
||||
/**
|
||||
* Focus on either the start or end of a nav list.
|
||||
*
|
||||
* @param {HTMLElement} node The element to focus on.
|
||||
*/
|
||||
const setFocusHomeEnd = node => {
|
||||
node.querySelector(SELECTORS.menuitem).focus();
|
||||
};
|
||||
|
||||
/**
|
||||
* We need to look within the menu to find a last node we can add focus to.
|
||||
*
|
||||
* @param {HTMLElement} elementRoot Menu to find a final child node within.
|
||||
* @return {HTMLElement}
|
||||
*/
|
||||
const findUsableLastNode = elementRoot => {
|
||||
const lastNode = elementRoot.lastElementChild;
|
||||
|
||||
// An example is the more menu existing but hidden on the page for the time being.
|
||||
if (!lastNode.classList.contains('d-none')) {
|
||||
return elementRoot.lastElementChild;
|
||||
} else {
|
||||
// Cast the HTMLCollection & reverse it.
|
||||
const extractedNodes = Array.prototype.map.call(elementRoot.children, node => {
|
||||
return node;
|
||||
}).reverse();
|
||||
|
||||
// Get rid of any nodes we can not set focus on.
|
||||
const nodesToUse = extractedNodes.filter((node => {
|
||||
if (!node.classList.contains('d-none')) {
|
||||
return node;
|
||||
}
|
||||
}));
|
||||
|
||||
// If we find no elements we can set focus on, fall back to the absolute first element.
|
||||
if (nodesToUse.length !== 0) {
|
||||
return nodesToUse[0];
|
||||
} else {
|
||||
return elementRoot.firstElementChild;
|
||||
}
|
||||
}
|
||||
};
|
@ -24,6 +24,7 @@
|
||||
*/
|
||||
|
||||
import $ from 'jquery';
|
||||
import keyboard_navigation from "core/keyboard_navigation";
|
||||
/**
|
||||
* Moremenu selectors.
|
||||
*/
|
||||
@ -176,6 +177,7 @@ const moveOutOfMoreDropdown = (menu, navNode) => {
|
||||
* @param {HTMLElement} menu The navbar moremenu.
|
||||
*/
|
||||
export default menu => {
|
||||
menu.firstElementChild.querySelector('[role="menuitem"]').setAttribute('tabindex', '0');
|
||||
// Pre-populate the "more" dropdown menu with navigation nodes which are set to be displayed in this menu
|
||||
// by default at all times.
|
||||
if ('children' in menu) {
|
||||
@ -196,10 +198,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);
|
||||
|
||||
// When the screen size changes make sure the menu still fits.
|
||||
window.addEventListener('resize', () => {
|
||||
autoCollapse(menu);
|
||||
keyboard_navigation(menu);
|
||||
});
|
||||
|
||||
const toggledropdown = e => {
|
||||
|
@ -24,6 +24,7 @@
|
||||
*/
|
||||
|
||||
import $ from 'jquery';
|
||||
import {space, enter} from 'core/key_codes';
|
||||
|
||||
/**
|
||||
* User menu constants.
|
||||
@ -65,21 +66,41 @@ const registerEventListeners = () => {
|
||||
|
||||
// Handle click event on the carousel navigation (control) links in the user menu.
|
||||
if (e.target.matches(selectors.userMenuCarouselNavigationLink)) {
|
||||
// By default the user menu dropdown element closes on a click event. This behaviour is not desirable
|
||||
// as we need to be able to navigate through the carousel items (submenus of the user menu) within the
|
||||
// user menu. Therefore, we need to prevent the propagation of this event and then manually call the
|
||||
// carousel transition.
|
||||
e.stopPropagation();
|
||||
// The id of the targeted carousel item.
|
||||
const targetedCarouselItemId = e.target.dataset.carouselTargetId;
|
||||
const targetedCarouselItem = userMenu.querySelector('#' + targetedCarouselItemId);
|
||||
// Get the position (index) of the targeted carousel item within the parent container element.
|
||||
const index = Array.from(targetedCarouselItem.parentNode.children).indexOf(targetedCarouselItem);
|
||||
// Navigate to the targeted carousel item.
|
||||
$(selectors.userMenuCarousel).carousel(index);
|
||||
carouselManagement(e);
|
||||
}
|
||||
});
|
||||
|
||||
userMenu.addEventListener('keydown', e => {
|
||||
// Handle keydown event on the carousel navigation (control) links in the user menu.
|
||||
if ((e.keyCode === space ||
|
||||
e.keyCode === enter) &&
|
||||
e.target.matches(selectors.userMenuCarouselNavigationLink)) {
|
||||
e.preventDefault();
|
||||
carouselManagement(e);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* We do the same actions here even if the caller was a click or button press.
|
||||
*
|
||||
* @param {Event} e The triggering element and key presses etc.
|
||||
*/
|
||||
const carouselManagement = e => {
|
||||
// By default the user menu dropdown element closes on a click event. This behaviour is not desirable
|
||||
// as we need to be able to navigate through the carousel items (submenus of the user menu) within the
|
||||
// user menu. Therefore, we need to prevent the propagation of this event and then manually call the
|
||||
// carousel transition.
|
||||
e.stopPropagation();
|
||||
// The id of the targeted carousel item.
|
||||
const targetedCarouselItemId = e.target.dataset.carouselTargetId;
|
||||
const targetedCarouselItem = userMenu.querySelector('#' + targetedCarouselItemId);
|
||||
// Get the position (index) of the targeted carousel item within the parent container element.
|
||||
const index = Array.from(targetedCarouselItem.parentNode.children).indexOf(targetedCarouselItem);
|
||||
// Navigate to the targeted carousel item.
|
||||
$(selectors.userMenuCarousel).carousel(index);
|
||||
|
||||
};
|
||||
|
||||
// Handle the 'hide.bs.dropdown' event (Fired when the dropdown menu is being closed).
|
||||
$(selectors.userMenu).on('hide.bs.dropdown', () => {
|
||||
// Reset the state once the user menu dropdown is closed and return back to the first (main) carousel item
|
||||
|
@ -61,7 +61,7 @@ class report_helper {
|
||||
|
||||
if (!empty($menu)) {
|
||||
$select = new url_select($menu, $activeurl, null, 'choosecoursereport');
|
||||
$select->set_label(get_string('report'), ['class' => 'accesshide']);
|
||||
$select->set_label(get_string('reporttype'), ['class' => 'accesshide']);
|
||||
$select->attributes['style'] = "margin-bottom: 1.5rem";
|
||||
$select->class .= " mb-4";
|
||||
echo $OUTPUT->render($select);
|
||||
|
@ -45,7 +45,7 @@
|
||||
}
|
||||
}}
|
||||
<nav class="moremenu">
|
||||
<ul id="moremenu-{{ uniqid }}" {{#tabs}}role="tablist" {{/tabs}}{{^tabs}}role="menu" {{/tabs}}class="nav more-nav {{navbarstyle}}">
|
||||
<ul id="moremenu-{{ uniqid }}" role="menubar" class="nav more-nav {{navbarstyle}}">
|
||||
{{#nodecollection}}
|
||||
{{#children}}
|
||||
{{> core/moremenu_children}}
|
||||
@ -54,8 +54,8 @@
|
||||
{{#nodearray}}
|
||||
{{> core/moremenu_children}}
|
||||
{{/nodearray}}
|
||||
<li class="nav-item dropdown dropdownmoremenu d-none" data-region="morebutton">
|
||||
<a class="dropdown-toggle nav-link" href="#" id="moremenu-dropdown{{ uniqid }}" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<li role="none" class="nav-item dropdown dropdownmoremenu d-none" data-region="morebutton">
|
||||
<a class="dropdown-toggle nav-link" href="#" id="moremenu-dropdown{{ uniqid }}" role="menuitem" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" tabindex="-1">
|
||||
{{#str}}moremenu, core{{/str}}
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu-right" data-region="moredropdown" aria-labelledby="moremenu-dropdown{{ uniqid }}" role="menu">
|
||||
|
@ -31,30 +31,31 @@
|
||||
}
|
||||
}}
|
||||
{{#haschildren}}
|
||||
<li class="dropdown nav-item" role="menuitem" data-forceintomoremenu="{{#forceintomoremenu}}true{{/forceintomoremenu}}{{^forceintomoremenu}}false{{/forceintomoremenu}}">
|
||||
<a class="dropdown-toggle nav-link" id="drop-down-{{uniqid}}" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" href="#" {{#title}}title="{{{title}}}"{{/title}} aria-controls="drop-down-menu-{{uniqid}}">
|
||||
<li class="dropdown nav-item" role="none" data-forceintomoremenu="{{#forceintomoremenu}}true{{/forceintomoremenu}}{{^forceintomoremenu}}false{{/forceintomoremenu}}">
|
||||
<a class="dropdown-toggle nav-link" id="drop-down-{{uniqid}}" role="menuitem" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" href="#" aria-controls="drop-down-menu-{{uniqid}}" tabindex="-1">
|
||||
{{{text}}}
|
||||
</a>
|
||||
<div class="dropdown-menu" role="menu" id="drop-down-menu-{{uniqid}}" aria-labelledby="drop-down-{{uniqid}}">
|
||||
{{#children}}
|
||||
{{^divider}}
|
||||
<a class="dropdown-item" role="menuitem" href="{{{url}}}" {{#title}}title="{{{title}}}"{{/title}}>{{{text}}}</a>
|
||||
<a class="dropdown-item" role="menuitem" href="{{{url}}}">{{{text}}}</a>
|
||||
{{/divider}}
|
||||
{{#divider}}
|
||||
<div class="dropdown-divider" role="presentation"></div>
|
||||
<div class="dropdown-divider"></div>
|
||||
{{/divider}}
|
||||
{{/children}}
|
||||
</div>
|
||||
</li>
|
||||
{{/haschildren}}
|
||||
{{^haschildren}}
|
||||
<li class="nav-item" role="{{#tab}}tab{{/tab}}{{^tab}}menuitem{{/tab}}" data-forceintomoremenu="{{#forceintomoremenu}}true{{/forceintomoremenu}}{{^forceintomoremenu}}false{{/forceintomoremenu}}">
|
||||
<li class="nav-item" role="none" data-forceintomoremenu="{{#forceintomoremenu}}true{{/forceintomoremenu}}{{^forceintomoremenu}}false{{/forceintomoremenu}}">
|
||||
{{#tab}}
|
||||
<a class="nav-link {{#isactive}}active{{/isactive}} {{#classes}}{{.}} {{/classes}}" href="{{tab}}" data-toggle="tab" aria-selected="false" tabindex="-1">{{{text}}}
|
||||
<a role="menuitem" class="nav-link {{#isactive}}active{{/isactive}} {{#classes}}{{.}} {{/classes}}" href="{{tab}}" data-toggle="tab" aria-selected="false" tabindex="-1">
|
||||
{{{text}}}
|
||||
</a>
|
||||
{{/tab}}
|
||||
{{^tab}}
|
||||
<a class="nav-link {{#isactive}}active{{/isactive}} {{#classes}}{{.}} {{/classes}}" href="{{{url}}}{{{action}}}" {{#isactive}}aria-current="true"{{/isactive}}>
|
||||
<a role="menuitem" class="nav-link {{#isactive}}active{{/isactive}} {{#classes}}{{.}} {{/classes}}" href="{{{url}}}{{{action}}}" {{#isactive}}aria-current="true"{{/isactive}} tabindex="-1">
|
||||
{{{text}}}
|
||||
</a>
|
||||
{{/tab}}
|
||||
|
@ -80,5 +80,5 @@
|
||||
{{title}}
|
||||
</a>
|
||||
{{/submenulink}}
|
||||
{{#divider}}<div class="dropdown-divider" role="presentation"></div>{{/divider}}
|
||||
{{#divider}}<div class="dropdown-divider"></div>{{/divider}}
|
||||
{{/items}}
|
||||
|
@ -41,7 +41,7 @@
|
||||
}}
|
||||
{{#items}}
|
||||
{{#link}}
|
||||
<a href="{{{url}}}" class="dropdown-item pl-5" role="menuitem" title="{{title}}" {{#isactive}}aria-current="true"{{/isactive}}>
|
||||
<a href="{{{url}}}" class="dropdown-item pl-5" role="menuitem" {{#isactive}}aria-current="true"{{/isactive}}>
|
||||
{{text}}
|
||||
</a>
|
||||
{{/link}}
|
||||
|
@ -70,10 +70,10 @@
|
||||
</span>
|
||||
<b class="caret"></b>
|
||||
</a>
|
||||
<div role="menu" aria-labelledby="user-menu-toggle" id="user-action-menu" class="dropdown-menu dropdown-menu-right">
|
||||
<div aria-label="{{#str}}user{{/str}}" id="user-action-menu" class="dropdown-menu dropdown-menu-right">
|
||||
<div id="usermenu-carousel" class="carousel slide" data-touch="false" data-interval="false" data-keyboard="false">
|
||||
<div class="carousel-inner">
|
||||
<div id="carousel-item-main" class="carousel-item active" tabindex="-1" aria-label="{{#str}}usermenu{{/str}}">
|
||||
<div id="carousel-item-main" class="carousel-item active" role="menu" tabindex="-1" aria-label="{{#str}}usermenu{{/str}}">
|
||||
{{> core/user_action_menu_items }}
|
||||
</div>
|
||||
{{#submenus}}
|
||||
|
@ -939,7 +939,14 @@ class behat_navigation extends behat_base {
|
||||
if ($parentnodes) {
|
||||
$tabname = behat_context_helper::escape($parentnodes[0]);
|
||||
$tabxpath = '//ul[@role=\'tablist\']/li/a[contains(normalize-space(.), ' . $tabname . ')]';
|
||||
if ($node = $this->getSession()->getPage()->find('xpath', $tabxpath)) {
|
||||
$menubarxpath = '//ul[@role=\'menubar\']/li/a[contains(normalize-space(.), ' . $tabname . ')]';
|
||||
$linkname = behat_context_helper::escape(get_string('moremenu'));
|
||||
$menubarmorexpath = '//ul[@role=\'menubar\']/li/a[contains(normalize-space(.), ' . $linkname . ')]';
|
||||
$tabnode = $this->getSession()->getPage()->find('xpath', $tabxpath);
|
||||
$menunode = $this->getSession()->getPage()->find('xpath', $menubarxpath);
|
||||
$menubuttons = $this->getSession()->getPage()->findAll('xpath', $menubarmorexpath);
|
||||
if ($tabnode || $menunode) {
|
||||
$node = is_object($tabnode) ? $tabnode : $menunode;
|
||||
if ($this->running_javascript()) {
|
||||
$this->execute('behat_general::i_click_on', [$node, 'NodeElement']);
|
||||
// Click on the tab and add 'active' tab to the xpath.
|
||||
@ -950,18 +957,17 @@ class behat_navigation extends behat_base {
|
||||
$xpath .= '//div[@id = ' . $tabid . ']';
|
||||
}
|
||||
array_shift($parentnodes);
|
||||
} else {
|
||||
$linkname = behat_context_helper::escape(get_string('moremenu'));
|
||||
$menuxpath = '//ul[@role=\'tablist\']/li/a[contains(normalize-space(.), ' . $linkname . ')]';
|
||||
$morebutton = $this->getSession()->getPage()->find('xpath', $menuxpath);
|
||||
if ($morebutton) {
|
||||
$this->execute('behat_general::i_click_on', [$morebutton, 'NodeElement']);
|
||||
$moreitemxpath = '//ul[@data-region=\'moredropdown\']/li/a[contains(normalize-space(.), ' . $tabname . ')]';
|
||||
if ($morenode = $this->getSession()->getPage()->find('xpath', $moreitemxpath)) {
|
||||
$this->execute('behat_general::i_click_on', [$morenode, 'NodeElement']);
|
||||
$xpath .= '//div[contains(@class,\'active\')]';
|
||||
array_shift($parentnodes);
|
||||
}
|
||||
} else if (count($menubuttons) > 0) {
|
||||
try {
|
||||
$this->execute('behat_general::i_click_on', [$menubuttons[1], 'NodeElement']);
|
||||
} catch (Exception $e) {
|
||||
$this->execute('behat_general::i_click_on', [$menubuttons[0], 'NodeElement']);
|
||||
}
|
||||
$moreitemxpath = '//ul[@data-region=\'moredropdown\']/li/a[contains(normalize-space(.), ' . $tabname . ')]';
|
||||
if ($morenode = $this->getSession()->getPage()->find('xpath', $moreitemxpath)) {
|
||||
$this->execute('behat_general::i_click_on', [$morenode, 'NodeElement']);
|
||||
$xpath .= '//div[contains(@class,\'active\')]';
|
||||
array_shift($parentnodes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -91,6 +91,7 @@ Feature: Saving, using and deleting feedback templates
|
||||
And I press "Use this template"
|
||||
And I set the field "Delete old items" to "1"
|
||||
And I press "Save changes"
|
||||
And I am on the "Another feedback in course 1" "feedback activity" page
|
||||
And I follow "Edit questions"
|
||||
And I should not see "What is your favourite subject"
|
||||
And I should see "this is a multiple choice 1"
|
||||
|
@ -43,7 +43,7 @@ Feature: A teacher can choose whether glossary entries require approval
|
||||
And I follow "Waiting approval"
|
||||
Then I should see "(this entry is currently hidden)"
|
||||
And I follow "Approve"
|
||||
And I click on "Test glossary name" "link" in the "page-header" "region"
|
||||
And I am on the "Test glossary name" "glossary activity" page
|
||||
Then I should see "Concept definition"
|
||||
And I log out
|
||||
# Check that the entry can now be viewed by students.
|
||||
|
@ -53,7 +53,7 @@
|
||||
<div role="menu" aria-labelledby="lang-menu-toggle" id="lang-action-menu" class="dropdown-menu dropdown-menu-right">
|
||||
{{#items}}
|
||||
{{#link}}
|
||||
<a href="{{{url}}}" class="dropdown-item pl-5" role="menuitem" title="{{title}}" {{#isactive}}aria-current="true"{{/isactive}}>
|
||||
<a href="{{{url}}}" class="dropdown-item pl-5" role="menuitem" {{#isactive}}aria-current="true"{{/isactive}}>
|
||||
{{text}}
|
||||
</a>
|
||||
{{/link}}
|
||||
|
Loading…
x
Reference in New Issue
Block a user