From 688a014e5912b03ce14a98f330987cdce5f98147 Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Mon, 27 Apr 2026 09:17:20 -0700 Subject: [PATCH] fix: Fix Escape in toolboxes and flyouts --- packages/blockly/core/flyout_base.ts | 11 ++++ packages/blockly/core/toolbox/toolbox.ts | 4 ++ .../tests/mocha/shortcut_items_test.js | 61 +++++++++++++++++++ 3 files changed, 76 insertions(+) diff --git a/packages/blockly/core/flyout_base.ts b/packages/blockly/core/flyout_base.ts index 3e0dbbee1..eab100253 100644 --- a/packages/blockly/core/flyout_base.ts +++ b/packages/blockly/core/flyout_base.ts @@ -289,6 +289,17 @@ export abstract class Flyout .getThemeManager() .subscribe(this.svgBackground_, 'flyoutOpacity', 'fill-opacity'); + this.svgGroup_.addEventListener('keydown', (e: KeyboardEvent) => { + if (e.key === 'Escape') { + getFocusManager().focusTree(this.targetWorkspace); + if (!this.targetWorkspace.isMutator) { + this.autoHide(false); + } + e.preventDefault(); + e.stopPropagation(); + } + }); + return this.svgGroup_; } diff --git a/packages/blockly/core/toolbox/toolbox.ts b/packages/blockly/core/toolbox/toolbox.ts index c77f32a86..22d5056be 100644 --- a/packages/blockly/core/toolbox/toolbox.ts +++ b/packages/blockly/core/toolbox/toolbox.ts @@ -311,6 +311,10 @@ export class Toolbox case 'ArrowRight': handled = this.toggleSelectedItem(true); break; + case 'Escape': + getFocusManager().focusTree(this.getWorkspace()); + handled = true; + break; } if (handled) { diff --git a/packages/blockly/tests/mocha/shortcut_items_test.js b/packages/blockly/tests/mocha/shortcut_items_test.js index 7b92b534c..d0868c6c0 100644 --- a/packages/blockly/tests/mocha/shortcut_items_test.js +++ b/packages/blockly/tests/mocha/shortcut_items_test.js @@ -89,27 +89,88 @@ suite('Keyboard Shortcut Items', function () { 'hideChaff', ); }); + test('Simple', function () { this.injectionDiv.dispatchEvent(this.event); sinon.assert.calledOnce(this.hideChaffSpy); }); + runReadOnlyTest(createKeyDownEvent(Blockly.utils.KeyCodes.ESC)); + test('Not called when focus is on an HTML input', function () { const event = createKeyDownEvent(Blockly.utils.KeyCodes.ESC); const input = document.createElement('textarea'); input.dispatchEvent(event); sinon.assert.notCalled(this.hideChaffSpy); }); + test('Not called on hidden workspaces', function () { this.workspace.visible = false; this.injectionDiv.dispatchEvent(this.event); sinon.assert.notCalled(this.hideChaffSpy); }); + test('Called when connection is focused', function () { setSelectedConnection(this.workspace); this.injectionDiv.dispatchEvent(this.event); sinon.assert.calledOnce(this.hideChaffSpy); }); + + test('In the toolbox focuses the workspace', function () { + Blockly.getFocusManager().focusNode( + this.workspace.getToolbox().getToolboxItems()[0], + ); + assert.equal( + Blockly.getFocusManager().getFocusedTree(), + this.workspace.getToolbox(), + ); + const event = new KeyboardEvent('keydown', { + keyCode: Blockly.utils.KeyCodes.ESC, + key: 'Escape', + }); + this.workspace.getToolbox().contentsDiv_.dispatchEvent(event); + assert.equal(Blockly.getFocusManager().getFocusedTree(), this.workspace); + }); + + test('In the flyout focues the workspace', function () { + this.injectionDiv.dispatchEvent( + createKeyDownEvent(Blockly.utils.KeyCodes.T), + ); + this.injectionDiv.dispatchEvent( + createKeyDownEvent(Blockly.utils.KeyCodes.RIGHT), + ); + assert.equal( + Blockly.getFocusManager().getFocusedTree(), + this.workspace.getFlyout().getWorkspace(), + ); + const event = new KeyboardEvent('keydown', { + keyCode: Blockly.utils.KeyCodes.ESC, + key: 'Escape', + }); + this.workspace.getFlyout().svgGroup_.dispatchEvent(event); + assert.equal(Blockly.getFocusManager().getFocusedTree(), this.workspace); + }); + + test('In a mutator flyout focuses the mutator workspace', async function () { + const block = this.workspace.newBlock('controls_if'); + block.initSvg(); + block.render(); + + const mutatorIcon = block.getIcon(Blockly.icons.MutatorIcon.TYPE); + await mutatorIcon.setBubbleVisible(true); + + const bubble = mutatorIcon.getBubble(); + Blockly.getFocusManager().focusTree(bubble.getWorkspace().getFlyout().getWorkspace()); + const event = new KeyboardEvent('keydown', { + keyCode: Blockly.utils.KeyCodes.ESC, + key: 'Escape', + }); + bubble.getWorkspace().getFlyout().svgGroup_.dispatchEvent(event); + assert.equal( + Blockly.getFocusManager().getFocusedTree(), + bubble.getWorkspace(), + ); + }); }); suite('Delete', function () {