From a5ff158b1f87907760b646d58739eaa4b47c4ba4 Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Wed, 29 Apr 2026 09:20:35 -0700 Subject: [PATCH] fix: Prevent errors when mixing keyboard/mouse input in the toolbox/flyout (#9773) --- .../interfaces/i_collapsible_toolbox_item.ts | 1 + .../navigators/toolbox_navigator.ts | 5 ++++- packages/blockly/core/toolbox/toolbox.ts | 19 +++++++++++++++---- packages/blockly/tests/mocha/toolbox_test.js | 1 + 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/packages/blockly/core/interfaces/i_collapsible_toolbox_item.ts b/packages/blockly/core/interfaces/i_collapsible_toolbox_item.ts index 6e29e5843..214847d7f 100644 --- a/packages/blockly/core/interfaces/i_collapsible_toolbox_item.ts +++ b/packages/blockly/core/interfaces/i_collapsible_toolbox_item.ts @@ -42,6 +42,7 @@ export function isCollapsibleToolboxItem( obj: any, ): obj is ICollapsibleToolboxItem { return ( + obj && typeof obj.getChildToolboxItems === 'function' && typeof obj.isExpanded === 'function' && typeof obj.toggleExpanded === 'function' && diff --git a/packages/blockly/core/keyboard_nav/navigators/toolbox_navigator.ts b/packages/blockly/core/keyboard_nav/navigators/toolbox_navigator.ts index 542ecca0b..808ac9bc8 100644 --- a/packages/blockly/core/keyboard_nav/navigators/toolbox_navigator.ts +++ b/packages/blockly/core/keyboard_nav/navigators/toolbox_navigator.ts @@ -45,7 +45,10 @@ export class ToolboxNavigator extends Navigator { } } - if (isSelectableToolboxItem(node) && !node.getContents().length) { + if ( + !isSelectableToolboxItem(node) || + (isSelectableToolboxItem(node) && !node.getContents().length) + ) { return null; } diff --git a/packages/blockly/core/toolbox/toolbox.ts b/packages/blockly/core/toolbox/toolbox.ts index 22d5056be..d87500fc6 100644 --- a/packages/blockly/core/toolbox/toolbox.ts +++ b/packages/blockly/core/toolbox/toolbox.ts @@ -277,22 +277,33 @@ export class Toolbox * @param e Click event to handle. */ protected onClick_(e: PointerEvent) { + const close = () => { + getFocusManager().focusNode(this); + this.clearSelection(); + (common.getMainWorkspace() as WorkspaceSvg).hideChaff(false); + e.preventDefault(); + }; + this.mouseDown = true; if (browserEvents.isRightButton(e) || e.target === this.HtmlDiv) { // Close flyout. - (common.getMainWorkspace() as WorkspaceSvg).hideChaff(false); + close(); } else { const targetElement = e.target; const itemId = (targetElement as Element).getAttribute('id'); if (itemId) { const item = this.getToolboxItemById(itemId); if (item?.isSelectable()) { - this.setSelectedItem(item); (item as ISelectableToolboxItem).onClick(e); + if (item === this.getSelectedItem()) { + close(); + } else { + this.setSelectedItem(item); + // Just close popups. + (common.getMainWorkspace() as WorkspaceSvg).hideChaff(true); + } } } - // Just close popups. - (common.getMainWorkspace() as WorkspaceSvg).hideChaff(true); } Touch.clearTouchIdentifier(); } diff --git a/packages/blockly/tests/mocha/toolbox_test.js b/packages/blockly/tests/mocha/toolbox_test.js index c02f09f4d..000183b1d 100644 --- a/packages/blockly/tests/mocha/toolbox_test.js +++ b/packages/blockly/tests/mocha/toolbox_test.js @@ -234,6 +234,7 @@ suite('Toolbox', function () { )[0]; const evt = { 'target': categoryXml, + 'stopPropagation': () => {}, }; const item = this.toolbox.contents.get(categoryXml.getAttribute('id')); const setSelectedSpy = sinon.spy(this.toolbox, 'setSelectedItem');