Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
668c61a
refactor!: Update dragging APIs.
gonfunko Feb 4, 2026
a79d9bc
fix: Fix bug that caused drags to always result in deletion
gonfunko Feb 4, 2026
5cb6e46
refactor: Clean up block drag handling with new API
gonfunko Feb 4, 2026
12289ad
chore: Format files
gonfunko Feb 6, 2026
57ff6fb
feat: Add an `isBoundedElement` type predicate
gonfunko Feb 6, 2026
185aa3d
feat: Make `Bubble` implement `IBoundedElement`
gonfunko Feb 6, 2026
85959f5
fix: Fix jumping/scrolling when moving blocks
gonfunko Feb 11, 2026
d713e77
feat: Add a `KeyboardMover`
gonfunko Feb 11, 2026
f0baed6
feat: Update the `BlockDragStrategy` to support constrained movement
gonfunko Feb 11, 2026
5e563f0
feat: Register keyboard shortcuts to drive movement
gonfunko Feb 11, 2026
a0fd564
feat: Display a move indicator on items that are being moved
gonfunko Feb 11, 2026
468a46f
fix: Reenable move hints
gonfunko Feb 12, 2026
1e0fdf2
fix: Fix bugs that caused elements to be mispositioned by keyboard mo…
gonfunko Feb 13, 2026
ee72968
fix: Fix a bug that caused certain connections to be visited out of o…
gonfunko Feb 17, 2026
c883395
fix: Fix a bug that caused blocks to become disconnected during const…
gonfunko Feb 17, 2026
ba9cb8c
test: Add tests for keyboard-driven movement
gonfunko Feb 17, 2026
9f9de2e
chore: Add exports
gonfunko Feb 17, 2026
d19dc6a
chore: Run formatter
gonfunko Feb 17, 2026
393bf2e
chore: Make the linter happy
gonfunko Feb 17, 2026
063c2c1
chore: Update closure compiler
gonfunko Feb 17, 2026
38995a3
fix: Fix test suite on non-macOS
gonfunko Feb 17, 2026
c8cced4
fix: Don't scroll in response to arrow keys while moving items
gonfunko Feb 20, 2026
3147b6a
fix: Fix positioning of move indicator in RTL
gonfunko Feb 20, 2026
c7a67c2
refactor: Clarify return types of drag-start related methods
gonfunko Feb 23, 2026
b676858
refactor: Make the `KeyboardMover` a singleton
gonfunko Feb 23, 2026
ada89b7
fix: Fix import path
gonfunko Feb 23, 2026
c30710e
refactor: Remove `WorkspaceSvg.keyboardMoveInProgress`
gonfunko Feb 23, 2026
4f2633b
fix: Fix tests
gonfunko Feb 24, 2026
dd0b2e0
chore: Remove unused import
gonfunko Feb 24, 2026
0ffc847
chore: Clean up comments and names
gonfunko Feb 27, 2026
fca3b65
refactor: Make `IDraggable` extend `IBoundedElement` and `ISelectable`
gonfunko Feb 27, 2026
63f79a2
chore: Rename test blocks file for move mode
gonfunko Feb 27, 2026
c591750
refactor: Make block connection offset a constant
gonfunko Feb 27, 2026
a6115c2
refactor: Export `KeyboardMover` class with a static instance
gonfunko Feb 27, 2026
f774b7b
fix: Use Command and Control as modifiers for unconstrained move mode
gonfunko Mar 2, 2026
ecb6069
fix: Fix test failures in CI
gonfunko Mar 2, 2026
6711da3
feat: Support allowlisting keyboard shortcuts for mid-move use
gonfunko Mar 2, 2026
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
131 changes: 45 additions & 86 deletions package-lock.json

Large diffs are not rendered by default.

29 changes: 20 additions & 9 deletions packages/blockly/core/block_svg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,11 @@ import type {IBoundedElement} from './interfaces/i_bounded_element.js';
import {IContextMenu} from './interfaces/i_contextmenu.js';
import type {ICopyable} from './interfaces/i_copyable.js';
import {IDeletable} from './interfaces/i_deletable.js';
import type {IDragStrategy, IDraggable} from './interfaces/i_draggable.js';
import type {
DragDisposition,
IDragStrategy,
IDraggable,
} from './interfaces/i_draggable.js';
import type {IFocusableNode} from './interfaces/i_focusable_node.js';
import type {IFocusableTree} from './interfaces/i_focusable_tree.js';
import {IIcon} from './interfaces/i_icon.js';
Expand Down Expand Up @@ -1810,18 +1814,21 @@ export class BlockSvg
}

