mirror of
https://github.com/google/blockly.git
synced 2026-01-13 11:57:10 +01:00
Enter accessibility (#2982)
* Fix shift clicking on a block * Add tests for toggle keyboar nav
This commit is contained in:
@@ -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 ||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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<string>}
|
||||
* 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<string>} 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<string,Blockly.Action>} 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;
|
||||
};
|
||||
|
||||
@@ -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<!Blockly.Action>}
|
||||
|
||||
@@ -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([{
|
||||
|
||||
@@ -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 {
|
||||
<input type="checkbox" onclick="toggleRenderingDebug(this.checked)" id="blockRenderDebugCheck">
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Enable Accessibility Mode:
|
||||
<input type="checkbox" onclick="toggleAccessibilityMode(this.checked)" id="accessibilityModeCheck">
|
||||
</p>
|
||||
|
||||
|
||||
<!-- The next three blocks of XML are sample toolboxes for testing basic
|
||||
configurations. For more information on building toolboxes, see https://developers.google.com/blockly/guides/configure/web/toolbox -->
|
||||
|
||||
|
||||
Reference in New Issue
Block a user