Merge branch 'MDL-84732-m500' of https://github.com/sammarshallou/moodle into MOODLE_500_STABLE

This commit is contained in:
Huong Nguyen
2025-06-04 10:53:19 +07:00
4 changed files with 33 additions and 6 deletions
+1 -1
View File
@@ -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&&(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}));
*/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=(()=>{const allFocusable=document.querySelectorAll(_selectors.default.elements.focusable);return Array.from(allFocusable).filter((focusable=>!!focusable.offsetParent))})();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
+15 -1
View File
@@ -85,6 +85,20 @@ const lockHandler = event => {
}
};
/**
* Gets all the focusable elements in the document that are not set to display:none. This is useful
* because sometimes, a nested modal dialog may be left in the DOM but set to display:none, and you
* can't actually focus display:none elements.
*
* @returns {HTMLElement[]} All focusable elements that aren't display:none, in DOM order
*/
const getAllFocusableElements = () => {
const allFocusable = document.querySelectorAll(Selectors.elements.focusable);
// The offsetParent check is a well-perfoming way to ensure that an element in the document
// does not have display:none.
return Array.from(allFocusable).filter(focusable => !!focusable.offsetParent);
};
/**
* Catch event for any keydown during focus lock.
*
@@ -102,7 +116,7 @@ const keyDownHandler = event => {
if (!event.shiftKey) {
// Have they already focused the last focusable element in the document?
const allFocusable = document.querySelectorAll(Selectors.elements.focusable);
const allFocusable = getAllFocusableElements();
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.
+16 -3
View File
@@ -4,8 +4,7 @@ Feature: Focus lock in modal popups
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
Background:
Given the following "courses" exist:
| fullname | shortname |
| Course 1 | C1 |
@@ -16,9 +15,11 @@ Feature: Focus lock in modal popups
And I follow "Add discussion topic"
And I click on "Image" "button"
@javascript
Scenario: Tab cycles through elements in modal, using image popup in Tiny as an example
# 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
When I press the tab key
And I press the tab key
And I press the tab key
And I press the tab key
@@ -33,3 +34,15 @@ Feature: Focus lock in modal popups
And I press the shift tab key
And I press the shift tab key
And the focused element is "Browse repositories" "button"
@javascript
Scenario: Focus continues to be locked to modal even after closing nested modal
# Open 'Browse repositories' nested modal, then close it again.
When I press "Browse repositories"
And I click on "Close" "button" in the "File picker" "dialogue"
And the focused element is "Browse repositories" "button"
# Focus should still wrap around to the start of the image modal.
When I press the tab key
And I press the tab key
Then the focused element is "Close" "button" in the "Insert image" "dialogue"