diff --git a/core/block.js b/core/block.js index 895c77c1b..194c48b86 100644 --- a/core/block.js +++ b/core/block.js @@ -325,7 +325,7 @@ Blockly.Block.prototype.dispose = function(healStack) { this.workspace.removeChangeListener(this.onchangeWrapper_); } - if (Blockly.keyboardAccessibilityMode) { + if (this.workspace.keyboardAccessibilityMode) { // No-op if this is called from the block_svg class. Blockly.navigation.moveCursorOnBlockDelete(this); } diff --git a/core/block_svg.js b/core/block_svg.js index 5c34f64ae..4a575b89e 100644 --- a/core/block_svg.js +++ b/core/block_svg.js @@ -1031,7 +1031,7 @@ Blockly.BlockSvg.prototype.dispose = function(healStack, animate) { Blockly.ContextMenu.hide(); } - if (Blockly.keyboardAccessibilityMode) { + if (this.workspace.keyboardAccessibilityMode) { Blockly.navigation.moveCursorOnBlockDelete(this); } diff --git a/core/blockly.js b/core/blockly.js index e220741b8..fd75b34a6 100644 --- a/core/blockly.js +++ b/core/blockly.js @@ -72,12 +72,6 @@ Blockly.selected = null; */ Blockly.cursor = null; -/** - * Whether or not we're currently in keyboard accessibility mode. - * @type {boolean} - */ -Blockly.keyboardAccessibilityMode = false; - /** * All of the connections on blocks that are currently being dragged. * @type {!Array.} @@ -183,6 +177,9 @@ Blockly.svgResize = function(workspace) { // are multiple workspaces and non-main workspaces are able to accept input. Blockly.onKeyDown = function(e) { var mainWorkspace = Blockly.mainWorkspace; + if (!mainWorkspace) { + return; + } if (Blockly.utils.isTargetInput(e) || (mainWorkspace.rendered && !mainWorkspace.isVisible())) { diff --git a/core/gesture.js b/core/gesture.js index ade6a2c86..c07a19bef 100644 --- a/core/gesture.js +++ b/core/gesture.js @@ -657,7 +657,7 @@ Blockly.Gesture.prototype.handleWsStart = function(e, ws) { this.setStartWorkspace_(ws); this.mostRecentEvent_ = e; this.doStart(e); - if (Blockly.keyboardAccessibilityMode) { + if (this.startWorkspace_.keyboardAccessibilityMode) { Blockly.navigation.setState(Blockly.navigation.STATE_WS); } }; diff --git a/core/inject.js b/core/inject.js index 45c638627..ee07595e9 100644 --- a/core/inject.js +++ b/core/inject.js @@ -61,6 +61,10 @@ Blockly.inject = function(container, opt_options) { (/** @type {!Blockly.BlocklyOptions} */ ({}))); var subContainer = document.createElement('div'); subContainer.className = 'injectionDiv'; + subContainer.tabIndex = 0; + Blockly.utils.aria.setState(subContainer, + Blockly.utils.aria.State.LABEL, Blockly.Msg['WORKSPACE_ARIA_LABEL']); + container.appendChild(subContainer); var svg = Blockly.createDom_(subContainer, options); @@ -78,6 +82,14 @@ Blockly.inject = function(container, opt_options) { Blockly.svgResize(workspace); + subContainer.addEventListener('focus', function() { + Blockly.mainWorkspace = workspace; + }); + + subContainer.addEventListener('blur', function() { + Blockly.mainWorkspace = null; + }); + return workspace; }; diff --git a/core/keyboard_nav/navigation.js b/core/keyboard_nav/navigation.js index 90319cb9b..7f6523554 100644 --- a/core/keyboard_nav/navigation.js +++ b/core/keyboard_nav/navigation.js @@ -666,8 +666,8 @@ Blockly.navigation.moveCursorOnBlockMutation = function(mutatedBlock) { * Enable accessibility mode. */ Blockly.navigation.enableKeyboardAccessibility = function() { - if (!Blockly.keyboardAccessibilityMode) { - Blockly.keyboardAccessibilityMode = true; + if (!Blockly.getMainWorkspace().keyboardAccessibilityMode) { + Blockly.getMainWorkspace().keyboardAccessibilityMode = true; Blockly.navigation.focusWorkspace_(); } }; @@ -676,9 +676,9 @@ Blockly.navigation.enableKeyboardAccessibility = function() { * Disable accessibility mode. */ Blockly.navigation.disableKeyboardAccessibility = function() { - if (Blockly.keyboardAccessibilityMode) { + if (Blockly.getMainWorkspace().keyboardAccessibilityMode) { var workspace = Blockly.getMainWorkspace(); - Blockly.keyboardAccessibilityMode = false; + Blockly.getMainWorkspace().keyboardAccessibilityMode = false; workspace.getCursor().hide(); workspace.getMarker().hide(); if (Blockly.navigation.getFlyoutCursor_()) { @@ -758,7 +758,7 @@ Blockly.navigation.onBlocklyAction = function(action) { var readOnly = Blockly.getMainWorkspace().options.readOnly; var actionHandled = false; - if (Blockly.keyboardAccessibilityMode) { + if (Blockly.getMainWorkspace().keyboardAccessibilityMode) { if (!readOnly) { actionHandled = Blockly.navigation.handleActions_(action); // If in readonly mode only handle valid actions. diff --git a/core/mutator.js b/core/mutator.js index 649e1fe5e..390696ddf 100644 --- a/core/mutator.js +++ b/core/mutator.js @@ -378,7 +378,8 @@ Blockly.Mutator.prototype.workspaceChanged_ = function(e) { block.render(); } - if (oldMutation != newMutation && Blockly.keyboardAccessibilityMode) { + if (oldMutation != newMutation && + this.workspace_.keyboardAccessibilityMode) { Blockly.navigation.moveCursorOnBlockMutation(block); } // Don't update the bubble until the drag has ended, to avoid moving blocks diff --git a/core/toolbox.js b/core/toolbox.js index f8914d894..d68bec94c 100644 --- a/core/toolbox.js +++ b/core/toolbox.js @@ -296,13 +296,13 @@ Blockly.Toolbox.prototype.handleAfterTreeSelected_ = function( if (this.lastCategory_ != newNode) { this.flyout_.scrollToStart(); } - if (Blockly.keyboardAccessibilityMode) { + if (this.workspace_.keyboardAccessibilityMode) { Blockly.navigation.setState(Blockly.navigation.STATE_TOOLBOX); } } else { // Hide the flyout. this.flyout_.hide(); - if (Blockly.keyboardAccessibilityMode && + if (this.workspace_.keyboardAccessibilityMode && !(newNode instanceof Blockly.Toolbox.TreeSeparator)) { Blockly.navigation.setState(Blockly.navigation.STATE_WS); } diff --git a/core/workspace.js b/core/workspace.js index 2119c8d17..0f7b74c9b 100644 --- a/core/workspace.js +++ b/core/workspace.js @@ -138,6 +138,13 @@ Blockly.Workspace = function(opt_options) { new Blockly.ThemeManager(this.options.theme || Blockly.Themes.Classic); this.themeManager_.subscribeWorkspace(this); + + /** + * True if keyboard accessibility mode is on, false otherwise. + * @type {boolean} + * @package + */ + this.keyboardAccessibilityMode = false; }; /** diff --git a/core/workspace_svg.js b/core/workspace_svg.js index a79cc05bf..a4af7e3c8 100644 --- a/core/workspace_svg.js +++ b/core/workspace_svg.js @@ -1198,7 +1198,7 @@ Blockly.WorkspaceSvg.prototype.pasteBlock_ = function(xmlBlock) { // Handle paste for keyboard navigation var markedNode = this.getMarker().getCurNode(); - if (Blockly.keyboardAccessibilityMode && markedNode && + if (this.keyboardAccessibilityMode && markedNode && markedNode.isConnection()) { var markedLocation = /** @type {!Blockly.Connection} */ (markedNode.getLocation()); diff --git a/msg/json/en.json b/msg/json/en.json index 1a9b1d043..b4552c430 100644 --- a/msg/json/en.json +++ b/msg/json/en.json @@ -1,7 +1,7 @@ { "@metadata": { "author": "Ellen Spertus ", - "lastupdated": "2019-10-04 13:16:14.900805", + "lastupdated": "2019-10-28 11:09:19.411955", "locale": "en", "messagedocumentation" : "qqq" }, @@ -403,5 +403,6 @@ "PROCEDURES_IFRETURN_HELPURL": "http://c2.com/cgi/wiki?GuardClause", "PROCEDURES_IFRETURN_WARNING": "Warning: This block may be used only within a function definition.", "WORKSPACE_COMMENT_DEFAULT_TEXT": "Say something...", + "WORKSPACE_ARIA_LABEL": "Blockly Workspace", "COLLAPSED_WARNINGS_WARNING": "Collapsed blocks contain warnings." } diff --git a/msg/json/qqq.json b/msg/json/qqq.json index c6c5d401a..f2b7d1723 100644 --- a/msg/json/qqq.json +++ b/msg/json/qqq.json @@ -1,13 +1,4 @@ { - "@metadata": { - "authors": [ - "Espertus", - "Liuxinyu970226", - "Metalhead64", - "Robby", - "Shirayuki" - ] - }, "VARIABLES_DEFAULT_NAME": "default name - A simple, general default name for a variable, preferably short. For more context, see [[Translating:Blockly#infrequent_message_types]].\n{{Identical|Item}}", "UNNAMED_KEY": "default name - A simple, default name for an unnamed function or variable. Preferably indicates that the item is unnamed.", "TODAY": "button text - Button that sets a calendar to today's date.\n{{Identical|Today}}", @@ -406,5 +397,6 @@ "PROCEDURES_IFRETURN_HELPURL": "{{Optional}} url - Information about guard clauses.", "PROCEDURES_IFRETURN_WARNING": "warning - This appears if the user tries to use this block outside of a function definition.", "WORKSPACE_COMMENT_DEFAULT_TEXT": "comment text - This text appears in a new workspace comment, to hint that the user can type here.", + "WORKSPACE_ARIA_LABEL": "workspace - This text is read out when a user navigates to the workspace while using a screen reader.", "COLLAPSED_WARNINGS_WARNING": "warning - This appears if the user collapses a block, and blocks inside that block have warnings attached to them. It should inform the user that the block they collapsed contains blocks that have warnings." } diff --git a/msg/messages.js b/msg/messages.js index 2cb5c3ed8..ef3f46bd1 100644 --- a/msg/messages.js +++ b/msg/messages.js @@ -1629,6 +1629,11 @@ Blockly.Msg.PROCEDURES_IFRETURN_WARNING = 'Warning: This block may be used only /// the user can type here. Blockly.Msg.WORKSPACE_COMMENT_DEFAULT_TEXT = 'Say something...'; +/** @type {string} */ +/// workspace - This text is read out when a user navigates to the workspace while +/// using a screen reader. +Blockly.Msg.WORKSPACE_ARIA_LABEL = 'Blockly Workspace'; + /** @type {string} */ /// warning - This appears if the user collapses a block, and blocks inside /// that block have warnings attached to them. It should inform the user that the diff --git a/tests/mocha/gesture_test.js b/tests/mocha/gesture_test.js index 460e885da..680bc0bc6 100644 --- a/tests/mocha/gesture_test.js +++ b/tests/mocha/gesture_test.js @@ -90,8 +90,8 @@ suite('Gesture', function() { }; var ws = Blockly.inject('blocklyDiv', {}); var gesture = new Blockly.Gesture(this.e, ws); - assertFalse(Blockly.keyboardAccessibilityMode); + assertFalse(Blockly.getMainWorkspace().keyboardAccessibilityMode); gesture.doWorkspaceClick_(event); - assertTrue(Blockly.keyboardAccessibilityMode); + assertTrue(Blockly.getMainWorkspace().keyboardAccessibilityMode); }); }); diff --git a/tests/mocha/navigation_test.js b/tests/mocha/navigation_test.js index f4ed06978..b2c2b501a 100644 --- a/tests/mocha/navigation_test.js +++ b/tests/mocha/navigation_test.js @@ -349,7 +349,7 @@ suite('Navigation', function() { this.workspace = new Blockly.Workspace({readOnly: false}); Blockly.user.keyMap.setKeyMap(Blockly.user.keyMap.createDefaultKeyMap()); Blockly.mainWorkspace = this.workspace; - Blockly.keyboardAccessibilityMode = true; + Blockly.getMainWorkspace().keyboardAccessibilityMode = true; Blockly.navigation.currentState_ = Blockly.navigation.STATE_WS; this.mockEvent = { @@ -407,24 +407,24 @@ suite('Navigation', function() { test('Toggle Action Off', function() { this.mockEvent.keyCode = 'Control75'; sinon.spy(Blockly.navigation, 'onBlocklyAction'); - Blockly.keyboardAccessibilityMode = true; + Blockly.getMainWorkspace().keyboardAccessibilityMode = true; var isHandled = Blockly.navigation.onKeyPress(this.mockEvent); chai.assert.isTrue(isHandled); chai.assert.isTrue(Blockly.navigation.onBlocklyAction.calledOnce); - chai.assert.isFalse(Blockly.keyboardAccessibilityMode); + chai.assert.isFalse(Blockly.getMainWorkspace().keyboardAccessibilityMode); Blockly.navigation.onBlocklyAction.restore(); }); test('Toggle Action On', function() { this.mockEvent.keyCode = 'Control75'; sinon.stub(Blockly.navigation, 'focusWorkspace_'); - Blockly.keyboardAccessibilityMode = false; + Blockly.getMainWorkspace().keyboardAccessibilityMode = false; var isHandled = Blockly.navigation.onKeyPress(this.mockEvent); chai.assert.isTrue(isHandled); chai.assert.isTrue(Blockly.navigation.focusWorkspace_.calledOnce); - chai.assert.isTrue(Blockly.keyboardAccessibilityMode); + chai.assert.isTrue(Blockly.getMainWorkspace().keyboardAccessibilityMode); Blockly.navigation.focusWorkspace_.restore(); this.workspace.dispose(); }); @@ -459,7 +459,7 @@ suite('Navigation', function() { this.workspace = new Blockly.Workspace({readOnly: true}); this.workspace.setCursor(new Blockly.Cursor()); Blockly.mainWorkspace = this.workspace; - Blockly.keyboardAccessibilityMode = true; + Blockly.getMainWorkspace().keyboardAccessibilityMode = true; Blockly.navigation.currentState_ = Blockly.navigation.STATE_WS; this.fieldBlock1 = this.workspace.newBlock('field_block');