diff --git a/ace.d.ts b/ace.d.ts index 58276a1e988..7ed46b6f7b6 100644 --- a/ace.d.ts +++ b/ace.d.ts @@ -227,6 +227,7 @@ export namespace Ace { value: string; session: EditSession; relativeLineNumbers: boolean; + enableKeyboardAccessibility: boolean; } export interface SearchOptions { diff --git a/src/css/editor.css.js b/src/css/editor.css.js index c62630005c2..a4e012ada7d 100644 --- a/src/css/editor.css.js +++ b/src/css/editor.css.js @@ -58,6 +58,11 @@ module.exports = ` font-variant-ligatures: no-common-ligatures; } +.ace_keyboard-focus:focus { + box-shadow: inset 0 0 0 2px #5E9ED6; + outline: none; +} + .ace_dragging .ace_scroller:before{ position: absolute; top: 0; diff --git a/src/editor.js b/src/editor.js index b9f928f055c..474e8448b24 100644 --- a/src/editor.js +++ b/src/editor.js @@ -19,6 +19,7 @@ var TokenIterator = require("./token_iterator").TokenIterator; var LineWidgets = require("./line_widgets").LineWidgets; var clipboard = require("./clipboard"); +var keys = require('./lib/keys'); /** * The main entry point into the Ace functionality. @@ -2858,6 +2859,53 @@ config.defineOptions(Editor.prototype, "editor", { this.$updatePlaceholder(); } }, + enableKeyboardAccessibility: { + set: function(value) { + var blurCommand = { + name: "blurTextInput", + description: "Set focus to the editor content div to allow tabbing through the page", + bindKey: "Esc", + exec: function(editor) { + editor.blur(); + editor.renderer.content.focus(); + }, + readOnly: true + }; + + var focusOnEnterKeyup = function (e) { + if (e.target == this.renderer.content && e.keyCode === keys['enter']){ + e.stopPropagation(); + e.preventDefault(); + this.focus(); + } + }; + + var keyboardFocusClassName = "ace_keyboard-focus"; + + // 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.textInput.getElement().setAttribute("tabindex", -1); + this.renderer.content.setAttribute("tabindex", 0); + this.renderer.content.classList.add(keyboardFocusClassName); + this.renderer.content.setAttribute("aria-label", + "Editor, press Enter key to start editing, press Escape key to exit" + ); + + this.renderer.content.addEventListener("keyup", focusOnEnterKeyup.bind(this)); + this.commands.addCommand(blurCommand); + } else { + this.textInput.getElement().setAttribute("tabindex", 0); + this.renderer.content.setAttribute("tabindex", -1); + this.renderer.content.classList.remove(keyboardFocusClassName); + this.renderer.content.setAttribute("aria-label", ""); + + this.renderer.content.removeEventListener("keyup", focusOnEnterKeyup.bind(this)); + this.commands.removeCommand(blurCommand); + } + }, + initialValue: false + }, customScrollbar: "renderer", hScrollBarAlwaysVisible: "renderer", vScrollBarAlwaysVisible: "renderer", diff --git a/src/editor_navigation_test.js b/src/editor_navigation_test.js index 9d16b52af95..102609a7447 100644 --- a/src/editor_navigation_test.js +++ b/src/editor_navigation_test.js @@ -8,7 +8,9 @@ if (typeof process !== "undefined") { var EditSession = require("./edit_session").EditSession; var Editor = require("./editor").Editor; var MockRenderer = require("./test/mockrenderer").MockRenderer; +var VirtualRenderer = require("./virtual_renderer").VirtualRenderer; var assert = require("./test/assertions"); +var keys = require('./lib/keys'); module.exports = { createEditSession : function(rows, cols) { @@ -154,6 +156,42 @@ module.exports = { editor.navigateDown(); assert.position(editor.getCursorPosition(), 1, 7); + }, + + "test: should allow to toggle between keyboard trapping modes": function() { + var editor = new Editor(new VirtualRenderer(), new EditSession(["1234", "1234567890"])); + + // Should not trap focus + editor.setOption('enableKeyboardAccessibility', true); + + // Focus on editor + editor.focus(); + + // Focus should be on textInput + assert.equal(document.activeElement, editor.textInput.getElement()); + assert.notEqual(document.activeElement, editor.renderer.content); + + editor.onCommandKey({}, 0, keys["escape"]); + + // Focus should be on the content div after pressing Esc + assert.equal(document.activeElement, editor.renderer.content); + assert.notEqual(document.activeElement, editor.textInput.getElement()); + + // Should trap focus + editor.setOption('enableKeyboardAccessibility', false); + + // Focus on editor + editor.focus(); + + // Focus should be on textInput + assert.equal(document.activeElement, editor.textInput.getElement()); + assert.notEqual(document.activeElement, editor.renderer.content); + + 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); } }; diff --git a/src/ext/options.js b/src/ext/options.js index 00f86bd36ff..471635fe182 100644 --- a/src/ext/options.js +++ b/src/ext/options.js @@ -197,6 +197,9 @@ var optionGroups = { }, "Use SVG gutter icons": { path: "useSvgGutterIcons" + }, + "Keyboard Accessibility Mode": { + path: "enableKeyboardAccessibility" } } };