@@ -50,18 +50,18 @@ export enum ConnectionPreposition {
5050 * The returned label will be specialized based on whether the block is part of a
5151 * flyout.
5252 *
53+ * Custom input labels (from {@link Input.setAriaLabelProvider}) are not included
54+ * here; they are used only in move-mode disambiguation and parent-input context
55+ * via {@link Input.getAriaLabelText}.
56+ *
5357 * @internal
5458 * @param block The block for which an ARIA representation should be created.
5559 * @param verbosity How much detail to include in the description.
56- * @param useCustomInputLabels Whether to use custom labels for inputs, if they
57- * exist. We don't want to do this when just reading a block's label, but do
58- * want to in other scenarios such as move mode.
5960 * @returns The ARIA representation for the specified block.
6061 */
6162export function computeAriaLabel (
6263 block : BlockSvg ,
6364 verbosity = Verbosity . STANDARD ,
64- useCustomInputLabels = true ,
6565) {
6666 if ( block . isSimpleReporter ( ) ) {
6767 // special case for full-block field blocks.
@@ -73,7 +73,7 @@ export function computeAriaLabel(
7373 return [
7474 verbosity >= Verbosity . STANDARD && getBeginStackLabel ( block ) ,
7575 getParentInputLabel ( block ) ,
76- ...getInputLabels ( block , verbosity , useCustomInputLabels ) ,
76+ ...getInputLabels ( block , verbosity ) ,
7777 verbosity === Verbosity . LOQUACIOUS && getParentToolboxCategoryLabel ( block ) ,
7878 verbosity >= Verbosity . STANDARD && getDisabledLabel ( block ) ,
7979 verbosity >= Verbosity . STANDARD && getCollapsedLabel ( block ) ,
@@ -197,6 +197,10 @@ export function computeFieldRowLabel(
197197 * statement input of the parent block (in this case, the label
198198 * would be redundant with the parent block's label)
199199 *
200+ * For statement inputs without their own field labels, labels from other
201+ * inputs in the same statement section are included (via
202+ * {@link getInputLabelsSubset}), consistent with move-target disambiguation.
203+ *
200204 * For statement inputs, the resolved label (whether custom or fallback) is
201205 * wrapped in the "Begin %1" prefix so the readout indicates that the child
202206 * block starts the body of the statement input.
@@ -235,7 +239,14 @@ function getParentInputLabel(block: BlockSvg) {
235239 return undefined ;
236240 }
237241
238- inputLabel = computeFieldRowLabel ( parentInput , true ) ;
242+ const sectionLabels = getInputLabelsSubset (
243+ parentBlock as BlockSvg ,
244+ parentInput ,
245+ ) ;
246+ if ( ! sectionLabels . length ) {
247+ return undefined ;
248+ }
249+ inputLabel = sectionLabels . join ( ', ' ) ;
239250 }
240251
241252 if ( parentInput . type === inputTypes . STATEMENT ) {
@@ -272,21 +283,18 @@ function getBeginStackLabel(block: BlockSvg) {
272283 * their contents are returned as a single item in the array per top-level
273284 * input.
274285 *
275- * Generally, if a custom label for an input is provided, that is preferred.
276- * However, we do not surface the custom labels when simply reading the text of
277- * the block. They are used as supplementary information for situations like
278- * move mode or when an input itself is focused.
286+ * Uses derived labels only (field row text and connected block content via
287+ * {@link Input.getLabel}). Custom input labels are not included; see
288+ * {@link Input.getAriaLabelText} for move-mode and parent-input usage.
279289 *
280290 * @internal
281291 * @param block The block to retrieve a list of field/input labels for.
282- * @param verbosity
283- * @param useCustomLabels whether to use the custom label for an input, if it's present.
292+ * @param verbosity How much detail to include in each input label.
284293 * @returns A list of field/input labels for the given block.
285294 */
286295export function getInputLabels (
287296 block : BlockSvg ,
288297 verbosity = Verbosity . STANDARD ,
289- useCustomLabels = true ,
290298) : string [ ] {
291299 const visibleInputs = block . inputList . filter ( ( input ) => input . isVisible ( ) ) ;
292300 let inputsToLabel = visibleInputs ;
@@ -306,15 +314,13 @@ export function getInputLabels(
306314 }
307315 }
308316
309- return inputsToLabel . map ( ( input ) => {
310- const customLabel = useCustomLabels ? input . getAriaLabelText ( ) : null ;
311- return customLabel ?? input . getLabel ( verbosity , useCustomLabels ) ;
312- } ) ;
317+ return inputsToLabel . map ( ( input ) => input . getLabel ( verbosity ) ) ;
313318}
314319
315320/**
316- * Returns a subset of labels for inputs on the given block, ending at the
317- * specified input.
321+ * Returns a subset of derived labels for inputs on the given block, ending at
322+ * the specified input. Used to disambiguate move targets and connection
323+ * highlights when no custom label is set.
318324 *
319325 * The subset is determined based on the input type:
320326 * - For non-statement inputs, only the label for the given input is returned.
@@ -323,41 +329,74 @@ export function getInputLabels(
323329 * begins immediately after the previous statement input, or at the start of
324330 * the block if none exists.
325331 *
332+ * Label resolution (see also {@link computeMoveConnectionLabel}):
333+ * 1. Custom labels ({@link Input.getAriaLabelText}) are handled by callers, not here.
334+ * 2. Derived labels from {@link Input.getLabel} (field row + child blocks).
335+ * 3. Numbered fallback ({@link Msg.INPUT_LABEL_INDEX}) when tier 2 is empty.
336+ * For the statement target input, the fallback is omitted if any earlier
337+ * input in the subset already produced a label.
338+ *
326339 * @internal
327340 * @param block The block to retrieve a list of field/input labels for.
328341 * @param input The input that defines the end of the subset.
329342 * @returns A list of field/input labels for the given block.
330343 */
331- export function getInputLabelsSubset (
332- block : BlockSvg ,
333- input : Input ,
334- includeFallbackLabels = true ,
335- ) : string [ ] {
344+ export function getInputLabelsSubset ( block : BlockSvg , input : Input ) : string [ ] {
336345 const inputIndex = block . inputList . indexOf ( input ) ;
337346 if ( inputIndex === - 1 ) {
338347 throw new Error (
339348 `Input with name "${ input . name } " not found on block with id "${ block . id } ".` ,
340349 ) ;
341350 }
351+ const isStatementTarget = input . type === inputTypes . STATEMENT ;
342352
343- const startIndex =
344- input . type === inputTypes . STATEMENT
345- ? findStartOfStatementSection ( block . inputList , inputIndex )
346- : inputIndex ;
353+ const startIndex = isStatementTarget
354+ ? findStartOfStatementSection ( block . inputList , inputIndex )
355+ : inputIndex ;
347356
348- return block . inputList
357+ // For statement inputs, we include all visible inputs from the start
358+ // of the current statement section up to and including the target input.
359+ // For non-statement inputs, this will just be the target input itself.
360+ const inputsInSubset = block . inputList
349361 . slice ( startIndex , inputIndex + 1 )
350- . filter ( ( input ) => input . isVisible ( ) )
351- . map (
352- ( input ) =>
353- input . getLabel ( Verbosity . TERSE , false ) ||
354- ( includeFallbackLabels
355- ? Msg [ 'INPUT_LABEL_INDEX' ] . replace (
356- '%1' ,
357- ( input . getIndex ( ) + 1 ) . toString ( ) ,
358- )
359- : undefined ) ,
360- )
362+ . filter ( ( subsetInput ) => subsetInput . isVisible ( ) ) ;
363+
364+ // The derived labels are based on the field row and any connected child
365+ // blocks.
366+ const derivedLabels = inputsInSubset . map ( ( subsetInput ) =>
367+ subsetInput . getLabel ( Verbosity . TERSE ) ,
368+ ) ;
369+
370+ // For statement inputs, we only include the fallback label ("input %1")
371+ // for the target input if no preceding input in the subset has a label.
372+ // This prevents, e.g., "else" statement inputs from being read as "else, input 2".
373+ const precedingLabelsProvideContext =
374+ isStatementTarget && derivedLabels . slice ( 0 , - 1 ) . some ( ( label ) => ! ! label ) ;
375+
376+ return derivedLabels
377+ . map ( ( label , index ) => {
378+ if ( label ) {
379+ return label ;
380+ }
381+ const subsetInput = inputsInSubset [ index ] ;
382+ // Dummy and end-row inputs are not connection inputs; getIndex() is -1
383+ // and would produce a misleading "input 0" fallback label.
384+ if (
385+ subsetInput . type === inputTypes . DUMMY ||
386+ subsetInput . type === inputTypes . END_ROW
387+ ) {
388+ return undefined ;
389+ }
390+ const isStatementTargetInput =
391+ isStatementTarget && index === derivedLabels . length - 1 ;
392+ if ( isStatementTargetInput && precedingLabelsProvideContext ) {
393+ return undefined ;
394+ }
395+ return Msg [ 'INPUT_LABEL_INDEX' ] . replace (
396+ '%1' ,
397+ ( subsetInput . getIndex ( ) + 1 ) . toString ( ) ,
398+ ) ;
399+ } )
361400 . filter ( ( label ) => label !== undefined ) ;
362401}
363402
@@ -468,7 +507,13 @@ function getAnnouncementTemplate(preposition: ConnectionPreposition): string {
468507}
469508
470509/**
471- * Returns a label for a connection includes either a block label, input label or both.
510+ * Returns a label for a connection that includes either a block label, input
511+ * label, or both.
512+ *
513+ * Input label resolution:
514+ * 1. Custom label from {@link Input.getAriaLabelText} when set.
515+ * 2. Otherwise derived labels from {@link getInputLabelsSubset} (field row,
516+ * child blocks, and numbered fallbacks as needed).
472517 *
473518 * @param conn The connection to generate a label for.
474519 * @param baseLabel An optional block label to include in the returned string.
@@ -483,8 +528,6 @@ function computeMoveConnectionLabel(
483528
484529 let inputLabel = input . getAriaLabelText ( ) ;
485530
486- // If the input doesn't have a custom ARIA label, compute one using the labels from
487- // nearby fields.
488531 if ( ! inputLabel ) {
489532 const labels = getInputLabelsSubset ( conn . getSourceBlock ( ) , input ) ;
490533 if ( ! labels . length ) return baseLabel ;
0 commit comments