From d554d065256cc2141874e607bb9f511020eb2e2c Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Mon, 13 Apr 2026 12:44:20 -0700 Subject: [PATCH 1/2] fix: Inhibit keyboard navigation shortcuts when the dropdown or widget divs are open --- packages/blockly/core/shortcut_items.ts | 37 ++++++++++++++++++++----- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/packages/blockly/core/shortcut_items.ts b/packages/blockly/core/shortcut_items.ts index 5e3cea346bf..44f026730bc 100644 --- a/packages/blockly/core/shortcut_items.ts +++ b/packages/blockly/core/shortcut_items.ts @@ -10,6 +10,7 @@ import {BlockSvg} from './block_svg.js'; import * as clipboard from './clipboard.js'; import {RenderedWorkspaceComment} from './comments.js'; import * as contextmenu from './contextmenu.js'; +import * as dropDownDiv from './dropdowndiv.js'; import * as eventUtils from './events/utils.js'; import {getFocusManager} from './focus_manager.js'; import {hasContextMenu} from './interfaces/i_contextmenu.js'; @@ -27,6 +28,7 @@ import {Coordinate} from './utils/coordinate.js'; import {KeyCodes} from './utils/keycodes.js'; import {Rect} from './utils/rect.js'; import * as svgMath from './utils/svg_math.js'; +import * as widgetDiv from './widgetdiv.js'; import {WorkspaceSvg} from './workspace_svg.js'; /** @@ -572,7 +574,10 @@ export function registerArrowNavigation() { /** Go to the next location to the right. */ right: { name: names.NAVIGATE_RIGHT, - preconditionFn: (workspace) => !workspace.isDragging(), + preconditionFn: (workspace) => + !workspace.isDragging() && + !dropDownDiv.isVisible() && + !widgetDiv.isVisible(), callback: (workspace, e) => { e.preventDefault(); keyboardNavigationController.setIsActive(true); @@ -590,7 +595,10 @@ export function registerArrowNavigation() { /** Go to the next location to the left. */ left: { name: names.NAVIGATE_LEFT, - preconditionFn: (workspace) => !workspace.isDragging(), + preconditionFn: (workspace) => + !workspace.isDragging() && + !dropDownDiv.isVisible() && + !widgetDiv.isVisible(), callback: (workspace, e) => { e.preventDefault(); keyboardNavigationController.setIsActive(true); @@ -608,7 +616,10 @@ export function registerArrowNavigation() { /** Go down to the next location. */ down: { name: names.NAVIGATE_DOWN, - preconditionFn: (workspace) => !workspace.isDragging(), + preconditionFn: (workspace) => + !workspace.isDragging() && + !dropDownDiv.isVisible() && + !widgetDiv.isVisible(), callback: (_workspace, e) => { e.preventDefault(); keyboardNavigationController.setIsActive(true); @@ -626,7 +637,10 @@ export function registerArrowNavigation() { /** Go up to the previous location. */ up: { name: names.NAVIGATE_UP, - preconditionFn: (workspace) => !workspace.isDragging(), + preconditionFn: (workspace) => + !workspace.isDragging() && + !dropDownDiv.isVisible() && + !widgetDiv.isVisible(), callback: (_workspace, e) => { e.preventDefault(); keyboardNavigationController.setIsActive(true); @@ -808,7 +822,10 @@ export function registerStackNavigation() { const nextStackShortcut: KeyboardShortcut = { name: names.NEXT_STACK, preconditionFn: (workspace) => - !workspace.isDragging() && !!resolveStack(workspace), + !workspace.isDragging() && + !!resolveStack(workspace) && + !dropDownDiv.isVisible() && + !widgetDiv.isVisible(), callback: (workspace) => { keyboardNavigationController.setIsActive(true); const start = resolveStack(workspace); @@ -824,7 +841,10 @@ export function registerStackNavigation() { const previousStackShortcut: KeyboardShortcut = { name: names.PREVIOUS_STACK, preconditionFn: (workspace) => - !workspace.isDragging() && !!resolveStack(workspace), + !workspace.isDragging() && + !!resolveStack(workspace) && + !dropDownDiv.isVisible() && + !widgetDiv.isVisible(), callback: (workspace) => { keyboardNavigationController.setIsActive(true); const start = resolveStack(workspace); @@ -853,7 +873,10 @@ export function registerStackNavigation() { export function registerPerformAction() { const performActionShortcut: KeyboardShortcut = { name: names.PERFORM_ACTION, - preconditionFn: (workspace) => !workspace.isDragging(), + preconditionFn: (workspace) => + !workspace.isDragging() && + !dropDownDiv.isVisible() && + !widgetDiv.isVisible(), callback: (_workspace, e) => { keyboardNavigationController.setIsActive(true); const focusedNode = getFocusManager().getFocusedNode(); From eb0a5c5aba06a156305298115647d13b9410ec4c Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Mon, 13 Apr 2026 14:46:23 -0700 Subject: [PATCH 2/2] test: Add tests --- .../tests/mocha/keyboard_navigation_test.js | 31 +++++++ .../tests/mocha/shortcut_items_test.js | 83 ++++++++++++++++++- 2 files changed, 113 insertions(+), 1 deletion(-) diff --git a/packages/blockly/tests/mocha/keyboard_navigation_test.js b/packages/blockly/tests/mocha/keyboard_navigation_test.js index 9d5614ca9c6..58fb3d9356b 100644 --- a/packages/blockly/tests/mocha/keyboard_navigation_test.js +++ b/packages/blockly/tests/mocha/keyboard_navigation_test.js @@ -255,6 +255,37 @@ suite('Keyboard navigation on Blocks', function () { pressKey(this.workspace, Blockly.utils.KeyCodes.RIGHT); assert.include(getFocusNodeId(), 'text_1_field_'); }); + + test('Is inhibited when widgetdiv is visible', function () { + focusBlock(this.workspace, 'text_print_1'); + this.workspace.getBlockById('text_print_1').showContextMenu(); + assert.isTrue(Blockly.WidgetDiv.isVisible()); + pressKey(this.workspace, Blockly.utils.KeyCodes.RIGHT); + assert.equal(getFocusedBlockId(), 'text_print_1'); + pressKey(this.workspace, Blockly.utils.KeyCodes.LEFT); + assert.equal(getFocusedBlockId(), 'text_print_1'); + pressKey(this.workspace, Blockly.utils.KeyCodes.UP); + assert.equal(getFocusedBlockId(), 'text_print_1'); + pressKey(this.workspace, Blockly.utils.KeyCodes.DOWN); + assert.equal(getFocusedBlockId(), 'text_print_1'); + }); + + test('Is inhibited when dropdowndiv is visible', function () { + focusBlock(this.workspace, 'logic_boolean_1'); + this.workspace + .getBlockById('logic_boolean_1') + .getField('BOOL') + .showEditor(); + assert.isTrue(Blockly.DropDownDiv.isVisible()); + pressKey(this.workspace, Blockly.utils.KeyCodes.RIGHT); + assert.equal(getFocusedBlockId(), 'logic_boolean_1'); + pressKey(this.workspace, Blockly.utils.KeyCodes.LEFT); + assert.equal(getFocusedBlockId(), 'logic_boolean_1'); + pressKey(this.workspace, Blockly.utils.KeyCodes.UP); + assert.equal(getFocusedBlockId(), 'logic_boolean_1'); + pressKey(this.workspace, Blockly.utils.KeyCodes.DOWN); + assert.equal(getFocusedBlockId(), 'logic_boolean_1'); + }); }); suite('Keyboard navigation on Fields', function () { diff --git a/packages/blockly/tests/mocha/shortcut_items_test.js b/packages/blockly/tests/mocha/shortcut_items_test.js index f9c7fe3f54a..1399a224de4 100644 --- a/packages/blockly/tests/mocha/shortcut_items_test.js +++ b/packages/blockly/tests/mocha/shortcut_items_test.js @@ -871,11 +871,16 @@ suite('Keyboard Shortcut Items', function () { setup(function () { this.block1 = this.workspace.newBlock('controls_if'); - this.block2 = this.workspace.newBlock('stack_block'); + this.block2 = this.workspace.newBlock('logic_compare'); this.block3 = this.workspace.newBlock('stack_block'); this.block2.moveBy(0, 100); this.block3.moveBy(0, 400); + for (const block of [this.block1, this.block2, this.block3]) { + block.initSvg(); + block.render(); + } + this.comment1 = this.workspace.newComment(); this.comment2 = this.workspace.newComment(); this.comment1.moveBy(0, 200); @@ -1017,6 +1022,50 @@ suite('Keyboard Shortcut Items', function () { this.block2, ); }); + + test('Navigating forward is inhibited when widgetdiv is visible', function () { + Blockly.getFocusManager().focusNode(this.block2); + this.block2.showContextMenu(); + assert.isTrue(Blockly.WidgetDiv.isVisible()); + this.injectionDiv.dispatchEvent(keyNextStack()); + assert.strictEqual( + Blockly.getFocusManager().getFocusedNode(), + this.block2, + ); + }); + + test('Navigating forward is inhibited when dropdowndiv is visible', function () { + Blockly.getFocusManager().focusNode(this.block2); + this.block2.getField('OP').showEditor(); + assert.isTrue(Blockly.DropDownDiv.isVisible()); + this.injectionDiv.dispatchEvent(keyNextStack()); + assert.strictEqual( + Blockly.getFocusManager().getFocusedNode(), + this.block2, + ); + }); + + test('Navigating backward is inhibited when widgetdiv is visible', function () { + Blockly.getFocusManager().focusNode(this.block2); + this.block2.showContextMenu(); + assert.isTrue(Blockly.WidgetDiv.isVisible()); + this.injectionDiv.dispatchEvent(keyPrevStack()); + assert.strictEqual( + Blockly.getFocusManager().getFocusedNode(), + this.block2, + ); + }); + + test('Navigating backward is inhibited when dropdowndiv is visible', function () { + Blockly.getFocusManager().focusNode(this.block2); + this.block2.getField('OP').showEditor(); + assert.isTrue(Blockly.DropDownDiv.isVisible()); + this.injectionDiv.dispatchEvent(keyPrevStack()); + assert.strictEqual( + Blockly.getFocusManager().getFocusedNode(), + this.block2, + ); + }); }); suite('Perform Action (Enter)', function () { @@ -1230,5 +1279,37 @@ suite('Keyboard Shortcut Items', function () { assert.isTrue(called); this.workspace.registerButtonCallback('CREATE_VARIABLE', oldCallback); }); + + test('Is inhibited when dropdowndiv is visible', function () { + const block = this.workspace.newBlock('logic_compare'); + block.initSvg(); + block.render(); + const field = block.getField('OP'); + Blockly.getFocusManager().focusNode(field); + field.showEditor(); + + assert.isTrue(Blockly.DropDownDiv.isVisible()); + + const event = createKeyDownEvent(Blockly.utils.KeyCodes.ENTER); + this.workspace.getInjectionDiv().dispatchEvent(event); + + assert.isTrue(Blockly.DropDownDiv.isVisible()); + }); + + test('Is inhibited when widgetdiv is visible', function () { + const block = this.workspace.newBlock('logic_compare'); + block.initSvg(); + block.render(); + const field = block.getField('OP'); + block.showContextMenu(); + Blockly.getFocusManager().focusNode(field); + + assert.isTrue(Blockly.WidgetDiv.isVisible()); + + const event = createKeyDownEvent(Blockly.utils.KeyCodes.ENTER); + this.workspace.getInjectionDiv().dispatchEvent(event); + + assert.isTrue(Blockly.WidgetDiv.isVisible()); + }); }); });