mirror of
https://github.com/moodle/moodle.git
synced 2025-08-05 08:56:36 +02:00
MDL-68390 aria: Add new core_aria module
This commit is contained in:
parent
ee23a8cf25
commit
4f1c8ce764
15 changed files with 401 additions and 41 deletions
2
lib/amd/build/aria.min.js
vendored
Normal file
2
lib/amd/build/aria.min.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
define ("core/aria",["exports","./local/aria/aria-hidden"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});Object.defineProperty(a,"hide",{enumerable:!0,get:function get(){return b.hide}});Object.defineProperty(a,"unhide",{enumerable:!0,get:function get(){return b.unhide}});Object.defineProperty(a,"hideSiblings",{enumerable:!0,get:function get(){return b.hideSiblings}});Object.defineProperty(a,"unhideSiblings",{enumerable:!0,get:function get(){return b.unhideSiblings}})});
|
||||||
|
//# sourceMappingURL=aria.min.js.map
|
1
lib/amd/build/aria.min.js.map
Normal file
1
lib/amd/build/aria.min.js.map
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"version":3,"sources":[],"names":[],"mappings":"","sourcesContent":[],"file":"aria.min.js"}
|
2
lib/amd/build/local/aria/aria-hidden.min.js
vendored
Normal file
2
lib/amd/build/local/aria/aria-hidden.min.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
define ("core/local/aria/aria-hidden",["exports","core/normalise","./selectors"],function(a,b,c){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.unhideSiblings=a.hideSiblings=a.unhide=a.hide=void 0;c=function(a){return a&&a.__esModule?a:{default:a}}(c);var d=new Map,e=new Map,f=function(){return MutationObserver&&"function"==typeof MutationObserver},g=function(a){if(!(a instanceof HTMLElement)){return}if(a.matches(c.default.elements.focusable)){h(a)}a.querySelectorAll(c.default.elements.focusable).forEach(h)},h=function(a){if("undefined"!=typeof a.dataset.ariaHiddenTabIndex){return}if(a.getAttribute("tabindex")){a.dataset.ariaHiddenTabIndex=a.getAttribute("tabindex")}else{a.dataset.ariaHiddenTabIndex=""}a.setAttribute("tabindex",-1)},i=function(a){if(!(a instanceof HTMLElement)){return}if(a.matches(c.default.elements.focusableToUnhide)){j(a)}a.querySelectorAll(c.default.elements.focusableToUnhide).forEach(j)},j=function(a){if(a.closest(c.default.aria.hidden)){return}var b=a.dataset.ariaHiddenTabIndex;if(""===b){a.removeAttribute("tabindex")}else{a.setAttribute("tabindex",b)}delete a.dataset.ariaHiddenTabIndex},k=function(a){return(0,b.getList)(a).forEach(l)};a.hide=k;var l=function(a){if(!(a instanceof HTMLElement)){return}if(a.closest(c.default.aria.hidden)){return}a.setAttribute("aria-hidden",!0);g(a);if(f()){var b=new MutationObserver(function(a){a.forEach(function(a){a.addedNodes.forEach(g)})});b.observe(a,{childList:!0,subtree:!0});d.set(a,b)}},m=function(a){return(0,b.getList)(a).forEach(n)};a.unhide=m;var n=function(a){if(!(a instanceof HTMLElement)){return}a.removeAttribute("aria-hidden");i(a);if(d.has(a)){d.get(a).disconnect();d.delete(a)}};a.hideSiblings=function hideSiblings(a){return(0,b.getList)(a).forEach(o)};var o=function(a){if(!(a instanceof HTMLElement)){return}if(!a.parentElement){return}a.parentElement.childNodes.forEach(function(b){if(b===a){return}k(b)});if(f()){var b=new MutationObserver(function(b){b.forEach(function(b){b.addedNodes.forEach(function(b){if(a.contains(b)){return}k(b)})})});b.observe(a.parentElement,{childList:!0,subtree:!0});e.set(a.parentElement,b)}};a.unhideSiblings=function unhideSiblings(a){return(0,b.getList)(a).forEach(p)};var p=function(a){if(!(a instanceof HTMLElement)){return}if(!a.parentElement){return}a.parentElement.childNodes.forEach(function(b){if(b===a){return}m(b)});if(e.has(a.parentElement)){e.get(a.parentElement).disconnect();e.delete(a.parentElement)}}});
|
||||||
|
//# sourceMappingURL=aria-hidden.min.js.map
|
1
lib/amd/build/local/aria/aria-hidden.min.js.map
Normal file
1
lib/amd/build/local/aria/aria-hidden.min.js.map
Normal file
File diff suppressed because one or more lines are too long
2
lib/amd/build/local/aria/selectors.min.js
vendored
Normal file
2
lib/amd/build/local/aria/selectors.min.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
define ("core/local/aria/selectors",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;a.default={aria:{hidden:"[aria-hidden]"},elements:{focusable:"input:not([type=\"hidden\"]), a[href], button, textarea, select, [tabindex]",focusableToUnhide:"[data-aria-hidden-tab-index]"}};return a.default});
|
||||||
|
//# sourceMappingURL=selectors.min.js.map
|
1
lib/amd/build/local/aria/selectors.min.js.map
Normal file
1
lib/amd/build/local/aria/selectors.min.js.map
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"version":3,"sources":["../../../src/local/aria/selectors.js"],"names":["aria","hidden","elements","focusable","focusableToUnhide"],"mappings":"qJAwBe,CACXA,IAAI,CAAE,CACFC,MAAM,CAAE,eADN,CADK,CAIXC,QAAQ,CAAE,CACNC,SAAS,CAAE,6EADL,CAENC,iBAAiB,CAAE,8BAFb,CAJC,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Selectors used for ARIA.\n *\n * @module core/local/aria/selectors\n * @class selectors\n * @package core\n * @copyright 2020 Andrew Nicols <andrew@nicols.co.uk>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default {\n aria: {\n hidden: '[aria-hidden]',\n },\n elements: {\n focusable: 'input:not([type=\"hidden\"]), a[href], button, textarea, select, [tabindex]',\n focusableToUnhide: '[data-aria-hidden-tab-index]',\n },\n};\n"],"file":"selectors.min.js"}
|
2
lib/amd/build/modal.min.js
vendored
2
lib/amd/build/modal.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
lib/amd/build/normalise.min.js
vendored
Normal file
2
lib/amd/build/normalise.min.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
define ("core/normalise",["exports","jquery"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.getList=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);var c=function(a){if(a instanceof HTMLElement){return[a]}if(a instanceof Array){return a}if(a instanceof NodeList){return Array.from(a)}if(a instanceof b.default){return a.get()}return Array.from(a)};a.getList=c});
|
||||||
|
//# sourceMappingURL=normalise.min.js.map
|
1
lib/amd/build/normalise.min.js.map
Normal file
1
lib/amd/build/normalise.min.js.map
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"version":3,"sources":["../src/normalise.js"],"names":["getList","nodes","HTMLElement","Array","NodeList","from","jQuery","get"],"mappings":"2IAyBA,uDAEO,GAAMA,CAAAA,CAAO,CAAG,SAAAC,CAAK,CAAI,CAC5B,GAAIA,CAAK,WAAYC,CAAAA,WAArB,CAAkC,CAE9B,MAAO,CAACD,CAAD,CACV,CAED,GAAIA,CAAK,WAAYE,CAAAA,KAArB,CAA4B,CAExB,MAAOF,CAAAA,CACV,CAED,GAAIA,CAAK,WAAYG,CAAAA,QAArB,CAA+B,CAE3B,MAAOD,CAAAA,KAAK,CAACE,IAAN,CAAWJ,CAAX,CACV,CAED,GAAIA,CAAK,WAAYK,UAArB,CAA6B,CAEzB,MAAOL,CAAAA,CAAK,CAACM,GAAN,EACV,CAGD,MAAOJ,CAAAA,KAAK,CAACE,IAAN,CAAWJ,CAAX,CACV,CAvBM,C","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Normalisation helpers.\n *\n * @module core/normalise\n * @class normalise\n * @package core\n * @copyright 2020 Andrew Nicols <andrew@nicols.co.uk>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport jQuery from 'jquery';\n\nexport const getList = nodes => {\n if (nodes instanceof HTMLElement) {\n // A single record to conver to a NodeList.\n return [nodes];\n }\n\n if (nodes instanceof Array) {\n // A single record to conver to a NodeList.\n return nodes;\n }\n\n if (nodes instanceof NodeList) {\n // Already a NodeList.\n return Array.from(nodes);\n }\n\n if (nodes instanceof jQuery) {\n // A jQuery object to a NodeList.\n return nodes.get();\n }\n\n // Fallback to just having a go.\n return Array.from(nodes);\n};\n"],"file":"normalise.min.js"}
|
31
lib/amd/src/aria.js
Normal file
31
lib/amd/src/aria.js
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
// This file is part of Moodle - http://moodle.org/
|
||||||
|
//
|
||||||
|
// Moodle is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Moodle is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helpers to perform ARIA compliance changes to the DOM.
|
||||||
|
*
|
||||||
|
* @module core/aria
|
||||||
|
* @class aria
|
||||||
|
* @package core
|
||||||
|
* @copyright 2020 Andrew Nicols <andrew@nicols.co.uk>
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/
|
||||||
|
|
||||||
|
export {
|
||||||
|
hide,
|
||||||
|
unhide,
|
||||||
|
hideSiblings,
|
||||||
|
unhideSiblings,
|
||||||
|
} from './local/aria/aria-hidden';
|
267
lib/amd/src/local/aria/aria-hidden.js
Normal file
267
lib/amd/src/local/aria/aria-hidden.js
Normal file
|
@ -0,0 +1,267 @@
|
||||||
|
// This file is part of Moodle - http://moodle.org/
|
||||||
|
//
|
||||||
|
// Moodle is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Moodle is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ARIA helpers related to the aria-hidden attribute.
|
||||||
|
*
|
||||||
|
* @module core/local/aria/aria-hidden.
|
||||||
|
* @class aria
|
||||||
|
* @package core
|
||||||
|
* @copyright 2020 Andrew Nicols <andrew@nicols.co.uk>
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/
|
||||||
|
import {getList} from 'core/normalise';
|
||||||
|
import Selectors from './selectors';
|
||||||
|
|
||||||
|
// The map of MutationObserver objects for an object.
|
||||||
|
const childObserverMap = new Map();
|
||||||
|
const siblingObserverMap = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the browser supports the MutationObserver system.
|
||||||
|
*
|
||||||
|
* @returns {Bool}
|
||||||
|
*/
|
||||||
|
const supportsMutationObservers = () => (MutationObserver && typeof MutationObserver === 'function');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable element focusability, disabling the tabindex for child elements which are normally focusable.
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} target
|
||||||
|
*/
|
||||||
|
const disableElementFocusability = target => {
|
||||||
|
if (!(target instanceof HTMLElement)) {
|
||||||
|
// This element is not an HTMLElement.
|
||||||
|
// This can happen for Text Nodes.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target.matches(Selectors.elements.focusable)) {
|
||||||
|
disableAndStoreTabIndex(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
target.querySelectorAll(Selectors.elements.focusable).forEach(disableAndStoreTabIndex);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the current tab-index and store it for later restoration.
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} element
|
||||||
|
*/
|
||||||
|
const disableAndStoreTabIndex = element => {
|
||||||
|
if (typeof element.dataset.ariaHiddenTabIndex !== 'undefined') {
|
||||||
|
// This child already has a hidden attribute.
|
||||||
|
// Do not modify it as the original value will be lost.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the old tabindex in a data attribute.
|
||||||
|
if (element.getAttribute('tabindex')) {
|
||||||
|
element.dataset.ariaHiddenTabIndex = element.getAttribute('tabindex');
|
||||||
|
} else {
|
||||||
|
element.dataset.ariaHiddenTabIndex = '';
|
||||||
|
}
|
||||||
|
element.setAttribute('tabindex', -1);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Re-enable element focusability, restoring any tabindex.
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} target
|
||||||
|
*/
|
||||||
|
const enableElementFocusability = target => {
|
||||||
|
if (!(target instanceof HTMLElement)) {
|
||||||
|
// This element is not an HTMLElement.
|
||||||
|
// This can happen for Text Nodes.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target.matches(Selectors.elements.focusableToUnhide)) {
|
||||||
|
restoreTabIndex(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
target.querySelectorAll(Selectors.elements.focusableToUnhide).forEach(restoreTabIndex);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restore the tab-index of the supplied element.
|
||||||
|
*
|
||||||
|
* When disabling focusability the current tab-index is stored in the ariaHiddenTabIndex data attribute.
|
||||||
|
* This is used to restore the tab-index, but only whilst the parent nodes remain unhidden.
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} element
|
||||||
|
*/
|
||||||
|
const restoreTabIndex = element => {
|
||||||
|
if (element.closest(Selectors.aria.hidden)) {
|
||||||
|
// This item still has a hidden parent, or is hidden itself. Do not unhide it.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldTabIndex = element.dataset.ariaHiddenTabIndex;
|
||||||
|
if (oldTabIndex === '') {
|
||||||
|
element.removeAttribute('tabindex');
|
||||||
|
} else {
|
||||||
|
element.setAttribute('tabindex', oldTabIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete element.dataset.ariaHiddenTabIndex;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the supplied DOM Module to be hidden.
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} target
|
||||||
|
* @returns {Array}
|
||||||
|
*/
|
||||||
|
export const hide = target => getList(target).forEach(_hide);
|
||||||
|
|
||||||
|
const _hide = target => {
|
||||||
|
if (!(target instanceof HTMLElement)) {
|
||||||
|
// This element is not an HTMLElement.
|
||||||
|
// This can happen for Text Nodes.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target.closest(Selectors.aria.hidden)) {
|
||||||
|
// This Element, or a parent Element, is already hidden.
|
||||||
|
// Stop processing.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the aria-hidden attribute to true.
|
||||||
|
target.setAttribute('aria-hidden', true);
|
||||||
|
|
||||||
|
// Based on advice from https://dequeuniversity.com/rules/axe/3.3/aria-hidden-focus, upon setting the aria-hidden
|
||||||
|
// attribute, all focusable elements underneath that element should be modified such that they are not focusable.
|
||||||
|
disableElementFocusability(target);
|
||||||
|
|
||||||
|
if (supportsMutationObservers()) {
|
||||||
|
// Add a MutationObserver to check for new children to the tree.
|
||||||
|
const newNodeObserver = new MutationObserver(mutationList => {
|
||||||
|
mutationList.forEach(mutation => {
|
||||||
|
mutation.addedNodes.forEach(disableElementFocusability);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
newNodeObserver.observe(target, {childList: true, subtree: true});
|
||||||
|
childObserverMap.set(target, newNodeObserver);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the effect of the hide action.
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} target
|
||||||
|
* @returns {Array}
|
||||||
|
*/
|
||||||
|
export const unhide = target => getList(target).forEach(_unhide);
|
||||||
|
|
||||||
|
const _unhide = target => {
|
||||||
|
if (!(target instanceof HTMLElement)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: The aria-hidden attribute should be removed, and not set to false.
|
||||||
|
// The presence of the attribute is sufficient for some browsers to treat it as being true, regardless of its value.
|
||||||
|
target.removeAttribute('aria-hidden');
|
||||||
|
|
||||||
|
// Restore the tabindex across all child nodes of the target.
|
||||||
|
enableElementFocusability(target);
|
||||||
|
|
||||||
|
// Remove the focusability MutationObserver watching this tree.
|
||||||
|
if (childObserverMap.has(target)) {
|
||||||
|
childObserverMap.get(target).disconnect();
|
||||||
|
childObserverMap.delete(target);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Correctly mark all siblings of the supplied target Element as hidden.
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} target
|
||||||
|
* @returns {Array}
|
||||||
|
*/
|
||||||
|
export const hideSiblings = target => getList(target).forEach(_hideSiblings);
|
||||||
|
|
||||||
|
const _hideSiblings = target => {
|
||||||
|
if (!(target instanceof HTMLElement)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!target.parentElement) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
target.parentElement.childNodes.forEach(node => {
|
||||||
|
if (node === target) {
|
||||||
|
// Skip self;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hide(node);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (supportsMutationObservers()) {
|
||||||
|
// Add a MutationObserver to check for new children to the tree.
|
||||||
|
const newNodeObserver = new MutationObserver(mutationList => {
|
||||||
|
mutationList.forEach(mutation => {
|
||||||
|
mutation.addedNodes.forEach(node => {
|
||||||
|
if (target.contains(node)) {
|
||||||
|
// Skip self, and children of self.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hide(node);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
newNodeObserver.observe(target.parentElement, {childList: true, subtree: true});
|
||||||
|
siblingObserverMap.set(target.parentElement, newNodeObserver);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Correctly reverse the hide action of all children of the supplied target Element.
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} target
|
||||||
|
* @returns {Array}
|
||||||
|
*/
|
||||||
|
export const unhideSiblings = target => getList(target).forEach(_unhideSiblings);
|
||||||
|
|
||||||
|
const _unhideSiblings = target => {
|
||||||
|
if (!(target instanceof HTMLElement)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!target.parentElement) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
target.parentElement.childNodes.forEach(node => {
|
||||||
|
if (node === target) {
|
||||||
|
// Skip self;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
unhide(node);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove the sibling MutationObserver watching this tree.
|
||||||
|
if (siblingObserverMap.has(target.parentElement)) {
|
||||||
|
siblingObserverMap.get(target.parentElement).disconnect();
|
||||||
|
siblingObserverMap.delete(target.parentElement);
|
||||||
|
}
|
||||||
|
};
|
33
lib/amd/src/local/aria/selectors.js
Normal file
33
lib/amd/src/local/aria/selectors.js
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
// This file is part of Moodle - http://moodle.org/
|
||||||
|
//
|
||||||
|
// Moodle is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Moodle is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selectors used for ARIA.
|
||||||
|
*
|
||||||
|
* @module core/local/aria/selectors
|
||||||
|
* @class selectors
|
||||||
|
* @package core
|
||||||
|
* @copyright 2020 Andrew Nicols <andrew@nicols.co.uk>
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
aria: {
|
||||||
|
hidden: '[aria-hidden]',
|
||||||
|
},
|
||||||
|
elements: {
|
||||||
|
focusable: 'input:not([type="hidden"]), a[href], button, textarea, select, [tabindex]',
|
||||||
|
focusableToUnhide: '[data-aria-hidden-tab-index]',
|
||||||
|
},
|
||||||
|
};
|
|
@ -33,7 +33,8 @@ define([
|
||||||
'core/modal_events',
|
'core/modal_events',
|
||||||
'core/local/aria/focuslock',
|
'core/local/aria/focuslock',
|
||||||
'core/pending',
|
'core/pending',
|
||||||
], function($, Templates, Notification, KeyCodes, CustomEvents, ModalBackdrop, Event, ModalEvents, FocusLock, Pending) {
|
'core/aria',
|
||||||
|
], function($, Templates, Notification, KeyCodes, CustomEvents, ModalBackdrop, Event, ModalEvents, FocusLock, Pending, Aria) {
|
||||||
|
|
||||||
var SELECTORS = {
|
var SELECTORS = {
|
||||||
CONTAINER: '[data-region="modal-container"]',
|
CONTAINER: '[data-region="modal-container"]',
|
||||||
|
@ -732,30 +733,10 @@ define([
|
||||||
* @method accessibilityShow
|
* @method accessibilityShow
|
||||||
*/
|
*/
|
||||||
Modal.prototype.accessibilityShow = function() {
|
Modal.prototype.accessibilityShow = function() {
|
||||||
// We need to get a list containing each sibling element and the shallowest
|
Aria.hideSiblings(this.root.get()[0]);
|
||||||
// non-ancestral nodes in the DOM. We can shortcut this a little by leveraging
|
|
||||||
// the fact that this dialogue is always appended to the document body therefore
|
|
||||||
// it's siblings are the shallowest non-ancestral nodes. If that changes then
|
|
||||||
// this code should also be updated.
|
|
||||||
$('body').children().each(function(index, child) {
|
|
||||||
// Skip the current modal.
|
|
||||||
if (!this.root.is(child)) {
|
|
||||||
child = $(child);
|
|
||||||
var hidden = child.attr('aria-hidden');
|
|
||||||
// If they are already hidden we can ignore them.
|
|
||||||
if (hidden !== 'true') {
|
|
||||||
// Save their current state.
|
|
||||||
child.data('previous-aria-hidden', hidden);
|
|
||||||
this.hiddenSiblings.push(child);
|
|
||||||
|
|
||||||
// Hide this node from screen readers.
|
|
||||||
child.attr('aria-hidden', 'true');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
// Make us visible to screen readers.
|
// Make us visible to screen readers.
|
||||||
this.root.attr('aria-hidden', 'false');
|
this.root.removeAttr('aria-hidden');
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -768,22 +749,7 @@ define([
|
||||||
Modal.prototype.accessibilityHide = function() {
|
Modal.prototype.accessibilityHide = function() {
|
||||||
this.root.attr('aria-hidden', 'true');
|
this.root.attr('aria-hidden', 'true');
|
||||||
|
|
||||||
// Restore the sibling nodes back to their original values.
|
Aria.unhideSiblings(this.root.get()[0]);
|
||||||
$.each(this.hiddenSiblings, function(index, sibling) {
|
|
||||||
sibling = $(sibling);
|
|
||||||
var previousValue = sibling.data('previous-aria-hidden');
|
|
||||||
// If the element didn't previously have an aria-hidden attribute
|
|
||||||
// then we can just remove the one we set.
|
|
||||||
if (typeof previousValue == 'undefined') {
|
|
||||||
sibling.removeAttr('aria-hidden');
|
|
||||||
} else {
|
|
||||||
// Otherwise set it back to the old value (which will be false).
|
|
||||||
sibling.attr('aria-hidden', previousValue);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Clear the cache. No longer need to store these.
|
|
||||||
this.hiddenSiblings = [];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
51
lib/amd/src/normalise.js
Normal file
51
lib/amd/src/normalise.js
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
// This file is part of Moodle - http://moodle.org/
|
||||||
|
//
|
||||||
|
// Moodle is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Moodle is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalisation helpers.
|
||||||
|
*
|
||||||
|
* @module core/normalise
|
||||||
|
* @class normalise
|
||||||
|
* @package core
|
||||||
|
* @copyright 2020 Andrew Nicols <andrew@nicols.co.uk>
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import jQuery from 'jquery';
|
||||||
|
|
||||||
|
export const getList = nodes => {
|
||||||
|
if (nodes instanceof HTMLElement) {
|
||||||
|
// A single record to conver to a NodeList.
|
||||||
|
return [nodes];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nodes instanceof Array) {
|
||||||
|
// A single record to conver to a NodeList.
|
||||||
|
return nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nodes instanceof NodeList) {
|
||||||
|
// Already a NodeList.
|
||||||
|
return Array.from(nodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nodes instanceof jQuery) {
|
||||||
|
// A jQuery object to a NodeList.
|
||||||
|
return nodes.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to just having a go.
|
||||||
|
return Array.from(nodes);
|
||||||
|
};
|
Loading…
Add table
Add a link
Reference in a new issue