Add keyboard navigation support for multiple workspaces (#3352)

* Add keyboard navigation support for multiple workspaces
This commit is contained in:
alschmiedt
2019-10-28 12:53:51 -07:00
committed by GitHub
parent cbc79444f6
commit cbf867f441
15 changed files with 51 additions and 36 deletions

View File

@@ -325,7 +325,7 @@ Blockly.Block.prototype.dispose = function(healStack) {
this.workspace.removeChangeListener(this.onchangeWrapper_); this.workspace.removeChangeListener(this.onchangeWrapper_);
} }
if (Blockly.keyboardAccessibilityMode) { if (this.workspace.keyboardAccessibilityMode) {
// No-op if this is called from the block_svg class. // No-op if this is called from the block_svg class.
Blockly.navigation.moveCursorOnBlockDelete(this); Blockly.navigation.moveCursorOnBlockDelete(this);
} }

View File

@@ -1031,7 +1031,7 @@ Blockly.BlockSvg.prototype.dispose = function(healStack, animate) {
Blockly.ContextMenu.hide(); Blockly.ContextMenu.hide();
} }
if (Blockly.keyboardAccessibilityMode) { if (this.workspace.keyboardAccessibilityMode) {
Blockly.navigation.moveCursorOnBlockDelete(this); Blockly.navigation.moveCursorOnBlockDelete(this);
} }

View File

