diff --git a/packages/blockly/core/inputs/input.ts b/packages/blockly/core/inputs/input.ts index 31b51a1c32d..48a4849d04b 100644 --- a/packages/blockly/core/inputs/input.ts +++ b/packages/blockly/core/inputs/input.ts @@ -375,7 +375,13 @@ export class Input { getRowId(): string { const inputs = this.getSourceBlock().inputList; - // The first input in a block has the same ID as its parent block. + // The first visible input shares the block's row id; this also covers + // the collapsed-input placeholder, since every other input is hidden. + if (this === inputs.find((i) => i.isVisible())) { + return (this.getSourceBlock() as BlockSvg).getRowId(); + } + + // Fallback when inputs[0] itself is hidden. if (this === inputs[0]) { return (this.getSourceBlock() as BlockSvg).getRowId(); } diff --git a/packages/blockly/core/keyboard_nav/navigation_policies/block_navigation_policy.ts b/packages/blockly/core/keyboard_nav/navigation_policies/block_navigation_policy.ts index bd144f74dc4..389888142ce 100644 --- a/packages/blockly/core/keyboard_nav/navigation_policies/block_navigation_policy.ts +++ b/packages/blockly/core/keyboard_nav/navigation_policies/block_navigation_policy.ts @@ -119,13 +119,12 @@ export class BlockNavigationPolicy implements INavigationPolicy { * @returns A list of navigable/focusable children of the given block. */ function getBlockNavigationCandidates(block: BlockSvg): IFocusableNode[] { - // Collapsed blocks have no navigable children. - if (block.isCollapsed()) return []; - const candidates: IFocusableNode[] = []; // Icons and open bubbles are navigable. for (const icon of block.getIcons()) { + // Icons hidden when the block is collapsed shouldn't be navigable. + if (block.isCollapsed() && !icon.isShownWhenCollapsed()) continue; candidates.push(icon); let bubble; if ( @@ -162,7 +161,7 @@ function getBlockNavigationCandidates(block: BlockSvg): IFocusableNode[] { } // The block's next connection is navigable. - if (block.nextConnection) { + if (block.nextConnection && !block.isCollapsed()) { candidates.push(block.nextConnection); } diff --git a/packages/blockly/tests/mocha/navigation_test.js b/packages/blockly/tests/mocha/navigation_test.js index 92cb2dc8bba..6baa85968c3 100644 --- a/packages/blockly/tests/mocha/navigation_test.js +++ b/packages/blockly/tests/mocha/navigation_test.js @@ -774,6 +774,43 @@ suite('Navigation', function () { const inNode = this.navigator.getFirstChild(this.blocks.buttonBlock); assert.isNull(inNode); }); + test('reachesClickableFieldOnCollapsedInput', function () { + // Models a pattern where a clickable field is appended to + // COLLAPSED_INPUT_NAME when the block collapses. + const block = this.blocks.buttonBlock; + block.setCollapsed(true); + const input = block.getInput(Blockly.constants.COLLAPSED_INPUT_NAME); + const expandButton = new Blockly.FieldImage( + 'https://www.gstatic.com/codesite/ph/images/star_on.gif', + 16, + 16, + 'expand', + () => {}, + ); + input.appendField(expandButton); + + const inNode = this.navigator.getInNode(block); + assert.equal(inNode, expandButton); + }); + test('skipsIconsHiddenWhenCollapsed', function () { + // Comment icons are hidden when the block is collapsed, so they + // should not be navigable. + const block = this.blocks.statementInput1; + block.setCommentText('comment'); + block.setCollapsed(true); + assert.isNull(this.navigator.getInNode(block)); + }); + test('reachesIconsShownWhenCollapsed', function () { + // Warning icons remain shown when the block is collapsed, so they + // should still be navigable. + const block = this.blocks.statementInput1; + block.setWarningText('warning'); + block.setCollapsed(true); + assert.equal( + this.navigator.getInNode(block), + block.getIcon(Blockly.icons.IconType.WARNING), + ); + }); }); suite('Out', function () {