MDL-78885 core_user: A11y fixes for user combobox search

Co-authored-by: Shamim Rezaie <[email protected]>
This commit is contained in:
Mathew May
2024-03-22 03:26:19 +11:00
committed by Shamim Rezaie
co-authored by Shamim Rezaie
parent 27c1692867
commit e4083441ec
25 changed files with 94 additions and 128 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
define("core_grades/comboboxsearch/grade",["exports","core/comboboxsearch/search_combobox","core_grades/searchwidget/repository","core/templates","core/utils"],(function(_exports,_search_combobox,Repository,_templates,_utils){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)}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_search_combobox=(obj=_search_combobox)&&obj.__esModule?obj:{default:obj},Repository=function(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]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(Repository);class GradeItemSearch extends _search_combobox.default{constructor(){super(),function(obj,key,value){key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value}(this,"courseID",void 0),this.selectors={...this.selectors,courseid:'[data-region="courseid"]',placeholder:'.gradesearchdropdown [data-region="searchplaceholder"]'};const component=document.querySelector(this.componentSelector());this.courseID=component.querySelector(this.selectors.courseid).dataset.courseid,this.renderDefault()}static init(){return new GradeItemSearch}componentSelector(){return".grade-search"}dropdownSelector(){return".gradesearchdropdown"}triggerSelector(){return".gradesearchwidget"}async renderDropdown(){const{html:html,js:js}=await(0,_templates.renderForPromise)("core/local/comboboxsearch/resultset",{results:this.getMatchedResults(),hasresults:this.getMatchedResults().length>0,searchterm:this.getSearchTerm()});(0,_templates.replaceNodeContents)(this.selectors.placeholder,html,js)}async renderDefault(){this.setMatchedResults(await this.filterDataset(await this.getDataset())),this.filterMatchDataset(),await this.renderDropdown(),this.updateNodes(),this.registerInputEvents(),this.$component.on("shown.bs.dropdown",(()=>{this.searchInput.focus({preventScroll:!0})}))}async fetchDataset(){return await Repository.gradeitemFetch(this.courseID).then((r=>r.gradeitems))}async filterDataset(filterableData){return""===this.getPreppedSearchTerm()?filterableData:filterableData.filter((grade=>Object.keys(grade).some((key=>""!==grade[key]&&grade[key].toString().toLowerCase().includes(this.getPreppedSearchTerm())))))}filterMatchDataset(){this.setMatchedResults(this.getMatchedResults().map((grade=>({id:grade.id,name:grade.name,link:this.selectOneLink(grade.id)}))))}registerInputEvents(){this.searchInput.addEventListener("input",(0,_utils.debounce)((async()=>{this.setSearchTerms(this.searchInput.value),""===this.searchInput.value?this.clearSearchButton.classList.add("d-none"):this.clearSearchButton.classList.remove("d-none"),await this.filterrenderpipe()}),300))}async clickHandler(e){e.target.closest(this.selectors.dropdown)&&e.stopImmediatePropagation(),this.clearSearchButton.addEventListener("click",(async()=>{this.searchInput.value="",this.setSearchTerms(this.searchInput.value),await this.filterrenderpipe()})),e.target.closest(".dropdown-item")&&0===e.button&&(window.location=e.target.closest(".dropdown-item").href)}keyHandler(e){switch(super.keyHandler(e),e.key){case"Tab":e.target.closest(this.selectors.input)&&(e.preventDefault(),this.clearSearchButton.focus({preventScroll:!0}));break;case"Escape":if("option"===document.activeElement.getAttribute("role"))e.stopPropagation(),this.searchInput.focus({preventScroll:!0});else if(e.target.closest(this.selectors.input)){this.component.querySelector(this.selectors.trigger).focus({preventScroll:!0})}}}registerInputHandlers(){this.searchInput.addEventListener("input",(0,_utils.debounce)((()=>{this.setSearchTerms(this.searchInput.value),""===this.getSearchTerm()?this.clearSearchButton.classList.add("d-none"):this.clearSearchButton.classList.remove("d-none")}),300))}selectOneLink(gradeID){throw new Error("selectOneLink(".concat(gradeID,") must be implemented in ").concat(this.constructor.name))}}return _exports.default=GradeItemSearch,_exports.default}));
define("core_grades/comboboxsearch/grade",["exports","core/comboboxsearch/search_combobox","core_grades/searchwidget/repository","core/templates","core/utils"],(function(_exports,_search_combobox,Repository,_templates,_utils){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)}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_search_combobox=(obj=_search_combobox)&&obj.__esModule?obj:{default:obj},Repository=function(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]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(Repository);class GradeItemSearch extends _search_combobox.default{constructor(){super(),function(obj,key,value){key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value}(this,"courseID",void 0),this.selectors={...this.selectors,courseid:'[data-region="courseid"]',placeholder:'.gradesearchdropdown [data-region="searchplaceholder"]'};const component=document.querySelector(this.componentSelector());this.courseID=component.querySelector(this.selectors.courseid).dataset.courseid,this.renderDefault()}static init(){return new GradeItemSearch}componentSelector(){return".grade-search"}dropdownSelector(){return".gradesearchdropdown"}triggerSelector(){return".gradesearchwidget"}async renderDropdown(){const{html:html,js:js}=await(0,_templates.renderForPromise)("core/local/comboboxsearch/resultset",{results:this.getMatchedResults(),hasresults:this.getMatchedResults().length>0,searchterm:this.getSearchTerm()});(0,_templates.replaceNodeContents)(this.selectors.placeholder,html,js)}async renderDefault(){this.setMatchedResults(await this.filterDataset(await this.getDataset())),this.filterMatchDataset(),await this.renderDropdown(),this.updateNodes(),this.registerInputEvents(),this.$component.on("shown.bs.dropdown",(()=>{this.searchInput.focus({preventScroll:!0})}))}async fetchDataset(){return await Repository.gradeitemFetch(this.courseID).then((r=>r.gradeitems))}async filterDataset(filterableData){return""===this.getPreppedSearchTerm()?filterableData:filterableData.filter((grade=>Object.keys(grade).some((key=>""!==grade[key]&&grade[key].toString().toLowerCase().includes(this.getPreppedSearchTerm())))))}filterMatchDataset(){this.setMatchedResults(this.getMatchedResults().map((grade=>({id:grade.id,name:grade.name,link:this.selectOneLink(grade.id)}))))}registerInputEvents(){this.searchInput.addEventListener("input",(0,_utils.debounce)((async()=>{this.setSearchTerms(this.searchInput.value),""===this.searchInput.value?this.clearSearchButton.classList.add("d-none"):this.clearSearchButton.classList.remove("d-none"),await this.filterrenderpipe()}),300))}async clickHandler(e){e.target.closest(this.selectors.dropdown)&&e.stopImmediatePropagation(),this.clearSearchButton.addEventListener("click",(async()=>{this.searchInput.value="",this.setSearchTerms(this.searchInput.value),await this.filterrenderpipe()})),e.target.closest(".dropdown-item")&&0===e.button&&(window.location=e.target.closest(".dropdown-item").href)}keyHandler(e){if(super.keyHandler(e),"Escape"===e.key)if("option"===document.activeElement.getAttribute("role"))e.stopPropagation(),this.searchInput.focus({preventScroll:!0});else if(e.target.closest(this.selectors.input)){this.component.querySelector(this.selectors.trigger).focus({preventScroll:!0})}}registerInputHandlers(){this.searchInput.addEventListener("input",(0,_utils.debounce)((()=>{this.setSearchTerms(this.searchInput.value),""===this.getSearchTerm()?this.clearSearchButton.classList.add("d-none"):this.clearSearchButton.classList.remove("d-none")}),300))}selectOneLink(gradeID){throw new Error("selectOneLink(".concat(gradeID,") must be implemented in ").concat(this.constructor.name))}}return _exports.default=GradeItemSearch,_exports.default}));
//# sourceMappingURL=grade.min.js.map
File diff suppressed because one or more lines are too long
-6
View File
@@ -199,12 +199,6 @@ export default class GradeItemSearch extends search_combobox {
super.keyHandler(e);
// Switch the key presses to handle keyboard nav.
switch (e.key) {
case 'Tab':
if (e.target.closest(this.selectors.input)) {
e.preventDefault();
this.clearSearchButton.focus({preventScroll: true});
}
break;
case 'Escape':
if (document.activeElement.getAttribute('role') === 'option') {
e.stopPropagation();
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-21
View File
@@ -218,27 +218,6 @@ export default class ColumnSearch extends search_combobox {
}
}
/**
* The handler for when a user presses a key within the component.
*
* @param {KeyboardEvent} e The triggering event that we are working with.
*/
async keyHandler(e) {
super.keyHandler(e);
// Switch the key presses to handle keyboard nav.
switch (e.key) {
case 'Tab':
if (e.target.closest(this.selectors.input)) {
if (!this.clearSearchButton.classList.contains('d-none')) {
e.preventDefault();
this.clearSearchButton.focus({preventScroll: true});
}
}
break;
}
}
/**
* Handle any keyboard inputs.
*/
@@ -209,11 +209,7 @@ Feature: Within the grader report, test that we can collapse columns
And I wait until "Mobile phone" "option_role" exists
And I press the tab key
And the focused element is "Clear search input" "button" in the ".dropdown-menu.show" "css_element"
And I press the tab key
And I press the tab key
And I press the tab key
And I press the tab key
And the focused element is "Close" "button" in the ".dropdown-menu.show" "css_element"
And I press the escape key
And I press the tab key
# The course grade category menu.
And the focused element is "Cell actions" "button"
@@ -159,10 +159,9 @@ Feature: Within the grader report, test that we can search for users
Given I click on "Turtle" in the "user" search widget
# The search input remains in the field on reload this is in keeping with other search implementations.
When the field "Search users" matches value "Turtle"
And I wait until "View all results (1)" "link" does not exist
# Test if we can then further retain the turtle result set and further filter from there.
Then I set the field "Search users" to "Turtle plagiarism"
And "Turtle Manatee" "list_item" should not exist
And "Turtle Manatee" "list_item" should not be visible
And I should see "No results for \"Turtle plagiarism\""
Scenario: A teacher can search for values besides the users' name
@@ -244,15 +243,15 @@ Feature: Within the grader report, test that we can search for users
And the page should meet "wcag131, wcag141, wcag412" accessibility standards
And the page should meet accessibility standards with "wcag131, wcag141, wcag412" extra tests
And I press the down key
And the focused element is "Student 1" "option_role"
And ".active" "css_element" should exist in the "Student 1" "option_role"
And I press the end key
And the focused element is "View all results (5)" "option_role"
And ".active" "css_element" should exist in the "View all results (5)" "option_role"
And I press the home key
And the focused element is "Student 1" "option_role"
And ".active" "css_element" should exist in the "Student 1" "option_role"
And I press the up key
And the focused element is "View all results (5)" "option_role"
And ".active" "css_element" should exist in the "View all results (5)" "option_role"
And I press the down key
And the focused element is "Student 1" "option_role"
And ".active" "css_element" should exist in the "Student 1" "option_role"
And I press the escape key
And the focused element is "Search users" "field"
Then I set the field "Search users" to "Goodmeme"
@@ -263,16 +262,15 @@ Feature: Within the grader report, test that we can search for users
And I set the field "Search users" to "ABC"
And I wait until "Turtle Manatee" "option_role" exists
And I press the down key
And the focused element is "Student 1" "option_role"
And ".active" "css_element" should exist in the "Student 1" "option_role"
# Lets check the tabbing order.
And I set the field "Search users" to "ABC"
And I wait until "View all results (5)" "option_role" exists
And I click on "Search users" "field"
And I wait until "Turtle Manatee" "option_role" exists
And I press the tab key
And the focused element is "Clear search input" "button"
And I press the tab key
And the focused element is "View all results (5)" "option_role"
And I press the tab key
And ".groupsearchwidget" "css_element" should exist
# Ensure we can interact with the input & clear search options with the keyboard.
# Space & Enter have the same handling for triggering the two functionalities.
@@ -289,15 +287,6 @@ Feature: Within the grader report, test that we can search for users
| Teacher 1 |
| Student 1 |
| Turtle Manatee |
# Sometimes with behat we get unattached nodes causing spurious failures.
And I wait "1" seconds
And I set the field "Search users" to "ABC"
And I wait until "Turtle Manatee" "option_role" exists
And I press the tab key
And the focused element is "Clear search input" "button"
And I press the enter key
And I wait until the page is ready
And I confirm "Turtle Manatee" in "user" search within the gradebook widget does not exist
Scenario: Once a teacher searches, it'll apply the currently set filters and inform the teacher as such
# Set up a basic filtering case.
@@ -128,7 +128,7 @@ Feature: Within the User report, a teacher can search for users.
And I wait until "View all results (5)" "link" does not exist
# Test if we can then further retain the turtle result set and further filter from there.
Then I set the field "Search users" to "Turtle plagiarism"
And "Turtle Manatee" "list_item" should not exist
And I wait until "Turtle Manatee" "list_item" does not exist
And I should see "No results for \"Turtle plagiarism\""
Scenario: A teacher can search for values besides the users' name
@@ -200,21 +200,19 @@ Feature: Within the User report, a teacher can search for users.
Scenario: A teacher can set focus and search using the input are with a keyboard
Given I set the field "Search users" to "ABC"
# Basic tests for the page.
And the page should meet accessibility standards
And the page should meet "wcag131, wcag141, wcag412" accessibility standards
And the page should meet accessibility standards with "wcag131, wcag141, wcag412" extra tests
# Move onto general keyboard navigation testing.
When "Turtle Manatee" "option_role" should exist
And I press the down key
And the focused element is "Student 1" "option_role"
And ".active" "css_element" should exist in the "Student 1" "option_role"
And I press the end key
And the focused element is "View all results (5)" "option_role"
And ".active" "css_element" should exist in the "View all results (5)" "option_role"
And I press the home key
And the focused element is "Student 1" "option_role"
And ".active" "css_element" should exist in the "Student 1" "option_role"
And I press the up key
And the focused element is "View all results (5)" "option_role"
And ".active" "css_element" should exist in the "View all results (5)" "option_role"
And I press the down key
And the focused element is "Student 1" "option_role"
And ".active" "css_element" should exist in the "Student 1" "option_role"
And I press the escape key
And the focused element is "Search users" "field"
Then I set the field "Search users" to "Goodmeme"
@@ -225,16 +223,15 @@ Feature: Within the User report, a teacher can search for users.
And I set the field "Search users" to "ABC"
And "Turtle Manatee" "option_role" should exist
And I press the down key
And the focused element is "Student 1" "option_role"
And ".active" "css_element" should exist in the "Student 1" "option_role"
# Lets check the tabbing order.
And I set the field "Search users" to "ABC"
And "View all results (5)" "option_role" should exist
And I wait until "Clear search input" "button" exists
And I click on "Search users" "field"
And I press the tab key
And the focused element is "Clear search input" "button" in the ".user-search" "css_element"
And I press the tab key
And the focused element is "View all results (5)" "option_role"
And I press the tab key
And ".groupsearchwidget" "css_element" should exist
# Ensure we can interact with the input & clear search options with the keyboard.
# Space & Enter have the same handling for triggering the two functionalities.
@@ -248,12 +245,3 @@ Feature: Within the User report, a teacher can search for users.
And "User Test" "heading" should not exist
And "Teacher 1" "heading" should not exist
And "Turtle Manatee" "heading" should not exist
# Sometimes with behat we get unattached nodes causing spurious failures.
And I wait "1" seconds
And I set the field "Search users" to "ABC"
And "Turtle Manatee" "option_role" should exist
And I press the tab key
And the focused element is "Clear search input" "button" in the ".user-search" "css_element"
And I press the enter key
And I wait until the page is ready
And I confirm "Turtle Manatee" in "user" search within the gradebook widget does not exist
+1 -1
View File
@@ -1,3 +1,3 @@
define("core_group/comboboxsearch/group",["exports","core/comboboxsearch/search_combobox","core_group/comboboxsearch/repository","core/templates","core/utils","core/notification"],(function(_exports,_search_combobox,_repository,_templates,_utils,_notification){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_search_combobox=_interopRequireDefault(_search_combobox),_notification=_interopRequireDefault(_notification);class GroupSearch extends _search_combobox.default{constructor(){super(),_defineProperty(this,"courseID",void 0),_defineProperty(this,"bannedFilterFields",["id","link","groupimageurl"]),this.selectors={...this.selectors,courseid:'[data-region="courseid"]',placeholder:'.groupsearchdropdown [data-region="searchplaceholder"]'};const component=document.querySelector(this.componentSelector());this.courseID=component.querySelector(this.selectors.courseid).dataset.courseid,this.renderDefault().catch(_notification.default.exception)}static init(){return new GroupSearch}componentSelector(){return".group-search"}dropdownSelector(){return".groupsearchdropdown"}triggerSelector(){return".groupsearchwidget"}async renderDropdown(){const{html:html,js:js}=await(0,_templates.renderForPromise)("core_group/comboboxsearch/resultset",{groups:this.getMatchedResults(),hasresults:this.getMatchedResults().length>0,searchterm:this.getSearchTerm()});(0,_templates.replaceNodeContents)(this.selectors.placeholder,html,js)}async renderDefault(){this.setMatchedResults(await this.filterDataset(await this.getDataset())),this.filterMatchDataset(),await this.renderDropdown(),this.updateNodes(),this.registerInputEvents(),this.$component.on("shown.bs.dropdown",(()=>{this.searchInput.focus({preventScroll:!0})}))}async fetchDataset(){return await(0,_repository.groupFetch)(this.courseID).then((r=>r.groups))}async filterDataset(filterableData){return""===this.getPreppedSearchTerm()?filterableData:filterableData.filter((group=>Object.keys(group).some((key=>""!==group[key]&&!this.bannedFilterFields.includes(key)&&group[key].toString().toLowerCase().includes(this.getPreppedSearchTerm())))))}filterMatchDataset(){this.setMatchedResults(this.getMatchedResults().map((group=>({id:group.id,name:group.name,link:this.selectOneLink(group.id),groupimageurl:group.groupimageurl}))))}registerInputEvents(){this.searchInput.addEventListener("input",(0,_utils.debounce)((async()=>{this.setSearchTerms(this.searchInput.value),""===this.searchInput.value?this.clearSearchButton.classList.add("d-none"):this.clearSearchButton.classList.remove("d-none"),await this.filterrenderpipe()}),300))}async clickHandler(e){e.target.closest(this.selectors.dropdown)&&e.stopImmediatePropagation(),this.clearSearchButton.addEventListener("click",(async()=>{this.searchInput.value="",this.setSearchTerms(this.searchInput.value),await this.filterrenderpipe()})),e.target.closest(".dropdown-item")&&0===e.button&&(window.location=e.target.closest(".dropdown-item").href)}keyHandler(e){switch(super.keyHandler(e),e.key){case"Tab":e.target.closest(this.selectors.input)&&(e.preventDefault(),this.clearSearchButton.focus({preventScroll:!0}));break;case"Escape":if("option"===document.activeElement.getAttribute("role"))e.stopPropagation(),this.searchInput.focus({preventScroll:!0});else if(e.target.closest(this.selectors.input)){this.component.querySelector(this.selectors.trigger).focus({preventScroll:!0})}}}registerInputHandlers(){this.searchInput.addEventListener("input",(0,_utils.debounce)((()=>{this.setSearchTerms(this.searchInput.value),""===this.getSearchTerm()?this.clearSearchButton.classList.add("d-none"):this.clearSearchButton.classList.remove("d-none")}),300))}selectOneLink(groupID){throw new Error("selectOneLink(".concat(groupID,") must be implemented in ").concat(this.constructor.name))}}return _exports.default=GroupSearch,_exports.default}));
define("core_group/comboboxsearch/group",["exports","core/comboboxsearch/search_combobox","core_group/comboboxsearch/repository","core/templates","core/utils","core/notification"],(function(_exports,_search_combobox,_repository,_templates,_utils,_notification){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_search_combobox=_interopRequireDefault(_search_combobox),_notification=_interopRequireDefault(_notification);class GroupSearch extends _search_combobox.default{constructor(){super(),_defineProperty(this,"courseID",void 0),_defineProperty(this,"bannedFilterFields",["id","link","groupimageurl"]),this.selectors={...this.selectors,courseid:'[data-region="courseid"]',placeholder:'.groupsearchdropdown [data-region="searchplaceholder"]'};const component=document.querySelector(this.componentSelector());this.courseID=component.querySelector(this.selectors.courseid).dataset.courseid,this.renderDefault().catch(_notification.default.exception)}static init(){return new GroupSearch}componentSelector(){return".group-search"}dropdownSelector(){return".groupsearchdropdown"}triggerSelector(){return".groupsearchwidget"}async renderDropdown(){const{html:html,js:js}=await(0,_templates.renderForPromise)("core_group/comboboxsearch/resultset",{groups:this.getMatchedResults(),hasresults:this.getMatchedResults().length>0,searchterm:this.getSearchTerm()});(0,_templates.replaceNodeContents)(this.selectors.placeholder,html,js)}async renderDefault(){this.setMatchedResults(await this.filterDataset(await this.getDataset())),this.filterMatchDataset(),await this.renderDropdown(),this.updateNodes(),this.registerInputEvents(),this.$component.on("shown.bs.dropdown",(()=>{this.searchInput.focus({preventScroll:!0})}))}async fetchDataset(){return await(0,_repository.groupFetch)(this.courseID).then((r=>r.groups))}async filterDataset(filterableData){return""===this.getPreppedSearchTerm()?filterableData:filterableData.filter((group=>Object.keys(group).some((key=>""!==group[key]&&!this.bannedFilterFields.includes(key)&&group[key].toString().toLowerCase().includes(this.getPreppedSearchTerm())))))}filterMatchDataset(){this.setMatchedResults(this.getMatchedResults().map((group=>({id:group.id,name:group.name,link:this.selectOneLink(group.id),groupimageurl:group.groupimageurl}))))}registerInputEvents(){this.searchInput.addEventListener("input",(0,_utils.debounce)((async()=>{this.setSearchTerms(this.searchInput.value),""===this.searchInput.value?this.clearSearchButton.classList.add("d-none"):this.clearSearchButton.classList.remove("d-none"),await this.filterrenderpipe()}),300))}async clickHandler(e){e.target.closest(this.selectors.dropdown)&&e.stopImmediatePropagation(),this.clearSearchButton.addEventListener("click",(async()=>{this.searchInput.value="",this.setSearchTerms(this.searchInput.value),await this.filterrenderpipe()})),e.target.closest(".dropdown-item")&&0===e.button&&(window.location=e.target.closest(".dropdown-item").href)}keyHandler(e){if(super.keyHandler(e),"Escape"===e.key)if("option"===document.activeElement.getAttribute("role"))e.stopPropagation(),this.searchInput.focus({preventScroll:!0});else if(e.target.closest(this.selectors.input)){this.component.querySelector(this.selectors.trigger).focus({preventScroll:!0})}}registerInputHandlers(){this.searchInput.addEventListener("input",(0,_utils.debounce)((()=>{this.setSearchTerms(this.searchInput.value),""===this.getSearchTerm()?this.clearSearchButton.classList.add("d-none"):this.clearSearchButton.classList.remove("d-none")}),300))}selectOneLink(groupID){throw new Error("selectOneLink(".concat(groupID,") must be implemented in ").concat(this.constructor.name))}}return _exports.default=GroupSearch,_exports.default}));
//# sourceMappingURL=group.min.js.map
File diff suppressed because one or more lines are too long
-6
View File
@@ -198,12 +198,6 @@ export default class GroupSearch extends search_combobox {
super.keyHandler(e);
// Switch the key presses to handle keyboard nav.
switch (e.key) {
case 'Tab':
if (e.target.closest(this.selectors.input)) {
e.preventDefault();
this.clearSearchButton.focus({preventScroll: true});
}
break;
case 'Escape':
if (document.activeElement.getAttribute('role') === 'option') {
e.stopPropagation();
+2
View File
@@ -142,3 +142,5 @@ gradeitemadvanced,core_grades
gradeitemadvanced_help,core_grades
to,core
from,core
aria-toggledropdown,core_grades
aria:dropdowngrades,core_grades
+2 -3
View File
@@ -889,9 +889,6 @@ $string['xml'] = 'XML';
$string['yes'] = 'Yes';
$string['yourgrade'] = 'Your grade';
$string['aria-toggledropdown'] = 'Toggle the following dropdown';
$string['aria:dropdowngrades'] = 'Grade items found';
// Deprecated since Moodle 4.0.
$string['navmethod'] = 'Navigation method';
$string['dropdown'] = 'Drop-down menu';
@@ -924,3 +921,5 @@ $string['studentsperpage_help'] = 'This setting determines the number of student
$string['grade'] = 'Grade';
$string['gradeitemadvanced'] = 'Advanced grade item options';
$string['gradeitemadvanced_help'] = 'Select all elements that should be displayed as advanced when editing grade items.';
$string['aria-toggledropdown'] = 'Toggle the following dropdown';
$string['aria:dropdowngrades'] = 'Grade items found';
+3
View File
@@ -159,6 +159,9 @@ $string['applyfilters'] = 'Apply filters';
$string['areyousure'] = 'Are you sure?';
$string['areyousuretorestorethis'] = 'Do you want to continue?';
$string['areyousuretorestorethisinfo'] = 'Later in this process you will have a choice of adding this backup to an existing course or creating a completely new course.';
$string['aria-toggledropdown'] = 'Toggle the following dropdown';
$string['aria-comboboxsearchdialogue'] = 'Search or select an option';
$string['aria-comboboxsearchitems'] = 'Select an option';
$string['asc'] = 'Ascending';
$string['assessment'] = 'Assessment';
$string['assignadmins'] = 'Assign admins';
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+11 -8
View File
@@ -17,6 +17,7 @@ import $ from 'jquery';
import CustomEvents from "core/custom_interaction_events";
import {debounce} from 'core/utils';
import Pending from 'core/pending';
import * as FocusLockManager from 'core/local/aria/focuslock';
/**
* The class that manages the state of the search within a combobox.
@@ -85,6 +86,14 @@ export default class {
if (this.searchInput !== null) {
this.registerInputHandlers();
}
// Handle the dropdown opening and closing trap focus.
this.$component.on('shown.bs.dropdown', () => {
FocusLockManager.trapFocus(this.component);
});
this.$component.on('hidden.bs.dropdown', () => {
FocusLockManager.untrapFocus();
});
}
/**
@@ -228,9 +237,9 @@ export default class {
*/
closeSearch(clear = false) {
this.toggleDropdown();
// Hide the "clear" search button search bar.
this.clearSearchButton.classList.add('d-none');
if (clear) {
// Hide the "clear" search button search bar.
this.clearSearchButton.classList.add('d-none');
// Clear the entered search query in the search bar and hide the search results container.
this.setSearchTerms('');
this.searchInput.value = "";
@@ -442,12 +451,6 @@ export default class {
e.preventDefault();
this.moveToLastNode();
break;
case 'Tab':
// If the current focus is on the view all link, then close the widget then set focus on the next tertiary nav item.
if (e.target.closest(this.selectors.viewall)) {
this.closeSearch();
}
break;
}
}
@@ -80,6 +80,7 @@ class behat_partial_named_selector extends \Behat\Mink\Selector\PartialNamedSele
'group_message' => 'group_message',
'autocomplete' => 'autocomplete',
'iframe' => 'iframe',
'option_role' => 'option_role',
);
/**
+7 -11
View File
@@ -46,12 +46,6 @@
<div class="{{#parentclasses}}{{.}}{{/parentclasses}}"
{{^usebutton}}
tabindex="-1"
data-toggle="dropdown"
aria-expanded="false"
role="combobox"
aria-haspopup="dialog"
aria-controls="dialog-{{instance}}-{{uniqid}}"
aria-label="{{#cleanstr}} aria-toggledropdown, core_grades {{/cleanstr}}"
data-input-element="input-{{uniqid}}"
{{/usebutton}}>
@@ -63,7 +57,7 @@
aria-haspopup="dialog"
aria-controls="dialog-{{instance}}-{{uniqid}}"
class="{{#buttonclasses}}{{.}}{{/buttonclasses}} btn dropdown-toggle keep-open d-flex text-left align-items-center p-0 font-weight-bold"
aria-label="{{#cleanstr}} aria-toggledropdown, core_grades {{/cleanstr}}"
aria-label="{{#cleanstr}} aria-toggledropdown, core {{/cleanstr}}"
data-input-element="input-{{uniqid}}">
{{{buttoncontent}}}
</div>
@@ -72,10 +66,12 @@
{{^usebutton}}{{{buttoncontent}}}{{/usebutton}}
<div class="{{#dropdownclasses}}{{.}}{{/dropdownclasses}} dropdown-menu {{#rtl}}dropdown-menu-right{{/rtl}}"
id="dialog-{{instance}}-{{uniqid}}"
role="dialog"
aria-modal="true"
aria-label="{{#cleanstr}} selectagroup, core {{/cleanstr}}"
id="dialog-{{instance}}-{{uniqid}}"
{{#usebutton}}
role="dialog"
aria-modal="true"
aria-label="{{#cleanstr}} aria-comboboxsearchdialogue, core {{/cleanstr}}"
{{/usebutton}}
>
<div class="w-100 p-3" data-region="placeholder">
{{#renderlater}}
@@ -42,7 +42,7 @@
}}
<div class="d-flex flex-column mh-100 h-100">
{{#hasresults}}
<ul id="{{$listid}}list{{/listid}}-result-listbox" class="searchresultitemscontainer d-flex flex-column mw-100 position-relative py-2 list-group h-100 mx-0 {{$listclasses}}{{/listclasses}}" role="listbox" data-region="search-result-items-container" tabindex="-1" aria-label="{{#cleanstr}} aria:dropdowngrades, core_grades {{/cleanstr}}">
<ul id="{{$listid}}list{{/listid}}-result-listbox" class="searchresultitemscontainer d-flex flex-column mw-100 position-relative py-2 list-group h-100 mx-0 {{$listclasses}}{{/listclasses}}" role="listbox" data-region="search-result-items-container" tabindex="-1" aria-label="{{#cleanstr}} aria-comboboxsearchitems, core {{/cleanstr}}">
{{$results}}
{{#results}}
{{>core/local/comboboxsearch/resultitem}}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+24 -6
View File
@@ -54,6 +54,9 @@ export default class UserSearch extends search_combobox {
const component = document.querySelector(this.componentSelector());
this.courseID = component.querySelector(this.selectors.courseid).dataset.courseid;
this.groupID = document.querySelector(this.selectors.groupid)?.dataset?.groupid;
// We need to render some content by default for ARIA purposes.
this.renderDefault();
}
static init() {
@@ -101,6 +104,23 @@ export default class UserSearch extends search_combobox {
replaceNodeContents(this.getHTMLElements().searchDropdown, html, js);
}
/**
* Build the content then replace the node by default we want our form to exist.
*/
async renderDefault() {
this.setMatchedResults(await this.filterDataset(await this.getDataset()));
this.filterMatchDataset();
const {html, js} = await renderForPromise('core_user/comboboxsearch/resultset', {
users: this.getMatchedResults().slice(0, 5),
hasresults: this.getMatchedResults().length > 0,
matches: this.getMatchedResults().length,
searchterm: this.getSearchTerm(),
selectall: this.selectAllResultsLink(),
});
replaceNodeContents(this.getHTMLElements().searchDropdown, html, js);
}
/**
* Get the data we will be searching against in this component.
*
@@ -193,7 +213,7 @@ export default class UserSearch extends search_combobox {
* @param {KeyboardEvent} e The triggering event that we are working with.
*/
keyHandler(e) {
super.keyHandler(e);
// We don't call the super here because we want to let aria.js handle the key presses mostly.
if (e.target === this.getHTMLElements().currentViewAll && (e.key === 'Enter' || e.key === 'Space')) {
window.location = this.selectAllResultsLink();
@@ -203,6 +223,7 @@ export default class UserSearch extends search_combobox {
switch (e.key) {
case 'Enter':
case ' ':
e.stopPropagation();
if (document.activeElement === this.getHTMLElements().searchInput) {
if (e.key === 'Enter' && this.selectAllResultsLink() !== null) {
window.location = this.selectAllResultsLink();
@@ -230,9 +251,6 @@ export default class UserSearch extends search_combobox {
// If the current focus is on clear search, then check if viewall exists then around tab to it.
if (e.target.closest(this.selectors.clearSearch)) {
if (this.currentViewAll && !e.shiftKey) {
e.preventDefault();
this.currentViewAll.focus({preventScroll: true});
} else {
this.closeSearch();
}
}
@@ -249,11 +267,11 @@ export default class UserSearch extends search_combobox {
if (on) {
this.searchDropdown.classList.add('show');
$(this.searchDropdown).show();
this.component.setAttribute('aria-expanded', 'true');
this.getHTMLElements().searchInput.setAttribute('aria-expanded', 'true');
} else {
this.searchDropdown.classList.remove('show');
$(this.searchDropdown).hide();
this.component.setAttribute('aria-expanded', 'false');
this.getHTMLElements().searchInput.setAttribute('aria-expanded', 'false');
}
}
@@ -37,12 +37,15 @@
<span class="d-none" data-region="groupid" data-groupid="{{group}}" aria-hidden="true"></span>
{{#currentvalue}}
{{< core/search_input_auto }}
{{$label}}{{#str}}
searchusers, core
{{/str}}{{/label}}
{{$label}}{{#str}}searchusers, core{{/str}}{{/label}}
{{$value}}{{currentvalue}}{{/value}}
{{$additionalattributes}}
role="combobox"
aria-expanded="false"
aria-controls="user-result-listbox"
aria-autocomplete="list"
aria-haspopup="listbox"
aria-label="{{#cleanstr}} aria-toggledropdown, core {{/cleanstr}}"
data-input-element="user-input-{{uniqid}}"
{{/additionalattributes}}
{{/ core/search_input_auto }}
@@ -52,14 +55,15 @@
{{/currentvalue}}
{{^currentvalue}}
{{< core/search_input_auto }}
{{$label}}{{#str}}
searchusers, core
{{/str}}{{/label}}
{{$placeholder}}{{#str}}
searchusers, core
{{/str}}{{/placeholder}}
{{$label}}{{#str}}searchusers, core{{/str}}{{/label}}
{{$placeholder}}{{#str}}searchusers, core{{/str}}{{/placeholder}}
{{$additionalattributes}}
role="combobox"
aria-expanded="false"
aria-controls="user-result-listbox"
aria-autocomplete="list"
aria-haspopup="listbox"
aria-label="{{#cleanstr}} aria-toggledropdown, core {{/cleanstr}}"
data-input-element="user-input-{{uniqid}}"
{{/additionalattributes}}
{{/ core/search_input_auto }}