Files
lemonspace_app/components/canvas/canvas-connection-validation.ts
Matthias Meister fa6a41f775 feat(canvas): implement edge insertion reflow and enhance connection validation
- 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.
2026-04-05 23:25:26 +02:00

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