Make screenreader focus behave correctly when cutting, moving or deleting a block. Unmark the marked spot after a block has been moved or copied to it.

This commit is contained in:
Sean Lip
2016-06-30 14:52:12 -07:00
parent 555eac8b7f
commit be664dcdb6
3 changed files with 79 additions and 34 deletions

View File

@@ -111,5 +111,7 @@ blocklyApp.ClipboardService = ng.core
Blockly.Msg.PASTED_BLOCK_TO_MARKED_SPOT_MSG +
reconstitutedBlock.toString());
}
this.markedConnection_ = null;
}
});

View File

@@ -117,9 +117,10 @@ blocklyApp.TreeService = ng.core
},
// 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);
runWhilePreservingFocus: function(func, treeId, optionalNewActiveDescId) {
var oldDescId = this.getActiveDescId(treeId);
var newDescId = optionalNewActiveDescId || oldDescId;
this.unmarkActiveDesc_(oldDescId);
func();
// The timeout is needed in order to give the DOM time to stabilize
@@ -127,8 +128,8 @@ blocklyApp.TreeService = ng.core
// pasteAbove().
var that = this;
setTimeout(function() {
that.markActiveDesc_(activeDescId);
that.activeDescendantIds_[treeId] = activeDescId;
that.markActiveDesc_(newDescId);
that.activeDescendantIds_[treeId] = newDescId;
document.getElementById(treeId).focus();
}, 0);
},
@@ -153,11 +154,43 @@ blocklyApp.TreeService = ng.core
isButtonOrFieldNode_: function(node) {
return ['BUTTON', 'INPUT'].indexOf(node.tagName) != -1;
},
getNextActiveDescWhenBlockIsDeleted: function(blockRootNode) {
// Go up a level, if possible.
var nextNode = blockRootNode.parentNode;
while (nextNode && nextNode.tagName != 'LI') {
nextNode = nextNode.parentNode;
}
if (nextNode) {
return nextNode;
}
// Otherwise, go to the next sibling.
var nextSibling = this.getNextSibling(blockRootNode);
if (nextSibling) {
return nextSibling;
}
// Otherwise, go to the previous sibling.
var previousSibling = this.getPreviousSibling(blockRootNode);
if (previousSibling) {
return previousSibling;
}
// Otherwise, this is a top-level isolated block, which means that
// something's gone wrong and this function should not have been called
// in the first place.
console.error('Could not handle deletion of block.' + blockRootNode);
},
onKeypress: function(e, tree) {
var treeId = tree.id;
var activeDesc = document.getElementById(this.getActiveDescId(treeId));
if (!activeDesc) {
console.log('ERROR: no active descendant for current tree.');
console.error('ERROR: no active descendant for current tree.');
// TODO(sll): This just gives the focus somewhere to go in the event
// of an error, but we need to generalize this to other trees (both
// within and outside the workspace).
this.setActiveDesc(
blocklyApp.workspace.topBlocks_[0].id + 'blockSummary', treeId);
return;
}

View File

@@ -168,7 +168,7 @@ blocklyApp.WorkspaceTreeComponent = ng.core
var elementsNeedingIds = this.getElementsNeedingIds_();
this.idMap = {}
this.idMap['parentList'] = this.utilsService.generateUniqueId();
this.idMap['parentList'] = this.block.id + 'parent';
for (var i = 0; i < elementsNeedingIds.length; i++) {
this.idMap[elementsNeedingIds[i]] =
this.block.id + elementsNeedingIds[i];
@@ -198,10 +198,15 @@ blocklyApp.WorkspaceTreeComponent = ng.core
isCompatibleWithClipboard: function(connection) {
return this.clipboardService.isCompatibleWithClipboard(connection);
},
isTopLevelBlock: function(block) {
return blocklyApp.workspace.topBlocks_.some(function(topBlock) {
return topBlock.id == block.id;
});
isIsolatedTopLevelBlock: function(block) {
// Returns whether the given block is at the top level, and has no
// siblings.
return Boolean(
!block.nextConnection.targetConnection &&
!block.previousConnection.targetConnection &&
blocklyApp.workspace.topBlocks_.some(function(topBlock) {
return topBlock.id == block.id;
}));
},
generateAriaLabelledByAttr: function(mainLabel, secondLabel, isDisabled) {
return this.utilsService.generateAriaLabelledByAttr(
@@ -219,40 +224,45 @@ blocklyApp.WorkspaceTreeComponent = ng.core
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);
getRootNode: function() {
// Gets the root HTML node for this component.
return document.getElementById(this.idMap['parentList']);
},
removeBlockAndSetFocus_: function(block, deleteBlockFunc) {
// This method runs the given function and then does one of two things:
// - If the block is an isolated top-level block, it shifts the tree
// focus.
// - Otherwise, it sets the correct new active desc for the current tree.
if (this.isIsolatedTopLevelBlock(block)) {
var nextNodeToFocusOn =
this.treeService.getNodeToFocusOnWhenTreeIsDeleted(this.tree.id);
deleteBlockFunc();
nextNodeToFocusOn.focus();
} else {
// TODO(sll): Change the active descendant of the tree.
this.clipboardService.cut(block);
var nextActiveDesc =
this.treeService.getNextActiveDescWhenBlockIsDeleted(
this.getRootNode());
this.treeService.runWhilePreservingFocus(
deleteBlockFunc, this.tree.id, nextActiveDesc.id);
}
},
cutToClipboard: function(block) {
var that = this;
this.removeBlockAndSetFocus_(block, function() {
that.clipboardService.cut(block);
});
},
deleteBlock: function(block) {
if (this.isTopLevelBlock(block)) {
nextNodeToFocusOn = this.treeService.getNodeToFocusOnWhenTreeIsDeleted(
this.tree.id);
this.removeBlockAndSetFocus_(block, function() {
block.dispose(true);
nextNodeToFocusOn.focus();
} else {
// TODO(sll): Change the active descendant of the tree.
block.dispose(true);
}
});
},
sendToMarkedSpot: function(block) {
this.clipboardService.pasteToMarkedConnection(block, false);
if (this.isTopLevelBlock(block)) {
nextNodeToFocusOn = this.treeService.getNodeToFocusOnWhenTreeIsDeleted(
this.tree.id);
this.removeBlockAndSetFocus_(block, function() {
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());
}