Skip to content
D
Documentation

SnapController

reference
2 min readUpdated

Import it from @grafloria/renderer.

ts
class SnapController

Methods

  • constructor(config: Partial<SnapConfig> = {})
  • getConfig(): SnapConfig
  • updateConfig(patch: Partial<SnapConfig>): void
  • syncWithEngineConfig(engine: DiagramEngine): void — Read the snapping knobs off the engine's interaction config.

snapToPortRadius has existed in InteractionConfig since the first interaction phase and was read by NOTHING — the config panel wrote it, the default was 30, and no code path ever consulted it. This is the seam that finally consumes it (see {@link findPortMagnet} / {@link findProximityConnection}), along with the waypoint editor's grid settings.

  • computeSnap(box: Rectangle, others: Rectangle[]): SnapResult — Snap box against others.

Precedence per axis (strongest first):

  1. alignment snapline (edge/centre) within snapThreshold
  2. equal-spacing guide within snapThreshold
  3. grid then keep-in-bounds clamps whatever survived. Precedence matters: a grid snap that fought an alignment snap would visibly jitter between the two.
  • snapPointToGrid(point: Point): Point — Quantise a single point to the grid (used by the vertex drag).
  • keepInBounds(box: Rectangle, bounds: Rectangle): Rectangle — Clamp a box so it stays fully inside bounds (no-op when it cannot fit).
  • siblingBoxes(engine: DiagramEngine, excludeIds: Iterable<string> = []): Rectangle[] — World bounding boxes of every node EXCEPT excludeIds (the moving ones).
  • portPosition(node: NodeModel, port: PortModel): Point — World position of a port — THE one the port is actually drawn at.

BUG: this used port.getAbsolutePosition(node.getBoundingBox()), which walks the BOUNDING BOX and lands on an edge midpoint — blind to the node's silhouette and to how many ports share the side. The renderer draws ports with getPortPositionForShape. So on a circle, a diamond, a hexagon, a cylinder — or ANY side carrying more than one port — the magnet was snapping to a point several pixels away from the port you could see, and proximity-connect measured its radius from the wrong place.

  • findPortMagnet( engine: DiagramEngine, worldX: number, worldY: number, options: { excludeNodeId?: string; radius?: number; filter?: (port: PortModel, node: NodeModel) => boolean; } = {} ): PortHit | null — The nearest port to a world point within snapToPortRadius — the magnet the connection drag snaps to. excludeNodeId keeps a drag from snapping back to the node it started on.
  • findProximityConnection( engine: DiagramEngine, nodeId: string, radius?: number ): ProximityCandidate | null — React-Flow-style proximity connect: after dragging nodeId, is one of ITS ports close enough to a compatible port on another node to auto-link them?

Validity is the same rule the connection drag enforces: different nodes, direction-compatible ports (output→input, or a bi port on either side), allowed by the connection groups, and not already connected.

  • buildProximityLinkCommand(candidate: ProximityCandidate): Command — Turn a candidate into the ONE undoable command that creates the link.
  • highlightProximityTarget(engine: DiagramEngine, candidate: ProximityCandidate | null): void — Paint the nearest valid target: exactly the flags the SVG renderer already draws for a connection drag (isValidTarget = eligible, isHighlighted = the one that would win). Clears every other port so the highlight can never go stale — the failure mode of "highlight on, nothing turns it off".

Was this page helpful?

SnapController — Grafloria