}\n */\n async renderAndShow() {\n // User has given something for us to filter against.\n this.setMatchedResults(await this.filterDataset(await this.getDataset()));\n await this.filterMatchDataset();\n // Replace the dropdown node contents and show the results.\n await this.renderDropdown();\n // Set the dropdown to open.\n this.toggleDropdown(true);\n }\n\n /**\n * Set the current focus either on the preceding or next result item.\n *\n * @param {Number} direction Is the user moving up or down the resultset?\n * @param {KeyboardEvent} e The JS event from the event handler.\n */\n keyUpDown(direction, e) {\n e.preventDefault();\n // Stop Bootstrap from being clever.\n e.stopPropagation();\n // Current focus is on the input box so depending on direction, go to the top or the bottom of the displayed results.\n if (document.activeElement === this.searchInput && this.resultNodes.length > 0) {\n if (direction === UP) {\n this.moveToLastNode();\n } else {\n this.moveToFirstNode();\n }\n }\n const index = this.resultNodes.indexOf(this.currentNode);\n if (this.currentNode) {\n if (direction === UP) {\n if (index === 0) {\n this.moveToLastNode();\n } else {\n this.moveToNode(index - 1);\n }\n } else {\n if (index + 1 >= this.resultNodes.length) {\n this.moveToFirstNode();\n } else {\n this.moveToNode(index + 1);\n }\n }\n } else {\n if (direction === UP) {\n this.moveToLastNode();\n } else {\n this.moveToFirstNode();\n }\n }\n }\n\n /**\n * The handler for when a user interacts with the component.\n *\n * @param {MouseEvent} e The triggering event that we are working with.\n */\n async clickHandler(e) {\n this.updateNodes();\n\n // Prevent normal key presses activating this.\n if (e.target.closest('.dropdown-item') && e.button === 0) {\n window.location = e.target.closest('.dropdown-item').href;\n }\n // The \"clear search\" button is triggered.\n if (e.target.closest(this.selectors.clearSearch) && e.button === 0) {\n this.closeSearch(true);\n this.searchInput.focus({preventScroll: true});\n }\n // User may have accidentally clicked off the dropdown and wants to reopen it.\n if (e.target.closest(this.selectors.input) && this.getSearchTerm() !== '' && e.button === 0) {\n await this.renderAndShow();\n }\n }\n\n /**\n * The handler for when a user presses a key within the component.\n *\n * @param {KeyboardEvent} e The triggering event that we are working with.\n */\n keyHandler(e) {\n this.updateNodes();\n // Switch the key presses to handle keyboard nav.\n switch (e.key) {\n case 'ArrowUp':\n this.keyUpDown(UP, e);\n break;\n case 'ArrowDown':\n this.keyUpDown(DOWN, e);\n break;\n case 'Home':\n e.preventDefault();\n this.moveToFirstNode();\n break;\n case 'End':\n e.preventDefault();\n this.moveToLastNode();\n break;\n }\n }\n\n /**\n * Set focus on a given node after parsed through the calling functions.\n *\n * @param {HTMLElement} node The node to set focus upon.\n */\n selectNode = (node) => {\n node.focus({preventScroll: true});\n this.searchDropdown.scrollTop = node.offsetTop - (node.clientHeight / 2);\n };\n\n /**\n * Set the focus on the first node within the array.\n */\n moveToFirstNode = () => {\n if (this.resultNodes.length > 0) {\n this.selectNode(this.resultNodes[0]);\n }\n };\n\n /**\n * Set the focus to the final node within the array.\n */\n moveToLastNode = () => {\n if (this.resultNodes.length > 0) {\n this.selectNode(this.resultNodes[this.resultNodes.length - 1]);\n }\n };\n\n /**\n * Set focus on any given specified node within the node array.\n *\n * @param {Number} index Which item within the array to set focus upon.\n */\n moveToNode = (index) => {\n if (this.resultNodes.length > 0) {\n this.selectNode(this.resultNodes[index]);\n }\n };\n}\n"],"names":["events","CustomEvents","activate","keyboardActivate","constructor","component","this","componentSelector","trigger","triggerSelector","input","clearSearch","dropdown","dropdownSelector","resultitems","viewall","document","querySelector","selectors","node","focus","preventScroll","searchDropdown","scrollTop","offsetTop","clientHeight","resultNodes","length","selectNode","index","setSearchTerms","searchInput","_this$searchInput","value","registerClickHandlers","registerKeyHandlers","registerInputHandlers","$component","on","FocusLockManager","trapFocus","untrapFocus","fetchDataset","Error","name","filterDataset","dataset","filterMatchDataset","renderDropdown","datasetSize","getDatasetSize","getMatchedResults","matchedResults","setMatchedResults","result","getSearchTerm","searchTerm","getPreppedSearchTerm","preppedSearchTerm","toLowerCase","getHTMLElements","updateNodes","currentViewAll","clearSearchButton","closeSearch","clear","toggleDropdown","classList","add","searchResultsVisible","offsetParent","show","remove","hide","querySelectorAll","currentNode","find","r","id","activeElement","addEventListener","clickHandler","bind","define","forEach","event","keyHandler","async","window","console","warn","pendingPromise","Pending","renderAndShow","then","resolve","pending","getDataset","keyUpDown","direction","e","preventDefault","stopPropagation","moveToLastNode","moveToFirstNode","indexOf","moveToNode","target","closest","button","location","href","key"],"mappings":"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"}
\ No newline at end of file
diff --git a/lib/amd/src/comboboxsearch/search_combobox.js b/lib/amd/src/comboboxsearch/search_combobox.js
index 0c8f51599c7..f7e26533a65 100644
--- a/lib/amd/src/comboboxsearch/search_combobox.js
+++ b/lib/amd/src/comboboxsearch/search_combobox.js
@@ -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;
}
}
diff --git a/lib/behat/classes/partial_named_selector.php b/lib/behat/classes/partial_named_selector.php
index 98ab02f6e82..89dcc36772e 100644
--- a/lib/behat/classes/partial_named_selector.php
+++ b/lib/behat/classes/partial_named_selector.php
@@ -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',
);
/**
diff --git a/lib/templates/comboboxsearch.mustache b/lib/templates/comboboxsearch.mustache
index 083fe57a6d7..7fee29af5ce 100644
--- a/lib/templates/comboboxsearch.mustache
+++ b/lib/templates/comboboxsearch.mustache
@@ -46,12 +46,6 @@
@@ -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}}}
@@ -72,10 +66,12 @@
{{^usebutton}}{{{buttoncontent}}}{{/usebutton}}