Skip to content
D
Documentation

SelectionToolsController

reference
2 min readUpdated

Import it from @grafloria/renderer.

ts
class SelectionToolsController

Methods

  • constructor(config: Partial<SelectionToolsConfig> = {})
  • getConfig(): SelectionToolsConfig
  • updateConfig(patch: Partial<SelectionToolsConfig>): void
  • isActive(): boolean — True while a resize / rotate / vertex drag owns the pointer.
  • activeGesture(): 'resize' | 'rotate' | 'vertex' | null — The kind of gesture in flight (null when idle) — hosts branch on it.
  • activeGestureNodeId(): string | null — The node the in-flight gesture is acting on (null when idle). Lets a host exclude it from a snap's sibling set — a box must not snap to itself.
  • computeLayer(engine: DiagramEngine, zoom = 1): SelectionToolLayer — Compute the tool layer for the CURRENT selection.

zoom converts the screen-px sizes in the config into world units, so the handles are the same physical size at every zoom level.

Scope note (deliberate): resize + rotate are offered for a SINGLE selected node. A multi-node selection gets the bounding frame, the halo and the remove button — proportional multi-node resize is not implemented.

  • hitTest(layer: SelectionToolLayer, worldX: number, worldY: number): ToolHandle | null — Which tool (if any) is under a world point.

Later handles win ties, so the halo/remove buttons (emitted last) stay clickable where they overlap a resize handle.

resize-ux: a handle that carries a segment (the four side resize handles) hit-tests as a BAND around that segment — the whole edge is grabbable, the React Flow line behaviour. POINT handles always beat bands: the corner dots sit exactly where two bands end, and the dot must win its own pixel (RF draws the dots on top of the lines for the same reason). Halo/remove/rotate are point handles, so their priority over resize is unchanged.

  • hitTestResize( layer: SelectionToolLayer, worldX: number, worldY: number, slop = 0 ): ToolHandle | null — resize-ux: the RESIZE handle under a world point, with optional extra grab slop (world units). One entry point for the mouse ladder (slop 0) and the touch ladder (finger-sized slop), so bands and dots grow identically.
  • beginResize( handle: ToolHandle, engine: DiagramEngine, worldX: number, worldY: number ): boolean — Arm a resize. Returns false when the handle/node is not resizable.
  • updateResize( engine: DiagramEngine, worldX: number, worldY: number, modifiers: ToolModifierState = {}, snap?: (box: Rectangle) => Rectangle ): boolean — Live-resize the node (direct model mutation, like the drag tool: smooth, no command churn — the single undo entry is minted at {@link endGesture}).
  • beginRotate(handle: ToolHandle, engine: DiagramEngine, worldX: number, worldY: number): boolean
  • updateRotate( engine: DiagramEngine, worldX: number, worldY: number, modifiers: ToolModifierState = {} ): boolean — Live-rotate; Shift snaps to rotationSnapDegrees.
  • beginVertexDrag(handle: ToolHandle, engine: DiagramEngine): boolean
  • updateVertexDrag( engine: DiagramEngine, worldX: number, worldY: number, snapPoint?: (p: Point) => Point ): boolean
  • endGesture(engine: DiagramEngine): Command | null — End the active gesture and return the ONE command that makes it undoable (null for a no-op gesture). The model already sits at its final state — the command re-applies it (a no-op) and records the inverse, exactly like the Node-drag commit.
  • cancelGesture(engine: DiagramEngine): void — Abandon the gesture, restoring the model to its pre-gesture state.
  • addVertexCommand(handle: ToolHandle, engine: DiagramEngine): Command | null — Insert a vertex at the clicked segment midpoint. Undoable.
  • removeVertexCommand(handle: ToolHandle, engine: DiagramEngine): Command | null — Remove the vertex under the tool. Undoable.
  • removeSelectionCommand(engine: DiagramEngine): Command | null — The command behind a remove button / halo delete: drop the whole selection (every selected node AND link) in ONE undo step.
  • cloneNodeCommand( engine: DiagramEngine, nodeId: string, offset: Point = { x: 40, y: 40 } ): Command | null — Halo clone: a copy of the node, offset, as one undoable AddNodeCommand. Ports get fresh ids (via the engine's shared {@link remapNodePortIds}) so the clone never shares port identity with its source — the bug paste/duplicate already learned.
  • forkNodeCommand( engine: DiagramEngine, nodeId: string, offset: Point = { x: 160, y: 0 } ): Command | null — Halo fork: clone the node AND link the original to the clone — one undo step (MacroCommand). The link runs from an output-capable port on the source to an input-capable port on the clone; null when either side has no usable port.

Was this page helpful?

SelectionToolsController — Grafloria