From e22287ec39150300ab84e3b5b47a31a112d02cc7 Mon Sep 17 00:00:00 2001 From: Michael Harvey <43474485+mikeharv@users.noreply.github.com> Date: Wed, 10 Jun 2026 13:28:25 -0400 Subject: [PATCH] fix: remove redundant labels for child blocks of parent input (#9967) * fix: remove redundant labels for child blocks of parent input * chore: make new argument optional --- packages/blockly/core/block_aria_composer.ts | 30 ++++++++++++++------ packages/blockly/core/inputs/input.ts | 13 +++++---- packages/blockly/core/rendered_connection.ts | 1 + 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/packages/blockly/core/block_aria_composer.ts b/packages/blockly/core/block_aria_composer.ts index 72ef21826..ed2e8e0dd 100644 --- a/packages/blockly/core/block_aria_composer.ts +++ b/packages/blockly/core/block_aria_composer.ts @@ -205,6 +205,9 @@ export function computeFieldRowLabel( * wrapped in the "Begin %1" prefix so the readout indicates that the child * block starts the body of the statement input. * + * Labels for child blocks of inputs are excluded because they are included + * with {@link getInputLabels} already. + * * @internal * @param block The block to generate a parent input label for. * @returns A description of the block's parent statement input, or undefined @@ -242,6 +245,7 @@ function getParentInputLabel(block: BlockSvg) { const sectionLabels = getInputLabelsSubset( parentBlock as BlockSvg, parentInput, + false, // Exclude labels from child blocks. ); if (!sectionLabels.length) { return undefined; @@ -338,17 +342,23 @@ export function getInputLabels( * * @internal * @param block The block to retrieve a list of field/input labels for. - * @param input The input that defines the end of the subset. + * @param endInput The input that defines the end of the subset. + * @param includeEndInputChildren Whether to include labels for child blocks + * connected to the end input. * @returns A list of field/input labels for the given block. */ -export function getInputLabelsSubset(block: BlockSvg, input: Input): string[] { - const inputIndex = block.inputList.indexOf(input); +export function getInputLabelsSubset( + block: BlockSvg, + endInput: Input, + includeEndInputChildren: boolean, +): string[] { + const inputIndex = block.inputList.indexOf(endInput); if (inputIndex === -1) { throw new Error( - `Input with name "${input.name}" not found on block with id "${block.id}".`, + `Input with name "${endInput.name}" not found on block with id "${block.id}".`, ); } - const isStatementTarget = input.type === inputTypes.STATEMENT; + const isStatementTarget = endInput.type === inputTypes.STATEMENT; const startIndex = isStatementTarget ? findStartOfStatementSection(block.inputList, inputIndex) @@ -362,9 +372,13 @@ export function getInputLabelsSubset(block: BlockSvg, input: Input): string[] { .filter((subsetInput) => subsetInput.isVisible()); // The derived labels are based on the field row and any connected child - // blocks. + // blocks. Labels for child blocks are potentially skipped if they would be + // redundant within the overall block label. const derivedLabels = inputsInSubset.map((subsetInput) => - subsetInput.getLabel(Verbosity.TERSE), + subsetInput.getLabel( + Verbosity.TERSE, + subsetInput !== endInput || includeEndInputChildren, + ), ); // For statement inputs, we only include the fallback label ("input %1") @@ -529,7 +543,7 @@ function computeMoveConnectionLabel( let inputLabel = input.getAriaLabelText(); if (!inputLabel) { - const labels = getInputLabelsSubset(conn.getSourceBlock(), input); + const labels = getInputLabelsSubset(conn.getSourceBlock(), input, true); if (!labels.length) return baseLabel; inputLabel = labels.join(', '); diff --git a/packages/blockly/core/inputs/input.ts b/packages/blockly/core/inputs/input.ts index 31b51a1c3..afb7d8209 100644 --- a/packages/blockly/core/inputs/input.ts +++ b/packages/blockly/core/inputs/input.ts @@ -400,18 +400,21 @@ export class Input { /** * Returns a derived accessibility label for this input: field row text plus - * labels of any connected child blocks. Does not include custom labels from - * {@link getAriaLabelText}; those are used in move-mode and parent-input - * context only. + * labels of any connected child blocks (unless excluded). Does not include + * custom labels from {@link getAriaLabelText}; those are used in move-mode + * and parent-input context only. * * @internal */ - getLabel(verbosity = Verbosity.STANDARD): string { + getLabel(verbosity = Verbosity.STANDARD, includeChildren = true): string { if (!this.isVisible()) return ''; const labels = computeFieldRowLabel(this, false, verbosity); - if (this.connection?.type === ConnectionType.INPUT_VALUE) { + if ( + includeChildren && + this.connection?.type === ConnectionType.INPUT_VALUE + ) { const childBlock = this.connection.targetBlock(); if (childBlock && !childBlock.isInsertionMarker()) { labels.push( diff --git a/packages/blockly/core/rendered_connection.ts b/packages/blockly/core/rendered_connection.ts index faa448d3a..856a74604 100644 --- a/packages/blockly/core/rendered_connection.ts +++ b/packages/blockly/core/rendered_connection.ts @@ -360,6 +360,7 @@ export class RenderedConnection getInputLabelsSubset( parentInput.getSourceBlock() as BlockSvg, parentInput, + true, ).join(', '); if (this.type === ConnectionType.NEXT_STATEMENT) { aria.setState(