From 59128bb4114bfcb3df623c7b7bd4e071ac30c0f9 Mon Sep 17 00:00:00 2001 From: Ferran Recio Date: Mon, 6 Sep 2021 20:04:17 +0200 Subject: [PATCH] MDL-72263 courseformat: adding core/tree to course index --- .../local/courseindex/courseindex.min.js | 2 +- .../local/courseindex/courseindex.min.js.map | 2 +- .../local/courseindex/keyboardnav.min.js | 2 + .../local/courseindex/keyboardnav.min.js.map | 1 + .../amd/src/local/courseindex/courseindex.js | 6 + .../amd/src/local/courseindex/keyboardnav.js | 153 ++++++++++++++++++ .../templates/local/courseindex/cm.mustache | 5 +- .../local/courseindex/section.mustache | 10 +- .../behat/courseindex_keyboardnav.feature | 99 ++++++++++++ lib/amd/build/local/aria/aria-hidden.min.js | 2 +- .../build/local/aria/aria-hidden.min.js.map | 2 +- lib/amd/build/tree.min.js.map | 2 +- lib/amd/src/local/aria/aria-hidden.js | 30 +++- lib/amd/src/tree.js | 2 +- lib/tests/behat/behat_general.php | 3 + theme/boost/amd/build/drawers.min.js | 2 +- theme/boost/amd/build/drawers.min.js.map | 2 +- theme/boost/amd/src/drawers.js | 19 ++- theme/classic/tests/behat/blacklist.json | 3 +- 19 files changed, 325 insertions(+), 22 deletions(-) create mode 100644 course/format/amd/build/local/courseindex/keyboardnav.min.js create mode 100644 course/format/amd/build/local/courseindex/keyboardnav.min.js.map create mode 100644 course/format/amd/src/local/courseindex/keyboardnav.js create mode 100644 course/format/tests/behat/courseindex_keyboardnav.feature diff --git a/course/format/amd/build/local/courseindex/courseindex.min.js b/course/format/amd/build/local/courseindex/courseindex.min.js index c2a3d422216..7b8524dd747 100644 --- a/course/format/amd/build/local/courseindex/courseindex.min.js +++ b/course/format/amd/build/local/courseindex/courseindex.min.js @@ -1,2 +1,2 @@ -define ("core_courseformat/local/courseindex/courseindex",["exports","core/reactive","core_courseformat/courseeditor","jquery"],function(a,b,c,d){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;d=function(a){return a&&a.__esModule?a:{default:a}}(d);function e(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){e=function(a){return typeof a}}else{e=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return e(a)}function f(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function g(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var i=a.apply(b,c);function g(a){f(i,d,e,g,h,"next",a)}function h(a){f(i,d,e,g,h,"throw",a)}g(void 0)})}}function h(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function i(a,b){for(var c=0,d;cb.length){a.removeChild(a.lastChild)}}},{key:"_deleteCm",value:function _deleteCm(a){var b=a.element;delete this.cms[b.id]}}],[{key:"init",value:function init(a,d){return new b({element:document.getElementById(a),reactive:(0,c.getCurrentCourseEditor)(),selectors:d})}}]);return b}(b.BaseComponent);a.default=r;return a.default}); +define ("core_courseformat/local/courseindex/courseindex",["exports","core/reactive","core_courseformat/courseeditor","jquery","core_courseformat/local/courseindex/keyboardnav"],function(a,b,c,d,e){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;d=f(d);e=f(e);function f(a){return a&&a.__esModule?a:{default:a}}function g(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){g=function(a){return typeof a}}else{g=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return g(a)}function h(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function i(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var i=a.apply(b,c);function f(a){h(i,d,e,f,g,"next",a)}function g(a){h(i,d,e,f,g,"throw",a)}f(void 0)})}}function j(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function k(a,b){for(var c=0,d;cb.length){a.removeChild(a.lastChild)}}},{key:"_deleteCm",value:function _deleteCm(a){var b=a.element;delete this.cms[b.id]}}],[{key:"init",value:function init(a,d){return new b({element:document.getElementById(a),reactive:(0,c.getCurrentCourseEditor)(),selectors:d})}}]);return b}(b.BaseComponent);a.default=t;return a.default}); //# sourceMappingURL=courseindex.min.js.map diff --git a/course/format/amd/build/local/courseindex/courseindex.min.js.map b/course/format/amd/build/local/courseindex/courseindex.min.js.map index 4a74dee1bb0..322c6c659ae 100644 --- a/course/format/amd/build/local/courseindex/courseindex.min.js.map +++ b/course/format/amd/build/local/courseindex/courseindex.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/local/courseindex/courseindex.js"],"names":["Component","name","selectors","SECTION","SECTION_CMLIST","CM","TOGGLER","COLLAPSE","classes","SECTIONHIDDEN","CMHIDDEN","SECTIONCURRENT","COLLAPSED","sections","cms","addEventListener","element","_sectionTogglers","getElements","forEach","section","dataset","id","cm","watch","handler","_refreshSectionCollapsed","_createCm","_deleteCm","_refreshCourseSectionlist","_refreshSectionCmlist","event","sectionlink","target","closest","isChevron","toggler","querySelector","isCollapsed","classList","contains","sectionId","getAttribute","reactive","dispatch","isactive","getElement","Error","_expandSectionNode","forceValue","collapsibleId","replace","collapsible","document","getElementById","togglerValue","collapse","state","fakeelement","createElement","add","innerHTML","get","sectionid","exporter","getExporter","data","renderComponent","newcomponent","newelement","parentNode","replaceChild","cmlist","listparent","_fixOrder","sectionlist","container","neworder","allitems","length","remove","itemid","index","item","currentitem","children","append","insertBefore","removeChild","lastChild","BaseComponent"],"mappings":"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","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 .\n\n/**\n * Course index main component.\n *\n * @module core_courseformat/local/courseindex/courseindex\n * @class core_courseformat/local/courseindex/courseindex\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {BaseComponent} from 'core/reactive';\nimport {getCurrentCourseEditor} from 'core_courseformat/courseeditor';\nimport jQuery from 'jquery';\n\nexport default class Component extends BaseComponent {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'courseindex';\n // Default query selectors.\n this.selectors = {\n SECTION: `[data-for='section']`,\n SECTION_CMLIST: `[data-for='cmlist']`,\n CM: `[data-for='cm']`,\n TOGGLER: `[data-action=\"togglecourseindexsection\"]`,\n COLLAPSE: `[data-toggle=\"collapse\"]`,\n };\n // Default classes to toggle on refresh.\n this.classes = {\n SECTIONHIDDEN: 'dimmed',\n CMHIDDEN: 'dimmed',\n SECTIONCURRENT: 'current',\n COLLAPSED: `collapsed`,\n };\n // Arrays to keep cms and sections elements.\n this.sections = {};\n this.cms = {};\n }\n\n /**\n * Static method to create a component instance form the mustache template.\n *\n * @param {element|string} target the DOM main element or its ID\n * @param {object} selectors optional css selector overrides\n * @return {Component}\n */\n static init(target, selectors) {\n return new Component({\n element: document.getElementById(target),\n reactive: getCurrentCourseEditor(),\n selectors,\n });\n }\n\n /**\n * Initial state ready method.\n */\n stateReady() {\n // Activate section togglers.\n this.addEventListener(this.element, 'click', this._sectionTogglers);\n\n // Get cms and sections elements.\n const sections = this.getElements(this.selectors.SECTION);\n sections.forEach((section) => {\n this.sections[section.dataset.id] = section;\n });\n const cms = this.getElements(this.selectors.CM);\n cms.forEach((cm) => {\n this.cms[cm.dataset.id] = cm;\n });\n }\n\n getWatchers() {\n return [\n {watch: `section.isactive:updated`, handler: this._refreshSectionCollapsed},\n {watch: `cm:created`, handler: this._createCm},\n {watch: `cm:deleted`, handler: this._deleteCm},\n // Sections and cm sorting.\n {watch: `course.sectionlist:updated`, handler: this._refreshCourseSectionlist},\n {watch: `section.cmlist:updated`, handler: this._refreshSectionCmlist},\n ];\n }\n\n /**\n * Setup sections toggler.\n *\n * Toggler click is delegated to the main course index element because new sections can\n * appear at any moment and this way we prevent accidental double bindings.\n *\n * @param {Event} event the triggered event\n */\n _sectionTogglers(event) {\n const sectionlink = event.target.closest(this.selectors.TOGGLER);\n const isChevron = event.target.closest(this.selectors.COLLAPSE);\n\n if (sectionlink || isChevron) {\n\n const section = event.target.closest(this.selectors.SECTION);\n const toggler = section.querySelector(this.selectors.COLLAPSE);\n const isCollapsed = toggler?.classList.contains(this.classes.COLLAPSED) ?? false;\n\n if (isChevron || isCollapsed) {\n // Update the state.\n const sectionId = section.getAttribute('data-id');\n this.reactive.dispatch(\n 'sectionPreferences',\n [sectionId],\n {\n isactive: isCollapsed,\n },\n );\n }\n }\n }\n\n /**\n * Update section collapsed.\n *\n * @param {object} args\n * @param {object} args.element The leement to be expanded\n */\n _refreshSectionCollapsed({element}) {\n const target = this.getElement(this.selectors.SECTION, element.id);\n if (!target) {\n throw new Error(`Unkown section with ID ${element.id}`);\n }\n // Check if it is already done.\n const toggler = target.querySelector(this.selectors.COLLAPSE);\n const isCollapsed = toggler?.classList.contains(this.classes.COLLAPSED) ?? false;\n\n if (element.isactive === isCollapsed) {\n this._expandSectionNode(element);\n }\n }\n\n /**\n * Expand a section node.\n *\n * By default the method will use element.isactive to decide if the\n * section is opened or closed. However, using forceValue it is possible\n * to open or close a section independant from the isactive attribute.\n *\n * @param {Object} element the course module state element\n * @param {boolean} forceValue optional forced expanded value\n */\n _expandSectionNode(element, forceValue) {\n const target = this.getElement(this.selectors.SECTION, element.id);\n const toggler = target.querySelector(this.selectors.COLLAPSE);\n let collapsibleId = toggler.dataset.target ?? toggler.getAttribute(\"href\");\n if (!collapsibleId) {\n return;\n }\n collapsibleId = collapsibleId.replace('#', '');\n const collapsible = document.getElementById(collapsibleId);\n if (!collapsible) {\n return;\n }\n\n if (forceValue === undefined) {\n forceValue = (element.isactive) ? true : false;\n }\n\n // Course index is based on Bootstrap 4 collapsibles. To collapse them we need jQuery to\n // interact with collapsibles methods. Hopefully, this will change in Bootstrap 5 because\n // it does not require jQuery anymore.\n const togglerValue = (forceValue) ? 'show' : 'hide';\n jQuery(collapsible).collapse(togglerValue);\n }\n\n /**\n * Create a newcm instance.\n *\n * @param {object} param\n * @param {Object} param.state\n * @param {Object} param.element\n */\n async _createCm({state, element}) {\n // Create a fake node while the component is loading.\n const fakeelement = document.createElement('li');\n fakeelement.classList.add('bg-pulse-grey', 'w-100');\n fakeelement.innerHTML = ' ';\n this.cms[element.id] = fakeelement;\n // Place the fake node on the correct position.\n this._refreshSectionCmlist({\n state,\n element: state.section.get(element.sectionid),\n });\n // Collect render data.\n const exporter = this.reactive.getExporter();\n const data = exporter.cm(state, element);\n // Create the new content.\n const newcomponent = await this.renderComponent(fakeelement, 'core_courseformat/local/courseindex/cm', data);\n // Replace the fake node with the real content.\n const newelement = newcomponent.getElement();\n this.cms[element.id] = newelement;\n fakeelement.parentNode.replaceChild(newelement, fakeelement);\n }\n\n /**\n * Refresh a section cm list.\n *\n * @param {object} param\n * @param {Object} param.element\n */\n _refreshSectionCmlist({element}) {\n const cmlist = element.cmlist ?? [];\n const listparent = this.getElement(this.selectors.SECTION_CMLIST, element.id);\n this._fixOrder(listparent, cmlist, this.cms);\n }\n\n /**\n * Refresh the section list.\n *\n * @param {object} param\n * @param {Object} param.element\n */\n _refreshCourseSectionlist({element}) {\n const sectionlist = element.sectionlist ?? [];\n this._fixOrder(this.element, sectionlist, this.sections);\n }\n\n /**\n * Fix/reorder the section or cms order.\n *\n * @param {Element} container the HTML element to reorder.\n * @param {Array} neworder an array with the ids order\n * @param {Array} allitems the list of html elements that can be placed in the container\n */\n _fixOrder(container, neworder, allitems) {\n\n // Empty lists should not be visible.\n if (!neworder.length) {\n container.classList.add('hidden');\n container.innerHTML = '';\n return;\n }\n\n // Grant the list is visible (in case it was empty).\n container.classList.remove('hidden');\n\n // Move the elements in order at the beginning of the list.\n neworder.forEach((itemid, index) => {\n const item = allitems[itemid];\n // Get the current element at that position.\n const currentitem = container.children[index];\n if (currentitem === undefined) {\n container.append(item);\n return;\n }\n if (currentitem !== item) {\n container.insertBefore(item, currentitem);\n }\n });\n // Remove the remaining elements.\n while (container.children.length > neworder.length) {\n container.removeChild(container.lastChild);\n }\n }\n\n /**\n * Remove a cm from the list.\n *\n * The actual DOM element removal is delegated to the cm component.\n *\n * @param {object} param\n * @param {Object} param.element\n */\n _deleteCm({element}) {\n delete this.cms[element.id];\n }\n}\n"],"file":"courseindex.min.js"} \ No newline at end of file +{"version":3,"sources":["../../../src/local/courseindex/courseindex.js"],"names":["Component","name","selectors","SECTION","SECTION_CMLIST","CM","TOGGLER","COLLAPSE","DRAWER","classes","SECTIONHIDDEN","CMHIDDEN","SECTIONCURRENT","COLLAPSED","SHOW","sections","cms","addEventListener","element","_sectionTogglers","getElements","forEach","section","dataset","id","cm","treeNav","TreeNav","watch","handler","_refreshSectionCollapsed","_createCm","_deleteCm","_refreshCourseSectionlist","_refreshSectionCmlist","event","sectionlink","target","closest","isChevron","toggler","querySelector","isCollapsed","classList","contains","sectionId","getAttribute","reactive","dispatch","isactive","getElement","Error","_expandSectionNode","forceValue","collapsibleId","replace","collapsible","document","getElementById","togglerValue","collapse","state","fakeelement","createElement","add","innerHTML","get","sectionid","exporter","getExporter","data","renderComponent","newcomponent","newelement","parentNode","replaceChild","cmlist","listparent","_fixOrder","sectionlist","container","neworder","allitems","length","remove","itemid","index","item","currentitem","children","append","insertBefore","removeChild","lastChild","BaseComponent"],"mappings":"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","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 .\n\n/**\n * Course index main component.\n *\n * @module core_courseformat/local/courseindex/courseindex\n * @class core_courseformat/local/courseindex/courseindex\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {BaseComponent} from 'core/reactive';\nimport {getCurrentCourseEditor} from 'core_courseformat/courseeditor';\nimport jQuery from 'jquery';\nimport TreeNav from 'core_courseformat/local/courseindex/keyboardnav';\n\nexport default class Component extends BaseComponent {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'courseindex';\n // Default query selectors.\n this.selectors = {\n SECTION: `[data-for='section']`,\n SECTION_CMLIST: `[data-for='cmlist']`,\n CM: `[data-for='cm']`,\n TOGGLER: `[data-action=\"togglecourseindexsection\"]`,\n COLLAPSE: `[data-toggle=\"collapse\"]`,\n DRAWER: `.drawer`,\n };\n // Default classes to toggle on refresh.\n this.classes = {\n SECTIONHIDDEN: 'dimmed',\n CMHIDDEN: 'dimmed',\n SECTIONCURRENT: 'current',\n COLLAPSED: `collapsed`,\n SHOW: `show`,\n };\n // Arrays to keep cms and sections elements.\n this.sections = {};\n this.cms = {};\n }\n\n /**\n * Static method to create a component instance form the mustache template.\n *\n * @param {element|string} target the DOM main element or its ID\n * @param {object} selectors optional css selector overrides\n * @return {Component}\n */\n static init(target, selectors) {\n return new Component({\n element: document.getElementById(target),\n reactive: getCurrentCourseEditor(),\n selectors,\n });\n }\n\n /**\n * Initial state ready method.\n */\n stateReady() {\n // Activate section togglers.\n this.addEventListener(this.element, 'click', this._sectionTogglers);\n\n // Get cms and sections elements.\n const sections = this.getElements(this.selectors.SECTION);\n sections.forEach((section) => {\n this.sections[section.dataset.id] = section;\n });\n const cms = this.getElements(this.selectors.CM);\n cms.forEach((cm) => {\n this.cms[cm.dataset.id] = cm;\n });\n\n // Configure Aria Tree.\n this.treeNav = new TreeNav(this);\n }\n\n getWatchers() {\n return [\n {watch: `section.isactive:updated`, handler: this._refreshSectionCollapsed},\n {watch: `cm:created`, handler: this._createCm},\n {watch: `cm:deleted`, handler: this._deleteCm},\n // Sections and cm sorting.\n {watch: `course.sectionlist:updated`, handler: this._refreshCourseSectionlist},\n {watch: `section.cmlist:updated`, handler: this._refreshSectionCmlist},\n ];\n }\n\n /**\n * Setup sections toggler.\n *\n * Toggler click is delegated to the main course index element because new sections can\n * appear at any moment and this way we prevent accidental double bindings.\n *\n * @param {Event} event the triggered event\n */\n _sectionTogglers(event) {\n const sectionlink = event.target.closest(this.selectors.TOGGLER);\n const isChevron = event.target.closest(this.selectors.COLLAPSE);\n\n if (sectionlink || isChevron) {\n\n const section = event.target.closest(this.selectors.SECTION);\n const toggler = section.querySelector(this.selectors.COLLAPSE);\n const isCollapsed = toggler?.classList.contains(this.classes.COLLAPSED) ?? false;\n\n if (isChevron || isCollapsed) {\n // Update the state.\n const sectionId = section.getAttribute('data-id');\n this.reactive.dispatch(\n 'sectionPreferences',\n [sectionId],\n {\n isactive: isCollapsed,\n },\n );\n }\n }\n }\n\n /**\n * Update section collapsed.\n *\n * @param {object} args\n * @param {object} args.element The leement to be expanded\n */\n _refreshSectionCollapsed({element}) {\n const target = this.getElement(this.selectors.SECTION, element.id);\n if (!target) {\n throw new Error(`Unkown section with ID ${element.id}`);\n }\n // Check if it is already done.\n const toggler = target.querySelector(this.selectors.COLLAPSE);\n const isCollapsed = toggler?.classList.contains(this.classes.COLLAPSED) ?? false;\n\n if (element.isactive === isCollapsed) {\n this._expandSectionNode(element);\n }\n }\n\n /**\n * Expand a section node.\n *\n * By default the method will use element.isactive to decide if the\n * section is opened or closed. However, using forceValue it is possible\n * to open or close a section independant from the isactive attribute.\n *\n * @param {Object} element the course module state element\n * @param {boolean} forceValue optional forced expanded value\n */\n _expandSectionNode(element, forceValue) {\n const target = this.getElement(this.selectors.SECTION, element.id);\n const toggler = target.querySelector(this.selectors.COLLAPSE);\n let collapsibleId = toggler.dataset.target ?? toggler.getAttribute(\"href\");\n if (!collapsibleId) {\n return;\n }\n collapsibleId = collapsibleId.replace('#', '');\n const collapsible = document.getElementById(collapsibleId);\n if (!collapsible) {\n return;\n }\n\n if (forceValue === undefined) {\n forceValue = (element.isactive) ? true : false;\n }\n\n // Course index is based on Bootstrap 4 collapsibles. To collapse them we need jQuery to\n // interact with collapsibles methods. Hopefully, this will change in Bootstrap 5 because\n // it does not require jQuery anymore.\n const togglerValue = (forceValue) ? 'show' : 'hide';\n jQuery(collapsible).collapse(togglerValue);\n }\n\n /**\n * Create a newcm instance.\n *\n * @param {object} param\n * @param {Object} param.state\n * @param {Object} param.element\n */\n async _createCm({state, element}) {\n // Create a fake node while the component is loading.\n const fakeelement = document.createElement('li');\n fakeelement.classList.add('bg-pulse-grey', 'w-100');\n fakeelement.innerHTML = ' ';\n this.cms[element.id] = fakeelement;\n // Place the fake node on the correct position.\n this._refreshSectionCmlist({\n state,\n element: state.section.get(element.sectionid),\n });\n // Collect render data.\n const exporter = this.reactive.getExporter();\n const data = exporter.cm(state, element);\n // Create the new content.\n const newcomponent = await this.renderComponent(fakeelement, 'core_courseformat/local/courseindex/cm', data);\n // Replace the fake node with the real content.\n const newelement = newcomponent.getElement();\n this.cms[element.id] = newelement;\n fakeelement.parentNode.replaceChild(newelement, fakeelement);\n }\n\n /**\n * Refresh a section cm list.\n *\n * @param {object} param\n * @param {Object} param.element\n */\n _refreshSectionCmlist({element}) {\n const cmlist = element.cmlist ?? [];\n const listparent = this.getElement(this.selectors.SECTION_CMLIST, element.id);\n this._fixOrder(listparent, cmlist, this.cms);\n }\n\n /**\n * Refresh the section list.\n *\n * @param {object} param\n * @param {Object} param.element\n */\n _refreshCourseSectionlist({element}) {\n const sectionlist = element.sectionlist ?? [];\n this._fixOrder(this.element, sectionlist, this.sections);\n }\n\n /**\n * Fix/reorder the section or cms order.\n *\n * @param {Element} container the HTML element to reorder.\n * @param {Array} neworder an array with the ids order\n * @param {Array} allitems the list of html elements that can be placed in the container\n */\n _fixOrder(container, neworder, allitems) {\n\n // Empty lists should not be visible.\n if (!neworder.length) {\n container.classList.add('hidden');\n container.innerHTML = '';\n return;\n }\n\n // Grant the list is visible (in case it was empty).\n container.classList.remove('hidden');\n\n // Move the elements in order at the beginning of the list.\n neworder.forEach((itemid, index) => {\n const item = allitems[itemid];\n // Get the current element at that position.\n const currentitem = container.children[index];\n if (currentitem === undefined) {\n container.append(item);\n return;\n }\n if (currentitem !== item) {\n container.insertBefore(item, currentitem);\n }\n });\n // Remove the remaining elements.\n while (container.children.length > neworder.length) {\n container.removeChild(container.lastChild);\n }\n }\n\n /**\n * Remove a cm from the list.\n *\n * The actual DOM element removal is delegated to the cm component.\n *\n * @param {object} param\n * @param {Object} param.element\n */\n _deleteCm({element}) {\n delete this.cms[element.id];\n }\n}\n"],"file":"courseindex.min.js"} \ No newline at end of file diff --git a/course/format/amd/build/local/courseindex/keyboardnav.min.js b/course/format/amd/build/local/courseindex/keyboardnav.min.js new file mode 100644 index 00000000000..ae1d0a8e783 --- /dev/null +++ b/course/format/amd/build/local/courseindex/keyboardnav.min.js @@ -0,0 +1,2 @@ +define ("core_courseformat/local/courseindex/keyboardnav",["exports","jquery","core/tree"],function(a,b,c){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=d(b);c=d(c);function d(a){return a&&a.__esModule?a:{default:a}}function e(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){e=function(a){return typeof a}}else{e=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return e(a)}function f(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function g(a,b){for(var c=0,d;c.\n\n/**\n * Course index keyboard navigation and aria-tree compatibility.\n *\n * Node tree and bootstrap collapsibles don't use the same HTML structure. However,\n * all keybindings and logic is compatible. This class translate the primitive opetations\n * to a bootstrap collapsible structure.\n *\n * @module core_courseformat/local/courseindex/keyboardnav\n * @class core_courseformat/local/courseindex/keyboardnav\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n// The core/tree uses jQuery to expand all nodes.\nimport $ from 'jquery';\nimport Tree from 'core/tree';\n\nexport default class extends Tree {\n\n /**\n * Setup the core/tree keyboard navigation.\n *\n * @param {CourseIndex} parent the parent component\n */\n constructor(parent) {\n // Init this value with the parent DOM element.\n super(parent.element);\n\n // Get selectors from parent.\n this.selectors = parent.selectors;\n\n // The core/tree library saves the visible elements cache inside the main tree node.\n // However, in edit mode content can change suddenly so we need to refresh caches when needed.\n if (parent.reactive.isEditing) {\n this._getVisibleItems = this.getVisibleItems;\n this.getVisibleItems = () => {\n this.refreshVisibleItemsCache();\n return this._getVisibleItems();\n };\n }\n // Add jQuery events to detect boostrap collapse and uncollapse.\n this.treeRoot.on('hidden.bs.collapse shown.bs.collapse', () => {\n this.refreshVisibleItemsCache();\n });\n // Register a custom callback for pressing enter key.\n this.registerEnterCallback(this.enterCallback.bind(this));\n }\n\n /**\n * Return the current active node.\n *\n * @return {Element|undefined} the active item if any\n */\n getActiveItem() {\n const activeItem = this.treeRoot.data('activeItem');\n if (activeItem) {\n return activeItem.get(0);\n }\n return undefined;\n }\n\n /**\n * Handle enter key on a collpasible node.\n *\n * @param {JQuery} item the jQuery object\n */\n enterCallback(item) {\n if (this.isGroupItem(item)) {\n // Group elements is like clicking a topic but without loosing the focus.\n window.location.href = item.find(this.selectors.TOGGLER).first().attr('href');\n item.find(this.selectors.TOGGLER).get(0).click();\n } else {\n // Activity links just follow the link href.\n window.location.href = item.find('a').first().attr('href');\n return;\n }\n }\n\n /**\n * Check if a gorup item is collapsed.\n *\n * @param {JQuery} item the jQuery object\n * @returns {boolean} if the element is collapsed\n */\n isGroupCollapsed(item) {\n const toggler = item.find(`[aria-expanded]`);\n return toggler.attr('aria-expanded') === 'false';\n }\n\n /**\n * Toggle a group item.\n *\n * @param {JQuery} item the jQuery object\n */\n toggleGroup(item) {\n const toggler = item.find(this.selectors.COLLAPSE);\n let collapsibleId = toggler.data('target') ?? toggler.attr('href');\n if (!collapsibleId) {\n return;\n }\n collapsibleId = collapsibleId.replace('#', '');\n\n // Bootstrap 4 uses jQuery to interact with collapsibles.\n $(`#${collapsibleId}`).collapse('toggle');\n }\n\n /**\n * Expand a group item.\n *\n * @param {JQuery} item the jQuery object\n */\n expandGroup(item) {\n if (this.isGroupCollapsed(item)) {\n this.toggleGroup(item);\n }\n }\n\n /**\n * Collpase a group item.\n *\n * @param {JQuery} item the jQuery object\n */\n collapseGroup(item) {\n if (!this.isGroupCollapsed(item)) {\n this.toggleGroup(item);\n }\n }\n\n /**\n * Expand all groups.\n */\n expandAllGroups() {\n const togglers = this.treeRoot.find(this.selectors.SECTION);\n togglers.each((index, item) => {\n this.expandGroup($(item));\n });\n }\n}\n"],"file":"keyboardnav.min.js"} \ No newline at end of file diff --git a/course/format/amd/src/local/courseindex/courseindex.js b/course/format/amd/src/local/courseindex/courseindex.js index e964b7a0f8e..7057504311c 100644 --- a/course/format/amd/src/local/courseindex/courseindex.js +++ b/course/format/amd/src/local/courseindex/courseindex.js @@ -25,6 +25,7 @@ import {BaseComponent} from 'core/reactive'; import {getCurrentCourseEditor} from 'core_courseformat/courseeditor'; import jQuery from 'jquery'; +import TreeNav from 'core_courseformat/local/courseindex/keyboardnav'; export default class Component extends BaseComponent { @@ -41,6 +42,7 @@ export default class Component extends BaseComponent { CM: `[data-for='cm']`, TOGGLER: `[data-action="togglecourseindexsection"]`, COLLAPSE: `[data-toggle="collapse"]`, + DRAWER: `.drawer`, }; // Default classes to toggle on refresh. this.classes = { @@ -48,6 +50,7 @@ export default class Component extends BaseComponent { CMHIDDEN: 'dimmed', SECTIONCURRENT: 'current', COLLAPSED: `collapsed`, + SHOW: `show`, }; // Arrays to keep cms and sections elements. this.sections = {}; @@ -85,6 +88,9 @@ export default class Component extends BaseComponent { cms.forEach((cm) => { this.cms[cm.dataset.id] = cm; }); + + // Configure Aria Tree. + this.treeNav = new TreeNav(this); } getWatchers() { diff --git a/course/format/amd/src/local/courseindex/keyboardnav.js b/course/format/amd/src/local/courseindex/keyboardnav.js new file mode 100644 index 00000000000..1db75752400 --- /dev/null +++ b/course/format/amd/src/local/courseindex/keyboardnav.js @@ -0,0 +1,153 @@ +// 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 . + +/** + * Course index keyboard navigation and aria-tree compatibility. + * + * Node tree and bootstrap collapsibles don't use the same HTML structure. However, + * all keybindings and logic is compatible. This class translate the primitive opetations + * to a bootstrap collapsible structure. + * + * @module core_courseformat/local/courseindex/keyboardnav + * @class core_courseformat/local/courseindex/keyboardnav + * @copyright 2021 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +// The core/tree uses jQuery to expand all nodes. +import $ from 'jquery'; +import Tree from 'core/tree'; + +export default class extends Tree { + + /** + * Setup the core/tree keyboard navigation. + * + * @param {CourseIndex} parent the parent component + */ + constructor(parent) { + // Init this value with the parent DOM element. + super(parent.element); + + // Get selectors from parent. + this.selectors = parent.selectors; + + // The core/tree library saves the visible elements cache inside the main tree node. + // However, in edit mode content can change suddenly so we need to refresh caches when needed. + if (parent.reactive.isEditing) { + this._getVisibleItems = this.getVisibleItems; + this.getVisibleItems = () => { + this.refreshVisibleItemsCache(); + return this._getVisibleItems(); + }; + } + // Add jQuery events to detect boostrap collapse and uncollapse. + this.treeRoot.on('hidden.bs.collapse shown.bs.collapse', () => { + this.refreshVisibleItemsCache(); + }); + // Register a custom callback for pressing enter key. + this.registerEnterCallback(this.enterCallback.bind(this)); + } + + /** + * Return the current active node. + * + * @return {Element|undefined} the active item if any + */ + getActiveItem() { + const activeItem = this.treeRoot.data('activeItem'); + if (activeItem) { + return activeItem.get(0); + } + return undefined; + } + + /** + * Handle enter key on a collpasible node. + * + * @param {JQuery} item the jQuery object + */ + enterCallback(item) { + if (this.isGroupItem(item)) { + // Group elements is like clicking a topic but without loosing the focus. + window.location.href = item.find(this.selectors.TOGGLER).first().attr('href'); + item.find(this.selectors.TOGGLER).get(0).click(); + } else { + // Activity links just follow the link href. + window.location.href = item.find('a').first().attr('href'); + return; + } + } + + /** + * Check if a gorup item is collapsed. + * + * @param {JQuery} item the jQuery object + * @returns {boolean} if the element is collapsed + */ + isGroupCollapsed(item) { + const toggler = item.find(`[aria-expanded]`); + return toggler.attr('aria-expanded') === 'false'; + } + + /** + * Toggle a group item. + * + * @param {JQuery} item the jQuery object + */ + toggleGroup(item) { + const toggler = item.find(this.selectors.COLLAPSE); + let collapsibleId = toggler.data('target') ?? toggler.attr('href'); + if (!collapsibleId) { + return; + } + collapsibleId = collapsibleId.replace('#', ''); + + // Bootstrap 4 uses jQuery to interact with collapsibles. + $(`#${collapsibleId}`).collapse('toggle'); + } + + /** + * Expand a group item. + * + * @param {JQuery} item the jQuery object + */ + expandGroup(item) { + if (this.isGroupCollapsed(item)) { + this.toggleGroup(item); + } + } + + /** + * Collpase a group item. + * + * @param {JQuery} item the jQuery object + */ + collapseGroup(item) { + if (!this.isGroupCollapsed(item)) { + this.toggleGroup(item); + } + } + + /** + * Expand all groups. + */ + expandAllGroups() { + const togglers = this.treeRoot.find(this.selectors.SECTION); + togglers.each((index, item) => { + this.expandGroup($(item)); + }); + } +} diff --git a/course/format/templates/local/courseindex/cm.mustache b/course/format/templates/local/courseindex/cm.mustache index ef89ba58f29..e65b360daa6 100644 --- a/course/format/templates/local/courseindex/cm.mustache +++ b/course/format/templates/local/courseindex/cm.mustache @@ -28,7 +28,7 @@ "isactive": 1, "uniqid": "0", "isactive": 1, - "accessvisible": 1, + "accessvisible": 1 } }}
  • {{#url}} {{#uservisible}} - + {{{name}}} {{/uservisible}} diff --git a/course/format/templates/local/courseindex/section.mustache b/course/format/templates/local/courseindex/section.mustache index 345ed159dd7..455e660e8e6 100644 --- a/course/format/templates/local/courseindex/section.mustache +++ b/course/format/templates/local/courseindex/section.mustache @@ -60,6 +60,8 @@ data-for="section" data-id="{{id}}" data-number="{{number}}" + role="treeitem" + aria-owns="courseindexcollapse{{number}}" >
    + aria-labelledby="courseindexsection{{number}}" + role="group" + >
      {{#cms}} {{> core_courseformat/local/courseindex/cm }} diff --git a/course/format/tests/behat/courseindex_keyboardnav.feature b/course/format/tests/behat/courseindex_keyboardnav.feature new file mode 100644 index 00000000000..34a5d400636 --- /dev/null +++ b/course/format/tests/behat/courseindex_keyboardnav.feature @@ -0,0 +1,99 @@ +@core @core_courseformat +Feature: Verify that courseindex is usable with the keyboard + In order to use the course index + As a user + I need to be able to navigate it without a mouse + + Background: + Given the following "course" exists: + | fullname | Course 1 | + | shortname | C1 | + | category | 0 | + | enablecompletion | 1 | + | numsections | 3 | + And the following "activities" exist: + | activity | name | intro | course | idnumber | section | + | assign | Activity sample 1 | Test assignment description | C1 | sample1 | 1 | + | book | Activity sample 2 | Test book description | C1 | sample2 | 2 | + | choice | Activity sample 3 | Test choice description | C1 | sample3 | 3 | + Given I am on the "C1" "Course" page logged in as "admin" + And I click on "Side panel" "button" + And I click on "Open course index drawer" "button" + And I should see "Topic 1" in the "courseindex-content" "region" + And the focused element is ".drawertoggle" "css_element" + And I press the tab key + Then the focused element is ".courseindex-section" "css_element" + + @javascript + Scenario: General focus on open course index. + When I press the shift tab key + And the focused element is ".drawertoggle" "css_element" + And I press enter + Then I should not see "Topic 1" in the "courseindex-content" "region" + + @javascript + Scenario: Opening and closing sections. + When I press the down key + And I should see "Activity sample 1" in the "courseindex-content" "region" + # Close section with left key. + Then I press the left key + And I should not see "Activity sample 1" in the "courseindex-content" "region" + # Open a section with right key + And I press the right key + And I should see "Activity sample 1" in the "courseindex-content" "region" + # Key down to focus the module and close the section with two left keys. + And I press the down key + And I press the left key + And I press the left key + And I should not see "Activity sample 1" in the "courseindex-content" "region" + # Open a section using enter key. + And I press the down key + And I press the left key + And I should not see "Activity sample 2" in the "courseindex-content" "region" + And I press enter + And I should see "Activity sample 2" in the "courseindex-content" "region" + + @javascript + Scenario: Navigate to an activity. + When I press the down key + And I press the right key + And I press enter + Then I should see "Activity sample 1" in the "page-header" "region" + + @javascript + Scenario: Navigate to first and last element. + # Close sections 1 and 3. + Given I press the down key + And I press the left key + And I should not see "Activity sample 1" in the "courseindex-content" "region" + And I press the down key + And I press the down key + And I press the down key + And I press the left key + And I should not see "Activity sample 3" in the "courseindex-content" "region" + # Use end key to go to the last element. + When I press the end key + And I press the right key + And I should see "Activity sample 3" in the "courseindex-content" "region" + And I press the left key + Then I should not see "Activity sample 3" in the "courseindex-content" "region" + # Use home key to go to the first element. + And I press the home key + And I press the down key + And I press the right key + And I should see "Activity sample 1" in the "courseindex-content" "region" + + @javascript + Scenario: Asterisc to open all sections. + # Close sections 1 and 2. + Given I press the down key + And I press the left key + And I should not see "Activity sample 1" in the "courseindex-content" "region" + And I press the down key + And I press the left key + And I should not see "Activity sample 2" in the "courseindex-content" "region" + And I should see "Activity sample 3" in the "courseindex-content" "region" + When I press the multiply key + Then I should see "Activity sample 1" in the "courseindex-content" "region" + And I should see "Activity sample 2" in the "courseindex-content" "region" + And I should see "Activity sample 3" in the "courseindex-content" "region" diff --git a/lib/amd/build/local/aria/aria-hidden.min.js b/lib/amd/build/local/aria/aria-hidden.min.js index f5493607069..a793cf4db40 100644 --- a/lib/amd/build/local/aria/aria-hidden.min.js +++ b/lib/amd/build/local/aria/aria-hidden.min.js @@ -1,2 +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)}}}); +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){if("childList"===a.type){a.addedNodes.forEach(g)}else if("attributes"===a.type){var b=a.target,c=b.getAttribute("tabindex");if("-1"!==c){b.dataset.ariaHiddenTabIndex=c;b.setAttribute("tabindex",-1)}}})});b.observe(a,{subtree:!0,childList:!0,attributes:!0,attributeFilter:["tabindex"]});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 diff --git a/lib/amd/build/local/aria/aria-hidden.min.js.map b/lib/amd/build/local/aria/aria-hidden.min.js.map index 35400961862..d9b723b95a6 100644 --- a/lib/amd/build/local/aria/aria-hidden.min.js.map +++ b/lib/amd/build/local/aria/aria-hidden.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/local/aria/aria-hidden.js"],"names":["childObserverMap","Map","siblingObserverMap","supportsMutationObservers","MutationObserver","disableElementFocusability","target","HTMLElement","matches","Selectors","elements","focusable","disableAndStoreTabIndex","querySelectorAll","forEach","element","dataset","ariaHiddenTabIndex","getAttribute","setAttribute","enableElementFocusability","focusableToUnhide","restoreTabIndex","closest","aria","hidden","oldTabIndex","removeAttribute","hide","_hide","newNodeObserver","mutationList","mutation","addedNodes","observe","childList","subtree","set","unhide","_unhide","has","get","disconnect","delete","hideSiblings","_hideSiblings","parentElement","childNodes","node","contains","unhideSiblings","_unhideSiblings"],"mappings":"sNAuBA,uD,GAGMA,CAAAA,CAAgB,CAAG,GAAIC,CAAAA,G,CACvBC,CAAkB,CAAG,GAAID,CAAAA,G,CAQzBE,CAAyB,CAAG,iBAAOC,CAAAA,gBAAgB,EAAgC,UAA5B,QAAOA,CAAAA,gBAAlC,C,CAQ5BC,CAA0B,CAAG,SAAAC,CAAM,CAAI,CACzC,GAAI,EAAEA,CAAM,WAAYC,CAAAA,WAApB,CAAJ,CAAsC,CAGlC,MACH,CAED,GAAID,CAAM,CAACE,OAAP,CAAeC,UAAUC,QAAV,CAAmBC,SAAlC,CAAJ,CAAkD,CAC9CC,CAAuB,CAACN,CAAD,CAC1B,CAEDA,CAAM,CAACO,gBAAP,CAAwBJ,UAAUC,QAAV,CAAmBC,SAA3C,EAAsDG,OAAtD,CAA8DF,CAA9D,CACH,C,CAQKA,CAAuB,CAAG,SAAAG,CAAO,CAAI,CACvC,GAAkD,WAA9C,QAAOA,CAAAA,CAAO,CAACC,OAAR,CAAgBC,kBAA3B,CAA+D,CAG3D,MACH,CAGD,GAAIF,CAAO,CAACG,YAAR,CAAqB,UAArB,CAAJ,CAAsC,CAClCH,CAAO,CAACC,OAAR,CAAgBC,kBAAhB,CAAqCF,CAAO,CAACG,YAAR,CAAqB,UAArB,CACxC,CAFD,IAEO,CACHH,CAAO,CAACC,OAAR,CAAgBC,kBAAhB,CAAqC,EACxC,CACDF,CAAO,CAACI,YAAR,CAAqB,UAArB,CAAiC,CAAC,CAAlC,CACH,C,CAQKC,CAAyB,CAAG,SAAAd,CAAM,CAAI,CACxC,GAAI,EAAEA,CAAM,WAAYC,CAAAA,WAApB,CAAJ,CAAsC,CAGlC,MACH,CAED,GAAID,CAAM,CAACE,OAAP,CAAeC,UAAUC,QAAV,CAAmBW,iBAAlC,CAAJ,CAA0D,CACtDC,CAAe,CAAChB,CAAD,CAClB,CAEDA,CAAM,CAACO,gBAAP,CAAwBJ,UAAUC,QAAV,CAAmBW,iBAA3C,EAA8DP,OAA9D,CAAsEQ,CAAtE,CACH,C,CAWKA,CAAe,CAAG,SAAAP,CAAO,CAAI,CAC/B,GAAIA,CAAO,CAACQ,OAAR,CAAgBd,UAAUe,IAAV,CAAeC,MAA/B,CAAJ,CAA4C,CAExC,MACH,CAED,GAAMC,CAAAA,CAAW,CAAGX,CAAO,CAACC,OAAR,CAAgBC,kBAApC,CACA,GAAoB,EAAhB,GAAAS,CAAJ,CAAwB,CACpBX,CAAO,CAACY,eAAR,CAAwB,UAAxB,CACH,CAFD,IAEO,CACHZ,CAAO,CAACI,YAAR,CAAqB,UAArB,CAAiCO,CAAjC,CACH,CAED,MAAOX,CAAAA,CAAO,CAACC,OAAR,CAAgBC,kBAC1B,C,CASYW,CAAI,CAAG,SAAAtB,CAAM,QAAI,cAAQA,CAAR,EAAgBQ,OAAhB,CAAwBe,CAAxB,CAAJ,C,aAEpBA,CAAAA,CAAK,CAAG,SAAAvB,CAAM,CAAI,CACpB,GAAI,EAAEA,CAAM,WAAYC,CAAAA,WAApB,CAAJ,CAAsC,CAGlC,MACH,CAED,GAAID,CAAM,CAACiB,OAAP,CAAed,UAAUe,IAAV,CAAeC,MAA9B,CAAJ,CAA2C,CAGvC,MACH,CAGDnB,CAAM,CAACa,YAAP,CAAoB,aAApB,KAIAd,CAA0B,CAACC,CAAD,CAA1B,CAEA,GAAIH,CAAyB,EAA7B,CAAiC,CAE7B,GAAM2B,CAAAA,CAAe,CAAG,GAAI1B,CAAAA,gBAAJ,CAAqB,SAAA2B,CAAY,CAAI,CACzDA,CAAY,CAACjB,OAAb,CAAqB,SAAAkB,CAAQ,CAAI,CAC7BA,CAAQ,CAACC,UAAT,CAAoBnB,OAApB,CAA4BT,CAA5B,CACH,CAFD,CAGH,CAJuB,CAAxB,CAMAyB,CAAe,CAACI,OAAhB,CAAwB5B,CAAxB,CAAgC,CAAC6B,SAAS,GAAV,CAAkBC,OAAO,GAAzB,CAAhC,EACApC,CAAgB,CAACqC,GAAjB,CAAqB/B,CAArB,CAA6BwB,CAA7B,CACH,CACJ,C,CASYQ,CAAM,CAAG,SAAAhC,CAAM,QAAI,cAAQA,CAAR,EAAgBQ,OAAhB,CAAwByB,CAAxB,CAAJ,C,eAEtBA,CAAAA,CAAO,CAAG,SAAAjC,CAAM,CAAI,CACtB,GAAI,EAAEA,CAAM,WAAYC,CAAAA,WAApB,CAAJ,CAAsC,CAClC,MACH,CAIDD,CAAM,CAACqB,eAAP,CAAuB,aAAvB,EAGAP,CAAyB,CAACd,CAAD,CAAzB,CAGA,GAAIN,CAAgB,CAACwC,GAAjB,CAAqBlC,CAArB,CAAJ,CAAkC,CAC9BN,CAAgB,CAACyC,GAAjB,CAAqBnC,CAArB,EAA6BoC,UAA7B,GACA1C,CAAgB,CAAC2C,MAAjB,CAAwBrC,CAAxB,CACH,CACJ,C,gBAS2B,QAAfsC,CAAAA,YAAe,CAAAtC,CAAM,QAAI,cAAQA,CAAR,EAAgBQ,OAAhB,CAAwB+B,CAAxB,CAAJ,C,IAE5BA,CAAAA,CAAa,CAAG,SAAAvC,CAAM,CAAI,CAC5B,GAAI,EAAEA,CAAM,WAAYC,CAAAA,WAApB,CAAJ,CAAsC,CAClC,MACH,CAED,GAAI,CAACD,CAAM,CAACwC,aAAZ,CAA2B,CACvB,MACH,CAEDxC,CAAM,CAACwC,aAAP,CAAqBC,UAArB,CAAgCjC,OAAhC,CAAwC,SAAAkC,CAAI,CAAI,CAC5C,GAAIA,CAAI,GAAK1C,CAAb,CAAqB,CAEjB,MACH,CAEDsB,CAAI,CAACoB,CAAD,CACP,CAPD,EASA,GAAI7C,CAAyB,EAA7B,CAAiC,CAE7B,GAAM2B,CAAAA,CAAe,CAAG,GAAI1B,CAAAA,gBAAJ,CAAqB,SAAA2B,CAAY,CAAI,CACzDA,CAAY,CAACjB,OAAb,CAAqB,SAAAkB,CAAQ,CAAI,CAC7BA,CAAQ,CAACC,UAAT,CAAoBnB,OAApB,CAA4B,SAAAkC,CAAI,CAAI,CAChC,GAAI1C,CAAM,CAAC2C,QAAP,CAAgBD,CAAhB,CAAJ,CAA2B,CAEvB,MACH,CAEDpB,CAAI,CAACoB,CAAD,CACP,CAPD,CAQH,CATD,CAUH,CAXuB,CAAxB,CAaAlB,CAAe,CAACI,OAAhB,CAAwB5B,CAAM,CAACwC,aAA/B,CAA8C,CAACX,SAAS,GAAV,CAAkBC,OAAO,GAAzB,CAA9C,EACAlC,CAAkB,CAACmC,GAAnB,CAAuB/B,CAAM,CAACwC,aAA9B,CAA6ChB,CAA7C,CACH,CACJ,C,kBAS6B,QAAjBoB,CAAAA,cAAiB,CAAA5C,CAAM,QAAI,cAAQA,CAAR,EAAgBQ,OAAhB,CAAwBqC,CAAxB,CAAJ,C,CAEpC,GAAMA,CAAAA,CAAe,CAAG,SAAA7C,CAAM,CAAI,CAC9B,GAAI,EAAEA,CAAM,WAAYC,CAAAA,WAApB,CAAJ,CAAsC,CAClC,MACH,CAED,GAAI,CAACD,CAAM,CAACwC,aAAZ,CAA2B,CACvB,MACH,CAEDxC,CAAM,CAACwC,aAAP,CAAqBC,UAArB,CAAgCjC,OAAhC,CAAwC,SAAAkC,CAAI,CAAI,CAC5C,GAAIA,CAAI,GAAK1C,CAAb,CAAqB,CAEjB,MACH,CAEDgC,CAAM,CAACU,CAAD,CACT,CAPD,EAUA,GAAI9C,CAAkB,CAACsC,GAAnB,CAAuBlC,CAAM,CAACwC,aAA9B,CAAJ,CAAkD,CAC9C5C,CAAkB,CAACuC,GAAnB,CAAuBnC,CAAM,CAACwC,aAA9B,EAA6CJ,UAA7C,GACAxC,CAAkB,CAACyC,MAAnB,CAA0BrC,CAAM,CAACwC,aAAjC,CACH,CACJ,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 .\n\n/**\n * ARIA helpers related to the aria-hidden attribute.\n *\n * @module core/local/aria/aria-hidden.\n * @copyright 2020 Andrew Nicols \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 newNodeObserver = new MutationObserver(mutationList => {\n mutationList.forEach(mutation => {\n mutation.addedNodes.forEach(disableElementFocusability);\n });\n });\n\n newNodeObserver.observe(target, {childList: true, subtree: true});\n childObserverMap.set(target, newNodeObserver);\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"],"file":"aria-hidden.min.js"} \ No newline at end of file +{"version":3,"sources":["../../../src/local/aria/aria-hidden.js"],"names":["childObserverMap","Map","siblingObserverMap","supportsMutationObservers","MutationObserver","disableElementFocusability","target","HTMLElement","matches","Selectors","elements","focusable","disableAndStoreTabIndex","querySelectorAll","forEach","element","dataset","ariaHiddenTabIndex","getAttribute","setAttribute","enableElementFocusability","focusableToUnhide","restoreTabIndex","closest","aria","hidden","oldTabIndex","removeAttribute","hide","_hide","mutationObserver","mutationList","mutation","type","addedNodes","proposedTabIndex","observe","subtree","childList","attributes","attributeFilter","set","unhide","_unhide","has","get","disconnect","delete","hideSiblings","_hideSiblings","parentElement","childNodes","node","newNodeObserver","contains","unhideSiblings","_unhideSiblings"],"mappings":"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","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 .\n\n/**\n * ARIA helpers related to the aria-hidden attribute.\n *\n * @module core/local/aria/aria-hidden.\n * @copyright 2020 Andrew Nicols \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"],"file":"aria-hidden.min.js"} \ No newline at end of file diff --git a/lib/amd/build/tree.min.js.map b/lib/amd/build/tree.min.js.map index f3385b19803..a62c8da2e9b 100644 --- a/lib/amd/build/tree.min.js.map +++ b/lib/amd/build/tree.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/tree.js"],"names":["define","$","SELECTORS","ITEM","GROUP","CLOSED_GROUP","FIRST_ITEM","VISIBLE_ITEM","UNLOADED_AJAX_ITEM","Tree","selector","selectCallback","treeRoot","data","keys","tab","enter","space","pageup","pagedown","end","home","left","up","right","down","asterisk","initialiseNodes","setActiveItem","find","refreshVisibleItemsCache","bindEventHandlers","prototype","registerEnterCallback","callback","enterCallback","getVisibleItems","item","currentActive","attr","isGroupItem","is","getGroupFromItem","ariaowns","plain","children","length","isGroupCollapsed","isGroupCollapsible","node","removeAllFromTabOrder","setAriaSelectedFalseOnItems","thisTree","each","unloadedNode","collapseGroup","expandGroup","expandAllGroups","groupNode","done","expandAllChildGroups","promise","Deferred","moduleName","closest","p","addClass","require","loader","load","finishExpandingGroup","removeClass","resolve","group","removeAttr","toggleGroup","handleKeyDown","e","target","currentIndex","index","altKey","ctrlKey","metaKey","shiftKey","keyCode","first","focus","preventDefault","last","links","not","triggerHandler","window","location","href","firstLink","focusParent","tree","filter","has","prev","eq","next","handleClick","currentTarget","handleFocus","on","click","bind","keydown"],"mappings":"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","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 .\n\n/**\n * Implement an accessible aria tree widget, from a nested unordered list.\n * Based on http://oaa-accessibility.org/example/41/.\n *\n * @module tool_lp/tree\n * @copyright 2015 Damyon Wiese \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery'], function($) {\n // Private variables and functions.\n var SELECTORS = {\n ITEM: '[role=treeitem]',\n GROUP: '[role=treeitem]:has([role=group]), [role=treeitem][aria-owns], [role=treeitem][data-requires-ajax=true]',\n CLOSED_GROUP: '[role=treeitem]:has([role=group])[aria-expanded=false], [role=treeitem][aria-owns][aria-expanded=false], ' +\n '[role=treeitem][data-requires-ajax=true][aria-expanded=false]',\n FIRST_ITEM: '[role=treeitem]:first',\n VISIBLE_ITEM: '[role=treeitem]:visible',\n UNLOADED_AJAX_ITEM: '[role=treeitem][data-requires-ajax=true][data-loaded=false][aria-expanded=true]'\n };\n\n /**\n * Constructor.\n *\n * @param {String} selector\n * @param {function} selectCallback Called when the active node is changed.\n */\n var Tree = function(selector, selectCallback) {\n this.treeRoot = $(selector);\n\n this.treeRoot.data('activeItem', null);\n this.selectCallback = selectCallback;\n this.keys = {\n tab: 9,\n enter: 13,\n space: 32,\n pageup: 33,\n pagedown: 34,\n end: 35,\n home: 36,\n left: 37,\n up: 38,\n right: 39,\n down: 40,\n asterisk: 106\n };\n\n // Apply the standard default initialisation for all nodes, starting with the tree root.\n this.initialiseNodes(this.treeRoot);\n // Make the first item the active item for the tree so that it is added to the tab order.\n this.setActiveItem(this.treeRoot.find(SELECTORS.FIRST_ITEM));\n // Create the cache of the visible items.\n this.refreshVisibleItemsCache();\n // Create the event handlers for the tree.\n this.bindEventHandlers();\n };\n\n Tree.prototype.registerEnterCallback = function(callback) {\n this.enterCallback = callback;\n };\n\n /**\n * Find all visible tree items and save a cache of them on the tree object.\n *\n * @method refreshVisibleItemsCache\n */\n Tree.prototype.refreshVisibleItemsCache = function() {\n this.treeRoot.data('visibleItems', this.treeRoot.find(SELECTORS.VISIBLE_ITEM));\n };\n\n /**\n * Get all visible tree items.\n *\n * @method getVisibleItems\n * @return {Object} visible items\n */\n Tree.prototype.getVisibleItems = function() {\n return this.treeRoot.data('visibleItems');\n };\n\n /**\n * Mark the given item as active within the tree and fire the callback for when the active item is set.\n *\n * @method setActiveItem\n * @param {object} item jquery object representing an item on the tree.\n */\n Tree.prototype.setActiveItem = function(item) {\n var currentActive = this.treeRoot.data('activeItem');\n if (item === currentActive) {\n return;\n }\n\n // Remove previous active from tab order.\n if (currentActive) {\n currentActive.attr('tabindex', '-1');\n currentActive.attr('aria-selected', 'false');\n }\n item.attr('tabindex', '0');\n item.attr('aria-selected', 'true');\n\n // Set the new active item.\n this.treeRoot.data('activeItem', item);\n\n if (typeof this.selectCallback === 'function') {\n this.selectCallback(item);\n }\n };\n\n /**\n * Determines if the given item is a group item (contains child tree items) in the tree.\n *\n * @method isGroupItem\n * @param {object} item jquery object representing an item on the tree.\n * @returns {bool}\n */\n Tree.prototype.isGroupItem = function(item) {\n return item.is(SELECTORS.GROUP);\n };\n\n /**\n * Determines if the given item is a group item (contains child tree items) in the tree.\n *\n * @method isGroupItem\n * @param {object} item jquery object representing an item on the tree.\n * @returns {bool}\n */\n Tree.prototype.getGroupFromItem = function(item) {\n var ariaowns = this.treeRoot.find('#' + item.attr('aria-owns'));\n var plain = item.children('[role=group]');\n if (ariaowns.length > plain.length) {\n return ariaowns;\n } else {\n return plain;\n }\n };\n\n /**\n * Determines if the given group item (contains child tree items) is collapsed.\n *\n * @method isGroupCollapsed\n * @param {object} item jquery object representing a group item on the tree.\n * @returns {bool}\n */\n Tree.prototype.isGroupCollapsed = function(item) {\n return item.attr('aria-expanded') === 'false';\n };\n\n /**\n * Determines if the given group item (contains child tree items) can be collapsed.\n *\n * @method isGroupCollapsible\n * @param {object} item jquery object representing a group item on the tree.\n * @returns {bool}\n */\n Tree.prototype.isGroupCollapsible = function(item) {\n return item.attr('data-collapsible') !== 'false';\n };\n\n /**\n * Performs the tree initialisation for all child items from the given node,\n * such as removing everything from the tab order and setting aria selected\n * on items.\n *\n * @method initialiseNodes\n * @param {object} node jquery object representing a node.\n */\n Tree.prototype.initialiseNodes = function(node) {\n this.removeAllFromTabOrder(node);\n this.setAriaSelectedFalseOnItems(node);\n\n // Get all ajax nodes that have been rendered as expanded but haven't loaded the child items yet.\n var thisTree = this;\n node.find(SELECTORS.UNLOADED_AJAX_ITEM).each(function() {\n var unloadedNode = $(this);\n // Collapse and then expand to trigger the ajax loading.\n thisTree.collapseGroup(unloadedNode);\n thisTree.expandGroup(unloadedNode);\n });\n };\n\n /**\n * Removes all child DOM elements of the given node from the tab order.\n *\n * @method removeAllFromTabOrder\n * @param {object} node jquery object representing a node.\n */\n Tree.prototype.removeAllFromTabOrder = function(node) {\n node.find('*').attr('tabindex', '-1');\n this.getGroupFromItem($(node)).find('*').attr('tabindex', '-1');\n };\n\n /**\n * Find all child tree items from the given node and set the aria selected attribute to false.\n *\n * @method setAriaSelectedFalseOnItems\n * @param {object} node jquery object representing a node.\n */\n Tree.prototype.setAriaSelectedFalseOnItems = function(node) {\n node.find(SELECTORS.ITEM).attr('aria-selected', 'false');\n };\n\n /**\n * Expand all group nodes within the tree.\n *\n * @method expandAllGroups\n */\n Tree.prototype.expandAllGroups = function() {\n var thisTree = this;\n\n this.treeRoot.find(SELECTORS.CLOSED_GROUP).each(function() {\n var groupNode = $(this);\n\n thisTree.expandGroup($(this)).done(function() {\n thisTree.expandAllChildGroups(groupNode);\n });\n });\n };\n\n /**\n * Find all child group nodes from the given node and expand them.\n *\n * @method expandAllChildGroups\n * @param {Object} item is the jquery id of the group.\n */\n Tree.prototype.expandAllChildGroups = function(item) {\n var thisTree = this;\n\n this.getGroupFromItem(item).find(SELECTORS.CLOSED_GROUP).each(function() {\n var groupNode = $(this);\n\n thisTree.expandGroup($(this)).done(function() {\n thisTree.expandAllChildGroups(groupNode);\n });\n });\n };\n\n /**\n * Expand a collapsed group.\n *\n * Handles expanding nodes that are ajax loaded (marked with a data-requires-ajax attribute).\n *\n * @method expandGroup\n * @param {Object} item is the jquery id of the parent item of the group.\n * @return {Object} a promise that is resolved when the group has been expanded.\n */\n Tree.prototype.expandGroup = function(item) {\n var promise = $.Deferred();\n // Ignore nodes that are explicitly maked as not expandable or are already expanded.\n if (item.attr('data-expandable') !== 'false' && this.isGroupCollapsed(item)) {\n // If this node requires ajax load and we haven't already loaded it.\n if (item.attr('data-requires-ajax') === 'true' && item.attr('data-loaded') !== 'true') {\n item.attr('data-loaded', false);\n // Get the closes ajax loading module specificed in the tree.\n var moduleName = item.closest('[data-ajax-loader]').attr('data-ajax-loader');\n var thisTree = this;\n // Flag this node as loading.\n const p = item.find('p');\n p.addClass('loading');\n // Require the ajax module (must be AMD) and try to load the items.\n require([moduleName], function(loader) {\n // All ajax module must implement a \"load\" method.\n loader.load(item).done(function() {\n item.attr('data-loaded', true);\n\n // Set defaults on the newly constructed part of the tree.\n thisTree.initialiseNodes(item);\n thisTree.finishExpandingGroup(item);\n // Make sure no child elements of the item we just loaded are tabbable.\n p.removeClass('loading');\n promise.resolve();\n });\n });\n } else {\n this.finishExpandingGroup(item);\n promise.resolve();\n }\n } else {\n promise.resolve();\n }\n return promise;\n };\n\n /**\n * Perform the necessary DOM changes to display a group item.\n *\n * @method finishExpandingGroup\n * @param {Object} item is the jquery id of the parent item of the group.\n */\n Tree.prototype.finishExpandingGroup = function(item) {\n // Expand the group.\n var group = this.getGroupFromItem(item);\n group.removeAttr('aria-hidden');\n item.attr('aria-expanded', 'true');\n\n // Update the list of visible items.\n this.refreshVisibleItemsCache();\n };\n\n /**\n * Collapse an expanded group.\n *\n * @method collapseGroup\n * @param {Object} item is the jquery id of the parent item of the group.\n */\n Tree.prototype.collapseGroup = function(item) {\n // If the item is not collapsible or already collapsed then do nothing.\n if (!this.isGroupCollapsible(item) || this.isGroupCollapsed(item)) {\n return;\n }\n\n // Collapse the group.\n var group = this.getGroupFromItem(item);\n group.attr('aria-hidden', 'true');\n item.attr('aria-expanded', 'false');\n\n // Update the list of visible items.\n this.refreshVisibleItemsCache();\n };\n\n /**\n * Expand or collapse a group.\n *\n * @method toggleGroup\n * @param {Object} item is the jquery id of the parent item of the group.\n */\n Tree.prototype.toggleGroup = function(item) {\n if (item.attr('aria-expanded') === 'true') {\n this.collapseGroup(item);\n } else {\n this.expandGroup(item);\n }\n };\n\n /**\n * Handle a key down event - ie navigate the tree.\n *\n * @method handleKeyDown\n * @param {Event} e The event.\n */\n // This function should be simplified. In the meantime..\n // eslint-disable-next-line complexity\n Tree.prototype.handleKeyDown = function(e) {\n var item = $(e.target);\n var currentIndex = this.getVisibleItems().index(item);\n\n if ((e.altKey || e.ctrlKey || e.metaKey) || (e.shiftKey && e.keyCode != this.keys.tab)) {\n // Do nothing.\n return;\n }\n\n switch (e.keyCode) {\n case this.keys.home: {\n // Jump to first item in tree.\n this.getVisibleItems().first().focus();\n\n e.preventDefault();\n return;\n }\n case this.keys.end: {\n // Jump to last visible item.\n this.getVisibleItems().last().focus();\n\n e.preventDefault();\n return;\n }\n case this.keys.enter: {\n var links = item.children('a').length ? item.children('a') : item.children().not(SELECTORS.GROUP).find('a');\n if (links.length) {\n if (links.first().data('overrides-tree-activation-key-handler')) {\n // If the link overrides handling of activation keys, let it do so.\n links.first().triggerHandler(e);\n } else if (typeof this.enterCallback === 'function') {\n // Use callback if there is one.\n this.enterCallback(item);\n } else {\n window.location.href = links.first().attr('href');\n }\n } else if (this.isGroupItem(item)) {\n this.toggleGroup(item, true);\n }\n\n e.preventDefault();\n return;\n }\n case this.keys.space: {\n if (this.isGroupItem(item)) {\n this.toggleGroup(item, true);\n } else if (item.children('a').length) {\n var firstLink = item.children('a').first();\n\n if (firstLink.data('overrides-tree-activation-key-handler')) {\n firstLink.triggerHandler(e);\n }\n }\n\n e.preventDefault();\n return;\n }\n case this.keys.left: {\n var focusParent = function(tree) {\n // Get the immediate visible parent group item that contains this element.\n tree.getVisibleItems().filter(function() {\n return tree.getGroupFromItem($(this)).has(item).length;\n }).focus();\n };\n\n // If this is a goup item then collapse it and focus the parent group\n // in accordance with the aria spec.\n if (this.isGroupItem(item)) {\n if (this.isGroupCollapsed(item)) {\n focusParent(this);\n } else {\n this.collapseGroup(item);\n }\n } else {\n focusParent(this);\n }\n\n e.preventDefault();\n return;\n }\n case this.keys.right: {\n // If this is a group item then expand it and focus the first child item\n // in accordance with the aria spec.\n if (this.isGroupItem(item)) {\n if (this.isGroupCollapsed(item)) {\n this.expandGroup(item);\n } else {\n // Move to the first item in the child group.\n this.getGroupFromItem(item).find(SELECTORS.ITEM).first().focus();\n }\n }\n\n e.preventDefault();\n return;\n }\n case this.keys.up: {\n\n if (currentIndex > 0) {\n var prev = this.getVisibleItems().eq(currentIndex - 1);\n\n prev.focus();\n }\n\n e.preventDefault();\n return;\n }\n case this.keys.down: {\n\n if (currentIndex < this.getVisibleItems().length - 1) {\n var next = this.getVisibleItems().eq(currentIndex + 1);\n\n next.focus();\n }\n\n e.preventDefault();\n return;\n }\n case this.keys.asterisk: {\n // Expand all groups.\n this.expandAllGroups();\n e.preventDefault();\n return;\n }\n }\n };\n\n /**\n * Handle a click (select).\n *\n * @method handleClick\n * @param {Event} e The event.\n */\n Tree.prototype.handleClick = function(e) {\n if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) {\n // Do nothing.\n return;\n }\n\n // Get the closest tree item from the event target.\n var item = $(e.target).closest('[role=\"treeitem\"]');\n if (!item.is(e.currentTarget)) {\n return;\n }\n\n // Update the active item.\n item.focus();\n\n // If the item is a group node.\n if (this.isGroupItem(item)) {\n this.toggleGroup(item);\n }\n };\n\n /**\n * Handle a focus event.\n *\n * @method handleFocus\n * @param {Event} e The event.\n */\n Tree.prototype.handleFocus = function(e) {\n this.setActiveItem($(e.target));\n };\n\n /**\n * Bind the event listeners we require.\n *\n * @method bindEventHandlers\n */\n Tree.prototype.bindEventHandlers = function() {\n // Bind event handlers to the tree items. Use event delegates to allow\n // for dynamically loaded parts of the tree.\n this.treeRoot.on({\n click: this.handleClick.bind(this),\n keydown: this.handleKeyDown.bind(this),\n focus: this.handleFocus.bind(this),\n }, SELECTORS.ITEM);\n };\n\n return /** @alias module:tool_lp/tree */ Tree;\n});\n"],"file":"tree.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/tree.js"],"names":["define","$","SELECTORS","ITEM","GROUP","CLOSED_GROUP","FIRST_ITEM","VISIBLE_ITEM","UNLOADED_AJAX_ITEM","Tree","selector","selectCallback","treeRoot","data","keys","tab","enter","space","pageup","pagedown","end","home","left","up","right","down","asterisk","initialiseNodes","setActiveItem","find","refreshVisibleItemsCache","bindEventHandlers","prototype","registerEnterCallback","callback","enterCallback","getVisibleItems","item","currentActive","attr","isGroupItem","is","getGroupFromItem","ariaowns","plain","children","length","isGroupCollapsed","isGroupCollapsible","node","removeAllFromTabOrder","setAriaSelectedFalseOnItems","thisTree","each","unloadedNode","collapseGroup","expandGroup","expandAllGroups","groupNode","done","expandAllChildGroups","promise","Deferred","moduleName","closest","p","addClass","require","loader","load","finishExpandingGroup","removeClass","resolve","group","removeAttr","toggleGroup","handleKeyDown","e","target","currentIndex","index","altKey","ctrlKey","metaKey","shiftKey","keyCode","first","focus","preventDefault","last","links","not","triggerHandler","window","location","href","firstLink","focusParent","tree","filter","has","prev","eq","next","handleClick","currentTarget","handleFocus","on","click","bind","keydown"],"mappings":"AAuBAA,OAAM,aAAC,CAAC,QAAD,CAAD,CAAa,SAASC,CAAT,CAAY,IAEvBC,CAAAA,CAAS,CAAG,CACZC,IAAI,CAAE,iBADM,CAEZC,KAAK,CAAE,yGAFK,CAGZC,YAAY,yKAHA,CAKZC,UAAU,CAAE,uBALA,CAMZC,YAAY,CAAE,yBANF,CAOZC,kBAAkB,CAAE,iFAPR,CAFW,CAkBvBC,CAAI,CAAG,SAASC,CAAT,CAAmBC,CAAnB,CAAmC,CAC1C,KAAKC,QAAL,CAAgBX,CAAC,CAACS,CAAD,CAAjB,CAEA,KAAKE,QAAL,CAAcC,IAAd,CAAmB,YAAnB,CAAiC,IAAjC,EACA,KAAKF,cAAL,CAAsBA,CAAtB,CACA,KAAKG,IAAL,CAAY,CACRC,GAAG,CAAO,CADF,CAERC,KAAK,CAAK,EAFF,CAGRC,KAAK,CAAK,EAHF,CAIRC,MAAM,CAAI,EAJF,CAKRC,QAAQ,CAAE,EALF,CAMRC,GAAG,CAAO,EANF,CAORC,IAAI,CAAM,EAPF,CAQRC,IAAI,CAAM,EARF,CASRC,EAAE,CAAQ,EATF,CAURC,KAAK,CAAK,EAVF,CAWRC,IAAI,CAAM,EAXF,CAYRC,QAAQ,CAAE,GAZF,CAAZ,CAgBA,KAAKC,eAAL,CAAqB,KAAKf,QAA1B,EAEA,KAAKgB,aAAL,CAAmB,KAAKhB,QAAL,CAAciB,IAAd,CAAmB3B,CAAS,CAACI,UAA7B,CAAnB,EAEA,KAAKwB,wBAAL,GAEA,KAAKC,iBAAL,EACH,CA9C0B,CAgD3BtB,CAAI,CAACuB,SAAL,CAAeC,qBAAf,CAAuC,SAASC,CAAT,CAAmB,CACtD,KAAKC,aAAL,CAAqBD,CACxB,CAFD,CASAzB,CAAI,CAACuB,SAAL,CAAeF,wBAAf,CAA0C,UAAW,CACjD,KAAKlB,QAAL,CAAcC,IAAd,CAAmB,cAAnB,CAAmC,KAAKD,QAAL,CAAciB,IAAd,CAAmB3B,CAAS,CAACK,YAA7B,CAAnC,CACH,CAFD,CAUAE,CAAI,CAACuB,SAAL,CAAeI,eAAf,CAAiC,UAAW,CACxC,MAAO,MAAKxB,QAAL,CAAcC,IAAd,CAAmB,cAAnB,CACV,CAFD,CAUAJ,CAAI,CAACuB,SAAL,CAAeJ,aAAf,CAA+B,SAASS,CAAT,CAAe,CAC1C,GAAIC,CAAAA,CAAa,CAAG,KAAK1B,QAAL,CAAcC,IAAd,CAAmB,YAAnB,CAApB,CACA,GAAIwB,CAAI,GAAKC,CAAb,CAA4B,CACxB,MACH,CAGD,GAAIA,CAAJ,CAAmB,CACfA,CAAa,CAACC,IAAd,CAAmB,UAAnB,CAA+B,IAA/B,EACAD,CAAa,CAACC,IAAd,CAAmB,eAAnB,CAAoC,OAApC,CACH,CACDF,CAAI,CAACE,IAAL,CAAU,UAAV,CAAsB,GAAtB,EACAF,CAAI,CAACE,IAAL,CAAU,eAAV,CAA2B,MAA3B,EAGA,KAAK3B,QAAL,CAAcC,IAAd,CAAmB,YAAnB,CAAiCwB,CAAjC,EAEA,GAAmC,UAA/B,QAAO,MAAK1B,cAAhB,CAA+C,CAC3C,KAAKA,cAAL,CAAoB0B,CAApB,CACH,CACJ,CApBD,CA6BA5B,CAAI,CAACuB,SAAL,CAAeQ,WAAf,CAA6B,SAASH,CAAT,CAAe,CACxC,MAAOA,CAAAA,CAAI,CAACI,EAAL,CAAQvC,CAAS,CAACE,KAAlB,CACV,CAFD,CAWAK,CAAI,CAACuB,SAAL,CAAeU,gBAAf,CAAkC,SAASL,CAAT,CAAe,IACzCM,CAAAA,CAAQ,CAAG,KAAK/B,QAAL,CAAciB,IAAd,CAAmB,IAAMQ,CAAI,CAACE,IAAL,CAAU,WAAV,CAAzB,CAD8B,CAEzCK,CAAK,CAAGP,CAAI,CAACQ,QAAL,CAAc,cAAd,CAFiC,CAG7C,GAAIF,CAAQ,CAACG,MAAT,CAAkBF,CAAK,CAACE,MAA5B,CAAoC,CAChC,MAAOH,CAAAA,CACV,CAFD,IAEO,CACH,MAAOC,CAAAA,CACV,CACJ,CARD,CAiBAnC,CAAI,CAACuB,SAAL,CAAee,gBAAf,CAAkC,SAASV,CAAT,CAAe,CAC7C,MAAsC,OAA/B,GAAAA,CAAI,CAACE,IAAL,CAAU,eAAV,CACV,CAFD,CAWA9B,CAAI,CAACuB,SAAL,CAAegB,kBAAf,CAAoC,SAASX,CAAT,CAAe,CAC/C,MAAyC,OAAlC,GAAAA,CAAI,CAACE,IAAL,CAAU,kBAAV,CACV,CAFD,CAYA9B,CAAI,CAACuB,SAAL,CAAeL,eAAf,CAAiC,SAASsB,CAAT,CAAe,CAC5C,KAAKC,qBAAL,CAA2BD,CAA3B,EACA,KAAKE,2BAAL,CAAiCF,CAAjC,EAGA,GAAIG,CAAAA,CAAQ,CAAG,IAAf,CACAH,CAAI,CAACpB,IAAL,CAAU3B,CAAS,CAACM,kBAApB,EAAwC6C,IAAxC,CAA6C,UAAW,CACpD,GAAIC,CAAAA,CAAY,CAAGrD,CAAC,CAAC,IAAD,CAApB,CAEAmD,CAAQ,CAACG,aAAT,CAAuBD,CAAvB,EACAF,CAAQ,CAACI,WAAT,CAAqBF,CAArB,CACH,CALD,CAMH,CAZD,CAoBA7C,CAAI,CAACuB,SAAL,CAAekB,qBAAf,CAAuC,SAASD,CAAT,CAAe,CAClDA,CAAI,CAACpB,IAAL,CAAU,GAAV,EAAeU,IAAf,CAAoB,UAApB,CAAgC,IAAhC,EACA,KAAKG,gBAAL,CAAsBzC,CAAC,CAACgD,CAAD,CAAvB,EAA+BpB,IAA/B,CAAoC,GAApC,EAAyCU,IAAzC,CAA8C,UAA9C,CAA0D,IAA1D,CACH,CAHD,CAWA9B,CAAI,CAACuB,SAAL,CAAemB,2BAAf,CAA6C,SAASF,CAAT,CAAe,CACxDA,CAAI,CAACpB,IAAL,CAAU3B,CAAS,CAACC,IAApB,EAA0BoC,IAA1B,CAA+B,eAA/B,CAAgD,OAAhD,CACH,CAFD,CASA9B,CAAI,CAACuB,SAAL,CAAeyB,eAAf,CAAiC,UAAW,CACxC,GAAIL,CAAAA,CAAQ,CAAG,IAAf,CAEA,KAAKxC,QAAL,CAAciB,IAAd,CAAmB3B,CAAS,CAACG,YAA7B,EAA2CgD,IAA3C,CAAgD,UAAW,CACvD,GAAIK,CAAAA,CAAS,CAAGzD,CAAC,CAAC,IAAD,CAAjB,CAEAmD,CAAQ,CAACI,WAAT,CAAqBvD,CAAC,CAAC,IAAD,CAAtB,EAA8B0D,IAA9B,CAAmC,UAAW,CAC1CP,CAAQ,CAACQ,oBAAT,CAA8BF,CAA9B,CACH,CAFD,CAGH,CAND,CAOH,CAVD,CAkBAjD,CAAI,CAACuB,SAAL,CAAe4B,oBAAf,CAAsC,SAASvB,CAAT,CAAe,CACjD,GAAIe,CAAAA,CAAQ,CAAG,IAAf,CAEA,KAAKV,gBAAL,CAAsBL,CAAtB,EAA4BR,IAA5B,CAAiC3B,CAAS,CAACG,YAA3C,EAAyDgD,IAAzD,CAA8D,UAAW,CACrE,GAAIK,CAAAA,CAAS,CAAGzD,CAAC,CAAC,IAAD,CAAjB,CAEAmD,CAAQ,CAACI,WAAT,CAAqBvD,CAAC,CAAC,IAAD,CAAtB,EAA8B0D,IAA9B,CAAmC,UAAW,CAC1CP,CAAQ,CAACQ,oBAAT,CAA8BF,CAA9B,CACH,CAFD,CAGH,CAND,CAOH,CAVD,CAqBAjD,CAAI,CAACuB,SAAL,CAAewB,WAAf,CAA6B,SAASnB,CAAT,CAAe,CACxC,GAAIwB,CAAAA,CAAO,CAAG5D,CAAC,CAAC6D,QAAF,EAAd,CAEA,GAAqC,OAAjC,GAAAzB,CAAI,CAACE,IAAL,CAAU,iBAAV,GAA4C,KAAKQ,gBAAL,CAAsBV,CAAtB,CAAhD,CAA6E,CAEzE,GAAwC,MAApC,GAAAA,CAAI,CAACE,IAAL,CAAU,oBAAV,GAA2E,MAA7B,GAAAF,CAAI,CAACE,IAAL,CAAU,aAAV,CAAlD,CAAuF,CACnFF,CAAI,CAACE,IAAL,CAAU,aAAV,KADmF,GAG/EwB,CAAAA,CAAU,CAAG1B,CAAI,CAAC2B,OAAL,CAAa,oBAAb,EAAmCzB,IAAnC,CAAwC,kBAAxC,CAHkE,CAI/Ea,CAAQ,CAAG,IAJoE,CAM7Ea,CAAC,CAAG5B,CAAI,CAACR,IAAL,CAAU,GAAV,CANyE,CAOnFoC,CAAC,CAACC,QAAF,CAAW,SAAX,EAEAC,OAAO,CAAC,CAACJ,CAAD,CAAD,CAAe,SAASK,CAAT,CAAiB,CAEnCA,CAAM,CAACC,IAAP,CAAYhC,CAAZ,EAAkBsB,IAAlB,CAAuB,UAAW,CAC9BtB,CAAI,CAACE,IAAL,CAAU,aAAV,KAGAa,CAAQ,CAACzB,eAAT,CAAyBU,CAAzB,EACAe,CAAQ,CAACkB,oBAAT,CAA8BjC,CAA9B,EAEA4B,CAAC,CAACM,WAAF,CAAc,SAAd,EACAV,CAAO,CAACW,OAAR,EACH,CATD,CAUH,CAZM,CAaV,CAtBD,IAsBO,CACH,KAAKF,oBAAL,CAA0BjC,CAA1B,EACAwB,CAAO,CAACW,OAAR,EACH,CACJ,CA5BD,IA4BO,CACHX,CAAO,CAACW,OAAR,EACH,CACD,MAAOX,CAAAA,CACV,CAnCD,CA2CApD,CAAI,CAACuB,SAAL,CAAesC,oBAAf,CAAsC,SAASjC,CAAT,CAAe,CAEjD,GAAIoC,CAAAA,CAAK,CAAG,KAAK/B,gBAAL,CAAsBL,CAAtB,CAAZ,CACAoC,CAAK,CAACC,UAAN,CAAiB,aAAjB,EACArC,CAAI,CAACE,IAAL,CAAU,eAAV,CAA2B,MAA3B,EAGA,KAAKT,wBAAL,EACH,CARD,CAgBArB,CAAI,CAACuB,SAAL,CAAeuB,aAAf,CAA+B,SAASlB,CAAT,CAAe,CAE1C,GAAI,CAAC,KAAKW,kBAAL,CAAwBX,CAAxB,CAAD,EAAkC,KAAKU,gBAAL,CAAsBV,CAAtB,CAAtC,CAAmE,CAC/D,MACH,CAGD,GAAIoC,CAAAA,CAAK,CAAG,KAAK/B,gBAAL,CAAsBL,CAAtB,CAAZ,CACAoC,CAAK,CAAClC,IAAN,CAAW,aAAX,CAA0B,MAA1B,EACAF,CAAI,CAACE,IAAL,CAAU,eAAV,CAA2B,OAA3B,EAGA,KAAKT,wBAAL,EACH,CAbD,CAqBArB,CAAI,CAACuB,SAAL,CAAe2C,WAAf,CAA6B,SAAStC,CAAT,CAAe,CACxC,GAAmC,MAA/B,GAAAA,CAAI,CAACE,IAAL,CAAU,eAAV,CAAJ,CAA2C,CACvC,KAAKgB,aAAL,CAAmBlB,CAAnB,CACH,CAFD,IAEO,CACH,KAAKmB,WAAL,CAAiBnB,CAAjB,CACH,CACJ,CAND,CAgBA5B,CAAI,CAACuB,SAAL,CAAe4C,aAAf,CAA+B,SAASC,CAAT,CAAY,IACnCxC,CAAAA,CAAI,CAAGpC,CAAC,CAAC4E,CAAC,CAACC,MAAH,CAD2B,CAEnCC,CAAY,CAAG,KAAK3C,eAAL,GAAuB4C,KAAvB,CAA6B3C,CAA7B,CAFoB,CAIvC,GAAKwC,CAAC,CAACI,MAAF,EAAYJ,CAAC,CAACK,OAAd,EAAyBL,CAAC,CAACM,OAA5B,EAAyCN,CAAC,CAACO,QAAF,EAAcP,CAAC,CAACQ,OAAF,EAAa,KAAKvE,IAAL,CAAUC,GAAlF,CAAwF,CAEpF,MACH,CAED,OAAQ8D,CAAC,CAACQ,OAAV,EACI,IAAK,MAAKvE,IAAL,CAAUO,IAAf,CAAqB,CAEjB,KAAKe,eAAL,GAAuBkD,KAAvB,GAA+BC,KAA/B,GAEAV,CAAC,CAACW,cAAF,GACA,MACH,CACD,IAAK,MAAK1E,IAAL,CAAUM,GAAf,CAAoB,CAEhB,KAAKgB,eAAL,GAAuBqD,IAAvB,GAA8BF,KAA9B,GAEAV,CAAC,CAACW,cAAF,GACA,MACH,CACD,IAAK,MAAK1E,IAAL,CAAUE,KAAf,CAAsB,CAClB,GAAI0E,CAAAA,CAAK,CAAGrD,CAAI,CAACQ,QAAL,CAAc,GAAd,EAAmBC,MAAnB,CAA4BT,CAAI,CAACQ,QAAL,CAAc,GAAd,CAA5B,CAAiDR,CAAI,CAACQ,QAAL,GAAgB8C,GAAhB,CAAoBzF,CAAS,CAACE,KAA9B,EAAqCyB,IAArC,CAA0C,GAA1C,CAA7D,CACA,GAAI6D,CAAK,CAAC5C,MAAV,CAAkB,CACd,GAAI4C,CAAK,CAACJ,KAAN,GAAczE,IAAd,CAAmB,uCAAnB,CAAJ,CAAiE,CAE7D6E,CAAK,CAACJ,KAAN,GAAcM,cAAd,CAA6Bf,CAA7B,CACH,CAHD,IAGO,IAAkC,UAA9B,QAAO,MAAK1C,aAAhB,CAA8C,CAEjD,KAAKA,aAAL,CAAmBE,CAAnB,CACH,CAHM,IAGA,CACHwD,MAAM,CAACC,QAAP,CAAgBC,IAAhB,CAAuBL,CAAK,CAACJ,KAAN,GAAc/C,IAAd,CAAmB,MAAnB,CAC1B,CACJ,CAVD,IAUO,IAAI,KAAKC,WAAL,CAAiBH,CAAjB,CAAJ,CAA4B,CAC/B,KAAKsC,WAAL,CAAiBtC,CAAjB,IACH,CAEDwC,CAAC,CAACW,cAAF,GACA,MACH,CACD,IAAK,MAAK1E,IAAL,CAAUG,KAAf,CAAsB,CAClB,GAAI,KAAKuB,WAAL,CAAiBH,CAAjB,CAAJ,CAA4B,CACxB,KAAKsC,WAAL,CAAiBtC,CAAjB,IACH,CAFD,IAEO,IAAIA,CAAI,CAACQ,QAAL,CAAc,GAAd,EAAmBC,MAAvB,CAA+B,CAClC,GAAIkD,CAAAA,CAAS,CAAG3D,CAAI,CAACQ,QAAL,CAAc,GAAd,EAAmByC,KAAnB,EAAhB,CAEA,GAAIU,CAAS,CAACnF,IAAV,CAAe,uCAAf,CAAJ,CAA6D,CACzDmF,CAAS,CAACJ,cAAV,CAAyBf,CAAzB,CACH,CACJ,CAEDA,CAAC,CAACW,cAAF,GACA,MACH,CACD,IAAK,MAAK1E,IAAL,CAAUQ,IAAf,CAAqB,CACjB,GAAI2E,CAAAA,CAAW,CAAG,SAASC,CAAT,CAAe,CAE7BA,CAAI,CAAC9D,eAAL,GAAuB+D,MAAvB,CAA8B,UAAW,CACrC,MAAOD,CAAAA,CAAI,CAACxD,gBAAL,CAAsBzC,CAAC,CAAC,IAAD,CAAvB,EAA+BmG,GAA/B,CAAmC/D,CAAnC,EAAyCS,MACnD,CAFD,EAEGyC,KAFH,EAGH,CALD,CASA,GAAI,KAAK/C,WAAL,CAAiBH,CAAjB,CAAJ,CAA4B,CACxB,GAAI,KAAKU,gBAAL,CAAsBV,CAAtB,CAAJ,CAAiC,CAC7B4D,CAAW,CAAC,IAAD,CACd,CAFD,IAEO,CACH,KAAK1C,aAAL,CAAmBlB,CAAnB,CACH,CACJ,CAND,IAMO,CACH4D,CAAW,CAAC,IAAD,CACd,CAEDpB,CAAC,CAACW,cAAF,GACA,MACH,CACD,IAAK,MAAK1E,IAAL,CAAUU,KAAf,CAAsB,CAGlB,GAAI,KAAKgB,WAAL,CAAiBH,CAAjB,CAAJ,CAA4B,CACxB,GAAI,KAAKU,gBAAL,CAAsBV,CAAtB,CAAJ,CAAiC,CAC7B,KAAKmB,WAAL,CAAiBnB,CAAjB,CACH,CAFD,IAEO,CAEH,KAAKK,gBAAL,CAAsBL,CAAtB,EAA4BR,IAA5B,CAAiC3B,CAAS,CAACC,IAA3C,EAAiDmF,KAAjD,GAAyDC,KAAzD,EACH,CACJ,CAEDV,CAAC,CAACW,cAAF,GACA,MACH,CACD,IAAK,MAAK1E,IAAL,CAAUS,EAAf,CAAmB,CAEf,GAAmB,CAAf,CAAAwD,CAAJ,CAAsB,CAClB,GAAIsB,CAAAA,CAAI,CAAG,KAAKjE,eAAL,GAAuBkE,EAAvB,CAA0BvB,CAAY,CAAG,CAAzC,CAAX,CAEAsB,CAAI,CAACd,KAAL,EACH,CAEDV,CAAC,CAACW,cAAF,GACA,MACH,CACD,IAAK,MAAK1E,IAAL,CAAUW,IAAf,CAAqB,CAEjB,GAAIsD,CAAY,CAAG,KAAK3C,eAAL,GAAuBU,MAAvB,CAAgC,CAAnD,CAAsD,CAClD,GAAIyD,CAAAA,CAAI,CAAG,KAAKnE,eAAL,GAAuBkE,EAAvB,CAA0BvB,CAAY,CAAG,CAAzC,CAAX,CAEAwB,CAAI,CAAChB,KAAL,EACH,CAEDV,CAAC,CAACW,cAAF,GACA,MACH,CACD,IAAK,MAAK1E,IAAL,CAAUY,QAAf,CAAyB,CAErB,KAAK+B,eAAL,GACAoB,CAAC,CAACW,cAAF,EAEH,CAjHL,CAmHH,CA5HD,CAoIA/E,CAAI,CAACuB,SAAL,CAAewE,WAAf,CAA6B,SAAS3B,CAAT,CAAY,CACrC,GAAIA,CAAC,CAACI,MAAF,EAAYJ,CAAC,CAACK,OAAd,EAAyBL,CAAC,CAACO,QAA3B,EAAuCP,CAAC,CAACM,OAA7C,CAAsD,CAElD,MACH,CAGD,GAAI9C,CAAAA,CAAI,CAAGpC,CAAC,CAAC4E,CAAC,CAACC,MAAH,CAAD,CAAYd,OAAZ,CAAoB,qBAApB,CAAX,CACA,GAAI,CAAC3B,CAAI,CAACI,EAAL,CAAQoC,CAAC,CAAC4B,aAAV,CAAL,CAA+B,CAC3B,MACH,CAGDpE,CAAI,CAACkD,KAAL,GAGA,GAAI,KAAK/C,WAAL,CAAiBH,CAAjB,CAAJ,CAA4B,CACxB,KAAKsC,WAAL,CAAiBtC,CAAjB,CACH,CACJ,CAnBD,CA2BA5B,CAAI,CAACuB,SAAL,CAAe0E,WAAf,CAA6B,SAAS7B,CAAT,CAAY,CACrC,KAAKjD,aAAL,CAAmB3B,CAAC,CAAC4E,CAAC,CAACC,MAAH,CAApB,CACH,CAFD,CASArE,CAAI,CAACuB,SAAL,CAAeD,iBAAf,CAAmC,UAAW,CAG1C,KAAKnB,QAAL,CAAc+F,EAAd,CAAiB,CACbC,KAAK,CAAE,KAAKJ,WAAL,CAAiBK,IAAjB,CAAsB,IAAtB,CADM,CAEbC,OAAO,CAAE,KAAKlC,aAAL,CAAmBiC,IAAnB,CAAwB,IAAxB,CAFI,CAGbtB,KAAK,CAAE,KAAKmB,WAAL,CAAiBG,IAAjB,CAAsB,IAAtB,CAHM,CAAjB,CAIG3G,CAAS,CAACC,IAJb,CAKH,CARD,CAUA,MAAyCM,CAAAA,CAC5C,CA/fK,CAAN","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 .\n\n/**\n * Implement an accessible aria tree widget, from a nested unordered list.\n * Based on http://oaa-accessibility.org/example/41/.\n *\n * @module core/tree\n * @copyright 2015 Damyon Wiese \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery'], function($) {\n // Private variables and functions.\n var SELECTORS = {\n ITEM: '[role=treeitem]',\n GROUP: '[role=treeitem]:has([role=group]), [role=treeitem][aria-owns], [role=treeitem][data-requires-ajax=true]',\n CLOSED_GROUP: '[role=treeitem]:has([role=group])[aria-expanded=false], [role=treeitem][aria-owns][aria-expanded=false], ' +\n '[role=treeitem][data-requires-ajax=true][aria-expanded=false]',\n FIRST_ITEM: '[role=treeitem]:first',\n VISIBLE_ITEM: '[role=treeitem]:visible',\n UNLOADED_AJAX_ITEM: '[role=treeitem][data-requires-ajax=true][data-loaded=false][aria-expanded=true]'\n };\n\n /**\n * Constructor.\n *\n * @param {String} selector\n * @param {function} selectCallback Called when the active node is changed.\n */\n var Tree = function(selector, selectCallback) {\n this.treeRoot = $(selector);\n\n this.treeRoot.data('activeItem', null);\n this.selectCallback = selectCallback;\n this.keys = {\n tab: 9,\n enter: 13,\n space: 32,\n pageup: 33,\n pagedown: 34,\n end: 35,\n home: 36,\n left: 37,\n up: 38,\n right: 39,\n down: 40,\n asterisk: 106\n };\n\n // Apply the standard default initialisation for all nodes, starting with the tree root.\n this.initialiseNodes(this.treeRoot);\n // Make the first item the active item for the tree so that it is added to the tab order.\n this.setActiveItem(this.treeRoot.find(SELECTORS.FIRST_ITEM));\n // Create the cache of the visible items.\n this.refreshVisibleItemsCache();\n // Create the event handlers for the tree.\n this.bindEventHandlers();\n };\n\n Tree.prototype.registerEnterCallback = function(callback) {\n this.enterCallback = callback;\n };\n\n /**\n * Find all visible tree items and save a cache of them on the tree object.\n *\n * @method refreshVisibleItemsCache\n */\n Tree.prototype.refreshVisibleItemsCache = function() {\n this.treeRoot.data('visibleItems', this.treeRoot.find(SELECTORS.VISIBLE_ITEM));\n };\n\n /**\n * Get all visible tree items.\n *\n * @method getVisibleItems\n * @return {Object} visible items\n */\n Tree.prototype.getVisibleItems = function() {\n return this.treeRoot.data('visibleItems');\n };\n\n /**\n * Mark the given item as active within the tree and fire the callback for when the active item is set.\n *\n * @method setActiveItem\n * @param {object} item jquery object representing an item on the tree.\n */\n Tree.prototype.setActiveItem = function(item) {\n var currentActive = this.treeRoot.data('activeItem');\n if (item === currentActive) {\n return;\n }\n\n // Remove previous active from tab order.\n if (currentActive) {\n currentActive.attr('tabindex', '-1');\n currentActive.attr('aria-selected', 'false');\n }\n item.attr('tabindex', '0');\n item.attr('aria-selected', 'true');\n\n // Set the new active item.\n this.treeRoot.data('activeItem', item);\n\n if (typeof this.selectCallback === 'function') {\n this.selectCallback(item);\n }\n };\n\n /**\n * Determines if the given item is a group item (contains child tree items) in the tree.\n *\n * @method isGroupItem\n * @param {object} item jquery object representing an item on the tree.\n * @returns {bool}\n */\n Tree.prototype.isGroupItem = function(item) {\n return item.is(SELECTORS.GROUP);\n };\n\n /**\n * Determines if the given item is a group item (contains child tree items) in the tree.\n *\n * @method isGroupItem\n * @param {object} item jquery object representing an item on the tree.\n * @returns {bool}\n */\n Tree.prototype.getGroupFromItem = function(item) {\n var ariaowns = this.treeRoot.find('#' + item.attr('aria-owns'));\n var plain = item.children('[role=group]');\n if (ariaowns.length > plain.length) {\n return ariaowns;\n } else {\n return plain;\n }\n };\n\n /**\n * Determines if the given group item (contains child tree items) is collapsed.\n *\n * @method isGroupCollapsed\n * @param {object} item jquery object representing a group item on the tree.\n * @returns {bool}\n */\n Tree.prototype.isGroupCollapsed = function(item) {\n return item.attr('aria-expanded') === 'false';\n };\n\n /**\n * Determines if the given group item (contains child tree items) can be collapsed.\n *\n * @method isGroupCollapsible\n * @param {object} item jquery object representing a group item on the tree.\n * @returns {bool}\n */\n Tree.prototype.isGroupCollapsible = function(item) {\n return item.attr('data-collapsible') !== 'false';\n };\n\n /**\n * Performs the tree initialisation for all child items from the given node,\n * such as removing everything from the tab order and setting aria selected\n * on items.\n *\n * @method initialiseNodes\n * @param {object} node jquery object representing a node.\n */\n Tree.prototype.initialiseNodes = function(node) {\n this.removeAllFromTabOrder(node);\n this.setAriaSelectedFalseOnItems(node);\n\n // Get all ajax nodes that have been rendered as expanded but haven't loaded the child items yet.\n var thisTree = this;\n node.find(SELECTORS.UNLOADED_AJAX_ITEM).each(function() {\n var unloadedNode = $(this);\n // Collapse and then expand to trigger the ajax loading.\n thisTree.collapseGroup(unloadedNode);\n thisTree.expandGroup(unloadedNode);\n });\n };\n\n /**\n * Removes all child DOM elements of the given node from the tab order.\n *\n * @method removeAllFromTabOrder\n * @param {object} node jquery object representing a node.\n */\n Tree.prototype.removeAllFromTabOrder = function(node) {\n node.find('*').attr('tabindex', '-1');\n this.getGroupFromItem($(node)).find('*').attr('tabindex', '-1');\n };\n\n /**\n * Find all child tree items from the given node and set the aria selected attribute to false.\n *\n * @method setAriaSelectedFalseOnItems\n * @param {object} node jquery object representing a node.\n */\n Tree.prototype.setAriaSelectedFalseOnItems = function(node) {\n node.find(SELECTORS.ITEM).attr('aria-selected', 'false');\n };\n\n /**\n * Expand all group nodes within the tree.\n *\n * @method expandAllGroups\n */\n Tree.prototype.expandAllGroups = function() {\n var thisTree = this;\n\n this.treeRoot.find(SELECTORS.CLOSED_GROUP).each(function() {\n var groupNode = $(this);\n\n thisTree.expandGroup($(this)).done(function() {\n thisTree.expandAllChildGroups(groupNode);\n });\n });\n };\n\n /**\n * Find all child group nodes from the given node and expand them.\n *\n * @method expandAllChildGroups\n * @param {Object} item is the jquery id of the group.\n */\n Tree.prototype.expandAllChildGroups = function(item) {\n var thisTree = this;\n\n this.getGroupFromItem(item).find(SELECTORS.CLOSED_GROUP).each(function() {\n var groupNode = $(this);\n\n thisTree.expandGroup($(this)).done(function() {\n thisTree.expandAllChildGroups(groupNode);\n });\n });\n };\n\n /**\n * Expand a collapsed group.\n *\n * Handles expanding nodes that are ajax loaded (marked with a data-requires-ajax attribute).\n *\n * @method expandGroup\n * @param {Object} item is the jquery id of the parent item of the group.\n * @return {Object} a promise that is resolved when the group has been expanded.\n */\n Tree.prototype.expandGroup = function(item) {\n var promise = $.Deferred();\n // Ignore nodes that are explicitly maked as not expandable or are already expanded.\n if (item.attr('data-expandable') !== 'false' && this.isGroupCollapsed(item)) {\n // If this node requires ajax load and we haven't already loaded it.\n if (item.attr('data-requires-ajax') === 'true' && item.attr('data-loaded') !== 'true') {\n item.attr('data-loaded', false);\n // Get the closes ajax loading module specificed in the tree.\n var moduleName = item.closest('[data-ajax-loader]').attr('data-ajax-loader');\n var thisTree = this;\n // Flag this node as loading.\n const p = item.find('p');\n p.addClass('loading');\n // Require the ajax module (must be AMD) and try to load the items.\n require([moduleName], function(loader) {\n // All ajax module must implement a \"load\" method.\n loader.load(item).done(function() {\n item.attr('data-loaded', true);\n\n // Set defaults on the newly constructed part of the tree.\n thisTree.initialiseNodes(item);\n thisTree.finishExpandingGroup(item);\n // Make sure no child elements of the item we just loaded are tabbable.\n p.removeClass('loading');\n promise.resolve();\n });\n });\n } else {\n this.finishExpandingGroup(item);\n promise.resolve();\n }\n } else {\n promise.resolve();\n }\n return promise;\n };\n\n /**\n * Perform the necessary DOM changes to display a group item.\n *\n * @method finishExpandingGroup\n * @param {Object} item is the jquery id of the parent item of the group.\n */\n Tree.prototype.finishExpandingGroup = function(item) {\n // Expand the group.\n var group = this.getGroupFromItem(item);\n group.removeAttr('aria-hidden');\n item.attr('aria-expanded', 'true');\n\n // Update the list of visible items.\n this.refreshVisibleItemsCache();\n };\n\n /**\n * Collapse an expanded group.\n *\n * @method collapseGroup\n * @param {Object} item is the jquery id of the parent item of the group.\n */\n Tree.prototype.collapseGroup = function(item) {\n // If the item is not collapsible or already collapsed then do nothing.\n if (!this.isGroupCollapsible(item) || this.isGroupCollapsed(item)) {\n return;\n }\n\n // Collapse the group.\n var group = this.getGroupFromItem(item);\n group.attr('aria-hidden', 'true');\n item.attr('aria-expanded', 'false');\n\n // Update the list of visible items.\n this.refreshVisibleItemsCache();\n };\n\n /**\n * Expand or collapse a group.\n *\n * @method toggleGroup\n * @param {Object} item is the jquery id of the parent item of the group.\n */\n Tree.prototype.toggleGroup = function(item) {\n if (item.attr('aria-expanded') === 'true') {\n this.collapseGroup(item);\n } else {\n this.expandGroup(item);\n }\n };\n\n /**\n * Handle a key down event - ie navigate the tree.\n *\n * @method handleKeyDown\n * @param {Event} e The event.\n */\n // This function should be simplified. In the meantime..\n // eslint-disable-next-line complexity\n Tree.prototype.handleKeyDown = function(e) {\n var item = $(e.target);\n var currentIndex = this.getVisibleItems().index(item);\n\n if ((e.altKey || e.ctrlKey || e.metaKey) || (e.shiftKey && e.keyCode != this.keys.tab)) {\n // Do nothing.\n return;\n }\n\n switch (e.keyCode) {\n case this.keys.home: {\n // Jump to first item in tree.\n this.getVisibleItems().first().focus();\n\n e.preventDefault();\n return;\n }\n case this.keys.end: {\n // Jump to last visible item.\n this.getVisibleItems().last().focus();\n\n e.preventDefault();\n return;\n }\n case this.keys.enter: {\n var links = item.children('a').length ? item.children('a') : item.children().not(SELECTORS.GROUP).find('a');\n if (links.length) {\n if (links.first().data('overrides-tree-activation-key-handler')) {\n // If the link overrides handling of activation keys, let it do so.\n links.first().triggerHandler(e);\n } else if (typeof this.enterCallback === 'function') {\n // Use callback if there is one.\n this.enterCallback(item);\n } else {\n window.location.href = links.first().attr('href');\n }\n } else if (this.isGroupItem(item)) {\n this.toggleGroup(item, true);\n }\n\n e.preventDefault();\n return;\n }\n case this.keys.space: {\n if (this.isGroupItem(item)) {\n this.toggleGroup(item, true);\n } else if (item.children('a').length) {\n var firstLink = item.children('a').first();\n\n if (firstLink.data('overrides-tree-activation-key-handler')) {\n firstLink.triggerHandler(e);\n }\n }\n\n e.preventDefault();\n return;\n }\n case this.keys.left: {\n var focusParent = function(tree) {\n // Get the immediate visible parent group item that contains this element.\n tree.getVisibleItems().filter(function() {\n return tree.getGroupFromItem($(this)).has(item).length;\n }).focus();\n };\n\n // If this is a goup item then collapse it and focus the parent group\n // in accordance with the aria spec.\n if (this.isGroupItem(item)) {\n if (this.isGroupCollapsed(item)) {\n focusParent(this);\n } else {\n this.collapseGroup(item);\n }\n } else {\n focusParent(this);\n }\n\n e.preventDefault();\n return;\n }\n case this.keys.right: {\n // If this is a group item then expand it and focus the first child item\n // in accordance with the aria spec.\n if (this.isGroupItem(item)) {\n if (this.isGroupCollapsed(item)) {\n this.expandGroup(item);\n } else {\n // Move to the first item in the child group.\n this.getGroupFromItem(item).find(SELECTORS.ITEM).first().focus();\n }\n }\n\n e.preventDefault();\n return;\n }\n case this.keys.up: {\n\n if (currentIndex > 0) {\n var prev = this.getVisibleItems().eq(currentIndex - 1);\n\n prev.focus();\n }\n\n e.preventDefault();\n return;\n }\n case this.keys.down: {\n\n if (currentIndex < this.getVisibleItems().length - 1) {\n var next = this.getVisibleItems().eq(currentIndex + 1);\n\n next.focus();\n }\n\n e.preventDefault();\n return;\n }\n case this.keys.asterisk: {\n // Expand all groups.\n this.expandAllGroups();\n e.preventDefault();\n return;\n }\n }\n };\n\n /**\n * Handle a click (select).\n *\n * @method handleClick\n * @param {Event} e The event.\n */\n Tree.prototype.handleClick = function(e) {\n if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) {\n // Do nothing.\n return;\n }\n\n // Get the closest tree item from the event target.\n var item = $(e.target).closest('[role=\"treeitem\"]');\n if (!item.is(e.currentTarget)) {\n return;\n }\n\n // Update the active item.\n item.focus();\n\n // If the item is a group node.\n if (this.isGroupItem(item)) {\n this.toggleGroup(item);\n }\n };\n\n /**\n * Handle a focus event.\n *\n * @method handleFocus\n * @param {Event} e The event.\n */\n Tree.prototype.handleFocus = function(e) {\n this.setActiveItem($(e.target));\n };\n\n /**\n * Bind the event listeners we require.\n *\n * @method bindEventHandlers\n */\n Tree.prototype.bindEventHandlers = function() {\n // Bind event handlers to the tree items. Use event delegates to allow\n // for dynamically loaded parts of the tree.\n this.treeRoot.on({\n click: this.handleClick.bind(this),\n keydown: this.handleKeyDown.bind(this),\n focus: this.handleFocus.bind(this),\n }, SELECTORS.ITEM);\n };\n\n return /** @alias module:tool_lp/tree */ Tree;\n});\n"],"file":"tree.min.js"} \ No newline at end of file diff --git a/lib/amd/src/local/aria/aria-hidden.js b/lib/amd/src/local/aria/aria-hidden.js index 6dbc9d8ef8c..3f592d173ba 100644 --- a/lib/amd/src/local/aria/aria-hidden.js +++ b/lib/amd/src/local/aria/aria-hidden.js @@ -153,14 +153,36 @@ const _hide = target => { if (supportsMutationObservers()) { // Add a MutationObserver to check for new children to the tree. - const newNodeObserver = new MutationObserver(mutationList => { + const mutationObserver = new MutationObserver(mutationList => { mutationList.forEach(mutation => { - mutation.addedNodes.forEach(disableElementFocusability); + if (mutation.type === 'childList') { + mutation.addedNodes.forEach(disableElementFocusability); + } else if (mutation.type === 'attributes') { + // The tabindex has been updated on a hidden attribute. + // Ensure that it is stored, ad set to -1 to prevent breakage. + const element = mutation.target; + const proposedTabIndex = element.getAttribute('tabindex'); + + if (proposedTabIndex !== "-1") { + element.dataset.ariaHiddenTabIndex = proposedTabIndex; + element.setAttribute('tabindex', -1); + } + } }); }); - newNodeObserver.observe(target, {childList: true, subtree: true}); - childObserverMap.set(target, newNodeObserver); + mutationObserver.observe(target, { + // Watch for changes to the entire subtree. + subtree: true, + + // Watch for new nodes. + childList: true, + + // Watch for attribute changes to the tabindex. + attributes: true, + attributeFilter: ['tabindex'], + }); + childObserverMap.set(target, mutationObserver); } }; diff --git a/lib/amd/src/tree.js b/lib/amd/src/tree.js index 00b4b0f7d53..e2ab04ef4ea 100644 --- a/lib/amd/src/tree.js +++ b/lib/amd/src/tree.js @@ -17,7 +17,7 @@ * Implement an accessible aria tree widget, from a nested unordered list. * Based on http://oaa-accessibility.org/example/41/. * - * @module tool_lp/tree + * @module core/tree * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ diff --git a/lib/tests/behat/behat_general.php b/lib/tests/behat/behat_general.php index f8c5706792a..fdd9e7432dd 100644 --- a/lib/tests/behat/behat_general.php +++ b/lib/tests/behat/behat_general.php @@ -1886,6 +1886,9 @@ EOF; case 'SPACE': $keys[] = behat_keys::SPACE; break; + case 'MULTIPLY': + $keys[] = behat_keys::MULTIPLY; + break; default: // You can enter a single ASCII character (e.g. a letter) to directly type that key. if (strlen($key) === 1) { diff --git a/theme/boost/amd/build/drawers.min.js b/theme/boost/amd/build/drawers.min.js index 29678d5da2b..e57cebf3ac0 100644 --- a/theme/boost/amd/build/drawers.min.js +++ b/theme/boost/amd/build/drawers.min.js @@ -1,2 +1,2 @@ -function _typeof(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){_typeof=function(a){return typeof a}}else{_typeof=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return _typeof(a)}define ("theme_boost/drawers",["exports","core/modal_backdrop","core/templates","core/aria","core/event_dispatcher","core/utils"],function(a,b,c,d,e,f){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=i(b);c=i(c);d=h(d);function g(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;g=function(){return a};return a}function h(a){if(a&&a.__esModule){return a}if(null===a||"object"!==_typeof(a)&&"function"!=typeof a){return{default:a}}var b=g();if(b&&b.has(a)){return b.get(a)}var c={},d=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var e in a){if(Object.prototype.hasOwnProperty.call(a,e)){var f=d?Object.getOwnPropertyDescriptor(a,e):null;if(f&&(f.get||f.set)){Object.defineProperty(c,e,f)}else{c[e]=a[e]}}}c.default=a;if(b){b.set(a,c)}return c}function i(a){return a&&a.__esModule?a:{default:a}}function j(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function k(a,b){for(var c=0,d;c=p.large},t=function(){if(!n){n=c.default.render("core/modal_backdrop",{}).then(function(a){return new b.default(a)}).then(function(a){a.getAttachmentPoint().get(0).addEventListener("click",function(a){a.preventDefault();u.closeAllDrawers()});return a}).catch()}return n},u=function(){function a(b){j(this,a);m(this,"drawerNode",null);this.drawerNode=b;if(this.drawerNode.classList.contains("show")){this.openDrawer()}else if(1==this.drawerNode.dataset.forceopen){if(!r()){this.openDrawer()}}else{d.hide(this.drawerNode)}o.set(b,this)}l(a,[{key:"dispatchEvent",value:function dispatchEvent(a){var b=1=window.innerHeight){a.classList.add("scrolled")}else{a.classList.remove("scrolled")}})})();(function registerListeners(){document.addEventListener("click",function(a){var b=a.target.closest("[data-toggle=\"drawers\"][data-action=\"toggle\"]");if(b&&b.dataset.target){a.preventDefault();var c=document.getElementById(b.dataset.target),d=u.getDrawerInstanceForNode(c);v(b);d.toggleVisibility()}var e=a.target.closest("[data-toggle=\"drawers\"][data-action=\"opendrawer\"]");if(e&&e.dataset.target){a.preventDefault();var f=document.getElementById(e.dataset.target),g=u.getDrawerInstanceForNode(f);v(b);g.openDrawer()}var h=a.target.closest("[data-toggle=\"drawers\"][data-action=\"closedrawer\"]");if(h&&h.dataset.target){a.preventDefault();var i=document.getElementById(h.dataset.target),j=u.getDrawerInstanceForNode(i);j.closeDrawer();w(h.dataset.target)}});document.addEventListener(u.eventTypes.drawerShow,function(a){if(s()){return}u.closeOtherDrawers(a.detail.drawerInstance)});window.addEventListener("resize",(0,f.debounce)(function closeOnResizeListener(){if(r()){var a=!1;o.forEach(function(b){if(b.isOpen){if(b.closeOnResize){b.closeDrawer()}else{a=!0}}});if(a){t().then(function(a){return a.show()}).catch()}}else{t().then(function(a){return a.hide()}).catch()}},400))})();var x=document.querySelectorAll("[data-region=\"fixed-drawer\"]");x.forEach(function(a){return u.getDrawerInstanceForNode(a)});return a.default}); +function _typeof(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){_typeof=function(a){return typeof a}}else{_typeof=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return _typeof(a)}define ("theme_boost/drawers",["exports","core/modal_backdrop","core/templates","core/aria","core/event_dispatcher","core/utils"],function(a,b,c,d,e,f){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=i(b);c=i(c);d=h(d);function g(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;g=function(){return a};return a}function h(a){if(a&&a.__esModule){return a}if(null===a||"object"!==_typeof(a)&&"function"!=typeof a){return{default:a}}var b=g();if(b&&b.has(a)){return b.get(a)}var c={},d=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var e in a){if(Object.prototype.hasOwnProperty.call(a,e)){var f=d?Object.getOwnPropertyDescriptor(a,e):null;if(f&&(f.get||f.set)){Object.defineProperty(c,e,f)}else{c[e]=a[e]}}}c.default=a;if(b){b.set(a,c)}return c}function i(a){return a&&a.__esModule?a:{default:a}}function j(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function k(a,b){for(var c=0,d;c=p.large},t=function(){if(!n){n=c.default.render("core/modal_backdrop",{}).then(function(a){return new b.default(a)}).then(function(a){a.getAttachmentPoint().get(0).addEventListener("click",function(a){a.preventDefault();u.closeAllDrawers()});return a}).catch()}return n},u=function(){function a(b){j(this,a);m(this,"drawerNode",null);this.drawerNode=b;if(this.drawerNode.classList.contains("show")){this.openDrawer(!0)}else if(1==this.drawerNode.dataset.forceopen){if(!r()){this.openDrawer(!0)}}else{d.hide(this.drawerNode)}o.set(b,this)}l(a,[{key:"dispatchEvent",value:function dispatchEvent(a){var b=1=window.innerHeight){a.classList.add("scrolled")}else{a.classList.remove("scrolled")}})})();(function registerListeners(){document.addEventListener("click",function(a){var b=a.target.closest("[data-toggle=\"drawers\"][data-action=\"toggle\"]");if(b&&b.dataset.target){a.preventDefault();var c=document.getElementById(b.dataset.target),d=u.getDrawerInstanceForNode(c);v(b);d.toggleVisibility()}var e=a.target.closest("[data-toggle=\"drawers\"][data-action=\"opendrawer\"]");if(e&&e.dataset.target){a.preventDefault();var f=document.getElementById(e.dataset.target),g=u.getDrawerInstanceForNode(f);v(b);g.openDrawer()}var h=a.target.closest("[data-toggle=\"drawers\"][data-action=\"closedrawer\"]");if(h&&h.dataset.target){a.preventDefault();var i=document.getElementById(h.dataset.target),j=u.getDrawerInstanceForNode(i);j.closeDrawer();w(h.dataset.target)}});document.addEventListener(u.eventTypes.drawerShow,function(a){if(s()){return}u.closeOtherDrawers(a.detail.drawerInstance)});window.addEventListener("resize",(0,f.debounce)(function closeOnResizeListener(){if(r()){var a=!1;o.forEach(function(b){if(b.isOpen){if(b.closeOnResize){b.closeDrawer()}else{a=!0}}});if(a){t().then(function(a){return a.show()}).catch()}}else{t().then(function(a){return a.hide()}).catch()}},400))})();var x=document.querySelectorAll("[data-region=\"fixed-drawer\"]");x.forEach(function(a){return u.getDrawerInstanceForNode(a)});return a.default}); //# sourceMappingURL=drawers.min.js.map diff --git a/theme/boost/amd/build/drawers.min.js.map b/theme/boost/amd/build/drawers.min.js.map index 106b9cef6cd..ecbcac85e56 100644 --- a/theme/boost/amd/build/drawers.min.js.map +++ b/theme/boost/amd/build/drawers.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/drawers.js"],"names":["backdropPromise","drawerMap","Map","sizes","medium","large","getCurrentWidth","DomRect","document","body","getBoundingClientRect","x","width","isSmall","browserWidth","isLarge","getBackdrop","Templates","render","then","html","ModalBackdrop","modalBackdrop","getAttachmentPoint","get","addEventListener","e","preventDefault","Drawers","closeAllDrawers","catch","drawerNode","classList","contains","openDrawer","dataset","forceopen","Aria","hide","set","eventname","cancelable","drawerInstance","showEvent","dispatchEvent","eventTypes","drawerShow","defaultPrevented","unhide","add","preference","M","util","set_user_preference","state","page","getElementById","backdrop","show","pageWrapper","style","overflow","closeButton","querySelector","focus","drawerShown","hideEvent","drawerHide","remove","drawerHidden","closeDrawer","parseInt","closeOnResize","has","forEach","comparisonInstance","setLastUsedToggle","toggleButton","target","querySelectorAll","btn","lastused","focusLastUsedToggle","lastUsedButton","scroller","drawerLayout","scrollTop","window","innerHeight","registerListeners","closest","targetDrawer","getDrawerInstanceForNode","toggleVisibility","openDrawerButton","closeDrawerButton","closeOtherDrawers","detail","closeOnResizeListener","anyOpen","isOpen","drawers"],"mappings":"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","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 .\n\n/**\n * Toggling the visibility of the secondary navigation on mobile.\n *\n * @module theme_boost/drawers\n * @copyright 2021 Bas Brands\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport ModalBackdrop from 'core/modal_backdrop';\nimport Templates from 'core/templates';\nimport * as Aria from 'core/aria';\nimport {dispatchEvent} from 'core/event_dispatcher';\nimport {debounce} from 'core/utils';\n\nlet backdropPromise = null;\n\nconst drawerMap = new Map();\n\n/**\n * Maximum sizes for breakpoints. This needs to correspond with Bootstrap\n * Breakpoints\n *\n * @private\n */\nconst sizes = {\n medium: 991,\n large: 1400\n};\n\n/**\n * Get the current body width.\n *\n * @returns {number} the current body width.\n * @private\n */\nconst getCurrentWidth = () => {\n const DomRect = document.body.getBoundingClientRect();\n return DomRect.x + DomRect.width;\n};\n\n/**\n * Check if the user uses a small size browser.\n *\n * @returns {boolean} true if the body is smaller than sizes.medium max size.\n * @private\n */\nconst isSmall = () => {\n const browserWidth = getCurrentWidth();\n return browserWidth < sizes.medium;\n};\n\n/**\n * Check if the user uses a large size browser.\n *\n * @returns {boolean} true if the body is smaller than sizes.large max size.\n * @private\n */\nconst isLarge = () => {\n const browserWidth = getCurrentWidth();\n return browserWidth >= sizes.large;\n};\n\n/**\n * Add a backdrop to the page.\n *\n * @returns {Promise} rendering of modal backdrop.\n * @private\n */\nconst getBackdrop = () => {\n if (!backdropPromise) {\n backdropPromise = Templates.render('core/modal_backdrop', {})\n .then(html => new ModalBackdrop(html))\n .then(modalBackdrop => {\n modalBackdrop.getAttachmentPoint().get(0).addEventListener('click', e => {\n e.preventDefault();\n Drawers.closeAllDrawers();\n });\n return modalBackdrop;\n })\n .catch();\n }\n return backdropPromise;\n};\n\n/**\n * The Drawers class is used to control on-screen drawer elements.\n *\n * It handles opening, and closing of drawer elements, as well as more detailed behaviours such as closing a drawer when\n * another drawer is opened, and supports closing a drawer when the screen is resized.\n *\n * Drawers are instantiated on page load, and can also be toggled lazily when toggling any drawer toggle, open button,\n * or close button.\n *\n * A range of show and hide events are also dispatched as detailed in the class\n * {@link module:theme_boost/drawers#eventTypes eventTypes} object.\n *\n * @example Standard usage\n *\n * // The module just needs to be included to add drawer support.\n * import 'theme_boost/drawers';\n *\n * @example Manually open or close any drawer\n *\n * import Drawers from 'theme_boost/drawers';\n *\n * const myDrawer = Drawers.getDrawerInstanceForNode(document.querySelector('.myDrawerNode');\n * myDrawer.closeDrawer();\n *\n * @example Listen to the before show event and cancel it\n *\n * import Drawers from 'theme_boost/drawers';\n *\n * document.addEventListener(Drawers.eventTypes.drawerShow, e => {\n * // The drawer which will be shown.\n * window.console.log(e.target);\n *\n * // The instance of the Drawers class for this drawer.\n * window.console.log(e.detail.drawerInstance);\n *\n * // Prevent this drawer from being shown.\n * e.preventDefault();\n * });\n *\n * @example Listen to the shown event\n *\n * document.addEventListener(Drawers.eventTypes.drawerShown, e => {\n * // The drawer which was shown.\n * window.console.log(e.target);\n *\n * // The instance of the Drawers class for this drawer.\n * window.console.log(e.detail.drawerInstance);\n * });\n */\nexport default class Drawers {\n /**\n * The underlying HTMLElement which is controlled.\n */\n drawerNode = null;\n\n constructor(drawerNode) {\n this.drawerNode = drawerNode;\n\n if (this.drawerNode.classList.contains('show')) {\n this.openDrawer();\n } else if (this.drawerNode.dataset.forceopen == 1) {\n if (!isSmall()) {\n this.openDrawer();\n }\n } else {\n Aria.hide(this.drawerNode);\n }\n\n drawerMap.set(drawerNode, this);\n }\n\n /**\n * Whether the drawer is open.\n *\n * @returns {boolean}\n */\n get isOpen() {\n return this.drawerNode.classList.contains('show');\n }\n\n /**\n * Whether the drawer should close when the window is resized\n *\n * @returns {boolean}\n */\n get closeOnResize() {\n return !!parseInt(this.drawerNode.dataset.closeOnResize);\n }\n\n /**\n * The list of event types.\n *\n * @static\n * @property {String} drawerShow See {@link event:theme_boost/drawers:show}\n * @property {String} drawerShown See {@link event:theme_boost/drawers:shown}\n * @property {String} drawerHide See {@link event:theme_boost/drawers:hide}\n * @property {String} drawerHidden See {@link event:theme_boost/drawers:hidden}\n */\n static eventTypes = {\n /**\n * An event triggered before a drawer is shown.\n *\n * @event theme_boost/drawers:show\n * @type {CustomEvent}\n * @property {HTMLElement} target The drawer that will be opened.\n */\n drawerShow: 'theme_boost/drawers:show',\n\n /**\n * An event triggered after a drawer is shown.\n *\n * @event theme_boost/drawers:shown\n * @type {CustomEvent}\n * @property {HTMLElement} target The drawer that was be opened.\n */\n drawerShown: 'theme_boost/drawers:shown',\n\n /**\n * An event triggered before a drawer is hidden.\n *\n * @event theme_boost/drawers:hide\n * @type {CustomEvent}\n * @property {HTMLElement} target The drawer that will be hidden.\n */\n drawerHide: 'theme_boost/drawers:hide',\n\n /**\n * An event triggered after a drawer is hidden.\n *\n * @event theme_boost/drawers:hidden\n * @type {CustomEvent}\n * @property {HTMLElement} target The drawer that was be hidden.\n */\n drawerHidden: 'theme_boost/drawers:hidden',\n };\n\n\n /**\n * Get the drawer instance for the specified node\n *\n * @param {HTMLElement} drawerNode\n * @returns {module:theme_boost/drawers}\n */\n static getDrawerInstanceForNode(drawerNode) {\n if (!drawerMap.has(drawerNode)) {\n new Drawers(drawerNode);\n }\n\n return drawerMap.get(drawerNode);\n }\n\n /**\n * Dispatch a drawer event.\n *\n * @param {string} eventname the event name\n * @param {boolean} cancelable if the event is cancelable\n * @returns {CustomEvent} the resulting custom event\n */\n dispatchEvent(eventname, cancelable = false) {\n return dispatchEvent(\n eventname,\n {\n drawerInstance: this,\n },\n this.drawerNode,\n {\n cancelable,\n }\n );\n }\n\n /**\n * Open the drawer.\n */\n openDrawer() {\n const showEvent = this.dispatchEvent(Drawers.eventTypes.drawerShow, true);\n if (showEvent.defaultPrevented) {\n return;\n }\n\n Aria.unhide(this.drawerNode);\n this.drawerNode.classList.add('show');\n\n const preference = this.drawerNode.dataset.preference;\n if (preference && !isSmall() && (this.drawerNode.dataset.forceopen != 1)) {\n M.util.set_user_preference(preference, true);\n }\n\n const state = this.drawerNode.dataset.state;\n if (state) {\n const page = document.getElementById('page');\n page.classList.add(state);\n }\n\n if (isSmall()) {\n getBackdrop().then(backdrop => {\n backdrop.show();\n\n const pageWrapper = document.getElementById('page-wrapper');\n pageWrapper.style.overflow = 'hidden';\n return backdrop;\n })\n .catch();\n }\n\n const closeButton = this.drawerNode.querySelector('[data-toggle=\"drawers\"][data-action=\"closedrawer\"]');\n closeButton.focus();\n\n this.dispatchEvent(Drawers.eventTypes.drawerShown);\n }\n\n /**\n * Close the drawer.\n */\n closeDrawer() {\n const hideEvent = this.dispatchEvent(Drawers.eventTypes.drawerHide, true);\n if (hideEvent.defaultPrevented) {\n return;\n }\n\n const preference = this.drawerNode.dataset.preference;\n if (preference) {\n M.util.set_user_preference(preference, false);\n }\n\n const state = this.drawerNode.dataset.state;\n if (state) {\n const page = document.getElementById('page');\n page.classList.remove(state);\n }\n\n Aria.hide(this.drawerNode);\n this.drawerNode.classList.remove('show');\n\n getBackdrop().then(backdrop => {\n backdrop.hide();\n\n if (isSmall()) {\n const pageWrapper = document.getElementById('page-wrapper');\n pageWrapper.style.overflow = 'auto';\n }\n return backdrop;\n })\n .catch();\n\n this.dispatchEvent(Drawers.eventTypes.drawerHidden);\n }\n\n /**\n * Toggle visibility of the drawer.\n */\n toggleVisibility() {\n if (this.drawerNode.classList.contains('show')) {\n this.closeDrawer();\n } else {\n this.openDrawer();\n }\n }\n\n /**\n * Close all drawers.\n */\n static closeAllDrawers() {\n drawerMap.forEach(drawerInstance => {\n drawerInstance.closeDrawer();\n });\n }\n\n /**\n * Close all drawers except for the specified drawer.\n *\n * @param {module:theme_boost/drawers} comparisonInstance\n */\n static closeOtherDrawers(comparisonInstance) {\n drawerMap.forEach(drawerInstance => {\n if (drawerInstance === comparisonInstance) {\n return;\n }\n\n drawerInstance.closeDrawer();\n });\n }\n}\n\n/**\n * Activate the scroller helper for the drawer layout.\n *\n * @private\n */\nconst scroller = () => {\n const body = document.querySelector('body');\n const drawerLayout = document.querySelector('#page.drawers');\n drawerLayout.addEventListener(\"scroll\", () => {\n if (drawerLayout.scrollTop >= window.innerHeight) {\n body.classList.add('scrolled');\n } else {\n body.classList.remove('scrolled');\n }\n });\n};\n\n/**\n * Set the last used attribute for the last used toggle button for a drawer.\n *\n * @param {object} toggleButton The clicked button.\n */\nconst setLastUsedToggle = (toggleButton) => {\n if (toggleButton.dataset.target) {\n document.querySelectorAll('[data-toggle=\"drawers\"][data-target=\"' + toggleButton.dataset.target + '\"]')\n .forEach(btn => {\n btn.dataset.lastused = false;\n });\n toggleButton.dataset.lastused = true;\n }\n};\n\n/**\n * Set the focus to the last used button to open this drawer.\n * @param {string} target The drawer target.\n */\nconst focusLastUsedToggle = (target) => {\n const lastUsedButton = document.querySelector('[data-toggle=\"drawers\"][data-target=\"' + target + '\"][data-lastused=\"true\"');\n if (lastUsedButton) {\n lastUsedButton.focus();\n }\n};\n\n/**\n * Register the event listeners for the drawer.\n *\n * @private\n */\nconst registerListeners = () => {\n // Listen for show/hide events.\n document.addEventListener('click', e => {\n const toggleButton = e.target.closest('[data-toggle=\"drawers\"][data-action=\"toggle\"]');\n if (toggleButton && toggleButton.dataset.target) {\n e.preventDefault();\n const targetDrawer = document.getElementById(toggleButton.dataset.target);\n const drawerInstance = Drawers.getDrawerInstanceForNode(targetDrawer);\n setLastUsedToggle(toggleButton);\n\n drawerInstance.toggleVisibility();\n }\n\n const openDrawerButton = e.target.closest('[data-toggle=\"drawers\"][data-action=\"opendrawer\"]');\n if (openDrawerButton && openDrawerButton.dataset.target) {\n e.preventDefault();\n const targetDrawer = document.getElementById(openDrawerButton.dataset.target);\n const drawerInstance = Drawers.getDrawerInstanceForNode(targetDrawer);\n setLastUsedToggle(toggleButton);\n\n drawerInstance.openDrawer();\n }\n\n const closeDrawerButton = e.target.closest('[data-toggle=\"drawers\"][data-action=\"closedrawer\"]');\n if (closeDrawerButton && closeDrawerButton.dataset.target) {\n e.preventDefault();\n const targetDrawer = document.getElementById(closeDrawerButton.dataset.target);\n const drawerInstance = Drawers.getDrawerInstanceForNode(targetDrawer);\n\n drawerInstance.closeDrawer();\n focusLastUsedToggle(closeDrawerButton.dataset.target);\n }\n });\n\n // Close drawer when another drawer opens.\n document.addEventListener(Drawers.eventTypes.drawerShow, e => {\n if (isLarge()) {\n return;\n }\n Drawers.closeOtherDrawers(e.detail.drawerInstance);\n });\n\n const closeOnResizeListener = () => {\n if (isSmall()) {\n let anyOpen = false;\n drawerMap.forEach(drawerInstance => {\n if (drawerInstance.isOpen) {\n if (drawerInstance.closeOnResize) {\n drawerInstance.closeDrawer();\n } else {\n anyOpen = true;\n }\n }\n });\n\n if (anyOpen) {\n getBackdrop().then(backdrop => backdrop.show()).catch();\n }\n } else {\n getBackdrop().then(backdrop => backdrop.hide()).catch();\n }\n };\n\n window.addEventListener('resize', debounce(closeOnResizeListener, 400));\n};\n\nscroller();\nregisterListeners();\n\nconst drawers = document.querySelectorAll('[data-region=\"fixed-drawer\"]');\ndrawers.forEach(drawerNode => Drawers.getDrawerInstanceForNode(drawerNode));\n"],"file":"drawers.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/drawers.js"],"names":["backdropPromise","drawerMap","Map","sizes","medium","large","getCurrentWidth","DomRect","document","body","getBoundingClientRect","x","width","isSmall","browserWidth","isLarge","getBackdrop","Templates","render","then","html","ModalBackdrop","modalBackdrop","getAttachmentPoint","get","addEventListener","e","preventDefault","Drawers","closeAllDrawers","catch","drawerNode","classList","contains","openDrawer","dataset","forceopen","Aria","hide","set","eventname","cancelable","drawerInstance","keepfocus","showEvent","dispatchEvent","eventTypes","drawerShow","defaultPrevented","unhide","add","preference","M","util","set_user_preference","state","page","getElementById","backdrop","show","pageWrapper","style","overflow","closeButton","querySelector","focus","drawerShown","hideEvent","drawerHide","remove","drawerHidden","closeDrawer","parseInt","closeOnResize","has","forEach","comparisonInstance","setLastUsedToggle","toggleButton","target","querySelectorAll","btn","lastused","focusLastUsedToggle","lastUsedButton","scroller","drawerLayout","scrollTop","window","innerHeight","registerListeners","closest","targetDrawer","getDrawerInstanceForNode","toggleVisibility","openDrawerButton","closeDrawerButton","closeOtherDrawers","detail","closeOnResizeListener","anyOpen","isOpen","drawers"],"mappings":"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","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 .\n\n/**\n * Toggling the visibility of the secondary navigation on mobile.\n *\n * @module theme_boost/drawers\n * @copyright 2021 Bas Brands\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport ModalBackdrop from 'core/modal_backdrop';\nimport Templates from 'core/templates';\nimport * as Aria from 'core/aria';\nimport {dispatchEvent} from 'core/event_dispatcher';\nimport {debounce} from 'core/utils';\n\nlet backdropPromise = null;\n\nconst drawerMap = new Map();\n\n/**\n * Maximum sizes for breakpoints. This needs to correspond with Bootstrap\n * Breakpoints\n *\n * @private\n */\nconst sizes = {\n medium: 991,\n large: 1400\n};\n\n/**\n * Get the current body width.\n *\n * @returns {number} the current body width.\n * @private\n */\nconst getCurrentWidth = () => {\n const DomRect = document.body.getBoundingClientRect();\n return DomRect.x + DomRect.width;\n};\n\n/**\n * Check if the user uses a small size browser.\n *\n * @returns {boolean} true if the body is smaller than sizes.medium max size.\n * @private\n */\nconst isSmall = () => {\n const browserWidth = getCurrentWidth();\n return browserWidth < sizes.medium;\n};\n\n/**\n * Check if the user uses a large size browser.\n *\n * @returns {boolean} true if the body is smaller than sizes.large max size.\n * @private\n */\nconst isLarge = () => {\n const browserWidth = getCurrentWidth();\n return browserWidth >= sizes.large;\n};\n\n/**\n * Add a backdrop to the page.\n *\n * @returns {Promise} rendering of modal backdrop.\n * @private\n */\nconst getBackdrop = () => {\n if (!backdropPromise) {\n backdropPromise = Templates.render('core/modal_backdrop', {})\n .then(html => new ModalBackdrop(html))\n .then(modalBackdrop => {\n modalBackdrop.getAttachmentPoint().get(0).addEventListener('click', e => {\n e.preventDefault();\n Drawers.closeAllDrawers();\n });\n return modalBackdrop;\n })\n .catch();\n }\n return backdropPromise;\n};\n\n/**\n * The Drawers class is used to control on-screen drawer elements.\n *\n * It handles opening, and closing of drawer elements, as well as more detailed behaviours such as closing a drawer when\n * another drawer is opened, and supports closing a drawer when the screen is resized.\n *\n * Drawers are instantiated on page load, and can also be toggled lazily when toggling any drawer toggle, open button,\n * or close button.\n *\n * A range of show and hide events are also dispatched as detailed in the class\n * {@link module:theme_boost/drawers#eventTypes eventTypes} object.\n *\n * @example Standard usage\n *\n * // The module just needs to be included to add drawer support.\n * import 'theme_boost/drawers';\n *\n * @example Manually open or close any drawer\n *\n * import Drawers from 'theme_boost/drawers';\n *\n * const myDrawer = Drawers.getDrawerInstanceForNode(document.querySelector('.myDrawerNode');\n * myDrawer.closeDrawer();\n *\n * @example Listen to the before show event and cancel it\n *\n * import Drawers from 'theme_boost/drawers';\n *\n * document.addEventListener(Drawers.eventTypes.drawerShow, e => {\n * // The drawer which will be shown.\n * window.console.log(e.target);\n *\n * // The instance of the Drawers class for this drawer.\n * window.console.log(e.detail.drawerInstance);\n *\n * // Prevent this drawer from being shown.\n * e.preventDefault();\n * });\n *\n * @example Listen to the shown event\n *\n * document.addEventListener(Drawers.eventTypes.drawerShown, e => {\n * // The drawer which was shown.\n * window.console.log(e.target);\n *\n * // The instance of the Drawers class for this drawer.\n * window.console.log(e.detail.drawerInstance);\n * });\n */\nexport default class Drawers {\n /**\n * The underlying HTMLElement which is controlled.\n */\n drawerNode = null;\n\n constructor(drawerNode) {\n this.drawerNode = drawerNode;\n\n if (this.drawerNode.classList.contains('show')) {\n this.openDrawer(true);\n } else if (this.drawerNode.dataset.forceopen == 1) {\n if (!isSmall()) {\n this.openDrawer(true);\n }\n } else {\n Aria.hide(this.drawerNode);\n }\n\n drawerMap.set(drawerNode, this);\n }\n\n /**\n * Whether the drawer is open.\n *\n * @returns {boolean}\n */\n get isOpen() {\n return this.drawerNode.classList.contains('show');\n }\n\n /**\n * Whether the drawer should close when the window is resized\n *\n * @returns {boolean}\n */\n get closeOnResize() {\n return !!parseInt(this.drawerNode.dataset.closeOnResize);\n }\n\n /**\n * The list of event types.\n *\n * @static\n * @property {String} drawerShow See {@link event:theme_boost/drawers:show}\n * @property {String} drawerShown See {@link event:theme_boost/drawers:shown}\n * @property {String} drawerHide See {@link event:theme_boost/drawers:hide}\n * @property {String} drawerHidden See {@link event:theme_boost/drawers:hidden}\n */\n static eventTypes = {\n /**\n * An event triggered before a drawer is shown.\n *\n * @event theme_boost/drawers:show\n * @type {CustomEvent}\n * @property {HTMLElement} target The drawer that will be opened.\n */\n drawerShow: 'theme_boost/drawers:show',\n\n /**\n * An event triggered after a drawer is shown.\n *\n * @event theme_boost/drawers:shown\n * @type {CustomEvent}\n * @property {HTMLElement} target The drawer that was be opened.\n */\n drawerShown: 'theme_boost/drawers:shown',\n\n /**\n * An event triggered before a drawer is hidden.\n *\n * @event theme_boost/drawers:hide\n * @type {CustomEvent}\n * @property {HTMLElement} target The drawer that will be hidden.\n */\n drawerHide: 'theme_boost/drawers:hide',\n\n /**\n * An event triggered after a drawer is hidden.\n *\n * @event theme_boost/drawers:hidden\n * @type {CustomEvent}\n * @property {HTMLElement} target The drawer that was be hidden.\n */\n drawerHidden: 'theme_boost/drawers:hidden',\n };\n\n\n /**\n * Get the drawer instance for the specified node\n *\n * @param {HTMLElement} drawerNode\n * @returns {module:theme_boost/drawers}\n */\n static getDrawerInstanceForNode(drawerNode) {\n if (!drawerMap.has(drawerNode)) {\n new Drawers(drawerNode);\n }\n\n return drawerMap.get(drawerNode);\n }\n\n /**\n * Dispatch a drawer event.\n *\n * @param {string} eventname the event name\n * @param {boolean} cancelable if the event is cancelable\n * @returns {CustomEvent} the resulting custom event\n */\n dispatchEvent(eventname, cancelable = false) {\n return dispatchEvent(\n eventname,\n {\n drawerInstance: this,\n },\n this.drawerNode,\n {\n cancelable,\n }\n );\n }\n\n /**\n * Open the drawer.\n *\n * By default, openDrawer move the page focus to the close drawer button.However, when\n * a drawer is opened by default this represents an accessibility problem as the initial\n * focus changes without any user interaction. Using the keepfocus param it is possible to\n * prevent the focus lose.\n *\n * @param {boolean} keepfocus true if the drawer has to open but don't alter the page focus.\n */\n openDrawer(keepfocus) {\n const showEvent = this.dispatchEvent(Drawers.eventTypes.drawerShow, true);\n if (showEvent.defaultPrevented) {\n return;\n }\n\n Aria.unhide(this.drawerNode);\n this.drawerNode.classList.add('show');\n\n const preference = this.drawerNode.dataset.preference;\n if (preference && !isSmall() && (this.drawerNode.dataset.forceopen != 1)) {\n M.util.set_user_preference(preference, true);\n }\n\n const state = this.drawerNode.dataset.state;\n if (state) {\n const page = document.getElementById('page');\n page.classList.add(state);\n }\n\n if (isSmall()) {\n getBackdrop().then(backdrop => {\n backdrop.show();\n\n const pageWrapper = document.getElementById('page-wrapper');\n pageWrapper.style.overflow = 'hidden';\n return backdrop;\n })\n .catch();\n }\n\n if (!keepfocus) {\n const closeButton = this.drawerNode.querySelector('[data-toggle=\"drawers\"][data-action=\"closedrawer\"]');\n closeButton.focus();\n }\n\n this.dispatchEvent(Drawers.eventTypes.drawerShown);\n }\n\n /**\n * Close the drawer.\n */\n closeDrawer() {\n const hideEvent = this.dispatchEvent(Drawers.eventTypes.drawerHide, true);\n if (hideEvent.defaultPrevented) {\n return;\n }\n\n const preference = this.drawerNode.dataset.preference;\n if (preference) {\n M.util.set_user_preference(preference, false);\n }\n\n const state = this.drawerNode.dataset.state;\n if (state) {\n const page = document.getElementById('page');\n page.classList.remove(state);\n }\n\n Aria.hide(this.drawerNode);\n this.drawerNode.classList.remove('show');\n\n getBackdrop().then(backdrop => {\n backdrop.hide();\n\n if (isSmall()) {\n const pageWrapper = document.getElementById('page-wrapper');\n pageWrapper.style.overflow = 'auto';\n }\n return backdrop;\n })\n .catch();\n\n this.dispatchEvent(Drawers.eventTypes.drawerHidden);\n }\n\n /**\n * Toggle visibility of the drawer.\n */\n toggleVisibility() {\n if (this.drawerNode.classList.contains('show')) {\n this.closeDrawer();\n } else {\n this.openDrawer();\n }\n }\n\n /**\n * Close all drawers.\n */\n static closeAllDrawers() {\n drawerMap.forEach(drawerInstance => {\n drawerInstance.closeDrawer();\n });\n }\n\n /**\n * Close all drawers except for the specified drawer.\n *\n * @param {module:theme_boost/drawers} comparisonInstance\n */\n static closeOtherDrawers(comparisonInstance) {\n drawerMap.forEach(drawerInstance => {\n if (drawerInstance === comparisonInstance) {\n return;\n }\n\n drawerInstance.closeDrawer();\n });\n }\n}\n\n/**\n * Activate the scroller helper for the drawer layout.\n *\n * @private\n */\nconst scroller = () => {\n const body = document.querySelector('body');\n const drawerLayout = document.querySelector('#page.drawers');\n drawerLayout.addEventListener(\"scroll\", () => {\n if (drawerLayout.scrollTop >= window.innerHeight) {\n body.classList.add('scrolled');\n } else {\n body.classList.remove('scrolled');\n }\n });\n};\n\n/**\n * Set the last used attribute for the last used toggle button for a drawer.\n *\n * @param {object} toggleButton The clicked button.\n */\nconst setLastUsedToggle = (toggleButton) => {\n if (toggleButton.dataset.target) {\n document.querySelectorAll('[data-toggle=\"drawers\"][data-target=\"' + toggleButton.dataset.target + '\"]')\n .forEach(btn => {\n btn.dataset.lastused = false;\n });\n toggleButton.dataset.lastused = true;\n }\n};\n\n/**\n * Set the focus to the last used button to open this drawer.\n * @param {string} target The drawer target.\n */\nconst focusLastUsedToggle = (target) => {\n const lastUsedButton = document.querySelector('[data-toggle=\"drawers\"][data-target=\"' + target + '\"][data-lastused=\"true\"');\n if (lastUsedButton) {\n lastUsedButton.focus();\n }\n};\n\n/**\n * Register the event listeners for the drawer.\n *\n * @private\n */\nconst registerListeners = () => {\n // Listen for show/hide events.\n document.addEventListener('click', e => {\n const toggleButton = e.target.closest('[data-toggle=\"drawers\"][data-action=\"toggle\"]');\n if (toggleButton && toggleButton.dataset.target) {\n e.preventDefault();\n const targetDrawer = document.getElementById(toggleButton.dataset.target);\n const drawerInstance = Drawers.getDrawerInstanceForNode(targetDrawer);\n setLastUsedToggle(toggleButton);\n\n drawerInstance.toggleVisibility();\n }\n\n const openDrawerButton = e.target.closest('[data-toggle=\"drawers\"][data-action=\"opendrawer\"]');\n if (openDrawerButton && openDrawerButton.dataset.target) {\n e.preventDefault();\n const targetDrawer = document.getElementById(openDrawerButton.dataset.target);\n const drawerInstance = Drawers.getDrawerInstanceForNode(targetDrawer);\n setLastUsedToggle(toggleButton);\n\n drawerInstance.openDrawer();\n }\n\n const closeDrawerButton = e.target.closest('[data-toggle=\"drawers\"][data-action=\"closedrawer\"]');\n if (closeDrawerButton && closeDrawerButton.dataset.target) {\n e.preventDefault();\n const targetDrawer = document.getElementById(closeDrawerButton.dataset.target);\n const drawerInstance = Drawers.getDrawerInstanceForNode(targetDrawer);\n\n drawerInstance.closeDrawer();\n focusLastUsedToggle(closeDrawerButton.dataset.target);\n }\n });\n\n // Close drawer when another drawer opens.\n document.addEventListener(Drawers.eventTypes.drawerShow, e => {\n if (isLarge()) {\n return;\n }\n Drawers.closeOtherDrawers(e.detail.drawerInstance);\n });\n\n const closeOnResizeListener = () => {\n if (isSmall()) {\n let anyOpen = false;\n drawerMap.forEach(drawerInstance => {\n if (drawerInstance.isOpen) {\n if (drawerInstance.closeOnResize) {\n drawerInstance.closeDrawer();\n } else {\n anyOpen = true;\n }\n }\n });\n\n if (anyOpen) {\n getBackdrop().then(backdrop => backdrop.show()).catch();\n }\n } else {\n getBackdrop().then(backdrop => backdrop.hide()).catch();\n }\n };\n\n window.addEventListener('resize', debounce(closeOnResizeListener, 400));\n};\n\nscroller();\nregisterListeners();\n\nconst drawers = document.querySelectorAll('[data-region=\"fixed-drawer\"]');\ndrawers.forEach(drawerNode => Drawers.getDrawerInstanceForNode(drawerNode));\n"],"file":"drawers.min.js"} \ No newline at end of file diff --git a/theme/boost/amd/src/drawers.js b/theme/boost/amd/src/drawers.js index 18e61effdf9..33d321bc041 100644 --- a/theme/boost/amd/src/drawers.js +++ b/theme/boost/amd/src/drawers.js @@ -155,10 +155,10 @@ export default class Drawers { this.drawerNode = drawerNode; if (this.drawerNode.classList.contains('show')) { - this.openDrawer(); + this.openDrawer(true); } else if (this.drawerNode.dataset.forceopen == 1) { if (!isSmall()) { - this.openDrawer(); + this.openDrawer(true); } } else { Aria.hide(this.drawerNode); @@ -269,8 +269,15 @@ export default class Drawers { /** * Open the drawer. + * + * By default, openDrawer move the page focus to the close drawer button.However, when + * a drawer is opened by default this represents an accessibility problem as the initial + * focus changes without any user interaction. Using the keepfocus param it is possible to + * prevent the focus lose. + * + * @param {boolean} keepfocus true if the drawer has to open but don't alter the page focus. */ - openDrawer() { + openDrawer(keepfocus) { const showEvent = this.dispatchEvent(Drawers.eventTypes.drawerShow, true); if (showEvent.defaultPrevented) { return; @@ -301,8 +308,10 @@ export default class Drawers { .catch(); } - const closeButton = this.drawerNode.querySelector('[data-toggle="drawers"][data-action="closedrawer"]'); - closeButton.focus(); + if (!keepfocus) { + const closeButton = this.drawerNode.querySelector('[data-toggle="drawers"][data-action="closedrawer"]'); + closeButton.focus(); + } this.dispatchEvent(Drawers.eventTypes.drawerShown); } diff --git a/theme/classic/tests/behat/blacklist.json b/theme/classic/tests/behat/blacklist.json index e5cc771456d..61ab01aa087 100644 --- a/theme/classic/tests/behat/blacklist.json +++ b/theme/classic/tests/behat/blacklist.json @@ -6,6 +6,7 @@ "lib/tests/behat/action_menu.feature", "blocks/tests/behat/hide_blocks.feature", "blocks/tests/behat/move_blocks.feature", - "course/format/tests/behat/course_courseindex.feature" + "course/format/tests/behat/course_courseindex.feature", + "course/format/tests/behat/courseindex_keyboardnav.feature" ] }