Files
xyflow/packages/system/src/utils/edges/positions.ts
T

125 lines
3.7 KiB
TypeScript

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;
}