/** Starts a drag on the block. */
startDrag(e?: PointerEvent): void {
this.dragStrategy.startDrag(e);
startDrag(e?: PointerEvent | KeyboardEvent) {
return this.dragStrategy.startDrag(e);
}

/** Drags the block to the given location. */
drag(newLoc: Coordinate, e?: PointerEvent): void {
drag(newLoc: Coordinate, e?: PointerEvent | KeyboardEvent): void {
this.dragStrategy.drag(newLoc, e);
}

/** Ends the drag on the block. */
endDrag(e?: PointerEvent): void {
this.dragStrategy.endDrag(e);
endDrag(
e: PointerEvent | KeyboardEvent | undefined,
disposition: DragDisposition,
): void {
this.dragStrategy.endDrag(e, disposition);
}

/** Moves the block back to where it was at the start of a drag. */
Expand Down Expand Up @@ -1880,9 +1887,13 @@ export class BlockSvg
/** See IFocusableNode.onNodeFocus. */
onNodeFocus(): void {
this.select();
this.workspace.scrollBoundsIntoView(
this.getBoundingRectangleWithoutChildren(),
);
if (getFocusManager().getFocusedNode() !== this) {
renderManagement.finishQueuedRenders().then(() => {
this.workspace.scrollBoundsIntoView(
this.getBoundingRectangleWithoutChildren(),
);
});
}
}

/** See IFocusableNode.onNodeBlur. */
Expand Down
13 changes: 12 additions & 1 deletion packages/blockly/core/blockly.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,14 +118,19 @@ import * as icons from './icons.js';
import {inject} from './inject.js';
import * as inputs from './inputs.js';
import {IFlyoutInflater} from './interfaces/i_flyout_inflater.js';
import {Direction, KeyboardMover} from './keyboard_nav/keyboard_mover.js';
import {MoveIndicator} from './keyboard_nav/move_indicator.js';
import {LabelFlyoutInflater} from './label_flyout_inflater.js';
import {SeparatorFlyoutInflater} from './separator_flyout_inflater.js';
import {FocusableTreeTraverser} from './utils/focusable_tree_traverser.js';

import {Input} from './inputs/input.js';
import {InsertionMarkerPreviewer} from './insertion_marker_previewer.js';
import {IAutoHideable} from './interfaces/i_autohideable.js';
import {IBoundedElement} from './interfaces/i_bounded_element.js';
import {
IBoundedElement,
isBoundedElement,
} from './interfaces/i_bounded_element.js';
import {IBubble} from './interfaces/i_bubble.js';
import {ICollapsibleToolboxItem} from './interfaces/i_collapsible_toolbox_item.js';
import {IComponent} from './interfaces/i_component.js';
Expand All @@ -137,6 +142,7 @@ import {IDeletable, isDeletable} from './interfaces/i_deletable.js';
import {IDeleteArea} from './interfaces/i_delete_area.js';
import {IDragTarget} from './interfaces/i_drag_target.js';
import {
DragDisposition,
IDragStrategy,
IDraggable,
isDraggable,
Expand Down Expand Up @@ -500,6 +506,8 @@ export {
BlockFlyoutInflater,
ButtonFlyoutInflater,
CodeGenerator,
Direction,
DragDisposition,
Field,
FieldCheckbox,
FieldCheckboxConfig,
Expand Down Expand Up @@ -584,6 +592,7 @@ export {
ImageProperties,
Input,
InsertionMarkerPreviewer,
KeyboardMover,
KeyboardNavigationController,
LabelFlyoutInflater,
LayerManager,
Expand All @@ -595,6 +604,7 @@ export {
MenuItem,
MenuOption,
MetricsManager,
MoveIndicator,
Msg,
Names,
Options,
Expand Down Expand Up @@ -626,6 +636,7 @@ export {
icons,
inject,
inputs,
isBoundedElement,
isCopyable,
isDeletable,
isDraggable,
Expand Down
36 changes: 33 additions & 3 deletions packages/blockly/core/bubbles/bubble.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import * as browserEvents from '../browser_events.js';
import * as common from '../common.js';
import {BubbleDragStrategy} from '../dragging/bubble_drag_strategy.js';
import {getFocusManager} from '../focus_manager.js';
import {IBoundedElement} from '../interfaces/i_bounded_element.js';
import {IBubble} from '../interfaces/i_bubble.js';
import type {IFocusableNode} from '../interfaces/i_focusable_node.js';
import type {IFocusableTree} from '../interfaces/i_focusable_tree.js';
Expand All @@ -29,7 +30,9 @@ import {WorkspaceSvg} from '../workspace_svg.js';
* bubble, where it has a "tail" that points to the block, and a "head" that
* displays arbitrary svg elements.
*/
export abstract class Bubble implements IBubble, ISelectable, IFocusableNode {
export abstract class Bubble
implements IBubble, ISelectable, IFocusableNode, IBoundedElement
{
/** The width of the border around the bubble. */
static readonly BORDER_WIDTH = 6;

Expand Down Expand Up @@ -274,6 +277,18 @@ export abstract class Bubble implements IBubble, ISelectable, IFocusableNode {
this.svgRoot.setAttribute('transform', `translate(${x}, ${y})`);
}

/**
* Moves the bubble by the given amounts in the x and y directions.
*
* @param dx The distance to move along the x axis.
* @param dy The distance to move along the y axis.
* @param _reason A description of why this move is happening.
*/
moveBy(dx: number, dy: number, _reason?: string[]) {
const origin = this.getRelativeToSurfaceXY();
this.moveTo(origin.x + dx, origin.y + dy);
}

/**
* Positions the bubble "optimally" so that the most of it is visible and
* it does not overlap the rect (if provided).
Expand Down Expand Up @@ -617,6 +632,21 @@ export abstract class Bubble implements IBubble, ISelectable, IFocusableNode {
);
}

/**
* Returns the bounds of this bubble.
*
* @returns A bounding box for this bubble.
*/
getBoundingRectangle(): Rect {
const origin = this.getRelativeToSurfaceXY();
return new Rect(
origin.y,
origin.y + this.size.height,
origin.x,
origin.x + this.size.width,
);
}

/** @internal */
getSvgRoot(): SVGElement {
return this.svgRoot;
Expand Down Expand Up @@ -664,8 +694,8 @@ export abstract class Bubble implements IBubble, ISelectable, IFocusableNode {
}

/** Starts a drag on the bubble. */
startDrag(): void {
this.dragStrategy.startDrag();
startDrag() {
return this.dragStrategy.startDrag();
}

/** Drags the bubble to the given location. */
Expand Down
16 changes: 7 additions & 9 deletions packages/blockly/core/bubbles/mini_workspace_bubble.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import type {BlocklyOptions} from '../blockly_options.js';
import {Abstract as AbstractEvent} from '../events/events_abstract.js';
import {KeyboardMover} from '../keyboard_nav/keyboard_mover.js';
import {Options} from '../options.js';
import {Coordinate} from '../utils/coordinate.js';
import * as dom from '../utils/dom.js';
Expand Down Expand Up @@ -153,11 +154,10 @@ export class MiniWorkspaceBubble extends Bubble {
* are dealt with by resizing the workspace to show them.
*/
private bumpBlocksIntoBounds() {
if (
this.miniWorkspace.isDragging() &&
!this.miniWorkspace.keyboardMoveInProgress
)
// Only bump for mouse-driven drags.
if (this.miniWorkspace.isDragging() && !KeyboardMover.mover.isMoving()) {
return;
}

const MARGIN = 20;

Expand Down Expand Up @@ -189,15 +189,13 @@ export class MiniWorkspaceBubble extends Bubble {
* mini workspace.
*/
private updateBubbleSize() {
if (
this.miniWorkspace.isDragging() &&
!this.miniWorkspace.keyboardMoveInProgress
)
if (this.miniWorkspace.isDragging() && !KeyboardMover.mover.isMoving()) {
return;
}

// Disable autolayout if a keyboard move is in progress to prevent the
// mutator bubble from jumping around.
this.autoLayout &&= !this.miniWorkspace.keyboardMoveInProgress;
this.autoLayout &&= !KeyboardMover.mover.isMoving();

const currSize = this.getSize();
const newSize = this.calculateWorkspaceSize();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,8 +239,8 @@ export class RenderedWorkspaceComment
}

/** Starts a drag on the comment. */
startDrag(): void {
this.dragStrategy.startDrag();
startDrag() {
return this.dragStrategy.startDrag();
}

/** Drags the comment to the given location. */
Expand Down
2 changes: 1 addition & 1 deletion packages/blockly/core/comments/workspace_comment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ export class WorkspaceComment {

/** Returns the position of the comment in workspace coordinates. */
getRelativeToSurfaceXY(): Coordinate {
return this.location;
return this.location.clone();
}

/** Disposes of this comment. */
Expand Down
Loading
Loading