Handle the finer points for setting focus correctly after deleting blocks from the workspace.

This commit is contained in:
Sean Lip
2016-11-22 17:58:37 -08:00
parent 5c79d2f36f
commit e8e8de93bb
4 changed files with 42 additions and 37 deletions

View File

@@ -65,13 +65,16 @@ blocklyApp.BlockOptionsModalComponent = ng.core.Component({
this.modalIsVisible = false;
this.actionButtonsInfo = [];
this.activeActionButtonIndex = 0;
this.onCancelCallback = null;
var that = this;
this.blockOptionsModalService.registerPreShowHook(
function(newActionButtonsInfo) {
function(newActionButtonsInfo, onCancelCallback) {
that.modalIsVisible = true;
that.actionButtonsInfo = newActionButtonsInfo;
that.activeActionButtonIndex = 0;
that.onCancelCallback = onCancelCallback;
that.keyboardInputService.setOverride({
// Tab key: no-op.
'9': function(evt) {
@@ -92,11 +95,15 @@ blocklyApp.BlockOptionsModalComponent = ng.core.Component({
if (that.activeActionButtonIndex <
that.actionButtonsInfo.length) {
that.actionButtonsInfo[that.activeActionButtonIndex].action();
} else {
that.onCancelCallback();
}
that.hideModal();
},
// Escape key: closes the modal.
'27': function() {
that.onCancelCallback();
that.hideModal();
},
// Up key: navigates to the previous item in the list.

View File

@@ -32,19 +32,19 @@ blocklyApp.BlockOptionsModalService = ng.core.Class({
'before it can be shown.');
};
this.modalIsShown = false;
this.onHideCallback = null;
this.onCancelCallback = null;
}],
registerPreShowHook: function(preShowHook) {
this.preShowHook = function() {
preShowHook(this.actionButtonsInfo);
preShowHook(this.actionButtonsInfo, this.onCancelCallback);
};
},
isModalShown: function() {
return this.modalIsShown;
},
showModal: function(actionButtonsInfo, onHideCallback) {
showModal: function(actionButtonsInfo, onCancelCallback) {
this.actionButtonsInfo = actionButtonsInfo;
this.onHideCallback = onHideCallback;
this.onCancelCallback = onCancelCallback;
if (this.preShowHook) {
this.preShowHook();
@@ -53,8 +53,5 @@ blocklyApp.BlockOptionsModalService = ng.core.Class({
},
hideModal: function() {
this.modalIsShown = false;
if (this.onHideCallback) {
this.onHideCallback();
}
}
});

View File

@@ -42,34 +42,18 @@ blocklyApp.TreeService = ng.core.Class({
}
],
// Returns a list of all top-level workspace tree nodes on the page.
getWorkspaceTreeNodes_: function() {
return Array.from(document.querySelectorAll('ol.blocklyWorkspaceTree'));
getWorkspaceFocusTargets_: function() {
return Array.from(
document.querySelectorAll('.blocklyWorkspaceFocusTarget'));
},
getSidebarButtonNodes_: function() {
return Array.from(document.querySelectorAll('button.blocklySidebarButton'));
},
// Returns a list of all top-level tree nodes on the page.
getAllTreeNodes_: function() {
return this.getWorkspaceTreeNodes_().concat(
return this.getWorkspaceFocusTargets_().concat(
this.getSidebarButtonNodes_());
},
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 (which may be a button in the sidebar).
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];
}
}
}
},
focusOnCurrentTree_: function(treeId) {
var trees = this.getAllTreeNodes_();
for (var i = 0; i < trees.length; i++) {
@@ -224,12 +208,13 @@ blocklyApp.TreeService = ng.core.Class({
console.error('Could not handle deletion of block.' + blockRootNode);
},
notifyUserAboutCurrentTree_: function(treeId) {
var workspaceTreeNodes = this.getWorkspaceTreeNodes_();
for (var i = 0; i < workspaceTreeNodes.length; i++) {
if (workspaceTreeNodes[i].id == treeId) {
var workspaceFocusTargets = this.getWorkspaceFocusTargets_();
for (var i = 0; i < workspaceFocusTargets.length; i++) {
if (workspaceFocusTargets[i].tagName == 'OL' &&
workspaceFocusTargets[i].id == treeId) {
this.notificationsService.setStatusMessage(
'Now in workspace group ' + (i + 1) + ' of ' +
workspaceTreeNodes.length);
workspaceFocusTargets.length);
}
}
},
@@ -252,12 +237,26 @@ blocklyApp.TreeService = ng.core.Class({
// - Otherwise, it sets the correct new active desc for the current tree.
var treeId = this.getTreeIdForBlock(block.id);
if (this.isIsolatedTopLevelBlock_(block)) {
var nextNodeToFocusOn = this.getNodeToFocusOnWhenTreeIsDeleted(treeId);
// Find the node to focus on after the deletion happens.
var nextNodeToFocusOn = null;
var focusTargets = this.getWorkspaceFocusTargets_();
for (var i = 0; i < focusTargets.length; i++) {
if (focusTargets[i].id == treeId) {
if (i + 1 < focusTargets.length) {
nextNodeToFocusOn = focusTargets[i + 1];
} else if (i > 0) {
nextNodeToFocusOn = focusTargets[i - 1];
}
break;
}
}
this.clearActiveDesc(treeId);
deleteBlockFunc();
// Invoke a digest cycle, so that the DOM settles.
setTimeout(function() {
nextNodeToFocusOn = nextNodeToFocusOn || document.getElementById(
'blocklyEmptyWorkspaceButton');
nextNodeToFocusOn.focus();
});
} else {
@@ -313,6 +312,7 @@ blocklyApp.TreeService = ng.core.Class({
translationIdForText: 'MARK_SPOT_BEFORE',
action: function() {
that.clipboardService.markConnection(block.previousConnection);
that.focusOnBlock(block.id);
},
isDisabled: function() {
return !block.previousConnection;
@@ -320,6 +320,7 @@ blocklyApp.TreeService = ng.core.Class({
}, {
action: function() {
that.clipboardService.markConnection(block.nextConnection);
that.focusOnBlock(block.id);
},
translationIdForText: 'MARK_SPOT_AFTER',
isDisabled: function() {
@@ -391,8 +392,6 @@ blocklyApp.TreeService = ng.core.Class({
}];
this.blockOptionsModalService.showModal(actionButtonsInfo, function() {
// TODO(sll): If there are no blocks in the workspace, focus on the
// entire workspace instead.
that.focusOnBlock(block.id);
});
},

View File

@@ -32,7 +32,7 @@ blocklyApp.WorkspaceComponent = ng.core.Component({
<div *ngIf="workspace" class="blocklyWorkspace">
<ol #tree *ngFor="#block of workspace.topBlocks_; #i = index"
tabindex="0" role="tree" class="blocklyTree blocklyWorkspaceTree"
tabindex="0" role="tree" class="blocklyTree blocklyWorkspaceFocusTarget"
[attr.aria-activedescendant]="getActiveDescId(tree.id)"
[attr.aria-labelledby]="workspaceTitle.id"
(keydown)="onKeypress($event, tree)">
@@ -43,7 +43,9 @@ blocklyApp.WorkspaceComponent = ng.core.Component({
<span *ngIf="workspace.topBlocks_.length === 0">
<p>
There are no blocks in the workspace.
<button (click)="showToolboxModalForCreateNewGroup()">
<button (click)="showToolboxModalForCreateNewGroup()"
class="blocklyWorkspaceFocusTarget"
id="blocklyEmptyWorkspaceButton">
Create new block group...
</button>
</p>