From fe907dba1615027ede0e95538a0df8752b116f7f Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Tue, 19 May 2026 13:50:32 -0700 Subject: [PATCH 1/3] fix: Improve initial block placement --- .../core/dragging/block_drag_strategy.ts | 85 ++++++++++++++++--- 1 file changed, 75 insertions(+), 10 deletions(-) diff --git a/packages/blockly/core/dragging/block_drag_strategy.ts b/packages/blockly/core/dragging/block_drag_strategy.ts index ea5cadc7d16..c5ad88d603b 100644 --- a/packages/blockly/core/dragging/block_drag_strategy.ts +++ b/packages/blockly/core/dragging/block_drag_strategy.ts @@ -35,7 +35,7 @@ import * as blocks from '../serialization/blocks.js'; import {Coordinate} from '../utils.js'; import * as aria from '../utils/aria.js'; import * as dom from '../utils/dom.js'; -import * as svgMath from '../utils/svg_math.js'; +import {Rect} from '../utils/rect.js'; import type {WorkspaceSvg} from '../workspace_svg.js'; /** Represents a valid pair of connections between the dragging block and a block on the workspace. */ @@ -116,15 +116,80 @@ export class BlockDragStrategy implements IDragStrategy { * @param newBlock The new block to position. */ private positionNewBlock(oldBlock: BlockSvg, newBlock: BlockSvg) { - const screenCoordinate = svgMath.wsToScreenCoordinates( - oldBlock.workspace, - oldBlock.getRelativeToSurfaceXY(), - ); - const workspaceCoordinates = svgMath.screenToWsCoordinates( - newBlock.workspace, - screenCoordinate, - ); - newBlock.moveDuringDrag(workspaceCoordinates); + const workspace = newBlock.workspace; + const initialY = 10; + const initialX = 10; + const xSpacing = 80; + + const filteredTopBlocks = workspace + .getTopBlocks(true) + .filter((block) => block.id !== newBlock.id); + const allBlockBounds = filteredTopBlocks.map((block) => { + const bounds = block.getBoundingRectangle(); + // Expand the bounds to avoid the new block being placed within snapping + // distance. + return new Rect( + bounds.top - 30, + bounds.bottom + 30, + bounds.left - 30, + bounds.right + 30, + ); + }); + + const toolboxWidth = workspace.getToolbox()?.getWidth(); + const workspaceWidth = + workspace.getParentSvg().clientWidth - (toolboxWidth ?? 0); + const workspaceHeight = workspace.getParentSvg().clientHeight; + const {height: newBlockHeight, width: newBlockWidth} = + newBlock.getHeightWidth(); + + const getNextIntersectingBlock = function ( + newBlockRect: Rect, + ): Rect | null { + for (const rect of allBlockBounds) { + if (newBlockRect.intersects(rect)) { + return rect; + } + } + return null; + }; + + let cursorY = initialY; + let cursorX = initialX; + const minBlockHeight = workspace + .getRenderer() + .getConstants().MIN_BLOCK_HEIGHT; + // Make the initial movement of shifting the block to its best possible + // position. + let boundingRect = newBlock.getBoundingRectangle(); + newBlock.moveBy(cursorX - boundingRect.left, cursorY - boundingRect.top, [ + 'cleanup', + ]); + + boundingRect = newBlock.getBoundingRectangle(); + let conflictingRect = getNextIntersectingBlock(boundingRect); + while (conflictingRect != null) { + const newCursorX = + conflictingRect.left + conflictingRect.getWidth() + xSpacing; + const newCursorY = + conflictingRect.top + conflictingRect.getHeight() + minBlockHeight; + if (newCursorX + newBlockWidth <= workspaceWidth) { + cursorX = newCursorX; + } else if (newCursorY + newBlockHeight <= workspaceHeight) { + cursorY = newCursorY; + cursorX = initialX; + } else { + // Off screen, but new blocks will be selected which will scroll them + // into view. + cursorY = newCursorY; + cursorX = initialX; + } + newBlock.moveBy(cursorX - boundingRect.left, cursorY - boundingRect.top, [ + 'cleanup', + ]); + boundingRect = newBlock.getBoundingRectangle(); + conflictingRect = getNextIntersectingBlock(boundingRect); + } } /** From 6d76338203d7f5d73cd432e0045c11056cad71cc Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Tue, 19 May 2026 13:50:59 -0700 Subject: [PATCH 2/3] chore: Remove unused arg --- packages/blockly/core/dragging/block_drag_strategy.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/blockly/core/dragging/block_drag_strategy.ts b/packages/blockly/core/dragging/block_drag_strategy.ts index c5ad88d603b..f114db5513a 100644 --- a/packages/blockly/core/dragging/block_drag_strategy.ts +++ b/packages/blockly/core/dragging/block_drag_strategy.ts @@ -112,10 +112,9 @@ export class BlockDragStrategy implements IDragStrategy { /** * Positions a cloned block on its new workspace. * - * @param oldBlock The flyout block that was cloned. * @param newBlock The new block to position. */ - private positionNewBlock(oldBlock: BlockSvg, newBlock: BlockSvg) { + private positionNewBlock(newBlock: BlockSvg) { const workspace = newBlock.workspace; const initialY = 10; const initialX = 10; @@ -215,7 +214,7 @@ export class BlockDragStrategy implements IDragStrategy { }, ) as BlockSvg; eventUtils.setRecordUndo(false); - this.positionNewBlock(this.block, newBlock); + this.positionNewBlock(newBlock); eventUtils.setRecordUndo(true); return newBlock; From 2215976d38c40ff53a0a4a24d280b8846ca9bc97 Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Thu, 21 May 2026 08:58:39 -0700 Subject: [PATCH 3/3] chore: Clarify variable names --- .../core/dragging/block_drag_strategy.ts | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/packages/blockly/core/dragging/block_drag_strategy.ts b/packages/blockly/core/dragging/block_drag_strategy.ts index f114db5513a..5320ec7b83e 100644 --- a/packages/blockly/core/dragging/block_drag_strategy.ts +++ b/packages/blockly/core/dragging/block_drag_strategy.ts @@ -93,6 +93,11 @@ export class BlockDragStrategy implements IDragStrategy { protected readonly BLOCK_CONNECTION_OFFSET = 10; + /** + * How far in from the edges of the workspace to position newly placed blocks. + */ + protected readonly WORKSPACE_MARGIN = 10; + constructor(private block: BlockSvg) { this.workspace = block.workspace; } @@ -116,9 +121,13 @@ export class BlockDragStrategy implements IDragStrategy { */ private positionNewBlock(newBlock: BlockSvg) { const workspace = newBlock.workspace; - const initialY = 10; - const initialX = 10; + const initialY = this.WORKSPACE_MARGIN; + const initialX = this.WORKSPACE_MARGIN; + // How far apart the new block should be placed horizontally from an + // existing one. const xSpacing = 80; + const blockPadding = + Math.max(config.connectingSnapRadius, config.snapRadius) + 2; const filteredTopBlocks = workspace .getTopBlocks(true) @@ -128,10 +137,10 @@ export class BlockDragStrategy implements IDragStrategy { // Expand the bounds to avoid the new block being placed within snapping // distance. return new Rect( - bounds.top - 30, - bounds.bottom + 30, - bounds.left - 30, - bounds.right + 30, + bounds.top - blockPadding, + bounds.bottom + blockPadding, + bounds.left - blockPadding, + bounds.right + blockPadding, ); });