Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions src/disabled_blocks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import * as Blockly from 'blockly/core';

const lastBlockDisabledReasons: Map<string, Set<string>> = new Map();

/**
* A change listener that enables disabled blocks when they
* are dragged, and re-disables them at the end of the drag.
*
* @param event Blockly event
*/
export function enableBlocksOnDrag(event: Blockly.Events.Abstract) {
// This listener only runs on Drag events that have a valid
// workspace and block id.
if (!isBlockDrag(event)) return;
if (!event.blockId) return;
const eventWorkspace = Blockly.common.getWorkspaceById(
event.workspaceId,
) as Blockly.WorkspaceSvg;
const block = eventWorkspace.getBlockById(event.blockId);
if (!block) return;

const oldUndo = Blockly.Events.getRecordUndo();
Blockly.Events.setRecordUndo(false);

if (event.isStart) {
// At start of drag, reset the lastBlockDisabledReasons
lastBlockDisabledReasons.clear();

// Enable all blocks including childeren
enableAllDraggedBlocks(block);
} else {
// Re-disable the block for its original reasons. If the block is no
// longer an orphan, the disableOrphans handler will enable the block.
redisableAllDraggedBlocks(block);
}

Blockly.Events.setRecordUndo(oldUndo);
}

/**
* Enables all blocks including children of the dragged blocks.
* Stores the reasons each block was disabled so they can be restored.
*
* @param block
*/
function enableAllDraggedBlocks(block: Blockly.BlockSvg) {
// getDescendants includes the block itself.
block.getDescendants(false).forEach((descendant) => {
const reasons = new Set(descendant.getDisabledReasons());
lastBlockDisabledReasons.set(descendant.id, reasons);
reasons.forEach((reason) => descendant.setDisabledReason(false, reason));
});
}

/**
* Re-disables all blocks using their original disabled reasons.
*
* @param block
*/
function redisableAllDraggedBlocks(block: Blockly.BlockSvg) {
block.getDescendants(false).forEach((descendant) => {
lastBlockDisabledReasons.get(descendant.id)?.forEach((reason) => {
descendant.setDisabledReason(true, reason);
});
});
}

/**
* Type guard for drag events.
*
* @param event
* @returns true iff event.type is EventType.BLOCK_DRAG
*/
function isBlockDrag(
event: Blockly.Events.Abstract,
): event is Blockly.Events.BlockDrag {
return event.type === Blockly.Events.BLOCK_DRAG;
}
7 changes: 7 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import * as Blockly from 'blockly/core';
import {NavigationController} from './navigation_controller';
import {getFlyoutElement, getToolboxElement} from './workspace_utilities';
import {enableBlocksOnDrag} from './disabled_blocks';

/** Options object for KeyboardNavigation instances. */
export interface NavigationOptions {
Expand Down Expand Up @@ -93,6 +94,9 @@ export class KeyboardNavigation {

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

// Add the event listener to enable disabled blocks on drag.
workspace.addChangeListener(enableBlocksOnDrag);

// Ensure that only the root SVG G (group) has a tab index.
this.injectionDivTabIndex = workspace
.getInjectionDiv()
Expand Down Expand Up @@ -233,6 +237,9 @@ export class KeyboardNavigation {
}
}

// Remove the event listener that enables blocks on drag
this.workspace.removeChangeListener(enableBlocksOnDrag);

this.workspace.getSvgGroup().removeEventListener('blur', this.blurListener);
this.workspace
.getSvgGroup()
Expand Down
2 changes: 2 additions & 0 deletions test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import {load} from './loadTestBlocks';
import {runCode, registerRunCodeShortcut} from './runCode';

(window as any).Blockly = Blockly;

Check warning on line 26 in test/index.ts

View workflow job for this annotation

GitHub Actions / Eslint check

Unexpected any. Specify a different type

/**
* Parse query params for inject and navigation options and update
* the fields on the options form to match.
Expand Down
Loading