Skip to content
D
Documentation

Interaction — functions

reference
2 min readUpdated

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

Was this page helpful?

Interaction — functions — Grafloria