From 1a7c4f7afabf77e92b08991df3128204c87b0ff3 Mon Sep 17 00:00:00 2001 From: Oliver Geer Date: Sat, 26 Jul 2025 22:35:29 +0100 Subject: [PATCH 1/2] Fix accidentally unlocalised string in FindAndReplace plugin --- plugins/find-and-replace.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/find-and-replace.js b/plugins/find-and-replace.js index a47c3c7..75f5629 100644 --- a/plugins/find-and-replace.js +++ b/plugins/find-and-replace.js @@ -240,7 +240,7 @@ codeInput.plugins.FindAndReplace = class extends codeInput.Plugin { findRegExpCheckbox.title = this.instructions.findRegExp; findRegExpCheckbox.classList.add("code-input_find-and-replace_reg-exp-checkbox"); - matchDescription.textContent = "Search for matches in your code."; + matchDescription.textContent = this.instructions.start; matchDescription.classList.add("code-input_find-and-replace_match-description"); From 728b117128d866b897bd43273224ea82cd8df6af Mon Sep 17 00:00:00 2001 From: Oliver Geer Date: Sun, 27 Jul 2025 16:37:34 +0100 Subject: [PATCH 2/2] Improve plugin dialog accessibility with GoToLine status message (fixes #159) and field focus outline --- plugins/find-and-replace.css | 4 ---- plugins/find-and-replace.js | 1 + plugins/go-to-line.css | 15 ++++++++----- plugins/go-to-line.js | 41 +++++++++++++++++++++++++++++++----- 4 files changed, 47 insertions(+), 14 deletions(-) diff --git a/plugins/find-and-replace.css b/plugins/find-and-replace.css index 52666a3..07c0171 100644 --- a/plugins/find-and-replace.css +++ b/plugins/find-and-replace.css @@ -60,10 +60,6 @@ border: 0; } -.code-input_find-and-replace_dialog input:hover { - outline: none; -} - .code-input_find-and-replace_dialog input.code-input_find-and-replace_error { color: #ff0000aa; } diff --git a/plugins/find-and-replace.js b/plugins/find-and-replace.js index 75f5629..f027fb3 100644 --- a/plugins/find-and-replace.js +++ b/plugins/find-and-replace.js @@ -194,6 +194,7 @@ codeInput.plugins.FindAndReplace = class extends codeInput.Plugin { const findInput = document.createElement('input'); const findCaseSensitiveCheckbox = document.createElement('input'); const findRegExpCheckbox = document.createElement('input'); + // TODO in next major version: use more semantic HTML element than code const matchDescription = document.createElement('code'); matchDescription.setAttribute("aria-live", "assertive"); // Screen reader must read the number of matches found. diff --git a/plugins/go-to-line.css b/plugins/go-to-line.css index 0467964..91cedd6 100644 --- a/plugins/go-to-line.css +++ b/plugins/go-to-line.css @@ -11,7 +11,6 @@ .code-input_go-to-line_dialog { position: absolute; top: 0; right: 14px; - height: 28px; padding: 6px; padding-top: 8px; border: solid 1px #00000044; @@ -48,10 +47,6 @@ color: #ff0000aa; } -.code-input_go-to-line_dialog input:focus { - outline: none; -} - /* Cancel icon */ .code-input_go-to-line_dialog span { display: inline-block; @@ -75,3 +70,13 @@ opacity: .8; background-color: #00000018; } + +/* For backwards compatibility, p element on the same level as buttons rather than +buttons being nested inside other element like in FindAndReplace. */ +.code-input_go-to-line_dialog p { + font-family: monospace; + width: 264px; + margin: 0; + overflow: hidden; + white-space: wrap; +} diff --git a/plugins/go-to-line.js b/plugins/go-to-line.js index af34068..6407516 100644 --- a/plugins/go-to-line.js +++ b/plugins/go-to-line.js @@ -10,6 +10,11 @@ codeInput.plugins.GoToLine = class extends codeInput.Plugin { instructions = { closeDialog: "Close Dialog and Return to Editor", input: "Line:Column / Line no. then Enter", + guidanceFormat: "Wrong format. Enter a line number (e.g. 1) or a line number then colon then column number (e.g. 1:3).", + guidanceLineRange: (current, max) => { return `Line number (currently ${current}) should be between 1 and ${max} inclusive.` }, + guidanceColumnRange: (line, current, max) => { return `On line ${line}, column number (currently ${current}) should be between 1 and ${max} inclusive.` }, + guidanceValidLine: (line, column) => { return `Press Enter to go to line ${line}.` }, + guidanceValidColumn: (line, column) => { return `Press Enter to go to line ${line}, column ${column}.` }, }; /** @@ -33,6 +38,8 @@ codeInput.plugins.GoToLine = class extends codeInput.Plugin { /* Called with a dialog box keyup event to check the validity of the line number entered and submit the dialog if Enter is pressed */ checkPrompt(dialog, event) { + if (event.key == 'Escape') return this.cancelPrompt(dialog, event); + // Line number(:column number) const lines = dialog.textarea.value.split('\n'); const maxLineNo = lines.length; @@ -40,25 +47,42 @@ codeInput.plugins.GoToLine = class extends codeInput.Plugin { let columnNo = 0; // Means go to start of indented line let maxColumnNo = 1; const querySplitByColons = dialog.input.value.split(':'); - if(querySplitByColons.length > 2) return dialog.input.classList.add('code-input_go-to-line_error'); - if (event.key == 'Escape') return this.cancelPrompt(dialog, event); + // Invalid format + if(querySplitByColons.length > 2 || !/^[0-9:]*$/.test(dialog.input.value)) { + dialog.guidance.textContent = this.instructions.guidanceFormat; + return dialog.input.classList.add('code-input_go-to-line_error'); + } + // Number(s) present if (dialog.input.value) { - if (!/^[0-9:]*$/.test(dialog.input.value) || lineNo < 1 || lineNo > maxLineNo) { + if (lineNo < 1 || lineNo > maxLineNo) { + // Out-of-range line number + dialog.guidance.textContent = this.instructions.guidanceLineRange(lineNo, maxLineNo); return dialog.input.classList.add('code-input_go-to-line_error'); } else { - // Check if line:column + // Check if line:column - if so calculate column number if(querySplitByColons.length >= 2) { columnNo = Number(querySplitByColons[1]); - maxColumnNo = lines[lineNo-1].length; + maxColumnNo = lines[lineNo-1].length+1; // column 1 always works since at start of line } if(columnNo < 0 || columnNo > maxColumnNo) { + dialog.guidance.textContent = this.instructions.guidanceColumnRange(lineNo, columnNo, maxColumnNo); return dialog.input.classList.add('code-input_go-to-line_error'); } else { + if(columnNo === 0) { + // No column specified, or 0 which for backwards compatibility acts + // like none selected + dialog.guidance.textContent = this.instructions.guidanceValidLine(lineNo); + } else { + dialog.guidance.textContent = this.instructions.guidanceValidColumn(lineNo, columnNo); + } dialog.input.classList.remove('code-input_go-to-line_error'); } } + } else { + // No value + dialog.guidance.textContent = ""; } if (event.key == 'Enter') { @@ -91,6 +115,7 @@ codeInput.plugins.GoToLine = class extends codeInput.Plugin { const textarea = codeInput.textareaElement; const dialog = document.createElement('div'); + const input = document.createElement('input'); // TODO: Make a button element (semantic HTML for accessibility) in next major version @@ -100,8 +125,13 @@ codeInput.plugins.GoToLine = class extends codeInput.Plugin { cancel.setAttribute("tabindex", 0); // Visible to keyboard navigation cancel.setAttribute("title", this.instructions.closeDialog); + const guidance = document.createElement('p'); + guidance.setAttribute("aria-live", "assertive"); // Screen reader must read the status message. + guidance.textContent = ""; + dialog.appendChild(input); dialog.appendChild(cancel); + dialog.appendChild(guidance); dialog.className = 'code-input_go-to-line_dialog'; input.spellcheck = false; @@ -109,6 +139,7 @@ codeInput.plugins.GoToLine = class extends codeInput.Plugin { dialog.codeInput = codeInput; dialog.textarea = textarea; dialog.input = input; + dialog.guidance = guidance; input.addEventListener('keypress', (event) => { /* Stop enter from submitting form */