MDL-70792 aria: Dropdown menu keyboard interaction fixes
* Move the focus to the last menu item when the menu is displayed by pressing the Up arrow key. * When the menu is open and Tab/Shift-Tab is pressed, focus on the next/previous focusable element on the DOM instead of focusing back on the menu trigger. * Combine event handling for the dropdown menu trigger.
This commit is contained in:
Vendored
+1
-1
@@ -1,2 +1,2 @@
|
||||
define ("theme_boost/aria",["exports","jquery","core/pending"],function(a,b,c){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=d(b);c=d(c);function d(a){return a&&a.__esModule?a:{default:a}}var e=function(){var a=!1,d=function(){a=!0},e=function(){var b=a;a=!1;return b};document.addEventListener("keydown",function(a){if(a.target.matches("[data-toggle=\"dropdown\"]")){var b=a.key;if("ArrowUp"==b){d()}if(" "==b||"Enter"==b){a.preventDefault();a.target.click()}}});var f=function(a){setTimeout(function delayedFocus(b){a.focus();b.resolve()},50,new c.default("core/aria:delayed-focus"))};(0,b.default)(document).on("keydown","[data-toggle=\"dropdown\"]",function(a){var b=a.target.parentElement.querySelector("[role=\"menu\"]"),c=!1,d=!1;if(b){c=b.querySelectorAll("[role=\"menuitem\"]")}if(c&&0<c.length){if(e()){d=c[c.length-1]}else{d=c[0]}}if(d){f(d)}});document.addEventListener("keypress",function(a){if(a.target.matches(".dropdown [role=\"menu\"] [role=\"menuitem\"]")){var g=a.target.closest("[role=\"menu\"]");if(!g){return}var h=g.querySelectorAll("[role=\"menuitem\"]");if(!h){return}for(var b=a.key.toLowerCase(),c=0;c<h.length;c++){var d=h[c],e=d.text.trim().toLowerCase();if(0==e.indexOf(b)){f(d);break}}}});document.addEventListener("keydown",function(a){if(a.target.matches(".dropdown [role=\"menu\"] [role=\"menuitem\"]")){var b=a.key,c=!1,d=a.target.closest("[role=\"menu\"]");if(!d){return}var e=d.querySelectorAll("[role=\"menuitem\"]");if(!e){return}if("ArrowDown"==b){for(var g=0;g<e.length-1;g++){if(e[g]==a.target){c=e[g+1];break}}if(!c){c=e[0]}}else if("ArrowUp"==b){for(var h=1;h<e.length;h++){if(e[h]==a.target){c=e[h-1];break}}if(!c){c=e[e.length-1]}}else if("Home"==b){c=e[0]}else if("End"==b){c=e[e.length-1]}if(c){a.preventDefault();f(c)}}});(0,b.default)(".dropdown").on("hidden.bs.dropdown",function(a){var b=a.target.querySelector("[data-toggle=\"dropdown\"]");if(b){f(b)}})},f=function(){window.addEventListener("load",function(){var a=document.querySelectorAll("[data-aria-autofocus=\"true\"][role=\"alert\"]");Array.prototype.forEach.call(a,function(a){a.innerHTML+=" ";a.removeAttribute("data-aria-autofocus")})})},g=function(a){for(var c=a.target.closest("[role=\"tablist\"]"),d="vertical"==c.getAttribute("aria-orientation"),e=window.right_to_left(),f=d?"ArrowDown":e?"ArrowLeft":"ArrowRight",g=d?"ArrowUp":e?"ArrowRight":"ArrowLeft",h=Array.prototype.filter.call(c.querySelectorAll("[role=\"tab\"]"),function(a){return"none"!==getComputedStyle(a).display}),j=0;j<h.length;j++){h[j].index=j}switch(a.key){case f:a.preventDefault();if(a.target.index!==void 0&&h[a.target.index+1]){h[a.target.index+1].focus()}else{h[0].focus()}break;case g:a.preventDefault();if(a.target.index!==void 0&&h[a.target.index-1]){h[a.target.index-1].focus()}else{h[h.length-1].focus()}break;case"Home":a.preventDefault();h[0].focus();break;case"End":a.preventDefault();h[h.length-1].focus();break;case"Enter":case" ":a.preventDefault();(0,b.default)(a.target).tab("show");h.forEach(function(a){a.tabIndex=-1});a.target.tabIndex=0;}},h=function(){document.addEventListener("keydown",function(a){if(["ArrowUp","ArrowDown","ArrowLeft","ArrowRight","Home","End","Enter"," "].includes(a.key)){if(a.target.matches("[role=\"tablist\"] [role=\"tab\"]")){g(a)}}});document.addEventListener("click",function(a){if(a.target.matches("[role=\"tablist\"] [role=\"tab\"]")){var c=a.target.closest("[role=\"tablist\"]").querySelectorAll("[role=\"tab\"]");a.preventDefault();(0,b.default)(a.target).tab("show");c.forEach(function(a){a.tabIndex=-1});a.target.tabIndex=0}})};a.init=function init(){e();f();h()}});
|
||||
define ("theme_boost/aria",["exports","jquery","core/pending"],function(a,b,c){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=d(b);c=d(c);function d(a){return a&&a.__esModule?a:{default:a}}var e=function(){var a=!1,d=!0,e=function(){var b=0<arguments.length&&arguments[0]!==void 0?arguments[0]:!0;a=b},f=function(){var b=a;a=!1;return b},g=function(a){setTimeout(function delayedFocus(b){a.focus();b.resolve()},50,new c.default("core/aria:delayed-focus"))},h=function(a){var b=a.key,c=!1;if(" "===b||"Enter"===b){c=!0;a.preventDefault();a.target.click()}if("ArrowUp"===b||"ArrowDown"===b){c=!0}if("Tab"===b){d=!1}if(!c){return}var h=a.target.parentElement.querySelector("[role=\"menu\"]"),i=!1,j=!1;if(h){i=h.querySelectorAll("[role=\"menuitem\"]")}if(i&&0<i.length){if("ArrowUp"===b){e()}else{e(!1)}if(f()){j=i[i.length-1]}else{j=i[0]}}if(j){g(j)}};document.addEventListener("keypress",function(a){if(a.target.matches(".dropdown [role=\"menu\"] [role=\"menuitem\"]")){var f=a.target.closest("[role=\"menu\"]");if(!f){return}var h=f.querySelectorAll("[role=\"menuitem\"]");if(!h){return}for(var b=a.key.toLowerCase(),c=0;c<h.length;c++){var d=h[c],e=d.text.trim().toLowerCase();if(0==e.indexOf(b)){g(d);break}}}});document.addEventListener("keydown",function(a){if(a.target.matches("[data-toggle=\"dropdown\"]")){h(a)}if(a.target.matches(".dropdown [role=\"menu\"] [role=\"menuitem\"]")){var b=a.key,c=!1,e=a.target.closest("[role=\"menu\"]");if(!e){return}var f=e.querySelectorAll("[role=\"menuitem\"]");if(!f){return}if("ArrowDown"==b){for(var j=0;j<f.length-1;j++){if(f[j]==a.target){c=f[j+1];break}}if(!c){c=f[0]}}else if("ArrowUp"==b){for(var i=1;i<f.length;i++){if(f[i]==a.target){c=f[i-1];break}}if(!c){c=f[f.length-1]}}else if("Home"==b){c=f[0]}else if("End"==b){c=f[f.length-1]}else if("Tab"==b){d=!1}if(c){a.preventDefault();g(c)}}});(0,b.default)(".dropdown").on("hidden.bs.dropdown",function(a){var b=a.target.querySelector("[data-toggle=\"dropdown\"]");if(b&&d){g(b)}d=!0})},f=function(){window.addEventListener("load",function(){var a=document.querySelectorAll("[data-aria-autofocus=\"true\"][role=\"alert\"]");Array.prototype.forEach.call(a,function(a){a.innerHTML+=" ";a.removeAttribute("data-aria-autofocus")})})},g=function(a){for(var c=a.target.closest("[role=\"tablist\"]"),d="vertical"==c.getAttribute("aria-orientation"),e=window.right_to_left(),f=d?"ArrowDown":e?"ArrowLeft":"ArrowRight",g=d?"ArrowUp":e?"ArrowRight":"ArrowLeft",h=Array.prototype.filter.call(c.querySelectorAll("[role=\"tab\"]"),function(a){return"none"!==getComputedStyle(a).display}),j=0;j<h.length;j++){h[j].index=j}switch(a.key){case f:a.preventDefault();if(a.target.index!==void 0&&h[a.target.index+1]){h[a.target.index+1].focus()}else{h[0].focus()}break;case g:a.preventDefault();if(a.target.index!==void 0&&h[a.target.index-1]){h[a.target.index-1].focus()}else{h[h.length-1].focus()}break;case"Home":a.preventDefault();h[0].focus();break;case"End":a.preventDefault();h[h.length-1].focus();break;case"Enter":case" ":a.preventDefault();(0,b.default)(a.target).tab("show");h.forEach(function(a){a.tabIndex=-1});a.target.tabIndex=0;}},h=function(){document.addEventListener("keydown",function(a){if(["ArrowUp","ArrowDown","ArrowLeft","ArrowRight","Home","End","Enter"," "].includes(a.key)){if(a.target.matches("[role=\"tablist\"] [role=\"tab\"]")){g(a)}}});document.addEventListener("click",function(a){if(a.target.matches("[role=\"tablist\"] [role=\"tab\"]")){var c=a.target.closest("[role=\"tablist\"]").querySelectorAll("[role=\"tab\"]");a.preventDefault();(0,b.default)(a.target).tab("show");c.forEach(function(a){a.tabIndex=-1});a.target.tabIndex=0}})};a.init=function init(){e();f();h()}});
|
||||
//# sourceMappingURL=aria.min.js.map
|
||||
|
||||
File diff suppressed because one or more lines are too long
+57
-29
@@ -29,8 +29,9 @@ import Pending from 'core/pending';
|
||||
*/
|
||||
const dropdownFix = () => {
|
||||
let focusEnd = false;
|
||||
const setFocusEnd = () => {
|
||||
focusEnd = true;
|
||||
let focusBackToTrigger = true;
|
||||
const setFocusEnd = (end = true) => {
|
||||
focusEnd = end;
|
||||
};
|
||||
const getFocusEnd = () => {
|
||||
const result = focusEnd;
|
||||
@@ -38,27 +39,6 @@ const dropdownFix = () => {
|
||||
return result;
|
||||
};
|
||||
|
||||
// Special handling for "up" keyboard control.
|
||||
document.addEventListener('keydown', e => {
|
||||
if (e.target.matches('[data-toggle="dropdown"]')) {
|
||||
const trigger = e.key;
|
||||
|
||||
// Up key opens the menu at the end.
|
||||
if (trigger == 'ArrowUp') {
|
||||
// Focus the end of the menu, not the beginning.
|
||||
setFocusEnd();
|
||||
}
|
||||
|
||||
// Space key or Enter key opens the menu.
|
||||
if (trigger == ' ' || trigger == 'Enter') {
|
||||
// Cancel random scroll.
|
||||
e.preventDefault();
|
||||
// Open the menu instead.
|
||||
e.target.click();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Special handling for navigation keys when menu is open.
|
||||
const shiftFocus = element => {
|
||||
const delayedFocus = pendingPromise => {
|
||||
@@ -68,10 +48,36 @@ const dropdownFix = () => {
|
||||
setTimeout(delayedFocus, 50, new Pending('core/aria:delayed-focus'));
|
||||
};
|
||||
|
||||
// We only want to set focus when users access the dropdown via keyboard as per
|
||||
// guidelines defined in w3 aria practices 1.1 menu-button.
|
||||
$(document).on('keydown', '[data-toggle="dropdown"]', (e) => {
|
||||
// We need to focus on the first menuitem.
|
||||
// Event handling for the dropdown menu button.
|
||||
const handleMenuButton = e => {
|
||||
const trigger = e.key;
|
||||
let fixFocus = false;
|
||||
|
||||
// Space key or Enter key opens the menu.
|
||||
if (trigger === ' ' || trigger === 'Enter') {
|
||||
fixFocus = true;
|
||||
// Cancel random scroll.
|
||||
e.preventDefault();
|
||||
// Open the menu instead.
|
||||
e.target.click();
|
||||
}
|
||||
|
||||
// Up and Down keys also open the menu.
|
||||
if (trigger === 'ArrowUp' || trigger === 'ArrowDown') {
|
||||
fixFocus = true;
|
||||
}
|
||||
|
||||
// Pressing tab on the menu button should focus on the next element in the DOM and not back to the menu trigger.
|
||||
if (trigger === 'Tab') {
|
||||
focusBackToTrigger = false;
|
||||
}
|
||||
|
||||
if (!fixFocus) {
|
||||
// No need to fix the focus. Return early.
|
||||
return;
|
||||
}
|
||||
|
||||
// Fix the focus on the menu items when the menu is opened.
|
||||
const menu = e.target.parentElement.querySelector('[role="menu"]');
|
||||
let menuItems = false;
|
||||
let foundMenuItem = false;
|
||||
@@ -80,6 +86,13 @@ const dropdownFix = () => {
|
||||
menuItems = menu.querySelectorAll('[role="menuitem"]');
|
||||
}
|
||||
if (menuItems && menuItems.length > 0) {
|
||||
// Up key opens the menu at the end.
|
||||
if (trigger === 'ArrowUp') {
|
||||
setFocusEnd();
|
||||
} else {
|
||||
setFocusEnd(false);
|
||||
}
|
||||
|
||||
if (getFocusEnd()) {
|
||||
foundMenuItem = menuItems[menuItems.length - 1];
|
||||
} else {
|
||||
@@ -87,10 +100,12 @@ const dropdownFix = () => {
|
||||
foundMenuItem = menuItems[0];
|
||||
}
|
||||
}
|
||||
|
||||
if (foundMenuItem) {
|
||||
shiftFocus(foundMenuItem);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Search for menu items by finding the first item that has
|
||||
// text starting with the typed character (case insensitive).
|
||||
document.addEventListener('keypress', e => {
|
||||
@@ -119,6 +134,13 @@ const dropdownFix = () => {
|
||||
|
||||
// Keyboard navigation for arrow keys, home and end keys.
|
||||
document.addEventListener('keydown', e => {
|
||||
|
||||
// We only want to set focus when users access the dropdown via keyboard as per
|
||||
// guidelines defined in w3 aria practices 1.1 menu-button.
|
||||
if (e.target.matches('[data-toggle="dropdown"]')) {
|
||||
handleMenuButton(e);
|
||||
}
|
||||
|
||||
if (e.target.matches('.dropdown [role="menu"] [role="menuitem"]')) {
|
||||
const trigger = e.key;
|
||||
let next = false;
|
||||
@@ -164,7 +186,11 @@ const dropdownFix = () => {
|
||||
} else if (trigger == 'End') {
|
||||
// End key.
|
||||
next = menuItems[menuItems.length - 1];
|
||||
} else if (trigger == 'Tab') {
|
||||
// Pressing tab in the menu should focus on the next element in the DOM and not back to the menu trigger.
|
||||
focusBackToTrigger = false;
|
||||
}
|
||||
|
||||
// Variable next is set if we do want to act on the keypress.
|
||||
if (next) {
|
||||
e.preventDefault();
|
||||
@@ -177,9 +203,11 @@ const dropdownFix = () => {
|
||||
$('.dropdown').on('hidden.bs.dropdown', e => {
|
||||
// We need to focus on the menu trigger.
|
||||
const trigger = e.target.querySelector('[data-toggle="dropdown"]');
|
||||
if (trigger) {
|
||||
if (trigger && focusBackToTrigger) {
|
||||
shiftFocus(trigger);
|
||||
}
|
||||
// Reset flag to focus back to the menu trigger.
|
||||
focusBackToTrigger = true;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user