diff --git a/accessible/app.component.js b/accessible/app.component.js
index 2a8b75218..5de03e575 100644
--- a/accessible/app.component.js
+++ b/accessible/app.component.js
@@ -55,11 +55,13 @@ blocklyApp.AppView = ng.core
`,
directives: [blocklyApp.ToolboxComponent, blocklyApp.WorkspaceComponent],
pipes: [blocklyApp.TranslatePipe],
- // The clipboard and utils services are declared here, so that all
+ // The clipboard, tree and utils services are declared here, so that all
// components in the application use the same instance of the service.
// https://www.sitepoint.com/angular-2-components-providers-classes-factories-values/
- providers: [blocklyApp.ClipboardService, blocklyApp.UtilsService]
+ providers: [
+ blocklyApp.ClipboardService, blocklyApp.TreeService,
+ blocklyApp.UtilsService]
})
.Class({
- constructor: function() {}
+ constructor: [function() {}]
});
diff --git a/accessible/clipboard.service.js b/accessible/clipboard.service.js
index dee70dfc0..de9b382a3 100644
--- a/accessible/clipboard.service.js
+++ b/accessible/clipboard.service.js
@@ -25,7 +25,6 @@
blocklyApp.ClipboardService = ng.core
.Class({
constructor: function() {
- blocklyApp.debug && console.log('Clipboard service constructed');
this.clipboardBlockXml_ = null;
this.clipboardBlockSuperiorConnection_ = null;
this.clipboardBlockNextConnection_ = null;
@@ -35,7 +34,6 @@ blocklyApp.ClipboardService = ng.core
var blockSummary = block.toString();
this.copy(block, false);
block.dispose(true);
- blocklyApp.debug && console.log('cut');
alert(Blockly.Msg.CUT_BLOCK_MSG + blockSummary);
},
copy: function(block, announce) {
@@ -43,7 +41,6 @@ blocklyApp.ClipboardService = ng.core
this.clipboardBlockSuperiorConnection_ = block.outputConnection ||
block.previousConnection;
this.clipboardBlockNextConnection_ = block.nextConnection;
- blocklyApp.debug && console.log('copy');
if (announce) {
alert(Blockly.Msg.COPIED_BLOCK_MSG + block.toString());
}
@@ -61,7 +58,6 @@ blocklyApp.ClipboardService = ng.core
default:
connection.connect(reconstitutedBlock.outputConnection);
}
- blocklyApp.debug && console.log('paste');
alert(
Blockly.Msg.PASTED_BLOCK_FROM_CLIPBOARD_MSG +
reconstitutedBlock.toString());
@@ -73,7 +69,6 @@ blocklyApp.ClipboardService = ng.core
this.markedConnection_.connect(
reconstitutedBlock.outputConnection ||
reconstitutedBlock.previousConnection);
- blocklyApp.debug && console.log('paste to marked connection');
if (announce) {
alert(
Blockly.Msg.PASTED_BLOCK_TO_MARKED_SPOT_MSG +
@@ -82,7 +77,6 @@ blocklyApp.ClipboardService = ng.core
},
markConnection: function(connection) {
this.markedConnection_ = connection;
- blocklyApp.debug && console.log('mark connection');
alert(Blockly.Msg.MARKED_SPOT_MSG);
},
isCompatibleWithConnection_: function(blockConnection, connection) {
diff --git a/accessible/field.component.js b/accessible/field.component.js
index 6f5f06284..bb6b4cf07 100644
--- a/accessible/field.component.js
+++ b/accessible/field.component.js
@@ -128,7 +128,6 @@ blocklyApp.FieldComponent = ng.core
return;
}
if (this.field instanceof Blockly.FieldVariable) {
- blocklyApp.debug && console.log();
Blockly.FieldVariable.dropdownChange.call(this.field, text);
} else {
this.field.setValue(text);
diff --git a/accessible/toolbox-tree.component.js b/accessible/toolbox-tree.component.js
index 364cf586e..cc701fc35 100644
--- a/accessible/toolbox-tree.component.js
+++ b/accessible/toolbox-tree.component.js
@@ -103,7 +103,7 @@ blocklyApp.ToolboxTreeComponent = ng.core
return blocklyApp.ToolboxTreeComponent;
})],
inputs: [
- 'block', 'displayBlockMenu', 'level', 'index', 'tree', 'noCategories'],
+ 'block', 'displayBlockMenu', 'level', 'index', 'tree', 'noCategories', 'isTopLevel'],
pipes: [blocklyApp.TranslatePipe]
})
.Class({
@@ -128,23 +128,12 @@ blocklyApp.ToolboxTreeComponent = ng.core
elementsNeedingIds.push('listItem' + i, 'listItem' + i + 'Label')
}
this.idMap = this.utilsService.generateIds(elementsNeedingIds);
- if (this.index == 0 && this.noCategories) {
+ if (this.isTopLevel) {
this.idMap['parentList'] = 'blockly-toolbox-tree-node0';
} else {
this.idMap['parentList'] = this.utilsService.generateUniqueId();
}
},
- ngAfterViewInit: function() {
- // If this is a top-level tree in the toolbox, set its active
- // descendant after the ids have been computed.
- if (this.index == 0 &&
- this.tree.getAttribute('aria-activedescendant') ==
- 'blockly-toolbox-tree-node0') {
- this.treeService.setActiveDesc(
- document.getElementById(this.idMap['parentList']),
- this.tree);
- }
- },
generateAriaLabelledByAttr: function(mainLabel, secondLabel, isDisabled) {
return this.utilsService.generateAriaLabelledByAttr(
mainLabel, secondLabel, isDisabled);
diff --git a/accessible/toolbox.component.js b/accessible/toolbox.component.js
index f6e8009d1..589a73712 100644
--- a/accessible/toolbox.component.js
+++ b/accessible/toolbox.component.js
@@ -39,17 +39,17 @@ blocklyApp.ToolboxComponent = ng.core
[id]="idMap['Parent' + i]" role="treeitem"
[ngClass]="{blocklyHasChildren: true, blocklyActiveDescendant: tree.getAttribute('aria-activedescendant') == idMap['Parent' + i]}"
*ngFor="#category of toolboxCategories; #i=index"
- aria-level="1" aria-selected=false>
+ aria-level="1" aria-selected=false
+ [attr.aria-label]="category.attributes.name.value">
{{category.attributes.name.value}}
- {{labelCategory(name, i, tree)}}
0">
+ [tree]="tree">
@@ -59,15 +59,14 @@ blocklyApp.ToolboxComponent = ng.core
+ [noCategories]="true"
+ [isTopLevel]="true">
`,
- directives: [blocklyApp.ToolboxTreeComponent],
- providers: [blocklyApp.TreeService],
+ directives: [blocklyApp.ToolboxTreeComponent]
})
.Class({
constructor: [
@@ -95,23 +94,22 @@ blocklyApp.ToolboxComponent = ng.core
elementsNeedingIds.push('Parent' + i, 'Label' + i);
}
this.idMap = this.utilsService.generateIds(elementsNeedingIds);
- this.idMap['Parent0'] = 'blockly-toolbox-tree-node0';
+ for (var i = 0; i < this.toolboxCategories.length; i++) {
+ this.idMap['Parent' + i] = 'blockly-toolbox-tree-node' + i;
+ }
} else {
// Create a single category is created with all the top-level blocks.
this.xmlHasCategories = false;
this.toolboxCategories = [Array.from(xmlToolboxElt.children)];
}
},
- labelCategory: function(label, i, tree) {
- var parent = label.parentNode;
- while (parent && parent.tagName != 'LI') {
- parent = parent.parentNode;
- }
- parent.setAttribute('aria-label', label.innerText);
- parent.id = 'blockly-toolbox-tree-node' + i;
- if (i == 0 && tree.getAttribute('aria-activedescendant') ==
- 'blockly-toolbox-tree-node0') {
- this.treeService.setActiveDesc(parent, tree);
+ ngAfterViewInit: function() {
+ // If this is a top-level tree in the toolbox, set its active
+ // descendant after the ids have been computed.
+ if (this.xmlHasCategories) {
+ this.treeService.setActiveDesc(
+ document.getElementById('blockly-toolbox-tree-node0'),
+ document.getElementById('blockly-toolbox-tree'));
}
},
getToolboxWorkspace: function(categoryNode) {
diff --git a/accessible/tree.service.js b/accessible/tree.service.js
index 27f5aebfe..84ac80541 100644
--- a/accessible/tree.service.js
+++ b/accessible/tree.service.js
@@ -18,8 +18,8 @@
*/
/**
- * @fileoverview Angular2 Service that handles all tree keyboard navigation.
- * A separate TreeService is constructed for each tree in the application.
+ * @fileoverview Angular2 Service that handles tree keyboard navigation.
+ * This is a singleton service for the entire application.
*
* @author madeeha@google.com (Madeeha Ghori)
*/
@@ -27,100 +27,146 @@
blocklyApp.TreeService = ng.core
.Class({
constructor: function() {
- blocklyApp.debug && console.log('making a new tree service');
- this.trees = document.getElementsByClassName('blocklyTree');
// Keeping track of the last key pressed. If the user presses
// enter (to edit a text input or press a button), the keyboard
// focus shifts to that element. In the next keystroke, if the user
// navigates away from the element using the arrow keys, we want
// to shift focus back to the tree as a whole.
this.previousKey_ = null;
+ // Stores active descendant ids for each tree in the page.
+ this.activeDescendantIds_ = {};
},
- // Make a given node the active descendant of a given tree.
- setActiveDesc: function(node, tree, keepFocus) {
- blocklyApp.debug && console.log('setting activeDesc for tree ' + tree.id);
+ getToolboxTreeNode_: function() {
+ return document.getElementById('blockly-toolbox-tree');
+ },
+ getWorkspaceToolbarButtonNodes_: function() {
+ return Array.from(document.querySelectorAll(
+ 'button.blocklyWorkspaceToolbarButton'));
+ },
+ // Returns a list of all top-level workspace tree nodes on the page.
+ getWorkspaceTreeNodes_: function() {
+ return Array.from(document.querySelectorAll('ol.blocklyWorkspaceTree'));
+ },
+ // Returns a list of all top-level tree nodes on the page.
+ getAllTreeNodes_: function() {
+ var treeNodes = [this.getToolboxTreeNode_()];
+ treeNodes = treeNodes.concat(this.getWorkspaceToolbarButtonNodes_());
+ treeNodes = treeNodes.concat(this.getWorkspaceTreeNodes_());
+ return treeNodes;
+ },
+ isTopLevelWorkspaceTree: function(treeId) {
+ return this.getWorkspaceTreeNodes_().some(function(tree) {
+ return tree.id == treeId;
+ });
+ },
+ getNodeToFocusOnWhenTreeIsDeleted: function(deletedTreeId) {
+ // This returns the node to focus on after the deletion happens.
+ // We shift focus to the next tree (if it exists), otherwise we shift
+ // focus to the previous tree.
+ var trees = this.getAllTreeNodes_();
+ for (var i = 0; i < trees.length; i++) {
+ if (trees[i].id == deletedTreeId) {
+ if (i + 1 < trees.length) {
+ return trees[i + 1];
+ } else if (i > 0) {
+ return trees[i - 1];
+ }
+ }
+ }
- var activeDesc = this.getActiveDesc(tree.id);
+ return this.getToolboxTreeNode_();
+ },
+ focusOnNextTree_: function(treeId) {
+ var trees = this.getAllTreeNodes_();
+ for (var i = 0; i < trees.length - 1; i++) {
+ if (trees[i].id == treeId) {
+ trees[i + 1].focus();
+ return true;
+ }
+ }
+ return false;
+ },
+ focusOnPreviousTree_: function(treeId) {
+ var trees = this.getAllTreeNodes_();
+ for (var i = trees.length - 1; i > 0; i--) {
+ if (trees[i].id == treeId) {
+ trees[i - 1].focus();
+ return true;
+ }
+ }
+ return false;
+ },
+ getActiveDescId: function(treeId) {
+ return this.activeDescendantIds_[treeId] || '';
+ },
+ unmarkActiveDesc_: function(activeDescId) {
+ var activeDesc = document.getElementById(activeDescId);
if (activeDesc) {
activeDesc.classList.remove('blocklyActiveDescendant');
activeDesc.setAttribute('aria-selected', 'false');
}
-
- node.classList.add('blocklyActiveDescendant');
- node.setAttribute('aria-selected', 'true');
- tree.setAttribute('aria-activedescendant', node.id);
-
- // Make sure keyboard focus is on the entire tree in the case where the
- // focus was previously on a button or input element.
- if (keepFocus) {
- tree.focus();
- }
},
- getActiveDesc: function(treeId) {
- var activeDescendantId = document.getElementById(
- treeId).getAttribute('aria-activedescendant');
- return document.getElementById(activeDescendantId);
+ markActiveDesc_: function(activeDescId) {
+ var newActiveDesc = document.getElementById(activeDescId);
+ newActiveDesc.classList.add('blocklyActiveDescendant');
+ newActiveDesc.setAttribute('aria-selected', 'true');
+ },
+ // Runs the given function while preserving the focus and active descendant
+ // for the given tree.
+ runWhilePreservingFocus: function(func, treeId) {
+ var activeDescId = this.getActiveDescId(treeId);
+ this.unmarkActiveDesc_(activeDescId);
+ func();
+
+ // The timeout is needed in order to give the DOM time to stabilize
+ // before setting the new active descendant, especially in cases like
+ // pasteAbove().
+ var that = this;
+ setTimeout(function() {
+ that.markActiveDesc_(activeDescId);
+ that.activeDescendantIds_[treeId] = activeDescId;
+ document.getElementById(treeId).focus();
+ }, 0);
+ },
+ // Make a given node the active descendant of a given tree.
+ setActiveDesc: function(newActiveDesc, tree) {
+ this.unmarkActiveDesc_(this.getActiveDescId(tree.id));
+ this.markActiveDesc_(newActiveDesc.id);
+ this.activeDescendantIds_[tree.id] = newActiveDesc.id;
},
onWorkspaceToolbarKeypress: function(e, treeId) {
- blocklyApp.debug && console.log(e.keyCode + 'inside TreeService onWorkspaceToolbarKeypress');
switch (e.keyCode) {
case 9:
// 16,9: shift, tab
if (e.shiftKey) {
- blocklyApp.debug && console.log('shifttabbing');
// If the previous key is shift, we're shift-tabbing mode.
- this.goToPreviousTree(treeId);
+ this.focusOnPreviousTree_(treeId);
} else {
// If previous key isn't shift, we're tabbing.
- this.goToNextTree(treeId);
+ this.focusOnNextTree_(treeId);
}
e.preventDefault();
e.stopPropagation();
break;
}
},
- goToNextTree: function(treeId, e) {
- for (var i = 0; i < this.trees.length; i++) {
- if (this.trees[i].id == treeId) {
- if (i + 1 < this.trees.length) {
- this.trees[i + 1].focus();
- }
- break;
- }
- }
- },
- goToPreviousTree: function(treeId, e) {
- if (treeId == this.trees[0].id) {
- return;
- }
- for (var i = (this.trees.length - 1); i >= 0; i--) {
- if (this.trees[i].id == treeId) {
- if (i - 1 < this.trees.length) {
- this.trees[i - 1].focus();
- }
- break;
- }
- }
- },
onKeypress: function(e, tree) {
var treeId = tree.id;
- var node = this.getActiveDesc(treeId);
+ var node = document.getElementById(this.getActiveDescId(treeId));
var keepFocus = this.previousKey_ == 13;
if (!node) {
blocklyApp.debug && console.log('KeyHandler: no active descendant');
}
- blocklyApp.debug && console.log(e.keyCode + ': inside TreeService');
switch (e.keyCode) {
case 9:
// 16,9: shift, tab
if (e.shiftKey) {
- blocklyApp.debug && console.log('shifttabbing');
// If the previous key is shift, we're shift-tabbing.
- this.goToPreviousTree(treeId);
+ this.focusOnPreviousTree_(treeId);
} else {
// If previous key isn't shift, we're tabbing
// we want to go to the run code button.
- this.goToNextTree(treeId);
+ this.focusOnNextTree_(treeId);
}
// Setting the previous key variable in each case because
// we only want to save the previous navigation keystroke,
@@ -131,7 +177,6 @@ blocklyApp.TreeService = ng.core
break;
case 37:
// Left-facing arrow: go out a level, if possible. If not, do nothing.
- blocklyApp.debug && console.log('in left arrow section');
var nextNode = node.parentNode;
if (node.tagName == 'BUTTON' || node.tagName == 'INPUT') {
nextNode = nextNode.parentNode;
@@ -143,17 +188,16 @@ blocklyApp.TreeService = ng.core
if (!nextNode || nextNode.className == 'treeview') {
return;
}
- this.setActiveDesc(nextNode, tree, keepFocus);
+ this.setActiveDesc(nextNode, tree);
this.previousKey_ = e.keyCode;
e.preventDefault();
e.stopPropagation();
break;
case 38:
// Up-facing arrow: go up a level, if possible. If not, do nothing.
- blocklyApp.debug && console.log('node passed in: ' + node.id);
var prevSibling = this.getPreviousSibling(node);
if (prevSibling && prevSibling.tagName != 'H1') {
- this.setActiveDesc(prevSibling, tree, keepFocus);
+ this.setActiveDesc(prevSibling, tree);
} else {
blocklyApp.debug && console.log('no previous sibling');
}
@@ -162,10 +206,9 @@ blocklyApp.TreeService = ng.core
e.stopPropagation();
break;
case 39:
- blocklyApp.debug && console.log('in right arrow section');
var firstChild = this.getFirstChild(node);
if (firstChild) {
- this.setActiveDesc(firstChild, tree, keepFocus);
+ this.setActiveDesc(firstChild, tree);
} else {
blocklyApp.debug && console.log('no valid child');
}
@@ -176,10 +219,9 @@ blocklyApp.TreeService = ng.core
case 40:
// Down-facing arrow: go down a level, if possible.
// If not, do nothing.
- blocklyApp.debug && console.log('preventing propogation');
var nextSibling = this.getNextSibling(node);
if (nextSibling) {
- this.setActiveDesc(nextSibling, tree, keepFocus);
+ this.setActiveDesc(nextSibling, tree);
} else {
blocklyApp.debug && console.log('no next sibling');
}
@@ -189,8 +231,7 @@ blocklyApp.TreeService = ng.core
break;
case 13:
// If I've pressed enter, I want to interact with a child.
- blocklyApp.debug && console.log('enter is pressed');
- var activeDesc = this.getActiveDesc(treeId);
+ var activeDesc = node;
if (activeDesc) {
var children = activeDesc.children;
var child = children[0];
@@ -268,15 +309,12 @@ blocklyApp.TreeService = ng.core
} else {
var parent = element.parentNode;
while (parent) {
- blocklyApp.debug && console.log('looping');
if (parent.tagName == 'OL') {
break;
}
if (parent.previousElementSibling) {
- blocklyApp.debug && console.log('parent has a sibling!');
var node = parent.previousElementSibling;
if (node.tagName == 'LI') {
- blocklyApp.debug && console.log('return the sibling of the parent!');
return node;
} else {
// Find the last list element child of the sibling of the parent.
@@ -291,7 +329,6 @@ blocklyApp.TreeService = ng.core
},
getLastChild: function(element) {
if (!element) {
- blocklyApp.debug && console.log('no element');
return element;
} else {
var childList = element.children;
diff --git a/accessible/utils.service.js b/accessible/utils.service.js
index 7a1e0d273..5afd89698 100644
--- a/accessible/utils.service.js
+++ b/accessible/utils.service.js
@@ -29,9 +29,7 @@ var blocklyApp = {};
blocklyApp.UtilsService = ng.core
.Class({
- constructor: function() {
- blocklyApp.debug && console.log('Utils service constructed');
- },
+ constructor: function() {},
generateUniqueId: function() {
return 'blockly-' + Blockly.genUid();
},
diff --git a/accessible/workspace-tree.component.js b/accessible/workspace-tree.component.js
index 25cccc9b1..2fe99002c 100644
--- a/accessible/workspace-tree.component.js
+++ b/accessible/workspace-tree.component.js
@@ -41,7 +41,7 @@ blocklyApp.WorkspaceTreeComponent = ng.core
- {{'CUT_BLOCK'|translate}}
+ {{'CUT_BLOCK'|translate}}
-
{{'PASTE_BELOW'|translate}}
@@ -59,7 +59,7 @@ blocklyApp.WorkspaceTreeComponent = ng.core
-
{{'PASTE_ABOVE'|translate}}
@@ -81,7 +81,7 @@ blocklyApp.WorkspaceTreeComponent = ng.core
- {{'MOVE_TO_MARKED_SPOT'|translate}}
+ [block]="inputBlock.connection.targetBlock()" [level]="level"
+ [tree]="tree">
+ [level]="level" [tree]="tree">
`,
directives: [blocklyApp.FieldComponent, ng.core.forwardRef(function() {
return blocklyApp.WorkspaceTreeComponent;
})],
- inputs: ['block', 'isTopBlock', 'topBlockIndex', 'level', 'parentId', 'tree'],
- pipes: [blocklyApp.TranslatePipe],
- providers: [blocklyApp.TreeService],
+ inputs: ['block', 'level', 'tree', 'isTopLevel'],
+ pipes: [blocklyApp.TranslatePipe]
})
.Class({
constructor: [
@@ -144,13 +142,14 @@ blocklyApp.WorkspaceTreeComponent = ng.core
this.treeService = _treeService;
this.utilsService = _utilsService;
}],
- ngOnInit: function() {
+ getElementsNeedingIds_: function() {
var elementsNeedingIds = ['blockSummary', 'listItem', 'label',
'cutListItem', 'cutButton', 'copyListItem', 'copyButton',
'pasteBelow', 'pasteBelowButton', 'pasteAbove', 'pasteAboveButton',
'markBelow', 'markBelowButton', 'markAbove', 'markAboveButton',
'sendToSelectedListItem', 'sendToSelectedButton', 'delete',
'deleteButton'];
+
for (var i = 0; i < this.block.inputList.length; i++) {
var inputBlock = this.block.inputList[i];
if (inputBlock.connection && !inputBlock.connection.targetBlock()) {
@@ -159,53 +158,99 @@ blocklyApp.WorkspaceTreeComponent = ng.core
'markSpotButton' + i, 'paste' + i, 'pasteButton' + i]);
}
}
- this.idMap = this.utilsService.generateIds(elementsNeedingIds);
- this.idMap['parentList'] =
- this.isTopBlock ? this.parentId + '-node0' :
- this.utilsService.generateUniqueId();
+
+ return elementsNeedingIds;
+ },
+ ngOnInit: function() {
+ var elementsNeedingIds = this.getElementsNeedingIds_();
+
+ this.idMap = {}
+ this.idMap['parentList'] = this.utilsService.generateUniqueId();
+ for (var i = 0; i < elementsNeedingIds.length; i++) {
+ this.idMap[elementsNeedingIds[i]] =
+ this.block.id + elementsNeedingIds[i];
+ }
},
ngAfterViewInit: function() {
- // If this is a top-level tree in the workspace, set its active
- // descendant after the ids have been computed.
- if (this.tree &&
- this.tree.getAttribute('aria-activedescendant') ==
- this.idMap['parentList']) {
+ // If this is a top-level tree in the workspace, set its id and active
+ // descendant.
+ if (this.tree && this.isTopLevel && !this.tree.id) {
+ this.tree.id = this.utilsService.generateUniqueId();
+ }
+
+ if (this.tree && this.isTopLevel &&
+ !this.treeService.getActiveDescId(this.tree.id)) {
this.treeService.setActiveDesc(
document.getElementById(this.idMap['parentList']),
this.tree);
}
},
- isCompatibleWithClipboard: function(connection) {
- return this.clipboardService.isClipboardCompatibleWithConnection(
- connection);
- },
- deleteBlock: function(block) {
- // If this is the top block, shift focus to the previous tree.
- var topBlocks = blocklyApp.workspace.topBlocks_;
- for (var i = 0; i < topBlocks.length; i++) {
- if (topBlocks[i].id == block.id) {
- this.treeService.goToPreviousTree(this.parentId);
- break;
- }
- }
- // If this is not the top block, change the active descendant of the tree.
- block.dispose(true);
- },
- generateAriaLabelledByAttr: function(mainLabel, secondLabel, isDisabled) {
- return this.utilsService.generateAriaLabelledByAttr(
- mainLabel, secondLabel, isDisabled);
- },
hasPreviousConnection: function(block) {
return Boolean(block.previousConnection);
},
hasNextConnection: function(block) {
return Boolean(block.nextConnection);
},
- sendToSelected: function(block) {
- if (this.clipboardService) {
- this.clipboardService.pasteToMarkedConnection(block, false);
- block.dispose(true);
- alert('Block moved to marked spot: ' + block.toString());
+ isCompatibleWithClipboard: function(connection) {
+ return this.clipboardService.isClipboardCompatibleWithConnection(
+ connection);
+ },
+ isTopLevelBlock: function(block) {
+ return blocklyApp.workspace.topBlocks_.some(function(topBlock) {
+ return topBlock.id == block.id;
+ });
+ },
+ pasteAbove: function(block) {
+ var that = this;
+ this.treeService.runWhilePreservingFocus(function() {
+ that.clipboardService.pasteFromClipboard(block.previousConnection);
+ }, this.tree.id);
+ },
+ pasteBelow: function(block) {
+ var that = this;
+ this.treeService.runWhilePreservingFocus(function() {
+ that.clipboardService.pasteFromClipboard(block.nextConnection);
+ }, this.tree.id);
+ },
+ cutToClipboard: function(block) {
+ if (this.isTopLevelBlock(block)) {
+ nextNodeToFocusOn = this.treeService.getNodeToFocusOnWhenTreeIsDeleted(
+ this.tree.id);
+ this.clipboardService.cut(block);
+ nextNodeToFocusOn.focus();
+ } else {
+ // TODO(sll): Change the active descendant of the tree.
+ this.clipboardService.cut(block);
}
+ },
+ deleteBlock: function(block) {
+ if (this.isTopLevelBlock(block)) {
+ nextNodeToFocusOn = this.treeService.getNodeToFocusOnWhenTreeIsDeleted(
+ this.tree.id);
+ block.dispose(true);
+ nextNodeToFocusOn.focus();
+ } else {
+ // TODO(sll): Change the active descendant of the tree.
+ block.dispose(true);
+ }
+ },
+ generateAriaLabelledByAttr: function(mainLabel, secondLabel, isDisabled) {
+ return this.utilsService.generateAriaLabelledByAttr(
+ mainLabel, secondLabel, isDisabled);
+ },
+ sendToMarkedSpot: function(block) {
+ this.clipboardService.pasteToMarkedConnection(block, false);
+
+ if (this.isTopLevelBlock(block)) {
+ nextNodeToFocusOn = this.treeService.getNodeToFocusOnWhenTreeIsDeleted(
+ this.tree.id);
+ block.dispose(true);
+ nextNodeToFocusOn.focus();
+ } else {
+ // TODO(sll): Change the active descendant of the tree.
+ block.dispose(true);
+ }
+
+ alert('Block moved to marked spot: ' + block.toString());
}
});
diff --git a/accessible/workspace.component.js b/accessible/workspace.component.js
index c00cf7e5f..38e50fae5 100644
--- a/accessible/workspace.component.js
+++ b/accessible/workspace.component.js
@@ -33,31 +33,31 @@ blocklyApp.WorkspaceComponent = ng.core
-
+
{{buttonConfig.text}}
-
+
{{'CLEAR_WORKSPACE'|translate}}
`,
directives: [blocklyApp.WorkspaceTreeComponent],
- pipes: [blocklyApp.TranslatePipe],
- providers: [blocklyApp.TreeService]
+ pipes: [blocklyApp.TranslatePipe]
})
.Class({
constructor: [blocklyApp.TreeService, function(_treeService) {
@@ -72,14 +72,20 @@ blocklyApp.WorkspaceComponent = ng.core
this.workspace = blocklyApp.workspace;
this.treeService = _treeService;
}],
- onWorkspaceToolbarKeypress: function(event) {
- var activeElementId = document.activeElement.id;
- this.treeService.onWorkspaceToolbarKeypress(event, activeElementId);
+ clearWorkspace: function() {
+ this.workspace.clear();
},
- onKeypress: function(event, tree){
- this.treeService.onKeypress(event, tree);
+ getActiveDescId: function(tree) {
+ return this.treeService.getActiveDescId(tree.id);
+ },
+ onWorkspaceToolbarKeypress: function(e) {
+ this.treeService.onWorkspaceToolbarKeypress(
+ e, document.activeElement.id);
+ },
+ onKeypress: function(e, tree) {
+ this.treeService.onKeypress(e, tree);
},
isWorkspaceEmpty: function() {
- return !blocklyApp.workspace.topBlocks_.length;
+ return !this.workspace.topBlocks_.length;
}
});
diff --git a/blockly_compressed.js b/blockly_compressed.js
index 4bb4256ff..8fa47ec9c 100644
--- a/blockly_compressed.js
+++ b/blockly_compressed.js
@@ -912,8 +912,8 @@ Blockly.Connection.prototype.canConnectWithReason_=function(a){if(!a)return Bloc
Blockly.Connection.CAN_CONNECT:Blockly.Connection.REASON_CHECKS_FAILED};
Blockly.Connection.prototype.checkConnection_=function(a){switch(this.canConnectWithReason_(a)){case Blockly.Connection.CAN_CONNECT:break;case Blockly.Connection.REASON_SELF_CONNECTION:throw"Attempted to connect a block to itself.";case Blockly.Connection.REASON_DIFFERENT_WORKSPACES:throw"Blocks not on same workspace.";case Blockly.Connection.REASON_WRONG_TYPE:throw"Attempt to connect incompatible types.";case Blockly.Connection.REASON_TARGET_NULL:throw"Target connection is null.";case Blockly.Connection.REASON_CHECKS_FAILED:throw"Connection checks failed.";
case Blockly.Connection.REASON_SHADOW_PARENT:throw"Connecting non-shadow to shadow block.";default:throw"Unknown connection failure: this should never happen!";}};
-Blockly.Connection.prototype.isConnectionAllowed=function(a){if(this.canConnectWithReason_(a)!=Blockly.Connection.CAN_CONNECT)return!1;if(a.type==Blockly.OUTPUT_VALUE||a.type==Blockly.PREVIOUS_STATEMENT)if(a.isConnected()||this.isConnected())return!1;return a.type==Blockly.INPUT_VALUE&&a.isConnected()&&!a.targetBlock().isMovable()&&!a.targetBlock().isShadow()||this.type==Blockly.PREVIOUS_STATEMENT&&a.isConnected()&&!this.sourceBlock_.nextConnection||-1!=Blockly.draggingConnections_.indexOf(a)?!1:
-!0};Blockly.Connection.prototype.connect=function(a){this.targetConnection!=a&&(this.checkConnection_(a),this.isSuperior()?this.connect_(a):a.connect_(this))};Blockly.Connection.connectReciprocally_=function(a,b){goog.asserts.assert(a&&b,"Cannot connect null connections.");a.targetConnection=b;b.targetConnection=a};
+Blockly.Connection.prototype.isConnectionAllowed=function(a){if(this.canConnectWithReason_(a)!=Blockly.Connection.CAN_CONNECT)return!1;if(a.type==Blockly.OUTPUT_VALUE||a.type==Blockly.PREVIOUS_STATEMENT)if(a.isConnected()||this.isConnected())return!1;return a.type==Blockly.INPUT_VALUE&&a.isConnected()&&!a.targetBlock().isMovable()&&!a.targetBlock().isShadow()||this.type==Blockly.PREVIOUS_STATEMENT&&a.isConnected()&&!this.sourceBlock_.nextConnection&&!a.targetBlock().isShadow()&&a.targetBlock().nextConnection||
+-1!=Blockly.draggingConnections_.indexOf(a)?!1:!0};Blockly.Connection.prototype.connect=function(a){this.targetConnection!=a&&(this.checkConnection_(a),this.isSuperior()?this.connect_(a):a.connect_(this))};Blockly.Connection.connectReciprocally_=function(a,b){goog.asserts.assert(a&&b,"Cannot connect null connections.");a.targetConnection=b;b.targetConnection=a};
Blockly.Connection.singleConnection_=function(a,b){for(var c=!1,d=0;dthis.lidOpen_&&(this.lidTask_=goog.Timer.callOnce(this.animateLid_,20,this))};
Blockly.Trashcan.prototype.close=function(){this.setOpen_(!1)};Blockly.Trashcan.prototype.click=function(){var a=this.workspace_.startScrollX-this.workspace_.scrollX,b=this.workspace_.startScrollY-this.workspace_.scrollY;Math.sqrt(a*a+b*b)>Blockly.DRAG_RADIUS||console.log("TODO: Inspect trash.")};Blockly.Xml={};Blockly.Xml.workspaceToDom=function(a){var b=goog.dom.createDom("xml");a=a.getTopBlocks(!0);for(var c=0,d;d=a[c];c++)b.appendChild(Blockly.Xml.blockToDomWithXY(d));return b};Blockly.Xml.blockToDomWithXY=function(a){var b;a.workspace.RTL&&(b=a.workspace.getWidth());var c=Blockly.Xml.blockToDom(a),d=a.getRelativeToSurfaceXY();c.setAttribute("x",Math.round(a.workspace.RTL?b-d.x:d.x));c.setAttribute("y",Math.round(d.y));return c};
Blockly.Xml.blockToDom=function(a){var b=goog.dom.createDom(a.isShadow()?"shadow":"block");b.setAttribute("type",a.type);b.setAttribute("id",a.id);if(a.mutationToDom){var c=a.mutationToDom();c&&(c.hasChildNodes()||c.hasAttributes())&&b.appendChild(c)}for(var c=0,d;d=a.inputList[c];c++)for(var e=0,f;f=d.fieldRow[e];e++)if(f.name&&f.EDITABLE){var g=goog.dom.createDom("field",null,f.getValue());g.setAttribute("name",f.name);b.appendChild(g)}if(c=a.getCommentText())c=goog.dom.createDom("comment",null,
@@ -1019,12 +1018,12 @@ Blockly.WorkspaceSvg=function(a){Blockly.WorkspaceSvg.superClass_.constructor.ca
Blockly.WorkspaceSvg.prototype.scrollY=0;Blockly.WorkspaceSvg.prototype.startScrollX=0;Blockly.WorkspaceSvg.prototype.startScrollY=0;Blockly.WorkspaceSvg.prototype.dragDeltaXY_=null;Blockly.WorkspaceSvg.prototype.scale=1;Blockly.WorkspaceSvg.prototype.trashcan=null;Blockly.WorkspaceSvg.prototype.scrollbar=null;Blockly.WorkspaceSvg.prototype.lastSound_=null;Blockly.WorkspaceSvg.prototype.setResizeHandlerWrapper=function(a){this.resizeHandlerWrapper_=a};
Blockly.WorkspaceSvg.prototype.createDom=function(a){this.svgGroup_=Blockly.createSvgElement("g",{"class":"blocklyWorkspace"},null);a&&(this.svgBackground_=Blockly.createSvgElement("rect",{height:"100%",width:"100%","class":a},this.svgGroup_),"blocklyMainBackground"==a&&(this.svgBackground_.style.fill="url(#"+this.options.gridPattern.id+")"));this.svgBlockCanvas_=Blockly.createSvgElement("g",{"class":"blocklyBlockCanvas"},this.svgGroup_,this);this.svgBubbleCanvas_=Blockly.createSvgElement("g",{"class":"blocklyBubbleCanvas"},
this.svgGroup_,this);a=Blockly.Scrollbar.scrollbarThickness;this.options.hasTrashcan&&(a=this.addTrashcan_(a));this.options.zoomOptions&&this.options.zoomOptions.controls&&(a=this.addZoomControls_(a));Blockly.bindEvent_(this.svgGroup_,"mousedown",this,this.onMouseDown_);var b=this;Blockly.bindEvent_(this.svgGroup_,"touchstart",null,function(a){Blockly.longStart_(a,b)});this.options.zoomOptions&&this.options.zoomOptions.wheel&&Blockly.bindEvent_(this.svgGroup_,"wheel",this,this.onMouseWheel_);this.options.hasCategories?
-this.toolbox_=new Blockly.Toolbox(this):this.options.languageTree&&this.addFlyout_();this.updateGridPattern_();return this.svgGroup_};
+this.toolbox_=new Blockly.Toolbox(this):this.options.languageTree&&this.addFlyout_();this.updateGridPattern_();this.recordDeleteAreas();return this.svgGroup_};
Blockly.WorkspaceSvg.prototype.dispose=function(){this.rendered=!1;Blockly.WorkspaceSvg.superClass_.dispose.call(this);this.svgGroup_&&(goog.dom.removeNode(this.svgGroup_),this.svgGroup_=null);this.svgBubbleCanvas_=this.svgBlockCanvas_=null;this.toolbox_&&(this.toolbox_.dispose(),this.toolbox_=null);this.flyout_&&(this.flyout_.dispose(),this.flyout_=null);this.trashcan&&(this.trashcan.dispose(),this.trashcan=null);this.scrollbar&&(this.scrollbar.dispose(),this.scrollbar=null);this.zoomControls_&&
(this.zoomControls_.dispose(),this.zoomControls_=null);this.options.parentWorkspace||goog.dom.removeNode(this.getParentSvg());this.resizeHandlerWrapper_&&(Blockly.unbindEvent_(this.resizeHandlerWrapper_),this.resizeHandlerWrapper_=null)};Blockly.WorkspaceSvg.prototype.newBlock=function(a,b){return new Blockly.BlockSvg(this,a,b)};
Blockly.WorkspaceSvg.prototype.addTrashcan_=function(a){this.trashcan=new Blockly.Trashcan(this);var b=this.trashcan.createDom();this.svgGroup_.insertBefore(b,this.svgBlockCanvas_);return this.trashcan.init(a)};Blockly.WorkspaceSvg.prototype.addZoomControls_=function(a){this.zoomControls_=new Blockly.ZoomControls(this);var b=this.zoomControls_.createDom();this.svgGroup_.appendChild(b);return this.zoomControls_.init(a)};
Blockly.WorkspaceSvg.prototype.addFlyout_=function(){this.flyout_=new Blockly.Flyout({disabledPatternId:this.options.disabledPatternId,parentWorkspace:this,RTL:this.RTL,horizontalLayout:this.horizontalLayout,toolboxPosition:this.options.toolboxPosition});this.flyout_.autoClose=!1;var a=this.flyout_.createDom();this.svgGroup_.insertBefore(a,this.svgBlockCanvas_)};Blockly.WorkspaceSvg.prototype.resizeContents=function(){this.scrollbar&&this.scrollbar.resize()};
-Blockly.WorkspaceSvg.prototype.resize=function(){this.toolbox_&&this.toolbox_.position();this.flyout_&&this.flyout_.position();this.trashcan&&this.trashcan.position();this.zoomControls_&&this.zoomControls_.position();this.scrollbar&&this.scrollbar.resize()};Blockly.WorkspaceSvg.prototype.getCanvas=function(){return this.svgBlockCanvas_};Blockly.WorkspaceSvg.prototype.getBubbleCanvas=function(){return this.svgBubbleCanvas_};
+Blockly.WorkspaceSvg.prototype.resize=function(){this.toolbox_&&this.toolbox_.position();this.flyout_&&this.flyout_.position();this.trashcan&&this.trashcan.position();this.zoomControls_&&this.zoomControls_.position();this.scrollbar&&this.scrollbar.resize();this.recordDeleteAreas()};Blockly.WorkspaceSvg.prototype.getCanvas=function(){return this.svgBlockCanvas_};Blockly.WorkspaceSvg.prototype.getBubbleCanvas=function(){return this.svgBubbleCanvas_};
Blockly.WorkspaceSvg.prototype.getParentSvg=function(){if(this.cachedParentSvg_)return this.cachedParentSvg_;for(var a=this.svgGroup_;a;){if("svg"==a.tagName)return this.cachedParentSvg_=a;a=a.parentNode}return null};Blockly.WorkspaceSvg.prototype.translate=function(a,b){var c="translate("+a+","+b+") scale("+this.scale+")";this.svgBlockCanvas_.setAttribute("transform",c);this.svgBubbleCanvas_.setAttribute("transform",c)};
Blockly.WorkspaceSvg.prototype.getWidth=function(){var a=this.getMetrics();return a?a.viewWidth/this.scale:0};Blockly.WorkspaceSvg.prototype.setVisible=function(a){this.getParentSvg().style.display=a?"block":"none";this.toolbox_&&(this.toolbox_.HtmlDiv.style.display=a?"block":"none");a?(this.render(),this.toolbox_&&this.toolbox_.position()):Blockly.hideChaff(!0)};Blockly.WorkspaceSvg.prototype.render=function(){for(var a=this.getAllBlocks(),b=a.length-1;0<=b;b--)a[b].render(!1)};
Blockly.WorkspaceSvg.prototype.traceOn=function(a){this.traceOn_=a;this.traceWrapper_&&(Blockly.unbindEvent_(this.traceWrapper_),this.traceWrapper_=null);a&&(this.traceWrapper_=Blockly.bindEvent_(this.svgBlockCanvas_,"blocklySelectChange",this,function(){this.traceOn_=!1}))};
@@ -1101,7 +1100,7 @@ void 0!==a.nextStatement&&this.setNextStatement(!0,a.nextStatement);void 0!==a.t
Blockly.Block.prototype.interpolate_=function(a,b,c){var d=Blockly.tokenizeInterpolation(a),e=[],f=0;a=[];for(var g=0;g=b.height&&(k-=f.height);c?f.width>=a.clientX&&(g+=f.width):a.clientX+f.width>=b.width&&(g-=f.width);Blockly.WidgetDiv.position(g,k,b,e,c);d.setAllowAutoFocus(!0);setTimeout(function(){h.focus()},1);Blockly.ContextMenu.currentBlock=null}else Blockly.ContextMenu.hide()};
Blockly.ContextMenu.hide=function(){Blockly.WidgetDiv.hideIfOwner(Blockly.ContextMenu);Blockly.ContextMenu.currentBlock=null};
Blockly.ContextMenu.callbackFactory=function(a,b){return function(){Blockly.Events.disable();var c=Blockly.Xml.domToBlock(b,a.workspace),d=a.getRelativeToSurfaceXY();d.x=a.RTL?d.x-Blockly.SNAP_RADIUS:d.x+Blockly.SNAP_RADIUS;d.y+=2*Blockly.SNAP_RADIUS;c.moveBy(d.x,d.y);Blockly.Events.enable();Blockly.Events.isEnabled()&&!c.isShadow()&&Blockly.Events.fire(new Blockly.Events.Create(c));c.select()}};Blockly.RenderedConnection=function(a,b){Blockly.RenderedConnection.superClass_.constructor.call(this,a,b);this.offsetInBlock_=new goog.math.Coordinate(0,0)};goog.inherits(Blockly.RenderedConnection,Blockly.Connection);Blockly.RenderedConnection.prototype.distanceFrom=function(a){var b=this.x_-a.x_;a=this.y_-a.y_;return Math.sqrt(b*b+a*a)};
-Blockly.RenderedConnection.prototype.bumpAwayFrom_=function(a){if(Blockly.dragMode_==Blockly.DRAG_NONE){var b=this.sourceBlock_.getRootBlock();if(!b.isInFlyout){var c=!1;if(!b.isMovable()){b=a.getSourceBlock().getRootBlock();if(!b.isMovable())return;a=this;c=!0}b.getSvgRoot().parentNode.appendChild(b.getSvgRoot());var d=a.x_+Blockly.SNAP_RADIUS-this.x_;a=a.y_+Blockly.SNAP_RADIUS-this.y_;c&&(a=-a);b.RTL&&(d=-d);b.moveBy(d,a)}}};
+Blockly.RenderedConnection.prototype.bumpAwayFrom_=function(a){if(Blockly.dragMode_==Blockly.DRAG_NONE){var b=this.sourceBlock_.getRootBlock();if(!b.isInFlyout){var c=!1;if(!b.isMovable()){b=a.getSourceBlock().getRootBlock();if(!b.isMovable())return;a=this;c=!0}var d=Blockly.selected==b;d||b.select();var e=a.x_+Blockly.SNAP_RADIUS-this.x_;a=a.y_+Blockly.SNAP_RADIUS-this.y_;c&&(a=-a);b.RTL&&(e=-e);b.moveBy(e,a);d||b.unselect()}}};
Blockly.RenderedConnection.prototype.moveTo=function(a,b){this.inDB_&&this.db_.removeConnection_(this);this.x_=a;this.y_=b;this.hidden_||this.db_.addConnection(this)};Blockly.RenderedConnection.prototype.moveBy=function(a,b){this.moveTo(this.x_+a,this.y_+b)};Blockly.RenderedConnection.prototype.moveToOffset=function(a){this.moveTo(a.x+this.offsetInBlock_.x,a.y+this.offsetInBlock_.y)};
Blockly.RenderedConnection.prototype.setOffsetInBlock=function(a,b){this.offsetInBlock_.x=a;this.offsetInBlock_.y=b};Blockly.RenderedConnection.prototype.tighten_=function(){var a=this.targetConnection.x_-this.x_,b=this.targetConnection.y_-this.y_;if(0!=a||0!=b){var c=this.targetBlock(),d=c.getSvgRoot();if(!d)throw"block is not rendered.";d=Blockly.getRelativeXY_(d);c.getSvgRoot().setAttribute("transform","translate("+(d.x-a)+","+(d.y-b)+")");c.moveConnections_(-a,-b)}};
Blockly.RenderedConnection.prototype.closest=function(a,b,c){return this.dbOpposite_.searchForClosest(this,a,b,c)};
@@ -1139,10 +1138,10 @@ Blockly.BlockSvg.prototype.getHeightWidth=function(){var a=this.height,b=this.wi
Blockly.BlockSvg.prototype.getBoundingRectangle=function(){var a=this.getRelativeToSurfaceXY(this),b=this.outputConnection?Blockly.BlockSvg.TAB_WIDTH:0,c=this.getHeightWidth(),d;this.RTL?(d=new goog.math.Coordinate(a.x-(c.width-b),a.y),a=new goog.math.Coordinate(a.x+b,a.y+c.height)):(d=new goog.math.Coordinate(a.x-b,a.y),a=new goog.math.Coordinate(a.x+c.width-b,a.y+c.height));return{topLeft:d,bottomRight:a}};
Blockly.BlockSvg.prototype.setCollapsed=function(a){if(this.collapsed_!=a){for(var b=[],c=0,d;d=this.inputList[c];c++)b.push.apply(b,d.setVisible(!a));if(a){d=this.getIcons();for(c=0;cthis.workspace.remainingCapacity()&&(d.enabled=!1);c.push(d);this.isEditable()&&!this.collapsed_&&this.workspace.options.comments&&(d={enabled:!goog.userAgent.IE},this.comment?(d.text=Blockly.Msg.REMOVE_COMMENT,
d.callback=function(){b.setCommentText(null)}):(d.text=Blockly.Msg.ADD_COMMENT,d.callback=function(){b.setCommentText("")}),c.push(d));if(!this.collapsed_)for(d=1;dthis.workspace.scale)){var a=Blockly.getSvgXY_(this.svgGroup_,this.workspace);this.outputConnection?(a.x+=(this.RTL?3:-3)*this.workspace.scale,a.y+=13*this.workspace.scale):this.previousConnection&&(a.x+=(this.RTL?-23:23)*this.workspace.scale,a.y+=3*this.workspace.scale);a=Blockly.createSvgElement("circle",{cx:a.x,cy:a.y,r:0,fill:"none",stroke:"#888","stroke-width":10},this.workspace.getParentSvg());
@@ -1223,15 +1222,16 @@ Blockly.Events.Delete=function(a){if(a){if(a.getParent())throw"Connected blocks
Blockly.Events.Delete.prototype.fromJson=function(a){Blockly.Events.Delete.superClass_.fromJson.call(this,a);this.ids=a.ids};Blockly.Events.Delete.prototype.run=function(a){var b=Blockly.Workspace.getById(this.workspaceId);if(a){a=0;for(var c;c=this.ids[a];a++){var d=b.getBlockById(c);d?d.dispose(!1,!1):c==this.blockId&&console.warn("Can't delete non-existant block: "+c)}}else a=goog.dom.createDom("xml"),a.appendChild(this.oldXml),Blockly.Xml.domToWorkspace(a,b)};
Blockly.Events.Change=function(a,b,c,d,e){a&&(Blockly.Events.Change.superClass_.constructor.call(this,a),this.element=b,this.name=c,this.oldValue=d,this.newValue=e)};goog.inherits(Blockly.Events.Change,Blockly.Events.Abstract);Blockly.Events.Change.prototype.type=Blockly.Events.CHANGE;Blockly.Events.Change.prototype.toJson=function(){var a=Blockly.Events.Change.superClass_.toJson.call(this);a.element=this.element;this.name&&(a.name=this.name);a.newValue=this.newValue;return a};
Blockly.Events.Change.prototype.fromJson=function(a){Blockly.Events.Change.superClass_.fromJson.call(this,a);this.element=a.element;this.name=a.name;this.newValue=a.newValue};Blockly.Events.Change.prototype.isNull=function(){return this.oldValue==this.newValue};
-Blockly.Events.Change.prototype.run=function(a){var b=Blockly.Workspace.getById(this.workspaceId).getBlockById(this.blockId);if(b)switch(b.mutator&&b.mutator.setVisible(!1),a=a?this.newValue:this.oldValue,this.element){case "field":(b=b.getField(this.name))?b.setValue(a):console.warn("Can't set non-existant field: "+this.name);break;case "comment":b.setCommentText(a||null);break;case "collapsed":b.setCollapsed(a);break;case "disabled":b.setDisabled(a);break;case "inline":b.setInputsInline(a);break;
-case "mutation":var c="";b.mutationToDom&&(c=(c=b.mutationToDom())&&Blockly.Xml.domToText(c));if(b.domToMutation){a=a||" ";var d=Blockly.Xml.textToDom(""+a+" ");b.domToMutation(d.firstChild)}Blockly.Events.fire(new Blockly.Events.Change(b,"mutation",null,c,a));break;default:console.warn("Unknown change type: "+this.element)}else console.warn("Can't change non-existant block: "+this.blockId)};
+Blockly.Events.Change.prototype.run=function(a){var b=Blockly.Workspace.getById(this.workspaceId).getBlockById(this.blockId);if(b)switch(b.mutator&&b.mutator.setVisible(!1),a=a?this.newValue:this.oldValue,this.element){case "field":if(b=b.getField(this.name)){var c=b.getValidator();c&&c.call(b,a);b.setValue(a)}else console.warn("Can't set non-existant field: "+this.name);break;case "comment":b.setCommentText(a||null);break;case "collapsed":b.setCollapsed(a);break;case "disabled":b.setDisabled(a);
+break;case "inline":b.setInputsInline(a);break;case "mutation":c="";b.mutationToDom&&(c=(c=b.mutationToDom())&&Blockly.Xml.domToText(c));if(b.domToMutation){a=a||" ";var d=Blockly.Xml.textToDom(""+a+" ");b.domToMutation(d.firstChild)}Blockly.Events.fire(new Blockly.Events.Change(b,"mutation",null,c,a));break;default:console.warn("Unknown change type: "+this.element)}else console.warn("Can't change non-existant block: "+this.blockId)};
Blockly.Events.Move=function(a){a&&(Blockly.Events.Move.superClass_.constructor.call(this,a),a=this.currentLocation_(),this.oldParentId=a.parentId,this.oldInputName=a.inputName,this.oldCoordinate=a.coordinate)};goog.inherits(Blockly.Events.Move,Blockly.Events.Abstract);Blockly.Events.Move.prototype.type=Blockly.Events.MOVE;
Blockly.Events.Move.prototype.toJson=function(){var a=Blockly.Events.Move.superClass_.toJson.call(this);this.newParentId&&(a.newParentId=this.newParentId);this.newInputName&&(a.newInputName=this.newInputName);this.newCoordinate&&(a.newCoordinate=Math.round(this.newCoordinate.x)+","+Math.round(this.newCoordinate.y));return a};
Blockly.Events.Move.prototype.fromJson=function(a){Blockly.Events.Move.superClass_.fromJson.call(this,a);this.newParentId=a.newParentId;this.newInputName=a.newInputName;a.newCoordinate&&(a=a.newCoordinate.split(","),this.newCoordinate=new goog.math.Coordinate(parseFloat(a[0]),parseFloat(a[1])))};Blockly.Events.Move.prototype.recordNew=function(){var a=this.currentLocation_();this.newParentId=a.parentId;this.newInputName=a.inputName;this.newCoordinate=a.coordinate};
Blockly.Events.Move.prototype.currentLocation_=function(){var a=Blockly.Workspace.getById(this.workspaceId).getBlockById(this.blockId),b={},c=a.getParent();if(c){if(b.parentId=c.id,a=c.getInputWithBlock(a))b.inputName=a.name}else b.coordinate=a.getRelativeToSurfaceXY();return b};Blockly.Events.Move.prototype.isNull=function(){return this.oldParentId==this.newParentId&&this.oldInputName==this.newInputName&&goog.math.Coordinate.equals(this.oldCoordinate,this.newCoordinate)};
Blockly.Events.Move.prototype.run=function(a){var b=Blockly.Workspace.getById(this.workspaceId),c=b.getBlockById(this.blockId);if(c){var d=a?this.newParentId:this.oldParentId,e=a?this.newInputName:this.oldInputName;a=a?this.newCoordinate:this.oldCoordinate;var f=null;if(d&&(f=b.getBlockById(d),!f)){console.warn("Can't connect to non-existant block: "+d);return}c.getParent()&&c.unplug();if(a)e=c.getRelativeToSurfaceXY(),c.moveBy(a.x-e.x,a.y-e.y);else{var c=c.outputConnection||c.previousConnection,
g;if(e){if(b=f.getInput(e))g=b.connection}else c.type==Blockly.PREVIOUS_STATEMENT&&(g=f.nextConnection);g?c.connect(g):console.warn("Can't connect to non-existant input: "+e)}}else console.warn("Can't move non-existant block: "+this.blockId)};Blockly.Events.Ui=function(a,b,c,d){Blockly.Events.Ui.superClass_.constructor.call(this,a);this.element=b;this.oldValue=c;this.newValue=d;this.recordUndo=!1};goog.inherits(Blockly.Events.Ui,Blockly.Events.Abstract);Blockly.Events.Ui.prototype.type=Blockly.Events.UI;
-Blockly.Events.Ui.prototype.toJson=function(){var a=Blockly.Events.Ui.superClass_.toJson.call(this);a.element=this.element;void 0!==this.newValue&&(a.newValue=this.newValue);return a};Blockly.Events.Ui.prototype.fromJson=function(a){Blockly.Events.Ui.superClass_.fromJson.call(this,a);this.element=a.element;this.newValue=a.newValue};Blockly.Msg={};goog.getMsgOrig=goog.getMsg;goog.getMsg=function(a,b){var c=goog.getMsg.blocklyMsgMap[a];c&&(a=Blockly.Msg[c]);return goog.getMsgOrig(a,b)};goog.getMsg.blocklyMsgMap={Today:"TODAY"};Blockly.FieldTextInput=function(a,b){Blockly.FieldTextInput.superClass_.constructor.call(this,a,b)};goog.inherits(Blockly.FieldTextInput,Blockly.Field);Blockly.FieldTextInput.FONTSIZE=11;Blockly.FieldTextInput.prototype.CURSOR="text";Blockly.FieldTextInput.prototype.spellcheck_=!0;Blockly.FieldTextInput.prototype.dispose=function(){Blockly.WidgetDiv.hideIfOwner(this);Blockly.FieldTextInput.superClass_.dispose.call(this)};
+Blockly.Events.Ui.prototype.toJson=function(){var a=Blockly.Events.Ui.superClass_.toJson.call(this);a.element=this.element;void 0!==this.newValue&&(a.newValue=this.newValue);return a};Blockly.Events.Ui.prototype.fromJson=function(a){Blockly.Events.Ui.superClass_.fromJson.call(this,a);this.element=a.element;this.newValue=a.newValue};
+Blockly.Events.disableOrphans=function(a){if(a.type==Blockly.Events.MOVE||a.type==Blockly.Events.CREATE){Blockly.Events.disable();if(a=Blockly.Workspace.getById(a.workspaceId).getBlockById(a.blockId))if(a.getParent()&&!a.getParent().disabled){do a.setDisabled(!1),a=a.getNextBlock();while(a)}else if((a.outputConnection||a.previousConnection)&&Blockly.dragMode_==Blockly.DRAG_NONE){do a.setDisabled(!0),a=a.getNextBlock();while(a)}Blockly.Events.enable()}};Blockly.Msg={};goog.getMsgOrig=goog.getMsg;goog.getMsg=function(a,b){var c=goog.getMsg.blocklyMsgMap[a];c&&(a=Blockly.Msg[c]);return goog.getMsgOrig(a,b)};goog.getMsg.blocklyMsgMap={Today:"TODAY"};Blockly.FieldTextInput=function(a,b){Blockly.FieldTextInput.superClass_.constructor.call(this,a,b)};goog.inherits(Blockly.FieldTextInput,Blockly.Field);Blockly.FieldTextInput.FONTSIZE=11;Blockly.FieldTextInput.prototype.CURSOR="text";Blockly.FieldTextInput.prototype.spellcheck_=!0;Blockly.FieldTextInput.prototype.dispose=function(){Blockly.WidgetDiv.hideIfOwner(this);Blockly.FieldTextInput.superClass_.dispose.call(this)};
Blockly.FieldTextInput.prototype.setValue=function(a){if(null!==a){if(this.sourceBlock_&&this.validator_){var b=this.validator_(a);null!==b&&void 0!==b&&(a=b)}Blockly.Field.prototype.setValue.call(this,a)}};Blockly.FieldTextInput.prototype.setSpellcheck=function(a){this.spellcheck_=a};
Blockly.FieldTextInput.prototype.showEditor_=function(a){this.workspace_=this.sourceBlock_.workspace;a=a||!1;if(!a&&(goog.userAgent.MOBILE||goog.userAgent.ANDROID||goog.userAgent.IPAD)){a=window.prompt(Blockly.Msg.CHANGE_VALUE_TITLE,this.text_);if(this.sourceBlock_&&this.validator_){var b=this.validator_(a);void 0!==b&&(a=b)}this.setValue(a)}else{Blockly.WidgetDiv.show(this,this.sourceBlock_.RTL,this.widgetDispose_());var b=Blockly.WidgetDiv.DIV,c=goog.dom.createDom("input","blocklyHtmlInput");c.setAttribute("spellcheck",
this.spellcheck_);var d=Blockly.FieldTextInput.FONTSIZE*this.workspace_.scale+"pt";b.style.fontSize=d;c.style.fontSize=d;Blockly.FieldTextInput.htmlInput_=c;b.appendChild(c);c.value=c.defaultValue=this.text_;c.oldValue_=null;this.validate_();this.resizeEditor_();a||(c.focus(),c.select());c.onKeyDownWrapper_=Blockly.bindEvent_(c,"keydown",this,this.onHtmlInputKeyDown_);c.onKeyUpWrapper_=Blockly.bindEvent_(c,"keyup",this,this.onHtmlInputChange_);c.onKeyPressWrapper_=Blockly.bindEvent_(c,"keypress",
@@ -1240,7 +1240,8 @@ Blockly.FieldTextInput.prototype.onHtmlInputChange_=function(a){a=Blockly.FieldT
Blockly.FieldTextInput.prototype.validate_=function(){var a=!0;goog.asserts.assertObject(Blockly.FieldTextInput.htmlInput_);var b=Blockly.FieldTextInput.htmlInput_;this.sourceBlock_&&this.validator_&&(a=this.validator_(b.value));null===a?Blockly.addClass_(b,"blocklyInvalidInput"):Blockly.removeClass_(b,"blocklyInvalidInput")};
Blockly.FieldTextInput.prototype.resizeEditor_=function(){var a=Blockly.WidgetDiv.DIV,b=this.fieldGroup_.getBBox();a.style.width=b.width*this.workspace_.scale+"px";a.style.height=b.height*this.workspace_.scale+"px";b=this.getAbsoluteXY_();if(this.sourceBlock_.RTL){var c=this.getScaledBBox_();b.x+=c.width;b.x-=a.offsetWidth}b.y+=1;goog.userAgent.GECKO&&Blockly.WidgetDiv.DIV.style.top&&(--b.x,--b.y);goog.userAgent.WEBKIT&&(b.y-=3);a.style.left=b.x+"px";a.style.top=b.y+"px"};
Blockly.FieldTextInput.prototype.widgetDispose_=function(){var a=this;return function(){var b=Blockly.FieldTextInput.htmlInput_,c=b.value;if(a.sourceBlock_&&a.validator_){var d=a.validator_(c);null===d?c=b.defaultValue:void 0!==d&&(c=d)}a.setValue(c);a.sourceBlock_.rendered&&a.sourceBlock_.render();Blockly.unbindEvent_(b.onKeyDownWrapper_);Blockly.unbindEvent_(b.onKeyUpWrapper_);Blockly.unbindEvent_(b.onKeyPressWrapper_);a.workspace_.removeChangeListener(b.onWorkspaceChangeWrapper_);Blockly.FieldTextInput.htmlInput_=
-null;b=Blockly.WidgetDiv.DIV.style;b.width="auto";b.height="auto";b.fontSize=""}};Blockly.FieldTextInput.numberValidator=function(a){if(null===a)return null;a=String(a);a=a.replace(/O/ig,"0");a=a.replace(/,/g,"");a=parseFloat(a||0);return isNaN(a)?null:String(a)};Blockly.FieldTextInput.nonnegativeIntegerValidator=function(a){(a=Blockly.FieldTextInput.numberValidator(a))&&(a=String(Math.max(0,Math.floor(a))));return a};Blockly.FieldAngle=function(a,b){this.symbol_=Blockly.createSvgElement("tspan",{},null);this.symbol_.appendChild(document.createTextNode("\u00b0"));Blockly.FieldAngle.superClass_.constructor.call(this,a,b)};goog.inherits(Blockly.FieldAngle,Blockly.FieldTextInput);
+null;b=Blockly.WidgetDiv.DIV.style;b.width="auto";b.height="auto";b.fontSize=""}};Blockly.FieldTextInput.numberValidator=function(a){console.warn("Blockly.FieldTextInput.numberValidator is deprecated. Use Blockly.FieldNumber instead.");if(null===a)return null;a=String(a);a=a.replace(/O/ig,"0");a=a.replace(/,/g,"");a=parseFloat(a||0);return isNaN(a)?null:String(a)};
+Blockly.FieldTextInput.nonnegativeIntegerValidator=function(a){(a=Blockly.FieldTextInput.numberValidator(a))&&(a=String(Math.max(0,Math.floor(a))));return a};Blockly.FieldAngle=function(a,b){this.symbol_=Blockly.createSvgElement("tspan",{},null);this.symbol_.appendChild(document.createTextNode("\u00b0"));Blockly.FieldAngle.superClass_.constructor.call(this,a,b)};goog.inherits(Blockly.FieldAngle,Blockly.FieldTextInput);
Blockly.FieldAngle.prototype.setValidator=function(a){Blockly.FieldAngle.superClass_.setValidator.call(this,a?function(b){var c=a.call(this,b);if(null===c)var d=c;else void 0===c&&(c=b),d=Blockly.FieldAngle.angleValidator.call(this,c),void 0===d&&(d=c);return d===b?void 0:d}:Blockly.FieldAngle.angleValidator)};Blockly.FieldAngle.ROUND=15;Blockly.FieldAngle.HALF=50;Blockly.FieldAngle.CLOCKWISE=!1;Blockly.FieldAngle.OFFSET=0;Blockly.FieldAngle.WRAP=360;
Blockly.FieldAngle.RADIUS=Blockly.FieldAngle.HALF-1;Blockly.FieldAngle.prototype.dispose_=function(){var a=this;return function(){Blockly.FieldAngle.superClass_.dispose_.call(a)();a.gauge_=null;a.clickWrapper_&&Blockly.unbindEvent_(a.clickWrapper_);a.moveWrapper1_&&Blockly.unbindEvent_(a.moveWrapper1_);a.moveWrapper2_&&Blockly.unbindEvent_(a.moveWrapper2_)}};
Blockly.FieldAngle.prototype.showEditor_=function(){Blockly.FieldAngle.superClass_.showEditor_.call(this,goog.userAgent.MOBILE||goog.userAgent.ANDROID||goog.userAgent.IPAD);var a=Blockly.WidgetDiv.DIV;if(a.firstChild){var a=Blockly.createSvgElement("svg",{xmlns:"http://www.w3.org/2000/svg","xmlns:html":"http://www.w3.org/1999/xhtml","xmlns:xlink":"http://www.w3.org/1999/xlink",version:"1.1",height:2*Blockly.FieldAngle.HALF+"px",width:2*Blockly.FieldAngle.HALF+"px"},a),b=Blockly.createSvgElement("circle",
@@ -1250,7 +1251,7 @@ Blockly.FieldAngle.prototype.onMouseMove=function(a){var b=this.gauge_.ownerSVGE
b,this.setValue(b),this.validate_(),this.resizeEditor_())};Blockly.FieldAngle.prototype.setText=function(a){Blockly.FieldAngle.superClass_.setText.call(this,a);this.textElement_&&(this.updateGraph_(),this.sourceBlock_.RTL?this.textElement_.insertBefore(this.symbol_,this.textElement_.firstChild):this.textElement_.appendChild(this.symbol_),this.size_.width=0)};
Blockly.FieldAngle.prototype.updateGraph_=function(){if(this.gauge_){var a=Number(this.getText())+Blockly.FieldAngle.OFFSET,b=goog.math.toRadians(a),a=["M ",Blockly.FieldAngle.HALF,",",Blockly.FieldAngle.HALF],c=Blockly.FieldAngle.HALF,d=Blockly.FieldAngle.HALF;if(!isNaN(b)){var e=goog.math.toRadians(Blockly.FieldAngle.OFFSET),f=Math.cos(e)*Blockly.FieldAngle.RADIUS,g=Math.sin(e)*-Blockly.FieldAngle.RADIUS;Blockly.FieldAngle.CLOCKWISE&&(b=2*e-b);c+=Math.cos(b)*Blockly.FieldAngle.RADIUS;d-=Math.sin(b)*
Blockly.FieldAngle.RADIUS;b=Math.abs(Math.floor((b-e)/Math.PI)%2);Blockly.FieldAngle.CLOCKWISE&&(b=1-b);a.push(" l ",f,",",g," A ",Blockly.FieldAngle.RADIUS,",",Blockly.FieldAngle.RADIUS," 0 ",b," ",Number(Blockly.FieldAngle.CLOCKWISE)," ",c,",",d," z")}this.gauge_.setAttribute("d",a.join(""));this.line_.setAttribute("x2",c);this.line_.setAttribute("y2",d)}};
-Blockly.FieldAngle.angleValidator=function(a){a=Blockly.FieldTextInput.numberValidator(a);null!==a&&(a%=360,0>a&&(a+=360),a>Blockly.FieldAngle.WRAP&&(a-=360),a=String(a));return a};Blockly.FieldCheckbox=function(a,b){Blockly.FieldCheckbox.superClass_.constructor.call(this,"",b);this.setValue(a)};goog.inherits(Blockly.FieldCheckbox,Blockly.Field);Blockly.FieldCheckbox.CHECK_CHAR="\u2713";Blockly.FieldCheckbox.prototype.CURSOR="default";
+Blockly.FieldAngle.angleValidator=function(a){if(null===a)return null;a=parseFloat(a||0);if(isNaN(a))return null;a%=360;0>a&&(a+=360);a>Blockly.FieldAngle.WRAP&&(a-=360);return String(a)};Blockly.FieldCheckbox=function(a,b){Blockly.FieldCheckbox.superClass_.constructor.call(this,"",b);this.setValue(a)};goog.inherits(Blockly.FieldCheckbox,Blockly.Field);Blockly.FieldCheckbox.CHECK_CHAR="\u2713";Blockly.FieldCheckbox.prototype.CURSOR="default";
Blockly.FieldCheckbox.prototype.init=function(){if(!this.fieldGroup_){Blockly.FieldCheckbox.superClass_.init.call(this);this.checkElement_=Blockly.createSvgElement("text",{"class":"blocklyText blocklyCheckbox",x:-3,y:14},this.fieldGroup_);var a=document.createTextNode(Blockly.FieldCheckbox.CHECK_CHAR);this.checkElement_.appendChild(a);this.checkElement_.style.display=this.state_?"block":"none"}};Blockly.FieldCheckbox.prototype.getValue=function(){return String(this.state_).toUpperCase()};
Blockly.FieldCheckbox.prototype.setValue=function(a){a="TRUE"==a;this.state_!==a&&(this.sourceBlock_&&Blockly.Events.isEnabled()&&Blockly.Events.fire(new Blockly.Events.Change(this.sourceBlock_,"field",this.name,this.state_,a)),this.state_=a,this.checkElement_&&(this.checkElement_.style.display=a?"block":"none"))};Blockly.FieldCheckbox.prototype.showEditor_=function(){var a=!this.state_;if(this.sourceBlock_&&this.validator_){var b=this.validator_(a);void 0!==b&&(a=b)}null!==a&&this.setValue(String(a).toUpperCase())};Blockly.FieldColour=function(a,b){Blockly.FieldColour.superClass_.constructor.call(this,a,b);this.setText(Blockly.Field.NBSP+Blockly.Field.NBSP+Blockly.Field.NBSP)};goog.inherits(Blockly.FieldColour,Blockly.Field);Blockly.FieldColour.prototype.colours_=null;Blockly.FieldColour.prototype.columns_=0;Blockly.FieldColour.prototype.init=function(){Blockly.FieldColour.superClass_.init.call(this);this.borderRect_.style.fillOpacity=1;this.setValue(this.getValue())};Blockly.FieldColour.prototype.CURSOR="default";
Blockly.FieldColour.prototype.dispose=function(){Blockly.WidgetDiv.hideIfOwner(this);Blockly.FieldColour.superClass_.dispose.call(this)};Blockly.FieldColour.prototype.getValue=function(){return this.colour_};Blockly.FieldColour.prototype.setValue=function(a){this.sourceBlock_&&Blockly.Events.isEnabled()&&this.colour_!=a&&Blockly.Events.fire(new Blockly.Events.Change(this.sourceBlock_,"field",this.name,this.colour_,a));this.colour_=a;this.borderRect_&&(this.borderRect_.style.fill=a)};
@@ -1270,7 +1271,9 @@ Blockly.FieldDropdown.prototype.setText=function(a){this.sourceBlock_&&this.arro
Blockly.FieldDropdown.prototype.dispose=function(){Blockly.WidgetDiv.hideIfOwner(this);Blockly.FieldDropdown.superClass_.dispose.call(this)};Blockly.FieldImage=function(a,b,c,d){this.sourceBlock_=null;this.height_=Number(c);this.width_=Number(b);this.size_=new goog.math.Size(this.width_,this.height_+2*Blockly.BlockSvg.INLINE_PADDING_Y);this.text_=d||"";this.setValue(a)};goog.inherits(Blockly.FieldImage,Blockly.Field);Blockly.FieldImage.prototype.rectElement_=null;Blockly.FieldImage.prototype.EDITABLE=!1;
Blockly.FieldImage.prototype.init=function(){if(!this.fieldGroup_){this.fieldGroup_=Blockly.createSvgElement("g",{},null);this.visible_||(this.fieldGroup_.style.display="none");this.imageElement_=Blockly.createSvgElement("image",{height:this.height_+"px",width:this.width_+"px"},this.fieldGroup_);this.setValue(this.src_);goog.userAgent.GECKO&&(this.rectElement_=Blockly.createSvgElement("rect",{height:this.height_+"px",width:this.width_+"px","fill-opacity":0},this.fieldGroup_));this.sourceBlock_.getSvgRoot().appendChild(this.fieldGroup_);
var a=this.rectElement_||this.imageElement_;a.tooltip=this.sourceBlock_;Blockly.Tooltip.bindMouseEvents(a)}};Blockly.FieldImage.prototype.dispose=function(){goog.dom.removeNode(this.fieldGroup_);this.rectElement_=this.imageElement_=this.fieldGroup_=null};Blockly.FieldImage.prototype.setTooltip=function(a){(this.rectElement_||this.imageElement_).tooltip=a};Blockly.FieldImage.prototype.getValue=function(){return this.src_};
-Blockly.FieldImage.prototype.setValue=function(a){null!==a&&(this.src_=a,this.imageElement_&&this.imageElement_.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",goog.isString(a)?a:""))};Blockly.FieldImage.prototype.setText=function(a){null!==a&&(this.text_=a)};Blockly.FieldImage.prototype.render_=function(){};Blockly.FieldNumber=function(a,b){Blockly.FieldNumber.superClass_.constructor.call(this,a,b)};goog.inherits(Blockly.FieldNumber,Blockly.FieldTextInput);Blockly.Variables={};Blockly.Variables.NAME_TYPE="VARIABLE";Blockly.Variables.allVariables=function(a){var b;if(a.getDescendants)b=a.getDescendants();else if(a.getAllBlocks)b=a.getAllBlocks();else throw"Not Block or Workspace: "+a;a=Object.create(null);for(var c=0;ca)}};
-Blockly.Flyout.prototype.getClientRect=function(){var a=this.svgGroup_.getBoundingClientRect(),b=a.left,c=a.top,d=a.width,a=a.height;return this.toolboxPosition_==Blockly.TOOLBOX_AT_TOP?new goog.math.Rect(-1E9,c-1E9,2E9,1E9+a):this.toolboxPosition_==Blockly.TOOLBOX_AT_BOTTOM?new goog.math.Rect(-1E9,c,2E9,1E9+a):this.toolboxPosition_==Blockly.TOOLBOX_AT_LEFT?new goog.math.Rect(b-1E9,-1E9,1E9+d,2E9):new goog.math.Rect(b,-1E9,1E9+d,2E9)};
+Blockly.Flyout.prototype.getClientRect=function(){if(!this.svgGroup_)return null;var a=this.svgGroup_.getBoundingClientRect(),b=a.left,c=a.top,d=a.width,a=a.height;return this.toolboxPosition_==Blockly.TOOLBOX_AT_TOP?new goog.math.Rect(-1E9,c-1E9,2E9,1E9+a):this.toolboxPosition_==Blockly.TOOLBOX_AT_BOTTOM?new goog.math.Rect(-1E9,c,2E9,1E9+a):this.toolboxPosition_==Blockly.TOOLBOX_AT_LEFT?new goog.math.Rect(b-1E9,-1E9,1E9+d,2E9):new goog.math.Rect(b,-1E9,1E9+d,2E9)};
Blockly.Flyout.terminateDrag_=function(){Blockly.Flyout.onMouseUpWrapper_&&(Blockly.unbindEvent_(Blockly.Flyout.onMouseUpWrapper_),Blockly.Flyout.onMouseUpWrapper_=null);Blockly.Flyout.onMouseMoveBlockWrapper_&&(Blockly.unbindEvent_(Blockly.Flyout.onMouseMoveBlockWrapper_),Blockly.Flyout.onMouseMoveBlockWrapper_=null);Blockly.Flyout.onMouseMoveWrapper_&&(Blockly.unbindEvent_(Blockly.Flyout.onMouseMoveWrapper_),Blockly.Flyout.onMouseMoveWrapper_=null);Blockly.Flyout.onMouseUpWrapper_&&(Blockly.unbindEvent_(Blockly.Flyout.onMouseUpWrapper_),
Blockly.Flyout.onMouseUpWrapper_=null);Blockly.Flyout.startDownEvent_=null;Blockly.Flyout.startBlock_=null;Blockly.Flyout.startFlyout_=null};
Blockly.Flyout.prototype.reflowHorizontal=function(a){this.workspace_.scale=this.targetWorkspace_.scale;for(var b=0,c=0,d;d=a[c];c++)b=Math.max(b,d.getHeightWidth().height);b+=1.5*this.MARGIN;b*=this.workspace_.scale;b+=Blockly.Scrollbar.scrollbarThickness;if(this.height_!=b){for(c=0;d=a[c];c++){var e=d.getHeightWidth();if(d.flyoutRect_){d.flyoutRect_.setAttribute("width",e.width);d.flyoutRect_.setAttribute("height",e.height);var f=d.outputConnection?Blockly.BlockSvg.TAB_WIDTH:0,g=d.getRelativeToSurfaceXY();
@@ -1346,7 +1349,7 @@ Blockly.Toolbox.prototype.position=function(){var a=this.HtmlDiv;if(a){var b=thi
Blockly.Toolbox.prototype.populate_=function(a){function b(a,g,h){for(var k=null,m=0,p;p=a.childNodes[m];m++)if(p.tagName)switch(p.tagName.toUpperCase()){case "CATEGORY":k=c.createNode(p.getAttribute("name"));k.blocks=[];g.add(k);var l=p.getAttribute("custom");l?k.blocks=l:b(p,k,h);l=p.getAttribute("colour");goog.isString(l)?(l.match(/^#[0-9a-fA-F]{6}$/)?k.hexColour=l:k.hexColour=Blockly.hueToRgb(l),e=!0):k.hexColour="";"true"==p.getAttribute("expanded")?(k.blocks.length&&c.setSelectedItem(k),k.setExpanded(!0)):
k.setExpanded(!1);k=p;break;case "SEP":k&&("CATEGORY"==k.tagName.toUpperCase()?g.add(new Blockly.Toolbox.TreeSeparator(d.treeSeparatorConfig_)):(p=parseFloat(p.getAttribute("gap")),isNaN(p)||(l=parseFloat(k.getAttribute("gap")),p=isNaN(l)?p:l+p,k.setAttribute("gap",p))));break;case "BLOCK":case "SHADOW":g.blocks.push(p),k=p}}var c=this.tree_,d=this;c.removeChildren();c.blocks=[];var e=!1;b(a,this.tree_,this.workspace_.options.pathToMedia);this.hasColours_=e;if(c.blocks.length)throw"Toolbox cannot have both blocks and categories in the root level.";
Blockly.resizeSvgContents(this.workspace_)};Blockly.Toolbox.prototype.addColour_=function(a){a=(a||this.tree_).getChildren();for(var b=0,c;c=a[b];b++){var d=c.getRowElement();if(d){var e=this.hasColours_?"8px solid "+(c.hexColour||"#ddd"):"none";this.workspace_.RTL?d.style.borderRight=e:d.style.borderLeft=e}this.addColour_(c)}};Blockly.Toolbox.prototype.clearSelection=function(){this.tree_.setSelectedItem(null)};
-Blockly.Toolbox.prototype.getClientRect=function(){var a=this.HtmlDiv.getBoundingClientRect(),b=a.left,c=a.top,d=a.width,a=a.height;return this.toolboxPosition==Blockly.TOOLBOX_AT_LEFT?new goog.math.Rect(-1E7,-1E7,1E7+b+d,2E7):this.toolboxPosition==Blockly.TOOLBOX_AT_RIGHT?new goog.math.Rect(b,-1E7,1E7+d,2E7):this.toolboxPosition==Blockly.TOOLBOX_AT_TOP?new goog.math.Rect(-1E7,-1E7,2E7,1E7+c+a):new goog.math.Rect(0,c,2E7,1E7+d)};
+Blockly.Toolbox.prototype.getClientRect=function(){if(!this.HtmlDiv)return null;var a=this.HtmlDiv.getBoundingClientRect(),b=a.left,c=a.top,d=a.width,a=a.height;return this.toolboxPosition==Blockly.TOOLBOX_AT_LEFT?new goog.math.Rect(-1E7,-1E7,1E7+b+d,2E7):this.toolboxPosition==Blockly.TOOLBOX_AT_RIGHT?new goog.math.Rect(b,-1E7,1E7+d,2E7):this.toolboxPosition==Blockly.TOOLBOX_AT_TOP?new goog.math.Rect(-1E7,-1E7,2E7,1E7+c+a):new goog.math.Rect(0,c,2E7,1E7+d)};
Blockly.Toolbox.TreeControl=function(a,b){this.toolbox_=a;goog.ui.tree.TreeControl.call(this,goog.html.SafeHtml.EMPTY,b)};goog.inherits(Blockly.Toolbox.TreeControl,goog.ui.tree.TreeControl);Blockly.Toolbox.TreeControl.prototype.enterDocument=function(){Blockly.Toolbox.TreeControl.superClass_.enterDocument.call(this);if(goog.events.BrowserFeature.TOUCH_ENABLED){var a=this.getElement();Blockly.bindEvent_(a,goog.events.EventType.TOUCHSTART,this,this.handleTouchEvent_)}};
Blockly.Toolbox.TreeControl.prototype.handleTouchEvent_=function(a){a.preventDefault();var b=this.getNodeFromEvent_(a);b&&a.type===goog.events.EventType.TOUCHSTART&&setTimeout(function(){b.onMouseDown(a)},1)};Blockly.Toolbox.TreeControl.prototype.createNode=function(a){return new Blockly.Toolbox.TreeNode(this.toolbox_,a?goog.html.SafeHtml.htmlEscape(a):goog.html.SafeHtml.EMPTY,this.getConfig(),this.getDomHelper())};
Blockly.Toolbox.TreeControl.prototype.setSelectedItem=function(a){var b=this.toolbox_;if(a!=this.selectedItem_&&a!=b.tree_){b.lastCategory_&&(b.lastCategory_.getRowElement().style.backgroundColor="");if(a){var c=a.hexColour||"#57e";a.getRowElement().style.backgroundColor=c;b.addColour_(a)}c=this.getSelectedItem();goog.ui.tree.TreeControl.prototype.setSelectedItem.call(this,a);a&&a.blocks&&a.blocks.length?(b.flyout_.show(a.blocks),b.lastCategory_!=a&&b.flyout_.scrollToStart()):b.flyout_.hide();c!=
diff --git a/blockly_uncompressed.js b/blockly_uncompressed.js
index fb8afa9e7..623ac3dc5 100644
--- a/blockly_uncompressed.js
+++ b/blockly_uncompressed.js
@@ -59,7 +59,7 @@ goog.addDependency("../../../" + dir + "/core/field_date.js", ['Blockly.FieldDat
goog.addDependency("../../../" + dir + "/core/field_dropdown.js", ['Blockly.FieldDropdown'], ['Blockly.Field', 'goog.dom', 'goog.events', 'goog.style', 'goog.ui.Menu', 'goog.ui.MenuItem', 'goog.userAgent']);
goog.addDependency("../../../" + dir + "/core/field_image.js", ['Blockly.FieldImage'], ['Blockly.Field', 'goog.dom', 'goog.math.Size', 'goog.userAgent']);
goog.addDependency("../../../" + dir + "/core/field_label.js", ['Blockly.FieldLabel'], ['Blockly.Field', 'Blockly.Tooltip', 'goog.dom', 'goog.math.Size']);
-goog.addDependency("../../../" + dir + "/core/field_number.js", ['Blockly.FieldNumber'], ['Blockly.FieldTextInput']);
+goog.addDependency("../../../" + dir + "/core/field_number.js", ['Blockly.FieldNumber'], ['Blockly.FieldTextInput', 'goog.math']);
goog.addDependency("../../../" + dir + "/core/field_textinput.js", ['Blockly.FieldTextInput'], ['Blockly.Field', 'Blockly.Msg', 'goog.asserts', 'goog.dom', 'goog.userAgent']);
goog.addDependency("../../../" + dir + "/core/field_variable.js", ['Blockly.FieldVariable'], ['Blockly.FieldDropdown', 'Blockly.Msg', 'Blockly.Variables', 'goog.string']);
goog.addDependency("../../../" + dir + "/core/flyout.js", ['Blockly.Flyout'], ['Blockly.Block', 'Blockly.Comment', 'Blockly.Events', 'Blockly.WorkspaceSvg', 'goog.dom', 'goog.events', 'goog.math.Rect', 'goog.userAgent']);
@@ -277,7 +277,7 @@ goog.addDependency("dom/controlrange_test.js", ['goog.dom.ControlRangeTest'], ['
goog.addDependency("dom/dataset.js", ['goog.dom.dataset'], ['goog.string', 'goog.userAgent.product']);
goog.addDependency("dom/dataset_test.js", ['goog.dom.datasetTest'], ['goog.dom', 'goog.dom.dataset', 'goog.testing.jsunit']);
goog.addDependency("dom/dom.js", ['goog.dom', 'goog.dom.Appendable', 'goog.dom.DomHelper'], ['goog.array', 'goog.asserts', 'goog.dom.BrowserFeature', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.dom.safe', 'goog.html.SafeHtml', 'goog.html.uncheckedconversions', 'goog.math.Coordinate', 'goog.math.Size', 'goog.object', 'goog.string', 'goog.string.Unicode', 'goog.userAgent']);
-goog.addDependency("dom/dom_test.js", ['goog.dom.dom_test'], ['goog.array', 'goog.dom', 'goog.dom.BrowserFeature', 'goog.dom.DomHelper', 'goog.dom.InputType', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.functions', 'goog.html.testing', 'goog.object', 'goog.string.Const', 'goog.string.Unicode', 'goog.testing.PropertyReplacer', 'goog.testing.asserts', 'goog.userAgent', 'goog.userAgent.product', 'goog.userAgent.product.isVersion']);
+goog.addDependency("dom/dom_test.js", ['goog.dom.dom_test'], ['goog.array', 'goog.asserts', 'goog.dom', 'goog.dom.BrowserFeature', 'goog.dom.DomHelper', 'goog.dom.InputType', 'goog.dom.NodeType', 'goog.dom.TagName', 'goog.dom.TypedTagName', 'goog.functions', 'goog.html.testing', 'goog.object', 'goog.string.Const', 'goog.string.Unicode', 'goog.testing.PropertyReplacer', 'goog.testing.asserts', 'goog.userAgent', 'goog.userAgent.product', 'goog.userAgent.product.isVersion']);
goog.addDependency("dom/fontsizemonitor.js", ['goog.dom.FontSizeMonitor', 'goog.dom.FontSizeMonitor.EventType'], ['goog.dom', 'goog.dom.TagName', 'goog.events', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.userAgent']);
goog.addDependency("dom/fontsizemonitor_test.js", ['goog.dom.FontSizeMonitorTest'], ['goog.dom', 'goog.dom.FontSizeMonitor', 'goog.dom.TagName', 'goog.events', 'goog.events.Event', 'goog.testing.PropertyReplacer', 'goog.testing.events', 'goog.testing.jsunit', 'goog.userAgent']);
goog.addDependency("dom/forms.js", ['goog.dom.forms'], ['goog.dom.InputType', 'goog.dom.TagName', 'goog.structs.Map', 'goog.window']);
@@ -318,6 +318,8 @@ goog.addDependency("dom/textrange.js", ['goog.dom.TextRange'], ['goog.array', 'g
goog.addDependency("dom/textrange_test.js", ['goog.dom.TextRangeTest'], ['goog.dom', 'goog.dom.ControlRange', 'goog.dom.Range', 'goog.dom.TextRange', 'goog.math.Coordinate', 'goog.style', 'goog.testing.ExpectedFailures', 'goog.testing.jsunit', 'goog.userAgent', 'goog.userAgent.product']);
goog.addDependency("dom/textrangeiterator.js", ['goog.dom.TextRangeIterator'], ['goog.array', 'goog.dom', 'goog.dom.NodeType', 'goog.dom.RangeIterator', 'goog.dom.TagName', 'goog.iter.StopIteration']);
goog.addDependency("dom/textrangeiterator_test.js", ['goog.dom.TextRangeIteratorTest'], ['goog.dom', 'goog.dom.TagName', 'goog.dom.TextRangeIterator', 'goog.iter.StopIteration', 'goog.testing.dom', 'goog.testing.jsunit']);
+goog.addDependency("dom/typedtagname.js", ['goog.dom.TypedTagName'], []);
+goog.addDependency("dom/typedtagname_test.js", ['goog.dom.TypedTagNameTest'], ['goog.dom.TypedTagName', 'goog.testing.jsunit']);
goog.addDependency("dom/vendor.js", ['goog.dom.vendor'], ['goog.string', 'goog.userAgent']);
goog.addDependency("dom/vendor_test.js", ['goog.dom.vendorTest'], ['goog.array', 'goog.dom.vendor', 'goog.labs.userAgent.util', 'goog.testing.MockUserAgent', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit', 'goog.userAgent', 'goog.userAgentTestUtil']);
goog.addDependency("dom/viewportsizemonitor.js", ['goog.dom.ViewportSizeMonitor'], ['goog.dom', 'goog.events', 'goog.events.EventTarget', 'goog.events.EventType', 'goog.math.Size']);
@@ -855,7 +857,7 @@ goog.addDependency("module/moduleloadcallback.js", ['goog.module.ModuleLoadCallb
goog.addDependency("module/moduleloadcallback_test.js", ['goog.module.ModuleLoadCallbackTest'], ['goog.debug.ErrorHandler', 'goog.debug.entryPointRegistry', 'goog.functions', 'goog.module.ModuleLoadCallback', 'goog.testing.jsunit', 'goog.testing.recordFunction']);
goog.addDependency("module/moduleloader.js", ['goog.module.ModuleLoader'], ['goog.Timer', 'goog.array', 'goog.events', 'goog.events.Event', 'goog.events.EventHandler', 'goog.events.EventId', 'goog.events.EventTarget', 'goog.labs.userAgent.browser', 'goog.log', 'goog.module.AbstractModuleLoader', 'goog.net.BulkLoader', 'goog.net.EventType', 'goog.net.jsloader', 'goog.userAgent', 'goog.userAgent.product']);
goog.addDependency("module/moduleloader_test.js", ['goog.module.ModuleLoaderTest'], ['goog.Promise', 'goog.array', 'goog.dom', 'goog.dom.TagName', 'goog.events', 'goog.functions', 'goog.module.ModuleLoader', 'goog.module.ModuleManager', 'goog.net.BulkLoader', 'goog.net.XmlHttp', 'goog.object', 'goog.testing.PropertyReplacer', 'goog.testing.TestCase', 'goog.testing.events.EventObserver', 'goog.testing.jsunit', 'goog.userAgent']);
-goog.addDependency("module/modulemanager.js", ['goog.module.ModuleManager', 'goog.module.ModuleManager.CallbackType', 'goog.module.ModuleManager.FailureType'], ['goog.Disposable', 'goog.array', 'goog.asserts', 'goog.async.Deferred', 'goog.debug.Trace', 'goog.dispose', 'goog.log', 'goog.module', 'goog.module.ModuleInfo', 'goog.module.ModuleLoadCallback', 'goog.object']);
+goog.addDependency("module/modulemanager.js", ['goog.module.ModuleManager', 'goog.module.ModuleManager.CallbackType', 'goog.module.ModuleManager.FailureType'], ['goog.Disposable', 'goog.array', 'goog.asserts', 'goog.async.Deferred', 'goog.debug.Trace', 'goog.dispose', 'goog.log', 'goog.module', 'goog.module.AbstractModuleLoader', 'goog.module.ModuleInfo', 'goog.module.ModuleLoadCallback', 'goog.object']);
goog.addDependency("module/modulemanager_test.js", ['goog.module.ModuleManagerTest'], ['goog.array', 'goog.functions', 'goog.module.BaseModule', 'goog.module.ModuleManager', 'goog.testing', 'goog.testing.MockClock', 'goog.testing.jsunit', 'goog.testing.recordFunction']);
goog.addDependency("module/testdata/modA_1.js", ['goog.module.testdata.modA_1'], []);
goog.addDependency("module/testdata/modA_2.js", ['goog.module.testdata.modA_2'], ['goog.module.ModuleManager']);
diff --git a/blocks/loops.js b/blocks/loops.js
index d9d8cbfcd..b9bae1e80 100644
--- a/blocks/loops.js
+++ b/blocks/loops.js
@@ -73,7 +73,9 @@ Blockly.Blocks['controls_repeat'] = {
{
"type": "field_number",
"name": "TIMES",
- "text": "10"
+ "value": 10,
+ "min": 0,
+ "precision": 1
}
],
"previousStatement": null,
@@ -84,8 +86,6 @@ Blockly.Blocks['controls_repeat'] = {
});
this.appendStatementInput('DO')
.appendField(Blockly.Msg.CONTROLS_REPEAT_INPUT_DO);
- this.getField('TIMES').setValidator(
- Blockly.FieldTextInput.nonnegativeIntegerValidator);
}
};
diff --git a/blocks/math.js b/blocks/math.js
index 311d9875b..475fd6ce2 100644
--- a/blocks/math.js
+++ b/blocks/math.js
@@ -43,8 +43,7 @@ Blockly.Blocks['math_number'] = {
this.setHelpUrl(Blockly.Msg.MATH_NUMBER_HELPURL);
this.setColour(Blockly.Blocks.math.HUE);
this.appendDummyInput()
- .appendField(new Blockly.FieldNumber('0',
- Blockly.FieldTextInput.numberValidator), 'NUM');
+ .appendField(new Blockly.FieldNumber('0'), 'NUM');
this.setOutput(true, 'Number');
// Assign 'this' to a variable for use in the tooltip closure below.
var thisBlock = this;
diff --git a/blocks_compressed.js b/blocks_compressed.js
index f68898e1c..042a84ad0 100644
--- a/blocks_compressed.js
+++ b/blocks_compressed.js
@@ -55,7 +55,7 @@ Blockly.Blocks.logic_boolean={init:function(){this.jsonInit({message0:"%1",args0
Blockly.Blocks.logic_null={init:function(){this.jsonInit({message0:Blockly.Msg.LOGIC_NULL,output:null,colour:Blockly.Blocks.logic.HUE,tooltip:Blockly.Msg.LOGIC_NULL_TOOLTIP,helpUrl:Blockly.Msg.LOGIC_NULL_HELPURL})}};
Blockly.Blocks.logic_ternary={init:function(){this.setHelpUrl(Blockly.Msg.LOGIC_TERNARY_HELPURL);this.setColour(Blockly.Blocks.logic.HUE);this.appendValueInput("IF").setCheck("Boolean").appendField(Blockly.Msg.LOGIC_TERNARY_CONDITION);this.appendValueInput("THEN").appendField(Blockly.Msg.LOGIC_TERNARY_IF_TRUE);this.appendValueInput("ELSE").appendField(Blockly.Msg.LOGIC_TERNARY_IF_FALSE);this.setOutput(!0);this.setTooltip(Blockly.Msg.LOGIC_TERNARY_TOOLTIP);this.prevParentConnection_=null},onchange:function(a){var b=
this.getInputTargetBlock("THEN"),c=this.getInputTargetBlock("ELSE"),d=this.outputConnection.targetConnection;if((b||c)&&d)for(var e=0;2>e;e++){var f=1==e?b:c;f&&!f.outputConnection.checkType_(d)&&(Blockly.Events.setGroup(a.group),d===this.prevParentConnection_?(this.unplug(),d.getSourceBlock().bumpNeighbours_()):(f.unplug(),f.bumpNeighbours_()),Blockly.Events.setGroup(!1))}this.prevParentConnection_=d}};Blockly.Blocks.loops={};Blockly.Blocks.loops.HUE=120;Blockly.Blocks.controls_repeat_ext={init:function(){this.jsonInit({message0:Blockly.Msg.CONTROLS_REPEAT_TITLE,args0:[{type:"input_value",name:"TIMES",check:"Number"}],previousStatement:null,nextStatement:null,colour:Blockly.Blocks.loops.HUE,tooltip:Blockly.Msg.CONTROLS_REPEAT_TOOLTIP,helpUrl:Blockly.Msg.CONTROLS_REPEAT_HELPURL});this.appendStatementInput("DO").appendField(Blockly.Msg.CONTROLS_REPEAT_INPUT_DO)}};
-Blockly.Blocks.controls_repeat={init:function(){this.jsonInit({message0:Blockly.Msg.CONTROLS_REPEAT_TITLE,args0:[{type:"field_number",name:"TIMES",text:"10"}],previousStatement:null,nextStatement:null,colour:Blockly.Blocks.loops.HUE,tooltip:Blockly.Msg.CONTROLS_REPEAT_TOOLTIP,helpUrl:Blockly.Msg.CONTROLS_REPEAT_HELPURL});this.appendStatementInput("DO").appendField(Blockly.Msg.CONTROLS_REPEAT_INPUT_DO);this.getField("TIMES").setValidator(Blockly.FieldTextInput.nonnegativeIntegerValidator)}};
+Blockly.Blocks.controls_repeat={init:function(){this.jsonInit({message0:Blockly.Msg.CONTROLS_REPEAT_TITLE,args0:[{type:"field_number",name:"TIMES",value:10,min:0,precision:1}],previousStatement:null,nextStatement:null,colour:Blockly.Blocks.loops.HUE,tooltip:Blockly.Msg.CONTROLS_REPEAT_TOOLTIP,helpUrl:Blockly.Msg.CONTROLS_REPEAT_HELPURL});this.appendStatementInput("DO").appendField(Blockly.Msg.CONTROLS_REPEAT_INPUT_DO)}};
Blockly.Blocks.controls_whileUntil={init:function(){var a=[[Blockly.Msg.CONTROLS_WHILEUNTIL_OPERATOR_WHILE,"WHILE"],[Blockly.Msg.CONTROLS_WHILEUNTIL_OPERATOR_UNTIL,"UNTIL"]];this.setHelpUrl(Blockly.Msg.CONTROLS_WHILEUNTIL_HELPURL);this.setColour(Blockly.Blocks.loops.HUE);this.appendValueInput("BOOL").setCheck("Boolean").appendField(new Blockly.FieldDropdown(a),"MODE");this.appendStatementInput("DO").appendField(Blockly.Msg.CONTROLS_WHILEUNTIL_INPUT_DO);this.setPreviousStatement(!0);this.setNextStatement(!0);
var b=this;this.setTooltip(function(){var a=b.getFieldValue("MODE");return{WHILE:Blockly.Msg.CONTROLS_WHILEUNTIL_TOOLTIP_WHILE,UNTIL:Blockly.Msg.CONTROLS_WHILEUNTIL_TOOLTIP_UNTIL}[a]})}};
Blockly.Blocks.controls_for={init:function(){this.jsonInit({message0:Blockly.Msg.CONTROLS_FOR_TITLE,args0:[{type:"field_variable",name:"VAR",variable:null},{type:"input_value",name:"FROM",check:"Number",align:"RIGHT"},{type:"input_value",name:"TO",check:"Number",align:"RIGHT"},{type:"input_value",name:"BY",check:"Number",align:"RIGHT"}],inputsInline:!0,previousStatement:null,nextStatement:null,colour:Blockly.Blocks.loops.HUE,helpUrl:Blockly.Msg.CONTROLS_FOR_HELPURL});this.appendStatementInput("DO").appendField(Blockly.Msg.CONTROLS_FOR_INPUT_DO);
@@ -63,7 +63,7 @@ var a=this;this.setTooltip(function(){return Blockly.Msg.CONTROLS_FOR_TOOLTIP.re
Blockly.Blocks.controls_forEach={init:function(){this.jsonInit({message0:Blockly.Msg.CONTROLS_FOREACH_TITLE,args0:[{type:"field_variable",name:"VAR",variable:null},{type:"input_value",name:"LIST",check:"Array"}],previousStatement:null,nextStatement:null,colour:Blockly.Blocks.loops.HUE,helpUrl:Blockly.Msg.CONTROLS_FOREACH_HELPURL});this.appendStatementInput("DO").appendField(Blockly.Msg.CONTROLS_FOREACH_INPUT_DO);var a=this;this.setTooltip(function(){return Blockly.Msg.CONTROLS_FOREACH_TOOLTIP.replace("%1",
a.getFieldValue("VAR"))})},customContextMenu:Blockly.Blocks.controls_for.customContextMenu};
Blockly.Blocks.controls_flow_statements={init:function(){var a=[[Blockly.Msg.CONTROLS_FLOW_STATEMENTS_OPERATOR_BREAK,"BREAK"],[Blockly.Msg.CONTROLS_FLOW_STATEMENTS_OPERATOR_CONTINUE,"CONTINUE"]];this.setHelpUrl(Blockly.Msg.CONTROLS_FLOW_STATEMENTS_HELPURL);this.setColour(Blockly.Blocks.loops.HUE);this.appendDummyInput().appendField(new Blockly.FieldDropdown(a),"FLOW");this.setPreviousStatement(!0);var b=this;this.setTooltip(function(){var a=b.getFieldValue("FLOW");return{BREAK:Blockly.Msg.CONTROLS_FLOW_STATEMENTS_TOOLTIP_BREAK,
-CONTINUE:Blockly.Msg.CONTROLS_FLOW_STATEMENTS_TOOLTIP_CONTINUE}[a]})},onchange:function(a){a=!1;var b=this;do{if(-1!=this.LOOP_TYPES.indexOf(b.type)){a=!0;break}b=b.getSurroundParent()}while(b);a?this.setWarningText(null):this.setWarningText(Blockly.Msg.CONTROLS_FLOW_STATEMENTS_WARNING)},LOOP_TYPES:["controls_repeat","controls_repeat_ext","controls_forEach","controls_for","controls_whileUntil"]};Blockly.Blocks.math={};Blockly.Blocks.math.HUE=230;Blockly.Blocks.math_number={init:function(){this.setHelpUrl(Blockly.Msg.MATH_NUMBER_HELPURL);this.setColour(Blockly.Blocks.math.HUE);this.appendDummyInput().appendField(new Blockly.FieldNumber("0",Blockly.FieldTextInput.numberValidator),"NUM");this.setOutput(!0,"Number");var a=this;this.setTooltip(function(){var b=a.getParent();return b&&b.getInputsInline()&&b.tooltip||Blockly.Msg.MATH_NUMBER_TOOLTIP})}};
+CONTINUE:Blockly.Msg.CONTROLS_FLOW_STATEMENTS_TOOLTIP_CONTINUE}[a]})},onchange:function(a){a=!1;var b=this;do{if(-1!=this.LOOP_TYPES.indexOf(b.type)){a=!0;break}b=b.getSurroundParent()}while(b);a?this.setWarningText(null):this.setWarningText(Blockly.Msg.CONTROLS_FLOW_STATEMENTS_WARNING)},LOOP_TYPES:["controls_repeat","controls_repeat_ext","controls_forEach","controls_for","controls_whileUntil"]};Blockly.Blocks.math={};Blockly.Blocks.math.HUE=230;Blockly.Blocks.math_number={init:function(){this.setHelpUrl(Blockly.Msg.MATH_NUMBER_HELPURL);this.setColour(Blockly.Blocks.math.HUE);this.appendDummyInput().appendField(new Blockly.FieldNumber("0"),"NUM");this.setOutput(!0,"Number");var a=this;this.setTooltip(function(){var b=a.getParent();return b&&b.getInputsInline()&&b.tooltip||Blockly.Msg.MATH_NUMBER_TOOLTIP})}};
Blockly.Blocks.math_arithmetic={init:function(){this.jsonInit({message0:"%1 %2 %3",args0:[{type:"input_value",name:"A",check:"Number"},{type:"field_dropdown",name:"OP",options:[[Blockly.Msg.MATH_ADDITION_SYMBOL,"ADD"],[Blockly.Msg.MATH_SUBTRACTION_SYMBOL,"MINUS"],[Blockly.Msg.MATH_MULTIPLICATION_SYMBOL,"MULTIPLY"],[Blockly.Msg.MATH_DIVISION_SYMBOL,"DIVIDE"],[Blockly.Msg.MATH_POWER_SYMBOL,"POWER"]]},{type:"input_value",name:"B",check:"Number"}],inputsInline:!0,output:"Number",colour:Blockly.Blocks.math.HUE,
helpUrl:Blockly.Msg.MATH_ARITHMETIC_HELPURL});var a=this;this.setTooltip(function(){var b=a.getFieldValue("OP");return{ADD:Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_ADD,MINUS:Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_MINUS,MULTIPLY:Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_MULTIPLY,DIVIDE:Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_DIVIDE,POWER:Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_POWER}[b]})}};
Blockly.Blocks.math_single={init:function(){this.jsonInit({message0:"%1 %2",args0:[{type:"field_dropdown",name:"OP",options:[[Blockly.Msg.MATH_SINGLE_OP_ROOT,"ROOT"],[Blockly.Msg.MATH_SINGLE_OP_ABSOLUTE,"ABS"],["-","NEG"],["ln","LN"],["log10","LOG10"],["e^","EXP"],["10^","POW10"]]},{type:"input_value",name:"NUM",check:"Number"}],output:"Number",colour:Blockly.Blocks.math.HUE,helpUrl:Blockly.Msg.MATH_SINGLE_HELPURL});var a=this;this.setTooltip(function(){var b=a.getFieldValue("OP");return{ROOT:Blockly.Msg.MATH_SINGLE_TOOLTIP_ROOT,
diff --git a/core/block.js b/core/block.js
index bf6041416..92b533be8 100644
--- a/core/block.js
+++ b/core/block.js
@@ -1021,7 +1021,7 @@ Blockly.Block.prototype.jsonInit = function(json) {
* @private
*/
Blockly.Block.prototype.interpolate_ = function(message, args, lastDummyAlign) {
- var tokens = Blockly.tokenizeInterpolation(message);
+ var tokens = Blockly.utils.tokenizeInterpolation(message);
// Interpolate the arguments. Build a list of elements.
var indexDup = [];
var indexCount = 0;
@@ -1111,7 +1111,8 @@ Blockly.Block.prototype.interpolate_ = function(message, args, lastDummyAlign) {
element['width'], element['height'], element['alt']);
break;
case 'field_number':
- field = new Blockly.FieldNumber(element['text']);
+ field = new Blockly.FieldNumber(element['value'],
+ element['min'], element['max'], element['precision']);
break;
case 'field_date':
if (Blockly.FieldDate) {
diff --git a/core/block_svg.js b/core/block_svg.js
index 6a0df6f56..ee70a93d1 100644
--- a/core/block_svg.js
+++ b/core/block_svg.js
@@ -535,7 +535,6 @@ Blockly.BlockSvg.prototype.onMouseDown_ = function(e) {
Blockly.terminateDrag_();
this.select();
Blockly.hideChaff();
- this.workspace.recordDeleteAreas();
if (Blockly.isRightButton(e)) {
// Right-click.
this.showContextMenu_(e);
diff --git a/core/blockly.js b/core/blockly.js
index 4fd5de0ca..cda45992e 100644
--- a/core/blockly.js
+++ b/core/blockly.js
@@ -549,13 +549,22 @@ Blockly.addChangeListener = function(func) {
/**
* Returns the main workspace. Returns the last used main workspace (based on
- * focus).
+ * focus). Try not to use this function, particularly if there are multiple
+ * Blockly instances on a page.
* @return {!Blockly.Workspace} The main workspace.
*/
Blockly.getMainWorkspace = function() {
return Blockly.mainWorkspace;
};
+// IE9 does not have a console. Create a stub to stop errors.
+if (!goog.global['console']) {
+ goog.global['console'] = {
+ 'log': function() {},
+ 'warn': function() {}
+ };
+}
+
// Export symbols that would otherwise be renamed by Closure compiler.
if (!goog.global['Blockly']) {
goog.global['Blockly'] = {};
diff --git a/core/connection.js b/core/connection.js
index 65d7fd9cf..3b8c82af8 100644
--- a/core/connection.js
+++ b/core/connection.js
@@ -370,10 +370,14 @@ Blockly.Connection.prototype.isConnectionAllowed = function(candidate) {
}
// Don't let a block with no next connection bump other blocks out of the
- // stack.
+ // stack. But covering up a shadow block or stack of shadow blocks is fine.
+ // Similarly, replacing a terminal statement with another terminal statement
+ // is allowed.
if (this.type == Blockly.PREVIOUS_STATEMENT &&
candidate.isConnected() &&
- !this.sourceBlock_.nextConnection) {
+ !this.sourceBlock_.nextConnection &&
+ !candidate.targetBlock().isShadow() &&
+ candidate.targetBlock().nextConnection) {
return false;
}
diff --git a/core/events.js b/core/events.js
index 9b9974478..f78222d03 100644
--- a/core/events.js
+++ b/core/events.js
@@ -303,8 +303,10 @@ Blockly.Events.Abstract = function(block) {
Blockly.Events.Abstract.prototype.toJson = function() {
var json = {
'type': this.type,
- 'blockId': this.blockId
};
+ if (this.blockId) {
+ json['blockId'] = this.blockId;
+ }
if (this.group) {
json['group'] = this.group;
}
@@ -550,6 +552,10 @@ Blockly.Events.Change.prototype.run = function(forward) {
case 'field':
var field = block.getField(this.name);
if (field) {
+ // Run the validator for any side-effects it may have.
+ // The validator's opinion on validity is ignored.
+ var validator = field.getValidator();
+ validator && validator.call(field, value);
field.setValue(value);
} else {
console.warn("Can't set non-existant field: " + this.name);
@@ -780,3 +786,34 @@ Blockly.Events.Ui.prototype.fromJson = function(json) {
this.element = json['element'];
this.newValue = json['newValue'];
};
+
+/**
+ * Enable/disable a block depending on whether it is properly connected.
+ * Use this on applications where all blocks should be connected to a top block.
+ * Recommend setting the 'disable' option to 'false' in the config so that
+ * users don't try to reenable disabled orphan blocks.
+ * @param {!Blockly.Events.Abstract} event Custom data for event.
+ */
+Blockly.Events.disableOrphans = function(event) {
+ if (event.type == Blockly.Events.MOVE ||
+ event.type == Blockly.Events.CREATE) {
+ Blockly.Events.disable();
+ var workspace = Blockly.Workspace.getById(event.workspaceId);
+ var block = workspace.getBlockById(event.blockId);
+ if (block) {
+ if (block.getParent() && !block.getParent().disabled) {
+ do {
+ block.setDisabled(false);
+ block = block.getNextBlock();
+ } while (block);
+ } else if ((block.outputConnection || block.previousConnection) &&
+ Blockly.dragMode_ == Blockly.DRAG_NONE) {
+ do {
+ block.setDisabled(true);
+ block = block.getNextBlock();
+ } while (block);
+ }
+ }
+ Blockly.Events.enable();
+ }
+};
diff --git a/core/field.js b/core/field.js
index 305ecd6d6..0ea102fff 100644
--- a/core/field.js
+++ b/core/field.js
@@ -156,10 +156,6 @@ Blockly.Field.prototype.init = function() {
Blockly.bindEvent_(this.fieldGroup_, 'mouseup', this, this.onMouseUp_);
// Force a render.
this.updateTextNode_();
- if (Blockly.Events.isEnabled()) {
- Blockly.Events.fire(new Blockly.Events.Change(
- this.sourceBlock_, 'field', this.name, '', this.getValue()));
- }
};
/**
@@ -232,6 +228,14 @@ Blockly.Field.prototype.setValidator = function(handler) {
this.validator_ = handler;
};
+/**
+ * Gets the validation function for editable fields.
+ * @return {Function} Validation function, or null.
+ */
+Blockly.Field.prototype.getValidator = function() {
+ return this.validator_;
+};
+
/**
* Gets the group element for this editable field.
* Used for measuring the size and for positioning.
diff --git a/core/field_angle.js b/core/field_angle.js
index b5ca196ce..35534a426 100644
--- a/core/field_angle.js
+++ b/core/field_angle.js
@@ -305,16 +305,19 @@ Blockly.FieldAngle.prototype.updateGraph_ = function() {
* @return {?string} A string representing a valid angle, or null if invalid.
*/
Blockly.FieldAngle.angleValidator = function(text) {
- var n = Blockly.FieldTextInput.numberValidator(text);
- if (n !== null) {
- n = n % 360;
- if (n < 0) {
- n += 360;
- }
- if (n > Blockly.FieldAngle.WRAP) {
- n -= 360;
- }
- n = String(n);
+ if (text === null) {
+ return null;
}
- return n;
+ var n = parseFloat(text || 0);
+ if (isNaN(n)) {
+ return null;
+ }
+ n = n % 360;
+ if (n < 0) {
+ n += 360;
+ }
+ if (n > Blockly.FieldAngle.WRAP) {
+ n -= 360;
+ }
+ return String(n);
};
diff --git a/core/field_number.js b/core/field_number.js
index eb92291bf..000022998 100644
--- a/core/field_number.js
+++ b/core/field_number.js
@@ -27,10 +27,14 @@
goog.provide('Blockly.FieldNumber');
goog.require('Blockly.FieldTextInput');
+goog.require('goog.math');
/**
* Class for an editable number field.
- * @param {string} text The initial content of the field.
+ * @param {string} value The initial content of the field.
+ * @param {number|string|undefined} opt_min Minimum value.
+ * @param {number|string|undefined} opt_max Maximum value.
+ * @param {number|string|undefined} opt_precision Precision for value.
* @param {Function=} opt_validator An optional function that is called
* to validate any constraints on what the user entered. Takes the new
* text as an argument and returns either the accepted text, a replacement
@@ -38,8 +42,88 @@ goog.require('Blockly.FieldTextInput');
* @extends {Blockly.FieldTextInput}
* @constructor
*/
-Blockly.FieldNumber = function(text, opt_validator) {
- Blockly.FieldNumber.superClass_.constructor.call(this, text,
- opt_validator);
+Blockly.FieldNumber =
+ function(value, opt_min, opt_max, opt_precision, opt_validator) {
+ Blockly.FieldNumber.superClass_.constructor.call(this, value, opt_validator);
+ this.setConstraints(opt_min, opt_max, opt_precision);
};
goog.inherits(Blockly.FieldNumber, Blockly.FieldTextInput);
+
+/**
+ * Set the maximum, minimum and precision constraints on this field.
+ * Any of these properties may be undefiend or NaN to be disabled.
+ * Setting precision (usually a power of 10) enforces a minimum step between
+ * values. That is, the user's value will rounded to the closest multiple of
+ * precision. The least significant digit place is inferred from the precision.
+ * Integers values can be enforces by choosing an integer precision.
+ * @param {number|string|undefined} min Minimum value.
+ * @param {number|string|undefined} max Maximum value.
+ * @param {number|string|undefined} precision Precision for value.
+ */
+Blockly.FieldNumber.prototype.setConstraints = function(min, max, precision) {
+ precision = parseFloat(precision);
+ this.precision_ = isNaN(precision) ? 0 : precision;
+ min = parseFloat(min);
+ this.min_ = isNaN(min) ? -Infinity : min;
+ max = parseFloat(max);
+ this.max_ = isNaN(max) ? Infinity : max;
+ this.setValue(this.numberValidator(this.getValue));
+};
+
+/**
+ * Sets a new change handler for number field.
+ * @param {Function} handler New change handler, or null.
+ */
+Blockly.FieldNumber.prototype.setValidator = function(handler) {
+ var wrappedHandler;
+ if (handler) {
+ // Wrap the user's change handler together with the angle validator.
+ wrappedHandler = function(value) {
+ var v1 = handler.call(this, value);
+ if (v1 === null) {
+ var v2 = v1;
+ } else {
+ if (v1 === undefined) {
+ v1 = value;
+ }
+ var v2 = this.numberValidator(v1);
+ if (v2 === undefined) {
+ v2 = v1;
+ }
+ }
+ return v2 === value ? undefined : v2;
+ };
+ } else {
+ wrappedHandler = this.numberValidator;
+ }
+ Blockly.FieldNumber.superClass_.setValidator.call(this, wrappedHandler);
+};
+
+/**
+ * Ensure that only a number in the correct range may be entered.
+ * @param {string} text The user's text.
+ * @return {?string} A string representing a valid number, or null if invalid.
+ */
+Blockly.FieldNumber.prototype.numberValidator = function(text) {
+ if (text === null) {
+ return null;
+ }
+ text = String(text);
+ // TODO: Handle cases like 'ten', '1.203,14', etc.
+ // 'O' is sometimes mistaken for '0' by inexperienced users.
+ text = text.replace(/O/ig, '0');
+ // Strip out thousands separators.
+ text = text.replace(/,/g, '');
+ var n = parseFloat(text || 0);
+ if (isNaN(n)) {
+ // Invalid number.
+ return null;
+ }
+ // Round to nearest multiple of precision.
+ if (this.precision_ && Number.isFinite(n)) {
+ n = Math.round(n / this.precision_) * this.precision_;
+ }
+ // Get the value in range.
+ n = goog.math.clamp(n, this.min_, this.max_);
+ return String(n);
+};
diff --git a/core/field_textinput.js b/core/field_textinput.js
index 994db7263..6ef6bf1d2 100644
--- a/core/field_textinput.js
+++ b/core/field_textinput.js
@@ -296,6 +296,8 @@ Blockly.FieldTextInput.prototype.widgetDispose_ = function() {
* @return {?string} A string representing a valid number, or null if invalid.
*/
Blockly.FieldTextInput.numberValidator = function(text) {
+ console.warn('Blockly.FieldTextInput.numberValidator is deprecated. ' +
+ 'Use Blockly.FieldNumber instead.');
if (text === null) {
return null;
}
diff --git a/core/flyout.js b/core/flyout.js
index 66d5c2c6e..e8aa2f872 100644
--- a/core/flyout.js
+++ b/core/flyout.js
@@ -286,8 +286,8 @@ Blockly.Flyout.prototype.getMetrics_ = function() {
contentWidth: (optionBox.width + 2 * this.MARGIN) * this.workspace_.scale,
viewTop: -this.workspace_.scrollY,
viewLeft: -this.workspace_.scrollX,
- contentTop: 0, // TODO: #349
- contentLeft: 0, // TODO: #349
+ contentTop: optionBox.y,
+ contentLeft: optionBox.x,
absoluteTop: absoluteTop,
absoluteLeft: absoluteLeft
};
@@ -426,9 +426,8 @@ Blockly.Flyout.prototype.setBackgroundPathVertical_ = function(width, height) {
* rounded corners.
* @private
*/
-Blockly.Flyout.prototype.setBackgroundPathHorizontal_ =
- function(width, height) {
- /* eslint-disable indent */
+Blockly.Flyout.prototype.setBackgroundPathHorizontal_ = function(width,
+ height) {
var atTop = this.toolboxPosition_ == Blockly.TOOLBOX_AT_TOP;
// Start at top left.
var path = ['M 0,' + (atTop ? 0 : this.CORNER_RADIUS)];
@@ -461,7 +460,7 @@ Blockly.Flyout.prototype.setBackgroundPathHorizontal_ =
path.push('z');
}
this.svgBackground_.setAttribute('d', path.join(' '));
-}; /* eslint-enable indent */
+};
/**
* Scroll the flyout to the top.
@@ -588,7 +587,6 @@ Blockly.Flyout.prototype.show = function(xmlList) {
}
this.reflow();
- this.offsetHorizontalRtlBlocks(this.workspace_.getTopBlocks(false));
this.filterForCapacity_();
// Correctly position the flyout's scrollbar when it opens.
@@ -605,9 +603,12 @@ Blockly.Flyout.prototype.show = function(xmlList) {
* @private
*/
Blockly.Flyout.prototype.layoutBlocks_ = function(blocks, gaps) {
- var margin = this.MARGIN * this.workspace_.scale;
+ var margin = this.MARGIN;
var cursorX = this.RTL ? margin : margin + Blockly.BlockSvg.TAB_WIDTH;
var cursorY = margin;
+ if (this.horizontalLayout_ && this.RTL) {
+ blocks = blocks.reverse();
+ }
for (var i = 0, block; block = blocks[i]; i++) {
var allBlocks = block.getDescendants();
for (var j = 0, child; child = allBlocks[j]; j++) {
@@ -623,8 +624,13 @@ Blockly.Flyout.prototype.layoutBlocks_ = function(blocks, gaps) {
if (this.horizontalLayout_) {
cursorX += tab;
}
- block.moveBy((this.horizontalLayout_ && this.RTL) ? -cursorX : cursorX,
- cursorY);
+
+ if (this.horizontalLayout_ && this.RTL) {
+ block.moveBy(cursorX + blockHW.width - tab, cursorY);
+ } else {
+ block.moveBy(cursorX, cursorY);
+ }
+
if (this.horizontalLayout_) {
cursorX += (blockHW.width + gaps[i] - tab);
} else {
@@ -952,6 +958,10 @@ Blockly.Flyout.prototype.filterForCapacity_ = function() {
* @return {goog.math.Rect} Rectangle in which to delete.
*/
Blockly.Flyout.prototype.getClientRect = function() {
+ if (!this.svgGroup_) {
+ return null;
+ }
+
var flyoutRect = this.svgGroup_.getBoundingClientRect();
// BIG_NUM is offscreen padding so that blocks dragged beyond the shown flyout
// area are still deleted. Must be larger than the largest screen size,
@@ -1070,10 +1080,9 @@ Blockly.Flyout.prototype.reflowVertical = function(blocks) {
if (this.RTL) {
// With the flyoutWidth known, right-align the blocks.
var oldX = block.getRelativeToSurfaceXY().x;
- var dx = flyoutWidth - this.MARGIN;
- dx /= this.workspace_.scale;
- dx -= Blockly.BlockSvg.TAB_WIDTH;
- block.moveBy(dx - oldX, 0);
+ var newX = flyoutWidth / this.workspace_.scale - this.MARGIN;
+ newX -= Blockly.BlockSvg.TAB_WIDTH;
+ block.moveBy(newX - oldX, 0);
}
if (block.flyoutRect_) {
block.flyoutRect_.setAttribute('width', blockHW.width);
@@ -1105,41 +1114,16 @@ Blockly.Flyout.prototype.reflowVertical = function(blocks) {
* Reflow blocks and their buttons.
*/
Blockly.Flyout.prototype.reflow = function() {
+ if (this.reflowWrapper_) {
+ this.workspace_.removeChangeListener(this.reflowWrapper_);
+ }
var blocks = this.workspace_.getTopBlocks(false);
if (this.horizontalLayout_) {
this.reflowHorizontal(blocks);
} else {
this.reflowVertical(blocks);
}
-};
-
-/**
- * In the horizontal RTL case all of the blocks will be laid out to the left of
- * the origin, but we won't know how big the workspace is until the layout pass
- * is done.
- * Now that it's done, shunt all the blocks to be right of the origin.
- * @param {!Array} blocks The blocks to reposition.
- */
-Blockly.Flyout.prototype.offsetHorizontalRtlBlocks = function(blocks) {
- if (this.horizontalLayout_ && this.RTL) {
- // We don't know this workspace's view width yet.
- this.position();
- try {
- var optionBox = this.workspace_.getCanvas().getBBox();
- } catch (e) {
- // Firefox has trouble with hidden elements (Bug 528969).
- optionBox = {height: 0, y: 0, width: 0, x: 0};
- }
-
- var offset = Math.max(-optionBox.x + this.MARGIN,
- this.width_ / this.workspace_.scale);
-
- for (var i = 0, block; block = blocks[i]; i++) {
- block.moveBy(offset, 0);
- if (block.flyoutRect_) {
- block.flyoutRect_.setAttribute('x',
- offset + Number(block.flyoutRect_.getAttribute('x')));
- }
- }
+ if (this.reflowWrapper_) {
+ this.workspace_.addChangeListener(this.reflowWrapper_);
}
};
diff --git a/core/generator.js b/core/generator.js
index 3c6a5c7e1..8ee110f86 100644
--- a/core/generator.js
+++ b/core/generator.js
@@ -77,6 +77,12 @@ Blockly.Generator.prototype.INDENT = ' ';
*/
Blockly.Generator.prototype.COMMENT_WRAP = 60;
+/**
+ * List of outer-inner pairings that do NOT require parentheses.
+ * @type {!Array.>}
+ */
+Blockly.Generator.prototype.ORDER_OVERRIDES = [];
+
/**
* Generate code for all blocks in the workspace to the specified language.
* @param {Blockly.Workspace} workspace Workspace to generate code from.
@@ -198,13 +204,13 @@ Blockly.Generator.prototype.blockToCode = function(block) {
* Generate code representing the specified value input.
* @param {!Blockly.Block} block The block containing the input.
* @param {string} name The name of the input.
- * @param {number} order The maximum binding strength (minimum order value)
+ * @param {number} outerOrder The maximum binding strength (minimum order value)
* of any operators adjacent to "block".
* @return {string} Generated code or '' if no blocks are connected or the
* specified input does not exist.
*/
-Blockly.Generator.prototype.valueToCode = function(block, name, order) {
- if (isNaN(order)) {
+Blockly.Generator.prototype.valueToCode = function(block, name, outerOrder) {
+ if (isNaN(outerOrder)) {
goog.asserts.fail('Expecting valid order from block "%s".', block.type);
}
var targetBlock = block.getInputTargetBlock(name);
@@ -226,8 +232,17 @@ Blockly.Generator.prototype.valueToCode = function(block, name, order) {
goog.asserts.fail('Expecting valid order from value block "%s".',
targetBlock.type);
}
- if (code && order <= innerOrder) {
- if (order == innerOrder && (order == 0 || order == 99)) {
+ if (!code) {
+ return '';
+ }
+
+ // Add parentheses if needed.
+ var parensNeeded = false;
+ var outerOrderClass = Math.floor(outerOrder);
+ var innerOrderClass = Math.floor(innerOrder);
+ if (outerOrderClass <= innerOrderClass) {
+ if (outerOrderClass == innerOrderClass &&
+ (outerOrderClass == 0 || outerOrderClass == 99)) {
// Don't generate parens around NONE-NONE and ATOMIC-ATOMIC pairs.
// 0 is the atomic order, 99 is the none order. No parentheses needed.
// In all known languages multiple such code blocks are not order
@@ -236,11 +251,22 @@ Blockly.Generator.prototype.valueToCode = function(block, name, order) {
// The operators outside this code are stonger than the operators
// inside this code. To prevent the code from being pulled apart,
// wrap the code in parentheses.
- // Technically, this should be handled on a language-by-language basis.
- // However all known (sane) languages use parentheses for grouping.
- code = '(' + code + ')';
+ parensNeeded = true;
+ // Check for special exceptions.
+ for (var i = 0; i < this.ORDER_OVERRIDES.length; i++) {
+ if (this.ORDER_OVERRIDES[i][0] == outerOrder &&
+ this.ORDER_OVERRIDES[i][1] == innerOrder) {
+ parensNeeded = false;
+ break;
+ }
+ }
}
}
+ if (parensNeeded) {
+ // Technically, this should be handled on a language-by-language basis.
+ // However all known (sane) languages use parentheses for grouping.
+ code = '(' + code + ')';
+ }
return code;
};
diff --git a/core/rendered_connection.js b/core/rendered_connection.js
index c77c53b3a..ef7130dc6 100644
--- a/core/rendered_connection.js
+++ b/core/rendered_connection.js
@@ -85,7 +85,8 @@ Blockly.RenderedConnection.prototype.bumpAwayFrom_ = function(staticConnection)
reverse = true;
}
// Raise it to the top for extra visibility.
- rootBlock.getSvgRoot().parentNode.appendChild(rootBlock.getSvgRoot());
+ var selected = Blockly.selected == rootBlock;
+ selected || rootBlock.select();
var dx = (staticConnection.x_ + Blockly.SNAP_RADIUS) - this.x_;
var dy = (staticConnection.y_ + Blockly.SNAP_RADIUS) - this.y_;
if (reverse) {
@@ -96,6 +97,7 @@ Blockly.RenderedConnection.prototype.bumpAwayFrom_ = function(staticConnection)
dx = -dx;
}
rootBlock.moveBy(dx, dy);
+ selected || rootBlock.unselect();
};
/**
diff --git a/core/scrollbar.js b/core/scrollbar.js
index ee080b0bc..da2dd942d 100644
--- a/core/scrollbar.js
+++ b/core/scrollbar.js
@@ -606,7 +606,8 @@ Blockly.Scrollbar.prototype.onMouseDownBar_ = function(e) {
e.stopPropagation();
return;
}
- var mouseXY = Blockly.mouseToSvg(e, this.workspace_.getParentSvg());
+ var mouseXY = Blockly.mouseToSvg(e, this.workspace_.getParentSvg(),
+ this.workspace_.getInverseScreenCTM());
var mouseLocation = this.horizontal_ ? mouseXY.x : mouseXY.y;
var handleXY = Blockly.getSvgXY_(this.svgHandle_, this.workspace_);
@@ -693,7 +694,7 @@ Blockly.Scrollbar.prototype.onMouseUpHandle_ = function() {
* @private
*/
Blockly.Scrollbar.prototype.constrainHandle_ = function(value) {
- if (value <= 0 || isNaN(value)) {
+ if (value <= 0 || isNaN(value) || this.scrollViewSize_ < this.handleLength_) {
value = 0;
} else {
value = Math.min(value, this.scrollViewSize_ - this.handleLength_);
diff --git a/core/toolbox.js b/core/toolbox.js
index d9ca56dcc..b5b27f869 100644
--- a/core/toolbox.js
+++ b/core/toolbox.js
@@ -390,6 +390,10 @@ Blockly.Toolbox.prototype.clearSelection = function() {
* @return {goog.math.Rect} Rectangle in which to delete.
*/
Blockly.Toolbox.prototype.getClientRect = function() {
+ if (!this.HtmlDiv) {
+ return null;
+ }
+
// BIG_NUM is offscreen padding so that blocks dragged beyond the toolbox
// area are still deleted. Must be smaller than Infinity, but larger than
// the largest screen size.
diff --git a/core/trashcan.js b/core/trashcan.js
index 0a2954308..28baa0f20 100644
--- a/core/trashcan.js
+++ b/core/trashcan.js
@@ -265,6 +265,10 @@ Blockly.Trashcan.prototype.position = function() {
* @return {goog.math.Rect} Rectangle in which to delete.
*/
Blockly.Trashcan.prototype.getClientRect = function() {
+ if (!this.svgGroup_) {
+ return null;
+ }
+
var trashRect = this.svgGroup_.getBoundingClientRect();
var left = trashRect.left + this.SPRITE_LEFT_ - this.MARGIN_HOTSPOT_;
var top = trashRect.top + this.SPRITE_TOP_ - this.MARGIN_HOTSPOT_;
diff --git a/core/utils.js b/core/utils.js
index 9b9e04d60..fe13572ac 100644
--- a/core/utils.js
+++ b/core/utils.js
@@ -308,14 +308,17 @@ Blockly.isRightButton = function(e) {
* The origin (0,0) is the top-left corner of the Blockly svg.
* @param {!Event} e Mouse event.
* @param {!Element} svg SVG element.
+ * @param {SVGMatrix} matrix Inverted screen CTM to use.
* @return {!Object} Object with .x and .y properties.
*/
-Blockly.mouseToSvg = function(e, svg) {
+Blockly.mouseToSvg = function(e, svg, matrix) {
var svgPoint = svg.createSVGPoint();
svgPoint.x = e.clientX;
svgPoint.y = e.clientY;
- var matrix = svg.getScreenCTM();
- matrix = matrix.inverse();
+
+ if (!matrix) {
+ matrix = svg.getScreenCTM().inverse();
+ }
return svgPoint.matrixTransform(matrix);
};
@@ -420,7 +423,7 @@ Blockly.isNumber = function(str) {
* @param {string} message Text containing interpolation tokens.
* @return {!Array.} Array of strings and numbers.
*/
-Blockly.tokenizeInterpolation = function(message) {
+Blockly.utils.tokenizeInterpolation = function(message) {
var tokens = [];
var chars = message.split('');
chars.push(''); // End marker.
diff --git a/core/workspace_svg.js b/core/workspace_svg.js
index de636a7a2..d857bdf21 100644
--- a/core/workspace_svg.js
+++ b/core/workspace_svg.js
@@ -144,6 +144,28 @@ Blockly.WorkspaceSvg.prototype.scrollbar = null;
*/
Blockly.WorkspaceSvg.prototype.lastSound_ = null;
+/**
+ * Inverted screen CTM, for use in mouseToSvg.
+ * @type {SVGMatrix}
+ * @private
+ */
+Blockly.WorkspaceSvg.prototype.inverseScreenCTM_ = null;
+
+/**
+ * Getter for the inverted screen CTM.
+ * @return {SVGMatrix} The matrix to use in mouseToSvg
+ */
+Blockly.WorkspaceSvg.prototype.getInverseScreenCTM = function() {
+ return this.inverseScreenCTM_;
+};
+
+/**
+ * Update the inverted screen CTM.
+ */
+Blockly.WorkspaceSvg.prototype.updateInverseScreenCTM = function() {
+ this.inverseScreenCTM_ = this.getParentSvg().getScreenCTM().inverse();
+};
+
/**
* Save resize handler data so we can delete it later in dispose.
* @param {!Array.} handler Data that can be passed to unbindEvent_.
@@ -211,6 +233,7 @@ Blockly.WorkspaceSvg.prototype.createDom = function(opt_backgroundClass) {
this.addFlyout_();
}
this.updateGridPattern_();
+ this.recordDeleteAreas();
return this.svgGroup_;
};
@@ -330,6 +353,7 @@ Blockly.WorkspaceSvg.prototype.resizeContents = function() {
// based on contents.
this.scrollbar.resize();
}
+ this.updateInverseScreenCTM();
};
/**
@@ -355,6 +379,9 @@ Blockly.WorkspaceSvg.prototype.resize = function() {
if (this.scrollbar) {
this.scrollbar.resize();
}
+
+ this.updateInverseScreenCTM();
+ this.recordDeleteAreas();
};
/**
@@ -661,7 +688,8 @@ Blockly.WorkspaceSvg.prototype.onMouseDown_ = function(e) {
*/
Blockly.WorkspaceSvg.prototype.startDrag = function(e, xy) {
// Record the starting offset between the bubble's location and the mouse.
- var point = Blockly.mouseToSvg(e, this.getParentSvg());
+ var point = Blockly.mouseToSvg(e, this.getParentSvg(),
+ this.getInverseScreenCTM());
// Fix scale of mouse event.
point.x /= this.scale;
point.y /= this.scale;
@@ -674,7 +702,8 @@ Blockly.WorkspaceSvg.prototype.startDrag = function(e, xy) {
* @return {!goog.math.Coordinate} New location of object.
*/
Blockly.WorkspaceSvg.prototype.moveDrag = function(e) {
- var point = Blockly.mouseToSvg(e, this.getParentSvg());
+ var point = Blockly.mouseToSvg(e, this.getParentSvg(),
+ this.getInverseScreenCTM());
// Fix scale of mouse event.
point.x /= this.scale;
point.y /= this.scale;
@@ -690,7 +719,8 @@ Blockly.WorkspaceSvg.prototype.onMouseWheel_ = function(e) {
// TODO: Remove terminateDrag and compensate for coordinate skew during zoom.
Blockly.terminateDrag_();
var delta = e.deltaY > 0 ? -1 : 1;
- var position = Blockly.mouseToSvg(e, this.getParentSvg());
+ var position = Blockly.mouseToSvg(e, this.getParentSvg(),
+ this.getInverseScreenCTM());
this.zoom(position.x, position.y, delta);
e.preventDefault();
};
diff --git a/dart_compressed.js b/dart_compressed.js
index edc056c6d..963bbc380 100644
--- a/dart_compressed.js
+++ b/dart_compressed.js
@@ -19,11 +19,11 @@ Blockly.Dart.colour_blend=function(a){var b=Blockly.Dart.valueToCode(a,"COLOUR1"
" num bn = (b1 * (1 - ratio) + b2 * ratio).round();"," String bs = bn.toInt().toRadixString(16);"," rs = '0$rs';"," rs = rs.substring(rs.length - 2);"," gs = '0$gs';"," gs = gs.substring(gs.length - 2);"," bs = '0$bs';"," bs = bs.substring(bs.length - 2);"," return '#$rs$gs$bs';","}"])+"("+b+", "+c+", "+a+")",Blockly.Dart.ORDER_UNARY_POSTFIX]};Blockly.Dart.lists={};Blockly.Dart.addReservedWords("Math");Blockly.Dart.lists_create_empty=function(a){return["[]",Blockly.Dart.ORDER_ATOMIC]};Blockly.Dart.lists_create_with=function(a){for(var b=Array(a.itemCount_),c=0;c
-
+
@@ -83,7 +83,7 @@
-
+
@@ -112,7 +112,7 @@
-
+
@@ -206,7 +206,7 @@
-
+
@@ -231,7 +231,7 @@
- text
+ {textVariable}
@@ -243,14 +243,14 @@
- text
+ {textVariable}
- text
+ {textVariable}
@@ -283,7 +283,7 @@
-
+
@@ -300,28 +300,28 @@
- list
+ {listVariable}
- list
+ {listVariable}
- list
+ {listVariable}
- list
+ {listVariable}
@@ -334,7 +334,7 @@
-
+
@@ -373,8 +373,8 @@
-
-
+
+