- Introduced a new CSS transition for edge insertion reflowing to improve visual feedback during node adjustments. - Enhanced the connection validation logic to include options for optimistic edges, ensuring better handling of edge creation scenarios. - Updated the canvas connection drop menu to support additional templates and improved edge insertion handling. - Refactored edge insertion logic to accommodate local node position adjustments during reflow operations. - Added tests for new edge insertion features and connection validation improvements.
88 lines
2.6 KiB
TypeScript
88 lines
2.6 KiB
TypeScript
import type { Connection, Edge as RFEdge, Node as RFNode } from "@xyflow/react";
|
|
|
|
import {
|
|
validateCanvasConnectionPolicy,
|
|
type CanvasConnectionValidationReason,
|
|
} from "@/lib/canvas-connection-policy";
|
|
|
|
import { isOptimisticEdgeId } from "./canvas-helpers";
|
|
|
|
export function validateCanvasConnection(
|
|
connection: Connection,
|
|
nodes: RFNode[],
|
|
edges: RFEdge[],
|
|
edgeToReplaceId?: string,
|
|
options?: {
|
|
includeOptimisticEdges?: boolean;
|
|
},
|
|
): CanvasConnectionValidationReason | null {
|
|
if (!connection.source || !connection.target) return "incomplete";
|
|
if (connection.source === connection.target) return "self-loop";
|
|
|
|
const sourceNode = nodes.find((node) => node.id === connection.source);
|
|
const targetNode = nodes.find((node) => node.id === connection.target);
|
|
if (!sourceNode || !targetNode) return "unknown-node";
|
|
|
|
return validateCanvasConnectionByType({
|
|
sourceType: sourceNode.type ?? "",
|
|
targetType: targetNode.type ?? "",
|
|
targetNodeId: connection.target,
|
|
edges,
|
|
edgeToReplaceId,
|
|
includeOptimisticEdges: options?.includeOptimisticEdges,
|
|
});
|
|
}
|
|
|
|
export function validateCanvasConnectionByType(args: {
|
|
sourceType: string;
|
|
targetType: string;
|
|
targetNodeId: string;
|
|
edges: RFEdge[];
|
|
edgeToReplaceId?: string;
|
|
includeOptimisticEdges?: boolean;
|
|
}): CanvasConnectionValidationReason | null {
|
|
const targetIncomingCount = args.edges.filter(
|
|
(edge) =>
|
|
edge.className !== "temp" &&
|
|
(args.includeOptimisticEdges || !isOptimisticEdgeId(edge.id)) &&
|
|
edge.target === args.targetNodeId &&
|
|
edge.id !== args.edgeToReplaceId,
|
|
).length;
|
|
|
|
return validateCanvasConnectionPolicy({
|
|
sourceType: args.sourceType,
|
|
targetType: args.targetType,
|
|
targetIncomingCount,
|
|
});
|
|
}
|
|
|
|
export function validateCanvasEdgeSplit(args: {
|
|
nodes: RFNode[];
|
|
edges: RFEdge[];
|
|
splitEdge: RFEdge;
|
|
middleNode: RFNode;
|
|
}): CanvasConnectionValidationReason | null {
|
|
const sourceNode = args.nodes.find((node) => node.id === args.splitEdge.source);
|
|
const targetNode = args.nodes.find((node) => node.id === args.splitEdge.target);
|
|
|
|
if (!sourceNode || !targetNode) {
|
|
return "unknown-node";
|
|
}
|
|
|
|
return (
|
|
validateCanvasConnectionByType({
|
|
sourceType: sourceNode.type ?? "",
|
|
targetType: args.middleNode.type ?? "",
|
|
targetNodeId: args.middleNode.id,
|
|
edges: args.edges,
|
|
}) ??
|
|
validateCanvasConnectionByType({
|
|
sourceType: args.middleNode.type ?? "",
|
|
targetType: targetNode.type ?? "",
|
|
targetNodeId: targetNode.id,
|
|
edges: args.edges,
|
|
edgeToReplaceId: args.splitEdge.id,
|
|
})
|
|
);
|
|
}
|