# 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.

`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.