@@ -72,12 +72,6 @@ Blockly.selected = null;
*/ */
Blockly.cursor = 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. * All of the connections on blocks that are currently being dragged.
* @type {!Array.<!Blockly.Connection>} * @type {!Array.<!Blockly.Connection>}
@@ -183,6 +177,9 @@ Blockly.svgResize = function(workspace) {
// are multiple workspaces and non-main workspaces are able to accept input. // are multiple workspaces and non-main workspaces are able to accept input.
Blockly.onKeyDown = function(e) { Blockly.onKeyDown = function(e) {
var mainWorkspace = Blockly.mainWorkspace; var mainWorkspace = Blockly.mainWorkspace;
if (!mainWorkspace) {
return;
}
if (Blockly.utils.isTargetInput(e) || if (Blockly.utils.isTargetInput(e) ||
(mainWorkspace.rendered && !mainWorkspace.isVisible())) { (mainWorkspace.rendered && !mainWorkspace.isVisible())) {

View File

@@ -657,7 +657,7 @@ Blockly.Gesture.prototype.handleWsStart = function(e, ws) {
this.setStartWorkspace_(ws); this.setStartWorkspace_(ws);
this.mostRecentEvent_ = e; this.mostRecentEvent_ = e;
this.doStart(e); this.doStart(e);
if (Blockly.keyboardAccessibilityMode) { if (this.startWorkspace_.keyboardAccessibilityMode) {
Blockly.navigation.setState(Blockly.navigation.STATE_WS); Blockly.navigation.setState(Blockly.navigation.STATE_WS);
} }
}; };

View File

@@ -61,6 +61,10 @@ Blockly.inject = function(container, opt_options) {
(/** @type {!Blockly.BlocklyOptions} */ ({}))); (/** @type {!Blockly.BlocklyOptions} */ ({})));
var subContainer = document.createElement('div'); var subContainer = document.createElement('div');
subContainer.className = 'injectionDiv'; subContainer.className = 'injectionDiv';
subContainer.tabIndex = 0;
Blockly.utils.aria.setState(subContainer,
Blockly.utils.aria.State.LABEL, Blockly.Msg['WORKSPACE_ARIA_LABEL']);
container.appendChild(subContainer); container.appendChild(subContainer);
var svg = Blockly.createDom_(subContainer, options); var svg = Blockly.createDom_(subContainer, options);
@@ -78,6 +82,14 @@ Blockly.inject = function(container, opt_options) {
Blockly.svgResize(workspace); Blockly.svgResize(workspace);
subContainer.addEventListener('focus', function() {
Blockly.mainWorkspace = workspace;
});
subContainer.addEventListener('blur', function() {
Blockly.mainWorkspace = null;
});
return workspace; return workspace;
}; };

View File

@@ -666,8 +666,8 @@ Blockly.navigation.moveCursorOnBlockMutation = function(mutatedBlock) {
* Enable accessibility mode. * Enable accessibility mode.
*/ */
Blockly.navigation.enableKeyboardAccessibility = function() { Blockly.navigation.enableKeyboardAccessibility = function() {
if (!Blockly.keyboardAccessibilityMode) { if (!Blockly.getMainWorkspace().keyboardAccessibilityMode) {
Blockly.keyboardAccessibilityMode = true; Blockly.getMainWorkspace().keyboardAccessibilityMode = true;
Blockly.navigation.focusWorkspace_(); Blockly.navigation.focusWorkspace_();
} }
}; };
@@ -676,9 +676,9 @@ Blockly.navigation.enableKeyboardAccessibility = function() {
* Disable accessibility mode. * Disable accessibility mode.
*/ */
Blockly.navigation.disableKeyboardAccessibility = function() { Blockly.navigation.disableKeyboardAccessibility = function() {
if (Blockly.keyboardAccessibilityMode) { if (Blockly.getMainWorkspace().keyboardAccessibilityMode) {
var workspace = Blockly.getMainWorkspace(); var workspace = Blockly.getMainWorkspace();
Blockly.keyboardAccessibilityMode = false; Blockly.getMainWorkspace().keyboardAccessibilityMode = false;
workspace.getCursor().hide(); workspace.getCursor().hide();
workspace.getMarker().hide(); workspace.getMarker().hide();
if (Blockly.navigation.getFlyoutCursor_()) { if (Blockly.navigation.getFlyoutCursor_()) {
@@ -758,7 +758,7 @@ Blockly.navigation.onBlocklyAction = function(action) {
var readOnly = Blockly.getMainWorkspace().options.readOnly; var readOnly = Blockly.getMainWorkspace().options.readOnly;
var actionHandled = false; var actionHandled = false;
if (Blockly.keyboardAccessibilityMode) { if (Blockly.getMainWorkspace().keyboardAccessibilityMode) {
if (!readOnly) { if (!readOnly) {
actionHandled = Blockly.navigation.handleActions_(action); actionHandled = Blockly.navigation.handleActions_(action);
// If in readonly mode only handle valid actions. // If in readonly mode only handle valid actions.

View File

@@ -378,7 +378,8 @@ Blockly.Mutator.prototype.workspaceChanged_ = function(e) {
block.render(); block.render();
} }
if (oldMutation != newMutation && Blockly.keyboardAccessibilityMode) { if (oldMutation != newMutation &&
this.workspace_.keyboardAccessibilityMode) {
Blockly.navigation.moveCursorOnBlockMutation(block); Blockly.navigation.moveCursorOnBlockMutation(block);
} }
// Don't update the bubble until the drag has ended, to avoid moving blocks // Don't update the bubble until the drag has ended, to avoid moving blocks

View File

@@ -296,13 +296,13 @@ Blockly.Toolbox.prototype.handleAfterTreeSelected_ = function(
if (this.lastCategory_ != newNode) { if (this.lastCategory_ != newNode) {
this.flyout_.scrollToStart(); this.flyout_.scrollToStart();
} }
if (Blockly.keyboardAccessibilityMode) { if (this.workspace_.keyboardAccessibilityMode) {
Blockly.navigation.setState(Blockly.navigation.STATE_TOOLBOX); Blockly.navigation.setState(Blockly.navigation.STATE_TOOLBOX);
} }
} else { } else {
// Hide the flyout. // Hide the flyout.
this.flyout_.hide(); this.flyout_.hide();
if (Blockly.keyboardAccessibilityMode && if (this.workspace_.keyboardAccessibilityMode &&
!(newNode instanceof Blockly.Toolbox.TreeSeparator)) { !(newNode instanceof Blockly.Toolbox.TreeSeparator)) {
Blockly.navigation.setState(Blockly.navigation.STATE_WS); Blockly.navigation.setState(Blockly.navigation.STATE_WS);
} }

View File

@@ -138,6 +138,13 @@ Blockly.Workspace = function(opt_options) {
new Blockly.ThemeManager(this.options.theme || Blockly.Themes.Classic); new Blockly.ThemeManager(this.options.theme || Blockly.Themes.Classic);
this.themeManager_.subscribeWorkspace(this); this.themeManager_.subscribeWorkspace(this);
/**
* True if keyboard accessibility mode is on, false otherwise.
* @type {boolean}
* @package
*/
this.keyboardAccessibilityMode = false;
}; };
/** /**

View File

@@ -1198,7 +1198,7 @@ Blockly.WorkspaceSvg.prototype.pasteBlock_ = function(xmlBlock) {
// Handle paste for keyboard navigation // Handle paste for keyboard navigation
var markedNode = this.getMarker().getCurNode(); var markedNode = this.getMarker().getCurNode();
if (Blockly.keyboardAccessibilityMode && markedNode && if (this.keyboardAccessibilityMode && markedNode &&
markedNode.isConnection()) { markedNode.isConnection()) {
var markedLocation = var markedLocation =
/** @type {!Blockly.Connection} */ (markedNode.getLocation()); /** @type {!Blockly.Connection} */ (markedNode.getLocation());

View File

@@ -1,7 +1,7 @@
{ {
"@metadata": { "@metadata": {
"author": "Ellen Spertus <ellen.spertus@gmail.com>", "author": "Ellen Spertus <ellen.spertus@gmail.com>",
"lastupdated": "2019-10-04 13:16:14.900805", "lastupdated": "2019-10-28 11:09:19.411955",
"locale": "en", "locale": "en",
"messagedocumentation" : "qqq" "messagedocumentation" : "qqq"
}, },
@@ -403,5 +403,6 @@
"PROCEDURES_IFRETURN_HELPURL": "http://c2.com/cgi/wiki?GuardClause", "PROCEDURES_IFRETURN_HELPURL": "http://c2.com/cgi/wiki?GuardClause",
"PROCEDURES_IFRETURN_WARNING": "Warning: This block may be used only within a function definition.", "PROCEDURES_IFRETURN_WARNING": "Warning: This block may be used only within a function definition.",
"WORKSPACE_COMMENT_DEFAULT_TEXT": "Say something...", "WORKSPACE_COMMENT_DEFAULT_TEXT": "Say something...",
"WORKSPACE_ARIA_LABEL": "Blockly Workspace",
"COLLAPSED_WARNINGS_WARNING": "Collapsed blocks contain warnings." "COLLAPSED_WARNINGS_WARNING": "Collapsed blocks contain warnings."
} }

View File

@@ -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}}", "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.", "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}}", "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_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.", "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_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." "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."
} }

View File

@@ -1629,6 +1629,11 @@ Blockly.Msg.PROCEDURES_IFRETURN_WARNING = 'Warning: This block may be used only
/// the user can type here. /// the user can type here.
Blockly.Msg.WORKSPACE_COMMENT_DEFAULT_TEXT = 'Say something...'; 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} */ /** @type {string} */
/// warning - This appears if the user collapses a block, and blocks inside /// 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 /// that block have warnings attached to them. It should inform the user that the

View File

@@ -90,8 +90,8 @@ suite('Gesture', function() {
}; };
var ws = Blockly.inject('blocklyDiv', {}); var ws = Blockly.inject('blocklyDiv', {});
var gesture = new Blockly.Gesture(this.e, ws); var gesture = new Blockly.Gesture(this.e, ws);
assertFalse(Blockly.keyboardAccessibilityMode); assertFalse(Blockly.getMainWorkspace().keyboardAccessibilityMode);
gesture.doWorkspaceClick_(event); gesture.doWorkspaceClick_(event);
assertTrue(Blockly.keyboardAccessibilityMode); assertTrue(Blockly.getMainWorkspace().keyboardAccessibilityMode);
}); });
}); });

