# Functions

Import these from `@grafloria/renderer`.

## Functions

### `angleAt`

Angle (deg) of `point` seen from `center`; 0° = straight up, growing clockwise.

```ts
function angleAt(center: Point, point: Point): number
```

### `applyResizeToNode`

Apply a resize drag to a possibly-ROTATED node.

The pointer delta arrives in WORLD space, but the box the user is resizing is
drawn in the node's local (unrotated) frame and rotated about its centre. So:

1. rotate the world delta by −θ → the node's local frame;
 2. resize the local box;
 3. the node re-renders rotated about its NEW centre, so compensate the
    position by rotating the centre shift back into world space.

At θ = 0 this collapses to `pos + (box.x, box.y)`. Getting step 3 wrong is what
makes rotated resize "swim" away from the cursor in most implementations.

```ts
function applyResizeToNode(
  start: { position: Point; size: { width: number; height: number }; rotation: number },
  handle: ResizeHandleId,
  worldDx: number,
  worldDy: number,
  options: ResizeOptions = {}
): { position: Point; size: { width: number; height: number } }
```

### `canConnectPorts`

Can ports `a` (on `nodeA`) and `b` (on `nodeB`) be linked?

The ONE rule shared by proximity connect and keyboard connect
, so the two can never disagree about what is legal.

It no longer OWNS that rule — it delegates to
`evaluatePortConnection`, the engine-side validator the connection drag also
runs through. A port
you could legally drag to was one the magnet refused to snap to.

`rejectDuplicatesByDefault` preserves this call site's own historical stance:
auto-linking a duplicate because a node drifted near is never what the user
meant, whatever the port's `allowDuplicateLinks` says.

Note the argument order: `a` is the SOURCE. The rule is directional, and the
proximity search calls it once per orientation.

```ts
function canConnectPorts(
  a: PortModel,
  b: PortModel,
  nodeA: NodeModel,
  nodeB: NodeModel,
  engine: DiagramEngine,
  diagram: { getLinks(): LinkModel[] }
): boolean
```

### `createDrawTool`

```ts
function createDrawTool(host: WhiteboardHost, options?: DrawToolOptions): DrawTool
```

### `createEraserTool`

```ts
function createEraserTool(host: WhiteboardHost, options?: EraserToolOptions): EraserTool
```

### `createRectangleTool`

```ts
function createRectangleTool(host: WhiteboardHost, options?: RectangleToolOptions): RectangleTool
```

### `createStrokeEditTool`

```ts
function createStrokeEditTool(
  host: WhiteboardHost,
  options?: StrokeEditToolOptions
): StrokeEditTool
```

### `normalizeAngle`

Fold an angle into [0, 360).

```ts
function normalizeAngle(deg: number): number
```

### `parseValidationPath`

Parse a ValidationEngine `path` back to the entity it names. Paths look like `node.<id>`, `node.<id>.port.<id>`, `node.<id>.hierarchy`,
`link.<id>`. Anything else (diagram-level) is not entity-scoped.

```ts
function parseValidationPath(
  path: string
): { entity: 'node' | 'link'; entityId: string } | null
```

### `resizeBox`

Apply a resize drag to a box, in the box's OWN frame.

`dx/dy` is the pointer delta since the drag began (same frame as the box). The
dragged edge(s) follow the pointer; the opposite edge stays put. Min/max clamp
the DRAGGED edge, never the anchored one — so a runaway drag can neither flip
nor shrink past the minimum nor grow past the maximum, all mid-gesture.

```ts
function resizeBox(
  start: Rectangle,
  handle: ResizeHandleId,
  dx: number,
  dy: number,
  options: ResizeOptions = {}
): Rectangle
```

### `rotatePoint`

Rotate `point` about `center` by `deg` degrees.

```ts
function rotatePoint(point: Point, center: Point, deg: number): Point
```

### `rotateVector`

Rotate `(x, y)` about the origin by `deg` degrees (SVG's positive = clockwise).

```ts
function rotateVector(x: number, y: number, deg: number): Point
```

### `sideHandleYieldsToPort`

resize-ux: the ONE side-handle-vs-port priority predicate.

The four side handles live ON the node border, where the default side ports
also live. React Flow resolves the coincidence by z-order: the port glyph
(drawn on top of the resize line) wins its own core, the line wins the rest
of the edge. Ours is the same rule expressed in hover state: when the pointer
is inside a port's grab radius (`hoveredPort` is set) on the SAME node, the
port claims the press/cursor; everywhere else along the band, resize does.

Exported so the mouse ladder, the touch ladder AND the hover-cursor logic
share one predicate — three private copies is how they drift.

```ts
function sideHandleYieldsToPort(
  handle: ToolHandle,
  hoveredPort: { nodeId?: string } | null | undefined
): boolean
```

### `snapAngle`

Snap `deg` to the nearest multiple of `step` (step ≤ 0 → unchanged).

```ts
function snapAngle(deg: number, step: number): number
```
