diff --git a/packages/blockly/core/toolbox/collapsible_category.ts b/packages/blockly/core/toolbox/collapsible_category.ts index 5048ff1269d..3418a46fd71 100644 --- a/packages/blockly/core/toolbox/collapsible_category.ts +++ b/packages/blockly/core/toolbox/collapsible_category.ts @@ -195,7 +195,7 @@ export class CollapsibleToolboxCategory this.subcategoriesDiv_!.style.display = 'block'; this.openIcon_(this.iconDom_); } else { - this.parentToolbox_.getFlyout()?.setVisible(false); + this.setFlyoutVisible(false); this.subcategoriesDiv_!.style.display = 'none'; this.closeIcon_(this.iconDom_); } @@ -237,9 +237,27 @@ export class CollapsibleToolboxCategory } override onClick(_e: Event) { + // Check for special case where the category has its own flyout items. + if (this.flyoutItems_.length > 0) { + const parentToolbox = this.getParentToolbox(); + const thisFlyoutVisible = + parentToolbox.getSelectedItem() === this && + parentToolbox.getFlyout()?.isVisible(); + + if (this.expanded_ && !thisFlyoutVisible) { + this.setFlyoutVisible(true); + return; + } + } + this.toggleExpanded(); } + /** Sets the visibility of the parent toolbox's flyout. */ + private setFlyoutVisible(isVisible: boolean) { + this.parentToolbox_.getFlyout()?.setVisible(isVisible); + } + /** Toggles whether or not the category is expanded. */ toggleExpanded() { this.setExpanded(!this.expanded_); diff --git a/packages/blockly/tests/mocha/toolbox_test.js b/packages/blockly/tests/mocha/toolbox_test.js index 923f08b54e2..8f0bb6e2616 100644 --- a/packages/blockly/tests/mocha/toolbox_test.js +++ b/packages/blockly/tests/mocha/toolbox_test.js @@ -261,6 +261,68 @@ suite('Toolbox', function () { sinon.assert.calledOnce(setSelectedSpy); sinon.assert.calledOnce(onClickSpy); }); + suite('collapsible category with flyout', function () { + setup(function () { + const collapsibleCategoryWithFlyout = { + kind: 'categoryToolbox', + contents: [ + { + kind: 'category', + name: 'Parent', + categorystyle: 'text_category', + contents: [ + { + kind: 'block', + type: 'text', + }, + { + kind: 'category', + name: 'Child', + categorystyle: 'text_category', + contents: [ + { + kind: 'block', + type: 'text_join', + }, + ], + }, + ], + }, + ], + }; + this.toolbox.render(collapsibleCategoryWithFlyout); + this.flyout = this.toolbox.getFlyout(); + this.parentCategory = this.toolbox.getToolboxItems()[0]; + }); + function clickCategory(category) { + const target = category.getClickTarget(); + const event = new PointerEvent('pointerdown', {bubbles: true}); + target.dispatchEvent(event); + } + test('if category collapsed and flyout hidden, click should uncollapse and open flyout', function () { + this.parentCategory.setExpanded(false); + clickCategory(this.parentCategory); + + assert.isTrue(this.parentCategory.isExpanded()); + assert.isTrue(this.flyout.isVisible()); + }); + test('if category expanded and flyout hidden, click should open flyout', function () { + this.parentCategory.setExpanded(true); + this.flyout.hide(); + clickCategory(this.parentCategory); + + assert.isTrue(this.parentCategory.isExpanded()); + assert.isTrue(this.flyout.isVisible()); + }); + test('category expanded and flyout visible, click should collapse and close', function () { + // Click in to expand, then click again to close + clickCategory(this.parentCategory); + clickCategory(this.parentCategory); + + assert.isFalse(this.parentCategory.isExpanded()); + assert.isFalse(this.flyout.isVisible()); + }); + }); }); suite('on key down', function () {