import { EdgePosition } from '../../types/edges'; import { ConnectionMode, OnError } from '../../types/general'; import { NodeBase, NodeHandle } from '../../types/nodes'; import { Position } from '../../types/utils'; import { errorMessages, internalsSymbol } from '../../constants'; import { HandleElement } from '../../types'; export type GetEdgePositionParams = { id: string; sourceNode: NodeBase; sourceHandle: string | null; targetNode: NodeBase; targetHandle: string | null; connectionMode: ConnectionMode; onError?: OnError; }; function isNodeInitialized(node: NodeBase): boolean { return !!node?.[internalsSymbol]?.handleBounds && !!node?.computed?.width; } export function getEdgePosition(params: GetEdgePositionParams): EdgePosition | null { const { sourceNode, targetNode } = params; if (!isNodeInitialized(sourceNode) || !isNodeInitialized(targetNode)) { return null; } const sourceHandleBounds = sourceNode[internalsSymbol]?.handleBounds || toHandleBounds(sourceNode.handles); const targetHandleBounds = targetNode[internalsSymbol]?.handleBounds || toHandleBounds(targetNode.handles); const sourceHandle = getHandle(sourceHandleBounds?.source ?? [], params.sourceHandle); const targetHandle = getHandle( // when connection type is loose we can define all handles as sources and connect source -> source params.connectionMode === ConnectionMode.Strict ? targetHandleBounds?.target ?? [] : (targetHandleBounds?.target ?? []).concat(targetHandleBounds?.source ?? []), params.targetHandle ); const sourcePosition = sourceHandle?.position || Position.Bottom; const targetPosition = targetHandle?.position || Position.Top; if (!sourceHandle || !targetHandle) { params.onError?.( '008', errorMessages['error008'](!sourceHandle ? 'source' : 'target', { id: params.id, sourceHandle: params.sourceHandle, targetHandle: params.targetHandle, }) ); return null; } const [sourceX, sourceY] = getHandlePosition(sourcePosition, sourceNode, sourceHandle); const [targetX, targetY] = getHandlePosition(targetPosition, targetNode, targetHandle); return { sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, }; } function toHandleBounds(handles?: NodeHandle[]) { if (!handles) { return null; } const source = []; const target = []; for (const handle of handles) { handle.width = handle.width || 1; handle.height = handle.height || 1; if (handle.type === 'source') { source.push(handle as HandleElement); } else if (handle.type === 'target') { target.push(handle as HandleElement); } } return { source, target, }; } function getHandlePosition(position: Position, node: NodeBase, handle: HandleElement | null = null): number[] { const x = (handle?.x ?? 0) + (node.computed?.positionAbsolute?.x ?? 0); const y = (handle?.y ?? 0) + (node.computed?.positionAbsolute?.y ?? 0); const width = handle?.width || (node?.computed?.width ?? node?.width ?? 0); const height = handle?.height || (node?.computed?.height ?? node?.height ?? 0); switch (position) { case Position.Top: return [x + width / 2, y]; case Position.Right: return [x + width, y + height / 2]; case Position.Bottom: return [x + width / 2, y + height]; case Position.Left: return [x, y + height / 2]; } } function getHandle(bounds: HandleElement[], handleId?: string | null): HandleElement | null { if (!bounds) { return null; } if (bounds.length === 1 || !handleId) { return bounds[0]; } else if (handleId) { return bounds.find((d) => d.id === handleId) || null; } return null; }