View File

@@ -349,7 +349,7 @@ suite('Navigation', function() {
this.workspace = new Blockly.Workspace({readOnly: false}); this.workspace = new Blockly.Workspace({readOnly: false});
Blockly.user.keyMap.setKeyMap(Blockly.user.keyMap.createDefaultKeyMap()); Blockly.user.keyMap.setKeyMap(Blockly.user.keyMap.createDefaultKeyMap());
Blockly.mainWorkspace = this.workspace; Blockly.mainWorkspace = this.workspace;
Blockly.keyboardAccessibilityMode = true; Blockly.getMainWorkspace().keyboardAccessibilityMode = true;
Blockly.navigation.currentState_ = Blockly.navigation.STATE_WS; Blockly.navigation.currentState_ = Blockly.navigation.STATE_WS;
this.mockEvent = { this.mockEvent = {
@@ -407,24 +407,24 @@ suite('Navigation', function() {
test('Toggle Action Off', function() { test('Toggle Action Off', function() {
this.mockEvent.keyCode = 'Control75'; this.mockEvent.keyCode = 'Control75';
sinon.spy(Blockly.navigation, 'onBlocklyAction'); sinon.spy(Blockly.navigation, 'onBlocklyAction');
Blockly.keyboardAccessibilityMode = true; Blockly.getMainWorkspace().keyboardAccessibilityMode = true;
var isHandled = Blockly.navigation.onKeyPress(this.mockEvent); var isHandled = Blockly.navigation.onKeyPress(this.mockEvent);
chai.assert.isTrue(isHandled); chai.assert.isTrue(isHandled);
chai.assert.isTrue(Blockly.navigation.onBlocklyAction.calledOnce); chai.assert.isTrue(Blockly.navigation.onBlocklyAction.calledOnce);
chai.assert.isFalse(Blockly.keyboardAccessibilityMode); chai.assert.isFalse(Blockly.getMainWorkspace().keyboardAccessibilityMode);
Blockly.navigation.onBlocklyAction.restore(); Blockly.navigation.onBlocklyAction.restore();
}); });
test('Toggle Action On', function() { test('Toggle Action On', function() {
this.mockEvent.keyCode = 'Control75'; this.mockEvent.keyCode = 'Control75';
sinon.stub(Blockly.navigation, 'focusWorkspace_'); sinon.stub(Blockly.navigation, 'focusWorkspace_');
Blockly.keyboardAccessibilityMode = false; Blockly.getMainWorkspace().keyboardAccessibilityMode = false;
var isHandled = Blockly.navigation.onKeyPress(this.mockEvent); var isHandled = Blockly.navigation.onKeyPress(this.mockEvent);
chai.assert.isTrue(isHandled); chai.assert.isTrue(isHandled);
chai.assert.isTrue(Blockly.navigation.focusWorkspace_.calledOnce); chai.assert.isTrue(Blockly.navigation.focusWorkspace_.calledOnce);
chai.assert.isTrue(Blockly.keyboardAccessibilityMode); chai.assert.isTrue(Blockly.getMainWorkspace().keyboardAccessibilityMode);
Blockly.navigation.focusWorkspace_.restore(); Blockly.navigation.focusWorkspace_.restore();
this.workspace.dispose(); this.workspace.dispose();
}); });
@@ -459,7 +459,7 @@ suite('Navigation', function() {
this.workspace = new Blockly.Workspace({readOnly: true}); this.workspace = new Blockly.Workspace({readOnly: true});
this.workspace.setCursor(new Blockly.Cursor()); this.workspace.setCursor(new Blockly.Cursor());
Blockly.mainWorkspace = this.workspace; Blockly.mainWorkspace = this.workspace;
Blockly.keyboardAccessibilityMode = true; Blockly.getMainWorkspace().keyboardAccessibilityMode = true;
Blockly.navigation.currentState_ = Blockly.navigation.STATE_WS; Blockly.navigation.currentState_ = Blockly.navigation.STATE_WS;
this.fieldBlock1 = this.workspace.newBlock('field_block'); this.fieldBlock1 = this.workspace.newBlock('field_block');