mirror of
https://github.com/google/blockly.git
synced 2026-05-19 18:40:11 +02:00
fix: labels for multi-statement blocks (#9868)
* fix: labels for multi-statement blocks * chore: re-add message after merge conflict
This commit is contained in:
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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);
|
||||
@@ -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()
|
||||
@@ -582,7 +606,7 @@ function getInputCountLabel(block: BlockSvg) {
|
||||
);
|
||||
}, 0);
|
||||
|
||||
switch (inputCount) {
|
||||
switch (valueInputCount) {
|
||||
case 0:
|
||||
return undefined;
|
||||
case 1:
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
@@ -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",
|
||||
|
||||
@@ -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}}",
|
||||
@@ -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.",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
Reference in New Issue
Block a user