diff --git a/packages/blockly/core/events/utils.ts b/packages/blockly/core/events/utils.ts index ac78c694273..b5d1b6d16ba 100644 --- a/packages/blockly/core/events/utils.ts +++ b/packages/blockly/core/events/utils.ts @@ -14,6 +14,7 @@ import type {Workspace} from '../workspace.js'; import type {WorkspaceSvg} from '../workspace_svg.js'; import type {Abstract} from './events_abstract.js'; import type {BlockCreate} from './events_block_create.js'; +import type {BlockDrag} from './events_block_drag.js'; import type {BlockMove} from './events_block_move.js'; import type {CommentCreate} from './events_comment_create.js'; import type {CommentMove} from './events_comment_move.js'; @@ -21,6 +22,7 @@ import type {CommentResize} from './events_comment_resize.js'; import { isBlockChange, isBlockCreate, + isBlockDrag, isBlockMove, isBubbleOpen, isClick, @@ -404,8 +406,17 @@ export function get( * @param event Custom data for event. */ export function disableOrphans(event: Abstract) { - if (isBlockMove(event) || isBlockCreate(event)) { - const blockEvent = event as BlockMove | BlockCreate; + // BlockMove/BlockCreate events that fire mid-drag are skipped below (we + // don't want to disable a block while it's still being positioned). For + // keyboard-driven inserts from the flyout there may be no further move + // event after the drag ends, so also act on the end-of-drag event, at + // which point the workspace is no longer dragging. + if ( + isBlockMove(event) || + isBlockCreate(event) || + (isBlockDrag(event) && !event.isStart) + ) { + const blockEvent = event as BlockMove | BlockCreate | BlockDrag; if (!blockEvent.workspaceId) { return; } diff --git a/packages/blockly/tests/mocha/event_test.js b/packages/blockly/tests/mocha/event_test.js index 856811a02e7..f83bd8affc6 100644 --- a/packages/blockly/tests/mocha/event_test.js +++ b/packages/blockly/tests/mocha/event_test.js @@ -1637,5 +1637,39 @@ suite('Events', function () { 'Undo stack should not contain any disabled events', ); }); + test('Orphan is disabled by end-of-drag event', function () { + this.workspace.addChangeListener(eventUtils.disableOrphans); + + // Simulate a drag/move being in progress, as happens when a block is + // inserted from the flyout via the keyboard. The block-create event + // fires while dragging, so its orphan-disabling branch is skipped. + const isDraggingStub = sinon + .stub(this.workspace, 'isDragging') + .returns(true); + const block = this.workspace.newBlock('controls_for'); + block.initSvg(); + block.render(); + + this.clock.runAll(); + + assert.isTrue( + block.isEnabled(), + 'Block should not be disabled while a drag is in progress', + ); + + // The drag ends with no further move event (e.g. the block was + // committed in place). The end-of-drag event should disable the orphan. + isDraggingStub.returns(false); + eventUtils.fire( + new Blockly.Events.BlockDrag(block, false, block.getDescendants(false)), + ); + + this.clock.runAll(); + + assert.isFalse( + block.isEnabled(), + 'Expected orphan block to be disabled after the drag ends', + ); + }); }); });