feat: Enable blocks during drags (#9935)

* feat: Enable disabled blocks while dragging

* test: Add tests
This commit is contained in:
Aaron Dodson
2026-05-26 14:12:02 -07:00
committed by GitHub
parent 4262858db3
commit c587882ff9
2 changed files with 97 additions and 0 deletions
@@ -92,6 +92,12 @@ export class BlockDragStrategy implements IDragStrategy {
/** Used to persist an event group when snapping is done async. */
private originalEventGroup = '';
/**
* Map from block IDs to reason(s) why it was disabled, used to restore
* disabled state post-drag.
*/
private lastBlockDisabledReasons: Map<string, Set<string>> = new Map();
protected readonly BLOCK_CONNECTION_OFFSET = 10;
/**
@@ -338,6 +344,9 @@ export class BlockDragStrategy implements IDragStrategy {
this.block.setDragging(true);
// Enable all blocks including children.
this.enableAllDraggedBlocks(this.block);
// For keyboard-driven moves, cache a list of valid connection points for
// use in constrained moved mode.
if (e instanceof KeyboardEvent) {
@@ -885,6 +894,9 @@ export class BlockDragStrategy implements IDragStrategy {
);
this.block.setDragging(false);
// Re-disable the block for its original reasons.
this.redisableAllDraggedBlocks(this.block);
}
if (this.connectionCandidate) {
@@ -979,6 +991,8 @@ export class BlockDragStrategy implements IDragStrategy {
this.startParentConn = null;
this.block.setDragging(false);
// Re-disable the block for its original reasons.
this.redisableAllDraggedBlocks(this.block);
this.dragging = false;
aria.announceDynamicAriaState(Msg['ANNOUNCE_MOVE_CANCELED']);
}
@@ -1236,4 +1250,41 @@ export class BlockDragStrategy implements IDragStrategy {
? MoveMode.CONSTRAINED
: MoveMode.UNCONSTRAINED;
}
/**
* Enables the given block and its children.
* Stores the reasons each block was disabled so they can be restored.
*
* @param block The block to enable.
*/
private enableAllDraggedBlocks(block: BlockSvg) {
const oldUndo = eventUtils.getRecordUndo();
eventUtils.setRecordUndo(false);
this.lastBlockDisabledReasons.clear();
// getDescendants includes the block itself.
block.getDescendants(false).forEach((descendant) => {
const reasons = new Set(descendant.getDisabledReasons());
this.lastBlockDisabledReasons.set(descendant.id, reasons);
reasons.forEach((reason) => descendant.setDisabledReason(false, reason));
});
eventUtils.setRecordUndo(oldUndo);
}
/**
* Re-disables the given block and its children using their original
* disabled reasons.
*
* @param block The block to re-disable, if applicable.
*/
private redisableAllDraggedBlocks(block: BlockSvg) {
console.log('redisable');
const oldUndo = eventUtils.getRecordUndo();
eventUtils.setRecordUndo(false);
block.getDescendants(false).forEach((descendant) => {
this.lastBlockDisabledReasons.get(descendant.id)?.forEach((reason) => {
descendant.setDisabledReason(true, reason);
});
});
eventUtils.setRecordUndo(oldUndo);
}
}
@@ -733,6 +733,52 @@ suite('Keyboard-driven movement', function () {
cancelMove(this.workspace);
});
test('Enables disabled blocks during drag and reenables when committed', function () {
const ifBlock = this.workspace.newBlock('controls_if');
const repeatBlock = this.workspace.newBlock('controls_repeat');
ifBlock.initSvg();
ifBlock.render();
repeatBlock.initSvg();
repeatBlock.render();
repeatBlock.previousConnection.connect(
ifBlock.getInput('DO0').connection,
);
ifBlock.setDisabledReason(true, 'test');
assert.isFalse(ifBlock.isEnabled());
Blockly.getFocusManager().focusNode(ifBlock);
startMove(this.workspace);
assert.isTrue(ifBlock.isEnabled());
endMove(this.workspace);
assert.isFalse(ifBlock.isEnabled());
});
test('Enables disabled blocks during drag and reenables when cancelled', function () {
const ifBlock = this.workspace.newBlock('controls_if');
const repeatBlock = this.workspace.newBlock('controls_repeat');
ifBlock.initSvg();
ifBlock.render();
repeatBlock.initSvg();
repeatBlock.render();
repeatBlock.previousConnection.connect(
ifBlock.getInput('DO0').connection,
);
ifBlock.setDisabledReason(true, 'test');
assert.isFalse(ifBlock.isEnabled());
Blockly.getFocusManager().focusNode(ifBlock);
startMove(this.workspace);
assert.isTrue(ifBlock.isEnabled());
cancelMove(this.workspace);
assert.isFalse(ifBlock.isEnabled());
});
suite('Statement move tests', function () {
// Clear the workspace and load start blocks.
setup(function () {