Update aria labels. In the process, refactor lists to comply with HTML5 spec to avoid ChromeVox getting confused about how many elements are in a list.

This commit is contained in:
Sean Lip
2016-07-28 17:47:43 -07:00
parent e6f0bf57a9
commit 408e306ffc
8 changed files with 74 additions and 79 deletions

View File

@@ -44,7 +44,7 @@ blocklyApp.AppView = ng.core
<label aria-hidden="true" hidden id="blockly-argument-input">{{'ARGUMENT_INPUT'|translate}}</label> <label aria-hidden="true" hidden id="blockly-argument-input">{{'ARGUMENT_INPUT'|translate}}</label>
<label aria-hidden="true" hidden id="blockly-argument-menu">{{'ARGUMENT_OPTIONS_LIST'|translate}}</label> <label aria-hidden="true" hidden id="blockly-argument-menu">{{'ARGUMENT_OPTIONS_LIST'|translate}}</label>
<label aria-hidden="true" hidden id="blockly-argument-text">{{'TEXT'|translate}}</label> <label aria-hidden="true" hidden id="blockly-argument-text">{{'TEXT'|translate}}</label>
<label aria-hidden="true" hidden id="blockly-block-menu">{{'BLOCK_ACTION_LIST'|translate}}</label> <label aria-hidden="true" hidden id="blockly-block-menu">{{'BLOCK_ACTION_LIST'|translate}} {{'FOR'|translate}}</label>
<label aria-hidden="true" hidden id="blockly-block-summary">{{'BLOCK_SUMMARY'|translate}}</label> <label aria-hidden="true" hidden id="blockly-block-summary">{{'BLOCK_SUMMARY'|translate}}</label>
<label aria-hidden="true" hidden id="blockly-button">{{'BUTTON'|translate}}</label> <label aria-hidden="true" hidden id="blockly-button">{{'BUTTON'|translate}}</label>
<label aria-hidden="true" hidden id="blockly-disabled">{{'DISABLED'|translate}}</label> <label aria-hidden="true" hidden id="blockly-disabled">{{'DISABLED'|translate}}</label>

View File

