Merge branch 'MDL-83809-main' of https://github.com/sammarshallou/moodle
This commit is contained in:
+1
-1
@@ -8,6 +8,6 @@ define("core/local/aria/focuslock",["exports","./selectors"],(function(_exports,
|
||||
* @module core/local/aria/focuslock
|
||||
* @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.untrapFocus=_exports.trapFocus=void 0,_selectors=(obj=_selectors)&&obj.__esModule?obj:{default:obj};const lockRegionStack=[],initialFocusElementStack=[],finalFocusElementStack=[];let lastFocus=null,ignoreFocusChanges=!1,isLocked=!1;const lockHandler=event=>{if(ignoreFocusChanges)return;let lockRegion=getCurrentLockRegion();for(;lockRegion&&!document.contains(lockRegion);)untrapFocus(),lockRegion=getCurrentLockRegion();lockRegion&&(lockRegion.contains(event.target)?lastFocus=event.target:(focusFirstDescendant(),lastFocus==document.activeElement&&focusLastDescendant(),lastFocus=document.activeElement))},focusFirstDescendant=()=>{const lockRegion=getCurrentLockRegion(),focusableElements=Array.from(lockRegion.querySelectorAll(_selectors.default.elements.focusable));return focusableElements.unshift(lockRegion),focusableElements.some((focusableElement=>attemptFocus(focusableElement)))},focusLastDescendant=()=>{const lockRegion=getCurrentLockRegion(),focusableElements=Array.from(lockRegion.querySelectorAll(_selectors.default.elements.focusable)).reverse();return focusableElements.push(lockRegion),focusableElements.some((focusableElement=>attemptFocus(focusableElement)))},attemptFocus=focusTarget=>{if(!(focusTarget=>{if(focusTarget.tabIndex>0||0===focusTarget.tabIndex&&null!==focusTarget.getAttribute("tabIndex"))return!0;if(focusTarget.disabled)return!1;switch(focusTarget.nodeName){case"A":return!!focusTarget.href&&"ignore"!=focusTarget.rel;case"INPUT":return"hidden"!=focusTarget.type&&"file"!=focusTarget.type;case"BUTTON":case"SELECT":case"TEXTAREA":return!0;default:return!1}})(focusTarget))return!1;ignoreFocusChanges=!0;try{focusTarget.focus()}catch(e){}return ignoreFocusChanges=!1,document.activeElement===focusTarget},getCurrentLockRegion=()=>lockRegionStack[lockRegionStack.length-1];_exports.trapFocus=newLockRegion=>{if((newLockRegion=>{if(newLockRegion===getCurrentLockRegion())return;lockRegionStack.push(newLockRegion);const currentLockRegion=getCurrentLockRegion(),element=document.createElement("div");element.tabIndex=0,element.style.position="fixed",element.style.top=0,element.style.left=0;const initialNode=element.cloneNode();currentLockRegion.parentNode.insertBefore(initialNode,currentLockRegion),initialFocusElementStack.push(initialNode);const finalNode=element.cloneNode();currentLockRegion.parentNode.insertBefore(finalNode,currentLockRegion.nextSibling),finalFocusElementStack.push(finalNode)})(newLockRegion),isLocked||document.addEventListener("focus",lockHandler,!0),!focusFirstDescendant()){const currentLockRegion=getCurrentLockRegion(),originalRegionTabIndex=currentLockRegion.tabIndex;currentLockRegion.tabIndex=0,attemptFocus(currentLockRegion),currentLockRegion.tabIndex=originalRegionTabIndex}lastFocus=document.activeElement,isLocked=!0};const untrapFocus=()=>{(()=>{lockRegionStack.pop();const finalNode=finalFocusElementStack.pop();finalNode&&finalNode.remove();const initialNode=initialFocusElementStack.pop();initialNode&&initialNode.remove()})(),lockRegionStack.length||(document.removeEventListener("focus",lockHandler,!0),lastFocus=null,ignoreFocusChanges=!1,isLocked=!1)};_exports.untrapFocus=untrapFocus}));
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.untrapFocus=_exports.trapFocus=void 0,_selectors=(obj=_selectors)&&obj.__esModule?obj:{default:obj};const lockRegionStack=[],initialFocusElementStack=[],finalFocusElementStack=[];let lastFocus=null,ignoreFocusChanges=!1,isLocked=!1;const lockHandler=event=>{if(ignoreFocusChanges)return;let lockRegion=getCurrentLockRegion();for(;lockRegion&&!document.contains(lockRegion);)untrapFocus(),lockRegion=getCurrentLockRegion();lockRegion&&(event&&lockRegion.contains(event.target)?lastFocus=event.target:(focusFirstDescendant(),lastFocus==document.activeElement&&focusLastDescendant(),lastFocus=document.activeElement))},keyDownHandler=event=>{if("Tab"===event.key&&getCurrentLockRegion())if(event.shiftKey){const firstFocusable=getCurrentLockRegion().querySelector(_selectors.default.elements.focusable);document.activeElement===firstFocusable&&(lockHandler(),event.preventDefault())}else{const allFocusable=document.querySelectorAll(_selectors.default.elements.focusable);document.activeElement===allFocusable[allFocusable.length-1]&&(lockHandler(),event.preventDefault())}},focusFirstDescendant=()=>{const lockRegion=getCurrentLockRegion(),focusableElements=Array.from(lockRegion.querySelectorAll(_selectors.default.elements.focusable));return focusableElements.unshift(lockRegion),focusableElements.some((focusableElement=>attemptFocus(focusableElement)))},focusLastDescendant=()=>{const lockRegion=getCurrentLockRegion(),focusableElements=Array.from(lockRegion.querySelectorAll(_selectors.default.elements.focusable)).reverse();return focusableElements.push(lockRegion),focusableElements.some((focusableElement=>attemptFocus(focusableElement)))},attemptFocus=focusTarget=>{if(!(focusTarget=>{if(focusTarget.tabIndex>0||0===focusTarget.tabIndex&&null!==focusTarget.getAttribute("tabIndex"))return!0;if(focusTarget.disabled)return!1;switch(focusTarget.nodeName){case"A":return!!focusTarget.href&&"ignore"!=focusTarget.rel;case"INPUT":return"hidden"!=focusTarget.type&&"file"!=focusTarget.type;case"BUTTON":case"SELECT":case"TEXTAREA":return!0;default:return!1}})(focusTarget))return!1;ignoreFocusChanges=!0;try{focusTarget.focus()}catch(e){}return ignoreFocusChanges=!1,document.activeElement===focusTarget},getCurrentLockRegion=()=>lockRegionStack[lockRegionStack.length-1];_exports.trapFocus=newLockRegion=>{if((newLockRegion=>{if(newLockRegion===getCurrentLockRegion())return;lockRegionStack.push(newLockRegion);const currentLockRegion=getCurrentLockRegion(),element=document.createElement("div");element.tabIndex=0,element.style.position="fixed",element.style.top=0,element.style.left=0;const initialNode=element.cloneNode();currentLockRegion.parentNode.insertBefore(initialNode,currentLockRegion),initialFocusElementStack.push(initialNode);const finalNode=element.cloneNode();currentLockRegion.parentNode.insertBefore(finalNode,currentLockRegion.nextSibling),finalFocusElementStack.push(finalNode)})(newLockRegion),isLocked||(document.addEventListener("focus",lockHandler,!0),document.addEventListener("keydown",keyDownHandler,!0)),!focusFirstDescendant()){const currentLockRegion=getCurrentLockRegion(),originalRegionTabIndex=currentLockRegion.tabIndex;currentLockRegion.tabIndex=0,attemptFocus(currentLockRegion),currentLockRegion.tabIndex=originalRegionTabIndex}lastFocus=document.activeElement,isLocked=!0};const untrapFocus=()=>{(()=>{lockRegionStack.pop();const finalNode=finalFocusElementStack.pop();finalNode&&finalNode.remove();const initialNode=initialFocusElementStack.pop();initialNode&&initialNode.remove()})(),lockRegionStack.length||(document.removeEventListener("focus",lockHandler,!0),document.removeEventListener("keydown",keyDownHandler,!0),lastFocus=null,ignoreFocusChanges=!1,isLocked=!1)};_exports.untrapFocus=untrapFocus}));
|
||||
|
||||
//# sourceMappingURL=focuslock.min.js.map
|
||||
File diff suppressed because one or more lines are too long
@@ -46,8 +46,11 @@ let isLocked = false;
|
||||
* This gives us a solution which supports focus locking of any kind, which loops in both directions, and which
|
||||
* prevents the lock from escaping the modal entirely.
|
||||
*
|
||||
* If no event is supplied then this function can be used to focus the first element in the lock region, or the
|
||||
* last element if the first element is already focused.
|
||||
*
|
||||
* @method
|
||||
* @param {Event} event The event from the focus change
|
||||
* @param {Event} [event] The event from the focus change
|
||||
*/
|
||||
const lockHandler = event => {
|
||||
if (ignoreFocusChanges) {
|
||||
@@ -71,7 +74,7 @@ const lockHandler = event => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (lockRegion.contains(event.target)) {
|
||||
if (event && lockRegion.contains(event.target)) {
|
||||
lastFocus = event.target;
|
||||
} else {
|
||||
focusFirstDescendant();
|
||||
@@ -82,6 +85,46 @@ const lockHandler = event => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Catch event for any keydown during focus lock.
|
||||
*
|
||||
* This is used to detect situations when the user would be tabbing out to the browser UI. In that
|
||||
* case, no 'focus' event is generated, so we need to trap it before it happens via the keydown
|
||||
* event.
|
||||
*
|
||||
* @param {KeyboardEvent} event
|
||||
*/
|
||||
const keyDownHandler = event => {
|
||||
// We only care about Tab keypresses and only if there is a current lock region.
|
||||
if (event.key !== 'Tab' || !getCurrentLockRegion()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!event.shiftKey) {
|
||||
// Have they already focused the last focusable element in the document?
|
||||
const allFocusable = document.querySelectorAll(Selectors.elements.focusable);
|
||||
if (document.activeElement === allFocusable[allFocusable.length - 1]) {
|
||||
// When the last thing is focused, focus would go to browser UI next, instead use
|
||||
// lockHandler to put focus back on the first element in lock region.
|
||||
lockHandler();
|
||||
event.preventDefault();
|
||||
}
|
||||
} else {
|
||||
// Have they already focused the first focusable element in the lock region?
|
||||
const lockRegion = getCurrentLockRegion();
|
||||
const firstFocusable = lockRegion.querySelector(Selectors.elements.focusable);
|
||||
if (document.activeElement === firstFocusable) {
|
||||
// When the first thing is focused, use lockHandler which will focus the last element
|
||||
// in lock region. We do this here rather than using lockHandler to get the focus event
|
||||
// because (a) there would be no focus event if the current element is the first in
|
||||
// document, and (b) temporarily focusing outside the region could result in unexpected
|
||||
// scrolling.
|
||||
lockHandler();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Focus the first descendant of the current lock region.
|
||||
*
|
||||
@@ -276,6 +319,7 @@ export const trapFocus = newLockRegion => {
|
||||
if (!isLocked) {
|
||||
// Add the focus handler.
|
||||
document.addEventListener('focus', lockHandler, true);
|
||||
document.addEventListener('keydown', keyDownHandler, true);
|
||||
}
|
||||
|
||||
// Attempt to focus on the first item in the lock region.
|
||||
@@ -312,6 +356,7 @@ export const untrapFocus = () => {
|
||||
}
|
||||
|
||||
document.removeEventListener('focus', lockHandler, true);
|
||||
document.removeEventListener('keydown', keyDownHandler, true);
|
||||
|
||||
lastFocus = null;
|
||||
ignoreFocusChanges = false;
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -45,6 +45,7 @@ export const displayFilepicker = (editor, filetype) => new Promise((resolve, rej
|
||||
const options = {
|
||||
...configuration,
|
||||
formcallback: resolve,
|
||||
previousActiveElement: document.activeElement,
|
||||
};
|
||||
M.core_filepicker.show(Y, options);
|
||||
return;
|
||||
|
||||
@@ -17,6 +17,14 @@ Feature: Use the TinyMCE editor to upload an image
|
||||
And I click on "Browse repositories" "button" in the "Insert image" "dialogue"
|
||||
Then "File picker" "dialogue" should exist
|
||||
|
||||
Scenario: Focus returns to the correct location after closing a nested FilePicker
|
||||
Given I log in as "admin"
|
||||
And I open my profile in edit mode
|
||||
When I click on the "Image" button for the "Description" TinyMCE editor
|
||||
And I press "Browse repositories"
|
||||
When I press the escape key
|
||||
Then the focused element is "Browse repositories" "button"
|
||||
|
||||
@_file_upload @test_tiny
|
||||
Scenario: Browsing repositories in the TinyMCE editor shows the FilePicker and upload url image
|
||||
Given I log in as "admin"
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
@core
|
||||
Feature: Focus lock in modal popups
|
||||
In order to navigate a modal popup with keyboard
|
||||
As a user
|
||||
The tab key should cycle through elements in the form and not go outside it
|
||||
|
||||
@javascript
|
||||
Scenario: Tab cycles through elements in modal, using image popup in Tiny as an example
|
||||
Given the following "courses" exist:
|
||||
| fullname | shortname |
|
||||
| Course 1 | C1 |
|
||||
And the following "activities" exist:
|
||||
| activity | name | course | idnumber |
|
||||
| forum | Test forum | C1 | forum1 |
|
||||
And I am on the "forum1" "Activity" page logged in as "admin"
|
||||
And I follow "Add discussion topic"
|
||||
And I click on "Image" "button"
|
||||
|
||||
# Repeated tabs just to get to the last element. This may need changing if controls are added
|
||||
# or removed to the form.
|
||||
And I press the tab key
|
||||
And I press the tab key
|
||||
And I press the tab key
|
||||
And I press the tab key
|
||||
And I press the tab key
|
||||
And the focused element is "Browse repositories" "button"
|
||||
|
||||
# Tab past last element should go back to the first one, which is the modal itself, then the close button.
|
||||
And I press the tab key
|
||||
And I press the tab key
|
||||
Then the focused element is "Close" "button" in the "Insert image" "dialogue"
|
||||
|
||||
And I press the shift tab key
|
||||
And I press the shift tab key
|
||||
And the focused element is "Browse repositories" "button"
|
||||
Reference in New Issue
Block a user