Skip to content

Commit

Permalink
Hide Ace content from assistive technologies to improve a11y (#5160)
Browse files Browse the repository at this point in the history
Some screen reader/browser configurations read the entire visible part of the document when entering Ace. This aims to improve the screen reader experience by hiding the ace_content div from assistive technologies when a11y mode is enabled.
  • Loading branch information
akoreman authored May 10, 2023
1 parent 2c8f232 commit 563afd1
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 34 deletions.
41 changes: 24 additions & 17 deletions src/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -2869,13 +2869,13 @@ config.defineOptions(Editor.prototype, "editor", {
bindKey: "Esc",
exec: function(editor) {
editor.blur();
editor.renderer.content.focus();
editor.renderer.scroller.focus();
},
readOnly: true
};

var focusOnEnterKeyup = function (e) {
if (e.target == this.renderer.content && e.keyCode === keys['enter']){
if (e.target == this.renderer.scroller && e.keyCode === keys['enter']){
e.preventDefault();
var row = this.getCursorPosition().row;

Expand All @@ -2891,18 +2891,19 @@ config.defineOptions(Editor.prototype, "editor", {
// Prevent focus to be captured when tabbing through the page. When focus is set to the content div,
// press Enter key to give focus to Ace and press Esc to again allow to tab through the page.
if (value){
this.keyboardFocusClassName = "ace_keyboard-focus";
this.renderer.enableKeyboardAccessibility = true;
this.renderer.keyboardFocusClassName = "ace_keyboard-focus";

this.textInput.getElement().setAttribute("tabindex", -1);
this.renderer.content.setAttribute("tabindex", 0);
this.renderer.content.setAttribute("role", "group");
this.renderer.content.setAttribute("aria-roledescription", nls("editor"));
this.renderer.content.classList.add(this.keyboardFocusClassName);
this.renderer.content.setAttribute("aria-label",
this.renderer.scroller.setAttribute("tabindex", 0);
this.renderer.scroller.setAttribute("role", "group");
this.renderer.scroller.setAttribute("aria-roledescription", nls("editor"));
this.renderer.scroller.classList.add(this.renderer.keyboardFocusClassName);
this.renderer.scroller.setAttribute("aria-label",
nls("Editor content, press Enter to start editing, press Escape to exit")
);

this.renderer.content.addEventListener("keyup", focusOnEnterKeyup.bind(this));
this.renderer.scroller.addEventListener("keyup", focusOnEnterKeyup.bind(this));
this.commands.addCommand(blurCommand);

this.renderer.$gutter.setAttribute("tabindex", 0);
Expand All @@ -2912,29 +2913,35 @@ config.defineOptions(Editor.prototype, "editor", {
this.renderer.$gutter.setAttribute("aria-label",
nls("Editor gutter, press Enter to interact with controls using arrow keys, press Escape to exit")
);
this.renderer.$gutter.classList.add(this.keyboardFocusClassName);
this.renderer.$gutter.classList.add(this.renderer.keyboardFocusClassName);

this.renderer.content.setAttribute("aria-hidden", true);

if (!gutterKeyboardHandler)
gutterKeyboardHandler = new GutterKeyboardHandler(this);

gutterKeyboardHandler.addListener();
} else {
this.renderer.enableKeyboardAccessibility = false;

this.textInput.getElement().setAttribute("tabindex", 0);
this.renderer.content.setAttribute("tabindex", -1);
this.renderer.content.removeAttribute("role");
this.renderer.content.removeAttribute("aria-roledescription");
this.renderer.content.classList.remove(this.keyboardFocusClassName);
this.renderer.content.removeAttribute("aria-label");
this.renderer.scroller.setAttribute("tabindex", -1);
this.renderer.scroller.removeAttribute("role");
this.renderer.scroller.removeAttribute("aria-roledescription");
this.renderer.scroller.classList.remove(this.renderer.keyboardFocusClassName);
this.renderer.scroller.removeAttribute("aria-label");

this.renderer.content.removeEventListener("keyup", focusOnEnterKeyup.bind(this));
this.renderer.scroller.removeEventListener("keyup", focusOnEnterKeyup.bind(this));
this.commands.removeCommand(blurCommand);

this.renderer.content.removeAttribute("aria-hidden");

this.renderer.$gutter.setAttribute("tabindex", -1);
this.renderer.$gutter.setAttribute("aria-hidden", true);
this.renderer.$gutter.removeAttribute("role");
this.renderer.$gutter.removeAttribute("aria-roledescription");
this.renderer.$gutter.removeAttribute("aria-label");
this.renderer.$gutter.classList.remove(this.keyboardFocusClassName);
this.renderer.$gutter.classList.remove(this.renderer.keyboardFocusClassName);

if (gutterKeyboardHandler)
gutterKeyboardHandler.removeListener();
Expand Down
37 changes: 33 additions & 4 deletions src/editor_navigation_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ var VirtualRenderer = require("./virtual_renderer").VirtualRenderer;
var assert = require("./test/assertions");
var keys = require('./lib/keys');

function emit(keyCode) {
var data = {bubbles: true, keyCode};
var event = new KeyboardEvent("keyup", data);

var el = document.activeElement;
el.dispatchEvent(event);
}

module.exports = {
createEditSession : function(rows, cols) {
var line = new Array(cols + 1).join("a");
Expand Down Expand Up @@ -169,12 +177,12 @@ module.exports = {

// Focus should be on textInput
assert.equal(document.activeElement, editor.textInput.getElement());
assert.notEqual(document.activeElement, editor.renderer.content);
assert.notEqual(document.activeElement, editor.renderer.scroller);

editor.onCommandKey({}, 0, keys["escape"]);

// Focus should be on the content div after pressing Esc
assert.equal(document.activeElement, editor.renderer.content);
assert.equal(document.activeElement, editor.renderer.scroller);
assert.notEqual(document.activeElement, editor.textInput.getElement());

// Should trap focus
Expand All @@ -185,13 +193,34 @@ module.exports = {

// Focus should be on textInput
assert.equal(document.activeElement, editor.textInput.getElement());
assert.notEqual(document.activeElement, editor.renderer.content);
assert.notEqual(document.activeElement, editor.renderer.scroller);

editor.onCommandKey({}, 0, keys["escape"]);

// Focus should still be on the textInput
assert.equal(document.activeElement, editor.textInput.getElement());
assert.notEqual(document.activeElement, editor.renderer.content);
assert.notEqual(document.activeElement, editor.renderer.scroller);
},

"test: should allow to focus on textInput using keyboard in non-trapping mode": function() {
var editor = new Editor(new VirtualRenderer(), new EditSession(["1234", "1234567890"]));

// Set to not trap focus mode
editor.setOption('enableKeyboardAccessibility', true);

// Focus on editor
editor.renderer.scroller.focus();

// Focus should be on scroller
assert.equal(document.activeElement, editor.renderer.scroller);
assert.notEqual(document.activeElement, editor.textInput.getElement());

// Press enter to give focus to the textinput
emit(keys["enter"]);

// Focus should be on the textinput
assert.equal(document.activeElement, editor.textInput.getElement());
assert.notEqual(document.activeElement, editor.renderer.scroller);
}
};

Expand Down
8 changes: 4 additions & 4 deletions src/keyboard/gutter_handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ class GutterKeyboardHandler {

var foldWidget = this.$getFoldWidget(index);

foldWidget.classList.add(this.editor.keyboardFocusClassName);
foldWidget.classList.add(this.editor.renderer.keyboardFocusClassName);
foldWidget.focus();
}

Expand All @@ -287,22 +287,22 @@ class GutterKeyboardHandler {

var annotation = this.$getAnnotation(index);

annotation.classList.add(this.editor.keyboardFocusClassName);
annotation.classList.add(this.editor.renderer.keyboardFocusClassName);
annotation.setAttribute("role", "button");
annotation.focus();
}

$blurFoldWidget(index) {
var foldWidget = this.$getFoldWidget(index);

foldWidget.classList.remove(this.editor.keyboardFocusClassName);
foldWidget.classList.remove(this.editor.renderer.keyboardFocusClassName);
foldWidget.blur();
}

$blurAnnotation(index) {
var annotation = this.$getAnnotation(index);

annotation.classList.remove(this.editor.keyboardFocusClassName);
annotation.classList.remove(this.editor.renderer.keyboardFocusClassName);
annotation.removeAttribute("role");
annotation.blur();
}
Expand Down
17 changes: 9 additions & 8 deletions src/keyboard/textinput.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,15 @@ var TextInput = function(parentNode, host) {
}
};
this.setAriaLabel = function() {
var row;
if (!host.session)
row = 0;
else
row = host.session.selection.cursor.row;
if(host.session && host.renderer.enableKeyboardAccessibility) {
var row = host.session.selection.cursor.row;

text.setAttribute("aria-roledescription", nls("editor"));
text.setAttribute("aria-label", nls("Cursor at row $0", [row + 1]));
text.setAttribute("aria-roledescription", nls("editor"));
text.setAttribute("aria-label", nls("Cursor at row $0", [row + 1]));
} else {
text.removeAttribute("aria-roledescription");
text.removeAttribute("aria-label");
}
};

this.setAriaOptions({role: "textbox"});
Expand Down Expand Up @@ -105,7 +106,7 @@ var TextInput = function(parentNode, host) {
}, host);
this.$focusScroll = false;
this.focus = function() {
// On focusing on the textarea, read active row to assistive tech.
// On focusing on the textarea, read active row number to assistive tech.
this.setAriaLabel();

if (tempStyle || HAS_FOCUS_ARGS || this.$focusScroll == "browser")
Expand Down
7 changes: 7 additions & 0 deletions src/layer/gutter.js
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,13 @@ class Gutter{
dom.setStyle(cell.element.style, "top", this.$lines.computeLineTop(row, config, session) + "px");

cell.text = rowText;

// If there are no annotations or fold widgets in the gutter cell, hide it from assistive tech.
if (annotationNode.style.display === "none" && foldWidget.style.display === "none")
cell.element.setAttribute("aria-hidden", true);
else
cell.element.setAttribute("aria-hidden", false);

return cell;
}

Expand Down
4 changes: 3 additions & 1 deletion src/virtual_renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -847,7 +847,9 @@ class VirtualRenderer {
// horizontal scrolling
if (changes & this.CHANGE_H_SCROLL) {
dom.translate(this.content, -this.scrollLeft, -config.offset);
this.scroller.className = this.scrollLeft <= 0 ? "ace_scroller" : "ace_scroller ace_scroll-left";
this.scroller.className = this.scrollLeft <= 0 ? "ace_scroller " : "ace_scroller ace_scroll-left ";
if (this.enableKeyboardAccessibility)
this.scroller.className += this.keyboardFocusClassName;
}

// full
Expand Down

0 comments on commit 563afd1

Please sign in to comment.