From eaea14c617c96a527ecaa0a920a05109b9b5f54e Mon Sep 17 00:00:00 2001 From: Kenny Williams Date: Wed, 2 Jul 2025 06:35:38 -0700 Subject: [PATCH 1/5] feat(smoothstep-edge): add bendPosition to getSmoothStepPath --- .../react/src/examples/EdgeRouting/index.tsx | 81 +++++++++++++++++++ .../src/components/Edges/SmoothStepEdge.tsx | 1 + .../components/edges/SmoothStepEdge.svelte | 3 +- packages/system/src/types/edges.ts | 1 + .../system/src/utils/edges/smoothstep-edge.ts | 23 +++++- 5 files changed, 104 insertions(+), 5 deletions(-) diff --git a/examples/react/src/examples/EdgeRouting/index.tsx b/examples/react/src/examples/EdgeRouting/index.tsx index 4e78212b..4f1fc97d 100644 --- a/examples/react/src/examples/EdgeRouting/index.tsx +++ b/examples/react/src/examples/EdgeRouting/index.tsx @@ -69,6 +69,55 @@ const nodes: Node[] = [ targetPosition: Position.Bottom, style: { background: 'rgba(255,255,255,0.5)' }, }, + // Bend Position Examples + { + 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)' }, + }, ]; const edges: Edge[] = [ @@ -102,6 +151,38 @@ const edges: Edge[] = [ source: '7', target: '8', }, + + // Bend Position Examples + { + id: 'e9-10', + source: '9', + target: '10', + label: 'bendPosition: 0.2', + pathOptions: { + bendPosition: 0.2, + }, + interactionWidth: 0, + }, + { + id: 'e11-12', + source: '11', + target: '12', + label: 'bendPosition: 0.5 (default)', + pathOptions: { + bendPosition: 0.5, + }, + interactionWidth: 0, + }, + { + id: 'e13-14', + source: '13', + target: '14', + label: 'bendPosition: 0.8', + pathOptions: { + bendPosition: 0.8, + }, + interactionWidth: 0, + }, ]; const defaultEdgeOptions = { diff --git a/packages/react/src/components/Edges/SmoothStepEdge.tsx b/packages/react/src/components/Edges/SmoothStepEdge.tsx index 8b1475cf..45771496 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, + bendPosition: pathOptions?.bendPosition, }); 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..ff47d7f5 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, + bendPosition: pathOptions?.bendPosition, }) ); diff --git a/packages/system/src/types/edges.ts b/packages/system/src/types/edges.ts index c0568200..3acfab99 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; + bendPosition?: 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..8e90e7c8 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 + */ + bendPosition?: number; } const handleDirections = { @@ -63,6 +69,7 @@ function getPoints({ targetPosition = Position.Top, center, offset, + bendPosition, }: { source: XYPosition; sourcePosition: Position; @@ -70,6 +77,7 @@ function getPoints({ targetPosition: Position; center: Partial; offset: number; + bendPosition: number; }): [XYPosition[], number, number, number, number] { const sourceDir = handleDirections[sourcePosition]; const targetDir = handleDirections[targetPosition]; @@ -88,17 +96,22 @@ 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, targetY: target.y, }); + // Calculate bend position based on the bendPosition parameter + const bendX = sourceGapped.x + (targetGapped.x - sourceGapped.x) * bendPosition; + const bendY = sourceGapped.y + (targetGapped.y - sourceGapped.y) * bendPosition; + // opposite handle positions, default case if (sourceDir[dirAccessor] * targetDir[dirAccessor] === -1) { - centerX = center.x ?? defaultCenterX; - centerY = center.y ?? defaultCenterY; + centerX = center.x ?? bendX; + centerY = center.y ?? bendY; + /* * ---> * | @@ -252,7 +265,8 @@ export function getSmoothStepPath({ centerX, centerY, offset = 20, -}: GetSmoothStepPathParams): [path: string, labelX: number, labelY: number, offsetX: number, offsetY: number] { + bendPosition = 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 }, sourcePosition, @@ -260,6 +274,7 @@ export function getSmoothStepPath({ targetPosition, center: { x: centerX, y: centerY }, offset, + bendPosition, }); const path = points.reduce((res, p, i) => { From 772f53a27daf7ce5fd42e7ff1fe7350e44702e8b Mon Sep 17 00:00:00 2001 From: moklick Date: Wed, 9 Jul 2025 14:45:52 +0200 Subject: [PATCH 2/5] chore(step-edge): rename bendPosition to stepPosition --- .../react/src/examples/EdgeRouting/index.tsx | 14 +++++++------- .../src/components/Edges/SmoothStepEdge.tsx | 2 +- .../lib/components/edges/SmoothStepEdge.svelte | 2 +- packages/system/src/types/edges.ts | 2 +- .../system/src/utils/edges/smoothstep-edge.ts | 18 +++++++++--------- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/examples/react/src/examples/EdgeRouting/index.tsx b/examples/react/src/examples/EdgeRouting/index.tsx index 4f1fc97d..a0603582 100644 --- a/examples/react/src/examples/EdgeRouting/index.tsx +++ b/examples/react/src/examples/EdgeRouting/index.tsx @@ -151,15 +151,15 @@ const edges: Edge[] = [ source: '7', target: '8', }, - + // Bend Position Examples { id: 'e9-10', source: '9', target: '10', - label: 'bendPosition: 0.2', + label: 'stepPosition: 0.2', pathOptions: { - bendPosition: 0.2, + stepPosition: 0.2, }, interactionWidth: 0, }, @@ -167,9 +167,9 @@ const edges: Edge[] = [ id: 'e11-12', source: '11', target: '12', - label: 'bendPosition: 0.5 (default)', + label: 'stepPosition: 0.5 (default)', pathOptions: { - bendPosition: 0.5, + stepPosition: 0.5, }, interactionWidth: 0, }, @@ -177,9 +177,9 @@ const edges: Edge[] = [ id: 'e13-14', source: '13', target: '14', - label: 'bendPosition: 0.8', + label: 'stepPosition: 0.8', pathOptions: { - bendPosition: 0.8, + stepPosition: 0.8, }, interactionWidth: 0, }, diff --git a/packages/react/src/components/Edges/SmoothStepEdge.tsx b/packages/react/src/components/Edges/SmoothStepEdge.tsx index 45771496..6d52f6a4 100644 --- a/packages/react/src/components/Edges/SmoothStepEdge.tsx +++ b/packages/react/src/components/Edges/SmoothStepEdge.tsx @@ -36,7 +36,7 @@ function createSmoothStepEdge(params: { isInternal: boolean }) { targetPosition, borderRadius: pathOptions?.borderRadius, offset: pathOptions?.offset, - bendPosition: pathOptions?.bendPosition, + 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 ff47d7f5..b8eb2c5b 100644 --- a/packages/svelte/src/lib/components/edges/SmoothStepEdge.svelte +++ b/packages/svelte/src/lib/components/edges/SmoothStepEdge.svelte @@ -31,7 +31,7 @@ targetPosition, borderRadius: pathOptions?.borderRadius, offset: pathOptions?.offset, - bendPosition: pathOptions?.bendPosition, + stepPosition: pathOptions?.stepPosition }) ); diff --git a/packages/system/src/types/edges.ts b/packages/system/src/types/edges.ts index 3acfab99..143e66dd 100644 --- a/packages/system/src/types/edges.ts +++ b/packages/system/src/types/edges.ts @@ -45,7 +45,7 @@ export type EdgeBase< export type SmoothStepPathOptions = { offset?: number; borderRadius?: number; - bendPosition?: 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 8e90e7c8..5eae694c 100644 --- a/packages/system/src/utils/edges/smoothstep-edge.ts +++ b/packages/system/src/utils/edges/smoothstep-edge.ts @@ -31,7 +31,7 @@ export interface GetSmoothStepPathParams { * 0 = at source, 1 = at target, 0.5 = midpoint * @default 0.5 */ - bendPosition?: number; + stepPosition?: number; } const handleDirections = { @@ -69,7 +69,7 @@ function getPoints({ targetPosition = Position.Top, center, offset, - bendPosition, + stepPosition, }: { source: XYPosition; sourcePosition: Position; @@ -77,7 +77,7 @@ function getPoints({ targetPosition: Position; center: Partial; offset: number; - bendPosition: number; + stepPosition: number; }): [XYPosition[], number, number, number, number] { const sourceDir = handleDirections[sourcePosition]; const targetDir = handleDirections[targetPosition]; @@ -103,9 +103,9 @@ function getPoints({ targetY: target.y, }); - // Calculate bend position based on the bendPosition parameter - const bendX = sourceGapped.x + (targetGapped.x - sourceGapped.x) * bendPosition; - const bendY = sourceGapped.y + (targetGapped.y - sourceGapped.y) * bendPosition; + // Calculate bend position based on the stepPosition parameter + const bendX = sourceGapped.x + (targetGapped.x - sourceGapped.x) * stepPosition; + const bendY = sourceGapped.y + (targetGapped.y - sourceGapped.y) * stepPosition; // opposite handle positions, default case if (sourceDir[dirAccessor] * targetDir[dirAccessor] === -1) { @@ -265,8 +265,8 @@ export function getSmoothStepPath({ centerX, centerY, offset = 20, - bendPosition = 0.5, -}: GetSmoothStepPathParams): [path: string, labelX: number, labelY: number, offsetX: number, offsetY: number] { + 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 }, sourcePosition, @@ -274,7 +274,7 @@ export function getSmoothStepPath({ targetPosition, center: { x: centerX, y: centerY }, offset, - bendPosition, + stepPosition, }); const path = points.reduce((res, p, i) => { From 630b2385e24a5db66295e09eedaadd8941c3790e Mon Sep 17 00:00:00 2001 From: Kenny Williams Date: Wed, 9 Jul 2025 18:08:01 -0700 Subject: [PATCH 3/5] fix(step-edge): always center align label --- .../react/src/examples/EdgeRouting/index.tsx | 84 ++++++++++++++++++- .../system/src/utils/edges/smoothstep-edge.ts | 15 ++-- 2 files changed, 91 insertions(+), 8 deletions(-) diff --git a/examples/react/src/examples/EdgeRouting/index.tsx b/examples/react/src/examples/EdgeRouting/index.tsx index a0603582..0830b431 100644 --- a/examples/react/src/examples/EdgeRouting/index.tsx +++ b/examples/react/src/examples/EdgeRouting/index.tsx @@ -69,7 +69,7 @@ const nodes: Node[] = [ targetPosition: Position.Bottom, style: { background: 'rgba(255,255,255,0.5)' }, }, - // Bend Position Examples + // Bend Position Examples (horizontal) { id: '9', position: { x: 300, y: 0 }, @@ -118,6 +118,55 @@ const nodes: Node[] = [ 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[] = [ @@ -152,7 +201,7 @@ const edges: Edge[] = [ target: '8', }, - // Bend Position Examples + // Bend Position Examples (horizontal) { id: 'e9-10', source: '9', @@ -183,6 +232,37 @@ const edges: Edge[] = [ }, 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 = { diff --git a/packages/system/src/utils/edges/smoothstep-edge.ts b/packages/system/src/utils/edges/smoothstep-edge.ts index 5eae694c..5ca5c489 100644 --- a/packages/system/src/utils/edges/smoothstep-edge.ts +++ b/packages/system/src/utils/edges/smoothstep-edge.ts @@ -103,14 +103,17 @@ function getPoints({ targetY: target.y, }); - // Calculate bend position based on the stepPosition parameter - const bendX = sourceGapped.x + (targetGapped.x - sourceGapped.x) * stepPosition; - const bendY = sourceGapped.y + (targetGapped.y - sourceGapped.y) * stepPosition; - // opposite handle positions, default case if (sourceDir[dirAccessor] * targetDir[dirAccessor] === -1) { - centerX = center.x ?? bendX; - centerY = center.y ?? bendY; + 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); + } /* * ---> From a60d77ce175705ed1cb4952cb6857539bd5b9a8d Mon Sep 17 00:00:00 2001 From: moklick Date: Thu, 10 Jul 2025 07:46:27 +0200 Subject: [PATCH 4/5] chore(edge-routing): use builtintedge type --- examples/react/src/examples/EdgeRouting/index.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/react/src/examples/EdgeRouting/index.tsx b/examples/react/src/examples/EdgeRouting/index.tsx index 0830b431..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 @@ -169,7 +169,7 @@ const nodes: Node[] = [ }, ]; -const edges: Edge[] = [ +const edges: BuiltInEdge[] = [ { id: 'e1-2', source: '1', @@ -276,8 +276,8 @@ const defaultEdgeOptions = { }, }; -const SimpleEdge = () => { +function EdgeRouting() { return ; -}; +} -export default SimpleEdge; +export default EdgeRouting; From f0ce2c876d8688e13632bc86286cf857f86dead6 Mon Sep 17 00:00:00 2001 From: moklick Date: Thu, 10 Jul 2025 11:31:54 +0200 Subject: [PATCH 5/5] chore(changeset): add --- .changeset/slow-snails-tickle.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/slow-snails-tickle.md 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