MDL-67285 core: YUI dialogues must use focuslock

YUI Dialogues were using an older method for locking focus to modals,
but this way conflicts with the way in which we lock focus for AMD
modules.

As a result, when an AMD dialogue launches a YUI dialogue the focus is
not correctly locked and it is not possible to focus on anything in the
YUI dialogue.

This includes a minor changes to the focuslock AMD module to ensure that
it is possible to loop the focus in both directions. Many of our older
YUI dialogues are themselves focusable. As a result we need to include
the lock region in the calculation when calculating the possible
descendants.
If we do not do so then the reverse looping does not work.
This commit is contained in:
Andrew Nicols
2019-11-25 10:35:08 +08:00
committed by Adrian Greeve
parent 5a108a3064
commit 751e4964b1
7 changed files with 56 additions and 10 deletions
+1 -1
View File
@@ -1,2 +1,2 @@
define ("core/local/aria/focuslock",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.untrapFocus=a.trapFocus=void 0;var b={focusable:"input:not([type=\"hidden\"]), a[href], button, textarea, select, [tabindex]"},c=[],d=[],e=[],f=null,g=!1,h=!1,i=function(a){if(g){return}var b=n();if(!b.parentNode){s()}if(b.contains(a.target)){f=a.target}else{j();if(f==document.activeElement){k()}f=document.activeElement}},j=function(){var a=n(),c=Array.from(a.querySelectorAll(b.focusable));return c.some(function(a){return m(a)})},k=function(){var a=n(),c=Array.from(a.querySelectorAll(b.focusable)).reverse();return c.some(function(a){return m(a)})},l=function(a){if(0<a.tabIndex||0===a.tabIndex&&null!==a.getAttribute("tabIndex")){return!0}if(a.disabled){return!1}switch(a.nodeName){case"A":return!!a.href&&"ignore"!=a.rel;case"INPUT":return"hidden"!=a.type&&"file"!=a.type;case"BUTTON":case"SELECT":case"TEXTAREA":return!0;default:return!1;}},m=function(a){if(!l(a)){return!1}g=!0;try{a.focus()}catch(a){}g=!1;return document.activeElement===a},n=function(){return c[c.length-1]},o=function(a){if(a===n()){return}c.push(a);var b=n(),f=document.createElement("div");f.tabIndex=0;f.style.position="fixed";f.style.top=0;f.style.left=0;var g=f.cloneNode();b.parentNode.insertBefore(g,b);d.push(g);var h=f.cloneNode();b.parentNode.insertBefore(h,b.nextSibling);e.push(h)},p=function(){c.pop();var a=e.pop();if(a){a.remove()}var b=d.pop();if(b){b.remove()}},q=function(){return!!c.length},r=function(a){o(a);if(!h){document.addEventListener("focus",i,!0)}if(!j()){var b=n(),c=b.tabIndex;b.tabIndex=0;m(b);b.tabIndex=c}f=document.activeElement;h=!0};a.trapFocus=r;var s=function(){p();if(q()){return}document.removeEventListener("focus",i,!0);f=null;g=!1;h=!1};a.untrapFocus=s});
define ("core/local/aria/focuslock",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.untrapFocus=a.trapFocus=void 0;var b={focusable:"input:not([type=\"hidden\"]), a[href], button, textarea, select, [tabindex]"},c=[],d=[],e=[],f=null,g=!1,h=!1,i=function(a){if(g){return}var b=n();if(!b.parentNode){s()}if(b.contains(a.target)){f=a.target}else{j();if(f==document.activeElement){k()}f=document.activeElement}},j=function(){var a=n(),c=Array.from(a.querySelectorAll(b.focusable));c.unshift(a);return c.some(function(a){return m(a)})},k=function(){var a=n(),c=Array.from(a.querySelectorAll(b.focusable)).reverse();c.push(a);return c.some(function(a){return m(a)})},l=function(a){if(0<a.tabIndex||0===a.tabIndex&&null!==a.getAttribute("tabIndex")){return!0}if(a.disabled){return!1}switch(a.nodeName){case"A":return!!a.href&&"ignore"!=a.rel;case"INPUT":return"hidden"!=a.type&&"file"!=a.type;case"BUTTON":case"SELECT":case"TEXTAREA":return!0;default:return!1;}},m=function(a){if(!l(a)){return!1}g=!0;try{a.focus()}catch(a){}g=!1;return document.activeElement===a},n=function(){return c[c.length-1]},o=function(a){if(a===n()){return}c.push(a);var b=n(),f=document.createElement("div");f.tabIndex=0;f.style.position="fixed";f.style.top=0;f.style.left=0;var g=f.cloneNode();b.parentNode.insertBefore(g,b);d.push(g);var h=f.cloneNode();b.parentNode.insertBefore(h,b.nextSibling);e.push(h)},p=function(){c.pop();var a=e.pop();if(a){a.remove()}var b=d.pop();if(b){b.remove()}},q=function(){return!!c.length},r=function(a){o(a);if(!h){document.addEventListener("focus",i,!0)}if(!j()){var b=n(),c=b.tabIndex;b.tabIndex=0;m(b);b.tabIndex=c}f=document.activeElement;h=!0};a.trapFocus=r;var s=function(){p();if(q()){return}document.removeEventListener("focus",i,!0);f=null;g=!1;h=!1};a.untrapFocus=s});
//# sourceMappingURL=focuslock.min.js.map
File diff suppressed because one or more lines are too long
+8
View File
@@ -91,6 +91,10 @@ const focusFirstDescendant = () => {
// to capture this.
// The use of Array.some just ensures that we stop as soon as we have a successful focus.
const focusableElements = Array.from(lockRegion.querySelectorAll(selectors.focusable));
// The lock region itself may be focusable. This is particularly true on Moodle's older dialogues.
// We must include it in the calculation of descendants to ensure that looping works correctly.
focusableElements.unshift(lockRegion);
return focusableElements.some(focusableElement => attemptFocus(focusableElement));
};
@@ -108,6 +112,10 @@ const focusLastDescendant = () => {
// to capture this.
// The use of Array.some just ensures that we stop as soon as we have a successful focus.
const focusableElements = Array.from(lockRegion.querySelectorAll(selectors.focusable)).reverse();
// The lock region itself may be focusable. This is particularly true on Moodle's older dialogues.
// We must include it in the calculation of descendants to ensure that looping works correctly.
focusableElements.push(lockRegion);
return focusableElements.some(focusableElement => attemptFocus(focusableElement));
};
@@ -149,6 +149,12 @@ Y.extend(DIALOGUE, Y.Panel, {
this.plug(Y.M.core.LockScroll);
}
// Remove the `focusoutside` listener.
// It conflicts with the ARIA focuslock manager which supports both YUI and non-YUI dialogues.
this.set('focusOn', Y.Array(this.get('focusOn')).filter(function(node) {
return node.eventName !== 'focusoutside';
}));
// Workaround upstream YUI bug http://yuilibrary.com/projects/yui3/ticket/2532507
// and allow setting of z-index in theme.
bb = this.get('boundingBox');
@@ -285,7 +291,10 @@ Y.extend(DIALOGUE, Y.Panel, {
this._orientationevent.detach();
this._orientationevent = null;
}
bb.detach('key', this.keyDelegation);
require(['core/local/aria/focuslock'], function(FocusLockManager) {
// Untrap focus when the dialogue is hidden.
FocusLockManager.untrapFocus();
});
if (this.get('modal')) {
// Hide this dialogue from screen readers.
@@ -305,7 +314,10 @@ Y.extend(DIALOGUE, Y.Panel, {
Y.one(titlebar).setStyle('cursor', 'move');
}
}
this.keyDelegation();
require(['core/local/aria/focuslock'], function(FocusLockManager) {
// Trap focus to the current bounding box.
FocusLockManager.trapFocus(this.get('boundingBox').getDOMNode());
}.bind(this));
// Only do accessibility hiding for modals because the ARIA spec
// says that all ARIA dialogues should be modal.
@@ -450,6 +462,7 @@ Y.extend(DIALOGUE, Y.Panel, {
* @method keyDelegation
*/
keyDelegation: function() {
Y.log('The keyDelegation function has been deprecated in favour of the AMD core/local/aria/focuslock module');
var bb = this.get('boundingBox');
bb.delegate('key', function(e) {
var target = e.target;
File diff suppressed because one or more lines are too long
@@ -149,6 +149,12 @@ Y.extend(DIALOGUE, Y.Panel, {
this.plug(Y.M.core.LockScroll);
}
// Remove the `focusoutside` listener.
// It conflicts with the ARIA focuslock manager which supports both YUI and non-YUI dialogues.
this.set('focusOn', Y.Array(this.get('focusOn')).filter(function(node) {
return node.eventName !== 'focusoutside';
}));
// Workaround upstream YUI bug http://yuilibrary.com/projects/yui3/ticket/2532507
// and allow setting of z-index in theme.
bb = this.get('boundingBox');
@@ -285,7 +291,10 @@ Y.extend(DIALOGUE, Y.Panel, {
this._orientationevent.detach();
this._orientationevent = null;
}
bb.detach('key', this.keyDelegation);
require(['core/local/aria/focuslock'], function(FocusLockManager) {
// Untrap focus when the dialogue is hidden.
FocusLockManager.untrapFocus();
});
if (this.get('modal')) {
// Hide this dialogue from screen readers.
@@ -305,7 +314,10 @@ Y.extend(DIALOGUE, Y.Panel, {
Y.one(titlebar).setStyle('cursor', 'move');
}
}
this.keyDelegation();
require(['core/local/aria/focuslock'], function(FocusLockManager) {
// Trap focus to the current bounding box.
FocusLockManager.trapFocus(this.get('boundingBox').getDOMNode());
}.bind(this));
// Only do accessibility hiding for modals because the ARIA spec
// says that all ARIA dialogues should be modal.
+15 -2
View File
@@ -119,6 +119,12 @@ Y.extend(DIALOGUE, Y.Panel, {
this.plug(Y.M.core.LockScroll);
}
// Remove the `focusoutside` listener.
// It conflicts with the ARIA focuslock manager which supports both YUI and non-YUI dialogues.
this.set('focusOn', Y.Array(this.get('focusOn')).filter(function(node) {
return node.eventName !== 'focusoutside';
}));
// Workaround upstream YUI bug http://yuilibrary.com/projects/yui3/ticket/2532507
// and allow setting of z-index in theme.
bb = this.get('boundingBox');
@@ -255,7 +261,10 @@ Y.extend(DIALOGUE, Y.Panel, {
this._orientationevent.detach();
this._orientationevent = null;
}
bb.detach('key', this.keyDelegation);
require(['core/local/aria/focuslock'], function(FocusLockManager) {
// Untrap focus when the dialogue is hidden.
FocusLockManager.untrapFocus();
});
if (this.get('modal')) {
// Hide this dialogue from screen readers.
@@ -275,7 +284,10 @@ Y.extend(DIALOGUE, Y.Panel, {
Y.one(titlebar).setStyle('cursor', 'move');
}
}
this.keyDelegation();
require(['core/local/aria/focuslock'], function(FocusLockManager) {
// Trap focus to the current bounding box.
FocusLockManager.trapFocus(this.get('boundingBox').getDOMNode());
}.bind(this));
// Only do accessibility hiding for modals because the ARIA spec
// says that all ARIA dialogues should be modal.
@@ -420,6 +432,7 @@ Y.extend(DIALOGUE, Y.Panel, {
* @method keyDelegation
*/
keyDelegation: function() {
Y.log('The keyDelegation function has been deprecated in favour of the AMD core/local/aria/focuslock module');
var bb = this.get('boundingBox');
bb.delegate('key', function(e) {
var target = e.target;