Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Accessibility - Add support for screenreaders in operations search #1862

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions src/web/HTMLOperation.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,23 @@ class HTMLOperation {
/**
* Renders the operation in HTML as a stub operation with no ingredients.
*
* @param {boolean} removeIcon - show icon for removing operation
* @param {string} elementId - element ID for aria usage
* @returns {string}
*/
toStubHtml(removeIcon) {
toStubHtml(removeIcon = false, elementId = null) {
let html = "<li class='operation'";

if (elementId) {
html += ` id='${elementId}'`;
}

if (this.description) {
const infoLink = this.infoURL ? `<hr>${titleFromWikiLink(this.infoURL)}` : "";

html += ` data-container='body' data-toggle='popover' data-placement='right'
data-content="${this.description}${infoLink}" data-html='true' data-trigger='hover'
data-boundary='viewport'`;
data-boundary='viewport' role='button'`;
}

html += ">" + this.name;
Expand Down
2 changes: 1 addition & 1 deletion src/web/html/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@
Operations
<span class="op-count"></span>
</div>
<input id="search" type="search" class="form-control" placeholder="Search..." autocomplete="off" tabindex="2" data-help-title="Searching for operations" data-help="<p>Use the search box to find useful operations.</p><p>Both operation names and descriptions are queried using a fuzzy matching algorithm.</p>">
<input id="search" type="search" class="form-control" placeholder="Search..." autocomplete="off" tabindex="2" data-help-title="Searching for operations" data-help="<p>Use the search box to find useful operations.</p><p>Both operation names and descriptions are queried using a fuzzy matching algorithm.</p>" aria-owns="search-results">
<ul id="search-results" class="op-list"></ul>
<div id="categories" class="panel-group no-select"></div>
</div>
Expand Down
43 changes: 30 additions & 13 deletions src/web/waiters/OperationsWaiter.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,16 @@ class OperationsWaiter {
this.removeIntent = false;
}


/**
* Handler for search events.
* Finds operations which match the given search term and displays them under the search box.
*
* @param {event} e
* @param {KeyboardEvent | ClipboardEvent | Event} e
*/
searchOperations(e) {
let ops, selected;

if (e.type === "search" || e.keyCode === 13) { // Search or Return
if ((e.type === "search" && e.target.value !== "") || e.keyCode === 13) { // Search (non-empty) or Return
e.preventDefault();
ops = document.querySelectorAll("#search-results li");
if (ops.length) {
Expand All @@ -49,27 +48,43 @@ class OperationsWaiter {
}
}

/**
* Sets up the operation element with the correct attributes when selected
* @param {HTMLElement} element
*/
const _selectOperation = (element) => {
element.classList.add("selected-op");
element.scrollIntoView({block: "nearest"});
$(element).popover("show");
e.target.setAttribute("aria-activedescendant", element.id);
};

/**
* Sets up the operation element with the correct attributes when deselected
* @param {HTMLElement} element
*/
const _deselectOperation = (element) => {
element.classList.remove("selected-op");
$(element).popover("hide");
};

if (e.keyCode === 40) { // Down
e.preventDefault();
ops = document.querySelectorAll("#search-results li");
if (ops.length) {
selected = this.getSelectedOp(ops);
if (selected > -1) {
ops[selected].classList.remove("selected-op");
}
if (selected > -1) _deselectOperation(ops[selected]);
if (selected === ops.length-1) selected = -1;
ops[selected+1].classList.add("selected-op");
_selectOperation(ops[selected+1]);
}
} else if (e.keyCode === 38) { // Up
e.preventDefault();
ops = document.querySelectorAll("#search-results li");
if (ops.length) {
selected = this.getSelectedOp(ops);
if (selected > -1) {
ops[selected].classList.remove("selected-op");
}
if (selected > -1) _deselectOperation(ops[selected]);
if (selected === 0) selected = ops.length;
ops[selected-1].classList.add("selected-op");
_selectOperation(ops[selected-1]);
}
} else {
const searchResultsEl = document.getElementById("search-results");
Expand All @@ -83,11 +98,13 @@ class OperationsWaiter {
searchResultsEl.removeChild(searchResultsEl.firstChild);
}

document.querySelector("#search").removeAttribute("aria-activedescendant");

$("#categories .show").collapse("hide");
if (str) {
const matchedOps = this.filterOperations(str, true);
const matchedOpsHtml = matchedOps
.map(v => v.toStubHtml())
.map((operation, idx) => operation.toStubHtml(false, `search-result-${idx}`))
.join("");

searchResultsEl.innerHTML = matchedOpsHtml;
Expand All @@ -103,7 +120,7 @@ class OperationsWaiter {
* @param {string} searchStr
* @param {boolean} highlight - Whether or not to highlight the matching string in the operation
* name and description
* @returns {string[]}
* @returns {HTMLOperation[]}
*/
filterOperations(inStr, highlight) {
const matchedOps = [];
Expand Down
Loading