Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion packages/blockly/core/block_aria_composer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import type {BlockSvg} from './block_svg.js';
import {ConnectionType} from './connection_type.js';
import {FieldLabel} from './field_label.js';
import type {Input} from './inputs/input.js';
import {inputTypes} from './inputs/input_types.js';
import {
Expand Down Expand Up @@ -123,6 +124,10 @@ export function configureAriaRole(block: BlockSvg) {
* `lookback` attribute is specified, all of the fields on the row immediately
* above the Input will be used instead.
*
* If the input contains multiple adjacent FieldLabel fields, they will be
* combined together into a singular label string so that screenreaders can
* know to read them together as one piece of text.
*
* Empty field labels are excluded because they don't provide useful context.
* Fields should generally have a helpful label, but there are exceptions, such
* as when empty label fields are used to control the layout of a block.
Expand All @@ -140,9 +145,34 @@ export function computeFieldRowLabel(
verbosity = Verbosity.STANDARD,
): string[] {
const includeTypeInfo = verbosity >= Verbosity.LOQUACIOUS;
let adjacentFieldLabels: Array<string> = [];
const fieldRowLabel = input.fieldRow
.filter((field) => field.isVisible())
.map((field) => field.computeAriaLabel(includeTypeInfo));
.flatMap((field, index, visibleFields) => {
const isFieldLabel = field instanceof FieldLabel;
if (isFieldLabel) {
if (
index < visibleFields.length - 1 &&
visibleFields[index + 1] instanceof FieldLabel
) {
// Both this item and the next item are FieldLabels. We want to
// combine these, so we add this one to the list for later handling.
adjacentFieldLabels.push(field.computeAriaLabel(includeTypeInfo));
return [];
} else if (adjacentFieldLabels.length >= 1) {
// There is at least one adjacent FieldLabel before this one but none
// after. Combine the FieldLabels into one string.
adjacentFieldLabels.push(field.computeAriaLabel(includeTypeInfo));
const label = adjacentFieldLabels.join(' ');
adjacentFieldLabels = [];
return label;
} else {
return field.computeAriaLabel(includeTypeInfo);
}
}
return field.computeAriaLabel(includeTypeInfo);
});

if (!fieldRowLabel.length && lookback) {
const inputs = input.getSourceBlock().inputList;
const index = inputs.indexOf(input);
Expand Down
23 changes: 20 additions & 3 deletions packages/blockly/tests/mocha/input_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -369,12 +369,29 @@ suite('Inputs', function () {
// AriaLabelProvider and without setting the provider (the default label)
assert.equal(labelA, labelB);
});
test('Field labels are comma separated', function () {
this.block.appendDummyInput().appendField('first').appendField('second');
test('Labels for fields are comma separated', function () {
this.block
.appendDummyInput()
.appendField('first')
.appendField(new Blockly.FieldNumber(0));

const label = this.block.getAriaLabel();

assert.include(label, 'first, 0');
});
test('Adjacent labels for FieldLabels are combined into one field.', function () {
this.block
.appendDummyInput()
.appendField('first')
.appendField('second')
.appendField(new Blockly.FieldNumber(0))
.appendField('third')
.appendField('fourth')
.appendField('fifth');

const label = this.block.getAriaLabel();

assert.include(label, 'first, second');
assert.include(label, 'first second, 0, third fourth fifth');
});
});
});
Loading