From ae2b100f5530cc5c5b3f4a2e0c1bcf2155cb2f51 Mon Sep 17 00:00:00 2001 From: Shamim Rezaie Date: Tue, 5 Oct 2021 05:50:00 +1100 Subject: [PATCH 1/3] MDL-72668 block_private_files: Switched from YUI to core/tree AMD --- .../private_files/amd/build/files_tree.min.js | 2 + .../amd/build/files_tree.min.js.map | 1 + blocks/private_files/amd/src/files_tree.js | 33 +++++++++ blocks/private_files/module.js | 47 ------------- blocks/private_files/renderer.php | 36 ++++++---- blocks/private_files/styles.css | 67 +++++++++++++++++++ lib/amd/build/tree.min.js.map | 2 +- lib/amd/src/tree.js | 4 +- 8 files changed, 130 insertions(+), 62 deletions(-) create mode 100644 blocks/private_files/amd/build/files_tree.min.js create mode 100644 blocks/private_files/amd/build/files_tree.min.js.map create mode 100644 blocks/private_files/amd/src/files_tree.js delete mode 100644 blocks/private_files/module.js diff --git a/blocks/private_files/amd/build/files_tree.min.js b/blocks/private_files/amd/build/files_tree.min.js new file mode 100644 index 00000000000..b587d0cc423 --- /dev/null +++ b/blocks/private_files/amd/build/files_tree.min.js @@ -0,0 +1,2 @@ +define ("block_private_files/files_tree",["exports","core/tree"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);var c=function(a){new b.default("#".concat(a," [role=\"tree\"]"))};a.init=c}); +//# sourceMappingURL=files_tree.min.js.map diff --git a/blocks/private_files/amd/build/files_tree.min.js.map b/blocks/private_files/amd/build/files_tree.min.js.map new file mode 100644 index 00000000000..c1fa974b3e7 --- /dev/null +++ b/blocks/private_files/amd/build/files_tree.min.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../src/files_tree.js"],"names":["init","blockId","Tree"],"mappings":"2JAsBA,uDAQO,GAAMA,CAAAA,CAAI,CAAG,SAACC,CAAD,CAAa,CAC7B,GAAIC,UAAJ,YAAaD,CAAb,qBACH,CAFM,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 * Changes the display of directories and files into a tree.\n *\n * @module block_private_files/files_tree\n * @copyright 2021 Shamim Rezaie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport Tree from 'core/tree';\n\n/**\n * The init function that does the job.\n * It changes the display of directories and files into a tree.\n *\n * @param {string} blockId\n */\nexport const init = (blockId) => {\n new Tree(`#${blockId} [role=\"tree\"]`);\n};\n"],"file":"files_tree.min.js"} \ No newline at end of file diff --git a/blocks/private_files/amd/src/files_tree.js b/blocks/private_files/amd/src/files_tree.js new file mode 100644 index 00000000000..fc8e9b80bc9 --- /dev/null +++ b/blocks/private_files/amd/src/files_tree.js @@ -0,0 +1,33 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * Changes the display of directories and files into a tree. + * + * @module block_private_files/files_tree + * @copyright 2021 Shamim Rezaie + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +import Tree from 'core/tree'; + +/** + * The init function that does the job. + * It changes the display of directories and files into a tree. + * + * @param {string} blockId + */ +export const init = (blockId) => { + new Tree(`#${blockId} [role="tree"]`); +}; diff --git a/blocks/private_files/module.js b/blocks/private_files/module.js deleted file mode 100644 index 203eb034bdc..00000000000 --- a/blocks/private_files/module.js +++ /dev/null @@ -1,47 +0,0 @@ -// 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 . - -/** - * Init private files treeview - * - * @package block_private_files - * @copyright 2009 Petr Skoda (http://skodak.org) - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -M.block_private_files = {}; - -M.block_private_files.init_tree = function(Y, expand_all, htmlid) { - Y.use('yui2-treeview', 'node-event-simulate', function(Y) { - var tree = new Y.YUI2.widget.TreeView(htmlid); - - tree.subscribe("clickEvent", function(node, event) { - // we want normal clicking which redirects to url - return false; - }); - - tree.subscribe("enterKeyPressed", function(node) { - // We want keyboard activation to trigger a click on the first link. - Y.one(node.getContentEl()).one('a').simulate('click'); - return false; - }); - - if (expand_all) { - tree.expandAll(); - } - - tree.render(); - }); -}; diff --git a/blocks/private_files/renderer.php b/blocks/private_files/renderer.php index d3570536aa8..ef6815a000a 100644 --- a/blocks/private_files/renderer.php +++ b/blocks/private_files/renderer.php @@ -35,14 +35,13 @@ class block_private_files_renderer extends plugin_renderer_base { } public function render_private_files_tree(private_files_tree $tree) { - $module = array('name'=>'block_private_files', 'fullpath'=>'/blocks/private_files/module.js', 'requires'=>array('yui2-treeview')); if (empty($tree->dir['subdirs']) && empty($tree->dir['files'])) { $html = $this->output->box(get_string('nofilesavailable', 'repository')); } else { $htmlid = 'private_files_tree_'.uniqid(); - $this->page->requires->js_init_call('M.block_private_files.init_tree', array(false, $htmlid)); + $this->page->requires->js_call_amd('block_private_files/files_tree', 'init', [$htmlid]); $html = '
'; - $html .= $this->htmllize_tree($tree, $tree->dir); + $html .= $this->htmllize_tree($tree, $tree->dir, true); $html .= '
'; } @@ -50,26 +49,39 @@ class block_private_files_renderer extends plugin_renderer_base { } /** - * Internal function - creates htmls structure suitable for YUI tree. + * Internal function - creates htmls structure suitable for core/tree AMD. + * + * @param private_files_tree $tree The renderable tree. + * @param array $dir The directory in the tree + * @param bool $isroot If it is the root directory in the tree. + * @return string */ - protected function htmllize_tree($tree, $dir) { + protected function htmllize_tree($tree, $dir, $isroot) { global $CFG; - $yuiconfig = array(); - $yuiconfig['type'] = 'html'; if (empty($dir['subdirs']) and empty($dir['files'])) { return ''; } - $result = '
    '; + if ($isroot) { + $result = '
      '; + } else { + $result = ''; diff --git a/blocks/private_files/styles.css b/blocks/private_files/styles.css index b553b4c2a05..c35ad8065a9 100644 --- a/blocks/private_files/styles.css +++ b/blocks/private_files/styles.css @@ -8,3 +8,70 @@ padding: 10px 0 0; margin-top: .5em; } + +.block_private_files ul[role="tree"] { + margin: 0; + padding: 0; +} +.block_private_files ul, +.block_private_files li { + list-style: none; +} + +.block_private_files [role="treeitem"] { + padding-left: 22px; + cursor: pointer; +} + +.block_private_files [role="treeitem"] p { + margin-bottom: 0; +} + +.block_private_files [role="treeitem"][aria-expanded] { + padding-left: 0; +} + +.block_private_files [role="treeitem"][aria-expanded="false"] > p::before { + /*rtl:remove*/ + content: url('[[pix:t/collapsed]]'); + /*rtl:raw: + content: url('[[pix:t/collapsed_rtl]]'); + */ + vertical-align: sub; + margin-right: 5px; +} + +.block_private_files [role="treeitem"][aria-expanded="true"] > p::before { + content: url('[[pix:t/expanded]]'); + vertical-align: sub; + margin-right: 5px; +} + +.block_private_files [role="treeitem"]:not([aria-expanded]) { + background-image: + repeating-linear-gradient(to right, rgba(0, 0, 0, .5) 0, rgba(0, 0, 0, .5) 1px, rgba(255, 255, 255, 0) 1px, rgba(255, 255, 255, 0) 2px), + repeating-linear-gradient(to top, rgba(0, 0, 0, 0.5) 0, rgba(0, 0, 0, 0.5) 1px, rgba(255, 255, 255, 0) 1px, rgba(255, 255, 255, 0) 2px); + background-repeat: no-repeat, no-repeat; + /*rtl:remove*/ + background-position: left 10px top 50%, left 8px top 0; + /*rtl:raw: + background-position: right 10px top 50%, right 8px top 0; + */ + background-size: 11px 1px, 1px 100%; +} + +.block_private_files [role="treeitem"]:not([aria-expanded]):last-child { + background-size: 11px 1px, 1px 50%; +} + +.block_private_files [role="group"] { + background-image: repeating-linear-gradient(to top, rgba(0, 0, 0, 0.5) 0, rgba(0, 0, 0, 0.5) 1px, rgba(255, 255, 255, 0) 1px, rgba(255, 255, 255, 0) 2px); + background-repeat: no-repeat; + background-position: left 8px top 100%; + background-size: 1px 100%; + margin-left: 0; +} + +.block_private_files [aria-hidden="true"]:not(.icon) { + display: none; +} diff --git a/lib/amd/build/tree.min.js.map b/lib/amd/build/tree.min.js.map index a62c8da2e9b..b5cb5a95d7f 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 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 +{"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 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 group 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:core/tree */ Tree;\n});\n"],"file":"tree.min.js"} \ No newline at end of file diff --git a/lib/amd/src/tree.js b/lib/amd/src/tree.js index e2ab04ef4ea..4d769272729 100644 --- a/lib/amd/src/tree.js +++ b/lib/amd/src/tree.js @@ -418,7 +418,7 @@ define(['jquery'], function($) { }).focus(); }; - // If this is a goup item then collapse it and focus the parent group + // If this is a group item then collapse it and focus the parent group // in accordance with the aria spec. if (this.isGroupItem(item)) { if (this.isGroupCollapsed(item)) { @@ -531,5 +531,5 @@ define(['jquery'], function($) { }, SELECTORS.ITEM); }; - return /** @alias module:tool_lp/tree */ Tree; + return /** @alias module:core/tree */ Tree; }); From b5c54555735da4da2f871a6fc5da6d0449996a34 Mon Sep 17 00:00:00 2001 From: Shamim Rezaie Date: Thu, 21 Oct 2021 05:35:26 +1100 Subject: [PATCH 2/3] MDL-72668 core: Highlight the focused tree item in a tree. --- theme/boost/scss/moodle/core.scss | 18 +++++++++++++++++- theme/boost/style/moodle.css | 15 +++++++++++++-- theme/classic/style/moodle.css | 15 +++++++++++++-- 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/theme/boost/scss/moodle/core.scss b/theme/boost/scss/moodle/core.scss index 7d5bb9a511c..3fa47152edb 100644 --- a/theme/boost/scss/moodle/core.scss +++ b/theme/boost/scss/moodle/core.scss @@ -149,7 +149,8 @@ a.dropdown-toggle, .modal-dialog[tabindex="0"], .moodle-dialogue-base .closebutton, button.close, -.form-autocomplete-selection { +.form-autocomplete-selection, +[role="treeitem"]:not([aria-expanded="true"]) { &.focus, &:focus { outline: 0; @@ -160,6 +161,21 @@ button.close, } } +// We don't want to highlight the whole li when it's expanded. Only the first child is highlighted. +[role="treeitem"][aria-expanded="true"] { + outline: 0; + &.focus, + &:focus { + > *:first-child { + outline: 0; + box-shadow: $input-btn-focus-box-shadow; + } + } + &:focus:hover { + text-decoration: none; + } +} + // Accessible focus styling for autocomplete elements. .form-autocomplete-suggestions li[aria-selected=true] { outline: 0; diff --git a/theme/boost/style/moodle.css b/theme/boost/style/moodle.css index 16ef241d01b..27fde67935d 100644 --- a/theme/boost/style/moodle.css +++ b/theme/boost/style/moodle.css @@ -10001,7 +10001,9 @@ a.dropdown-toggle:focus, button.close.focus, button.close:focus, .form-autocomplete-selection.focus, -.form-autocomplete-selection:focus { +.form-autocomplete-selection:focus, +[role="treeitem"]:not([aria-expanded="true"]).focus, +[role="treeitem"]:not([aria-expanded="true"]):focus { outline: 0; box-shadow: 0 0 0 0.2rem rgba(15, 111, 197, 0.75); } @@ -10021,9 +10023,18 @@ a.dropdown-toggle:focus:hover, .modal-dialog[tabindex="0"]:focus:hover, .moodle-dialogue-base .closebutton:focus:hover, button.close:focus:hover, -.form-autocomplete-selection:focus:hover { +.form-autocomplete-selection:focus:hover, +[role="treeitem"]:not([aria-expanded="true"]):focus:hover { text-decoration: none; } +[role="treeitem"][aria-expanded="true"] { + outline: 0; } + [role="treeitem"][aria-expanded="true"].focus > *:first-child, [role="treeitem"][aria-expanded="true"]:focus > *:first-child { + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(15, 111, 197, 0.75); } + [role="treeitem"][aria-expanded="true"]:focus:hover { + text-decoration: none; } + .form-autocomplete-suggestions li[aria-selected=true] { outline: 0; box-shadow: 0 0 0 0.2rem rgba(15, 111, 197, 0.75); } diff --git a/theme/classic/style/moodle.css b/theme/classic/style/moodle.css index c2b816be0d6..b6d96ab2493 100644 --- a/theme/classic/style/moodle.css +++ b/theme/classic/style/moodle.css @@ -10001,7 +10001,9 @@ a.dropdown-toggle:focus, button.close.focus, button.close:focus, .form-autocomplete-selection.focus, -.form-autocomplete-selection:focus { +.form-autocomplete-selection:focus, +[role="treeitem"]:not([aria-expanded="true"]).focus, +[role="treeitem"]:not([aria-expanded="true"]):focus { outline: 0; box-shadow: 0 0 0 0.2rem rgba(15, 111, 197, 0.75); } @@ -10021,9 +10023,18 @@ a.dropdown-toggle:focus:hover, .modal-dialog[tabindex="0"]:focus:hover, .moodle-dialogue-base .closebutton:focus:hover, button.close:focus:hover, -.form-autocomplete-selection:focus:hover { +.form-autocomplete-selection:focus:hover, +[role="treeitem"]:not([aria-expanded="true"]):focus:hover { text-decoration: none; } +[role="treeitem"][aria-expanded="true"] { + outline: 0; } + [role="treeitem"][aria-expanded="true"].focus > *:first-child, [role="treeitem"][aria-expanded="true"]:focus > *:first-child { + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(15, 111, 197, 0.75); } + [role="treeitem"][aria-expanded="true"]:focus:hover { + text-decoration: none; } + .form-autocomplete-suggestions li[aria-selected=true] { outline: 0; box-shadow: 0 0 0 0.2rem rgba(15, 111, 197, 0.75); } From 86ed2e7e409082d6db4310322206298e587d5a30 Mon Sep 17 00:00:00 2001 From: Shamim Rezaie Date: Thu, 4 Nov 2021 13:54:02 +1100 Subject: [PATCH 3/3] MDL-72668 block_badges: Add alt attribute to badge images --- badges/renderer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/badges/renderer.php b/badges/renderer.php index 81b733024d4..57c49998010 100644 --- a/badges/renderer.php +++ b/badges/renderer.php @@ -59,7 +59,7 @@ class core_badges_renderer extends plugin_renderer_base { $name = html_writer::tag('span', $bname, array('class' => 'badge-name')); - $image = html_writer::empty_tag('img', array('src' => $imageurl, 'class' => 'badge-image')); + $image = html_writer::empty_tag('img', ['src' => $imageurl, 'class' => 'badge-image', 'alt' => $badge->imagecaption]); if (!empty($badge->dateexpire) && $badge->dateexpire < time()) { $image .= $this->output->pix_icon('i/expired', get_string('expireddate', 'badges', userdate($badge->dateexpire)),