MDL-75837 gradebook: accessibility improvements

This commit is contained in:
Mathew May 2022-11-01 14:03:10 +08:00 committed by Mihail Geshoski
parent f589d592c9
commit 57e65480a3
23 changed files with 82 additions and 226 deletions

View File

@ -1,10 +1,10 @@
define("core_grades/searchwidget/basewidget",["exports","core/utils","core/templates","core/aria","core_grades/searchwidget/selectors","core/notification"],(function(_exports,_utils,Templates,_aria,Selectors,_notification){var obj;function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}
define("core_grades/searchwidget/basewidget",["exports","core/utils","core/templates","core_grades/searchwidget/selectors","core/notification"],(function(_exports,_utils,Templates,Selectors,_notification){var obj;function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}
/**
* A widget to search users or grade items within the gradebook.
*
* @module core_grades/searchwidget/basewidget
* @copyright 2022 Mathew May <mathew.solutions>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.showLoader=_exports.registerListenerEvents=_exports.promisesAndResolvers=_exports.init=void 0,Templates=_interopRequireWildcard(Templates),Selectors=_interopRequireWildcard(Selectors),_notification=(obj=_notification)&&obj.__esModule?obj:{default:obj};_exports.init=async function(widgetContentContainer,bodyPromise,data,searchFunc){let unsearchableContent=arguments.length>4&&void 0!==arguments[4]?arguments[4]:null;bodyPromise.then((async bodyContent=>{if(widgetContentContainer.innerHTML=bodyContent,unsearchableContent){widgetContentContainer.querySelector(Selectors.regions.unsearchableContent).innerHTML+=unsearchableContent}const searchResultsContainer=widgetContentContainer.querySelector(Selectors.regions.searchResults);await showLoader(searchResultsContainer),await renderSearchResults(searchResultsContainer,data),registerListenerEvents(widgetContentContainer,data,searchFunc)})).catch(_notification.default.exception)};const registerListenerEvents=(widgetContentContainer,data,searchFunc)=>{const searchResultsContainer=widgetContentContainer.querySelector(Selectors.regions.searchResults),searchInput=widgetContentContainer.querySelector(Selectors.actions.search),clearSearchButton=widgetContentContainer.querySelector(Selectors.actions.clearSearch);searchInput.addEventListener("input",(0,_utils.debounce)((async()=>{searchInput.value.length>0?clearSearchButton.classList.remove("d-none"):clearSearchButton.classList.add("d-none"),await renderSearchResults(searchResultsContainer,debounceCallee(searchInput.value,data,searchFunc()))}),300)),clearSearchButton.addEventListener("click",(async e=>{e.stopPropagation(),searchInput.value="",searchInput.focus(),clearSearchButton.classList.add("d-none"),await renderSearchResults(searchResultsContainer,debounceCallee(searchInput.value,data,searchFunc()))})),(0,_aria.comboBox)(searchInput)};_exports.registerListenerEvents=registerListenerEvents;const showLoader=async container=>{const{html:html,js:js}=await Templates.renderForPromise("core_grades/searchwidget/loading",{});Templates.replaceNodeContents(container,html,js)};_exports.showLoader=showLoader;const debounceCallee=(searchValue,data,searchFunction)=>searchValue.length>0?searchFunction(data,searchValue):data,renderSearchResults=async(searchResultsContainer,searchResultsData)=>{const templateData={searchresults:searchResultsData},{html:html,js:js}=await Templates.renderForPromise("core_grades/searchwidget/searchresults",templateData);await Templates.replaceNodeContents(searchResultsContainer,html,js)};_exports.promisesAndResolvers=()=>{let bodyPromiseResolver;const bodyPromise=new Promise((resolve=>{bodyPromiseResolver=resolve}));return{bodyPromiseResolver:bodyPromiseResolver,bodyPromise:bodyPromise}}}));
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.showLoader=_exports.registerListenerEvents=_exports.promisesAndResolvers=_exports.init=void 0,Templates=_interopRequireWildcard(Templates),Selectors=_interopRequireWildcard(Selectors),_notification=(obj=_notification)&&obj.__esModule?obj:{default:obj};_exports.init=async function(widgetContentContainer,bodyPromise,data,searchFunc){let unsearchableContent=arguments.length>4&&void 0!==arguments[4]?arguments[4]:null;bodyPromise.then((async bodyContent=>{if(widgetContentContainer.innerHTML=bodyContent,unsearchableContent){widgetContentContainer.querySelector(Selectors.regions.unsearchableContent).innerHTML+=unsearchableContent}const searchResultsContainer=widgetContentContainer.querySelector(Selectors.regions.searchResults);await showLoader(searchResultsContainer),await renderSearchResults(searchResultsContainer,data),registerListenerEvents(widgetContentContainer,data,searchFunc)})).catch(_notification.default.exception)};const registerListenerEvents=(widgetContentContainer,data,searchFunc)=>{const searchResultsContainer=widgetContentContainer.querySelector(Selectors.regions.searchResults),searchInput=widgetContentContainer.querySelector(Selectors.actions.search);searchInput.focus();const clearSearchButton=widgetContentContainer.querySelector(Selectors.actions.clearSearch);searchInput.addEventListener("input",(0,_utils.debounce)((async()=>{searchInput.value.length>0?clearSearchButton.classList.remove("d-none"):clearSearchButton.classList.add("d-none"),await renderSearchResults(searchResultsContainer,debounceCallee(searchInput.value,data,searchFunc()))}),300)),clearSearchButton.addEventListener("click",(async e=>{e.stopPropagation(),searchInput.value="",searchInput.focus(),clearSearchButton.classList.add("d-none"),await renderSearchResults(searchResultsContainer,debounceCallee(searchInput.value,data,searchFunc()))}))};_exports.registerListenerEvents=registerListenerEvents;const showLoader=async container=>{const{html:html,js:js}=await Templates.renderForPromise("core_grades/searchwidget/loading",{});Templates.replaceNodeContents(container,html,js)};_exports.showLoader=showLoader;const debounceCallee=(searchValue,data,searchFunction)=>searchValue.length>0?searchFunction(data,searchValue):data,renderSearchResults=async(searchResultsContainer,searchResultsData)=>{const templateData={searchresults:searchResultsData},{html:html,js:js}=await Templates.renderForPromise("core_grades/searchwidget/searchresults",templateData);await Templates.replaceNodeContents(searchResultsContainer,html,js)};_exports.promisesAndResolvers=()=>{let bodyPromiseResolver;const bodyPromise=new Promise((resolve=>{bodyPromiseResolver=resolve}));return{bodyPromiseResolver:bodyPromiseResolver,bodyPromise:bodyPromise}}}));
//# sourceMappingURL=basewidget.min.js.map

File diff suppressed because one or more lines are too long

View File

@ -22,7 +22,6 @@
*/
import {debounce} from 'core/utils';
import * as Templates from 'core/templates';
import {comboBox} from 'core/aria';
import * as Selectors from 'core_grades/searchwidget/selectors';
import Notification from 'core/notification';
@ -69,6 +68,8 @@ export const init = async(widgetContentContainer, bodyPromise, data, searchFunc,
export const registerListenerEvents = (widgetContentContainer, data, searchFunc) => {
const searchResultsContainer = widgetContentContainer.querySelector(Selectors.regions.searchResults);
const searchInput = widgetContentContainer.querySelector(Selectors.actions.search);
// We want to focus on the first known user interable element within the dropdown.
searchInput.focus();
const clearSearchButton = widgetContentContainer.querySelector(Selectors.actions.clearSearch);
// The search input is triggered.
@ -108,9 +109,6 @@ export const registerListenerEvents = (widgetContentContainer, data, searchFunc)
)
);
});
// Trigger event handling for the results in line with aria guidelines.
comboBox(searchInput);
};
/**

View File

@ -105,6 +105,7 @@ class get_enrolled_users_for_search_widget extends external_api {
$userpicture->size = 1;
$user->profileimage = $userpicture->get_url($PAGE)->out(false);
$user->email = $guiuser->email;
$user->active = false; // @TODO MDL-76246
$users[] = $user;
}
@ -151,6 +152,7 @@ class get_enrolled_users_for_search_widget extends external_api {
core_user::get_property_type('email'),
'An email address - allow email as root@localhost',
VALUE_OPTIONAL),
'active' => new external_value(PARAM_BOOL, 'Are we currently on this item?', VALUE_REQUIRED)
];
return new external_single_structure($userfields);
}

View File

@ -116,8 +116,10 @@ class get_groups_for_search_widget extends external_api {
'group' => $group->id
]);
return (object) [
'id' => $group->id,
'name' => $group->name,
'url' => $url->out(false),
'active' => false // @TODO MDL-76246
];
}, $groupsmenu);
}
@ -147,8 +149,10 @@ class get_groups_for_search_widget extends external_api {
*/
public static function group_description(): external_description {
$groupfields = [
'id' => new external_value(PARAM_ALPHANUM, 'An ID for the group', VALUE_REQUIRED),
'url' => new external_value(PARAM_URL, 'The link that applies the group action', VALUE_REQUIRED),
'name' => new external_value(PARAM_TEXT, 'The full name of the group', VALUE_REQUIRED),
'active' => new external_value(PARAM_BOOL, 'Are we currently on this item?', VALUE_REQUIRED)
];
return new external_single_structure($groupfields);
}

View File

@ -85,7 +85,7 @@ class singleview extends core_course_external {
$gradeitems = array_map(function ($gradeitem) use ($PAGE, $USER, $params) {
$item = new \stdClass();
$item->gid = $gradeitem->id;
$item->id = $gradeitem->id;
$url = new moodle_url('/grade/report/singleview/index.php', [
'id' => $params['courseid'],
'itemid' => $gradeitem->id,
@ -94,6 +94,7 @@ class singleview extends core_course_external {
);
$item->name = $gradeitem->get_name(true);
$item->url = $url->out(false);
$item->active = false; // @TODO MDL-76246
return $item;
}, $gradeableitems);
@ -114,13 +115,14 @@ class singleview extends core_course_external {
return new external_single_structure([
'gradeitems' => new external_multiple_structure(
new external_single_structure([
'gid' => new external_value(PARAM_INT, 'ID of the grade item', VALUE_OPTIONAL),
'id' => new external_value(PARAM_INT, 'ID of the grade item', VALUE_OPTIONAL),
'url' => new external_value(
PARAM_URL,
'The link to the grade report',
VALUE_OPTIONAL
),
'name' => new external_value(PARAM_TEXT, 'The full name of the grade item', VALUE_OPTIONAL),
'active' => new external_value(PARAM_BOOL, 'Are we currently on this item?', VALUE_REQUIRED)
])
),
'warnings' => new external_warnings(),

View File

@ -27,7 +27,7 @@
}
}}
<div class="zero-state mx-auto w-50 text-center my-6">
<img src="{{imglink}}" alt="{{#str}}pluginname, gradereport_singleview{{/str}}" role="presentation" class="my-5">
<img src="{{imglink}}" alt="{{#str}}pluginname, gradereport_singleview{{/str}}" aria-hidden="true" class="my-5">
<h3>{{#str}}pluginname, gradereport_singleview{{/str}}</h3>
<p>{{#str}}viewsingleuserorgradeitem, gradereport_singleview{{/str}}</p>
<div class="justify-content-center d-flex">

View File

@ -25,7 +25,7 @@
}
}}
<div class="zero-state mx-auto w-50 text-center my-6">
<img src="{{imglink}}" alt="{{#str}}viewsinglegradeitem, gradereport_singleview{{/str}}" role="presentation" class="my-5">
<img src="{{imglink}}" alt="{{#str}}viewsinglegradeitem, gradereport_singleview{{/str}}" aria-hidden="true" class="my-5">
<h3>{{#str}}viewsinglegradeitem, gradereport_singleview{{/str}}</h3>
<p>{{#str}}singleviewdescription, gradereport_singleview{{/str}}</p>
</div>

View File

@ -25,7 +25,7 @@
}
}}
<div class="zero-state mx-auto w-50 text-center my-6">
<img src="{{imglink}}" alt="{{#str}}viewsingleuser, gradereport_singleview{{/str}}" role="presentation" class="my-5">
<img src="{{imglink}}" alt="{{#str}}viewsingleuser, gradereport_singleview{{/str}}" aria-hidden="true" class="my-5">
<h3>{{#str}}viewsingleuser, gradereport_singleview{{/str}}</h3>
<p>{{#str}}singleviewdescription, gradereport_singleview{{/str}}</p>
</div>

View File

@ -5,6 +5,6 @@ define("gradereport_user/user",["exports","core/pending","core/templates","core_
* @module gradereport_user/user
* @copyright 2022 Mathew May <mathew.solutions>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_pending=_interopRequireDefault(_pending),Templates=_interopRequireWildcard(Templates),Repository=_interopRequireWildcard(Repository),WidgetBase=_interopRequireWildcard(WidgetBase),_url=_interopRequireDefault(_url),_jquery=_interopRequireDefault(_jquery),Selectors=_interopRequireWildcard(Selectors);_exports.init=()=>{const pendingPromise=new _pending.default;registerListenerEvents(),pendingPromise.resolve()};const registerListenerEvents=()=>{let{bodyPromiseResolver:bodyPromiseResolver,bodyPromise:bodyPromise}=WidgetBase.promisesAndResolvers();const dropdownMenuContainer=document.querySelector(Selectors.elements.getSearchWidgetDropdownSelector("user"));(0,_jquery.default)(Selectors.elements.getSearchWidgetSelector("user")).on("show.bs.dropdown",(async e=>{const courseID=e.relatedTarget.dataset.courseid,groupId=e.relatedTarget.dataset.groupid,actionBaseUrl=_url.default.relativeUrl("/grade/report/user/index.php",{},!1);await WidgetBase.showLoader(dropdownMenuContainer);const data=await Repository.userFetch(courseID,actionBaseUrl,groupId).catch((async e=>{const errorTemplateData={errormessage:e.message};bodyPromiseResolver(await Templates.render("core_grades/searchwidget/error",errorTemplateData))}));if(data===[])return;const allUsersOptionName=await(0,_str.get_string)("allusersnum","gradereport_user",data.users.length),allUsersOption=await Templates.render("core_grades/searchwidget/searchitem",{id:0,name:allUsersOptionName,url:_url.default.relativeUrl("/grade/report/user/index.php",{id:courseID,userid:0},!1)});await WidgetBase.init(dropdownMenuContainer,bodyPromise,data.users,searchUsers(),allUsersOption),bodyPromiseResolver(Templates.render("core_grades/searchwidget/user/usersearch_body",{displayunsearchablecontent:!0}))})),(0,_jquery.default)(Selectors.elements.getSearchWidgetSelector("user")).on("hide.bs.dropdown",(()=>{dropdownMenuContainer.innerHTML=""}))},searchUsers=()=>()=>(users,searchTerm)=>{if(""===searchTerm)return users;searchTerm=searchTerm.toLowerCase();const searchResults=[];return users.forEach((user=>{user.fullname.toLowerCase().includes(searchTerm)&&searchResults.push(user)})),searchResults}}));
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_pending=_interopRequireDefault(_pending),Templates=_interopRequireWildcard(Templates),Repository=_interopRequireWildcard(Repository),WidgetBase=_interopRequireWildcard(WidgetBase),_url=_interopRequireDefault(_url),_jquery=_interopRequireDefault(_jquery),Selectors=_interopRequireWildcard(Selectors);_exports.init=()=>{const pendingPromise=new _pending.default;registerListenerEvents(),pendingPromise.resolve()};const registerListenerEvents=()=>{let{bodyPromiseResolver:bodyPromiseResolver,bodyPromise:bodyPromise}=WidgetBase.promisesAndResolvers();const dropdownMenuContainer=document.querySelector(Selectors.elements.getSearchWidgetDropdownSelector("user"));(0,_jquery.default)(Selectors.elements.getSearchWidgetSelector("user")).on("show.bs.dropdown",(async e=>{const courseID=e.relatedTarget.dataset.courseid,groupId=e.relatedTarget.dataset.groupid,actionBaseUrl=_url.default.relativeUrl("/grade/report/user/index.php",{},!1);await WidgetBase.showLoader(dropdownMenuContainer);const data=await Repository.userFetch(courseID,actionBaseUrl,groupId).catch((async e=>{const errorTemplateData={errormessage:e.message};bodyPromiseResolver(await Templates.render("core_grades/searchwidget/error",errorTemplateData))}));if(data===[])return;const allUsersOptionName=await(0,_str.get_string)("allusersnum","gradereport_user",data.users.length),allUsersOption=await Templates.render("gradereport_user/all_users_item",{id:0,name:allUsersOptionName,url:_url.default.relativeUrl("/grade/report/user/index.php",{id:courseID,userid:0},!1)});await WidgetBase.init(dropdownMenuContainer,bodyPromise,data.users,searchUsers(),allUsersOption),bodyPromiseResolver(Templates.render("core_grades/searchwidget/user/usersearch_body",{displayunsearchablecontent:!0}))})),(0,_jquery.default)(Selectors.elements.getSearchWidgetSelector("user")).on("hide.bs.dropdown",(()=>{dropdownMenuContainer.innerHTML=""}))},searchUsers=()=>()=>(users,searchTerm)=>{if(""===searchTerm)return users;searchTerm=searchTerm.toLowerCase();const searchResults=[];return users.forEach((user=>{user.fullname.toLowerCase().includes(searchTerm)&&searchResults.push(user)})),searchResults}}));
//# sourceMappingURL=user.min.js.map

File diff suppressed because one or more lines are too long

View File

@ -76,7 +76,7 @@ const registerListenerEvents = () => {
// The HTML for the 'All users' option which will be rendered in the non-searchable content are of the widget.
const allUsersOptionName = await getString('allusersnum', 'gradereport_user', data.users.length);
const allUsersOption = await Templates.render('core_grades/searchwidget/searchitem', {
const allUsersOption = await Templates.render('gradereport_user/all_users_item', {
id: 0,
name: allUsersOptionName,
url: Url.relativeUrl('/grade/report/user/index.php', {id: courseID, userid: 0}, false),

View File

@ -0,0 +1,33 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template gradereport_user/all_users_item
Search result line items.
Example context (json):
{
"id": "0",
"name": "All users(10)",
"url": "http://foo.bar/gradereport/?userid=0&id=2"
}
}}
<a role="menuitem" id="item-{{id}}" href="{{url}}" class="dropdown-item d-flex px-2 py-1" tabindex="0">
<span class="pull-left result-cell text-truncate mr-2">
{{name}}
</span>
</a>

View File

@ -25,7 +25,7 @@
}
}}
<div class="zero-state mx-auto w-50 text-center my-6">
<img src="{{imglink}}" alt="{{#str}}selectuser, gradereport_user{{/str}}" role="presentation" class="my-5">
<img src="{{imglink}}" alt="{{#str}}selectuser, gradereport_user{{/str}}" aria-hidden="true" class="my-5">
<h3>{{#str}}selectuser, gradereport_user{{/str}}</h3>
<p>{{#str}}selectuserinstructions, gradereport_user{{/str}}</p>
</div>

View File

@ -24,23 +24,26 @@
"id": "1",
"name": "Quiz 1",
"url": "http://foo.bar/gradereport/?userid=25",
"fullname": "Cameron Greeve"
"fullname": "Cameron Greeve",
"active": true
}
}}
{{#name}}
<a role="option" href="{{url}}" class="dropdown-item d-flex px-2 py-1">
<span class="pull-left result-cell text-truncate mr-2">
{{name}}
</span>
</a>
{{/name}}
{{^name}}
<a role="option" href="{{url}}" class="dropdown-item d-flex px-2 py-1 align-items-center">
<span class="w-50 pull-left text-truncate mr-2">
{{fullname}}
</span>
<span class="w-50 pull-left text-truncate small email">
{{email}}
</span>
</a>
{{/name}}
<li class="w-100 result-row" role="none" id="result-row-{{id}}">
{{#name}}
<a role="menuitem" id="item-{{id}}" href="{{url}}" class="dropdown-item d-flex px-2 py-1" tabindex="-1"">
<span class="pull-left result-cell text-truncate mr-2">
{{name}}
</span>
</a>
{{/name}}
{{^name}}
<a role="menuitem" id="user-{{id}}" href="{{url}}" class="dropdown-item d-flex px-2 py-1 align-items-center" tabindex="-1">
<span class="w-50 pull-left text-truncate mr-2">
{{fullname}}
</span>
<span class="w-50 pull-left text-truncate small email">
{{email}}
</span>
</a>
{{/name}}
</li>

View File

@ -34,13 +34,13 @@
]
}
}}
<div class="searchresultitemscontainer-wrapper">
<div class="searchresultitemscontainer d-flex flex-column mw-100 position-relative py-2" role="listbox" data-region="search-result-items-container">
<div class="searchresultitemscontainer-wrapper dropdown">
<ul class="searchresultitemscontainer d-flex flex-column mw-100 position-relative py-2 list-group" role="menu" data-region="search-result-items-container" tabindex="0">
{{#searchresults}}
{{>core_grades/searchwidget/searchitem}}
{{/searchresults}}
{{^searchresults}}
<p>{{#str}} resultsfound, core, 0 {{/str}}</p>
{{/searchresults}}
</div>
</ul>
</div>

View File

@ -32,5 +32,5 @@
<div class="searchresultscontainer" data-region="search-results-container-widget" aria-live="polite"></div>
{{#displayunsearchablecontent}}
<div class="unsearchablecontentcontainer" data-region="unsearchable-content-container-widget" aria-live="polite"></div>
<div class="unsearchablecontentcontainer" data-region="unsearchable-content-container-widget" role="menu" aria-live="polite"></div>
{{/displayunsearchablecontent}}

View File

@ -1,3 +1,3 @@
define("core/aria",["exports","./local/aria/aria-hidden","./local/aria/aria-combobox"],(function(_exports,_ariaHidden,_ariaCombobox){Object.defineProperty(_exports,"__esModule",{value:!0}),Object.defineProperty(_exports,"comboBox",{enumerable:!0,get:function(){return _ariaCombobox.comboBox}}),Object.defineProperty(_exports,"hide",{enumerable:!0,get:function(){return _ariaHidden.hide}}),Object.defineProperty(_exports,"hideSiblings",{enumerable:!0,get:function(){return _ariaHidden.hideSiblings}}),Object.defineProperty(_exports,"unhide",{enumerable:!0,get:function(){return _ariaHidden.unhide}}),Object.defineProperty(_exports,"unhideSiblings",{enumerable:!0,get:function(){return _ariaHidden.unhideSiblings}})}));
define("core/aria",["exports","./local/aria/aria-hidden"],(function(_exports,_ariaHidden){Object.defineProperty(_exports,"__esModule",{value:!0}),Object.defineProperty(_exports,"hide",{enumerable:!0,get:function(){return _ariaHidden.hide}}),Object.defineProperty(_exports,"hideSiblings",{enumerable:!0,get:function(){return _ariaHidden.hideSiblings}}),Object.defineProperty(_exports,"unhide",{enumerable:!0,get:function(){return _ariaHidden.unhide}}),Object.defineProperty(_exports,"unhideSiblings",{enumerable:!0,get:function(){return _ariaHidden.unhideSiblings}})}));
//# sourceMappingURL=aria.min.js.map

View File

@ -1,3 +0,0 @@
define("core/local/aria/aria-combobox",["exports","core/key_codes"],(function(_exports,_key_codes){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.comboBox=void 0;_exports.comboBox=comboInput=>{registerEventListeners(comboInput)};const registerEventListeners=comboInput=>{document.addEventListener("keydown",(e=>{if(e.target===comboInput){let next=null;const comboResultArea=document.querySelector('[data-region="search-result-items-container"]'),resultRows=Array.from(comboResultArea.querySelectorAll('[role="row"]')),resultCells=Array.from(comboResultArea.querySelectorAll('[role="gridcell"]')),activeResultRow=comboResultArea.querySelector('.active[role="row"]'),activeResultCell=comboResultArea.querySelector('.focused-cell[role="gridcell"]');switch(e.keyCode){case _key_codes.arrowUp:if(null===activeResultRow)next=setFirstActiveRow(next,resultRows,comboInput,resultRows.length-1);else for(let i=0;i<resultRows.length;i++)if(resultRows[i].id===activeResultRow.id){next=resultRows[i-1];break}break;case _key_codes.arrowDown:if(null===activeResultRow)next=setFirstActiveRow(next,resultRows,comboInput,0);else for(let i=0;i<resultRows.length-1;i++)if(resultRows[i].id===activeResultRow.id){next=resultRows[i+1];break}break;case _key_codes.home:next=resultRows[0];break;case _key_codes.end:next=resultRows[resultRows.length-1];break;case _key_codes.enter||_key_codes.space:window.location=activeResultCell.href;break;case _key_codes.arrowLeft:if(null===activeResultRow)next=setFirstActiveRow(next,resultRows,comboInput,0);else for(let i=0;i<resultCells.length;i++)if(resultCells[i].id===activeResultCell.id){if(void 0===resultCells[i-1]){resultCells[i].classList.remove("focused-cell"),resultCells[resultCells.length-1].classList.add("focused-cell");break}resultCells[i].classList.remove("focused-cell"),resultCells[i-1].classList.add("focused-cell");break}break;case _key_codes.arrowRight:if(null===activeResultRow)next=setFirstActiveRow(next,resultRows,comboInput,0);else for(let i=0;i<resultCells.length-1;i++){if(resultCells[i].id===activeResultCell.id){resultCells[i].classList.remove("focused-cell"),resultCells[i+1].classList.add("focused-cell");break}if(void 0===resultCells[i+2]){resultCells[i+1].classList.remove("focused-cell"),resultCells[0].classList.add("focused-cell");break}}break;default:window.console.log("nothing to see here!")}nextHandler(next,e,activeResultRow,comboInput)}}))},setFirstActiveRow=(next,resultRows,comboInput,val)=>((next=resultRows[val]).setAttribute("aria-selected","true"),next.classList.add("active"),comboInput.setAttribute("aria-activedescendant",next.id),next.querySelector(".result-cell").classList.add("focused-cell"),next),nextHandler=(next,e,activeResultRow,comboInput)=>{next&&(e.preventDefault(),null!==activeResultRow&&(activeResultRow.classList.remove("active"),activeResultRow.querySelector(".result-cell").classList.remove("focused-cell")),next.classList.add("active"),next.querySelector(".result-cell").classList.add("focused-cell"),comboInput.setAttribute("aria-activedescendant",next.id))}}));
//# sourceMappingURL=aria-combobox.min.js.map

File diff suppressed because one or more lines are too long

View File

@ -27,6 +27,3 @@ export {
hideSiblings,
unhideSiblings,
} from './local/aria/aria-hidden';
export {
comboBox
} from './local/aria/aria-combobox';

View File

@ -1,180 +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 <http://www.gnu.org/licenses/>.
import {end, arrowLeft, arrowRight, arrowUp, arrowDown, home, enter, space} from 'core/key_codes';
/**
* ARIA helpers related to the combobox role.
*
* @module core/local/aria/aria-combobox.
* @copyright 2022 Mathew May <mathew.solutions>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Our entry point into adding accessibility handling for comboboxes.
*
* @param {Element} comboInput The combobox area to add aria handling to.
*/
export const comboBox = (comboInput) => {
registerEventListeners(comboInput);
};
/**
* Event management of the provided combobox.
*
* @param {Element} comboInput The combobox area to add aria handling to.
*/
const registerEventListeners = (comboInput) => {
document.addEventListener('keydown', (e) => {
if (e.target === comboInput) {
let next = null;
const comboResultArea = document.querySelector('[data-region="search-result-items-container"]');
const resultRows = Array.from(comboResultArea.querySelectorAll('[role="row"]'));
const resultCells = Array.from(comboResultArea.querySelectorAll('[role="gridcell"]'));
const activeResultRow = comboResultArea.querySelector('.active[role="row"]');
const activeResultCell = comboResultArea.querySelector('.focused-cell[role="gridcell"]');
switch (e.keyCode) {
case arrowUp: {
// TODO: Handle the wrapping.
if (activeResultRow === null) {
next = setFirstActiveRow(next, resultRows, comboInput, resultRows.length - 1);
} else {
for (let i = 0; i < resultRows.length; i++) {
if (resultRows[i].id === activeResultRow.id) {
next = resultRows[i - 1];
break;
}
}
}
break;
}
case arrowDown: {
if (activeResultRow === null) {
next = setFirstActiveRow(next, resultRows, comboInput, 0);
} else {
for (let i = 0; i < resultRows.length - 1; i++) {
if (resultRows[i].id === activeResultRow.id) {
next = resultRows[i + 1];
break;
}
}
}
break;
}
case home: {
next = resultRows[0];
break;
}
case end: {
next = resultRows[resultRows.length - 1];
break;
}
case enter || space: {
// Redirect the user to the appropriate link.
// TODO: Space does not work, special handler on the cell itself?
window.location = activeResultCell.href;
break;
}
case arrowLeft: {
if (activeResultRow === null) {
next = setFirstActiveRow(next, resultRows, comboInput, 0);
} else {
for (let i = 0; i < resultCells.length; i++) {
if (resultCells[i].id === activeResultCell.id) {
if (resultCells[i - 1] === undefined) {
resultCells[i].classList.remove('focused-cell');
resultCells[resultCells.length - 1].classList.add('focused-cell');
break;
} else {
resultCells[i].classList.remove('focused-cell');
resultCells[i - 1].classList.add('focused-cell');
break;
}
}
}
}
break;
}
case arrowRight: {
if (activeResultRow === null) {
next = setFirstActiveRow(next, resultRows, comboInput, 0);
} else {
for (let i = 0; i < resultCells.length - 1; i++) {
if (resultCells[i].id === activeResultCell.id) {
resultCells[i].classList.remove('focused-cell');
resultCells[i + 1].classList.add('focused-cell');
break;
}
if (resultCells[i + 2] === undefined) {
resultCells[i + 1].classList.remove('focused-cell');
resultCells[0].classList.add('focused-cell');
break;
}
}
}
break;
}
default: {
window.console.log('nothing to see here!');
break;
}
}
// Variable next is set if we do want to act on the keypress.
nextHandler(next, e, activeResultRow, comboInput);
}
});
};
/**
* With search, we can't automatically set aria elements in the results field, so we do it here.
*
* @param {Element} next
* @param {Array} resultRows
* @param {Element} comboInput
* @param {Number} val
* @returns {Element}
*/
const setFirstActiveRow = (next, resultRows, comboInput, val) => {
// Set first option as active.
next = resultRows[val];
next.setAttribute('aria-selected', 'true');
next.classList.add('active');
comboInput.setAttribute('aria-activedescendant', next.id);
next.querySelector('.result-cell').classList.add('focused-cell');
return next;
};
/**
* Given we have a value to next set active, handle some of the basic handling.
*
* @param {Element} next
* @param {Event} e
* @param {Element} activeResultRow
* @param {Element} comboInput
*/
const nextHandler = (next, e, activeResultRow, comboInput) => {
if (next) {
e.preventDefault();
if (activeResultRow !== null) {
activeResultRow.classList.remove('active');
activeResultRow.querySelector('.result-cell').classList.remove('focused-cell');
}
next.classList.add('active');
// Find whatever the first result cell is to add the class.
next.querySelector('.result-cell').classList.add('focused-cell');
comboInput.setAttribute('aria-activedescendant', next.id);
}
};

View File

@ -43,6 +43,7 @@
</label>
<input
type="text"
role="searchbox"
data-region="input"
data-action="search"
id="searchinput"