@@ -28,44 +28,37 @@ blocklyApp.FieldComponent = ng.core
.Component({ .Component({
selector: 'blockly-field', selector: 'blockly-field',
template: ` template: `
<li [id]="idMap['listItem']" role="treeitem" *ngIf="isTextInput()" <input *ngIf="isTextInput()" [id]="idMap['input']"
[attr.aria-level]="level"> [ngModel]="field.getValue()" (ngModelChange)="field.setValue($event)"
<input [id]="idMap['input']" [ngModel]="field.getValue()" (ngModelChange)="field.setValue($event)" [disabled]="disabled" type="text" aria-label="Press Enter to edit text">
[disabled]="disabled" type="text" aria-label="text">
</li> <input *ngIf="isNumberInput()" [id]="idMap['input']"
<li [id]="idMap['listItem']" role="treeitem" *ngIf="isNumberInput()" [ngModel]="field.getValue()" (ngModelChange)="field.setValue($event)"
[attr.aria-level]="level"> [disabled]="disabled" type="number" aria-label="Press Enter to edit number">
<input [id]="idMap['input']" [ngModel]="field.getValue()" (ngModelChange)="field.setValue($event)"
[disabled]="disabled" type="number" aria-label="number"> <div *ngIf="isDropdown()"
</li> [attr.aria-labelledBy]="generateAriaLabelledByAttr('blockly-argument-menu', idMap['label'])">
<li [id]="idMap['listItem']" role="treeitem" *ngIf="isDropdown()"
[attr.aria-labelledBy]="generateAriaLabelledByAttr('blockly-argument-menu', idMap['label'])"
[attr.aria-level]="level">
<label [id]="idMap['label']">{{'CURRENT_ARGUMENT_VALUE'|translate}} {{field.getText()}}</label> <label [id]="idMap['label']">{{'CURRENT_ARGUMENT_VALUE'|translate}} {{field.getText()}}</label>
<ol role="group"> <ol role="group">
<li [id]="idMap[optionValue]" role="treeitem" *ngFor="#optionValue of getOptions()" <li [id]="idMap[optionValue]" role="treeitem" *ngFor="#optionValue of getOptions()"
[attr.aria-labelledBy]="generateAriaLabelledByAttr(idMap[optionValue + 'Button'], 'blockly-button')" [attr.aria-labelledBy]="generateAriaLabelledByAttr(idMap[optionValue + 'Button'], 'blockly-button')">
[attr.aria-level]="level + 1">
<button [id]="idMap[optionValue + 'Button']" (click)="handleDropdownChange(field, optionValue)" <button [id]="idMap[optionValue + 'Button']" (click)="handleDropdownChange(field, optionValue)"
[disabled]="disabled"> [disabled]="disabled">
{{optionText[optionValue]}} {{optionText[optionValue]}}
</button> </button>
</li> </li>
</ol> </ol>
</li> </div>
<li [id]="idMap['listItem']" role="treeitem" *ngIf="isCheckbox()"
[attr.aria-level]="level"> <div *ngIf="isCheckbox()">
// Checkboxes are not currently supported. // Checkboxes are not currently supported.
</li> </div>
<li [id]="idMap['listItem']" role="treeitem" *ngIf="isTextField() && hasVisibleText()"
[attr.aria-labelledBy]="utilsService.generateAriaLabelledByAttr('blockly-argument-text', idMap['label'])" <label [id]="idMap['label']" *ngIf="isTextField() && hasVisibleText()">
[attr.aria-level]="level"> {{field.getText()}}
<label [id]="idMap['label']"> </label>
{{field.getText()}}
</label>
</li>
`, `,
inputs: ['field', 'level', 'index', 'parentId', 'disabled'], inputs: ['field', 'index', 'parentId', 'disabled'],
pipes: [blocklyApp.TranslatePipe] pipes: [blocklyApp.TranslatePipe]
}) })
.Class({ .Class({

View File

@@ -58,6 +58,7 @@ Blockly.Msg.COPY_TO_MARKED_SPOT = 'copy to marked spot';
Blockly.Msg.TOOLBOX = 'Toolbox'; Blockly.Msg.TOOLBOX = 'Toolbox';
Blockly.Msg.WORKSPACE = 'Workspace'; Blockly.Msg.WORKSPACE = 'Workspace';
Blockly.Msg.ANY = 'any'; Blockly.Msg.ANY = 'any';
Blockly.Msg.FOR = 'for';
Blockly.Msg.STATEMENT = 'statement'; Blockly.Msg.STATEMENT = 'statement';
Blockly.Msg.VALUE = 'value'; Blockly.Msg.VALUE = 'value';
Blockly.Msg.CUT_BLOCK_MSG = 'Cut block: '; Blockly.Msg.CUT_BLOCK_MSG = 'Cut block: ';

View File

@@ -40,57 +40,51 @@ blocklyApp.ToolboxTreeComponent = ng.core
[attr.aria-level]="level + 1"> [attr.aria-level]="level + 1">
<label #label [id]="idMap['label']">{{'BLOCK_ACTION_LIST'|translate}}</label> <label #label [id]="idMap['label']">{{'BLOCK_ACTION_LIST'|translate}}</label>
<ol role="group" *ngIf="displayBlockMenu"> <ol role="group" *ngIf="displayBlockMenu">
<li #workspaceCopy [id]="idMap['workspaceCopy']" role="treeitem" <li [id]="idMap['workspaceCopy']" role="treeitem"
[attr.aria-labelledBy]="generateAriaLabelledByAttr(idMap['workspaceCopyButton'], 'blockly-button')" [attr.aria-labelledBy]="generateAriaLabelledByAttr(idMap['workspaceCopyButton'], 'blockly-button')"
[attr.aria-level]="level + 2"> [attr.aria-level]="level + 2">
<button #workspaceCopyButton [id]="idMap['workspaceCopyButton']" <button [id]="idMap['workspaceCopyButton']" (click)="copyToWorkspace()">
(click)="copyToWorkspace()">
{{'COPY_TO_WORKSPACE'|translate}} {{'COPY_TO_WORKSPACE'|translate}}
</button> </button>
</li> </li>
<li #blockCopy [id]="idMap['blockCopy']" role="treeitem" <li [id]="idMap['blockCopy']" role="treeitem"
[attr.aria-labelledBy]="generateAriaLabelledByAttr(idMap['blockCopyButton'], 'blockly-button')" [attr.aria-labelledBy]="generateAriaLabelledByAttr(idMap['blockCopyButton'], 'blockly-button')"
[attr.aria-level]="level + 2"> [attr.aria-level]="level + 2">
<button #blockCopyButton <button [id]="idMap['blockCopyButton']" (click)="copyToClipboard()">
[id]="idMap['blockCopyButton']"
(click)="copyToClipboard()">
{{'COPY_TO_CLIPBOARD'|translate}} {{'COPY_TO_CLIPBOARD'|translate}}
</button> </button>
</li> </li>
<li #sendToSelected [id]="idMap['sendToSelected']" role="treeitem" <li [id]="idMap['sendToSelected']" role="treeitem"
[attr.aria-labelledBy]="generateAriaLabelledByAttr(idMap['sendToSelectedButton'], 'blockly-button', !canBeCopiedToMarkedConnection())" [attr.aria-labelledBy]="generateAriaLabelledByAttr(idMap['sendToSelectedButton'], 'blockly-button', !canBeCopiedToMarkedConnection())"
[attr.aria-level]="level + 2"> [attr.aria-level]="level + 2">
<button #sendToSelectedButton <button [id]="idMap['sendToSelectedButton']" (click)="copyToMarkedSpot()"
[id]="idMap['sendToSelectedButton']"
(click)="copyToMarkedSpot()"
[disabled]="!canBeCopiedToMarkedConnection()"> [disabled]="!canBeCopiedToMarkedConnection()">
{{'COPY_TO_MARKED_SPOT'|translate}} {{'COPY_TO_MARKED_SPOT'|translate}}
</button> </button>
</li> </li>
</ol> </ol>
</li> </li>
<div *ngFor="#inputBlock of block.inputList; #i=index"> <template ngFor #inputBlock [ngForOf]="block.inputList" #i="index">
<blockly-field *ngFor="#field of inputBlock.fieldRow; #j=index" <li role="treeitem" [id]="idMap['listItem' + i]" [attr.aria-level]="level + 1">
[field]="field" [level]="level + 1" [disabled]="true"> <blockly-field *ngFor="#field of inputBlock.fieldRow" [field]="field" [disabled]="true">
</blockly-field> </blockly-field>
</li>
<blockly-toolbox-tree *ngIf="inputBlock.connection && inputBlock.connection.targetBlock()" <blockly-toolbox-tree *ngIf="inputBlock.connection && inputBlock.connection.targetBlock()"
[block]="inputBlock.connection.targetBlock()" [block]="inputBlock.connection.targetBlock()" [level]="level + 1"
[displayBlockMenu]="false" [displayBlockMenu]="false">
[level]="level + 1">
</blockly-toolbox-tree> </blockly-toolbox-tree>
<li #listItem1 [id]="idMap['listItem' + i]" role="treeitem" <li [id]="idMap['inputList' + i]" role="treeitem"
*ngIf="inputBlock.connection && !inputBlock.connection.targetBlock()" *ngIf="inputBlock.connection && !inputBlock.connection.targetBlock()"
[attr.aria-labelledBy]="generateAriaLabelledByAttr('blockly-argument-text', idMap['listItem' + i + 'Label'])"
[attr.aria-level]="level + 1"> [attr.aria-level]="level + 1">
<!--TODO(madeeha): i18n here will need to happen in a different way due to the way grammar changes based on language.--> <label>
<label #label [id]="idMap['listItem' + i + 'Label']"> {{utilsService.getInputTypeLabel(inputBlock.connection)}} {{utilsService.getBlockTypeLabel(inputBlock)}} needed:
{{utilsService.getInputTypeLabel(inputBlock.connection)}}
{{utilsService.getBlockTypeLabel(inputBlock)}} needed:
</label> </label>
</li> </li>
</div> </template>
</ol> </ol>
</li> </li>
<blockly-toolbox-tree *ngIf= "block.nextConnection && block.nextConnection.targetBlock()" <blockly-toolbox-tree *ngIf= "block.nextConnection && block.nextConnection.targetBlock()"
[level]="level" [level]="level"
[block]="block.nextConnection.targetBlock()" [block]="block.nextConnection.targetBlock()"
@@ -123,7 +117,7 @@ blocklyApp.ToolboxTreeComponent = ng.core
'blockCopyButton', 'sendToSelected', 'sendToSelectedButton']); 'blockCopyButton', 'sendToSelected', 'sendToSelectedButton']);
} }
for (var i = 0; i < this.block.inputList.length; i++){ for (var i = 0; i < this.block.inputList.length; i++){
elementsNeedingIds.push('listItem' + i, 'listItem' + i + 'Label') elementsNeedingIds.push('listItem' + i, 'inputList' + i);
} }
this.idMap = this.utilsService.generateIds(elementsNeedingIds); this.idMap = this.utilsService.generateIds(elementsNeedingIds);
if (this.isTopLevel) { if (this.isTopLevel) {

View File

@@ -246,12 +246,13 @@ blocklyApp.TreeService = ng.core
if (e.keyCode == 13) { if (e.keyCode == 13) {
// Enter key. The user wants to interact with a button or an input // Enter key. The user wants to interact with a button or an input
// field. // field.
if (activeDesc.children.length == 1) { var currentChild = activeDesc;
var child = activeDesc.children[0]; while (currentChild.children.length) {
if (child.tagName == 'BUTTON') { currentChild = currentChild.children[0];
child.click(); if (currentChild.tagName == 'BUTTON') {
} else if (child.tagName == 'INPUT') { currentChild.click();
child.focus(); } else if (currentChild.tagName == 'INPUT') {
currentChild.focus();
} }
} }
} else if (e.keyCode == 9) { } else if (e.keyCode == 9) {

View File

@@ -48,13 +48,10 @@ blocklyApp.UtilsService = ng.core
return attrValue; return attrValue;
}, },
getInputTypeLabel: function(connection) { getInputTypeLabel: function(connection) {
// Returns an upper case string in the case of official input type names. // Returns the input type name, or 'any' if any official input type
// Returns the lower case string 'any' if any official input type qualifies. // qualifies.
// The differentiation between upper and lower case signifies the difference
// between an input type (BOOLEAN, LIST, etc) and the colloquial english term
// 'any'.
if (connection.check_) { if (connection.check_) {
return connection.check_.join(', ').toUpperCase(); return connection.check_.join(', ');
} else { } else {
return Blockly.Msg.ANY; return Blockly.Msg.ANY;
} }

View File

@@ -51,17 +51,24 @@ blocklyApp.WorkspaceTreeComponent = ng.core
</ol> </ol>
</li> </li>
<div *ngFor="#inputBlock of block.inputList; #i = index"> <template ngFor #inputBlock [ngForOf]="block.inputList" #i="index">
<blockly-field *ngFor="#field of inputBlock.fieldRow" [field]="field" [level]="level + 1"></blockly-field> <li role="treeitem" [id]="idMap['listItem' + i]" [attr.aria-level]="level + 1" *ngIf="inputBlock.fieldRow.length">
<blockly-field *ngFor="#field of inputBlock.fieldRow" [field]="field">
</blockly-field>
</li>
<blockly-workspace-tree *ngIf="inputBlock.connection && inputBlock.connection.targetBlock()" <blockly-workspace-tree *ngIf="inputBlock.connection && inputBlock.connection.targetBlock()"
[block]="inputBlock.connection.targetBlock()" [level]="level + 1" [block]="inputBlock.connection.targetBlock()" [level]="level + 1"
[tree]="tree"> [tree]="tree">
</blockly-workspace-tree> </blockly-workspace-tree>
<li #inputList [attr.aria-level]="level + 1" [id]="idMap['inputList' + i]" <li #inputList [id]="idMap['inputList' + i]" role="treeitem"
*ngIf="inputBlock.connection && !inputBlock.connection.targetBlock()"
[attr.aria-labelledBy]="generateAriaLabelledByAttr('blockly-menu', idMap['inputMenuLabel' + i])" [attr.aria-labelledBy]="generateAriaLabelledByAttr('blockly-menu', idMap['inputMenuLabel' + i])"
*ngIf="inputBlock.connection && !inputBlock.connection.targetBlock()" (keydown)="treeService.onKeypress($event, tree)"> [attr.aria-level]="level + 1"
<!-- TODO(madeeha): i18n here will need to happen in a different way due to the way grammar changes based on language. --> (keydown)="treeService.onKeypress($event, tree)">
<label [id]="idMap['inputMenuLabel' + i]"> {{utilsService.getInputTypeLabel(inputBlock.connection)}} {{utilsService.getBlockTypeLabel(inputBlock)}} needed: </label> <label [id]="idMap['inputMenuLabel' + i]">
{{utilsService.getInputTypeLabel(inputBlock.connection)}} {{utilsService.getBlockTypeLabel(inputBlock)}} needed:
</label>
<ol role="group"> <ol role="group">
<li *ngFor="#fieldButtonInfo of fieldButtonsInfo" <li *ngFor="#fieldButtonInfo of fieldButtonsInfo"
[id]="idMap[fieldButtonInfo.baseIdKey]" role="treeitem" [id]="idMap[fieldButtonInfo.baseIdKey]" role="treeitem"
@@ -75,7 +82,7 @@ blocklyApp.WorkspaceTreeComponent = ng.core
</li> </li>
</ol> </ol>
</li> </li>
</div> </template>
</ol> </ol>
</li> </li>
@@ -318,11 +325,10 @@ blocklyApp.WorkspaceTreeComponent = ng.core
}); });
for (var i = 0; i < this.block.inputList.length; i++) { for (var i = 0; i < this.block.inputList.length; i++) {
var inputBlock = this.block.inputList[i]; var inputBlock = this.block.inputList[i];
if (inputBlock.connection && !inputBlock.connection.targetBlock()) { that.idKeys.push(
that.idKeys.push( 'inputList' + i, 'inputMenuLabel' + i, 'markSpot' + i,
'inputList' + i, 'inputMenuLabel' + i, 'markSpot' + i, 'markSpotButton' + i, 'paste' + i, 'pasteButton' + i,
'markSpotButton' + i, 'paste' + i, 'pasteButton' + i); 'listItem' + i);
}
} }
}, },
ngDoCheck: function() { ngDoCheck: function() {

View File

@@ -4,6 +4,9 @@
.blocklyTree .blocklyActiveDescendant > label, .blocklyTree .blocklyActiveDescendant > label,
.blocklyTree .blocklyActiveDescendant > div > label, .blocklyTree .blocklyActiveDescendant > div > label,
.blocklyActiveDescendant > button, .blocklyActiveDescendant > button,
.blocklyActiveDescendant > input { .blocklyActiveDescendant > input,
.blocklyActiveDescendant > blockly-field > label,
.blocklyActiveDescendant > blockly-field > input,
.blocklyActiveDescendant > blockly-field > div > label {
outline: 2px dotted #00f; outline: 2px dotted #00f;
} }