Add accessibility interfaces and fix navigation types (#3908)

* Add accessibility interfaces and fix navigation types
This commit is contained in:
Sam El-Husseini
2020-05-21 11:18:10 -07:00
committed by GitHub
parent 3f4faeab46
commit 317834ff59
19 changed files with 287 additions and 114 deletions

View File

@@ -30,6 +30,8 @@ goog.require('Blockly.utils.object');
goog.require('Blockly.utils.string');
goog.require('Blockly.Workspace');
goog.requireType('Blockly.IASTNodeLocation');
/**
* Class for one block.
@@ -40,6 +42,7 @@ goog.require('Blockly.Workspace');
* @param {string=} opt_id Optional ID. Use this ID if provided, otherwise
* create a new ID.
* @constructor
* @implements {Blockly.IASTNodeLocation}
* @throws When block is not valid or block name is not allowed.
*/
Blockly.Block = function(workspace, prototypeName, opt_id) {

View File

@@ -32,9 +32,11 @@ goog.require('Blockly.utils.dom');
goog.require('Blockly.utils.object');
goog.require('Blockly.utils.Rect');
goog.requireType('Blockly.IASTNodeLocationSvg');
goog.requireType('Blockly.IBoundedElement');
goog.requireType('Blockly.ICopyable');
/**
* Class for a block's SVG representation.
* Not normally called directly, workspace.newBlock() is preferred.
@@ -44,6 +46,7 @@ goog.requireType('Blockly.ICopyable');
* @param {string=} opt_id Optional ID. Use this ID if provided, otherwise
* create a new ID.
* @extends {Blockly.Block}
* @implements {Blockly.IASTNodeLocationSvg}
* @implements {Blockly.IBoundedElement}
* @implements {Blockly.ICopyable}
* @constructor

View File

@@ -160,7 +160,7 @@ Blockly.svgResize = function(workspace) {
/**
* Handle a key-down on SVG drawing surface. Does nothing if the main workspace
* is not visible.
* @param {!Event} e Key down event.
* @param {!KeyboardEvent} e Key down event.
* @package
*/
// TODO (https://github.com/google/blockly/issues/1998) handle cases where there

View File

@@ -16,12 +16,15 @@ goog.require('Blockly.Events');
goog.require('Blockly.Events.BlockMove');
goog.require('Blockly.Xml');
goog.requireType('Blockly.IASTNodeLocationWithBlock');
/**
* Class for a connection between blocks.
* @param {!Blockly.Block} source The block establishing this connection.
* @param {number} type The type of the connection.
* @constructor
* @implements {Blockly.IASTNodeLocationWithBlock}
*/
Blockly.Connection = function(source, type) {
/**

View File

@@ -24,6 +24,9 @@ goog.require('Blockly.utils.style');
goog.require('Blockly.utils.userAgent');
goog.requireType('Blockly.blockRendering.ConstantProvider');
goog.requireType('Blockly.IASTNodeLocationSvg');
goog.requireType('Blockly.IASTNodeLocationWithBlock');
goog.requireType('Blockly.IBlocklyActionable');
/**
@@ -36,6 +39,9 @@ goog.requireType('Blockly.blockRendering.ConstantProvider');
* the individual field's documentation for a list of properties this
* parameter supports.
* @constructor
* @implements {Blockly.IASTNodeLocationSvg}
* @implements {Blockly.IASTNodeLocationWithBlock}
* @implements {Blockly.IBlocklyActionable}
*/
Blockly.Field = function(value, opt_validator, opt_config) {
/**
@@ -1105,7 +1111,8 @@ Blockly.Field.prototype.setMarkerSvg = function(markerSvg) {
* @protected
*/
Blockly.Field.prototype.updateMarkers_ = function() {
var workspace = this.sourceBlock_.workspace;
var workspace =
/** @type {!Blockly.WorkspaceSvg} */ (this.sourceBlock_.workspace);
if (workspace.keyboardAccessibilityMode && this.cursorSvg_) {
workspace.getCursor().draw();
}

View File

@@ -29,12 +29,15 @@ goog.require('Blockly.utils.dom');
goog.require('Blockly.WorkspaceSvg');
goog.require('Blockly.Xml');
goog.requireType('Blockly.IBlocklyActionable');
/**
* Class for a flyout.
* @param {!Blockly.Options} workspaceOptions Dictionary of options for the
* workspace.
* @constructor
* @implements {Blockly.IBlocklyActionable}
*/
Blockly.Flyout = function(workspaceOptions) {
workspaceOptions.getMetrics = this.getMetrics_.bind(this);

View File

@@ -0,0 +1,72 @@
/**
* @license
* Copyright 2020 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview AST Node and keyboard navigation interfaces.
* @author samelh@google.com (Sam El-Husseini)
*/
'use strict';
goog.provide('Blockly.IASTNodeLocation');
goog.provide('Blockly.IASTNodeLocationSvg');
goog.provide('Blockly.IASTNodeLocationWithBlock');
goog.provide('Blockly.IBlocklyActionable');
/**
* An AST node location interface.
* @interface
*/
Blockly.IASTNodeLocation = function() {};
/**
* An AST node location SVG interface.
* @interface
* @extends {Blockly.IASTNodeLocation}
*/
Blockly.IASTNodeLocationSvg = function() {};
/**
* Add the marker svg to this node's svg group.
* @param {SVGElement} markerSvg The svg root of the marker to be added to the
* svg group.
*/
Blockly.IASTNodeLocationSvg.prototype.setMarkerSvg;
/**
* Add the cursor svg to this node's svg group.
* @param {SVGElement} cursorSvg The svg root of the cursor to be added to the
* svg group.
*/
Blockly.IASTNodeLocationSvg.prototype.setCursorSvg;
/**
* An AST node location that has an associated block.
* @interface
* @extends {Blockly.IASTNodeLocation}
*/
Blockly.IASTNodeLocationWithBlock = function() {};
/**
* Get the source block associated with this node.
* @return {Blockly.Block} The source block.
*/
Blockly.IASTNodeLocationWithBlock.prototype.getSourceBlock;
/**
* An interface for an object that handles Blockly actions when keyboard
* navigation is enabled.
* @interface
*/
Blockly.IBlocklyActionable = function() {};
/**
* Handles the given action.
* @param {!Blockly.Action} action The action to be handled.
* @return {boolean} True if the action has been handled, false otherwise.
*/
Blockly.IBlocklyActionable.prototype.onBlocklyAction;

View File

@@ -14,6 +14,9 @@ goog.provide('Blockly.ASTNode');
goog.require('Blockly.utils.Coordinate');
goog.requireType('Blockly.IASTNodeLocation');
goog.requireType('Blockly.IASTNodeLocationWithBlock');
/**
* Class for an AST node.
@@ -21,9 +24,8 @@ goog.require('Blockly.utils.Coordinate');
* creating a node directly.
* @param {string} type The type of the location.
* Must be in Blockly.ASTNode.types.
* @param {!(Blockly.Block|Blockly.Connection|Blockly.Field|Blockly.Workspace)}
* location The position in the AST.
* @param {!Object=} opt_params Optional dictionary of options.
* @param {!Blockly.IASTNodeLocation} location The position in the AST.
* @param {!Blockly.ASTNode.Params=} opt_params Optional dictionary of options.
* @constructor
*/
Blockly.ASTNode = function(type, location, opt_params) {
@@ -48,14 +50,28 @@ Blockly.ASTNode = function(type, location, opt_params) {
/**
* The location of the AST node.
* @type {!(Blockly.Block|Blockly.Connection|Blockly.Field|Blockly.Workspace)}
* @type {!Blockly.IASTNodeLocation}
* @private
*/
this.location_ = location;
/**
* The coordinate on the workspace.
* @type {Blockly.utils.Coordinate}
* @private
*/
this.wsCoordinate_ = null;
this.processParams_(opt_params || null);
};
/**
* @typedef {{
* wsCoordinate: Blockly.utils.Coordinate,
* }}
*/
Blockly.ASTNode.Params;
/**
* Object holding different types for an AST node.
* @enum {string}
@@ -219,7 +235,7 @@ Blockly.ASTNode.createTopNode = function(block) {
/**
* Parse the optional parameters.
* @param {Object} params The user specified parameters.
* @param {?Blockly.ASTNode.Params} params The user specified parameters.
* @private
*/
Blockly.ASTNode.prototype.processParams_ = function(params) {
@@ -235,8 +251,8 @@ Blockly.ASTNode.prototype.processParams_ = function(params) {
* Gets the value pointed to by this node.
* It is the callers responsibility to check the node type to figure out what
* type of object they get back from this.
* @return {!(Blockly.Field|Blockly.Connection|Blockly.Block|Blockly.Workspace)}
* The current field, connection, workspace, or block the cursor is on.
* @return {!Blockly.IASTNodeLocation} The current field, connection, workspace, or
* block the cursor is on.
*/
Blockly.ASTNode.prototype.getLocation = function() {
return this.location_;
@@ -279,7 +295,8 @@ Blockly.ASTNode.prototype.isConnection = function() {
* @private
*/
Blockly.ASTNode.prototype.findNextForInput_ = function() {
var parentInput = this.location_.getParentInput();
var location = /** @type {!Blockly.Connection} */ (this.location_);
var parentInput = location.getParentInput();
var block = parentInput.getSourceBlock();
var curIdx = block.inputList.indexOf(parentInput);
for (var i = curIdx + 1, input; (input = block.inputList[i]); i++) {
@@ -335,11 +352,12 @@ Blockly.ASTNode.prototype.findNextForField_ = function() {
* @private
*/
Blockly.ASTNode.prototype.findPrevForInput_ = function() {
var location = this.location_.getParentInput();
var block = location.getSourceBlock();
var curIdx = block.inputList.indexOf(location);
var location = /** @type {!Blockly.Connection} */ (this.location_);
var parentInput = location.getParentInput();
var block = parentInput.getSourceBlock();
var curIdx = block.inputList.indexOf(parentInput);
for (var i = curIdx, input; (input = block.inputList[i]); i--) {
if (input.connection && input !== location) {
if (input.connection && input !== parentInput) {
return Blockly.ASTNode.createInputNode(input);
}
var fieldRow = input.fieldRow;
@@ -394,7 +412,8 @@ Blockly.ASTNode.prototype.findPrevForField_ = function() {
Blockly.ASTNode.prototype.navigateBetweenStacks_ = function(forward) {
var curLocation = this.getLocation();
if (!(curLocation instanceof Blockly.Block)) {
curLocation = curLocation.getSourceBlock();
curLocation = /** @type {!Blockly.IASTNodeLocationWithBlock} */ (
curLocation).getSourceBlock();
}
if (!curLocation || !curLocation.workspace) {
return null;
@@ -499,7 +518,8 @@ Blockly.ASTNode.prototype.getSourceBlock = function() {
} else if (this.getType() === Blockly.ASTNode.types.WORKSPACE) {
return null;
} else {
return this.getLocation().getSourceBlock();
return /** @type {Blockly.IASTNodeLocationWithBlock} */ (
this.getLocation()).getSourceBlock();
}
};
@@ -514,7 +534,8 @@ Blockly.ASTNode.prototype.next = function() {
return this.navigateBetweenStacks_(true);
case Blockly.ASTNode.types.OUTPUT:
return Blockly.ASTNode.createBlockNode(this.location_.getSourceBlock());
var connection = /** @type {!Blockly.Connection} */ (this.location_);
return Blockly.ASTNode.createBlockNode(connection.getSourceBlock());
case Blockly.ASTNode.types.FIELD:
return this.findNextForField_();
@@ -523,14 +544,17 @@ Blockly.ASTNode.prototype.next = function() {
return this.findNextForInput_();
case Blockly.ASTNode.types.BLOCK:
var nextConnection = this.location_.nextConnection;
var block = /** @type {!Blockly.Block} */ (this.location_);
var nextConnection = block.nextConnection;
return Blockly.ASTNode.createConnectionNode(nextConnection);
case Blockly.ASTNode.types.PREVIOUS:
return Blockly.ASTNode.createBlockNode(this.location_.getSourceBlock());
var connection = /** @type {!Blockly.Connection} */ (this.location_);
return Blockly.ASTNode.createBlockNode(connection.getSourceBlock());
case Blockly.ASTNode.types.NEXT:
var targetConnection = this.location_.targetConnection;
var connection = /** @type {!Blockly.Connection} */ (this.location_);
var targetConnection = connection.targetConnection;
return Blockly.ASTNode.createConnectionNode(targetConnection);
}
@@ -546,7 +570,8 @@ Blockly.ASTNode.prototype.next = function() {
Blockly.ASTNode.prototype.in = function() {
switch (this.type_) {
case Blockly.ASTNode.types.WORKSPACE:
var topBlocks = this.location_.getTopBlocks(true);
var workspace = /** @type {!Blockly.Workspace} */ (this.location_);
var topBlocks = workspace.getTopBlocks(true);
if (topBlocks.length > 0) {
return Blockly.ASTNode.createStackNode(topBlocks[0]);
}
@@ -561,7 +586,8 @@ Blockly.ASTNode.prototype.in = function() {
return this.findFirstFieldOrInput_(block);
case Blockly.ASTNode.types.INPUT:
var targetConnection = this.location_.targetConnection;
var connection = /** @type {!Blockly.Connection} */ (this.location_);
var targetConnection = connection.targetConnection;
return Blockly.ASTNode.createConnectionNode(targetConnection);
}
@@ -589,19 +615,21 @@ Blockly.ASTNode.prototype.prev = function() {
return this.findPrevForInput_();
case Blockly.ASTNode.types.BLOCK:
var block = this.location_;
var block = /** @type {!Blockly.Block} */ (this.location_);
var topConnection = block.previousConnection || block.outputConnection;
return Blockly.ASTNode.createConnectionNode(topConnection);
case Blockly.ASTNode.types.PREVIOUS:
var targetConnection = this.location_.targetConnection;
var connection = /** @type {!Blockly.Connection} */ (this.location_);
var targetConnection = connection.targetConnection;
if (targetConnection && !targetConnection.getParentInput()) {
return Blockly.ASTNode.createConnectionNode(targetConnection);
}
break;
case Blockly.ASTNode.types.NEXT:
return Blockly.ASTNode.createBlockNode(this.location_.getSourceBlock());
var connection = /** @type {!Blockly.Connection} */ (this.location_);
return Blockly.ASTNode.createBlockNode(connection.getSourceBlock());
}
return null;
@@ -616,35 +644,40 @@ Blockly.ASTNode.prototype.prev = function() {
Blockly.ASTNode.prototype.out = function() {
switch (this.type_) {
case Blockly.ASTNode.types.STACK:
var blockPos = this.location_.getRelativeToSurfaceXY();
var block = /** @type {!Blockly.Block} */ (this.location_);
var blockPos = block.getRelativeToSurfaceXY();
// TODO: Make sure this is in the bounds of the workspace.
var wsCoordinate = new Blockly.utils.Coordinate(
blockPos.x, blockPos.y + Blockly.ASTNode.DEFAULT_OFFSET_Y);
return Blockly.ASTNode.createWorkspaceNode(
this.location_.workspace, wsCoordinate);
return Blockly.ASTNode.createWorkspaceNode(block.workspace, wsCoordinate);
case Blockly.ASTNode.types.OUTPUT:
var target = this.location_.targetConnection;
var connection = /** @type {!Blockly.Connection} */ (this.location_);
var target = connection.targetConnection;
if (target) {
return Blockly.ASTNode.createConnectionNode(target);
}
return Blockly.ASTNode.createStackNode(this.location_.getSourceBlock());
return Blockly.ASTNode.createStackNode(connection.getSourceBlock());
case Blockly.ASTNode.types.FIELD:
return Blockly.ASTNode.createBlockNode(this.location_.getSourceBlock());
var field = /** @type {!Blockly.Field} */ (this.location_);
return Blockly.ASTNode.createBlockNode(field.getSourceBlock());
case Blockly.ASTNode.types.INPUT:
return Blockly.ASTNode.createBlockNode(this.location_.getSourceBlock());
var connection = /** @type {!Blockly.Connection} */ (this.location_);
return Blockly.ASTNode.createBlockNode(connection.getSourceBlock());
case Blockly.ASTNode.types.BLOCK:
var block = /** @type {!Blockly.Block} */ (this.location_);
return this.getOutAstNodeForBlock_(block);
case Blockly.ASTNode.types.PREVIOUS:
return this.getOutAstNodeForBlock_(this.location_.getSourceBlock());
var connection = /** @type {!Blockly.Connection} */ (this.location_);
return this.getOutAstNodeForBlock_(connection.getSourceBlock());
case Blockly.ASTNode.types.NEXT:
return this.getOutAstNodeForBlock_(this.location_.getSourceBlock());
var connection = /** @type {!Blockly.Connection} */ (this.location_);
return this.getOutAstNodeForBlock_(connection.getSourceBlock());
}
return null;

View File

@@ -19,12 +19,15 @@ goog.require('Blockly.Marker');
goog.require('Blockly.navigation');
goog.require('Blockly.utils.object');
goog.requireType('Blockly.IBlocklyActionable');
/**
* Class for a cursor.
* A cursor controls how a user navigates the Blockly AST.
* @constructor
* @extends {Blockly.Marker}
* @implements {Blockly.IBlocklyActionable}
*/
Blockly.Cursor = function() {
Blockly.Cursor.superClass_.constructor.call(this);
@@ -144,7 +147,8 @@ Blockly.Cursor.prototype.onBlocklyAction = function(action) {
// If we are on a field give it the option to handle the action
if (this.getCurNode() &&
this.getCurNode().getType() === Blockly.ASTNode.types.FIELD &&
this.getCurNode().getLocation().onBlocklyAction(action)) {
(/** @type {!Blockly.Field} */ (this.getCurNode().getLocation()))
.onBlocklyAction(action)) {
return true;
}
switch (action.name) {

View File

@@ -101,7 +101,7 @@ Blockly.user.keyMap.getKeyByAction = function(action) {
/**
* Serialize the key event.
* @param {!Event} e A key up event holding the key code.
* @param {!KeyboardEvent} e A key up event holding the key code.
* @return {string} A string containing the serialized key event.
* @package
*/

View File

@@ -102,10 +102,19 @@ Blockly.navigation.MARKER_NAME = 'local_marker_1';
/**
* Get the local marker.
* @return {!Blockly.Marker} The local marker for the main workspace.
* @return {Blockly.Marker} The local marker for the main workspace.
*/
Blockly.navigation.getMarker = function() {
return Blockly.getMainWorkspace().getMarker(Blockly.navigation.MARKER_NAME);
return Blockly.navigation.getNavigationWorkspace()
.getMarker(Blockly.navigation.MARKER_NAME);
};
/**
* Get the workspace that is being navigated.
* @return {!Blockly.WorkspaceSvg} The workspace being navigated.
*/
Blockly.navigation.getNavigationWorkspace = function() {
return /** @type {!Blockly.WorkspaceSvg} */ (Blockly.getMainWorkspace());
};
/**
@@ -114,8 +123,7 @@ Blockly.navigation.getMarker = function() {
* @private
*/
Blockly.navigation.focusToolbox_ = function() {
var workspace = Blockly.getMainWorkspace();
var toolbox = workspace.getToolbox();
var toolbox = Blockly.navigation.getNavigationWorkspace().getToolbox();
if (toolbox) {
Blockly.navigation.currentState_ = Blockly.navigation.STATE_TOOLBOX;
Blockly.navigation.resetFlyout_(false /* shouldHide */);
@@ -134,9 +142,9 @@ Blockly.navigation.focusToolbox_ = function() {
Blockly.navigation.focusFlyout_ = function() {
var topBlock = null;
Blockly.navigation.currentState_ = Blockly.navigation.STATE_FLYOUT;
var workspace = Blockly.getMainWorkspace();
var workspace = Blockly.navigation.getNavigationWorkspace();
var toolbox = workspace.getToolbox();
var flyout = toolbox ? toolbox.flyout_ : workspace.getFlyout();
var flyout = toolbox ? toolbox.getFlyout() : workspace.getFlyout();
if (!Blockly.navigation.getMarker().getCurNode()) {
Blockly.navigation.markAtCursor_();
@@ -159,7 +167,7 @@ Blockly.navigation.focusFlyout_ = function() {
*/
Blockly.navigation.focusWorkspace_ = function() {
Blockly.hideChaff();
var workspace = Blockly.getMainWorkspace();
var workspace = Blockly.navigation.getNavigationWorkspace();
var cursor = workspace.getCursor();
var reset = !!workspace.getToolbox();
var topBlocks = workspace.getTopBlocks(true);
@@ -186,14 +194,14 @@ Blockly.navigation.focusWorkspace_ = function() {
* @private
*/
Blockly.navigation.getFlyoutCursor_ = function() {
var workspace = Blockly.getMainWorkspace();
var workspace = Blockly.navigation.getNavigationWorkspace();
var cursor = null;
if (workspace.rendered) {
var toolbox = workspace.getToolbox();
var flyout = toolbox ? toolbox.flyout_ : workspace.getFlyout();
cursor = flyout ? flyout.workspace_.getCursor() : null;
var flyout = toolbox ? toolbox.getFlyout() : workspace.getFlyout();
cursor = flyout ? flyout.getWorkspace().getCursor() : null;
}
return cursor;
return /** @type {Blockly.FlyoutCursor} */ (cursor);
};
/**
@@ -202,7 +210,7 @@ Blockly.navigation.getFlyoutCursor_ = function() {
* it on the workspace.
*/
Blockly.navigation.insertFromFlyout = function() {
var workspace = Blockly.getMainWorkspace();
var workspace = Blockly.navigation.getNavigationWorkspace();
var flyout = workspace.getFlyout();
if (!flyout || !flyout.isVisible()) {
Blockly.navigation.warn_('Trying to insert from the flyout when the flyout does not ' +
@@ -210,7 +218,8 @@ Blockly.navigation.insertFromFlyout = function() {
return;
}
var curBlock = Blockly.navigation.getFlyoutCursor_().getCurNode().getLocation();
var curBlock = /** @type {!Blockly.BlockSvg} */ (
Blockly.navigation.getFlyoutCursor_().getCurNode().getLocation());
if (!curBlock.isEnabled()) {
Blockly.navigation.warn_('Can\'t insert a disabled block.');
return;
@@ -243,7 +252,7 @@ Blockly.navigation.resetFlyout_ = function(shouldHide) {
if (Blockly.navigation.getFlyoutCursor_()) {
Blockly.navigation.getFlyoutCursor_().hide();
if (shouldHide) {
Blockly.getMainWorkspace().getFlyout().hide();
Blockly.navigation.getNavigationWorkspace().getFlyout().hide();
}
}
};
@@ -260,7 +269,8 @@ Blockly.navigation.resetFlyout_ = function(shouldHide) {
*/
Blockly.navigation.modifyWarn_ = function() {
var markerNode = Blockly.navigation.getMarker().getCurNode();
var cursorNode = Blockly.getMainWorkspace().getCursor().getCurNode();
var cursorNode = Blockly.navigation.getNavigationWorkspace()
.getCursor().getCurNode();
if (!markerNode) {
Blockly.navigation.warn_('Cannot insert with no marked node.');
@@ -300,7 +310,7 @@ Blockly.navigation.modifyWarn_ = function() {
/**
* Disconnect the block from its parent and move to the position of the
* workspace node.
* @param {Blockly.Block} block The block to be moved to the workspace.
* @param {Blockly.BlockSvg} block The block to be moved to the workspace.
* @param {!Blockly.ASTNode} wsNode The workspace node holding the position the
* block will be moved to.
* @return {boolean} True if the block can be moved to the workspace,
@@ -331,7 +341,8 @@ Blockly.navigation.moveBlockToWorkspace_ = function(block, wsNode) {
*/
Blockly.navigation.modify_ = function() {
var markerNode = Blockly.navigation.getMarker().getCurNode();
var cursorNode = Blockly.getMainWorkspace().getCursor().getCurNode();
var cursorNode = Blockly.navigation.getNavigationWorkspace()
.getCursor().getCurNode();
if (!Blockly.navigation.modifyWarn_()) {
return false;
}
@@ -343,18 +354,19 @@ Blockly.navigation.modify_ = function() {
var markerLoc = markerNode.getLocation();
if (markerNode.isConnection() && cursorNode.isConnection()) {
cursorLoc = /** @type {!Blockly.Connection} */ (cursorLoc);
markerLoc = /** @type {!Blockly.Connection} */ (markerLoc);
cursorLoc = /** @type {!Blockly.RenderedConnection} */ (cursorLoc);
markerLoc = /** @type {!Blockly.RenderedConnection} */ (markerLoc);
return Blockly.navigation.connect_(cursorLoc, markerLoc);
} else if (markerNode.isConnection() &&
(cursorType == Blockly.ASTNode.types.BLOCK ||
cursorType == Blockly.ASTNode.types.STACK)) {
cursorLoc = /** @type {!Blockly.Block} */ (cursorLoc);
markerLoc = /** @type {!Blockly.Connection} */ (markerLoc);
cursorLoc = /** @type {!Blockly.BlockSvg} */ (cursorLoc);
markerLoc = /** @type {!Blockly.RenderedConnection} */ (markerLoc);
return Blockly.navigation.insertBlock(cursorLoc, markerLoc);
} else if (markerType == Blockly.ASTNode.types.WORKSPACE) {
var block = cursorNode ? cursorNode.getSourceBlock() : null;
return Blockly.navigation.moveBlockToWorkspace_(block, markerNode);
return Blockly.navigation.moveBlockToWorkspace_(
/** @type {Blockly.BlockSvg} */ (block), markerNode);
}
Blockly.navigation.warn_('Unexpected state in Blockly.navigation.modify_.');
return false;
@@ -363,9 +375,10 @@ Blockly.navigation.modify_ = function() {
/**
* If one of the connections source blocks is a child of the other, disconnect
* the child.
* @param {!Blockly.Connection} movingConnection The connection that is being
* moved.
* @param {!Blockly.Connection} destConnection The connection to be moved to.
* @param {!Blockly.RenderedConnection} movingConnection The connection that is
* being moved.
* @param {!Blockly.RenderedConnection} destConnection The connection to be
* moved to.
* @private
*/
Blockly.navigation.disconnectChild_ = function(movingConnection, destConnection) {
@@ -384,9 +397,10 @@ Blockly.navigation.disconnectChild_ = function(movingConnection, destConnection)
/**
* If the two blocks are compatible move the moving connection to the target
* connection and connect them.
* @param {Blockly.Connection} movingConnection The connection that is being
* moved.
* @param {Blockly.Connection} destConnection The connection to be moved to.
* @param {Blockly.RenderedConnection} movingConnection The connection that is
* being moved.
* @param {Blockly.RenderedConnection} destConnection The connection to be moved
* to.
* @return {boolean} True if the connections were connected, false otherwise.
* @private
*/
@@ -414,8 +428,10 @@ Blockly.navigation.moveAndConnect_ = function(movingConnection, destConnection)
/**
* If the given connection is superior find the inferior connection on the
* source block.
* @param {Blockly.Connection} connection The connection trying to be connected.
* @return {Blockly.Connection} The inferior connection or null if none exists.
* @param {Blockly.RenderedConnection} connection The connection trying to be
* connected.
* @return {Blockly.RenderedConnection} The inferior connection or null if none
* exists.
* @private
*/
Blockly.navigation.getInferiorConnection_ = function(connection) {
@@ -434,8 +450,10 @@ Blockly.navigation.getInferiorConnection_ = function(connection) {
/**
* If the given connection is inferior tries to find a superior connection to
* connect to.
* @param {Blockly.Connection} connection The connection trying to be connected.
* @return {Blockly.Connection} The superior connection or null if none exists.
* @param {Blockly.RenderedConnection} connection The connection trying to be
* connected.
* @return {Blockly.RenderedConnection} The superior connection or null if none
* exists.
* @private
*/
Blockly.navigation.getSuperiorConnection_ = function(connection) {
@@ -453,9 +471,10 @@ Blockly.navigation.getSuperiorConnection_ = function(connection) {
* If the given connections are not compatible try finding compatible connections
* on the source blocks of the given connections.
*
* @param {Blockly.Connection} movingConnection The connection that is being
* moved.
* @param {Blockly.Connection} destConnection The connection to be moved to.
* @param {Blockly.RenderedConnection} movingConnection The connection that is
* being moved.
* @param {Blockly.RenderedConnection} destConnection The connection to be moved
* to.
* @return {boolean} True if the two connections or their target connections
* were connected, false otherwise.
* @private
@@ -495,8 +514,9 @@ Blockly.navigation.connect_ = function(movingConnection, destConnection) {
/**
* Tries to connect the given block to the destination connection, making an
* intelligent guess about which connection to use to on the moving block.
* @param {!Blockly.Block} block The block to move.
* @param {!Blockly.Connection} destConnection The connection to connect to.
* @param {!Blockly.BlockSvg} block The block to move.
* @param {!Blockly.RenderedConnection} destConnection The connection to connect
* to.
* @return {boolean} Whether the connection was successful.
*/
Blockly.navigation.insertBlock = function(block, destConnection) {
@@ -518,7 +538,8 @@ Blockly.navigation.insertBlock = function(block, destConnection) {
break;
case Blockly.OUTPUT_VALUE:
for (var i = 0; i < block.inputList.length; i++) {
var inputConnection = block.inputList[i].connection;
var inputConnection = /** @type {Blockly.RenderedConnection} */ (
block.inputList[i].connection);
if (inputConnection && inputConnection.type === Blockly.INPUT_VALUE &&
Blockly.navigation.connect_(inputConnection, destConnection)) {
return true;
@@ -543,13 +564,14 @@ Blockly.navigation.insertBlock = function(block, destConnection) {
* @private
*/
Blockly.navigation.disconnectBlocks_ = function() {
var workspace = Blockly.getMainWorkspace();
var workspace = Blockly.navigation.getNavigationWorkspace();
var curNode = workspace.getCursor().getCurNode();
if (!curNode.isConnection()) {
Blockly.navigation.log_('Cannot disconnect blocks when the cursor is not on a connection');
return;
}
var curConnection = /** @type {!Blockly.Connection} */ (curNode.getLocation());
var curConnection =
/** @type {!Blockly.RenderedConnection} */ (curNode.getLocation());
if (!curConnection.isConnected()) {
Blockly.navigation.log_('Cannot disconnect unconnected connection');
return;
@@ -584,7 +606,7 @@ Blockly.navigation.disconnectBlocks_ = function() {
*/
Blockly.navigation.markAtCursor_ = function() {
Blockly.navigation.getMarker().setCurNode(
Blockly.getMainWorkspace().getCursor().getCurNode());
Blockly.navigation.getNavigationWorkspace().getCursor().getCurNode());
};
/**
@@ -608,10 +630,10 @@ Blockly.navigation.setState = function(newState) {
/**
* Before a block is deleted move the cursor to the appropriate position.
* @param {!Blockly.Block} deletedBlock The block that is being deleted.
* @param {!Blockly.BlockSvg} deletedBlock The block that is being deleted.
*/
Blockly.navigation.moveCursorOnBlockDelete = function(deletedBlock) {
var workspace = Blockly.getMainWorkspace();
var workspace = Blockly.navigation.getNavigationWorkspace();
if (!workspace) {
return;
}
@@ -645,11 +667,11 @@ Blockly.navigation.moveCursorOnBlockDelete = function(deletedBlock) {
/**
* When a block that the cursor is on is mutated move the cursor to the block
* level.
* @param {!Blockly.Block} mutatedBlock The block that is being mutated.
* @param {!Blockly.BlockSvg} mutatedBlock The block that is being mutated.
* @package
*/
Blockly.navigation.moveCursorOnBlockMutation = function(mutatedBlock) {
var cursor = Blockly.getMainWorkspace().getCursor();
var cursor = Blockly.navigation.getNavigationWorkspace().getCursor();
if (cursor) {
var curNode = cursor.getCurNode();
var block = curNode ? curNode.getSourceBlock() : null;
@@ -664,8 +686,9 @@ Blockly.navigation.moveCursorOnBlockMutation = function(mutatedBlock) {
* Enable accessibility mode.
*/
Blockly.navigation.enableKeyboardAccessibility = function() {
if (!Blockly.getMainWorkspace().keyboardAccessibilityMode) {
Blockly.getMainWorkspace().keyboardAccessibilityMode = true;
var workspace = Blockly.navigation.getNavigationWorkspace();
if (!workspace.keyboardAccessibilityMode) {
workspace.keyboardAccessibilityMode = true;
Blockly.navigation.focusWorkspace_();
}
};
@@ -674,9 +697,9 @@ Blockly.navigation.enableKeyboardAccessibility = function() {
* Disable accessibility mode.
*/
Blockly.navigation.disableKeyboardAccessibility = function() {
if (Blockly.getMainWorkspace().keyboardAccessibilityMode) {
var workspace = Blockly.getMainWorkspace();
Blockly.getMainWorkspace().keyboardAccessibilityMode = false;
var workspace = Blockly.navigation.getNavigationWorkspace();
if (workspace.keyboardAccessibilityMode) {
workspace.keyboardAccessibilityMode = false;
workspace.getCursor().hide();
Blockly.navigation.getMarker().hide();
if (Blockly.navigation.getFlyoutCursor_()) {
@@ -733,7 +756,7 @@ Blockly.navigation.error_ = function(msg) {
/**
* Handler for all the keyboard navigation events.
* @param {!Event} e The keyboard event.
* @param {!KeyboardEvent} e The keyboard event.
* @return {boolean} True if the key was handled false otherwise.
*/
Blockly.navigation.onKeyPress = function(e) {
@@ -753,10 +776,11 @@ Blockly.navigation.onKeyPress = function(e) {
* @return {boolean} True if the action has been handled, false otherwise.
*/
Blockly.navigation.onBlocklyAction = function(action) {
var readOnly = Blockly.getMainWorkspace().options.readOnly;
var workspace = Blockly.navigation.getNavigationWorkspace();
var readOnly = workspace.options.readOnly;
var actionHandled = false;
if (Blockly.getMainWorkspace().keyboardAccessibilityMode) {
if (workspace.keyboardAccessibilityMode) {
if (!readOnly) {
actionHandled = Blockly.navigation.handleActions_(action);
// If in readonly mode only handle valid actions.
@@ -799,9 +823,9 @@ Blockly.navigation.handleActions_ = function(action) {
* @private
*/
Blockly.navigation.flyoutOnAction_ = function(action) {
var workspace = Blockly.getMainWorkspace();
var workspace = Blockly.navigation.getNavigationWorkspace();
var toolbox = workspace.getToolbox();
var flyout = toolbox ? toolbox.flyout_ : workspace.getFlyout();
var flyout = toolbox ? toolbox.getFlyout() : workspace.getFlyout();
if (flyout && flyout.onBlocklyAction(action)) {
return true;
@@ -829,7 +853,7 @@ Blockly.navigation.flyoutOnAction_ = function(action) {
* @private
*/
Blockly.navigation.toolboxOnAction_ = function(action) {
var workspace = Blockly.getMainWorkspace();
var workspace = Blockly.navigation.getNavigationWorkspace();
var toolbox = workspace.getToolbox();
var handled = toolbox ? toolbox.onBlocklyAction(action) : false;
@@ -862,8 +886,9 @@ Blockly.navigation.toolboxOnAction_ = function(action) {
* @private
*/
Blockly.navigation.moveWSCursor_ = function(xDirection, yDirection) {
var cursor = Blockly.getMainWorkspace().getCursor();
var curNode = Blockly.getMainWorkspace().getCursor().getCurNode();
var workspace = Blockly.navigation.getNavigationWorkspace();
var cursor = workspace.getCursor();
var curNode = workspace.getCursor().getCurNode();
if (curNode.getType() !== Blockly.ASTNode.types.WORKSPACE) {
return false;
@@ -874,7 +899,7 @@ Blockly.navigation.moveWSCursor_ = function(xDirection, yDirection) {
var newY = yDirection * Blockly.navigation.WS_MOVE_DISTANCE + wsCoord.y;
cursor.setCurNode(Blockly.ASTNode.createWorkspaceNode(
Blockly.getMainWorkspace(), new Blockly.utils.Coordinate(newX, newY)));
workspace, new Blockly.utils.Coordinate(newX, newY)));
return true;
};
@@ -885,7 +910,8 @@ Blockly.navigation.moveWSCursor_ = function(xDirection, yDirection) {
* @private
*/
Blockly.navigation.workspaceOnAction_ = function(action) {
if (Blockly.getMainWorkspace().getCursor().onBlocklyAction(action)) {
var workspace = Blockly.navigation.getNavigationWorkspace();
if (workspace.getCursor().onBlocklyAction(action)) {
return true;
}
switch (action.name) {
@@ -916,11 +942,11 @@ Blockly.navigation.workspaceOnAction_ = function(action) {
* @private
*/
Blockly.navigation.handleEnterForWS_ = function() {
var cursor = Blockly.getMainWorkspace().getCursor();
var cursor = Blockly.navigation.getNavigationWorkspace().getCursor();
var curNode = cursor.getCurNode();
var nodeType = curNode.getType();
if (nodeType == Blockly.ASTNode.types.FIELD) {
curNode.getLocation().showEditor();
(/** @type {!Blockly.Field} */(curNode.getLocation())).showEditor();
} else if (curNode.isConnection() ||
nodeType == Blockly.ASTNode.types.WORKSPACE) {
Blockly.navigation.markAtCursor_();

View File

@@ -38,10 +38,9 @@ Blockly.TabNavigateCursor.prototype.validNode_ = function(node) {
var isValid = false;
var type = node && node.getType();
if (node) {
var location = node.getLocation();
var location = /** @type {Blockly.Field} */ (node.getLocation());
if (type == Blockly.ASTNode.types.FIELD &&
location && location.isTabNavigable() &&
(/** @type {!Blockly.Field} */ (location)).isClickable()) {
location && location.isTabNavigable() && location.isClickable()) {
isValid = true;
}
}

View File

@@ -378,7 +378,8 @@ Blockly.Mutator.prototype.workspaceChanged_ = function(e) {
block.initSvg();
block.render();
if (Blockly.getMainWorkspace().keyboardAccessibilityMode) {
if ((/** @type {!Blockly.WorkspaceSvg} */ (Blockly.getMainWorkspace()))
.keyboardAccessibilityMode) {
Blockly.navigation.moveCursorOnBlockMutation(block);
}
var newMutationDom = block.mutationToDom();

View File

@@ -59,6 +59,12 @@ Blockly.RenderedConnection = function(source, type) {
* @private
*/
this.trackedState_ = Blockly.RenderedConnection.TrackedState.WILL_TRACK;
/**
* Connection this connection connects to. Null if not connected.
* @type {Blockly.RenderedConnection}
*/
this.targetConnection = null;
};
Blockly.utils.object.inherits(Blockly.RenderedConnection, Blockly.Connection);

View File

@@ -42,7 +42,7 @@ Blockly.blockRendering.MarkerSvg = function(workspace, constants, marker) {
/**
* The workspace, field, or block that the marker SVG element should be
* attached to.
* @type {Blockly.WorkspaceSvg|Blockly.Field|Blockly.BlockSvg}
* @type {Blockly.IASTNodeLocationSvg}
* @private
*/
this.parent_ = null;
@@ -135,9 +135,8 @@ Blockly.blockRendering.MarkerSvg.prototype.createDom = function() {
/**
* Attaches the SVG root of the marker to the SVG group of the parent.
* @param {!Blockly.WorkspaceSvg|!Blockly.Field|!Blockly.BlockSvg} newParent
* The workspace, field, or block that the marker SVG element should be
* attached to.
* @param {!Blockly.IASTNodeLocationSvg} newParent The workspace, field, or
* block that the marker SVG element should be attached to.
* @protected
*/
Blockly.blockRendering.MarkerSvg.prototype.setParent_ = function(newParent) {
@@ -191,13 +190,15 @@ Blockly.blockRendering.MarkerSvg.prototype.draw = function(oldNode, curNode) {
* @protected
*/
Blockly.blockRendering.MarkerSvg.prototype.showAtLocation_ = function(curNode) {
var curNodeAsConnection =
/** @type {!Blockly.Connection} */ (curNode.getLocation());
if (curNode.getType() == Blockly.ASTNode.types.BLOCK) {
this.showWithBlock_(curNode);
} else if (curNode.getType() == Blockly.ASTNode.types.OUTPUT) {
this.showWithOutput_(curNode);
} else if (curNode.getLocation().type == Blockly.INPUT_VALUE) {
} else if (curNodeAsConnection.type == Blockly.INPUT_VALUE) {
this.showWithInput_(curNode);
} else if (curNode.getLocation().type == Blockly.NEXT_STATEMENT) {
} else if (curNodeAsConnection.type == Blockly.NEXT_STATEMENT) {
this.showWithNext_(curNode);
} else if (curNode.getType() == Blockly.ASTNode.types.PREVIOUS) {
this.showWithPrevious_(curNode);
@@ -330,8 +331,10 @@ Blockly.blockRendering.MarkerSvg.prototype.showWithInput_ = function(curNode) {
* @protected
*/
Blockly.blockRendering.MarkerSvg.prototype.showWithNext_ = function(curNode) {
var connection = curNode.getLocation();
var targetBlock = /** @type {Blockly.BlockSvg} */ (connection.getSourceBlock());
var connection =
/** @type {!Blockly.RenderedConnection} */ (curNode.getLocation());
var targetBlock =
/** @type {Blockly.BlockSvg} */ (connection.getSourceBlock());
var x = 0;
var y = connection.getOffsetInBlock().y;
var width = targetBlock.getHeightWidth().width;
@@ -548,7 +551,8 @@ Blockly.blockRendering.MarkerSvg.prototype.fireMarkerEvent_ = function(
var eventType = this.isCursor() ? 'cursorMove' : 'markerMove';
var event = new Blockly.Events.Ui(curBlock, eventType, oldNode, curNode);
if (curNode.getType() == Blockly.ASTNode.types.WORKSPACE) {
event.workspaceId = curNode.getLocation().id;
event.workspaceId =
(/** @type {!Blockly.Workspace} */ (curNode.getLocation())).id;
}
Blockly.Events.fire(event);
};

View File

@@ -38,7 +38,7 @@ Blockly.utils.object.inherits(Blockly.zelos.MarkerSvg,
*/
Blockly.zelos.MarkerSvg.prototype.showWithInputOutput_ = function(curNode) {
var block = /** @type {!Blockly.BlockSvg} */ (curNode.getSourceBlock());
var connection = curNode.getLocation();
var connection = /** @type {!Blockly.Connection} */ (curNode.getLocation());
var offsetInBlock = connection.getOffsetInBlock();
this.positionCircle_(offsetInBlock.x, offsetInBlock.y);

View File

@@ -27,6 +27,8 @@ goog.require('Blockly.utils.object');
goog.require('Blockly.utils.Rect');
goog.require('Blockly.utils.toolbox');
goog.requireType('Blockly.IBlocklyActionable');
/**
* Class for a Toolbox.
@@ -34,6 +36,7 @@ goog.require('Blockly.utils.toolbox');
* @param {!Blockly.WorkspaceSvg} workspace The workspace in which to create new
* blocks.
* @constructor
* @implements {Blockly.IBlocklyActionable}
*/
Blockly.Toolbox = function(workspace) {
/**

View File

@@ -18,12 +18,15 @@ goog.require('Blockly.utils');
goog.require('Blockly.utils.math');
goog.require('Blockly.VariableMap');
goog.requireType('Blockly.IASTNodeLocation');
/**
* Class for a workspace. This is a data structure that contains blocks.
* There is no UI, and can be created headlessly.
* @param {!Blockly.Options=} opt_options Dictionary of options.
* @constructor
* @implements {Blockly.IASTNodeLocation}
*/
Blockly.Workspace = function(opt_options) {
/** @type {string} */

View File

@@ -39,6 +39,7 @@ goog.require('Blockly.WorkspaceDragSurfaceSvg');
goog.require('Blockly.Xml');
goog.requireType('Blockly.blockRendering.Renderer');
goog.requireType('Blockly.IASTNodeLocationSvg');
goog.requireType('Blockly.IBoundedElement');
@@ -51,6 +52,7 @@ goog.requireType('Blockly.IBoundedElement');
* @param {Blockly.WorkspaceDragSurfaceSvg=} opt_wsDragSurface Drag surface for
* the workspace.
* @extends {Blockly.Workspace}
* @implements {Blockly.IASTNodeLocationSvg}
* @constructor
*/
Blockly.WorkspaceSvg = function(options,
@@ -1298,8 +1300,9 @@ Blockly.WorkspaceSvg.prototype.pasteBlock_ = function(xmlBlock) {
if (this.keyboardAccessibilityMode && markedNode &&
markedNode.isConnection()) {
var markedLocation =
/** @type {!Blockly.Connection} */ (markedNode.getLocation());
Blockly.navigation.insertBlock(block, markedLocation);
/** @type {!Blockly.RenderedConnection} */ (markedNode.getLocation());
Blockly.navigation.insertBlock(/** @type {!Blockly.BlockSvg} */ (block),
markedLocation);
return;
}