From bdeec065bcf978287c5908e5ea1cbfb8a256626a Mon Sep 17 00:00:00 2001 From: Matt C Date: Thu, 30 May 2024 09:23:53 +0100 Subject: [PATCH] Add initial support for screenreaders in search --- src/web/HTMLOperation.mjs | 10 +++++-- src/web/html/index.html | 2 +- src/web/waiters/OperationsWaiter.mjs | 43 +++++++++++++++++++--------- 3 files changed, 39 insertions(+), 16 deletions(-) diff --git a/src/web/HTMLOperation.mjs b/src/web/HTMLOperation.mjs index 30cfd1d9..725f0b5f 100755 --- a/src/web/HTMLOperation.mjs +++ b/src/web/HTMLOperation.mjs @@ -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 = "
  • ${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; diff --git a/src/web/html/index.html b/src/web/html/index.html index 38bf7ccc..a81b6c28 100755 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -173,7 +173,7 @@ Operations - +
    diff --git a/src/web/waiters/OperationsWaiter.mjs b/src/web/waiters/OperationsWaiter.mjs index 45a40c82..6c2de064 100755 --- a/src/web/waiters/OperationsWaiter.mjs +++ b/src/web/waiters/OperationsWaiter.mjs @@ -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) { @@ -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"); @@ -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; @@ -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 = [];