Skip to content

Commit c43ac15

Browse files
committed
feat: adds easy-connect example
1 parent 30487bb commit c43ac15

File tree

13 files changed

+327
-26
lines changed

13 files changed

+327
-26
lines changed

playground/constants.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
CustomNode,
88
Dagre,
99
DragNDrop,
10+
EasyConnect,
1011
Edges,
1112
HandleConnect,
1213
Interaction,
@@ -37,6 +38,7 @@ export const SolidFlowExamplesMap = {
3738
CustomNode,
3839
Dagre,
3940
DragNDrop,
41+
EasyConnect,
4042
Edges,
4143
HandleConnect,
4244
Interaction,
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { getStraightPath } from "@xyflow/system";
2+
3+
import type { ConnectionLineComponentProps } from "@/index";
4+
5+
export default function CustomConnectionLine(props: ConnectionLineComponentProps) {
6+
const edgePath = () => {
7+
const [path] = getStraightPath({
8+
sourceX: props.fromX,
9+
sourceY: props.fromY,
10+
targetX: props.toX,
11+
targetY: props.toY,
12+
});
13+
return path;
14+
};
15+
16+
return (
17+
<g>
18+
<path style={props.connectionLineStyle} fill="none" d={edgePath()} />
19+
<circle cx={props.toX} cy={props.toY} fill="black" r={3} stroke="black" stroke-width={1.5} />
20+
</g>
21+
);
22+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { Show } from "solid-js";
2+
3+
import { Handle, useConnection } from "@/index";
4+
import type { NodeProps } from "@/types";
5+
6+
export default function CustomNode(props: NodeProps<Record<string, never>, "custom">) {
7+
const connection = useConnection();
8+
const isTarget = () => connection().inProgress && connection().fromNode?.id !== props.id;
9+
const label = () => (isTarget() ? "Drop here" : "Drag to connect");
10+
11+
return (
12+
<div class="customNode">
13+
<div
14+
class="customNodeBody"
15+
style={{
16+
"border-style": isTarget() ? "dashed" : "solid",
17+
"background-color": isTarget() ? "#ffcce3" : "#ccd9f6",
18+
}}
19+
>
20+
{/* If handles are conditionally rendered and not present initially, you need to update the node internals */}
21+
{/* In this case we don't need to use useUpdateNodeInternals, since !isConnecting is true at the beginning and all handles are rendered initially. */}
22+
<Show when={!connection().inProgress}>
23+
<Handle class="customHandle" position="right" type="source" />
24+
</Show>
25+
{/* We want to disable the target handle, if the connection was started from this node */}
26+
<Show when={!connection().inProgress || isTarget()}>
27+
<Handle class="customHandle" position="left" type="target" isConnectableStart={false} />
28+
</Show>
29+
{label()}
30+
</div>
31+
</div>
32+
);
33+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { getStraightPath } from "@xyflow/system";
2+
import { Show } from "solid-js";
3+
4+
import { useInternalNode } from "@/index";
5+
import type { EdgeProps } from "@/types";
6+
7+
import { getEdgeParams } from "./utils";
8+
9+
export default function FloatingEdge(props: EdgeProps) {
10+
const sourceNode = useInternalNode(() => props.source);
11+
const targetNode = useInternalNode(() => props.target);
12+
13+
const edgePath = () => {
14+
const src = sourceNode();
15+
const tgt = targetNode();
16+
17+
if (!src || !tgt) return null;
18+
19+
const { sx, sy, tx, ty } = getEdgeParams(src, tgt);
20+
21+
const [path] = getStraightPath({
22+
sourceX: sx,
23+
sourceY: sy,
24+
targetX: tx,
25+
targetY: ty,
26+
});
27+
28+
return path;
29+
};
30+
31+
return (
32+
<Show when={edgePath()}>
33+
{(path) => (
34+
<path
35+
id={props.id}
36+
class="solid-flow__edge-path"
37+
d={path()}
38+
marker-end={props.markerEnd}
39+
style={props.style}
40+
/>
41+
)}
42+
</Show>
43+
);
44+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import "./style.css";
2+
3+
import { MarkerType } from "@xyflow/system";
4+
5+
import { Background, createEdgeStore, createNodeStore, SolidFlow } from "@/index";
6+
import type { NodeTypes } from "@/types";
7+
8+
import CustomConnectionLine from "./CustomConnectionLine";
9+
import CustomNode from "./CustomNode";
10+
import FloatingEdge from "./FloatingEdge";
11+
12+
const nodeTypes = {
13+
custom: CustomNode,
14+
} satisfies NodeTypes;
15+
16+
const edgeTypes = {
17+
floating: FloatingEdge,
18+
};
19+
20+
const defaultEdgeOptions = {
21+
style: { "stroke-width": "3px", stroke: "black" },
22+
type: "floating",
23+
markerEnd: {
24+
type: MarkerType.ArrowClosed,
25+
color: "black",
26+
},
27+
};
28+
29+
const connectionLineStyle = {
30+
"stroke-width": "3px",
31+
stroke: "black",
32+
};
33+
34+
export const EasyConnect = () => {
35+
const [nodes] = createNodeStore<typeof nodeTypes>([
36+
{
37+
id: "1",
38+
type: "custom",
39+
position: { x: 0, y: 0 },
40+
data: {},
41+
},
42+
{
43+
id: "2",
44+
type: "custom",
45+
position: { x: 250, y: 320 },
46+
data: {},
47+
},
48+
{
49+
id: "3",
50+
type: "custom",
51+
position: { x: 40, y: 300 },
52+
data: {},
53+
},
54+
{
55+
id: "4",
56+
type: "custom",
57+
position: { x: 300, y: 0 },
58+
data: {},
59+
},
60+
]);
61+
62+
const [edges] = createEdgeStore([]);
63+
64+
return (
65+
<SolidFlow
66+
nodes={nodes}
67+
edges={edges}
68+
// onConnect={onConnect}
69+
fitView
70+
nodeTypes={nodeTypes}
71+
edgeTypes={edgeTypes}
72+
defaultEdgeOptions={defaultEdgeOptions}
73+
connectionLineComponent={CustomConnectionLine}
74+
connectionLineStyle={connectionLineStyle}
75+
>
76+
<Background />
77+
</SolidFlow>
78+
);
79+
};
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
.customNodeBody {
2+
width: 150px;
3+
height: 80px;
4+
border: 3px solid black;
5+
position: relative;
6+
overflow: hidden;
7+
border-radius: 10px;
8+
display: flex;
9+
justify-content: center;
10+
align-items: center;
11+
font-weight: bold;
12+
}
13+
14+
.customNode:before {
15+
content: "";
16+
position: absolute;
17+
top: -10px;
18+
left: 50%;
19+
height: 20px;
20+
width: 40px;
21+
transform: translate(-50%, 0);
22+
background: #d6d5e6;
23+
z-index: 1000;
24+
line-height: 1;
25+
border-radius: 4px;
26+
color: #fff;
27+
font-size: 9px;
28+
border: 2px solid #222138;
29+
}
30+
31+
div.customHandle {
32+
width: 100%;
33+
height: 100%;
34+
background: blue;
35+
position: absolute;
36+
top: 0;
37+
left: 0;
38+
border-radius: 0;
39+
transform: none;
40+
border: none;
41+
opacity: 0;
42+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { Position, type XYPosition } from "@xyflow/system";
2+
3+
import type { InternalNode } from "@/types";
4+
5+
// this helper function returns the intersection point
6+
// of the line between the center of the intersectionNode and the target node
7+
function getNodeIntersection(intersectionNode: InternalNode, targetNode: InternalNode) {
8+
// https://math.stackexchange.com/questions/1724792/an-algorithm-for-finding-the-intersection-point-between-a-center-of-vision-and-a
9+
10+
const { width: intersectionNodeWidth, height: intersectionNodeHeight } =
11+
intersectionNode.measured;
12+
const intersectionNodePosition = intersectionNode.internals.positionAbsolute;
13+
const targetPosition = targetNode.internals.positionAbsolute!;
14+
15+
const w = intersectionNodeWidth! / 2;
16+
const h = intersectionNodeHeight! / 2;
17+
18+
const x2 = intersectionNodePosition.x + w;
19+
const y2 = intersectionNodePosition.y + h;
20+
const x1 = targetPosition.x + w;
21+
const y1 = targetPosition.y + h;
22+
23+
const xx1 = (x1 - x2) / (2 * w) - (y1 - y2) / (2 * h);
24+
const yy1 = (x1 - x2) / (2 * w) + (y1 - y2) / (2 * h);
25+
const a = 1 / (Math.abs(xx1) + Math.abs(yy1) || 1);
26+
const xx3 = a * xx1;
27+
const yy3 = a * yy1;
28+
const x = w * (xx3 + yy3) + x2;
29+
const y = h * (-xx3 + yy3) + y2;
30+
return { x, y };
31+
}
32+
33+
// returns the position (top,right,bottom or right) passed node compared to the intersection point
34+
function getEdgePosition(node: InternalNode, intersectionPoint: XYPosition) {
35+
const n = { ...node.internals.positionAbsolute, ...node };
36+
const nx = Math.round(n.x!);
37+
const ny = Math.round(n.y!);
38+
const px = Math.round(intersectionPoint.x);
39+
const py = Math.round(intersectionPoint.y);
40+
41+
const { width = 0, height = 0 } = node.measured;
42+
43+
if (px <= nx + 1) {
44+
return Position.Left;
45+
}
46+
if (px >= nx + width - 1) {
47+
return Position.Right;
48+
}
49+
if (py <= ny + 1) {
50+
return Position.Top;
51+
}
52+
if (py >= n.y! + height - 1) {
53+
return Position.Bottom;
54+
}
55+
56+
return Position.Top;
57+
}
58+
59+
// returns the parameters (sx, sy, tx, ty, sourcePos, targetPos) you need to create an edge
60+
export function getEdgeParams(source: InternalNode, target: InternalNode) {
61+
const sourceIntersectionPoint = getNodeIntersection(source, target);
62+
const targetIntersectionPoint = getNodeIntersection(target, source);
63+
64+
const sourcePos = getEdgePosition(source, sourceIntersectionPoint);
65+
const targetPos = getEdgePosition(target, targetIntersectionPoint);
66+
67+
return {
68+
sx: sourceIntersectionPoint.x,
69+
sy: sourceIntersectionPoint.y,
70+
tx: targetIntersectionPoint.x,
71+
ty: targetIntersectionPoint.y,
72+
sourcePos,
73+
targetPos,
74+
};
75+
}

playground/examples/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export { CustomConnectionLine } from "./custom-connection-line/page";
66
export { CustomNode } from "./custom-node/page";
77
export { Dagre } from "./dagre/page";
88
export { DragNDrop } from "./drag-n-drop/page";
9+
export { EasyConnect } from "./easy-connect/page";
910
export { Edges } from "./edges/page";
1011
export { HandleConnect } from "./handle-connect/page";
1112
export { Interaction } from "./interaction/page";
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export { default as ConnectionLine } from "./ConnectionLine";
2+
export type { ConnectionLineComponentProps } from "./types";

src/components/graph/handle/Handle.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -227,9 +227,10 @@ export const Handle = <NodeType extends Node = Node, EdgeType extends Edge = Edg
227227
style={local.style}
228228
class={clsx(
229229
"solid-flow__handle",
230+
`solid-flow__handle-${local.position}`,
230231
store.noDragClass,
231232
store.noPanClass,
232-
local.position,
233+
local.class,
233234
{
234235
valid: valid(),
235236
connectingto: connectingTo(),
@@ -241,7 +242,6 @@ export const Handle = <NodeType extends Node = Node, EdgeType extends Edge = Edg
241242
connectable: connectable(),
242243
connectionindicator: connectionIndicator(),
243244
},
244-
local.class,
245245
)}
246246
{...rest}
247247
>

0 commit comments

Comments
 (0)