Skip to content
Merged
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
46 changes: 35 additions & 11 deletions packages/blockly/core/block_aria_composer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,12 +260,28 @@ export function getInputLabels(
verbosity = Verbosity.STANDARD,
useCustomLabels = true,
): string[] {
return block.inputList
.filter((input) => input.isVisible())
.map((input) => {
const customLabel = useCustomLabels ? input.getAriaLabelText() : null;
return customLabel ?? input.getLabel(verbosity);
});
const visibleInputs = block.inputList.filter((input) => input.isVisible());
let inputsToLabel = visibleInputs;

// For terse and standard verbosity levels, if there are multiple statement inputs,
// only include labels up to the first one.
if (verbosity <= Verbosity.STANDARD) {
const statementInputs = visibleInputs.filter(
(input) => input.type === inputTypes.STATEMENT,
);

if (statementInputs.length > 1) {
inputsToLabel = visibleInputs.slice(
0,
visibleInputs.indexOf(statementInputs[0]) + 1,
);
}
}

return inputsToLabel.map((input) => {
const customLabel = useCustomLabels ? input.getAriaLabelText() : null;
return customLabel ?? input.getLabel(verbosity);
});
}

/**
Expand Down Expand Up @@ -482,9 +498,7 @@ export function computeMoveLabel(
let blockLabel = isMoveStart
? local.getSourceBlock().getStackBlocksCountLabel()
: '';
let neighbourLabel = (neighbour.getSourceBlock() as BlockSvg).getAriaLabel(
Verbosity.TERSE,
);
let neighbourLabel = neighbour.getSourceBlock().getAriaLabel(Verbosity.TERSE);

if (includeLocalContext) {
blockLabel = computeMoveConnectionLabel(local, blockLabel);
Expand Down Expand Up @@ -571,7 +585,17 @@ function getShadowBlockLabel(block: BlockSvg) {
* otherwise undefined.
*/
function getInputCountLabel(block: BlockSvg) {
const inputCount = block.inputList.reduce((totalSum, input) => {
const branchCount = block.inputList.filter(
(input) => input.type === inputTypes.STATEMENT,
).length;

if (branchCount > 1) {
return Msg['BLOCK_LABEL_HAS_BRANCHES'].replace(
'%1',
branchCount.toString(),
);
}
const valueInputCount = block.inputList.reduce((totalSum, input) => {
return (
input.fieldRow.reduce((fieldCount, field) => {
return field.EDITABLE && !field.isFullBlockField()
Expand All @@ -582,7 +606,7 @@ function getInputCountLabel(block: BlockSvg) {
);
}, 0);

switch (inputCount) {
switch (valueInputCount) {
case 0:
return undefined;
case 1:
Expand Down
3 changes: 2 additions & 1 deletion packages/blockly/msg/json/en.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"@metadata": {
"author": "Ellen Spertus <ellen.spertus@gmail.com>",
"lastupdated": "2026-05-12 16:03:06.800029",
"lastupdated": "2026-05-14 08:05:42.601410",
"locale": "en",
"messagedocumentation" : "qqq"
},
Expand Down Expand Up @@ -475,6 +475,7 @@
"BLOCK_LABEL_REPLACEABLE": "replaceable",
"BLOCK_LABEL_HAS_INPUT": "has input",
"BLOCK_LABEL_HAS_INPUTS": "has inputs",
"BLOCK_LABEL_HAS_BRANCHES": "has %1 branches",
"BLOCK_LABEL_STATEMENT": "command",
"BLOCK_LABEL_CONTAINER": "container",
"BLOCK_LABEL_VALUE": "value",
Expand Down
15 changes: 15 additions & 0 deletions packages/blockly/msg/json/qqq.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,18 @@
{
"@metadata": {
"authors": [
"Ajeje Brazorf",
"Amire80",
"Espertus",
"Liuxinyu970226",
"McDutchie",
"Metalhead64",
"Nike",
"Robby",
"Shirayuki",
"YaronSh"
]
},
"VARIABLES_DEFAULT_NAME": "default name - A simple, general default name for a variable, preferably short. For more context, see [[Translating:Blockly#infrequent_message_types]].\n{{Identical|Item}}",
"UNNAMED_KEY": "default name - A simple, default name for an unnamed function or variable. Preferably indicates that the item is unnamed.",
"TODAY": "button text - Button that sets a calendar to today's date.\n{{Identical|Today}}",
Expand Down Expand Up @@ -469,6 +483,7 @@
"BLOCK_LABEL_REPLACEABLE": "Part of an accessibility label for a block that indicates that it is replaceable, i.e. that it is a shadow block.",
"BLOCK_LABEL_HAS_INPUT": "Part of an accessibility label for a block that indicates that it has a single input.",
"BLOCK_LABEL_HAS_INPUTS": "Part of an accessibility label for a block that indicates that it has more than one input.",
"BLOCK_LABEL_HAS_BRANCHES": "Part of an accessibility label for a block that indicates that it has more than one statement input, such as branches of an if-else block.",
"BLOCK_LABEL_STATEMENT": "Part of an accessibility label for a block that indicates that it is a statement block, i.e. that it has a next or previous connection. 'command' here is used in the sense of a computer command, or a command block in Scratch.",
"BLOCK_LABEL_CONTAINER": "Part of an accessibility label for a block that indicates that it is a container block, i.e. that it has one or more statement inputs.",
"BLOCK_LABEL_VALUE": "Part of an accessibility label for a block that indicates that it is a value block, i.e. that it has an output connection.",
Expand Down
4 changes: 4 additions & 0 deletions packages/blockly/msg/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -1881,6 +1881,10 @@ Blockly.Msg.BLOCK_LABEL_HAS_INPUT = 'has input';
/// than one input.
Blockly.Msg.BLOCK_LABEL_HAS_INPUTS = 'has inputs';
/** @type {string} */
/// Part of an accessibility label for a block that indicates that it has more
/// than one statement input, such as branches of an if-else block.
Blockly.Msg.BLOCK_LABEL_HAS_BRANCHES = 'has %1 branches';
/** @type {string} */
/// Part of an accessibility label for a block that indicates that it is
/// a statement block, i.e. that it has a next or previous connection.
/// "command" here is used in the sense of a computer command, or a
Expand Down
28 changes: 28 additions & 0 deletions packages/blockly/tests/mocha/aria_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,34 @@ suite('ARIA', function () {
);
assert.isTrue(label.endsWith('has inputs'));
});
test('Blocks with multiple statement inputs are properly labeled', function () {
const json = {
'blocks': {
'languageVersion': 0,
'blocks': [
{
'type': 'controls_if',
'id': 'ifBlock',
'x': 0,
'y': 100,
'extraState': {
'elseIfCount': 2,
'hasElse': true,
},
},
],
},
};
Blockly.serialization.workspaces.load(json, this.workspace);
const block = this.workspace.getBlockById('ifBlock');
const label = Blockly.utils.aria.getState(
block.getFocusableElement(),
Blockly.utils.aria.State.LABEL,
);
assert.isFalse(label.includes('else if, do'));
assert.isFalse(label.includes('else,'));
assert.isTrue(label.endsWith('has 4 branches'));
});
});

suite('Rendered connection highlight ARIA', function () {
Expand Down
39 changes: 37 additions & 2 deletions packages/blockly/tests/mocha/keyboard_movement_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1263,14 +1263,49 @@ suite('Keyboard-driven movement', function () {
this.moveAndAssert(
moveRight,
['Moving', 'else if, do', 'around', 'draw', '❤️'],
[this.getBlockLabel(ifBlock)],
['of'],
);
this.moveAndAssert(
moveRight,
['Moving', 'if, do', 'around', 'draw', '❤️'],
[this.getBlockLabel(ifBlock)],
['of'],
);
cancelMove(this.workspace);
});
test("doesn't announce full block labels for multi-statement target blocks", function () {
const json = {
'blocks': {
'languageVersion': 0,
'blocks': [
{
'type': 'draw_emoji',
'id': 'drawBlock',
'x': 0,
'y': 0,
},
{
'type': 'controls_if',
'id': 'ifBlock',
'x': 0,
'y': 100,
'extraState': {
'elseIfCount': 2,
},
},
],
},
};
Blockly.serialization.workspaces.load(json, this.workspace);
const drawBlock = this.workspace.getBlockById('drawBlock');
const ifBlock = this.workspace.getBlockById('ifBlock');

Blockly.getFocusManager().focusNode(drawBlock);
startMove(this.workspace); // on workspace
this.moveAndAssert(
moveRight,
['Moving', 'before', ifBlock.getAriaLabel(0)],
[ifBlock.getAriaLabel(1), ifBlock.getAriaLabel(2)],
);
cancelMove(this.workspace);
});
test('disambiguates with custom input labels around blocks', function () {
Expand Down