Skip to content

Commit 463f95e

Browse files
authored
fix: labels for multi-statement blocks (#9868)
* fix: labels for multi-statement blocks * chore: re-add message after merge conflict
1 parent 6513d08 commit 463f95e

6 files changed

Lines changed: 121 additions & 14 deletions

File tree

packages/blockly/core/block_aria_composer.ts

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -260,12 +260,28 @@ export function getInputLabels(
260260
verbosity = Verbosity.STANDARD,
261261
useCustomLabels = true,
262262
): string[] {
263-
return block.inputList
264-
.filter((input) => input.isVisible())
265-
.map((input) => {
266-
const customLabel = useCustomLabels ? input.getAriaLabelText() : null;
267-
return customLabel ?? input.getLabel(verbosity);
268-
});
263+
const visibleInputs = block.inputList.filter((input) => input.isVisible());
264+
let inputsToLabel = visibleInputs;
265+
266+
// For terse and standard verbosity levels, if there are multiple statement inputs,
267+
// only include labels up to the first one.
268+
if (verbosity <= Verbosity.STANDARD) {
269+
const statementInputs = visibleInputs.filter(
270+
(input) => input.type === inputTypes.STATEMENT,
271+
);
272+
273+
if (statementInputs.length > 1) {
274+
inputsToLabel = visibleInputs.slice(
275+
0,
276+
visibleInputs.indexOf(statementInputs[0]) + 1,
277+
);
278+
}
279+
}
280+
281+
return inputsToLabel.map((input) => {
282+
const customLabel = useCustomLabels ? input.getAriaLabelText() : null;
283+
return customLabel ?? input.getLabel(verbosity);
284+
});
269285
}
270286

271287
/**
@@ -482,9 +498,7 @@ export function computeMoveLabel(
482498
let blockLabel = isMoveStart
483499
? local.getSourceBlock().getStackBlocksCountLabel()
484500
: '';
485-
let neighbourLabel = (neighbour.getSourceBlock() as BlockSvg).getAriaLabel(
486-
Verbosity.TERSE,
487-
);
501+
let neighbourLabel = neighbour.getSourceBlock().getAriaLabel(Verbosity.TERSE);
488502

489503
if (includeLocalContext) {
490504
blockLabel = computeMoveConnectionLabel(local, blockLabel);
@@ -571,7 +585,17 @@ function getShadowBlockLabel(block: BlockSvg) {
571585
* otherwise undefined.
572586
*/
573587
function getInputCountLabel(block: BlockSvg) {
574-
const inputCount = block.inputList.reduce((totalSum, input) => {
588+
const branchCount = block.inputList.filter(
589+
(input) => input.type === inputTypes.STATEMENT,
590+
).length;
591+
592+
if (branchCount > 1) {
593+
return Msg['BLOCK_LABEL_HAS_BRANCHES'].replace(
594+
'%1',
595+
branchCount.toString(),
596+
);
597+
}
598+
const valueInputCount = block.inputList.reduce((totalSum, input) => {
575599
return (
576600
input.fieldRow.reduce((fieldCount, field) => {
577601
return field.EDITABLE && !field.isFullBlockField()
@@ -582,7 +606,7 @@ function getInputCountLabel(block: BlockSvg) {
582606
);
583607
}, 0);
584608

585-
switch (inputCount) {
609+
switch (valueInputCount) {
586610
case 0:
587611
return undefined;
588612
case 1:

packages/blockly/msg/json/en.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"@metadata": {
33
"author": "Ellen Spertus <ellen.spertus@gmail.com>",
4-
"lastupdated": "2026-05-12 16:03:06.800029",
4+
"lastupdated": "2026-05-14 08:05:42.601410",
55
"locale": "en",
66
"messagedocumentation" : "qqq"
77
},
@@ -475,6 +475,7 @@
475475
"BLOCK_LABEL_REPLACEABLE": "replaceable",
476476
"BLOCK_LABEL_HAS_INPUT": "has input",
477477
"BLOCK_LABEL_HAS_INPUTS": "has inputs",
478+
"BLOCK_LABEL_HAS_BRANCHES": "has %1 branches",
478479
"BLOCK_LABEL_STATEMENT": "command",
479480
"BLOCK_LABEL_CONTAINER": "container",
480481
"BLOCK_LABEL_VALUE": "value",

packages/blockly/msg/json/qqq.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,18 @@
11
{
2+
"@metadata": {
3+
"authors": [
4+
"Ajeje Brazorf",
5+
"Amire80",
6+
"Espertus",
7+
"Liuxinyu970226",
8+
"McDutchie",
9+
"Metalhead64",
10+
"Nike",
11+
"Robby",
12+
"Shirayuki",
13+
"YaronSh"
14+
]
15+
},
216
"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}}",
317
"UNNAMED_KEY": "default name - A simple, default name for an unnamed function or variable. Preferably indicates that the item is unnamed.",
418
"TODAY": "button text - Button that sets a calendar to today's date.\n{{Identical|Today}}",
@@ -469,6 +483,7 @@
469483
"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.",
470484
"BLOCK_LABEL_HAS_INPUT": "Part of an accessibility label for a block that indicates that it has a single input.",
471485
"BLOCK_LABEL_HAS_INPUTS": "Part of an accessibility label for a block that indicates that it has more than one input.",
486+
"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.",
472487
"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.",
473488
"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.",
474489
"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.",

packages/blockly/msg/messages.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1881,6 +1881,10 @@ Blockly.Msg.BLOCK_LABEL_HAS_INPUT = 'has input';
18811881
/// than one input.
18821882
Blockly.Msg.BLOCK_LABEL_HAS_INPUTS = 'has inputs';
18831883
/** @type {string} */
1884+
/// Part of an accessibility label for a block that indicates that it has more
1885+
/// than one statement input, such as branches of an if-else block.
1886+
Blockly.Msg.BLOCK_LABEL_HAS_BRANCHES = 'has %1 branches';
1887+
/** @type {string} */
18841888
/// Part of an accessibility label for a block that indicates that it is
18851889
/// a statement block, i.e. that it has a next or previous connection.
18861890
/// "command" here is used in the sense of a computer command, or a

packages/blockly/tests/mocha/aria_test.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,34 @@ suite('ARIA', function () {
521521
);
522522
assert.isTrue(label.endsWith('has inputs'));
523523
});
524+
test('Blocks with multiple statement inputs are properly labeled', function () {
525+
const json = {
526+
'blocks': {
527+
'languageVersion': 0,
528+
'blocks': [
529+
{
530+
'type': 'controls_if',
531+
'id': 'ifBlock',
532+
'x': 0,
533+
'y': 100,
534+
'extraState': {
535+
'elseIfCount': 2,
536+
'hasElse': true,
537+
},
538+
},
539+
],
540+
},
541+
};
542+
Blockly.serialization.workspaces.load(json, this.workspace);
543+
const block = this.workspace.getBlockById('ifBlock');
544+
const label = Blockly.utils.aria.getState(
545+
block.getFocusableElement(),
546+
Blockly.utils.aria.State.LABEL,
547+
);
548+
assert.isFalse(label.includes('else if, do'));
549+
assert.isFalse(label.includes('else,'));
550+
assert.isTrue(label.endsWith('has 4 branches'));
551+
});
524552
});
525553

526554
suite('Rendered connection highlight ARIA', function () {

packages/blockly/tests/mocha/keyboard_movement_test.js

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1263,14 +1263,49 @@ suite('Keyboard-driven movement', function () {
12631263
this.moveAndAssert(
12641264
moveRight,
12651265
['Moving', 'else if, do', 'around', 'draw', '❤️'],
1266-
[this.getBlockLabel(ifBlock)],
1266+
['of'],
12671267
);
12681268
this.moveAndAssert(
12691269
moveRight,
12701270
['Moving', 'if, do', 'around', 'draw', '❤️'],
1271-
[this.getBlockLabel(ifBlock)],
1271+
['of'],
12721272
);
1273+
cancelMove(this.workspace);
1274+
});
1275+
test("doesn't announce full block labels for multi-statement target blocks", function () {
1276+
const json = {
1277+
'blocks': {
1278+
'languageVersion': 0,
1279+
'blocks': [
1280+
{
1281+
'type': 'draw_emoji',
1282+
'id': 'drawBlock',
1283+
'x': 0,
1284+
'y': 0,
1285+
},
1286+
{
1287+
'type': 'controls_if',
1288+
'id': 'ifBlock',
1289+
'x': 0,
1290+
'y': 100,
1291+
'extraState': {
1292+
'elseIfCount': 2,
1293+
},
1294+
},
1295+
],
1296+
},
1297+
};
1298+
Blockly.serialization.workspaces.load(json, this.workspace);
1299+
const drawBlock = this.workspace.getBlockById('drawBlock');
1300+
const ifBlock = this.workspace.getBlockById('ifBlock');
12731301

1302+
Blockly.getFocusManager().focusNode(drawBlock);
1303+
startMove(this.workspace); // on workspace
1304+
this.moveAndAssert(
1305+
moveRight,
1306+
['Moving', 'before', ifBlock.getAriaLabel(0)],
1307+
[ifBlock.getAriaLabel(1), ifBlock.getAriaLabel(2)],
1308+
);
12741309
cancelMove(this.workspace);
12751310
});
12761311
test('disambiguates with custom input labels around blocks', function () {

0 commit comments

Comments
 (0)