Functions
Import these from @grafloria/renderer.
Functions
angleAt
Angle (deg) of point seen from center; 0° = straight up, growing clockwise.
tsfunction 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:
- rotate the world delta by −θ → the node's local frame;
- resize the local box;
- 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.
tsfunction 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.
tsfunction canConnectPorts(
a: PortModel,
b: PortModel,
nodeA: NodeModel,
nodeB: NodeModel,
engine: DiagramEngine,
diagram: { getLinks(): LinkModel[] }
): boolean
createDrawTool
tsfunction createDrawTool(host: WhiteboardHost, options?: DrawToolOptions): DrawTool
createEraserTool
tsfunction createEraserTool(host: WhiteboardHost, options?: EraserToolOptions): EraserTool
createRectangleTool
tsfunction createRectangleTool(host: WhiteboardHost, options?: RectangleToolOptions): RectangleTool
createStrokeEditTool
tsfunction createStrokeEditTool(
host: WhiteboardHost,
options?: StrokeEditToolOptions
): StrokeEditTool
normalizeAngle
Fold an angle into [0, 360).
tsfunction 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.
tsfunction 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.
tsfunction resizeBox(
start: Rectangle,
handle: ResizeHandleId,
dx: number,
dy: number,
options: ResizeOptions = {}
): Rectangle
rotatePoint
Rotate point about center by deg degrees.
tsfunction rotatePoint(point: Point, center: Point, deg: number): Point
rotateVector
Rotate (x, y) about the origin by deg degrees (SVG's positive = clockwise).
tsfunction 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.
tsfunction sideHandleYieldsToPort(
handle: ToolHandle,
hoveredPort: { nodeId?: string } | null | undefined
): boolean
snapAngle
Snap deg to the nearest multiple of step (step ≤ 0 → unchanged).
tsfunction snapAngle(deg: number, step: number): number
Was this page helpful?