Proyectos de Subversion Moodle

Rev

Autoría | Ultima modificación | Ver Log |

{"version":3,"file":"aria-hidden.min.js","sources":["../../../src/local/aria/aria-hidden.js"],"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 * ARIA helpers related to the aria-hidden attribute.\n *\n * @module     core/local/aria/aria-hidden.\n * @copyright  2020 Andrew Nicols <andrew@nicols.co.uk>\n * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport {getList} from 'core/normalise';\nimport Selectors from './selectors';\n\n// The map of MutationObserver objects for an object.\nconst childObserverMap = new Map();\nconst siblingObserverMap = new Map();\n\n/**\n * Determine whether the browser supports the MutationObserver system.\n *\n * @method\n * @returns {Bool}\n */\nconst supportsMutationObservers = () => (MutationObserver && typeof MutationObserver === 'function');\n\n/**\n * Disable element focusability, disabling the tabindex for child elements which are normally focusable.\n *\n * @method\n * @param {HTMLElement} target\n */\nconst disableElementFocusability = target => {\n    if (!(target instanceof HTMLElement)) {\n        // This element is not an HTMLElement.\n        // This can happen for Text Nodes.\n        return;\n    }\n\n    if (target.matches(Selectors.elements.focusable)) {\n        disableAndStoreTabIndex(target);\n    }\n\n    target.querySelectorAll(Selectors.elements.focusable).forEach(disableAndStoreTabIndex);\n};\n\n/**\n * Remove the current tab-index and store it for later restoration.\n *\n * @method\n * @param {HTMLElement} element\n */\nconst disableAndStoreTabIndex = element => {\n    if (typeof element.dataset.ariaHiddenTabIndex !== 'undefined') {\n        // This child already has a hidden attribute.\n        // Do not modify it as the original value will be lost.\n        return;\n    }\n\n    // Store the old tabindex in a data attribute.\n    if (element.getAttribute('tabindex')) {\n        element.dataset.ariaHiddenTabIndex = element.getAttribute('tabindex');\n    } else {\n        element.dataset.ariaHiddenTabIndex = '';\n    }\n    element.setAttribute('tabindex', -1);\n};\n\n/**\n * Re-enable element focusability, restoring any tabindex.\n *\n * @method\n * @param {HTMLElement} target\n */\nconst enableElementFocusability = target => {\n    if (!(target instanceof HTMLElement)) {\n        // This element is not an HTMLElement.\n        // This can happen for Text Nodes.\n        return;\n    }\n\n    if (target.matches(Selectors.elements.focusableToUnhide)) {\n        restoreTabIndex(target);\n    }\n\n    target.querySelectorAll(Selectors.elements.focusableToUnhide).forEach(restoreTabIndex);\n};\n\n/**\n * Restore the tab-index of the supplied element.\n *\n * When disabling focusability the current tab-index is stored in the ariaHiddenTabIndex data attribute.\n * This is used to restore the tab-index, but only whilst the parent nodes remain unhidden.\n *\n * @method\n * @param {HTMLElement} element\n */\nconst restoreTabIndex = element => {\n    if (element.closest(Selectors.aria.hidden)) {\n        // This item still has a hidden parent, or is hidden itself. Do not unhide it.\n        return;\n    }\n\n    const oldTabIndex = element.dataset.ariaHiddenTabIndex;\n    if (oldTabIndex === '') {\n        element.removeAttribute('tabindex');\n    } else {\n        element.setAttribute('tabindex', oldTabIndex);\n    }\n\n    delete element.dataset.ariaHiddenTabIndex;\n};\n\n/**\n * Update the supplied DOM Module to be hidden.\n *\n * @method\n * @param {HTMLElement} target\n * @returns {Array}\n */\nexport const hide = target => getList(target).forEach(_hide);\n\nconst _hide = target => {\n    if (!(target instanceof HTMLElement)) {\n        // This element is not an HTMLElement.\n        // This can happen for Text Nodes.\n        return;\n    }\n\n    if (target.closest(Selectors.aria.hidden)) {\n        // This Element, or a parent Element, is already hidden.\n        // Stop processing.\n        return;\n    }\n\n    // Set the aria-hidden attribute to true.\n    target.setAttribute('aria-hidden', true);\n\n    // Based on advice from https://dequeuniversity.com/rules/axe/3.3/aria-hidden-focus, upon setting the aria-hidden\n    // attribute, all focusable elements underneath that element should be modified such that they are not focusable.\n    disableElementFocusability(target);\n\n    if (supportsMutationObservers()) {\n        // Add a MutationObserver to check for new children to the tree.\n        const mutationObserver = new MutationObserver(mutationList => {\n            mutationList.forEach(mutation => {\n                if (mutation.type === 'childList') {\n                    mutation.addedNodes.forEach(disableElementFocusability);\n                } else if (mutation.type === 'attributes') {\n                    // The tabindex has been updated on a hidden attribute.\n                    // Ensure that it is stored, ad set to -1 to prevent breakage.\n                    const element = mutation.target;\n                    const proposedTabIndex = element.getAttribute('tabindex');\n\n                    if (proposedTabIndex !== \"-1\") {\n                        element.dataset.ariaHiddenTabIndex = proposedTabIndex;\n                        element.setAttribute('tabindex', -1);\n                    }\n                }\n            });\n        });\n\n        mutationObserver.observe(target, {\n            // Watch for changes to the entire subtree.\n            subtree: true,\n\n            // Watch for new nodes.\n            childList: true,\n\n            // Watch for attribute changes to the tabindex.\n            attributes: true,\n            attributeFilter: ['tabindex'],\n        });\n        childObserverMap.set(target, mutationObserver);\n    }\n};\n\n/**\n * Reverse the effect of the hide action.\n *\n * @method\n * @param {HTMLElement} target\n * @returns {Array}\n */\nexport const unhide = target => getList(target).forEach(_unhide);\n\nconst _unhide = target => {\n    if (!(target instanceof HTMLElement)) {\n        return;\n    }\n\n    // Note: The aria-hidden attribute should be removed, and not set to false.\n    // The presence of the attribute is sufficient for some browsers to treat it as being true, regardless of its value.\n    target.removeAttribute('aria-hidden');\n\n    // Restore the tabindex across all child nodes of the target.\n    enableElementFocusability(target);\n\n    // Remove the focusability MutationObserver watching this tree.\n    if (childObserverMap.has(target)) {\n        childObserverMap.get(target).disconnect();\n        childObserverMap.delete(target);\n    }\n};\n\n/**\n * Correctly mark all siblings of the supplied target Element as hidden.\n *\n * @method\n * @param {HTMLElement} target\n * @returns {Array}\n */\nexport const hideSiblings = target => getList(target).forEach(_hideSiblings);\n\nconst _hideSiblings = target => {\n    if (!(target instanceof HTMLElement)) {\n        return;\n    }\n\n    if (!target.parentElement) {\n        return;\n    }\n\n    target.parentElement.childNodes.forEach(node => {\n        if (node === target) {\n            // Skip self;\n            return;\n        }\n\n        hide(node);\n    });\n\n    if (supportsMutationObservers()) {\n        // Add a MutationObserver to check for new children to the tree.\n        const newNodeObserver = new MutationObserver(mutationList => {\n            mutationList.forEach(mutation => {\n                mutation.addedNodes.forEach(node => {\n                    if (target.contains(node)) {\n                        // Skip self, and children of self.\n                        return;\n                    }\n\n                    hide(node);\n                });\n            });\n        });\n\n        newNodeObserver.observe(target.parentElement, {childList: true, subtree: true});\n        siblingObserverMap.set(target.parentElement, newNodeObserver);\n    }\n};\n\n/**\n * Correctly reverse the hide action of all children of the supplied target Element.\n *\n * @method\n * @param {HTMLElement} target\n * @returns {Array}\n */\nexport const unhideSiblings = target => getList(target).forEach(_unhideSiblings);\n\nconst _unhideSiblings = target => {\n    if (!(target instanceof HTMLElement)) {\n        return;\n    }\n\n    if (!target.parentElement) {\n        return;\n    }\n\n    target.parentElement.childNodes.forEach(node => {\n        if (node === target) {\n            // Skip self;\n            return;\n        }\n\n        unhide(node);\n    });\n\n    // Remove the sibling MutationObserver watching this tree.\n    if (siblingObserverMap.has(target.parentElement)) {\n        siblingObserverMap.get(target.parentElement).disconnect();\n        siblingObserverMap.delete(target.parentElement);\n    }\n};\n"],"names":["childObserverMap","Map","siblingObserverMap","supportsMutationObservers","MutationObserver","disableElementFocusability","target","HTMLElement","matches","Selectors","elements","focusable","disableAndStoreTabIndex","querySelectorAll","forEach","element","dataset","ariaHiddenTabIndex","getAttribute","setAttribute","restoreTabIndex","closest","aria","hidden","oldTabIndex","removeAttribute","hide","_hide","mutationObserver","mutationList","mutation","type","addedNodes","proposedTabIndex","observe","subtree","childList","attributes","attributeFilter","set","unhide","_unhide","focusableToUnhide","enableElementFocusability","has","get","disconnect","delete","_hideSiblings","parentElement","childNodes","node","newNodeObserver","contains","_unhideSiblings"],"mappings":";;;;;;;oNA0BMA,iBAAmB,IAAIC,IACvBC,mBAAqB,IAAID,IAQzBE,0BAA4B,IAAOC,kBAAgD,mBAArBA,iBAQ9DC,2BAA6BC,SACzBA,kBAAkBC,cAMpBD,OAAOE,QAAQC,mBAAUC,SAASC,YAClCC,wBAAwBN,QAG5BA,OAAOO,iBAAiBJ,mBAAUC,SAASC,WAAWG,QAAQF,2BAS5DA,wBAA0BG,eACsB,IAAvCA,QAAQC,QAAQC,qBAOvBF,QAAQG,aAAa,YACrBH,QAAQC,QAAQC,mBAAqBF,QAAQG,aAAa,YAE1DH,QAAQC,QAAQC,mBAAqB,GAEzCF,QAAQI,aAAa,YAAa,KAgChCC,gBAAkBL,aAChBA,QAAQM,QAAQZ,mBAAUa,KAAKC,qBAK7BC,YAAcT,QAAQC,QAAQC,mBAChB,KAAhBO,YACAT,QAAQU,gBAAgB,YAExBV,QAAQI,aAAa,WAAYK,oBAG9BT,QAAQC,QAAQC,oBAUdS,KAAOpB,SAAU,sBAAQA,QAAQQ,QAAQa,gCAEhDA,MAAQrB,YACJA,kBAAkBC,cAMpBD,OAAOe,QAAQZ,mBAAUa,KAAKC,UAOlCjB,OAAOa,aAAa,eAAe,GAInCd,2BAA2BC,QAEvBH,6BAA6B,OAEvByB,iBAAmB,IAAIxB,kBAAiByB,eAC1CA,aAAaf,SAAQgB,cACK,cAAlBA,SAASC,KACTD,SAASE,WAAWlB,QAAQT,iCACzB,GAAsB,eAAlByB,SAASC,KAAuB,OAGjChB,QAAUe,SAASxB,OACnB2B,iBAAmBlB,QAAQG,aAAa,YAErB,OAArBe,mBACAlB,QAAQC,QAAQC,mBAAqBgB,iBACrClB,QAAQI,aAAa,YAAa,WAMlDS,iBAAiBM,QAAQ5B,OAAQ,CAE7B6B,SAAS,EAGTC,WAAW,EAGXC,YAAY,EACZC,gBAAiB,CAAC,cAEtBtC,iBAAiBuC,IAAIjC,OAAQsB,oBAWxBY,OAASlC,SAAU,sBAAQA,QAAQQ,QAAQ2B,sCAElDA,QAAUnC,SACNA,kBAAkBC,cAMxBD,OAAOmB,gBAAgB,eAvHOnB,CAAAA,SACxBA,kBAAkBC,cAMpBD,OAAOE,QAAQC,mBAAUC,SAASgC,oBAClCtB,gBAAgBd,QAGpBA,OAAOO,iBAAiBJ,mBAAUC,SAASgC,mBAAmB5B,QAAQM,mBA+GtEuB,CAA0BrC,QAGtBN,iBAAiB4C,IAAItC,UACrBN,iBAAiB6C,IAAIvC,QAAQwC,aAC7B9C,iBAAiB+C,OAAOzC,iCAWJA,SAAU,sBAAQA,QAAQQ,QAAQkC,qBAExDA,cAAgB1C,YACZA,kBAAkBC,aAInBD,OAAO2C,gBAIZ3C,OAAO2C,cAAcC,WAAWpC,SAAQqC,OAChCA,OAAS7C,QAKboB,KAAKyB,SAGLhD,6BAA6B,OAEvBiD,gBAAkB,IAAIhD,kBAAiByB,eACzCA,aAAaf,SAAQgB,WACjBA,SAASE,WAAWlB,SAAQqC,OACpB7C,OAAO+C,SAASF,OAKpBzB,KAAKyB,eAKjBC,gBAAgBlB,QAAQ5B,OAAO2C,cAAe,CAACb,WAAW,EAAMD,SAAS,IACzEjC,mBAAmBqC,IAAIjC,OAAO2C,cAAeG,2CAWvB9C,SAAU,sBAAQA,QAAQQ,QAAQwC,uBAE1DA,gBAAkBhD,SACdA,kBAAkBC,aAInBD,OAAO2C,gBAIZ3C,OAAO2C,cAAcC,WAAWpC,SAAQqC,OAChCA,OAAS7C,QAKbkC,OAAOW,SAIPjD,mBAAmB0C,IAAItC,OAAO2C,iBAC9B/C,mBAAmB2C,IAAIvC,OAAO2C,eAAeH,aAC7C5C,mBAAmB6C,OAAOzC,OAAO2C"}