import { DiagramComponent, pickAttributes, validateOptionalHandler } from "./diagram-component.js"; import { DrawingLink, DrawingSurface, DrawingTriple } from "./drawing-core.js"; import { DiagramNode } from "./diagram-node.js"; const LINK_ATTRIBUTES = [ "content", "contentType", "cx", "cy", "width", "height", "backgroundColor", "borderColor", "borderWidth", "textColor", "sourceX", "sourceY", "targetX", "targetY", "lineColor", "lineWidth", "hasArrow" ]; /** * Public rendering handle for one diagram link. * * A link owns only visual and interaction state. Its source and target are * DiagramNode instances from the same DiagramEngine. */ export class DiagramLink extends DiagramComponent { constructor(engine, attributes) { const component = new DrawingLink(pickAttributes(attributes, LINK_ATTRIBUTES)); super(engine, component, LINK_ATTRIBUTES); } /** Read or replace the source node. Pass null to disconnect it. */ sourceNode(node) { return this.connectNode(DrawingSurface.CONNECTION_TYPE_SOURCE, node); } /** Read or replace the target node. Pass null to disconnect it. */ targetNode(node) { return this.connectNode(DrawingSurface.CONNECTION_TYPE_TARGET, node); } /** Register a callback for a source or target connection change. */ onConnectionChange(handler) { validateOptionalHandler(handler, "connection-change"); this.component.connectionChangeHandler = handler ? (type, node, event) => { handler(this, type, node ? this.engine.handleFor(node) : null, event); } : null; return this; } /** Straighten both link segments between their current endpoints. */ straighten() { const relation = this.component.relations() .find((candidate) => candidate instanceof DrawingTriple); const sourceNode = relation ? relation.sourceNode() : null; const targetNode = relation ? relation.targetNode() : null; if (!sourceNode || !targetNode) { this.component.straighten(); return this; } const sourcePoint = relation.connectedPoint( sourceNode, targetNode.cx(), targetNode.cy()); const targetPoint = relation.connectedPoint( targetNode, sourceNode.cx(), sourceNode.cy()); this.component.straighten( sourcePoint.x, sourcePoint.y, targetPoint.x, targetPoint.y); return this; } /** Read or update one connected endpoint. */ connectNode(type, node) { const connectedComponent = this.engine.drawingSurface .connectedNode(type, this.component); if (node === undefined) { return connectedComponent ? this.engine.handleFor(connectedComponent) : null; } if (node !== null && !this.validateNode(type, node)) return this; if (connectedComponent) { this.engine.drawingSurface.disconnect(type, connectedComponent, this.component); } if (node !== null) { this.engine.drawingSurface.connect(type, node.component, this.component); } return this; } /** Ensure an endpoint belongs to this engine and is not used at both ends. */ validateNode(type, node) { if (!(node instanceof DiagramNode) || node.engine !== this.engine) { throw new TypeError("Invalid diagram node"); } const otherType = DrawingSurface.anotherConnectionType(type); const otherNode = this.engine.drawingSurface.connectedNode(otherType, this.component); if (otherNode === node.component) { return false; } return true; } }