# SelectionToolsController

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.
- `hitTest(layer: SelectionToolLayer, worldX: number, worldY: number): ToolHandle | null` — Which tool (if any) is under a world point.
- `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}). `snap` optionally quantises the resulting box (grid / alignment) — the host passes the SnapController's hook so snapping composes with resizing.
- `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.
