UNPKG

@blockly/keyboard-navigation

Version:
168 lines (143 loc) 4.17 kB
/** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import * as Blockly from 'blockly/core'; /** * Bubble that displays a four-way arrow attached to a block to indicate that * it is in move mode. */ export class MoveIndicatorBubble implements Blockly.IBubble, Blockly.IRenderedElement { /** * Root SVG element for this bubble. */ svgRoot: SVGGElement; /** * The location of this bubble in workspace coordinates. */ location = new Blockly.utils.Coordinate(0, 0); /** * Creates a new move indicator bubble. * * @param sourceBlock The block this bubble should be associated with. */ /* eslint-disable @typescript-eslint/naming-convention */ constructor(private sourceBlock: Blockly.BlockSvg) { this.svgRoot = Blockly.utils.dom.createSvgElement( Blockly.utils.Svg.G, {}, this.sourceBlock.workspace.getBubbleCanvas(), ); const rtl = this.sourceBlock.workspace.RTL; Blockly.utils.dom.createSvgElement( Blockly.utils.Svg.CIRCLE, { 'fill': 'white', 'fill-opacity': '0.8', 'stroke': 'grey', 'stroke-width': '1', 'r': 20, 'cx': 20 * (rtl ? -1 : 1), 'cy': 20, }, this.svgRoot, ); Blockly.utils.dom.createSvgElement( Blockly.utils.Svg.PATH, { 'fill': 'none', 'stroke': 'currentColor', 'stroke-linecap': 'round', 'stroke-linejoin': 'round', 'stroke-width': '2', 'd': 'm18 9l3 3l-3 3m-3-3h6M6 9l-3 3l3 3m-3-3h6m0 6l3 3l3-3m-3-3v6m3-15l-3-3l-3 3m3-3v6', 'transform': `translate(${(rtl ? -4 : 1) * 8} 8)`, }, this.svgRoot, ); this.updateLocation(); } /** * Returns whether this bubble is movable by the user. * * @returns Always returns false. */ isMovable(): boolean { return false; } /** * Returns the root SVG element for this bubble. * * @returns The root SVG element. */ getSvgRoot(): SVGGElement { return this.svgRoot; } /** * Recalculates this bubble's location, keeping it adjacent to its block. */ updateLocation() { const bounds = this.sourceBlock.getBoundingRectangleWithoutChildren(); const x = this.sourceBlock.workspace.RTL ? bounds.left + 20 : bounds.right - 20; const y = bounds.top - 20; this.moveTo(x, y); this.sourceBlock.workspace.getLayerManager()?.moveToDragLayer(this); } /** * Moves this bubble to the specified location. * * @param x The location on the X axis to move to. * @param y The location on the Y axis to move to. */ moveTo(x: number, y: number) { this.location.x = x; this.location.y = y; this.svgRoot.setAttribute('transform', `translate(${x}, ${y})`); } /** * Returns this bubble's location in workspace coordinates. * * @returns The bubble's location. */ getRelativeToSurfaceXY(): Blockly.utils.Coordinate { return this.location; } /** * Disposes of this move indicator bubble. */ dispose() { Blockly.utils.dom.removeNode(this.svgRoot); } // These methods are required by the interfaces, but intentionally have no // implementation, largely because this bubble's location is fixed relative // to its block and is not draggable by the user. showContextMenu() {} setDragging(dragging: boolean) {} startDrag(event: PointerEvent) {} drag(newLocation: Blockly.utils.Coordinate, event: PointerEvent) {} moveDuringDrag(newLocation: Blockly.utils.Coordinate) {} endDrag() {} revertDrag() {} setDeleteStyle(enable: boolean) {} /** See IFocusableNode.getFocusableElement. */ getFocusableElement(): HTMLElement | SVGElement { throw new Error('This node is not focusable.'); } /** See IFocusableNode.getFocusableTree. */ getFocusableTree(): Blockly.IFocusableTree { throw new Error('This node is not focusable.'); } /** See IFocusableNode.onNodeFocus. */ onNodeFocus(): void {} /** See IFocusableNode.onNodeBlur. */ onNodeBlur(): void {} /** See IFocusableNode.canBeFocused. */ canBeFocused(): boolean { return false; } }