Skip to content

Commit 77b9da1

Browse files
authored
feat: enable blocks on drag (#457)
* feat: enable blocks on drag * fix: fail faster
1 parent b4d18e2 commit 77b9da1

File tree

3 files changed

+93
-0
lines changed

3 files changed

+93
-0
lines changed

src/disabled_blocks.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import * as Blockly from 'blockly/core';
8+
9+
const lastBlockDisabledReasons: Map<string, Set<string>> = new Map();
10+
11+
/**
12+
* A change listener that enables disabled blocks when they
13+
* are dragged, and re-disables them at the end of the drag.
14+
*
15+
* @param event Blockly event
16+
*/
17+
export function enableBlocksOnDrag(event: Blockly.Events.Abstract) {
18+
// This listener only runs on Drag events that have a valid
19+
// workspace and block id.
20+
if (!isBlockDrag(event)) return;
21+
if (!event.blockId) return;
22+
const eventWorkspace = Blockly.common.getWorkspaceById(
23+
event.workspaceId,
24+
) as Blockly.WorkspaceSvg;
25+
const block = eventWorkspace.getBlockById(event.blockId);
26+
if (!block) return;
27+
28+
const oldUndo = Blockly.Events.getRecordUndo();
29+
Blockly.Events.setRecordUndo(false);
30+
31+
if (event.isStart) {
32+
// At start of drag, reset the lastBlockDisabledReasons
33+
lastBlockDisabledReasons.clear();
34+
35+
// Enable all blocks including childeren
36+
enableAllDraggedBlocks(block);
37+
} else {
38+
// Re-disable the block for its original reasons. If the block is no
39+
// longer an orphan, the disableOrphans handler will enable the block.
40+
redisableAllDraggedBlocks(block);
41+
}
42+
43+
Blockly.Events.setRecordUndo(oldUndo);
44+
}
45+
46+
/**
47+
* Enables all blocks including children of the dragged blocks.
48+
* Stores the reasons each block was disabled so they can be restored.
49+
*
50+
* @param block
51+
*/
52+
function enableAllDraggedBlocks(block: Blockly.BlockSvg) {
53+
// getDescendants includes the block itself.
54+
block.getDescendants(false).forEach((descendant) => {
55+
const reasons = new Set(descendant.getDisabledReasons());
56+
lastBlockDisabledReasons.set(descendant.id, reasons);
57+
reasons.forEach((reason) => descendant.setDisabledReason(false, reason));
58+
});
59+
}
60+
61+
/**
62+
* Re-disables all blocks using their original disabled reasons.
63+
*
64+
* @param block
65+
*/
66+
function redisableAllDraggedBlocks(block: Blockly.BlockSvg) {
67+
block.getDescendants(false).forEach((descendant) => {
68+
lastBlockDisabledReasons.get(descendant.id)?.forEach((reason) => {
69+
descendant.setDisabledReason(true, reason);
70+
});
71+
});
72+
}
73+
74+
/**
75+
* Type guard for drag events.
76+
*
77+
* @param event
78+
* @returns true iff event.type is EventType.BLOCK_DRAG
79+
*/
80+
function isBlockDrag(
81+
event: Blockly.Events.Abstract,
82+
): event is Blockly.Events.BlockDrag {
83+
return event.type === Blockly.Events.BLOCK_DRAG;
84+
}

src/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import * as Blockly from 'blockly/core';
88
import {NavigationController} from './navigation_controller';
99
import {getFlyoutElement, getToolboxElement} from './workspace_utilities';
10+
import {enableBlocksOnDrag} from './disabled_blocks';
1011

1112
/** Options object for KeyboardNavigation instances. */
1213
export interface NavigationOptions {
@@ -93,6 +94,9 @@ export class KeyboardNavigation {
9394

9495
this.cursor = new Blockly.LineCursor(workspace, options.cursor);
9596

97+
// Add the event listener to enable disabled blocks on drag.
98+
workspace.addChangeListener(enableBlocksOnDrag);
99+
96100
// Ensure that only the root SVG G (group) has a tab index.
97101
this.injectionDivTabIndex = workspace
98102
.getInjectionDiv()
@@ -233,6 +237,9 @@ export class KeyboardNavigation {
233237
}
234238
}
235239

240+
// Remove the event listener that enables blocks on drag
241+
this.workspace.removeChangeListener(enableBlocksOnDrag);
242+
236243
this.workspace.getSvgGroup().removeEventListener('blur', this.blurListener);
237244
this.workspace
238245
.getSvgGroup()

test/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import {javascriptGenerator} from 'blockly/javascript';
2323
import {load} from './loadTestBlocks';
2424
import {runCode, registerRunCodeShortcut} from './runCode';
2525

26+
(window as any).Blockly = Blockly;
27+
2628
/**
2729
* Parse query params for inject and navigation options and update
2830
* the fields on the options form to match.

0 commit comments

Comments
 (0)