Skip to content

Commit 728b117

Browse files
committed
Improve plugin dialog accessibility with GoToLine status message (fixes #159) and field focus outline
1 parent 1a7c4f7 commit 728b117

File tree

4 files changed

+47
-14
lines changed

4 files changed

+47
-14
lines changed

plugins/find-and-replace.css

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,6 @@
6060
border: 0;
6161
}
6262

63-
.code-input_find-and-replace_dialog input:hover {
64-
outline: none;
65-
}
66-
6763
.code-input_find-and-replace_dialog input.code-input_find-and-replace_error {
6864
color: #ff0000aa;
6965
}

plugins/find-and-replace.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ codeInput.plugins.FindAndReplace = class extends codeInput.Plugin {
194194
const findInput = document.createElement('input');
195195
const findCaseSensitiveCheckbox = document.createElement('input');
196196
const findRegExpCheckbox = document.createElement('input');
197+
// TODO in next major version: use more semantic HTML element than code
197198
const matchDescription = document.createElement('code');
198199
matchDescription.setAttribute("aria-live", "assertive"); // Screen reader must read the number of matches found.
199200

plugins/go-to-line.css

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
.code-input_go-to-line_dialog {
1212
position: absolute;
1313
top: 0; right: 14px;
14-
height: 28px;
1514
padding: 6px;
1615
padding-top: 8px;
1716
border: solid 1px #00000044;
@@ -48,10 +47,6 @@
4847
color: #ff0000aa;
4948
}
5049

51-
.code-input_go-to-line_dialog input:focus {
52-
outline: none;
53-
}
54-
5550
/* Cancel icon */
5651
.code-input_go-to-line_dialog span {
5752
display: inline-block;
@@ -75,3 +70,13 @@
7570
opacity: .8;
7671
background-color: #00000018;
7772
}
73+
74+
/* For backwards compatibility, p element on the same level as buttons rather than
75+
buttons being nested inside other element like in FindAndReplace. */
76+
.code-input_go-to-line_dialog p {
77+
font-family: monospace;
78+
width: 264px;
79+
margin: 0;
80+
overflow: hidden;
81+
white-space: wrap;
82+
}

plugins/go-to-line.js

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ codeInput.plugins.GoToLine = class extends codeInput.Plugin {
1010
instructions = {
1111
closeDialog: "Close Dialog and Return to Editor",
1212
input: "Line:Column / Line no. then Enter",
13+
guidanceFormat: "Wrong format. Enter a line number (e.g. 1) or a line number then colon then column number (e.g. 1:3).",
14+
guidanceLineRange: (current, max) => { return `Line number (currently ${current}) should be between 1 and ${max} inclusive.` },
15+
guidanceColumnRange: (line, current, max) => { return `On line ${line}, column number (currently ${current}) should be between 1 and ${max} inclusive.` },
16+
guidanceValidLine: (line, column) => { return `Press Enter to go to line ${line}.` },
17+
guidanceValidColumn: (line, column) => { return `Press Enter to go to line ${line}, column ${column}.` },
1318
};
1419

1520
/**
@@ -33,32 +38,51 @@ codeInput.plugins.GoToLine = class extends codeInput.Plugin {
3338

3439
/* Called with a dialog box keyup event to check the validity of the line number entered and submit the dialog if Enter is pressed */
3540
checkPrompt(dialog, event) {
41+
if (event.key == 'Escape') return this.cancelPrompt(dialog, event);
42+
3643
// Line number(:column number)
3744
const lines = dialog.textarea.value.split('\n');
3845
const maxLineNo = lines.length;
3946
const lineNo = Number(dialog.input.value.split(':')[0]);
4047
let columnNo = 0; // Means go to start of indented line
4148
let maxColumnNo = 1;
4249
const querySplitByColons = dialog.input.value.split(':');
43-
if(querySplitByColons.length > 2) return dialog.input.classList.add('code-input_go-to-line_error');
4450

45-
if (event.key == 'Escape') return this.cancelPrompt(dialog, event);
51+
// Invalid format
52+
if(querySplitByColons.length > 2 || !/^[0-9:]*$/.test(dialog.input.value)) {
53+
dialog.guidance.textContent = this.instructions.guidanceFormat;
54+
return dialog.input.classList.add('code-input_go-to-line_error');
55+
}
4656

57+
// Number(s) present
4758
if (dialog.input.value) {
48-
if (!/^[0-9:]*$/.test(dialog.input.value) || lineNo < 1 || lineNo > maxLineNo) {
59+
if (lineNo < 1 || lineNo > maxLineNo) {
60+
// Out-of-range line number
61+
dialog.guidance.textContent = this.instructions.guidanceLineRange(lineNo, maxLineNo);
4962
return dialog.input.classList.add('code-input_go-to-line_error');
5063
} else {
51-
// Check if line:column
64+
// Check if line:column - if so calculate column number
5265
if(querySplitByColons.length >= 2) {
5366
columnNo = Number(querySplitByColons[1]);
54-
maxColumnNo = lines[lineNo-1].length;
67+
maxColumnNo = lines[lineNo-1].length+1; // column 1 always works since at start of line
5568
}
5669
if(columnNo < 0 || columnNo > maxColumnNo) {
70+
dialog.guidance.textContent = this.instructions.guidanceColumnRange(lineNo, columnNo, maxColumnNo);
5771
return dialog.input.classList.add('code-input_go-to-line_error');
5872
} else {
73+
if(columnNo === 0) {
74+
// No column specified, or 0 which for backwards compatibility acts
75+
// like none selected
76+
dialog.guidance.textContent = this.instructions.guidanceValidLine(lineNo);
77+
} else {
78+
dialog.guidance.textContent = this.instructions.guidanceValidColumn(lineNo, columnNo);
79+
}
5980
dialog.input.classList.remove('code-input_go-to-line_error');
6081
}
6182
}
83+
} else {
84+
// No value
85+
dialog.guidance.textContent = "";
6286
}
6387

6488
if (event.key == 'Enter') {
@@ -91,6 +115,7 @@ codeInput.plugins.GoToLine = class extends codeInput.Plugin {
91115
const textarea = codeInput.textareaElement;
92116

93117
const dialog = document.createElement('div');
118+
94119
const input = document.createElement('input');
95120

96121
// TODO: Make a button element (semantic HTML for accessibility) in next major version
@@ -100,15 +125,21 @@ codeInput.plugins.GoToLine = class extends codeInput.Plugin {
100125
cancel.setAttribute("tabindex", 0); // Visible to keyboard navigation
101126
cancel.setAttribute("title", this.instructions.closeDialog);
102127

128+
const guidance = document.createElement('p');
129+
guidance.setAttribute("aria-live", "assertive"); // Screen reader must read the status message.
130+
guidance.textContent = "";
131+
103132
dialog.appendChild(input);
104133
dialog.appendChild(cancel);
134+
dialog.appendChild(guidance);
105135

106136
dialog.className = 'code-input_go-to-line_dialog';
107137
input.spellcheck = false;
108138
input.placeholder = this.instructions.input;
109139
dialog.codeInput = codeInput;
110140
dialog.textarea = textarea;
111141
dialog.input = input;
142+
dialog.guidance = guidance;
112143

113144
input.addEventListener('keypress', (event) => {
114145
/* Stop enter from submitting form */

0 commit comments

Comments
 (0)