From 6fcf103760f5e8a556eef35054f002bd66e541e1 Mon Sep 17 00:00:00 2001 From: e218736 <147728997+e218736@users.noreply.github.com> Date: Wed, 21 Feb 2024 10:13:40 +0000 Subject: [PATCH] statusbar popup keyboard navigation --- src/web/stylesheets/layout/_io.css | 5 + src/web/utils/statusBar.mjs | 236 +++++++++++++++-------------- src/web/waiters/ControlsWaiter.mjs | 20 +-- 3 files changed, 135 insertions(+), 126 deletions(-) diff --git a/src/web/stylesheets/layout/_io.css b/src/web/stylesheets/layout/_io.css index 0146bf27..de639753 100755 --- a/src/web/stylesheets/layout/_io.css +++ b/src/web/stylesheets/layout/_io.css @@ -478,6 +478,11 @@ background-color: #ddd } +/* Change color of dropup links on focus */ +.cm-status-bar-select-content a:focus { + background-color: #ddd; +} + /* Change the background color of the dropup button when the dropup content is shown */ .cm-status-bar-select:hover .cm-status-bar-select-btn { background-color: #f1f1f1; diff --git a/src/web/utils/statusBar.mjs b/src/web/utils/statusBar.mjs index 8505c2db..454b36b0 100644 --- a/src/web/utils/statusBar.mjs +++ b/src/web/utils/statusBar.mjs @@ -4,11 +4,8 @@ * @license Apache-2.0 */ -import { showPanel } from "@codemirror/view"; -import { - CHR_ENC_SIMPLE_LOOKUP, - CHR_ENC_SIMPLE_REVERSE_LOOKUP, -} from "../../core/lib/ChrEnc.mjs"; +import {showPanel} from "@codemirror/view"; +import {CHR_ENC_SIMPLE_LOOKUP, CHR_ENC_SIMPLE_REVERSE_LOOKUP} from "../../core/lib/ChrEnc.mjs"; /** * A Status bar extension for CodeMirror @@ -44,10 +41,7 @@ class StatusBarPanel { dom.className = "cm-status-bar"; dom.setAttribute("data-help-title", `${this.label} status bar`); - dom.setAttribute( - "data-help", - `This status bar provides information about data in the ${this.label}. Help topics are available for each of the components by activating help when hovering over them.` - ); + dom.setAttribute("data-help", `This status bar provides information about data in the ${this.label}. Help topics are available for each of the components by activating help when hovering over them.`); lhs.innerHTML = this.constructLHS(); rhs.innerHTML = this.constructRHS(); @@ -58,25 +52,27 @@ class StatusBarPanel { const eventHandler = this.showDropUp.bind(this); dom.querySelectorAll(".cm-status-bar-select-btn").forEach((el) => { - el.addEventListener("click", eventHandler, false), - el.addEventListener("keydown", eventHandler, false); + el.addEventListener("click", eventHandler, false); }); - dom.querySelector(".eol-select").addEventListener( - "click", - this.eolSelectClick.bind(this), - false - ); - dom.querySelector(".chr-enc-select").addEventListener( - "click", - this.chrEncSelectClick.bind(this), - false - ); - dom.querySelector(".cm-status-bar-filter-input").addEventListener( - "keyup", - this.chrEncFilter.bind(this), - false + dom.querySelectorAll(".cm-status-bar-select-btn").forEach((el) => { + el.addEventListener("keydown", eventHandler, false); + }); + + const selectContent = dom.querySelectorAll( + ".cm-status-bar-select-content" ); + selectContent.forEach((el) => { + const aTags = el.getElementsByTagName("a"); + + for (let i = 0; i < aTags.length; i++) { + aTags[i].addEventListener("keydown", arrowNav, false); + } + }); + dom.querySelector(".eol-select").addEventListener("click", this.eolSelectClick.bind(this), false); + dom.querySelector(".chr-enc-select").addEventListener("click", this.chrEncSelectClick.bind(this), false); + dom.querySelector(".cm-status-bar-filter-input").addEventListener("keyup", this.chrEncFilter.bind(this), false); + return dom; } @@ -117,14 +113,14 @@ class StatusBarPanel { e.preventDefault(); const eolLookup = { - LF: "\u000a", - VT: "\u000b", - FF: "\u000c", - CR: "\u000d", - CRLF: "\u000d\u000a", - NEL: "\u0085", - LS: "\u2028", - PS: "\u2029", + "LF": "\u000a", + "VT": "\u000b", + "FF": "\u000c", + "CR": "\u000d", + "CRLF": "\u000d\u000a", + "NEL": "\u0085", + "LS": "\u2028", + "PS": "\u2029" }; const eolval = eolLookup[e.target.getAttribute("data-val")]; @@ -198,9 +194,9 @@ class StatusBarPanel { * @param {boolean} selectionSet */ updateSelection(state, selectionSet) { - const selLen = state?.selection?.main - ? state.selection.main.to - state.selection.main.from - : 0; + const selLen = state?.selection?.main ? + state.selection.main.to - state.selection.main.from : + 0; const selInfo = this.dom.querySelector(".sel-info"), curOffsetInfo = this.dom.querySelector(".cur-offset-info"); @@ -218,12 +214,11 @@ class StatusBarPanel { if (state.lineBreak.length !== 1) { const fromLine = state.doc.lineAt(from).number; const toLine = state.doc.lineAt(to).number; - from += state.lineBreak.length * fromLine - fromLine - 1; - to += state.lineBreak.length * toLine - toLine - 1; + from += (state.lineBreak.length * fromLine) - fromLine - 1; + to += (state.lineBreak.length * toLine) - toLine - 1; } - if (selLen > 0) { - // Range + if (selLen > 0) { // Range const start = this.dom.querySelector(".sel-start-value"), end = this.dom.querySelector(".sel-end-value"), length = this.dom.querySelector(".sel-length-value"); @@ -233,8 +228,7 @@ class StatusBarPanel { start.textContent = from; end.textContent = to; length.textContent = to - from; - } else { - // Position + } else { // Position const offset = this.dom.querySelector(".cur-offset-value"); selInfo.style.display = "none"; @@ -258,7 +252,7 @@ class StatusBarPanel { "\u000d\u000a": ["CRLF", "Carriage Return + Line Feed"], "\u0085": ["NEL", "Next Line"], "\u2028": ["LS", "Line Separator"], - "\u2029": ["PS", "Paragraph Separator"], + "\u2029": ["PS", "Paragraph Separator"] }; const val = this.dom.querySelector(".eol-value"); @@ -266,10 +260,7 @@ class StatusBarPanel { const eolName = eolLookup[state.lineBreak]; val.textContent = eolName[0]; button.setAttribute("title", `End of line sequence:
${eolName[1]}`); - button.setAttribute( - "data-original-title", - `End of line sequence:
${eolName[1]}` - ); + button.setAttribute("data-original-title", `End of line sequence:
${eolName[1]}`); this.eolVal = state.lineBreak; } @@ -280,21 +271,13 @@ class StatusBarPanel { const chrEncVal = this.chrEncGetter(); if (chrEncVal === this.chrEncVal) return; - const name = CHR_ENC_SIMPLE_REVERSE_LOOKUP[chrEncVal] - ? CHR_ENC_SIMPLE_REVERSE_LOOKUP[chrEncVal] - : "Raw Bytes"; + const name = CHR_ENC_SIMPLE_REVERSE_LOOKUP[chrEncVal] ? CHR_ENC_SIMPLE_REVERSE_LOOKUP[chrEncVal] : "Raw Bytes"; const val = this.dom.querySelector(".chr-enc-value"); const button = val.closest(".cm-status-bar-select-btn"); val.textContent = name; - button.setAttribute( - "title", - `${this.label} character encoding:
${name}` - ); - button.setAttribute( - "data-original-title", - `${this.label} character encoding:
${name}` - ); + button.setAttribute("title", `${this.label} character encoding:
${name}`); + button.setAttribute("data-original-title", `${this.label} character encoding:
${name}`); this.chrEncVal = chrEncVal; } @@ -311,9 +294,7 @@ class StatusBarPanel { bakingTimeInfo.style.display = "inline-block"; bakingTime.textContent = this.timing.duration(this.tabNumGetter()); - const info = this.timing - .printStages(this.tabNumGetter()) - .replace(/\n/g, "
"); + const info = this.timing.printStages(this.tabNumGetter()).replace(/\n/g, "
"); bakingTimeInfo.setAttribute("data-original-title", info); } else { bakingTimeInfo.style.display = "none"; @@ -326,11 +307,11 @@ class StatusBarPanel { */ updateSizing(view) { const viewHeight = view.contentDOM.parentNode.clientHeight; - this.dom - .querySelectorAll(".cm-status-bar-select-scroll") - .forEach((el) => { - el.style.maxHeight = viewHeight - 50 + "px"; - }); + this.dom.querySelectorAll(".cm-status-bar-select-scroll").forEach( + el => { + el.style.maxHeight = (viewHeight - 50) + "px"; + } + ); } /** @@ -341,27 +322,19 @@ class StatusBarPanel { if (this.htmlOutput?.html === "") { // Enable all controls - this.dom.querySelectorAll(".disabled").forEach((el) => { + this.dom.querySelectorAll(".disabled").forEach(el => { el.classList.remove("disabled"); }); } else { // Disable chrenc, length, selection etc. - this.dom - .querySelectorAll(".cm-status-bar-select-btn") - .forEach((el) => { - el.classList.add("disabled"); - }); + this.dom.querySelectorAll(".cm-status-bar-select-btn").forEach(el => { + el.classList.add("disabled"); + }); - this.dom - .querySelector(".stats-length-value") - .parentNode.classList.add("disabled"); - this.dom - .querySelector(".stats-lines-value") - .parentNode.classList.add("disabled"); + this.dom.querySelector(".stats-length-value").parentNode.classList.add("disabled"); + this.dom.querySelector(".stats-lines-value").parentNode.classList.add("disabled"); this.dom.querySelector(".sel-info").classList.add("disabled"); - this.dom - .querySelector(".cur-offset-info") - .classList.add("disabled"); + this.dom.querySelector(".cur-offset-info").classList.add("disabled"); } } @@ -371,7 +344,7 @@ class StatusBarPanel { */ constructLHS() { return ` - + abc @@ -398,40 +371,33 @@ class StatusBarPanel { * @returns {string} */ constructRHS() { - const chrEncOptions = Object.keys(CHR_ENC_SIMPLE_LOOKUP) - .map( - (name) => - `${name}` - ) - .join(""); + const chrEncOptions = Object.keys(CHR_ENC_SIMPLE_LOOKUP).map(name => + `${name}` + ).join(""); let chrEncHelpText = "", eolHelpText = ""; if (this.label === "Input") { - chrEncHelpText = - "The input character encoding defines how the input text is encoded into bytes which are then processed by the Recipe.

The 'Raw bytes' option attempts to treat the input as individual bytes in the range 0-255. If it detects any characters with Unicode values above 255, it will treat the entire input as UTF-8. 'Raw bytes' is usually the best option if you are inputting binary data, such as a file."; - eolHelpText = - "The End of Line Sequence defines which bytes are considered EOL terminators. Pressing the return key will enter this value into the input and create a new line.

Changing the EOL sequence will not modify any existing data in the input but may change how previously entered line breaks are displayed. Lines added while a different EOL terminator was set may not now result in a new line, but may be displayed as control characters instead."; + chrEncHelpText = "The input character encoding defines how the input text is encoded into bytes which are then processed by the Recipe.

The 'Raw bytes' option attempts to treat the input as individual bytes in the range 0-255. If it detects any characters with Unicode values above 255, it will treat the entire input as UTF-8. 'Raw bytes' is usually the best option if you are inputting binary data, such as a file."; + eolHelpText = "The End of Line Sequence defines which bytes are considered EOL terminators. Pressing the return key will enter this value into the input and create a new line.

Changing the EOL sequence will not modify any existing data in the input but may change how previously entered line breaks are displayed. Lines added while a different EOL terminator was set may not now result in a new line, but may be displayed as control characters instead."; } else { - chrEncHelpText = - "The output character encoding defines how the output bytes are decoded into text which can be displayed to you.

The 'Raw bytes' option treats the output data as individual bytes in the range 0-255."; - eolHelpText = - "The End of Line Sequence defines which bytes are considered EOL terminators.

Changing this value will not modify the value of the output, but may change how certain bytes are displayed and whether they result in a new line being created."; + chrEncHelpText = "The output character encoding defines how the output bytes are decoded into text which can be displayed to you.

The 'Raw bytes' option treats the output data as individual bytes in the range 0-255."; + eolHelpText = "The End of Line Sequence defines which bytes are considered EOL terminators.

Changing this value will not modify the value of the output, but may change how certain bytes are displayed and whether they result in a new line being created."; } return ` -