Skip to content

Commit a5731f2

Browse files
committed
fix: preserve stack below when deleting a top stack block
1 parent 93776b6 commit a5731f2

3 files changed

Lines changed: 71 additions & 4 deletions

File tree

src/context_menu_items.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,7 @@ export function registerDeleteBlock() {
3434
if (!scope.block) {
3535
return
3636
}
37-
Blockly.Events.setGroup(true)
38-
scope.block.dispose(true, true)
39-
Blockly.Events.setGroup(false)
37+
deleteBlock(scope.block)
4038
},
4139
scopeType: Blockly.ContextMenuRegistry.ScopeType.BLOCK,
4240
id: 'blockDelete',
@@ -45,6 +43,28 @@ export function registerDeleteBlock() {
4543
Blockly.ContextMenuRegistry.registry.register(deleteOption)
4644
}
4745

46+
export function deleteBlock(block: Blockly.Block) {
47+
if (block.workspace.isFlyout) return
48+
49+
const priorGroup = Blockly.Events.getGroup()
50+
Blockly.Events.setGroup(true)
51+
try {
52+
if (!block.outputConnection && !block.previousConnection?.isConnected()) {
53+
block.nextConnection?.disconnect()
54+
}
55+
if (block.workspace instanceof Blockly.WorkspaceSvg) {
56+
block.workspace.hideChaff()
57+
}
58+
if (block instanceof Blockly.BlockSvg) {
59+
block.dispose(!block.outputConnection, true)
60+
} else {
61+
block.dispose(!block.outputConnection)
62+
}
63+
} finally {
64+
Blockly.Events.setGroup(priorGroup)
65+
}
66+
}
67+
4868
function getDeletableBlocksInStack(block: Blockly.Block): Blockly.Block[] {
4969
let descendants = block.getDescendants(false).filter(isDeletable)
5070
const nextBlock = block.getNextBlock()

src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,9 @@ if (!blockCommentMenuItem) {
189189
}
190190
Blockly.ContextMenuRegistry.registry.unregister('blockDelete')
191191
contextMenuItems.registerDeleteBlock()
192+
Blockly.BlockSvg.prototype.checkAndDelete = function () {
193+
contextMenuItems.deleteBlock(this)
194+
}
192195
contextMenuItems.registerDuplicateBlock()
193196
contextMenuItems.registerCopyShortcut()
194197
contextMenuItems.registerCutShortcut()

tests/unit/context_menu_items.test.ts

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*/
55
import * as Blockly from 'blockly/core'
66
import { afterAll, afterEach, assert, beforeAll, beforeEach, describe, expect, it } from 'vitest'
7-
import { registerDeleteBlock } from '../../src/context_menu_items'
7+
import { deleteBlock, registerDeleteBlock } from '../../src/context_menu_items'
88

99
// Tests for the scratch-specific delete context menu override (registerDeleteBlock).
1010
// The copy/cut/paste override (registerDuplicateBlock, issue #3470) is tested
@@ -171,4 +171,48 @@ describe('registerDeleteBlock', () => {
171171
delete Blockly.Blocks.test_output_block
172172
}
173173
})
174+
175+
it('callback deletes only a top stack block and preserves its next block', () => {
176+
const first = workspace.newBlock('test_stack_block')
177+
const second = workspace.newBlock('test_stack_block')
178+
const nextConn = first.nextConnection
179+
const prevConn = second.previousConnection
180+
assert(nextConn, 'Expected next connection')
181+
assert(prevConn, 'Expected previous connection')
182+
nextConn.connect(prevConn)
183+
184+
const item = Blockly.ContextMenuRegistry.registry.getItem('blockDelete')
185+
assert(item, 'Expected blockDelete item to be registered')
186+
const callback = item.callback as (scope: Blockly.ContextMenuRegistry.Scope) => void
187+
callback({ block: asBlockSvg(first) })
188+
189+
expect(workspace.getAllBlocks(false)).toEqual([second])
190+
expect(second.getParent()).toBeNull()
191+
})
192+
193+
it('checkAndDelete override routes through deleteBlock and preserves next block', () => {
194+
const first = workspace.newBlock('test_stack_block')
195+
const second = workspace.newBlock('test_stack_block')
196+
const nextConn = first.nextConnection
197+
const prevConn = second.previousConnection
198+
assert(nextConn, 'Expected next connection')
199+
assert(prevConn, 'Expected previous connection')
200+
nextConn.connect(prevConn)
201+
202+
// Mirror the wiring in src/index.ts so we cover the keyboard-delete path.
203+
// Tests use plain Workspace (not WorkspaceSvg), so blocks aren't BlockSvg
204+
// instances — invoke the override via prototype to simulate the call site.
205+
const originalCheckAndDelete = Blockly.BlockSvg.prototype.checkAndDelete
206+
Blockly.BlockSvg.prototype.checkAndDelete = function () {
207+
deleteBlock(this)
208+
}
209+
try {
210+
Blockly.BlockSvg.prototype.checkAndDelete.call(first)
211+
} finally {
212+
Blockly.BlockSvg.prototype.checkAndDelete = originalCheckAndDelete
213+
}
214+
215+
expect(workspace.getAllBlocks(false)).toEqual([second])
216+
expect(second.getParent()).toBeNull()
217+
})
174218
})

0 commit comments

Comments
 (0)