mirror of
https://github.com/google/blockly.git
synced 2026-05-26 14:00:07 +02:00
feat: use custom labels for block parent input labels (#9867)
This commit is contained in:
@@ -154,10 +154,24 @@ export function computeFieldRowLabel(
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a description of the parent statement input a block is attached to.
|
||||
* When a block is connected to a statement input, the input's field row label
|
||||
* will be prepended to the block's description to indicate that the block
|
||||
* begins a clause in its parent block.
|
||||
* Returns a description of the parent input a block is attached to.
|
||||
* When a block is connected to an input, the input's label will sometimes
|
||||
* be prepended to the block's description.
|
||||
*
|
||||
* If an input has a custom label, the custom label will be prepended
|
||||
* to the first child block connected to that input.
|
||||
*
|
||||
* If an input does not have a custom label, the input's fallback
|
||||
* label determined from the field row will be prepended to the
|
||||
* child block's label only if the following are true:
|
||||
* - the parent block has at least one statement input
|
||||
* - the child block in question is not attached to the first
|
||||
* statement input of the parent block (in this case, the label
|
||||
* would be redundant with the parent block's label)
|
||||
*
|
||||
* For statement inputs, the resolved label (whether custom or fallback) is
|
||||
* wrapped in the "Begin %1" prefix so the readout indicates that the child
|
||||
* block starts the body of the statement input.
|
||||
*
|
||||
* @internal
|
||||
* @param block The block to generate a parent input label for.
|
||||
@@ -168,24 +182,41 @@ function getParentInputLabel(block: BlockSvg) {
|
||||
const parentInput = (
|
||||
block.outputConnection ?? block.previousConnection
|
||||
)?.targetConnection?.getParentInput();
|
||||
const parentBlock = parentInput?.getSourceBlock();
|
||||
if (!parentInput) return undefined;
|
||||
|
||||
if (parentBlock?.isInsertionMarker()) return undefined;
|
||||
if (!parentBlock?.statementInputCount) return undefined;
|
||||
const parentBlock = parentInput.getSourceBlock();
|
||||
if (parentBlock.isInsertionMarker()) return undefined;
|
||||
|
||||
const firstStatementInput = parentBlock.inputList.find(
|
||||
(i) => i.type === inputTypes.STATEMENT,
|
||||
);
|
||||
// The first statement input in a block has no field row label as it would
|
||||
// be duplicative of the block's label.
|
||||
if (!parentInput || parentInput === firstStatementInput) {
|
||||
return undefined;
|
||||
// parentInput is only non-null when this block is directly attached to the
|
||||
// input (i.e. it is the first child block in that input). A custom label
|
||||
// is always prepended for the first child; a fallback label from the field
|
||||
// row is only used in select circumstances.
|
||||
let inputLabel: string | string[];
|
||||
const customLabel = parentInput.getAriaLabelText();
|
||||
if (customLabel) {
|
||||
inputLabel = customLabel;
|
||||
} else {
|
||||
if (!parentBlock.statementInputCount) return undefined;
|
||||
|
||||
const firstStatementInput = parentBlock.inputList.find(
|
||||
(i) => i.type === inputTypes.STATEMENT,
|
||||
);
|
||||
// The first statement input in a block has no field row label as it would
|
||||
// be duplicative of the block's label.
|
||||
if (parentInput === firstStatementInput) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
inputLabel = computeFieldRowLabel(parentInput, true);
|
||||
}
|
||||
|
||||
const parentInputLabel = computeFieldRowLabel(parentInput, true);
|
||||
return parentInput.type === inputTypes.STATEMENT
|
||||
? Msg['BLOCK_LABEL_BEGIN_PREFIX'].replace('%1', parentInputLabel.join(' '))
|
||||
: parentInputLabel;
|
||||
if (parentInput.type === inputTypes.STATEMENT) {
|
||||
const labelText = Array.isArray(inputLabel)
|
||||
? inputLabel.join(' ')
|
||||
: inputLabel;
|
||||
return Msg['BLOCK_LABEL_BEGIN_PREFIX'].replace('%1', labelText);
|
||||
}
|
||||
return inputLabel;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -371,7 +371,7 @@ suite('ARIA', function () {
|
||||
assert.notInclude(label, 'Begin stack');
|
||||
});
|
||||
|
||||
test('Nested statement blocks in first statement input do not include their parent input in their label', function () {
|
||||
test('Statement blocks in first statement input do not include their parent input in their label', function () {
|
||||
const ifBlock = this.makeBlock('controls_ifelse');
|
||||
const printBlock = this.makeBlock('text_print');
|
||||
ifBlock.getInput('IF0').connection.connect(printBlock.previousConnection);
|
||||
@@ -382,7 +382,7 @@ suite('ARIA', function () {
|
||||
assert.isFalse(label.startsWith('Begin do'));
|
||||
});
|
||||
|
||||
test('Nested statement blocks in subsequent statement inputs include their parent input in their label', function () {
|
||||
test('Statement blocks in subsequent statement inputs include their parent input in their label', function () {
|
||||
const ifBlock = this.makeBlock('controls_ifelse');
|
||||
const printBlock = this.makeBlock('text_print');
|
||||
ifBlock
|
||||
@@ -395,6 +395,75 @@ suite('ARIA', function () {
|
||||
assert.isTrue(label.startsWith('Begin else'));
|
||||
});
|
||||
|
||||
test('A custom statement input label is wrapped in the "Begin" prefix', function () {
|
||||
const ifBlock = this.makeBlock('controls_ifelse');
|
||||
ifBlock.getInput('ELSE').setAriaLabelProvider('otherwise do');
|
||||
const printBlock = this.makeBlock('text_print');
|
||||
ifBlock
|
||||
.getInput('ELSE')
|
||||
.connection.connect(printBlock.previousConnection);
|
||||
const label = Blockly.utils.aria.getState(
|
||||
printBlock.getFocusableElement(),
|
||||
Blockly.utils.aria.State.LABEL,
|
||||
);
|
||||
assert.include(label, 'Begin otherwise do');
|
||||
});
|
||||
|
||||
test('A custom label on the first statement input is prepended to its child block label', function () {
|
||||
const ifBlock = this.makeBlock('controls_ifelse');
|
||||
ifBlock.getInput('DO0').setAriaLabelProvider('then do');
|
||||
const printBlock = this.makeBlock('text_print');
|
||||
ifBlock.getInput('DO0').connection.connect(printBlock.previousConnection);
|
||||
const label = Blockly.utils.aria.getState(
|
||||
printBlock.getFocusableElement(),
|
||||
Blockly.utils.aria.State.LABEL,
|
||||
);
|
||||
assert.include(label, 'Begin then do');
|
||||
});
|
||||
|
||||
test('A custom input label is only used for the first child block in a statement input stack', function () {
|
||||
const ifBlock = this.makeBlock('controls_ifelse');
|
||||
ifBlock.getInput('ELSE').setAriaLabelProvider('otherwise do');
|
||||
const firstPrintBlock = this.makeBlock('text_print');
|
||||
ifBlock
|
||||
.getInput('ELSE')
|
||||
.connection.connect(firstPrintBlock.previousConnection);
|
||||
const secondPrintBlock = this.makeBlock('text_print');
|
||||
firstPrintBlock.nextConnection.connect(
|
||||
secondPrintBlock.previousConnection,
|
||||
);
|
||||
const subsequentLabel = Blockly.utils.aria.getState(
|
||||
secondPrintBlock.getFocusableElement(),
|
||||
Blockly.utils.aria.State.LABEL,
|
||||
);
|
||||
assert.notInclude(subsequentLabel, 'otherwise do');
|
||||
});
|
||||
|
||||
test('A custom input label is prepended to the child block of a value input', function () {
|
||||
const ifBlock = this.makeBlock('controls_ifelse');
|
||||
ifBlock.getInput('IF0').setAriaLabelProvider('condition');
|
||||
const boolBlock = this.makeBlock('logic_boolean');
|
||||
ifBlock.getInput('IF0').connection.connect(boolBlock.outputConnection);
|
||||
const label = Blockly.utils.aria.getState(
|
||||
boolBlock.getFocusableElement(),
|
||||
Blockly.utils.aria.State.LABEL,
|
||||
);
|
||||
assert.include(label, 'condition');
|
||||
});
|
||||
|
||||
test('A block connected to a value input without a custom label does not include the input label', function () {
|
||||
const negateBlock = this.makeBlock('logic_negate');
|
||||
const boolBlock = this.makeBlock('logic_boolean');
|
||||
negateBlock
|
||||
.getInput('BOOL')
|
||||
.connection.connect(boolBlock.outputConnection);
|
||||
const label = Blockly.utils.aria.getState(
|
||||
boolBlock.getFocusableElement(),
|
||||
Blockly.utils.aria.State.LABEL,
|
||||
);
|
||||
assert.notInclude(label, 'not');
|
||||
});
|
||||
|
||||
test('Disabled blocks indicate that in their label', function () {
|
||||
const block = this.makeBlock('text_print');
|
||||
let label = Blockly.utils.aria.getState(
|
||||
|
||||
Reference in New Issue
Block a user