diff --git a/accessible/README b/accessible/README
index 37ea67879..661b3a105 100644
--- a/accessible/README
+++ b/accessible/README
@@ -33,20 +33,25 @@ To customize the toolbar, you will need to declare an ACCESSIBLE_GLOBALS object
in the global scope that looks like this:
var ACCESSIBLE_GLOBALS = {
- toolbarButtonConfig: [],
- mediaPathPrefix: null
+ mediaPathPrefix: null,
+ toolbarButtonConfig: []
};
-The value corresponding to 'toolbarButtonConfig' can be modified by adding
-objects representing buttons on the toolbar. Each of these objects should have
-two keys:
+The value of mediaPathPrefix should be the location of the accessible/media
+folder.
+
+The value of 'toolbarButtonConfig' should be a list of objects, each
+representing buttons on the toolbar. Each of these objects should have four
+keys:
- 'text' (the text to display on the button)
+ - 'ariaDescribedBy' (the value of the button's aria-describedby attribute)
+ - 'onClickNotification' (a notification that the screenreader should read
+ when the button is clicked)
+ - 'isHidden' (a function that returns true if the button should not be
+ displayed, and false otherwise)
- 'action' (the function that gets run when the button is clicked)
-In addition, if you want audio to be played, set mediaPathPrefix to the
-location of the accessible/media folder.
-
Limitations
-----------
diff --git a/accessible/app.component.js b/accessible/app.component.js
index 42d04608e..7ea38278b 100644
--- a/accessible/app.component.js
+++ b/accessible/app.component.js
@@ -29,28 +29,19 @@ blocklyApp.AppView = ng.core
.Component({
selector: 'blockly-app',
template: `
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
`,
directives: [blocklyApp.ToolboxComponent, blocklyApp.WorkspaceComponent],
pipes: [blocklyApp.TranslatePipe],
diff --git a/accessible/clipboard.service.js b/accessible/clipboard.service.js
index 9a7e7451a..251feba45 100644
--- a/accessible/clipboard.service.js
+++ b/accessible/clipboard.service.js
@@ -61,6 +61,9 @@ blocklyApp.ClipboardService = ng.core
return this.markedConnection_.getSourceBlock();
}
},
+ isAnyConnectionMarked: function() {
+ return Boolean(this.markedConnection_);
+ },
isMovableToMarkedConnection: function(block) {
// It should not be possible to move any ancestor of the block containing
// the marked spot to the marked spot.
diff --git a/accessible/field-segment.component.js b/accessible/field-segment.component.js
new file mode 100644
index 000000000..acb765363
--- /dev/null
+++ b/accessible/field-segment.component.js
@@ -0,0 +1,167 @@
+/**
+ * AccessibleBlockly
+ *
+ * Copyright 2016 Google Inc.
+ * https://developers.google.com/blockly/
+ *
+ * Licensed under the Apache License, Version 2.0 (the 'License');
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an 'AS IS' BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @fileoverview Angular2 Component that renders a "field segment" (a group
+ * of non-editable Blockly.Field followed by 0 or 1 editable Blockly.Field)
+ * in a block. Also handles any interactions with the field.
+ * @author madeeha@google.com (Madeeha Ghori)
+ */
+
+blocklyApp.FieldSegmentComponent = ng.core
+ .Component({
+ selector: 'blockly-field-segment',
+ template: `
+
+
+
+
+
+
+ {{getPrefixText()}}
+
+
+
+
+ {{getPrefixText()}}
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ inputs: ['prefixFields', 'mainField', 'mainFieldId', 'level'],
+ pipes: [blocklyApp.TranslatePipe]
+ })
+ .Class({
+ constructor: [
+ blocklyApp.NotificationsService, blocklyApp.UtilsService,
+ function(_notificationsService, _utilsService) {
+ this.optionText = {
+ keys: []
+ };
+ this.notificationsService = _notificationsService;
+ this.utilsService = _utilsService;
+ }],
+ ngOnInit: function() {
+ var elementsNeedingIds = this.generateElementNames(this.mainField);
+ // Warning: this assumes that the elements returned by
+ // this.generateElementNames() are unique.
+ this.idMap = this.utilsService.generateIds(elementsNeedingIds);
+ },
+ getPrefixText: function() {
+ var prefixTexts = this.prefixFields.map(function(prefixField) {
+ return prefixField.getText();
+ });
+ return prefixTexts.join(' ');
+ },
+ getFieldDescription: function() {
+ var description = this.mainField.getText();
+ if (this.prefixFields.length > 0) {
+ description = this.getPrefixText() + ': ' + description;
+ }
+ return description;
+ },
+ setNumberValue: function(newValue) {
+ // Do not permit a residual value of NaN after a backspace event.
+ this.mainField.setValue(newValue || 0);
+ },
+ generateAriaLabelledByAttr: function(mainLabel, secondLabel) {
+ return mainLabel + ' ' + secondLabel;
+ },
+ generateElementNames: function() {
+ var elementNames = [];
+ if (this.isDropdown()) {
+ var keys = this.getOptions();
+ for (var i = 0; i < keys.length; i++){
+ elementNames.push(keys[i], keys[i] + 'Button');
+ }
+ }
+ return elementNames;
+ },
+ isNumberInput: function() {
+ return this.mainField instanceof Blockly.FieldNumber;
+ },
+ isTextInput: function() {
+ return this.mainField instanceof Blockly.FieldTextInput &&
+ !(this.mainField instanceof Blockly.FieldNumber);
+ },
+ isDropdown: function() {
+ return this.mainField instanceof Blockly.FieldDropdown;
+ },
+ isCheckbox: function() {
+ return this.mainField instanceof Blockly.FieldCheckbox;
+ },
+ isTextField: function() {
+ return !(this.mainField instanceof Blockly.FieldTextInput) &&
+ !(this.mainField instanceof Blockly.FieldDropdown) &&
+ !(this.mainField instanceof Blockly.FieldCheckbox);
+ },
+ hasVisibleText: function() {
+ var text = this.mainField.getText().trim();
+ return !!text;
+ },
+ getOptions: function() {
+ if (this.optionText.keys.length) {
+ return this.optionText.keys;
+ }
+ var options = this.mainField.getOptions_();
+ for (var i = 0; i < options.length; i++) {
+ var tuple = options[i];
+ this.optionText[tuple[1]] = tuple[0];
+ this.optionText.keys.push(tuple[1]);
+ }
+ return this.optionText.keys;
+ },
+ handleDropdownChange: function(field, optionValue) {
+ if (optionValue == 'NO_ACTION') {
+ return;
+ }
+ if (this.mainField instanceof Blockly.FieldVariable) {
+ Blockly.FieldVariable.dropdownChange.call(this.mainField, optionValue);
+ } else {
+ this.mainField.setValue(optionValue);
+ }
+
+ this.notificationsService.setStatusMessage(
+ 'Selected option ' + this.optionText[optionValue]);
+ }
+ });
diff --git a/accessible/field.component.js b/accessible/field.component.js
deleted file mode 100644
index 5a5302960..000000000
--- a/accessible/field.component.js
+++ /dev/null
@@ -1,143 +0,0 @@
-/**
- * AccessibleBlockly
- *
- * Copyright 2016 Google Inc.
- * https://developers.google.com/blockly/
- *
- * Licensed under the Apache License, Version 2.0 (the 'License');
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an 'AS IS' BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/**
- * @fileoverview Angular2 Component that details how a Blockly.Field is
- * rendered in the toolbox in AccessibleBlockly. Also handles any interactions
- * with the field.
- * @author madeeha@google.com (Madeeha Ghori)
- */
-
-blocklyApp.FieldComponent = ng.core
- .Component({
- selector: 'blockly-field',
- template: `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- // Checkboxes are not currently supported.
-
-
-
- `,
- inputs: ['field', 'index', 'parentId', 'disabled', 'mainFieldId', 'level'],
- pipes: [blocklyApp.TranslatePipe]
- })
- .Class({
- constructor: [blocklyApp.UtilsService, function(_utilsService) {
- this.optionText = {
- keys: []
- };
- this.utilsService = _utilsService;
- }],
- ngOnInit: function() {
- var elementsNeedingIds = this.generateElementNames(this.field);
- // Warning: this assumes that the elements returned by
- // this.generateElementNames() are unique.
- this.idMap = this.utilsService.generateIds(elementsNeedingIds);
- },
- setNumberValue: function(newValue) {
- // Do not permit a residual value of NaN after a backspace event.
- this.field.setValue(newValue || 0);
- },
- generateAriaLabelledByAttr: function(mainLabel, secondLabel) {
- return mainLabel + ' ' + secondLabel;
- },
- generateElementNames: function() {
- var elementNames = [];
- if (this.isDropdown()) {
- var keys = this.getOptions();
- for (var i = 0; i < keys.length; i++){
- elementNames.push(keys[i], keys[i] + 'Button');
- }
- }
- return elementNames;
- },
- isNumberInput: function() {
- return this.field instanceof Blockly.FieldNumber;
- },
- isTextInput: function() {
- return this.field instanceof Blockly.FieldTextInput &&
- !(this.field instanceof Blockly.FieldNumber);
- },
- isDropdown: function() {
- return this.field instanceof Blockly.FieldDropdown;
- },
- isCheckbox: function() {
- return this.field instanceof Blockly.FieldCheckbox;
- },
- isTextField: function() {
- return !(this.field instanceof Blockly.FieldTextInput) &&
- !(this.field instanceof Blockly.FieldDropdown) &&
- !(this.field instanceof Blockly.FieldCheckbox);
- },
- hasVisibleText: function() {
- var text = this.field.getText().trim();
- return !!text;
- },
- getOptions: function() {
- if (this.optionText.keys.length) {
- return this.optionText.keys;
- }
- var options = this.field.getOptions_();
- for (var i = 0; i < options.length; i++) {
- var tuple = options[i];
- this.optionText[tuple[1]] = tuple[0];
- this.optionText.keys.push(tuple[1]);
- }
- return this.optionText.keys;
- },
- handleDropdownChange: function(field, text) {
- if (text == 'NO_ACTION') {
- return;
- }
- if (this.field instanceof Blockly.FieldVariable) {
- Blockly.FieldVariable.dropdownChange.call(this.field, text);
- } else {
- this.field.setValue(text);
- }
- }
- });
diff --git a/accessible/media/accessible.css b/accessible/media/accessible.css
index 3d88b87ad..0deaebb7c 100644
--- a/accessible/media/accessible.css
+++ b/accessible/media/accessible.css
@@ -8,16 +8,30 @@
}
.blocklyToolbarColumn {
float: left;
+ margin-left: 10px;
margin-top: 20px;
- width: 200px;
+ width: 150px;
+}
+
+.blocklyAriaLiveStatus {
+ background: #c8f7be;
+ border-radius: 10px;
+ bottom: 80px;
+ left: 20px;
+ max-width: 275px;
+ padding: 10px;
+ position: fixed;
}
.blocklyTree .blocklyActiveDescendant > label,
.blocklyTree .blocklyActiveDescendant > div > label,
.blocklyActiveDescendant > button,
.blocklyActiveDescendant > input,
-.blocklyActiveDescendant > blockly-field > label,
-.blocklyActiveDescendant > blockly-field > input,
-.blocklyActiveDescendant > blockly-field > div > label {
+.blocklyActiveDescendant > blockly-field-segment > label,
+.blocklyActiveDescendant > blockly-field-segment > input {
outline: 2px dotted #00f;
}
+
+.blocklyDropdownListItem[aria-selected="true"] button {
+ font-weight: bold;
+}
diff --git a/accessible/media/delete.mp3 b/accessible/media/delete.mp3
index 442bd9c1f..1e71bdcf4 100644
Binary files a/accessible/media/delete.mp3 and b/accessible/media/delete.mp3 differ
diff --git a/accessible/media/delete.ogg b/accessible/media/delete.ogg
index 67f84ac19..a65b11228 100644
Binary files a/accessible/media/delete.ogg and b/accessible/media/delete.ogg differ
diff --git a/accessible/media/delete.wav b/accessible/media/delete.wav
index 18debcf96..455bcd3bb 100644
Binary files a/accessible/media/delete.wav and b/accessible/media/delete.wav differ
diff --git a/accessible/messages.js b/accessible/messages.js
index af3a871ac..967ddac72 100644
--- a/accessible/messages.js
+++ b/accessible/messages.js
@@ -19,55 +19,42 @@
*/
/**
- * @fileoverview Accessible strings.
+ * @fileoverview Translatable string constants for Accessible Blockly.
* @author madeeha@google.com (Madeeha Ghori)
*/
'use strict';
-// The following are all Accessible Blockly strings.
-// None of the alert messages have periods on them. This is because the user
-// will have their punctuation setting set to 'all', which will result in any
-// punctuation being read out to them.
-Blockly.Msg.RUN_CODE = 'Run Code';
-Blockly.Msg.CLEAR_WORKSPACE = 'Clear Workspace';
-Blockly.Msg.BLOCK_ACTION_LIST = 'block action list';
-Blockly.Msg.CUT_BLOCK = 'cut block';
-Blockly.Msg.COPY_BLOCK = 'copy block';
-Blockly.Msg.PASTE_BEFORE = 'paste before';
-Blockly.Msg.PASTE_AFTER = 'paste after';
-Blockly.Msg.MARK_SPOT_BEFORE = 'mark spot before';
-Blockly.Msg.MARK_SPOT_AFTER = 'mark spot after';
-Blockly.Msg.MOVE_TO_MARKED_SPOT = 'move to marked spot';
-Blockly.Msg.DELETE = 'delete';
-Blockly.Msg.MARK_THIS_SPOT = 'mark this spot';
-Blockly.Msg.PASTE = 'paste';
-Blockly.Msg.TOOLBOX_LOAD_MSG = 'Loading Toolbox…';
-Blockly.Msg.WORKSPACE_LOAD_MSG = 'Loading Workspace…';
-Blockly.Msg.BLOCK_SUMMARY = 'block summary';
-Blockly.Msg.OPTION_LIST = 'option list';
-Blockly.Msg.ARGUMENT_OPTIONS_LIST = 'argument options list';
-Blockly.Msg.ARGUMENT_INPUT = 'argument input';
-Blockly.Msg.ARGUMENT_BLOCK_ACTION_LIST = 'argument block action list';
-Blockly.Msg.TEXT = 'text';
-Blockly.Msg.BUTTON = 'button';
-Blockly.Msg.DISABLED = 'disabled';
-Blockly.Msg.CURRENT_ARGUMENT_VALUE = 'current argument value:';
-Blockly.Msg.COPY_TO_WORKSPACE = 'create new group with this block';
-Blockly.Msg.COPY_TO_CLIPBOARD = 'copy to clipboard';
-Blockly.Msg.COPY_TO_MARKED_SPOT = 'copy to marked spot';
Blockly.Msg.TOOLBOX = 'Toolbox';
Blockly.Msg.WORKSPACE = 'Workspace';
+Blockly.Msg.TOOLBOX_BLOCK = 'toolbox block. Move right to view submenu.';
+Blockly.Msg.WORKSPACE_BLOCK = 'workspace block. Move right to view submenu.';
+
+Blockly.Msg.CLEAR_WORKSPACE = 'Erase Workspace';
+
+Blockly.Msg.COPY_TO_CLIPBOARD = 'Copy to clipboard.';
+Blockly.Msg.COPY_TO_MARKED_SPOT = 'Attach to link.';
+Blockly.Msg.COPY_TO_WORKSPACE = 'Add to workspace.';
+
+Blockly.Msg.COPY_BLOCK = 'Copy block.';
+Blockly.Msg.DELETE = 'Delete block.';
+Blockly.Msg.MARK_SPOT_BEFORE = 'Add link before.';
+Blockly.Msg.MARK_SPOT_AFTER = 'Add link after.';
+Blockly.Msg.MARK_THIS_SPOT = 'Add link inside.';
+Blockly.Msg.MOVE_TO_MARKED_SPOT = 'Attach to link.';
+Blockly.Msg.PASTE_AFTER = 'Paste after.';
+Blockly.Msg.PASTE_BEFORE = 'Paste before.';
+Blockly.Msg.PASTE_INSIDE = 'Paste inside.';
+
Blockly.Msg.ANY = 'any';
+Blockly.Msg.BLOCK = 'block';
+Blockly.Msg.BUTTON = 'Button.';
Blockly.Msg.FOR = 'for';
-Blockly.Msg.STATEMENT = 'statement';
Blockly.Msg.VALUE = 'value';
-Blockly.Msg.CUT_BLOCK_MSG = 'Cut block: ';
-Blockly.Msg.COPIED_BLOCK_MSG = 'copied';
-Blockly.Msg.PASTED_BLOCK_FROM_CLIPBOARD_MSG = 'pasted';
-Blockly.Msg.PASTED_BLOCK_TO_MARKED_SPOT_MSG = 'moved to marked spot';
-Blockly.Msg.MARKED_SPOT_MSG = 'Marked spot';
+
+Blockly.Msg.BLOCK_OPTIONS = 'Block options: ';
+
Blockly.Msg.BLOCK_MOVED_TO_MARKED_SPOT_MSB = 'Block moved to marked spot: ';
-Blockly.Msg.TOOLBOX_BLOCK = 'toolbox block';
-Blockly.Msg.WORKSPACE_BLOCK = 'workspace block';
-Blockly.Msg.SUBMENU_INDICATOR = 'move right to view submenu';
-Blockly.Msg.BLOCK_OPTIONS = 'Block options';
+Blockly.Msg.COPIED_BLOCK_MSG = 'copied. ';
+Blockly.Msg.MARKED_SPOT_MSG = 'Marked spot. ';
+Blockly.Msg.PASTED_BLOCK_FROM_CLIPBOARD_MSG = 'pasted. ';
+Blockly.Msg.PASTED_BLOCK_TO_MARKED_SPOT_MSG = 'moved to marked spot. ';
diff --git a/accessible/toolbox-tree.component.js b/accessible/toolbox-tree.component.js
index ee60a4d22..b3cbbb61d 100644
--- a/accessible/toolbox-tree.component.js
+++ b/accessible/toolbox-tree.component.js
@@ -30,13 +30,21 @@ blocklyApp.ToolboxTreeComponent = ng.core
template: `