diff --git a/.changeset/slow-snails-tickle.md b/.changeset/slow-snails-tickle.md
new file mode 100644
index 00000000..f55d0e71
--- /dev/null
+++ b/.changeset/slow-snails-tickle.md
@@ -0,0 +1,7 @@
+---
+'@xyflow/react': patch
+'@xyflow/svelte': patch
+'@xyflow/system': patch
+---
+
+Add stepPosition param to step edge
diff --git a/examples/react/src/examples/EdgeRouting/index.tsx b/examples/react/src/examples/EdgeRouting/index.tsx
index 4e78212b..5996f0b2 100644
--- a/examples/react/src/examples/EdgeRouting/index.tsx
+++ b/examples/react/src/examples/EdgeRouting/index.tsx
@@ -1,4 +1,4 @@
-import { ReactFlow, Node, Edge, Position, MarkerType } from '@xyflow/react';
+import { ReactFlow, Node, Position, MarkerType, BuiltInEdge } from '@xyflow/react';
const nodes: Node[] = [
// LTR
@@ -69,9 +69,107 @@ const nodes: Node[] = [
targetPosition: Position.Bottom,
style: { background: 'rgba(255,255,255,0.5)' },
},
+ // Bend Position Examples (horizontal)
+ {
+ id: '9',
+ position: { x: 300, y: 0 },
+ data: { label: 'Source' },
+ sourcePosition: Position.Right,
+ targetPosition: Position.Right,
+ style: { background: 'rgba(255,255,255,0.5)' },
+ },
+ {
+ id: '10',
+ position: { x: 600, y: 150 },
+ data: { label: 'Target' },
+ sourcePosition: Position.Left,
+ targetPosition: Position.Left,
+ style: { background: 'rgba(255,255,255,0.5)' },
+ },
+ {
+ id: '11',
+ position: { x: 300, y: 300 },
+ data: { label: 'Source' },
+ sourcePosition: Position.Right,
+ targetPosition: Position.Right,
+ style: { background: 'rgba(255,255,255,0.5)' },
+ },
+ {
+ id: '12',
+ position: { x: 600, y: 450 },
+ data: { label: 'Target' },
+ sourcePosition: Position.Left,
+ targetPosition: Position.Left,
+ style: { background: 'rgba(255,255,255,0.5)' },
+ },
+ {
+ id: '13',
+ position: { x: 300, y: 600 },
+ data: { label: 'Source' },
+ sourcePosition: Position.Right,
+ targetPosition: Position.Right,
+ style: { background: 'rgba(255,255,255,0.5)' },
+ },
+ {
+ id: '14',
+ position: { x: 600, y: 750 },
+ data: { label: 'Target' },
+ sourcePosition: Position.Left,
+ targetPosition: Position.Left,
+ style: { background: 'rgba(255,255,255,0.5)' },
+ },
+ // Bend Position Examples (vertical)
+ {
+ id: '15',
+ position: { x: 800, y: 0 },
+ data: { label: 'Source' },
+ sourcePosition: Position.Bottom,
+ targetPosition: Position.Bottom,
+ style: { background: 'rgba(255,255,255,0.5)' },
+ },
+ {
+ id: '16',
+ position: { x: 950, y: 150 },
+ data: { label: 'Target' },
+ sourcePosition: Position.Top,
+ targetPosition: Position.Top,
+ style: { background: 'rgba(255,255,255,0.5)' },
+ },
+ {
+ id: '17',
+ position: { x: 800, y: 300 },
+ data: { label: 'Source' },
+ sourcePosition: Position.Bottom,
+ targetPosition: Position.Bottom,
+ style: { background: 'rgba(255,255,255,0.5)' },
+ },
+ {
+ id: '18',
+ position: { x: 950, y: 450 },
+ data: { label: 'Target' },
+ sourcePosition: Position.Top,
+ targetPosition: Position.Top,
+ style: { background: 'rgba(255,255,255,0.5)' },
+ },
+ {
+ id: '19',
+ position: { x: 800, y: 600 },
+ data: { label: 'Source' },
+ sourcePosition: Position.Bottom,
+ targetPosition: Position.Bottom,
+ style: { background: 'rgba(255,255,255,0.5)' },
+ },
+ {
+ id: '20',
+ position: { x: 950, y: 750 },
+ data: { label: 'Target' },
+ sourcePosition: Position.Top,
+ targetPosition: Position.Top,
+ style: { background: 'rgba(255,255,255,0.5)' },
+ },
];
-const edges: Edge[] = [
+const edges: BuiltInEdge[] = [
{
id: 'e1-2',
source: '1',
@@ -102,6 +200,69 @@ const edges: Edge[] = [
source: '7',
target: '8',
},
+
+ // Bend Position Examples (horizontal)
+ {
+ id: 'e9-10',
+ source: '9',
+ target: '10',
+ label: 'stepPosition: 0.2',
+ pathOptions: {
+ stepPosition: 0.2,
+ },
+ interactionWidth: 0,
+ },
+ {
+ id: 'e11-12',
+ source: '11',
+ target: '12',
+ label: 'stepPosition: 0.5 (default)',
+ pathOptions: {
+ stepPosition: 0.5,
+ },
+ interactionWidth: 0,
+ },
+ {
+ id: 'e13-14',
+ source: '13',
+ target: '14',
+ label: 'stepPosition: 0.8',
+ pathOptions: {
+ stepPosition: 0.8,
+ },
+ interactionWidth: 0,
+ },
+ // Bend Position Examples (vertical)
+ {
+ id: 'e15-16',
+ source: '15',
+ target: '16',
+ label: 'stepPosition: 0.2',
+ pathOptions: {
+ stepPosition: 0.2,
+ },
+ interactionWidth: 0,
+ },
+ {
+ id: 'e17-18',
+ source: '17',
+ target: '18',
+ label: 'stepPosition: 0.5',
+ pathOptions: {
+ stepPosition: 0.5,
+ },
+ interactionWidth: 0,
+ },
+ {
+ id: 'e19-20',
+ source: '19',
+ target: '20',
+ label: 'stepPosition: 0.8',
+ pathOptions: {
+ stepPosition: 0.8,
+ },
+ interactionWidth: 0,
+ },
];
const defaultEdgeOptions = {
@@ -115,8 +276,8 @@ const defaultEdgeOptions = {
},
};
-const SimpleEdge = () => {
+function EdgeRouting() {
return ;
-};
+}
-export default SimpleEdge;
+export default EdgeRouting;
diff --git a/packages/react/src/components/Edges/SmoothStepEdge.tsx b/packages/react/src/components/Edges/SmoothStepEdge.tsx
index 8b1475cf..6d52f6a4 100644
--- a/packages/react/src/components/Edges/SmoothStepEdge.tsx
+++ b/packages/react/src/components/Edges/SmoothStepEdge.tsx
@@ -36,6 +36,7 @@ function createSmoothStepEdge(params: { isInternal: boolean }) {
targetPosition,
borderRadius: pathOptions?.borderRadius,
offset: pathOptions?.offset,
+ stepPosition: pathOptions?.stepPosition,
});
const _id = params.isInternal ? undefined : id;
diff --git a/packages/svelte/src/lib/components/edges/SmoothStepEdge.svelte b/packages/svelte/src/lib/components/edges/SmoothStepEdge.svelte
index a1cae125..b8eb2c5b 100644
--- a/packages/svelte/src/lib/components/edges/SmoothStepEdge.svelte
+++ b/packages/svelte/src/lib/components/edges/SmoothStepEdge.svelte
@@ -30,7 +30,8 @@
sourcePosition,
targetPosition,
borderRadius: pathOptions?.borderRadius,
- offset: pathOptions?.offset
+ offset: pathOptions?.offset,
+ stepPosition: pathOptions?.stepPosition
})
);
diff --git a/packages/system/src/types/edges.ts b/packages/system/src/types/edges.ts
index c0568200..143e66dd 100644
--- a/packages/system/src/types/edges.ts
+++ b/packages/system/src/types/edges.ts
@@ -45,6 +45,7 @@ export type EdgeBase<
export type SmoothStepPathOptions = {
offset?: number;
borderRadius?: number;
+ stepPosition?: number;
};
export type StepPathOptions = {
diff --git a/packages/system/src/utils/edges/smoothstep-edge.ts b/packages/system/src/utils/edges/smoothstep-edge.ts
index b10c5a07..5ca5c489 100644
--- a/packages/system/src/utils/edges/smoothstep-edge.ts
+++ b/packages/system/src/utils/edges/smoothstep-edge.ts
@@ -26,6 +26,12 @@ export interface GetSmoothStepPathParams {
centerY?: number;
/** @default 20 */
offset?: number;
+ /**
+ * Controls where the bend occurs along the path.
+ * 0 = at source, 1 = at target, 0.5 = midpoint
+ * @default 0.5
+ */
+ stepPosition?: number;
}
const handleDirections = {
@@ -63,6 +69,7 @@ function getPoints({
targetPosition = Position.Top,
center,
offset,
+ stepPosition,
}: {
source: XYPosition;
sourcePosition: Position;
@@ -70,6 +77,7 @@ function getPoints({
targetPosition: Position;
center: Partial;
offset: number;
+ stepPosition: number;
}): [XYPosition[], number, number, number, number] {
const sourceDir = handleDirections[sourcePosition];
const targetDir = handleDirections[targetPosition];
@@ -88,7 +96,7 @@ function getPoints({
const sourceGapOffset = { x: 0, y: 0 };
const targetGapOffset = { x: 0, y: 0 };
- const [defaultCenterX, defaultCenterY, defaultOffsetX, defaultOffsetY] = getEdgeCenter({
+ const [, , defaultOffsetX, defaultOffsetY] = getEdgeCenter({
sourceX: source.x,
sourceY: source.y,
targetX: target.x,
@@ -97,8 +105,16 @@ function getPoints({
// opposite handle positions, default case
if (sourceDir[dirAccessor] * targetDir[dirAccessor] === -1) {
- centerX = center.x ?? defaultCenterX;
- centerY = center.y ?? defaultCenterY;
+ if (dirAccessor === 'x') {
+ // Primary direction is horizontal, so stepPosition affects X coordinate
+ centerX = center.x ?? (sourceGapped.x + (targetGapped.x - sourceGapped.x) * stepPosition);
+ centerY = center.y ?? (sourceGapped.y + targetGapped.y) / 2;
+ } else {
+ // Primary direction is vertical, so stepPosition affects Y coordinate
+ centerX = center.x ?? (sourceGapped.x + targetGapped.x) / 2;
+ centerY = center.y ?? (sourceGapped.y + (targetGapped.y - sourceGapped.y) * stepPosition);
+ }
+
/*
* --->
* |
@@ -252,6 +268,7 @@ export function getSmoothStepPath({
centerX,
centerY,
offset = 20,
+ stepPosition = 0.5,
}: GetSmoothStepPathParams): [path: string, labelX: number, labelY: number, offsetX: number, offsetY: number] {
const [points, labelX, labelY, offsetX, offsetY] = getPoints({
source: { x: sourceX, y: sourceY },
@@ -260,6 +277,7 @@ export function getSmoothStepPath({
targetPosition,
center: { x: centerX, y: centerY },
offset,
+ stepPosition,
});
const path = points.reduce((res, p, i) => {