Adding the common modal class. (#1017)

Centralizes accessible modal behavior.
This commit is contained in:
CoryDCode
2017-04-05 16:06:45 -07:00
committed by GitHub
parent 4b44635867
commit 33355415df
9 changed files with 130 additions and 303 deletions

View File

@@ -36,11 +36,11 @@ blocklyApp.AppComponent = ng.core.Component({
<span aria-live="polite" role="status">{{getAriaLiveReadout()}}</span>
</div>
<blockly-block-options-modal></blockly-block-options-modal>
<blockly-toolbox-modal></blockly-toolbox-modal>
<blockly-add-variable-modal></blockly-add-variable-modal>
<blockly-rename-variable-modal></blockly-rename-variable-modal>
<blockly-remove-variable-modal></blockly-remove-variable-modal>
<blockly-toolbox-modal></blockly-toolbox-modal>
<blockly-block-options-modal></blockly-block-options-modal>
<label id="blockly-translate-button" aria-hidden="true" hidden>
{{'BUTTON'|translate}}

View File

@@ -39,7 +39,7 @@ blocklyApp.BlockOptionsModalComponent = ng.core.Component({
*ngFor="#buttonInfo of actionButtonsInfo; #buttonIndex=index">
<button [id]="getOptionId(buttonIndex)"
(click)="buttonInfo.action(); hideModal();"
[ngClass]="{activeButton: activeActionButtonIndex == buttonIndex}">
[ngClass]="{activeButton: activeButtonIndex == buttonIndex}">
{{buttonInfo.translationIdForText|translate}}
</button>
</div>
@@ -48,7 +48,7 @@ blocklyApp.BlockOptionsModalComponent = ng.core.Component({
<div class="blocklyModalButtonContainer">
<button [id]="getCancelOptionId()"
(click)="dismissModal()"
[ngClass]="{activeButton: activeActionButtonIndex == actionButtonsInfo.length}">
[ngClass]="{activeButton: activeButtonIndex == actionButtonsInfo.length}">
{{'CANCEL'|translate}}
</button>
</div>
@@ -68,7 +68,7 @@ blocklyApp.BlockOptionsModalComponent = ng.core.Component({
this.modalIsVisible = false;
this.actionButtonsInfo = [];
this.activeActionButtonIndex = -1;
this.activeButtonIndex = -1;
this.onDismissCallback = null;
var that = this;
@@ -79,67 +79,26 @@ blocklyApp.BlockOptionsModalComponent = ng.core.Component({
that.activeActionButtonIndex = -1;
that.onDismissCallback = onDismissCallback;
that.keyboardInputService.setOverride({
// Tab key: navigates to the previous or next item in the list.
'9': function(evt) {
Blockly.CommonModal.setupKeyboardOverrides(that);
that.keyboardInputService.addOverride('13', function(evt) {
evt.preventDefault();
evt.stopPropagation();
if (evt.shiftKey) {
// Move to the previous item in the list.
if (that.activeActionButtonIndex <= 0) {
that.activeActionButtonIndex = 0;
that.audioService.playOopsSound();
} else {
that.activeActionButtonIndex--;
}
} else {
// Move to the next item in the list.
if (that.activeActionButtonIndex ==
that.actionButtonsInfo.length) {
that.audioService.playOopsSound();
} else {
that.activeActionButtonIndex++;
}
}
that.focusOnOption(that.activeActionButtonIndex);
},
// Enter key: selects an action, performs it, and closes the modal.
'13': function(evt) {
evt.preventDefault();
evt.stopPropagation();
if (that.activeActionButtonIndex == -1) {
if (that.activeButtonIndex == -1) {
return;
}
var button = document.getElementById(
that.getOptionId(that.activeActionButtonIndex));
if (that.activeActionButtonIndex <
that.getOptionId(that.activeButtonIndex));
if (that.activeButtonIndex <
that.actionButtonsInfo.length) {
that.actionButtonsInfo[that.activeActionButtonIndex].action();
that.actionButtonsInfo[that.activeButtonIndex].action();
} else {
that.dismissModal();
}
that.hideModal();
},
// Escape key: closes the modal.
'27': function() {
that.dismissModal();
},
// Up key: no-op.
'38': function(evt) {
// Prevent the page from scrolling.
evt.preventDefault();
},
// Down key: no-op.
'40': function(evt) {
// Prevent the page from scrolling.
evt.preventDefault();
}
});
});
setTimeout(function() {
document.getElementById('blockOptionsModal').focus();
@@ -152,6 +111,10 @@ blocklyApp.BlockOptionsModalComponent = ng.core.Component({
var button = document.getElementById(this.getOptionId(index));
button.focus();
},
// Counts the number of interactive elements for the modal.
numInteractiveElements: function() {
return this.actionButtonsInfo.length + 1;
},
// Returns the ID for the corresponding option button.
getOptionId: function(index) {
return 'block-options-modal-option-' + index;

74
accessible/commonModal.js Normal file
View File

@@ -0,0 +1,74 @@
Blockly.CommonModal = function() {};
Blockly.CommonModal.setupKeyboardOverrides = function(component) {
component.keyboardInputService.setOverride({
// Tab key: navigates to the previous or next item in the list.
'9': function(evt) {
evt.preventDefault();
evt.stopPropagation();
if (evt.shiftKey) {
// Move to the previous item in the list.
if (component.activeButtonIndex <= 0) {
component.activeActionButtonIndex = 0;
component.audioService.playOopsSound();
} else {
component.activeButtonIndex--;
}
} else {
// Move to the next item in the list.
if (component.activeButtonIndex == component.numInteractiveElements(component) - 1) {
component.audioService.playOopsSound();
} else {
component.activeButtonIndex++;
}
}
component.focusOnOption(component.activeButtonIndex, component);
},
// Escape key: closes the modal.
'27': function() {
component.dismissModal();
},
// Up key: no-op.
'38': function(evt) {
evt.preventDefault();
},
// Down key: no-op.
'40': function(evt) {
evt.preventDefault();
}
});
}
Blockly.CommonModal.getInteractiveElements = function(component) {
return Array.prototype.filter.call(
component.getInteractiveContainer().elements, function(element) {
if (element.type === 'hidden') {
return false;
}
if (element.disabled) {
return false;
}
if (element.tabIndex < 0) {
return false;
}
return true;
});
};
Blockly.CommonModal.numInteractiveElements = function(component) {
var elements = this.getInteractiveElements(component);
return elements.length;
};
Blockly.CommonModal.focusOnOption = function(index, component) {
var elements = this.getInteractiveElements(component);
var button = elements[index];
button.focus();
};
Blockly.CommonModal.hideModal = function() {
this.modalIsVisible = false;
this.keyboardInputService.clearOverride();
};

View File

@@ -46,6 +46,9 @@ blocklyApp.KeyboardInputService = ng.core.Class({
setOverride: function(newKeysToActions) {
this.keysToActionsOverride = newKeysToActions;
},
addOverride: function(keyCode, action) {
this.keysToActionsOverride[keyCode] = action;
},
clearOverride: function() {
this.keysToActionsOverride = null;
}

View File

@@ -103,34 +103,8 @@ blocklyApp.ToolboxModalComponent = ng.core.Component({
that.firstBlockIndexes.push(cumulativeIndex);
that.totalNumBlocks = cumulativeIndex;
that.keyboardInputService.setOverride({
// Tab key: navigates to the previous or next item in the list.
'9': function(evt) {
evt.preventDefault();
evt.stopPropagation();
if (evt.shiftKey) {
// Move to the previous item in the list.
if (that.activeButtonIndex <= 0) {
that.activeActionButtonIndex = 0;
that.audioService.playOopsSound();
} else {
that.activeButtonIndex--;
}
} else {
// Move to the next item in the list.
if (that.activeButtonIndex == that.totalNumBlocks) {
that.audioService.playOopsSound();
} else {
that.activeButtonIndex++;
}
}
that.focusOnOption(that.activeButtonIndex);
},
// Enter key: selects a block (or the 'Cancel' button), and closes
// the modal.
'13': function(evt) {
Blockly.CommonModal.setupKeyboardOverrides(that);
that.keyboardInputService.addOverride('13', function(evt) {
evt.preventDefault();
evt.stopPropagation();
@@ -154,20 +128,7 @@ blocklyApp.ToolboxModalComponent = ng.core.Component({
// The 'Cancel' button has been pressed.
that.dismissModal();
},
// Escape key: closes the modal.
'27': function() {
that.dismissModal();
},
// Up key: no-op.
'38': function(evt) {
evt.preventDefault();
},
// Down key: no-op.
'40': function(evt) {
evt.preventDefault();
}
});
});
setTimeout(function() {
document.getElementById('toolboxModal').focus();
@@ -177,10 +138,15 @@ blocklyApp.ToolboxModalComponent = ng.core.Component({
}
],
// Closes the modal (on both success and failure).
hideModal_: function() {
this.modalIsVisible = false;
this.keyboardInputService.clearOverride();
this.toolboxModalService.hideModal();
hideModal_: Blockly.CommonModal.hideModal,
// Focuses on the button represented by the given index.
focusOnOption: function(index) {
var button = document.getElementById(this.getOptionId(index));
button.focus();
},
// Counts the number of interactive elements for the modal.
numInteractiveElements: function() {
return this.totalNumBlocks + 1;
},
getOverallIndex: function(categoryIndex, blockIndex) {
return this.firstBlockIndexes[categoryIndex] + blockIndex;
@@ -191,11 +157,6 @@ blocklyApp.ToolboxModalComponent = ng.core.Component({
getBlockDescription: function(block) {
return this.utilsService.getBlockDescription(block);
},
// Focuses on the button represented by the given index.
focusOnOption: function(index) {
var button = document.getElementById(this.getOptionId(index));
button.focus();
},
// Returns the ID for the corresponding option button.
getOptionId: function(index) {
return 'toolbox-modal-option-' + index;

View File

@@ -68,44 +68,7 @@ blocklyApp.VariableAddModalComponent = ng.core.Component({
function() {
that.modalIsVisible = true;
that.keyboardInputService.setOverride({
// Tab key: navigates to the previous or next item in the list.
'9': function(evt) {
evt.preventDefault();
evt.stopPropagation();
if (evt.shiftKey) {
// Move to the previous item in the list.
if (that.activeButtonIndex <= 0) {
that.activeActionButtonIndex = 0;
that.audioService.playOopsSound();
} else {
that.activeButtonIndex--;
}
} else {
// Move to the next item in the list.
if (that.activeButtonIndex == that.numInteractiveElements() - 1) {
that.audioService.playOopsSound();
} else {
that.activeButtonIndex++;
}
}
that.focusOnOption(that.activeButtonIndex);
},
// Escape key: closes the modal.
'27': function() {
that.dismissModal();
},
// Up key: no-op.
'38': function(evt) {
evt.preventDefault();
},
// Down key: no-op.
'40': function(evt) {
evt.preventDefault();
}
});
Blockly.CommonModal.setupKeyboardOverrides(that);
setTimeout(function() {
document.getElementById('mainFieldId').focus();
@@ -119,36 +82,16 @@ blocklyApp.VariableAddModalComponent = ng.core.Component({
this.variableName = newValue;
},
// Closes the modal (on both success and failure).
hideModal_: function() {
this.modalIsVisible = false;
this.keyboardInputService.clearOverride();
},
hideModal_: Blockly.CommonModal.hideModal,
// Focuses on the button represented by the given index.
focusOnOption: function(index) {
var elements = this.getInteractiveElements();
var button = elements[index];
button.focus();
},
focusOnOption: Blockly.CommonModal.focusOnOption,
// Counts the number of interactive elements for the modal.
numInteractiveElements: function() {
var elements = this.getInteractiveElements();
return elements.length;
},
numInteractiveElements: Blockly.CommonModal.numInteractiveElements,
// Gets all the interactive elements for the modal.
getInteractiveElements: function() {
return Array.prototype.filter.call(
document.getElementById("varForm").elements, function(element) {
if (element.type === 'hidden') {
return false;
}
if (element.disabled) {
return false;
}
if (element.tabIndex < 0) {
return false;
}
return true;
});
getInteractiveElements: Blockly.CommonModal.getInteractiveElements,
// Gets the container with interactive elements.
getInteractiveContainer: function() {
return document.getElementById("varForm");
},
// Submits the name change for the variable.
submit: function() {

View File

@@ -70,44 +70,7 @@ blocklyApp.VariableRemoveModalComponent = ng.core.Component({
that.count = count
that.modalIsVisible = true;
that.keyboardInputService.setOverride({
// Tab key: navigates to the previous or next item in the list.
'9': function(evt) {
evt.preventDefault();
evt.stopPropagation();
if (evt.shiftKey) {
// Move to the previous item in the list.
if (that.activeButtonIndex <= 0) {
that.activeActionButtonIndex = 0;
that.audioService.playOopsSound();
} else {
that.activeButtonIndex--;
}
} else {
// Move to the next item in the list.
if (that.activeButtonIndex == that.numInteractiveElements() - 1) {
that.audioService.playOopsSound();
} else {
that.activeButtonIndex++;
}
}
that.focusOnOption(that.activeButtonIndex);
},
// Escape key: closes the modal.
'27': function() {
that.dismissModal();
},
// Up key: no-op.
'38': function(evt) {
evt.preventDefault();
},
// Down key: no-op.
'40': function(evt) {
evt.preventDefault();
}
});
Blockly.CommonModal.setupKeyboardOverrides(that);
setTimeout(function() {
document.getElementById('label').focus();
@@ -116,41 +79,17 @@ blocklyApp.VariableRemoveModalComponent = ng.core.Component({
);
}
],
// Caches the current text variable as the user types.
setTextValue: function(newValue) {
this.variableName = newValue;
},
// Closes the modal (on both success and failure).
hideModal_: function() {
this.modalIsVisible = false;
this.keyboardInputService.clearOverride();
},
hideModal_: Blockly.CommonModal.hideModal,
// Focuses on the button represented by the given index.
focusOnOption: function(index) {
var elements = this.getInteractiveElements();
var button = elements[index];
button.focus();
},
focusOnOption: Blockly.CommonModal.focusOnOption,
// Counts the number of interactive elements for the modal.
numInteractiveElements: function() {
var elements = this.getInteractiveElements();
return elements.length;
},
numInteractiveElements: Blockly.CommonModal.numInteractiveElements,
// Gets all the interactive elements for the modal.
getInteractiveElements: function() {
return Array.prototype.filter.call(
document.getElementById("varForm").elements, function(element) {
if (element.type === 'hidden') {
return false;
}
if (element.disabled) {
return false;
}
if (element.tabIndex < 0) {
return false;
}
return true;
});
getInteractiveElements: Blockly.CommonModal.getInteractiveElements,
// Gets the container with interactive elements.
getInteractiveContainer: function() {
return document.getElementById("varForm");
},
// Submits the name change for the variable.
submit: function() {

View File

@@ -70,44 +70,7 @@ blocklyApp.VariableRenameModalComponent = ng.core.Component({
that.currentVariableName = oldName;
that.modalIsVisible = true;
that.keyboardInputService.setOverride({
// Tab key: navigates to the previous or next item in the list.
'9': function(evt) {
evt.preventDefault();
evt.stopPropagation();
if (evt.shiftKey) {
// Move to the previous item in the list.
if (that.activeButtonIndex <= 0) {
that.activeActionButtonIndex = 0;
that.audioService.playOopsSound();
} else {
that.activeButtonIndex--;
}
} else {
// Move to the next item in the list.
if (that.activeButtonIndex == that.numInteractiveElements() - 1) {
that.audioService.playOopsSound();
} else {
that.activeButtonIndex++;
}
}
that.focusOnOption(that.activeButtonIndex);
},
// Escape key: closes the modal.
'27': function() {
that.dismissModal();
},
// Up key: no-op.
'38': function(evt) {
evt.preventDefault();
},
// Down key: no-op.
'40': function(evt) {
evt.preventDefault();
}
});
Blockly.CommonModal.setupKeyboardOverrides(that);
setTimeout(function() {
document.getElementById('mainFieldId').focus();
@@ -121,36 +84,16 @@ blocklyApp.VariableRenameModalComponent = ng.core.Component({
this.variableName = newValue;
},
// Closes the modal (on both success and failure).
hideModal_: function() {
this.modalIsVisible = false;
this.keyboardInputService.clearOverride();
},
hideModal_: Blockly.CommonModal.hideModal,
// Focuses on the button represented by the given index.
focusOnOption: function(index) {
var elements = this.getInteractiveElements();
var button = elements[index];
button.focus();
},
focusOnOption: Blockly.CommonModal.focusOnOption,
// Counts the number of interactive elements for the modal.
numInteractiveElements: function() {
var elements = this.getInteractiveElements();
return elements.length;
},
numInteractiveElements: Blockly.CommonModal.numInteractiveElements,
// Gets all the interactive elements for the modal.
getInteractiveElements: function() {
return Array.prototype.filter.call(
document.getElementById("varForm").elements, function(element) {
if (element.type === 'hidden') {
return false;
}
if (element.disabled) {
return false;
}
if (element.tabIndex < 0) {
return false;
}
return true;
});
getInteractiveElements: Blockly.CommonModal.getInteractiveElements,
// Gets the container with interactive elements.
getInteractiveContainer: function() {
return document.getElementById("varForm");
},
// Submits the name change for the variable.
submit: function() {

View File

@@ -29,6 +29,7 @@
<script src="../../accessible/translate.pipe.js"></script>
<script src="../../accessible/variable-modal.service.js"></script>
<script src="../../accessible/commonModal.js"></script>
<script src="../../accessible/field-segment.component.js"></script>
<script src="../../accessible/block-options-modal.component.js"></script>
<script src="../../accessible/toolbox-modal.component.js"></script>