Files
xyflow/packages/system/src/utils/edges/positions.ts
T
Moritz KlackandGitHub e5c667d068 Feat(nodes): add initialWidth and initialHeight (#3953)
* feat(react/svelte): add initialWidth/initialHeight closes #3793

* chore(packages): update changelogs
2024-02-27 15:21:35 +01:00

123 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';
import { getNodeDimensions } from '../general';
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?.handles?.length) &&
!!(node?.computed?.width || node?.width || node?.initialWidth)
);
}
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, height } = handle ?? getNodeDimensions(node);
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 no handleId is given, we use the first handle, otherwise we check for the id
return (!handleId ? bounds[0] : bounds.find((d) => d.id === handleId)) || null;
}