diff --git a/core/blockly.js b/core/blockly.js
index b075c751e..2e793b1a3 100644
--- a/core/blockly.js
+++ b/core/blockly.js
@@ -216,9 +216,7 @@ Blockly.onKeyDown_ = function(e) {
if (mainWorkspace.options.readOnly) {
// When in read only mode handle key actions for keyboard navigation.
- if (Blockly.keyboardAccessibilityMode) {
- Blockly.navigation.onKeyPress(e);
- }
+ Blockly.navigation.onKeyPress(e);
return;
}
@@ -227,8 +225,7 @@ Blockly.onKeyDown_ = function(e) {
// Pressing esc closes the context menu.
Blockly.hideChaff();
Blockly.navigation.onBlocklyAction(Blockly.navigation.ACTION_EXIT);
- } else if (Blockly.keyboardAccessibilityMode &&
- Blockly.navigation.onKeyPress(e)) {
+ } else if (Blockly.navigation.onKeyPress(e)) {
// If the keyboard or field handled the key press return.
return;
} else if (e.keyCode == Blockly.utils.KeyCodes.BACKSPACE ||
diff --git a/core/gesture.js b/core/gesture.js
index d9ad6c8b4..ed2899132 100644
--- a/core/gesture.js
+++ b/core/gesture.js
@@ -498,9 +498,12 @@ Blockly.Gesture.prototype.doStart = function(e) {
Blockly.Tooltip.block();
if (this.targetBlock_) {
- this.targetBlock_.select();
if (!this.targetBlock_.isInFlyout && e.shiftKey) {
Blockly.navigation.enableKeyboardAccessibility();
+ this.creatorWorkspace_.cursor.setLocation(
+ Blockly.navigation.getTopNode(this.targetBlock_));
+ } else {
+ this.targetBlock_.select();
}
}
@@ -760,7 +763,6 @@ Blockly.Gesture.prototype.doBlockClick_ = function() {
* @private
*/
Blockly.Gesture.prototype.doWorkspaceClick_ = function(e) {
- Blockly.navigation.disableKeyboardAccessibility();
var ws = this.creatorWorkspace_;
if (e.shiftKey) {
Blockly.navigation.enableKeyboardAccessibility();
diff --git a/core/keyboard_nav/key_map.js b/core/keyboard_nav/key_map.js
index 6672ab1f7..9bb163b28 100644
--- a/core/keyboard_nav/key_map.js
+++ b/core/keyboard_nav/key_map.js
@@ -37,10 +37,15 @@ goog.require('Blockly.utils.KeyCodes');
Blockly.user.keyMap.map_ = {};
/**
- * List of modifier keys checked when serializing the key event.
- * @type {Array}
+ * Object holding valid modifiers.
+ * @enum {string}
*/
-Blockly.user.keyMap.modifierKeys = ['Shift', 'Control', 'Alt', 'Meta'];
+Blockly.user.keyMap.modifierKeys = {
+ SHIFT: 'Shift',
+ CONTROL: 'Control',
+ ALT: 'Alt',
+ META: 'Meta'
+};
/**
* Update the key map to contain the new action.
@@ -113,9 +118,9 @@ Blockly.user.keyMap.getKeyByAction = function(action) {
* @return {!string} A string containing the serialized key event.
*/
Blockly.user.keyMap.serializeKeyEvent = function(e) {
- var modifierKeys = Blockly.user.keyMap.modifierKeys;
+ var modifiers = Object.values(Blockly.user.keyMap.modifierKeys);
var key = '';
- for (var i = 0, keyName; keyName = modifierKeys[i]; i++) {
+ for (var i = 0, keyName; keyName = modifiers[i]; i++) {
if (e.getModifierState(keyName)) {
key += keyName;
}
@@ -124,6 +129,27 @@ Blockly.user.keyMap.serializeKeyEvent = function(e) {
return key;
};
+/**
+ * Create the serialized key code that will be used in the key map.
+ * @param {!number} keyCode Number code representing the key.
+ * @param {!Array} modifiers List of modifiers to be used with the key.
+ * All valid modifiers can be found in the Blockly.user.keyMap.modifierKeys.
+ * @return {string} The serialized key code for the given modifiers and key.
+ */
+Blockly.user.keyMap.createSerializedKey = function(keyCode, modifiers) {
+ var key = '';
+ var validModifiers = Object.values(Blockly.user.keyMap.modifierKeys);
+ for (var i = 0, keyName; keyName = modifiers[i]; i++) {
+ if (validModifiers.indexOf(keyName) > -1) {
+ key += keyName;
+ } else {
+ throw Error(keyName + ' is not a valid modifier key.');
+ }
+ }
+ key += keyCode;
+ return key;
+};
+
/**
* Creates the default key map.
* @return {!Object} An object holding the default key
@@ -131,6 +157,9 @@ Blockly.user.keyMap.serializeKeyEvent = function(e) {
*/
Blockly.user.keyMap.createDefaultKeyMap = function() {
var map = {};
+ var controlK = Blockly.user.keyMap.createSerializedKey(
+ Blockly.utils.KeyCodes.K, [Blockly.user.keyMap.modifierKeys.CONTROL]);
+
map[Blockly.utils.KeyCodes.W] = Blockly.navigation.ACTION_PREVIOUS;
map[Blockly.utils.KeyCodes.A] = Blockly.navigation.ACTION_OUT;
map[Blockly.utils.KeyCodes.S] = Blockly.navigation.ACTION_NEXT;
@@ -141,5 +170,6 @@ Blockly.user.keyMap.createDefaultKeyMap = function() {
map[Blockly.utils.KeyCodes.T] = Blockly.navigation.ACTION_TOOLBOX;
map[Blockly.utils.KeyCodes.E] = Blockly.navigation.ACTION_EXIT;
map[Blockly.utils.KeyCodes.ESC] = Blockly.navigation.ACTION_EXIT;
+ map[controlK] = Blockly.navigation.ACTION_TOGGLE_KEYBOARD_NAV;
return map;
};
diff --git a/core/keyboard_nav/navigation.js b/core/keyboard_nav/navigation.js
index a236f0f6a..ad85e5593 100644
--- a/core/keyboard_nav/navigation.js
+++ b/core/keyboard_nav/navigation.js
@@ -105,7 +105,8 @@ Blockly.navigation.actionNames = {
MARK: 'mark',
DISCONNECT: 'disconnect',
TOOLBOX: 'toolbox',
- EXIT: 'exit'
+ EXIT: 'exit',
+ TOGGLE_KEYBOARD_NAV: 'toggle_keyboard_nav'
};
/**
* Set the navigation cursor.
@@ -153,7 +154,7 @@ Blockly.navigation.removeMark = function() {
* block.
* @package
*/
-Blockly.navigation.getTopNode_ = function(block) {
+Blockly.navigation.getTopNode = function(block) {
var prevConnection = block.previousConnection;
var outConnection = block.outputConnection;
var topConnection = prevConnection ? prevConnection : outConnection;
@@ -390,7 +391,7 @@ Blockly.navigation.insertFromFlyout = function() {
}
Blockly.navigation.focusWorkspace();
- Blockly.navigation.cursor_.setLocation(Blockly.navigation.getTopNode_(newBlock));
+ Blockly.navigation.cursor_.setLocation(Blockly.navigation.getTopNode(newBlock));
Blockly.navigation.removeMark();
};
@@ -724,9 +725,8 @@ Blockly.navigation.disconnectBlocks = function() {
/*************************/
/**
- * Sets the cursor to the previous or output connection of the selected block
- * on the workspace.
- * If no block is selected, places the cursor at a fixed point on the workspace.
+ * Finds where the cursor should go on the workspace. This is either the top
+ * block or a set position on the workspace.
*/
Blockly.navigation.focusWorkspace = function() {
var cursor = Blockly.navigation.cursor_;
@@ -735,11 +735,8 @@ Blockly.navigation.focusWorkspace = function() {
Blockly.navigation.resetFlyout(reset);
Blockly.navigation.currentState_ = Blockly.navigation.STATE_WS;
- if (Blockly.selected) {
- cursor.setLocation(Blockly.navigation.getTopNode_(Blockly.selected));
- Blockly.selected.unselect();
- } else if (topBlocks.length > 0) {
- cursor.setLocation(Blockly.navigation.getTopNode_(topBlocks[0]));
+ if (topBlocks.length > 0) {
+ cursor.setLocation(Blockly.navigation.getTopNode(topBlocks[0]));
} else {
var ws = cursor.workspace_;
// TODO: Find the center of the visible workspace.
@@ -860,15 +857,22 @@ Blockly.navigation.onKeyPress = function(e) {
var actionHandled = false;
if (action) {
- if (!readOnly) {
- if (curNode && curNode.getType() === Blockly.ASTNode.types.FIELD) {
- actionHandled = curNode.getLocation().onBlocklyAction(action);
- }
- if (!actionHandled) {
+ if (Blockly.keyboardAccessibilityMode) {
+ if (!readOnly) {
+ if (curNode && curNode.getType() === Blockly.ASTNode.types.FIELD) {
+ actionHandled = curNode.getLocation().onBlocklyAction(action);
+ }
+ if (!actionHandled) {
+ actionHandled = Blockly.navigation.onBlocklyAction(action);
+ }
+ // If in readonly mode only handle valid actions.
+ } else if (Blockly.navigation.READONLY_ACTION_LIST.indexOf(action) > -1) {
actionHandled = Blockly.navigation.onBlocklyAction(action);
}
- } else if (Blockly.navigation.READONLY_ACTION_LIST.indexOf(action) > -1) {
- actionHandled = Blockly.navigation.onBlocklyAction(action);
+ // If not in accessibility mode only hanlde turning on keyboard navigation.
+ } else if (action.name === Blockly.navigation.actionNames.TOGGLE_KEYBOARD_NAV) {
+ Blockly.navigation.enableKeyboardAccessibility();
+ actionHandled = true;
}
}
return actionHandled;
@@ -882,7 +886,10 @@ Blockly.navigation.onKeyPress = function(e) {
* @package
*/
Blockly.navigation.onBlocklyAction = function(action) {
- if (Blockly.navigation.currentState_ === Blockly.navigation.STATE_WS) {
+ if (action.name === Blockly.navigation.actionNames.TOGGLE_KEYBOARD_NAV) {
+ Blockly.navigation.disableKeyboardAccessibility();
+ return true;
+ } else if (Blockly.navigation.currentState_ === Blockly.navigation.STATE_WS) {
return Blockly.navigation.workspaceOnAction_(action);
} else if (Blockly.navigation.currentState_ === Blockly.navigation.STATE_FLYOUT) {
return Blockly.navigation.flyoutOnAction_(action);
@@ -1116,6 +1123,13 @@ Blockly.navigation.ACTION_TOOLBOX = new Blockly.Action(
Blockly.navigation.ACTION_EXIT = new Blockly.Action(
Blockly.navigation.actionNames.EXIT, 'Close the current modal, such as a toolbox or field editor.');
+/**
+ * The action to toggle keyboard navigation mode on and off.
+ * @type {!Blockly.Action}
+ */
+Blockly.navigation.ACTION_TOGGLE_KEYBOARD_NAV = new Blockly.Action(
+ Blockly.navigation.actionNames.TOGGLE_KEYBOARD_NAV, 'Turns on and off keyboard navigation.');
+
/**
* List of actions that can be performed in read only mode.
* @type {!Array}
diff --git a/tests/mocha/navigation_test.js b/tests/mocha/navigation_test.js
index 6d3b61a22..e6b9f29cb 100644
--- a/tests/mocha/navigation_test.js
+++ b/tests/mocha/navigation_test.js
@@ -370,6 +370,37 @@ suite('Navigation', function() {
field.onBlocklyAction.restore();
Blockly.navigation.onBlocklyAction.restore();
});
+
+ test('Toggle Action Off', function() {
+ var cursor = new Blockly.Cursor();
+ Blockly.navigation.setCursor(cursor);
+ this.mockEvent.keyCode = 'Control75';
+ sinon.spy(Blockly.navigation, 'onBlocklyAction');
+ Blockly.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);
+ Blockly.navigation.onBlocklyAction.restore();
+ });
+
+ test('Toggle Action On', function() {
+ var cursor = new Blockly.Cursor();
+ Blockly.navigation.setCursor(cursor);
+ this.workspace = Blockly.inject('blocklyDiv', {readOnly: false});
+ this.mockEvent.keyCode = 'Control75';
+ sinon.stub(Blockly.navigation, 'focusWorkspace');
+ Blockly.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);
+ Blockly.navigation.focusWorkspace.restore();
+ this.workspace.dispose();
+ });
+
suite('Test key press in read only mode', function() {
setup(function() {
Blockly.defineBlocksWithJsonArray([{
diff --git a/tests/playground.html b/tests/playground.html
index 30b23d5b7..a4098efb6 100644
--- a/tests/playground.html
+++ b/tests/playground.html
@@ -351,6 +351,14 @@ function toggleRenderingDebug(state) {
}
}
+function toggleAccessibilityMode(state) {
+ if (state) {
+ Blockly.navigation.enableKeyboardAccessibility();
+ } else {
+ Blockly.navigation.disableKeyboardAccessibility();
+ }
+}
+
function logger(e) {
console.log(e);
}
@@ -520,6 +528,12 @@ h1 {
+
+ Enable Accessibility Mode:
+
+
+
+