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:
committed by
Adrian Greeve
parent
5a108a3064
commit
751e4964b1
+1
-1
@@ -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
@@ -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));
|
||||
};
|
||||
|
||||
|
||||
+15
-2
@@ -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;
|
||||
|
||||
+2
-2
File diff suppressed because one or more lines are too long
+14
-2
@@ -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
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user