From eca56db352a09e70179d9bd9db2f8e08b657918b Mon Sep 17 00:00:00 2001 From: Susanna Zanatta Date: Fri, 12 Apr 2024 15:52:03 +0100 Subject: [PATCH] #52 Arrow keys navigation and screen reader support for progress bar --- amd/build/progressbar.min.js | 2 +- amd/build/progressbar.min.js.map | 2 +- amd/src/progressbar.js | 13 +++++++++++-- classes/output/renderer.php | 18 +++++++++++++++++- 4 files changed, 30 insertions(+), 5 deletions(-) diff --git a/amd/build/progressbar.min.js b/amd/build/progressbar.min.js index a98a75195f..f10170ee99 100644 --- a/amd/build/progressbar.min.js +++ b/amd/build/progressbar.min.js @@ -5,6 +5,6 @@ * @copyright 2020 Jonathon Fowler * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -define("block_completion_progress/progressbar",["jquery","core/pubsub","core/utils"],(function($,PubSub,Utils){function showInfo(event){var cell=$(this),container=cell.closest(".block_completion_progress .barContainer"),visibleinfo=container.siblings(".progressEventInfo:visible"),infotoshow=container.siblings("#"+cell.data("infoRef"));visibleinfo.is(infotoshow)||(visibleinfo.hide(),infotoshow.show()),event.preventDefault()}function showAllInfo(event){var initialinfo=$(this).closest(".progressEventInfo");initialinfo.siblings(".progressEventInfo").show(),initialinfo.hide(),event.preventDefault()}function viewActivity(){var cell=$(this),infolink=cell.closest(".block_completion_progress .barContainer").siblings("#"+cell.data("infoRef")).find("a.action_link");null!==infolink.prop("onclick")?infolink.click():document.location=infolink.prop("href")}function scrollContainer(event){var barrow=$(this).closest(".block_completion_progress .barContainer").find(".barRow"),amount=event.data*barrow.prop("scrollWidth")*.15;barrow.prop("scrollLeft",barrow.prop("scrollLeft")+amount),event.preventDefault()}function checkArrows(){var barrow=$(this),barcontainer=barrow.closest(".block_completion_progress .barContainer"),leftarrow=barcontainer.find(".left-arrow-svg"),rightarrow=barcontainer.find(".right-arrow-svg"),scrolled=barrow.prop("scrollLeft"),scrollWidth=barrow.prop("scrollWidth")-barrow.prop("offsetWidth"),threshold=Math.floor(.25*barrow.find(".progressBarCell:first-child").width());"rtl"===document.dir?((scrolled=-scrolled)>threshold?rightarrow.css("display","block"):rightarrow.css("display","none"),scrollWidth>threshold&&scrolledthreshold?leftarrow.css("display","block"):leftarrow.css("display","none"),scrollWidth>threshold&&scrolled=0;i--)initialiseBar(instanceids[i])}}})); +define("block_completion_progress/progressbar",["jquery","core/pubsub","core/utils"],(function($,PubSub,Utils){function showInfo(event){var cell=$(this),container=cell.closest(".block_completion_progress .barContainer"),cells=container.find(".progressBarCell"),visibleinfo=container.siblings(".progressEventInfo:visible"),infotoshow=container.siblings("#"+cell.data("infoRef"));cells.each((function(){$(this).attr("aria-selected",!1),$(this).attr("tabindex","-1")})),cell.attr("aria-selected",!0),cell.attr("tabindex","0"),visibleinfo.is(infotoshow)||(visibleinfo.hide(),infotoshow.show()),event.preventDefault()}function showAllInfo(event){var initialinfo=$(this).closest(".progressEventInfo");initialinfo.siblings(".progressEventInfo").show(),initialinfo.hide(),event.preventDefault()}function viewActivity(){var cell=$(this),infolink=cell.closest(".block_completion_progress .barContainer").siblings("#"+cell.data("infoRef")).find("a.action_link");null!==infolink.prop("onclick")?infolink.click():document.location=infolink.prop("href")}function scrollContainer(event){var barrow=$(this).closest(".block_completion_progress .barContainer").find(".barRow"),amount=event.data*barrow.prop("scrollWidth")*.15;barrow.prop("scrollLeft",barrow.prop("scrollLeft")+amount),event.preventDefault()}function checkArrows(){var barrow=$(this),barcontainer=barrow.closest(".block_completion_progress .barContainer"),leftarrow=barcontainer.find(".left-arrow-svg"),rightarrow=barcontainer.find(".right-arrow-svg"),scrolled=barrow.prop("scrollLeft"),scrollWidth=barrow.prop("scrollWidth")-barrow.prop("offsetWidth"),threshold=Math.floor(.25*barrow.find(".progressBarCell:first-child").width());"rtl"===document.dir?((scrolled=-scrolled)>threshold?rightarrow.css("display","block"):rightarrow.css("display","none"),scrollWidth>threshold&&scrolledthreshold?leftarrow.css("display","block"):leftarrow.css("display","none"),scrollWidth>threshold&&scrolled=0;i--)initialiseBar(instanceids[i])}}})); //# sourceMappingURL=progressbar.min.js.map \ No newline at end of file diff --git a/amd/build/progressbar.min.js.map b/amd/build/progressbar.min.js.map index df61c6b501..e0cada7822 100644 --- a/amd/build/progressbar.min.js.map +++ b/amd/build/progressbar.min.js.map @@ -1 +1 @@ -{"version":3,"file":"progressbar.min.js","sources":["../src/progressbar.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Completion Progress block progress bar behaviour.\n *\n * @module block_completion_progress/progressbar\n * @copyright 2020 Jonathon Fowler \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery', 'core/pubsub', 'core/utils'],\n function($, PubSub, Utils) {\n /**\n * Show progress event information for a cell.\n * @param {Event} event\n */\n function showInfo(event) {\n var cell = $(this);\n var container = cell.closest('.block_completion_progress .barContainer');\n var visibleinfo = container.siblings('.progressEventInfo:visible');\n var infotoshow = container.siblings('#' + cell.data('infoRef'));\n\n if (!visibleinfo.is(infotoshow)) {\n visibleinfo.hide();\n infotoshow.show();\n }\n\n event.preventDefault();\n }\n\n /**\n * Show all progress event information (for accessibility).\n * @param {Event} event\n */\n function showAllInfo(event) {\n var initialinfo = $(this).closest('.progressEventInfo');\n\n initialinfo.siblings('.progressEventInfo').show();\n initialinfo.hide();\n\n event.preventDefault();\n }\n\n /**\n * Navigate to a cell's activity location.\n */\n function viewActivity() {\n var cell = $(this);\n var container = cell.closest('.block_completion_progress .barContainer');\n var infotoshow = container.siblings('#' + cell.data('infoRef'));\n var infolink = infotoshow.find('a.action_link');\n if (infolink.prop('onclick') !== null) {\n infolink.click();\n } else {\n document.location = infolink.prop('href');\n }\n }\n\n /**\n * Scroll the bar corresponding to the arrow clicked.\n * @param {Event} event\n */\n function scrollContainer(event) {\n var barrow = $(this).closest('.block_completion_progress .barContainer').find('.barRow');\n var amount = event.data * barrow.prop('scrollWidth') * 0.15;\n\n barrow.prop('scrollLeft', barrow.prop('scrollLeft') + amount);\n\n event.preventDefault();\n }\n\n /**\n * Show or hide the scroll arrows based on the visible position.\n */\n function checkArrows() {\n var barrow = $(this);\n var barcontainer = barrow.closest('.block_completion_progress .barContainer');\n var leftarrow = barcontainer.find('.left-arrow-svg');\n var rightarrow = barcontainer.find('.right-arrow-svg');\n var scrolled = barrow.prop('scrollLeft');\n var scrollWidth = barrow.prop('scrollWidth') - barrow.prop('offsetWidth');\n var threshold = Math.floor(barrow.find('.progressBarCell:first-child').width() * 0.25);\n\n if (document.dir === 'rtl') {\n scrolled = -scrolled;\n\n if (scrolled > threshold) {\n rightarrow.css('display', 'block');\n } else {\n rightarrow.css('display', 'none');\n }\n if (scrollWidth > threshold && scrolled < scrollWidth - threshold) {\n leftarrow.css('display', 'block');\n } else {\n leftarrow.css('display', 'none');\n }\n } else {\n if (scrolled > threshold) {\n leftarrow.css('display', 'block');\n } else {\n leftarrow.css('display', 'none');\n }\n if (scrollWidth > threshold && scrolled < scrollWidth - threshold) {\n rightarrow.css('display', 'block');\n } else {\n rightarrow.css('display', 'none');\n }\n }\n }\n\n /**\n * Prepare scroll mode behaviour.\n * @param {jQuery} barcontainers there could be many nodes here in overview mode\n */\n function setupScroll(barcontainers) {\n var barrows = barcontainers.find('.barRow');\n\n /**\n * Check arrow visibility for each of the bar rows.\n */\n function checkEachBar() {\n barrows.each(checkArrows);\n }\n\n barrows.scroll(checkArrows);\n $(window).resize(checkEachBar);\n PubSub.subscribe('nav-drawer-toggle-end', checkEachBar); // Boost ≤3.11.\n $(document).on('theme_boost/drawers:shown theme_boost/drawers:hidden',\n Utils.debounce(checkEachBar, 250)); // Boost ≥4.0.\n\n // On page load, place the 'now' marker in the centre of the scrolled bar\n // and adjust which arrows should be visible.\n $(function() {\n var nowicons = barcontainers.find('.nowicon');\n nowicons.each(function() {\n var nowicon = $(this);\n var barrow = nowicon.closest('.block_completion_progress .barRow');\n\n barrow.prop('scrollLeft', 0);\n barrow.prop('scrollLeft', nowicon.offset().left - barrow.offset().left -\n barrow.width() / 2);\n });\n\n barrows.each(checkArrows);\n });\n\n barcontainers.on('click', '.left-arrow-svg', -1, scrollContainer);\n barcontainers.on('click', '.right-arrow-svg', 1, scrollContainer);\n }\n\n /**\n * Set up event handlers for a particular progress bar instance.\n * @param {integer} instanceid the bar instance id\n */\n function initialiseBar(instanceid) {\n var barcontainers = $('.block_completion_progress ' +\n '.barContainer[data-instanceid=\"' + instanceid + '\"]');\n\n // Show information elements on hover or tap.\n barcontainers.on('touchstart mouseover', '.progressBarCell', showInfo);\n\n // Navigate to the activity when its cell is clicked.\n barcontainers.on('click', '.progressBarCell[data-haslink=true]', viewActivity);\n\n // Show all information elements when the 'show all' link is clicked.\n barcontainers.siblings('.progressEventInfo').find('.progressShowAllInfo').click(showAllInfo);\n\n setupScroll(barcontainers);\n }\n\n return /** @alias module:block_completion_progress/progressbar */ {\n /**\n * Initialise progress bar instances.\n * @param {array} instanceids an array of progress bar instance ids\n */\n init: function(instanceids) {\n for (var i = instanceids.length - 1; i >= 0; i--) {\n initialiseBar(instanceids[i]);\n }\n },\n };\n });\n"],"names":["define","$","PubSub","Utils","showInfo","event","cell","this","container","closest","visibleinfo","siblings","infotoshow","data","is","hide","show","preventDefault","showAllInfo","initialinfo","viewActivity","infolink","find","prop","click","document","location","scrollContainer","barrow","amount","checkArrows","barcontainer","leftarrow","rightarrow","scrolled","scrollWidth","threshold","Math","floor","width","dir","css","initialiseBar","instanceid","barcontainers","on","barrows","checkEachBar","each","scroll","window","resize","subscribe","debounce","nowicon","offset","left","setupScroll","init","instanceids","i","length"],"mappings":";;;;;;;AAsBAA,+CAAO,CAAC,SAAU,cAAe,eAC7B,SAASC,EAAGC,OAAQC,gBAKPC,SAASC,WACVC,KAAOL,EAAEM,MACTC,UAAYF,KAAKG,QAAQ,4CACzBC,YAAcF,UAAUG,SAAS,8BACjCC,WAAaJ,UAAUG,SAAS,IAAML,KAAKO,KAAK,YAE/CH,YAAYI,GAAGF,cAChBF,YAAYK,OACZH,WAAWI,QAGfX,MAAMY,0BAODC,YAAYb,WACbc,YAAclB,EAAEM,MAAME,QAAQ,sBAElCU,YAAYR,SAAS,sBAAsBK,OAC3CG,YAAYJ,OAEZV,MAAMY,0BAMDG,mBACDd,KAAOL,EAAEM,MAGTc,SAFYf,KAAKG,QAAQ,4CACFE,SAAS,IAAML,KAAKO,KAAK,YAC1BS,KAAK,iBACE,OAA7BD,SAASE,KAAK,WACdF,SAASG,QAETC,SAASC,SAAWL,SAASE,KAAK,iBAQjCI,gBAAgBtB,WACjBuB,OAAS3B,EAAEM,MAAME,QAAQ,4CAA4Ca,KAAK,WAC1EO,OAASxB,MAAMQ,KAAOe,OAAOL,KAAK,eAAiB,IAEvDK,OAAOL,KAAK,aAAcK,OAAOL,KAAK,cAAgBM,QAEtDxB,MAAMY,0BAMDa,kBACDF,OAAS3B,EAAEM,MACXwB,aAAeH,OAAOnB,QAAQ,4CAC9BuB,UAAYD,aAAaT,KAAK,mBAC9BW,WAAaF,aAAaT,KAAK,oBAC/BY,SAAWN,OAAOL,KAAK,cACvBY,YAAcP,OAAOL,KAAK,eAAiBK,OAAOL,KAAK,eACvDa,UAAYC,KAAKC,MAA4D,IAAtDV,OAAON,KAAK,gCAAgCiB,SAElD,QAAjBd,SAASe,MACTN,UAAYA,UAEGE,UACXH,WAAWQ,IAAI,UAAW,SAE1BR,WAAWQ,IAAI,UAAW,QAE1BN,YAAcC,WAAaF,SAAWC,YAAcC,UACpDJ,UAAUS,IAAI,UAAW,SAEzBT,UAAUS,IAAI,UAAW,UAGzBP,SAAWE,UACXJ,UAAUS,IAAI,UAAW,SAEzBT,UAAUS,IAAI,UAAW,QAEzBN,YAAcC,WAAaF,SAAWC,YAAcC,UACpDH,WAAWQ,IAAI,UAAW,SAE1BR,WAAWQ,IAAI,UAAW,kBAiD7BC,cAAcC,gBACfC,cAAgB3C,EAAE,6DACkB0C,WAAa,MAGrDC,cAAcC,GAAG,uBAAwB,mBAAoBzC,UAG7DwC,cAAcC,GAAG,QAAS,sCAAuCzB,cAGjEwB,cAAcjC,SAAS,sBAAsBW,KAAK,wBAAwBE,MAAMN,sBAnD/D0B,mBACbE,QAAUF,cAActB,KAAK,oBAKxByB,eACLD,QAAQE,KAAKlB,aAGjBgB,QAAQG,OAAOnB,aACf7B,EAAEiD,QAAQC,OAAOJ,cACjB7C,OAAOkD,UAAU,wBAAyBL,cAC1C9C,EAAEwB,UAAUoB,GAAG,uDACX1C,MAAMkD,SAASN,aAAc,MAIjC9C,GAAE,WACiB2C,cAActB,KAAK,YACzB0B,MAAK,eACNM,QAAUrD,EAAEM,MACZqB,OAAS0B,QAAQ7C,QAAQ,sCAE7BmB,OAAOL,KAAK,aAAc,GAC1BK,OAAOL,KAAK,aAAc+B,QAAQC,SAASC,KAAO5B,OAAO2B,SAASC,KAC9D5B,OAAOW,QAAU,MAGzBO,QAAQE,KAAKlB,gBAGjBc,cAAcC,GAAG,QAAS,mBAAoB,EAAGlB,iBACjDiB,cAAcC,GAAG,QAAS,mBAAoB,EAAGlB,iBAoBjD8B,CAAYb,qBAGkD,CAK9Dc,KAAM,SAASC,iBACN,IAAIC,EAAID,YAAYE,OAAS,EAAGD,GAAK,EAAGA,IACzClB,cAAciB,YAAYC"} \ No newline at end of file +{"version":3,"file":"progressbar.min.js","sources":["../src/progressbar.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Completion Progress block progress bar behaviour.\n *\n * @module block_completion_progress/progressbar\n * @copyright 2020 Jonathon Fowler \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery', 'core/pubsub', 'core/utils'],\n function($, PubSub, Utils) {\n /**\n * Show progress event information for a cell.\n * @param {Event} event\n */\n function showInfo(event) {\n var cell = $(this);\n var container = cell.closest('.block_completion_progress .barContainer');\n var cells = container.find('.progressBarCell');\n var visibleinfo = container.siblings('.progressEventInfo:visible');\n var infotoshow = container.siblings('#' + cell.data('infoRef'));\n\n // Manage tab attributes for keyboard navigation.\n cells.each(function() {\n $(this).attr('aria-selected', false);\n $(this).attr('tabindex', '-1');\n });\n cell.attr('aria-selected', true);\n cell.attr('tabindex', '0');\n\n if (!visibleinfo.is(infotoshow)) {\n visibleinfo.hide();\n infotoshow.show();\n }\n\n event.preventDefault();\n }\n\n /**\n * Show all progress event information (for accessibility).\n * @param {Event} event\n */\n function showAllInfo(event) {\n var initialinfo = $(this).closest('.progressEventInfo');\n\n initialinfo.siblings('.progressEventInfo').show();\n initialinfo.hide();\n\n event.preventDefault();\n }\n\n /**\n * Navigate to a cell's activity location.\n */\n function viewActivity() {\n var cell = $(this);\n var container = cell.closest('.block_completion_progress .barContainer');\n var infotoshow = container.siblings('#' + cell.data('infoRef'));\n var infolink = infotoshow.find('a.action_link');\n if (infolink.prop('onclick') !== null) {\n infolink.click();\n } else {\n document.location = infolink.prop('href');\n }\n }\n\n /**\n * Scroll the bar corresponding to the arrow clicked.\n * @param {Event} event\n */\n function scrollContainer(event) {\n var barrow = $(this).closest('.block_completion_progress .barContainer').find('.barRow');\n var amount = event.data * barrow.prop('scrollWidth') * 0.15;\n\n barrow.prop('scrollLeft', barrow.prop('scrollLeft') + amount);\n\n event.preventDefault();\n }\n\n /**\n * Show or hide the scroll arrows based on the visible position.\n */\n function checkArrows() {\n var barrow = $(this);\n var barcontainer = barrow.closest('.block_completion_progress .barContainer');\n var leftarrow = barcontainer.find('.left-arrow-svg');\n var rightarrow = barcontainer.find('.right-arrow-svg');\n var scrolled = barrow.prop('scrollLeft');\n var scrollWidth = barrow.prop('scrollWidth') - barrow.prop('offsetWidth');\n var threshold = Math.floor(barrow.find('.progressBarCell:first-child').width() * 0.25);\n\n if (document.dir === 'rtl') {\n scrolled = -scrolled;\n\n if (scrolled > threshold) {\n rightarrow.css('display', 'block');\n } else {\n rightarrow.css('display', 'none');\n }\n if (scrollWidth > threshold && scrolled < scrollWidth - threshold) {\n leftarrow.css('display', 'block');\n } else {\n leftarrow.css('display', 'none');\n }\n } else {\n if (scrolled > threshold) {\n leftarrow.css('display', 'block');\n } else {\n leftarrow.css('display', 'none');\n }\n if (scrollWidth > threshold && scrolled < scrollWidth - threshold) {\n rightarrow.css('display', 'block');\n } else {\n rightarrow.css('display', 'none');\n }\n }\n }\n\n /**\n * Prepare scroll mode behaviour.\n * @param {jQuery} barcontainers there could be many nodes here in overview mode\n */\n function setupScroll(barcontainers) {\n var barrows = barcontainers.find('.barRow');\n\n /**\n * Check arrow visibility for each of the bar rows.\n */\n function checkEachBar() {\n barrows.each(checkArrows);\n }\n\n barrows.scroll(checkArrows);\n $(window).resize(checkEachBar);\n PubSub.subscribe('nav-drawer-toggle-end', checkEachBar); // Boost ≤3.11.\n $(document).on('theme_boost/drawers:shown theme_boost/drawers:hidden',\n Utils.debounce(checkEachBar, 250)); // Boost ≥4.0.\n\n // On page load, place the 'now' marker in the centre of the scrolled bar\n // and adjust which arrows should be visible.\n $(function() {\n var nowicons = barcontainers.find('.nowicon');\n nowicons.each(function() {\n var nowicon = $(this);\n var barrow = nowicon.closest('.block_completion_progress .barRow');\n\n barrow.prop('scrollLeft', 0);\n barrow.prop('scrollLeft', nowicon.offset().left - barrow.offset().left -\n barrow.width() / 2);\n });\n\n barrows.each(checkArrows);\n });\n\n barcontainers.on('click', '.left-arrow-svg', -1, scrollContainer);\n barcontainers.on('click', '.right-arrow-svg', 1, scrollContainer);\n }\n\n /**\n * Set up event handlers for a particular progress bar instance.\n * @param {integer} instanceid the bar instance id\n */\n function initialiseBar(instanceid) {\n var barcontainers = $('.block_completion_progress ' +\n '.barContainer[data-instanceid=\"' + instanceid + '\"]');\n\n // Show information elements on hover, tap, or focus.\n barcontainers.on('touchstart mouseover focus', '.progressBarCell', showInfo);\n\n // Navigate to the activity when its cell is clicked.\n barcontainers.on('click', '.progressBarCell[data-haslink=true]', viewActivity);\n\n // Show all information elements when the 'show all' link is clicked.\n barcontainers.siblings('.progressEventInfo').find('.progressShowAllInfo').click(showAllInfo);\n\n setupScroll(barcontainers);\n }\n\n return /** @alias module:block_completion_progress/progressbar */ {\n /**\n * Initialise progress bar instances.\n * @param {array} instanceids an array of progress bar instance ids\n */\n init: function(instanceids) {\n for (var i = instanceids.length - 1; i >= 0; i--) {\n initialiseBar(instanceids[i]);\n }\n },\n };\n });\n"],"names":["define","$","PubSub","Utils","showInfo","event","cell","this","container","closest","cells","find","visibleinfo","siblings","infotoshow","data","each","attr","is","hide","show","preventDefault","showAllInfo","initialinfo","viewActivity","infolink","prop","click","document","location","scrollContainer","barrow","amount","checkArrows","barcontainer","leftarrow","rightarrow","scrolled","scrollWidth","threshold","Math","floor","width","dir","css","initialiseBar","instanceid","barcontainers","on","barrows","checkEachBar","scroll","window","resize","subscribe","debounce","nowicon","offset","left","setupScroll","init","instanceids","i","length"],"mappings":"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"} \ No newline at end of file diff --git a/amd/src/progressbar.js b/amd/src/progressbar.js index dd1fc1bc67..0b06bd7ffa 100644 --- a/amd/src/progressbar.js +++ b/amd/src/progressbar.js @@ -29,9 +29,18 @@ define(['jquery', 'core/pubsub', 'core/utils'], function showInfo(event) { var cell = $(this); var container = cell.closest('.block_completion_progress .barContainer'); + var cells = container.find('.progressBarCell'); var visibleinfo = container.siblings('.progressEventInfo:visible'); var infotoshow = container.siblings('#' + cell.data('infoRef')); + // Manage tab attributes for keyboard navigation. + cells.each(function() { + $(this).attr('aria-selected', false); + $(this).attr('tabindex', '-1'); + }); + cell.attr('aria-selected', true); + cell.attr('tabindex', '0'); + if (!visibleinfo.is(infotoshow)) { visibleinfo.hide(); infotoshow.show(); @@ -168,8 +177,8 @@ define(['jquery', 'core/pubsub', 'core/utils'], var barcontainers = $('.block_completion_progress ' + '.barContainer[data-instanceid="' + instanceid + '"]'); - // Show information elements on hover or tap. - barcontainers.on('touchstart mouseover', '.progressBarCell', showInfo); + // Show information elements on hover, tap, or focus. + barcontainers.on('touchstart mouseover focus', '.progressBarCell', showInfo); // Navigate to the activity when its cell is clicked. barcontainers.on('click', '.progressBarCell[data-haslink=true]', viewActivity); diff --git a/classes/output/renderer.php b/classes/output/renderer.php index 738b81abb0..b625823f93 100644 --- a/classes/output/renderer.php +++ b/classes/output/renderer.php @@ -98,7 +98,12 @@ public function render_completion_progress(completion_progress $progress) { $showpercentage = $config->showpercentage ?? defaults::SHOWPERCENTAGE; $rowoptions = ['style' => '']; - $cellsoptions = ['style' => '']; + $cellsoptions = [ + 'style' => '', + 'role' => 'tablist', + 'tabindex' => '0', + 'aria-labelledby' => 'progressBarInfo'.$instance.'-'.$userid.'-info' + ]; $barclasses = ['barRow']; $content .= html_writer::start_div('barContainer', ['data-instanceid' => $instance]); @@ -176,13 +181,19 @@ public function render_completion_progress(completion_progress $progress) { $cellcontent = ''; $celloptions = [ 'class' => 'progressBarCell', + 'id' => 'progressBarTab'.$instance.'-'.$userid.'-'.$activity->id, 'data-info-ref' => 'progressBarInfo'.$instance.'-'.$userid.'-'.$activity->id, + 'tabindex' => $counter === 1 ? '0' : '-1', + 'role' => 'tab', + 'aria-label' => $activity->name . $complete ]; if ($complete === 'submitted') { $celloptions['class'] .= ' submittedNotComplete'; + $celloptions['aria-label'] = $activity->name . ', ' . get_string('submitted', 'block_completion_progress'); } else if ($complete == COMPLETION_COMPLETE || $complete == COMPLETION_COMPLETE_PASS) { $celloptions['class'] .= ' completed'; + $celloptions['aria-label'] = $activity->name . ', ' . get_string('completed', 'block_completion_progress'); } else if ( $complete == COMPLETION_COMPLETE_FAIL || @@ -190,9 +201,11 @@ public function render_completion_progress(completion_progress $progress) { (isset($activity->expected) && $activity->expected > 0 && $activity->expected < $now) ) { $celloptions['class'] .= ' notCompleted'; + $celloptions['aria-label'] = $activity->name . ', ' . get_string('completion-fail', 'completion'); } else { $celloptions['class'] .= ' futureNotCompleted'; + $celloptions['aria-label'] = $activity->name . ', ' . get_string('completion-n', 'completion'); } if (empty($activity->link)) { $celloptions['data-haslink'] = 'false'; @@ -262,6 +275,9 @@ public function render_completion_progress(completion_progress $progress) { 'class' => 'progressEventInfo', 'id' => 'progressBarInfo'.$instance.'-'.$userid.'-'.$activity->id, 'style' => 'display: none;', + 'role' => 'tabpanel', + 'tabindex' => '0', + 'aria-labelledby' => 'progressBarTab'.$instance.'-'.$userid.'-'.$activity->id, ]; $content .= html_writer::start_tag('div', $divoptions);