streamlined connections

This commit is contained in:
peterkogo
2024-06-26 15:03:12 +02:00
parent 44254648c8
commit 5493f045ff
25 changed files with 355 additions and 474 deletions
+52 -33
View File
@@ -1,4 +1,4 @@
import { pointToRendererPoint, rendererPointToPoint, getHostForElement, calcAutoPan, getEventPosition } from '../utils';
import { pointToRendererPoint, getHostForElement, calcAutoPan, getEventPosition, getHandlePosition } from '../utils';
import {
ConnectionMode,
type OnConnect,
@@ -7,13 +7,13 @@ import {
type Connection,
type PanBy,
type Transform,
type ConnectingHandle,
type Handle,
type OnConnectEnd,
type UpdateConnection,
type IsValidConnection,
type ConnectionHandle,
NodeLookup,
Position,
oppositePosition,
} from '../types';
import { getClosestHandle, isConnectionValid, getHandleLookup, getHandleType } from './utils';
@@ -39,11 +39,11 @@ export type OnPointerDownParams = {
isValidConnection?: IsValidConnection;
onReconnectEnd?: (evt: MouseEvent | TouchEvent) => void;
getTransform: () => Transform;
getFromHandle: () => ConnectingHandle | null;
getFromHandle: () => Handle | null;
};
export type IsValidParams = {
handle: Pick<ConnectionHandle, 'nodeId' | 'id' | 'type'> | null;
handle: Pick<Handle, 'nodeId' | 'id' | 'type'> | null;
connectionMode: ConnectionMode;
fromNodeId: string;
fromHandleId: string | null;
@@ -52,6 +52,7 @@ export type IsValidParams = {
doc: Document | ShadowRoot;
lib: string;
flowId: string | null;
handleLookup?: Handle[];
};
export type XYHandleInstance = {
@@ -63,13 +64,11 @@ type Result = {
handleDomNode: Element | null;
isValid: boolean;
connection: Connection | null;
toHandle: ConnectingHandle | null;
toHandle: Handle | null;
};
const alwaysValid = () => true;
let fromHandle: ConnectingHandle | null = null;
function onPointerDown(
event: MouseEvent | TouchEvent,
{
@@ -99,7 +98,7 @@ function onPointerDown(
// when xyflow is used inside a shadow root we can't use document
const doc = getHostForElement(event.target as HTMLElement);
let autoPanId = 0;
let closestHandle: ConnectionHandle | null;
let closestHandle: Handle | null;
const { x, y } = getEventPosition(event);
const clickedHandle = doc?.elementFromPoint(x, y);
@@ -113,10 +112,10 @@ function onPointerDown(
let position = getEventPosition(event, containerBounds);
let autoPanStarted = false;
let connection: Connection | null = null;
let isValid = false;
let isValid: boolean | null = false;
let handleDomNode: Element | null = null;
const handleLookup = getHandleLookup({
const [handleLookup, fromHandleInternal] = getHandleLookup({
nodeLookup,
nodeId,
handleId,
@@ -135,18 +134,30 @@ function onPointerDown(
}
// Stays the same for all consecutive pointermove events
fromHandle = {
const fromHandle: Handle = {
...fromHandleInternal,
nodeId,
handleId,
type: handleType,
position: (clickedHandle?.getAttribute('data-handlepos') as Position) ?? Position.Top,
position: fromHandleInternal.position,
};
const fromNodeInternal = nodeLookup.get(nodeId)!;
const from = getHandlePosition(fromNodeInternal, fromHandle, Position.Left, true);
updateConnection({
position,
inProgress: true,
isValid: null,
from,
fromHandle,
fromPosition: fromHandle.position,
fromNode: fromNodeInternal.internals.userNode,
to: pointToRendererPoint(position, getTransform()),
toHandle: null,
toPosition: oppositePosition[fromHandle.position],
toNode: null,
});
onConnectStart?.(event, { nodeId, handleId, handleType });
@@ -180,26 +191,29 @@ function onPointerDown(
doc,
lib,
flowId,
handleLookup,
});
handleDomNode = result.handleDomNode;
connection = result.connection;
isValid = result.isValid;
isValid = isConnectionValid(!!closestHandle, result.isValid);
updateConnection({
inProgress: true,
isValid,
from,
fromHandle,
position:
fromPosition: fromHandle.position,
fromNode: fromNodeInternal.internals.userNode,
to:
closestHandle && isValid
? rendererPointToPoint(
{
x: closestHandle.x,
y: closestHandle.y,
},
transform
)
: position,
isValid: isConnectionValid(!!closestHandle, isValid),
? { x: closestHandle.x, y: closestHandle.y }
: pointToRendererPoint(position, transform),
toHandle: result.toHandle,
toPosition: isValid && result.toHandle ? result.toHandle.position : oppositePosition[fromHandle.position],
toNode: result.toHandle ? nodeLookup.get(result.toHandle.nodeId)!.internals.userNode : null,
});
}
@@ -222,7 +236,6 @@ function onPointerDown(
isValid = false;
connection = null;
handleDomNode = null;
fromHandle = null;
doc.removeEventListener('mousemove', onPointerMove as EventListener);
doc.removeEventListener('mouseup', onPointerUp as EventListener);
@@ -251,6 +264,7 @@ function isValidHandle(
lib,
flowId,
isValidConnection = alwaysValid,
handleLookup,
}: IsValidParams
) {
const isTarget = fromType === 'target';
@@ -301,12 +315,17 @@ function isValidHandle(
result.isValid = isValid && isValidConnection(connection);
result.toHandle = {
nodeId: handleNodeId as string,
handleId,
type: handleType as HandleType,
position: handleToCheck.getAttribute('data-handlepos') as Position,
};
if (handleLookup) {
const toHandle = handleLookup.find(
(h) => h.id === handleId && h.nodeId === handleNodeId && h.type === handleType
);
if (toHandle) {
result.toHandle = {
...toHandle,
};
}
}
}
return result;
+24 -28
View File
@@ -3,40 +3,34 @@ import {
type HandleType,
type NodeHandleBounds,
type XYPosition,
type ConnectionHandle,
type Handle,
InternalNodeBase,
NodeLookup,
} from '../types';
// this functions collects all handles and adds an absolute position
// so that we can later find the closest handle to the mouse position
export function getHandles(
function getHandles(
node: InternalNodeBase,
handleBounds: NodeHandleBounds,
type: HandleType,
currentHandle: string
): ConnectionHandle[] {
return (handleBounds[type] || []).reduce<ConnectionHandle[]>((res, handle) => {
if (`${node.id}-${handle.id}-${type}` !== currentHandle) {
const [x, y] = getHandlePosition(node, handle);
res.push({
id: handle.id || null,
type,
nodeId: node.id,
x,
y,
});
currentHandle: { nodeId: string; handleId: string | null; handleType: HandleType }
): [Handle[], Handle | null] {
let excludedHandle = null;
const handles = (handleBounds[type] || []).reduce<Handle[]>((res, handle) => {
if (node.id === currentHandle.nodeId && type === currentHandle.handleType && handle.id === currentHandle.handleId) {
excludedHandle = handle;
} else {
const handleXY = getHandlePosition(node, handle);
res.push({ ...handle, ...handleXY });
}
return res;
}, []);
return [handles, excludedHandle];
}
export function getClosestHandle(
pos: XYPosition,
connectionRadius: number,
handles: ConnectionHandle[]
): ConnectionHandle | null {
let closestHandles: ConnectionHandle[] = [];
export function getClosestHandle(pos: XYPosition, connectionRadius: number, handles: Handle[]): Handle | null {
let closestHandles: Handle[] = [];
let minDistance = Infinity;
for (const handle of handles) {
@@ -66,7 +60,7 @@ type GetHandleLookupParams = {
nodeLookup: NodeLookup;
nodeId: string;
handleId: string | null;
handleType: string;
handleType: HandleType;
};
export function getHandleLookup({
@@ -74,19 +68,21 @@ export function getHandleLookup({
nodeId,
handleId,
handleType,
}: GetHandleLookupParams): ConnectionHandle[] {
const connectionHandles: ConnectionHandle[] = [];
}: GetHandleLookupParams): [Handle[], Handle] {
const connectionHandles: Handle[] = [];
const currentHandle = { nodeId, handleId, handleType };
let excludedHandle: Handle | null = null;
for (const [, node] of nodeLookup) {
for (const node of nodeLookup.values()) {
if (node.internals.handleBounds) {
const id = `${nodeId}-${handleId}-${handleType}`;
const sourceHandles = getHandles(node, node.internals.handleBounds, 'source', id);
const targetHandles = getHandles(node, node.internals.handleBounds, 'target', id);
const [sourceHandles, excludedSource] = getHandles(node, node.internals.handleBounds, 'source', currentHandle);
const [targetHandles, excludedTarget] = getHandles(node, node.internals.handleBounds, 'target', currentHandle);
excludedHandle = excludedHandle ? excludedHandle : excludedSource ?? excludedTarget;
connectionHandles.push(...sourceHandles, ...targetHandles);
}
}
return connectionHandles;
return [connectionHandles, excludedHandle!];
}
export function getHandleType(