From d2df1dced7a7de4ed69b003c0069c7d7141bef05 Mon Sep 17 00:00:00 2001 From: Ze-Zheng Wu Date: Sun, 17 Aug 2025 02:30:16 +0800 Subject: [PATCH 001/167] fix: skip resize handler updates when DOM node is not visible Prevent unnecessary resize calculations and error messages for invisible elements by checking visibility before processing dimensions --- .changeset/stupid-lizards-wash.md | 5 +++++ packages/react/src/hooks/useResizeHandler.ts | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/stupid-lizards-wash.md diff --git a/.changeset/stupid-lizards-wash.md b/.changeset/stupid-lizards-wash.md new file mode 100644 index 00000000..df4eae58 --- /dev/null +++ b/.changeset/stupid-lizards-wash.md @@ -0,0 +1,5 @@ +--- +'@xyflow/react': patch +--- + +Skip resize handler updates when DOM node is not visible diff --git a/packages/react/src/hooks/useResizeHandler.ts b/packages/react/src/hooks/useResizeHandler.ts index 4348208a..4577e23d 100644 --- a/packages/react/src/hooks/useResizeHandler.ts +++ b/packages/react/src/hooks/useResizeHandler.ts @@ -13,7 +13,7 @@ export function useResizeHandler(domNode: MutableRefObject { const updateDimensions = () => { - if (!domNode.current) { + if (!domNode.current || !(domNode.current.checkVisibility?.() ?? true)) { return false; } const size = getDimensions(domNode.current); From d7a3172a60db32ec30d5d8507b063e134280b00e Mon Sep 17 00:00:00 2001 From: peterkogo Date: Mon, 25 Aug 2025 19:12:33 +0200 Subject: [PATCH 002/167] prevent pinch zooming while making a new conection --- packages/react/src/container/ZoomPane/index.tsx | 5 ++++- packages/svelte/src/lib/actions/zoom/index.ts | 1 + packages/svelte/src/lib/container/Zoom/Zoom.svelte | 3 ++- packages/system/src/types/panzoom.ts | 1 + packages/system/src/xypanzoom/XYPanZoom.ts | 2 ++ packages/system/src/xypanzoom/filter.ts | 7 +++++++ 6 files changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/react/src/container/ZoomPane/index.tsx b/packages/react/src/container/ZoomPane/index.tsx index 61af78e6..bce2b2b7 100644 --- a/packages/react/src/container/ZoomPane/index.tsx +++ b/packages/react/src/container/ZoomPane/index.tsx @@ -25,6 +25,7 @@ type ZoomPaneProps = Omit< const selector = (s: ReactFlowState) => ({ userSelectionActive: s.userSelectionActive, lib: s.lib, + connectionInProgress: s.connection.inProgress, }); export function ZoomPane({ @@ -51,7 +52,7 @@ export function ZoomPane({ }: ZoomPaneProps) { const store = useStoreApi(); const zoomPane = useRef(null); - const { userSelectionActive, lib } = useStore(selector, shallow); + const { userSelectionActive, lib, connectionInProgress } = useStore(selector, shallow); const zoomActivationKeyPressed = useKeyPress(zoomActivationKeyCode); const panZoom = useRef(); @@ -126,6 +127,7 @@ export function ZoomPane({ noWheelClassName, lib, onTransformChange, + connectionInProgress, }); }, [ onPaneContextMenu, @@ -143,6 +145,7 @@ export function ZoomPane({ noWheelClassName, lib, onTransformChange, + connectionInProgress, ]); return ( diff --git a/packages/svelte/src/lib/actions/zoom/index.ts b/packages/svelte/src/lib/actions/zoom/index.ts index 34ead520..27d7328f 100644 --- a/packages/svelte/src/lib/actions/zoom/index.ts +++ b/packages/svelte/src/lib/actions/zoom/index.ts @@ -37,6 +37,7 @@ type ZoomParams = { paneClickDistance: number; onTransformChange: (transform: Transform) => void; onDraggingChange: (dragging: boolean) => void; + connectionInProgress: boolean; }; export default function zoom(domNode: Element, params: ZoomParams) { diff --git a/packages/svelte/src/lib/container/Zoom/Zoom.svelte b/packages/svelte/src/lib/container/Zoom/Zoom.svelte index 42a644a6..9c159c99 100644 --- a/packages/svelte/src/lib/container/Zoom/Zoom.svelte +++ b/packages/svelte/src/lib/container/Zoom/Zoom.svelte @@ -70,7 +70,8 @@ paneClickDistance, onTransformChange: (transform: Transform) => { store.viewport = { x: transform[0], y: transform[1], zoom: transform[2] }; - } + }, + connectionInProgress: store.connection.inProgress }} > {@render children()} diff --git a/packages/system/src/types/panzoom.ts b/packages/system/src/types/panzoom.ts index ae968fc8..7fa54e97 100644 --- a/packages/system/src/types/panzoom.ts +++ b/packages/system/src/types/panzoom.ts @@ -42,6 +42,7 @@ export type PanZoomUpdateOptions = { zoomActivationKeyPressed: boolean; lib: string; onTransformChange: OnTransformChange; + connectionInProgress: boolean; }; export type PanZoomInstance = { diff --git a/packages/system/src/xypanzoom/XYPanZoom.ts b/packages/system/src/xypanzoom/XYPanZoom.ts index 09d28dc2..85b8ba1c 100644 --- a/packages/system/src/xypanzoom/XYPanZoom.ts +++ b/packages/system/src/xypanzoom/XYPanZoom.ts @@ -108,6 +108,7 @@ export function XYPanZoom({ zoomActivationKeyPressed, lib, onTransformChange, + connectionInProgress, }: PanZoomUpdateOptions) { if (userSelectionActive && !zoomPanValues.isZoomingOrPanning) { destroy(); @@ -178,6 +179,7 @@ export function XYPanZoom({ noPanClassName, noWheelClassName, lib, + connectionInProgress, }); d3ZoomInstance.filter(filter); diff --git a/packages/system/src/xypanzoom/filter.ts b/packages/system/src/xypanzoom/filter.ts index 57f92486..8446f20c 100644 --- a/packages/system/src/xypanzoom/filter.ts +++ b/packages/system/src/xypanzoom/filter.ts @@ -12,6 +12,7 @@ export type FilterParams = { noWheelClassName: string; noPanClassName: string; lib: string; + connectionInProgress: boolean; }; export function createFilter({ @@ -25,6 +26,7 @@ export function createFilter({ noWheelClassName, noPanClassName, lib, + connectionInProgress, }: FilterParams) { return (event: any): boolean => { const zoomScroll = zoomActivationKeyPressed || zoomOnScroll; @@ -48,6 +50,11 @@ export function createFilter({ return false; } + // we want to disable pinch-zooming while making a connection + if (connectionInProgress) { + return false; + } + // if the target element is inside an element with the nowheel class, we prevent zooming if (isWrappedWithClass(event, noWheelClassName) && event.type === 'wheel') { return false; From 61778080c73f136870bfdccdb2d003490e857f7f Mon Sep 17 00:00:00 2001 From: peterkogo Date: Tue, 26 Aug 2025 11:21:07 +0200 Subject: [PATCH 003/167] prevent multitouch aborting connection --- packages/system/src/xyhandle/XYHandle.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/system/src/xyhandle/XYHandle.ts b/packages/system/src/xyhandle/XYHandle.ts index aa71d49d..82de2f8a 100644 --- a/packages/system/src/xyhandle/XYHandle.ts +++ b/packages/system/src/xyhandle/XYHandle.ts @@ -207,6 +207,11 @@ function onPointerDown( } function onPointerUp(event: MouseEvent | TouchEvent) { + // Prevent multi-touch aborting connection + if ('touches' in event && event.touches.length > 0) { + return; + } + if (connectionStarted) { if ((closestHandle || resultHandleDomNode) && connection && isValid) { onConnect?.(connection); From f869808e3dfd3d289db034d21d505d62f84cdde3 Mon Sep 17 00:00:00 2001 From: peterkogo Date: Tue, 26 Aug 2025 11:24:04 +0200 Subject: [PATCH 004/167] add(changeset) --- .changeset/chilled-tigers-compare.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/chilled-tigers-compare.md diff --git a/.changeset/chilled-tigers-compare.md b/.changeset/chilled-tigers-compare.md new file mode 100644 index 00000000..74a4f7e5 --- /dev/null +++ b/.changeset/chilled-tigers-compare.md @@ -0,0 +1,7 @@ +--- +'@xyflow/react': patch +'@xyflow/svelte': patch +'@xyflow/system': patch +--- + +Prevent multi-touch events while making a new connection From 1c218d27f1944b1752d147063eadc2043e1b3568 Mon Sep 17 00:00:00 2001 From: peterkogo Date: Tue, 26 Aug 2025 13:06:15 +0200 Subject: [PATCH 005/167] make getIntersectingNodes and isNodeIntersecting behave the same --- packages/react/src/hooks/useReactFlow.ts | 11 ++++++++++- packages/svelte/src/lib/hooks/useSvelteFlow.svelte.ts | 11 ++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/react/src/hooks/useReactFlow.ts b/packages/react/src/hooks/useReactFlow.ts index 7fb66b8f..c72cb1b9 100644 --- a/packages/react/src/hooks/useReactFlow.ts +++ b/packages/react/src/hooks/useReactFlow.ts @@ -202,6 +202,11 @@ export function useReactFlow { const isRect = isRectObject(nodeOrRect); const nodeRect = isRect ? nodeOrRect : getNodeRect(nodeOrRect); @@ -240,7 +245,11 @@ export function useReactFlow 0; - return partiallyVisible || overlappingArea >= nodeRect.width * nodeRect.height; + return ( + partiallyVisible || + overlappingArea >= area.width * area.height || + overlappingArea >= nodeRect.width * nodeRect.height + ); }, updateNode, updateNodeData: (id, dataUpdate, options = { replace: false }) => { diff --git a/packages/svelte/src/lib/hooks/useSvelteFlow.svelte.ts b/packages/svelte/src/lib/hooks/useSvelteFlow.svelte.ts index 40a1da97..19307f1f 100644 --- a/packages/svelte/src/lib/hooks/useSvelteFlow.svelte.ts +++ b/packages/svelte/src/lib/hooks/useSvelteFlow.svelte.ts @@ -390,6 +390,11 @@ export function useSvelteFlow 0; - return partiallyVisible || overlappingArea >= nodeRect.width * nodeRect.height; + return ( + partiallyVisible || + overlappingArea >= area.width * area.height || + overlappingArea >= nodeRect.width * nodeRect.height + ); }, deleteElements: async ({ nodes: nodesToRemove = [], edges: edgesToRemove = [] }) => { const { nodes: matchingNodes, edges: matchingEdges } = await getElementsToRemove< From 56381ae546f392b13e7c1390df60e48c9072f128 Mon Sep 17 00:00:00 2001 From: peterkogo Date: Tue, 26 Aug 2025 13:07:01 +0200 Subject: [PATCH 006/167] add(changeset) --- .changeset/gentle-chefs-travel.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/gentle-chefs-travel.md diff --git a/.changeset/gentle-chefs-travel.md b/.changeset/gentle-chefs-travel.md new file mode 100644 index 00000000..08a5c774 --- /dev/null +++ b/.changeset/gentle-chefs-travel.md @@ -0,0 +1,6 @@ +--- +'@xyflow/react': patch +'@xyflow/svelte': patch +--- + +Make isNodeIntersecting behave the same as getIntersectingNodes From 61bb5da5283338eafd022d06c47e500257042480 Mon Sep 17 00:00:00 2001 From: peterkogo Date: Tue, 26 Aug 2025 16:52:38 +0200 Subject: [PATCH 007/167] allow wheel events when connection is being made --- packages/system/src/xypanzoom/filter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/system/src/xypanzoom/filter.ts b/packages/system/src/xypanzoom/filter.ts index 8446f20c..70375f4c 100644 --- a/packages/system/src/xypanzoom/filter.ts +++ b/packages/system/src/xypanzoom/filter.ts @@ -51,7 +51,7 @@ export function createFilter({ } // we want to disable pinch-zooming while making a connection - if (connectionInProgress) { + if (connectionInProgress && !(event.type === 'wheel')) { return false; } From 12d9e4ace48d244a94bc7fdf55dc9fd1a65b78b4 Mon Sep 17 00:00:00 2001 From: Moritz Klack Date: Tue, 26 Aug 2025 18:31:27 +0200 Subject: [PATCH 008/167] Refactor event type checks for xypanzoom filter --- packages/system/src/xypanzoom/filter.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/system/src/xypanzoom/filter.ts b/packages/system/src/xypanzoom/filter.ts index 70375f4c..3822f7aa 100644 --- a/packages/system/src/xypanzoom/filter.ts +++ b/packages/system/src/xypanzoom/filter.ts @@ -31,6 +31,7 @@ export function createFilter({ return (event: any): boolean => { const zoomScroll = zoomActivationKeyPressed || zoomOnScroll; const pinchZoom = zoomOnPinch && event.ctrlKey; + const isWheelEvent = event.type === 'wheel'; if ( event.button === 1 && @@ -51,24 +52,24 @@ export function createFilter({ } // we want to disable pinch-zooming while making a connection - if (connectionInProgress && !(event.type === 'wheel')) { + if (connectionInProgress && !isWheelEvent) { return false; } // if the target element is inside an element with the nowheel class, we prevent zooming - if (isWrappedWithClass(event, noWheelClassName) && event.type === 'wheel') { + if (isWrappedWithClass(event, noWheelClassName) && isWheelEvent) { return false; } // if the target element is inside an element with the nopan class, we prevent panning if ( isWrappedWithClass(event, noPanClassName) && - (event.type !== 'wheel' || (panOnScroll && event.type === 'wheel' && !zoomActivationKeyPressed)) + (!isWheelEvent || (panOnScroll && isWheelEvent && !zoomActivationKeyPressed)) ) { return false; } - if (!zoomOnPinch && event.ctrlKey && event.type === 'wheel') { + if (!zoomOnPinch && event.ctrlKey && isWheelEvent) { return false; } @@ -78,7 +79,7 @@ export function createFilter({ } // when there is no scroll handling enabled, we prevent all wheel events - if (!zoomScroll && !panOnScroll && !pinchZoom && event.type === 'wheel') { + if (!zoomScroll && !panOnScroll && !pinchZoom && isWheelEvent) { return false; } @@ -97,6 +98,6 @@ export function createFilter({ (Array.isArray(panOnDrag) && panOnDrag.includes(event.button)) || !event.button || event.button <= 1; // default filter for d3-zoom - return (!event.ctrlKey || event.type === 'wheel') && buttonAllowed; + return (!event.ctrlKey || isWheelEvent) && buttonAllowed; }; } From 2a35c65a02c6c8aec6b005ac3e72988f996669c4 Mon Sep 17 00:00:00 2001 From: Ze-Zheng Wu Date: Mon, 1 Sep 2025 20:04:21 +0800 Subject: [PATCH 009/167] fix: node selection instability by using client coordinates for drag threshold --- .changeset/chilly-beans-juggle.md | 5 +++++ packages/system/src/xydrag/XYDrag.ts | 6 ++++-- 2 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 .changeset/chilly-beans-juggle.md diff --git a/.changeset/chilly-beans-juggle.md b/.changeset/chilly-beans-juggle.md new file mode 100644 index 00000000..a7811f25 --- /dev/null +++ b/.changeset/chilly-beans-juggle.md @@ -0,0 +1,5 @@ +--- +'@xyflow/system': patch +--- + +Fix node drag threshold behavior across different zoom levels. The drag threshold calculation now uses client coordinates instead of transformed coordinates, ensuring consistent behavior regardless of zoom level. diff --git a/packages/system/src/xydrag/XYDrag.ts b/packages/system/src/xydrag/XYDrag.ts index 1da0720d..66afb31c 100644 --- a/packages/system/src/xydrag/XYDrag.ts +++ b/packages/system/src/xydrag/XYDrag.ts @@ -334,8 +334,10 @@ export function XYDrag voi } if (!dragStarted) { - const x = pointerPos.xSnapped - (lastPos.x ?? 0); - const y = pointerPos.ySnapped - (lastPos.y ?? 0); + // Calculate distance in client coordinates for consistent drag threshold behavior across zoom levels + const currentMousePosition = getEventPosition(event.sourceEvent, containerBounds!); + const x = currentMousePosition.x - (mousePosition.x ?? 0); + const y = currentMousePosition.y - (mousePosition.y ?? 0); const distance = Math.sqrt(x * x + y * y); if (distance > nodeDragThreshold) { From 3592651d12169d9b0b679deedd91ec3740a13609 Mon Sep 17 00:00:00 2001 From: peterkogo Date: Tue, 2 Sep 2025 10:30:33 +0200 Subject: [PATCH 010/167] shorten patch note --- .changeset/chilly-beans-juggle.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/chilly-beans-juggle.md b/.changeset/chilly-beans-juggle.md index a7811f25..64f1c8a0 100644 --- a/.changeset/chilly-beans-juggle.md +++ b/.changeset/chilly-beans-juggle.md @@ -2,4 +2,4 @@ '@xyflow/system': patch --- -Fix node drag threshold behavior across different zoom levels. The drag threshold calculation now uses client coordinates instead of transformed coordinates, ensuring consistent behavior regardless of zoom level. +Fix nodeDragThreshold changing with zoom level From 99c67422a79607f708eb5907732d1b035315f644 Mon Sep 17 00:00:00 2001 From: peterkogo Date: Tue, 2 Sep 2025 10:30:49 +0200 Subject: [PATCH 011/167] remove unnecessary check --- packages/system/src/xydrag/XYDrag.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/system/src/xydrag/XYDrag.ts b/packages/system/src/xydrag/XYDrag.ts index 66afb31c..c78e1035 100644 --- a/packages/system/src/xydrag/XYDrag.ts +++ b/packages/system/src/xydrag/XYDrag.ts @@ -336,8 +336,8 @@ export function XYDrag voi if (!dragStarted) { // Calculate distance in client coordinates for consistent drag threshold behavior across zoom levels const currentMousePosition = getEventPosition(event.sourceEvent, containerBounds!); - const x = currentMousePosition.x - (mousePosition.x ?? 0); - const y = currentMousePosition.y - (mousePosition.y ?? 0); + const x = currentMousePosition.x - mousePosition.x; + const y = currentMousePosition.y - mousePosition.y; const distance = Math.sqrt(x * x + y * y); if (distance > nodeDragThreshold) { From 62fd1e0d9d91bf2680e76f94675df1fe273d4a0a Mon Sep 17 00:00:00 2001 From: peterkogo Date: Tue, 2 Sep 2025 11:44:40 +0200 Subject: [PATCH 012/167] correctly propagate style in Svelte Flow --- packages/svelte/src/lib/container/SvelteFlow/Wrapper.svelte | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/svelte/src/lib/container/SvelteFlow/Wrapper.svelte b/packages/svelte/src/lib/container/SvelteFlow/Wrapper.svelte index 847dc4e4..57608967 100644 --- a/packages/svelte/src/lib/container/SvelteFlow/Wrapper.svelte +++ b/packages/svelte/src/lib/container/SvelteFlow/Wrapper.svelte @@ -80,7 +80,6 @@ autoPanOnConnect, autoPanOnNodeDrag, colorModeSSR, - style, defaultEdgeOptions, elevateNodesOnSelect, elevateEdgesOnSelect, From 086b28e3a09a26449d3f67401ca2c1eb63f18cd6 Mon Sep 17 00:00:00 2001 From: peterkogo Date: Tue, 2 Sep 2025 11:45:08 +0200 Subject: [PATCH 013/167] add hide/unhide buttons to example --- examples/react/src/examples/Basic/index.tsx | 86 +++++++++++-------- .../src/routes/examples/overview/Flow.svelte | 16 ++++ 2 files changed, 64 insertions(+), 38 deletions(-) diff --git a/examples/react/src/examples/Basic/index.tsx b/examples/react/src/examples/Basic/index.tsx index 168bcff6..a24c908c 100644 --- a/examples/react/src/examples/Basic/index.tsx +++ b/examples/react/src/examples/Basic/index.tsx @@ -1,4 +1,4 @@ -import { MouseEvent, useCallback } from 'react'; +import { MouseEvent, useCallback, useState } from 'react'; import { ReactFlow, MiniMap, @@ -134,47 +134,57 @@ const BasicFlow = () => { }); fitView(); }; + const [isHidden, setIsHidden] = useState(false); + const toggleVisibility = () => { + setIsHidden(!isHidden); + }; return ( - - - - + <> + + + + - - - - - + + + + + - - - - - - - + + + + + + + + + ); }; diff --git a/examples/svelte/src/routes/examples/overview/Flow.svelte b/examples/svelte/src/routes/examples/overview/Flow.svelte index 3075a0e8..e83548fd 100644 --- a/examples/svelte/src/routes/examples/overview/Flow.svelte +++ b/examples/svelte/src/routes/examples/overview/Flow.svelte @@ -155,6 +155,8 @@ // console.log(edges.map((edge) => ({ id: edge.id, selected: edge.selected }))); edges.forEach((edge) => console.log({ id: edge.id, selected: edge.selected })); }); + + let hidden = $state(false); + + diff --git a/packages/svelte/src/lib/components/Selection/Selection.svelte b/packages/svelte/src/lib/components/Selection/Selection.svelte index 25a7de2e..1422b185 100644 --- a/packages/svelte/src/lib/components/Selection/Selection.svelte +++ b/packages/svelte/src/lib/components/Selection/Selection.svelte @@ -1,4 +1,5 @@ {#if isVisible}
Date: Tue, 28 Oct 2025 15:46:50 +0100 Subject: [PATCH 136/167] chore(svelte/selection): cleanup --- .../src/lib/components/NodeSelection/NodeSelection.svelte | 2 +- .../svelte/src/lib/components/Selection/Selection.svelte | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/svelte/src/lib/components/NodeSelection/NodeSelection.svelte b/packages/svelte/src/lib/components/NodeSelection/NodeSelection.svelte index 5dd87b42..65e12b6a 100644 --- a/packages/svelte/src/lib/components/NodeSelection/NodeSelection.svelte +++ b/packages/svelte/src/lib/components/NodeSelection/NodeSelection.svelte @@ -85,7 +85,7 @@ onkeydown={store.disableKeyboardA11y ? undefined : onkeydown} bind:this={ref} > - +
{/if} diff --git a/packages/svelte/src/lib/components/Selection/Selection.svelte b/packages/svelte/src/lib/components/Selection/Selection.svelte index 1422b185..25a7de2e 100644 --- a/packages/svelte/src/lib/components/Selection/Selection.svelte +++ b/packages/svelte/src/lib/components/Selection/Selection.svelte @@ -1,5 +1,4 @@ {#if isVisible}
Date: Tue, 28 Oct 2025 16:13:41 +0100 Subject: [PATCH 137/167] chore(changeset) --- .changeset/thirty-goats-invent.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/thirty-goats-invent.md diff --git a/.changeset/thirty-goats-invent.md b/.changeset/thirty-goats-invent.md new file mode 100644 index 00000000..c57c53f5 --- /dev/null +++ b/.changeset/thirty-goats-invent.md @@ -0,0 +1,5 @@ +--- +'@xyflow/svelte': patch +--- + +Remove focus outline from selection box From 1522b2451d5f25deb583f2178ea560cb696f76ca Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 28 Oct 2025 15:16:21 +0000 Subject: [PATCH 138/167] chore(packages): bump --- .changeset/eight-rice-laugh.md | 5 ----- .changeset/nasty-clouds-shake.md | 5 ----- .changeset/neat-rules-hammer.md | 5 ----- .changeset/sour-jokes-argue.md | 7 ------- .changeset/thirty-goats-invent.md | 5 ----- packages/react/CHANGELOG.md | 9 +++++++++ packages/react/package.json | 2 +- packages/svelte/CHANGELOG.md | 17 +++++++++++++++++ packages/svelte/package.json | 2 +- packages/system/CHANGELOG.md | 6 ++++++ packages/system/package.json | 2 +- 11 files changed, 35 insertions(+), 30 deletions(-) delete mode 100644 .changeset/eight-rice-laugh.md delete mode 100644 .changeset/nasty-clouds-shake.md delete mode 100644 .changeset/neat-rules-hammer.md delete mode 100644 .changeset/sour-jokes-argue.md delete mode 100644 .changeset/thirty-goats-invent.md diff --git a/.changeset/eight-rice-laugh.md b/.changeset/eight-rice-laugh.md deleted file mode 100644 index dd528c10..00000000 --- a/.changeset/eight-rice-laugh.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@xyflow/svelte': patch ---- - -Fix showing selection box when no nodes are selected diff --git a/.changeset/nasty-clouds-shake.md b/.changeset/nasty-clouds-shake.md deleted file mode 100644 index 6334f0d2..00000000 --- a/.changeset/nasty-clouds-shake.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@xyflow/svelte': patch ---- - -Remove `edgeId` from `EdgeToolbar` props diff --git a/.changeset/neat-rules-hammer.md b/.changeset/neat-rules-hammer.md deleted file mode 100644 index 2476b38d..00000000 --- a/.changeset/neat-rules-hammer.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@xyflow/svelte': patch ---- - -Pass nodeId to MiniMapNode diff --git a/.changeset/sour-jokes-argue.md b/.changeset/sour-jokes-argue.md deleted file mode 100644 index dca49fd6..00000000 --- a/.changeset/sour-jokes-argue.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@xyflow/react': patch -'@xyflow/svelte': patch -'@xyflow/system': patch ---- - -Fix onPaneClick events being suppressed when selectionOnDrag=true diff --git a/.changeset/thirty-goats-invent.md b/.changeset/thirty-goats-invent.md deleted file mode 100644 index c57c53f5..00000000 --- a/.changeset/thirty-goats-invent.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@xyflow/svelte': patch ---- - -Remove focus outline from selection box diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md index 4423a720..e01bee5b 100644 --- a/packages/react/CHANGELOG.md +++ b/packages/react/CHANGELOG.md @@ -1,5 +1,14 @@ # @xyflow/react +## 12.9.1 + +### Patch Changes + +- [#5572](https://github.com/xyflow/xyflow/pull/5572) [`5ec0cac7f`](https://github.com/xyflow/xyflow/commit/5ec0cac7fad21109b74839969c0818f88ddc87d9) Thanks [@peterkogo](https://github.com/peterkogo)! - Fix onPaneClick events being suppressed when selectionOnDrag=true + +- Updated dependencies [[`5ec0cac7f`](https://github.com/xyflow/xyflow/commit/5ec0cac7fad21109b74839969c0818f88ddc87d9)]: + - @xyflow/system@0.0.72 + ## 12.9.0 ### Minor Changes diff --git a/packages/react/package.json b/packages/react/package.json index 176b735d..b6a9eb82 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@xyflow/react", - "version": "12.9.0", + "version": "12.9.1", "description": "React Flow - A highly customizable React library for building node-based editors and interactive flow charts.", "keywords": [ "react", diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 018bfd8c..fc6877d6 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,22 @@ # @xyflow/svelte +## 1.4.1 + +### Patch Changes + +- [#5572](https://github.com/xyflow/xyflow/pull/5572) [`2a401ed67`](https://github.com/xyflow/xyflow/commit/2a401ed675b85417da46fbca68832c58d0f9bf02) Thanks [@peterkogo](https://github.com/peterkogo)! - Fix showing selection box when no nodes are selected + +- [#5558](https://github.com/xyflow/xyflow/pull/5558) [`dca0a30f2`](https://github.com/xyflow/xyflow/commit/dca0a30f273a05d592d8b89b3f0f30d829f5bbd0) Thanks [@peterkogo](https://github.com/peterkogo)! - Remove `edgeId` from `EdgeToolbar` props + +- [#5565](https://github.com/xyflow/xyflow/pull/5565) [`cbab3d8be`](https://github.com/xyflow/xyflow/commit/cbab3d8be72b4935ccaaa7a1f89011923b66c57b) Thanks [@0x0f0f0f](https://github.com/0x0f0f0f)! - Pass nodeId to MiniMapNode + +- [#5572](https://github.com/xyflow/xyflow/pull/5572) [`5ec0cac7f`](https://github.com/xyflow/xyflow/commit/5ec0cac7fad21109b74839969c0818f88ddc87d9) Thanks [@peterkogo](https://github.com/peterkogo)! - Fix onPaneClick events being suppressed when selectionOnDrag=true + +- [#5572](https://github.com/xyflow/xyflow/pull/5572) [`9dccac89f`](https://github.com/xyflow/xyflow/commit/9dccac89fb85e51f34f96dc3c18f2f9c7ae5a86a) Thanks [@peterkogo](https://github.com/peterkogo)! - Remove focus outline from selection box + +- Updated dependencies [[`5ec0cac7f`](https://github.com/xyflow/xyflow/commit/5ec0cac7fad21109b74839969c0818f88ddc87d9)]: + - @xyflow/system@0.0.72 + ## 1.4.0 ### Minor Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index b1751052..f78663de 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -1,6 +1,6 @@ { "name": "@xyflow/svelte", - "version": "1.4.0", + "version": "1.4.1", "description": "Svelte Flow - A highly customizable Svelte library for building node-based editors, workflow systems, diagrams and more.", "keywords": [ "svelte", diff --git a/packages/system/CHANGELOG.md b/packages/system/CHANGELOG.md index 23f9217f..ea065959 100644 --- a/packages/system/CHANGELOG.md +++ b/packages/system/CHANGELOG.md @@ -1,5 +1,11 @@ # @xyflow/system +## 0.0.72 + +### Patch Changes + +- [#5572](https://github.com/xyflow/xyflow/pull/5572) [`5ec0cac7f`](https://github.com/xyflow/xyflow/commit/5ec0cac7fad21109b74839969c0818f88ddc87d9) Thanks [@peterkogo](https://github.com/peterkogo)! - Fix onPaneClick events being suppressed when selectionOnDrag=true + ## 0.0.71 ### Patch Changes diff --git a/packages/system/package.json b/packages/system/package.json index 5d9c475e..55b2e164 100644 --- a/packages/system/package.json +++ b/packages/system/package.json @@ -1,6 +1,6 @@ { "name": "@xyflow/system", - "version": "0.0.71", + "version": "0.0.72", "description": "xyflow core system that powers React Flow and Svelte Flow.", "keywords": [ "node-based UI", From 11aa29b58759bb492d2277ca8b7d3d9aa4576927 Mon Sep 17 00:00:00 2001 From: peterkogo Date: Wed, 29 Oct 2025 12:11:05 +0100 Subject: [PATCH 139/167] always pass current pointer position to connection --- .../CustomConnectionLine/ConnectionLine.tsx | 3 ++- .../ConnectionLine.svelte | 2 ++ .../src/components/ConnectionLine/index.tsx | 3 ++- packages/react/src/types/edges.ts | 2 ++ packages/system/src/types/general.ts | 4 ++++ packages/system/src/xyhandle/XYHandle.ts | 20 ++----------------- 6 files changed, 14 insertions(+), 20 deletions(-) diff --git a/examples/react/src/examples/CustomConnectionLine/ConnectionLine.tsx b/examples/react/src/examples/CustomConnectionLine/ConnectionLine.tsx index ddba960b..bf6a82d7 100644 --- a/examples/react/src/examples/CustomConnectionLine/ConnectionLine.tsx +++ b/examples/react/src/examples/CustomConnectionLine/ConnectionLine.tsx @@ -1,6 +1,7 @@ import { ConnectionLineComponentProps } from '@xyflow/react'; -function ConnectionLine({ fromX, fromY, toX, toY }: ConnectionLineComponentProps) { +function ConnectionLine({ fromX, fromY, toX, toY, pointer }: ConnectionLineComponentProps) { + console.log('pointer', pointer); return ( <> {#if connection.current.inProgress} diff --git a/packages/react/src/components/ConnectionLine/index.tsx b/packages/react/src/components/ConnectionLine/index.tsx index c179fae2..29463fa4 100644 --- a/packages/react/src/components/ConnectionLine/index.tsx +++ b/packages/react/src/components/ConnectionLine/index.tsx @@ -69,7 +69,7 @@ const ConnectionLine = ({ CustomComponent, isValid, }: ConnectionLineProps) => { - const { inProgress, from, fromNode, fromHandle, fromPosition, to, toNode, toHandle, toPosition } = + const { inProgress, from, fromNode, fromHandle, fromPosition, to, toNode, toHandle, toPosition, pointer } = useConnection(); if (!inProgress) { @@ -92,6 +92,7 @@ const ConnectionLine = ({ connectionStatus={getConnectionStatus(isValid)} toNode={toNode} toHandle={toHandle} + pointer={pointer} /> ); } diff --git a/packages/react/src/types/edges.ts b/packages/react/src/types/edges.ts index dee2650e..9e563b76 100644 --- a/packages/react/src/types/edges.ts +++ b/packages/react/src/types/edges.ts @@ -22,6 +22,7 @@ import type { OnError, OnReconnect, FinalConnectionState, + XYPosition, } from '@xyflow/system'; import { EdgeTypes, InternalNode, Node } from '.'; @@ -279,6 +280,7 @@ export type ConnectionLineComponentProps = { connectionStatus: 'valid' | 'invalid' | null; toNode: InternalNode | null; toHandle: Handle | null; + pointer: XYPosition; }; export type ConnectionLineComponent = ComponentType< diff --git a/packages/system/src/types/general.ts b/packages/system/src/types/general.ts index 15f64262..d43c50ee 100644 --- a/packages/system/src/types/general.ts +++ b/packages/system/src/types/general.ts @@ -292,6 +292,7 @@ export const initialConnection: NoConnection = { toHandle: null, toPosition: null, toNode: null, + pointer: null, }; export type NoConnection = { @@ -305,6 +306,7 @@ export type NoConnection = { toHandle: null; toPosition: null; toNode: null; + pointer: null; }; export type ConnectionInProgress = { /** Indicates whether a connection is currently in progress. */ @@ -330,6 +332,8 @@ export type ConnectionInProgress Date: Wed, 29 Oct 2025 12:13:21 +0100 Subject: [PATCH 140/167] chore(changeset) --- .changeset/sharp-toys-cheer.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/sharp-toys-cheer.md diff --git a/.changeset/sharp-toys-cheer.md b/.changeset/sharp-toys-cheer.md new file mode 100644 index 00000000..885eebcc --- /dev/null +++ b/.changeset/sharp-toys-cheer.md @@ -0,0 +1,7 @@ +--- +'@xyflow/react': minor +'@xyflow/svelte': minor +'@xyflow/system': patch +--- + +Pass current pointer position to connection From f739c7fae129e143acf3765c0e768000f04c2b8e Mon Sep 17 00:00:00 2001 From: Svilen Ivanov <92510182+svilen-ivanov-kubit@users.noreply.github.com> Date: Thu, 30 Oct 2025 16:04:40 +0200 Subject: [PATCH 141/167] Avoid modifying node.measured object, create copy instead When working with immutable `node` data, modifying in-place `node.measured` properties is not allowed. Since we perform a shallow copy, we need to create a new `measured` object to populate `width` and `height` --- packages/react/src/utils/changes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react/src/utils/changes.ts b/packages/react/src/utils/changes.ts index f2d9bdd3..422832f2 100644 --- a/packages/react/src/utils/changes.ts +++ b/packages/react/src/utils/changes.ts @@ -125,7 +125,7 @@ function applyChange(change: any, element: any): any { case 'dimensions': { if (typeof change.dimensions !== 'undefined') { - element.measured ??= {}; + element.measured = {}; element.measured.width = change.dimensions.width; element.measured.height = change.dimensions.height; From 0d2cda7dff46faa373c918ebe5a0db85ac75cbba Mon Sep 17 00:00:00 2001 From: peterkogo Date: Thu, 30 Oct 2025 12:42:55 +0100 Subject: [PATCH 142/167] also postpone starting selection when clicking on a node --- packages/react/src/container/Pane/index.tsx | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/packages/react/src/container/Pane/index.tsx b/packages/react/src/container/Pane/index.tsx index 94f23c35..43c55383 100644 --- a/packages/react/src/container/Pane/index.tsx +++ b/packages/react/src/container/Pane/index.tsx @@ -121,7 +121,7 @@ export function Pane({ // We are using capture here in order to prevent other pointer events // to be able to create a selection above a node or an edge const onPointerDownCapture = (event: ReactPointerEvent): void => { - const { resetSelectedElements, domNode } = store.getState(); + const { domNode } = store.getState(); containerBounds.current = domNode?.getBoundingClientRect(); if (!containerBounds.current) return; @@ -155,13 +155,6 @@ export function Pane({ event.stopPropagation(); event.preventDefault(); } - - if (paneClickDistance === 0 || selectionKeyPressed) { - resetSelectedElements(); - - onSelectionStart?.(event); - selectionInProgress.current = true; - } }; const onPointerMove = (event: ReactPointerEvent): void => { @@ -184,14 +177,10 @@ export function Pane({ const { x: mouseX, y: mouseY } = getEventPosition(event.nativeEvent, containerBounds.current); const { startX, startY } = userSelectionRect; - if ( - !selectionInProgress.current && - event.target === container.current && - !selectionKeyPressed && - paneClickDistance > 0 - ) { + if (!selectionInProgress.current) { + const requiredDistance = selectionKeyPressed ? 0 : paneClickDistance; const distance = Math.hypot(mouseX - startX, mouseY - startY); - if (distance <= paneClickDistance) { + if (distance <= requiredDistance) { return; } resetSelectedElements(); From 07fbc7e34ad858cb7d96b48af8af44b5e222d6aa Mon Sep 17 00:00:00 2001 From: peterkogo Date: Thu, 30 Oct 2025 12:53:40 +0100 Subject: [PATCH 143/167] also postpone starting selection when clicking on a node --- .../src/routes/examples/figma/+page.svelte | 1 + .../svelte/src/lib/container/Pane/Pane.svelte | 17 +++-------------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/examples/svelte/src/routes/examples/figma/+page.svelte b/examples/svelte/src/routes/examples/figma/+page.svelte index 9c448353..33de11ea 100644 --- a/examples/svelte/src/routes/examples/figma/+page.svelte +++ b/examples/svelte/src/routes/examples/figma/+page.svelte @@ -39,6 +39,7 @@ bind:edges fitView selectionMode={SelectionMode.Partial} + multiSelectionKey={['Meta', 'Shift']} selectionOnDrag panOnScroll paneClickDistance={100} diff --git a/packages/svelte/src/lib/container/Pane/Pane.svelte b/packages/svelte/src/lib/container/Pane/Pane.svelte index d2fdec9d..b7de8cfa 100644 --- a/packages/svelte/src/lib/container/Pane/Pane.svelte +++ b/packages/svelte/src/lib/container/Pane/Pane.svelte @@ -120,13 +120,6 @@ event.stopPropagation(); event.preventDefault(); } - - if (paneClickDistance === 0 || store.selectionKeyPressed) { - store.unselectNodesAndEdges(); - - onselectionstart?.(event); - selectionInProgress = true; - } } function onPointerMove(event: PointerEvent) { @@ -137,14 +130,10 @@ const mousePos = getEventPosition(event, containerBounds); const { startX = 0, startY = 0 } = store.selectionRect; - if ( - !selectionInProgress && - event.target === container && - !store.selectionKeyPressed && - paneClickDistance > 0 - ) { + if (!selectionInProgress) { + const requiredDistance = store.selectionKeyPressed ? 0 : paneClickDistance; const distance = Math.hypot(mousePos.x - startX, mousePos.y - startY); - if (distance <= paneClickDistance) { + if (distance <= requiredDistance) { return; } store.unselectNodesAndEdges(); From b298819062a2c1a8927333133a7055eee8d3c2b4 Mon Sep 17 00:00:00 2001 From: moklick Date: Thu, 30 Oct 2025 14:01:46 +0100 Subject: [PATCH 144/167] chore(pane): handle nodes selection --- packages/react/src/container/Pane/index.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/react/src/container/Pane/index.tsx b/packages/react/src/container/Pane/index.tsx index 43c55383..32a0e6df 100644 --- a/packages/react/src/container/Pane/index.tsx +++ b/packages/react/src/container/Pane/index.tsx @@ -257,11 +257,14 @@ export function Pane({ store.setState({ userSelectionActive: false, userSelectionRect: null, - nodesSelectionActive: selectedNodeIds.current.size > 0, }); if (selectionInProgress.current) { onSelectionEnd?.(event); + + store.setState({ + nodesSelectionActive: selectedNodeIds.current.size > 0, + }); } }; From a8ee089d7689d9a58113690c8e90e1c1e109602a Mon Sep 17 00:00:00 2001 From: moklick Date: Thu, 30 Oct 2025 15:23:56 +0100 Subject: [PATCH 145/167] chore(changesets): add --- .changeset/red-cycles-wonder.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/red-cycles-wonder.md diff --git a/.changeset/red-cycles-wonder.md b/.changeset/red-cycles-wonder.md new file mode 100644 index 00000000..db7dfd20 --- /dev/null +++ b/.changeset/red-cycles-wonder.md @@ -0,0 +1,6 @@ +--- +'@xyflow/react': patch +'@xyflow/svelte': patch +--- + +Reset selection box when user selects a node From a343807361e2a38f36818942fb0980f349bbe768 Mon Sep 17 00:00:00 2001 From: Moritz Klack Date: Thu, 30 Oct 2025 15:24:47 +0100 Subject: [PATCH 146/167] Update changeset for selection box reset Reset the selection box when a user selects a node. --- .changeset/red-cycles-wonder.md | 1 - 1 file changed, 1 deletion(-) diff --git a/.changeset/red-cycles-wonder.md b/.changeset/red-cycles-wonder.md index db7dfd20..6241d126 100644 --- a/.changeset/red-cycles-wonder.md +++ b/.changeset/red-cycles-wonder.md @@ -1,6 +1,5 @@ --- '@xyflow/react': patch -'@xyflow/svelte': patch --- Reset selection box when user selects a node From a52ccc25201bb22f1a7d65a1c4b4e385e15a6f29 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 30 Oct 2025 14:30:05 +0000 Subject: [PATCH 147/167] chore(packages): bump --- .changeset/red-cycles-wonder.md | 5 ----- packages/react/CHANGELOG.md | 6 ++++++ packages/react/package.json | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) delete mode 100644 .changeset/red-cycles-wonder.md diff --git a/.changeset/red-cycles-wonder.md b/.changeset/red-cycles-wonder.md deleted file mode 100644 index 6241d126..00000000 --- a/.changeset/red-cycles-wonder.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@xyflow/react': patch ---- - -Reset selection box when user selects a node diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md index e01bee5b..700256b5 100644 --- a/packages/react/CHANGELOG.md +++ b/packages/react/CHANGELOG.md @@ -1,5 +1,11 @@ # @xyflow/react +## 12.9.2 + +### Patch Changes + +- [#5593](https://github.com/xyflow/xyflow/pull/5593) [`a8ee089d7`](https://github.com/xyflow/xyflow/commit/a8ee089d7689d9a58113690c8e90e1c1e109602a) Thanks [@moklick](https://github.com/moklick)! - Reset selection box when user selects a node + ## 12.9.1 ### Patch Changes diff --git a/packages/react/package.json b/packages/react/package.json index b6a9eb82..18074467 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@xyflow/react", - "version": "12.9.1", + "version": "12.9.2", "description": "React Flow - A highly customizable React library for building node-based editors and interactive flow charts.", "keywords": [ "react", From 8b73e4ac2cec6779cb88889ff437f41d246e0236 Mon Sep 17 00:00:00 2001 From: peterkogo Date: Mon, 3 Nov 2025 12:41:32 +0100 Subject: [PATCH 148/167] change selectionOnDrag default to false --- packages/svelte/src/lib/container/SvelteFlow/SvelteFlow.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte/src/lib/container/SvelteFlow/SvelteFlow.svelte b/packages/svelte/src/lib/container/SvelteFlow/SvelteFlow.svelte index d92ff2d6..ab9378b8 100644 --- a/packages/svelte/src/lib/container/SvelteFlow/SvelteFlow.svelte +++ b/packages/svelte/src/lib/container/SvelteFlow/SvelteFlow.svelte @@ -61,7 +61,7 @@ panOnScroll = false, panOnScrollSpeed = 0.5, panOnDrag = true, - selectionOnDrag = true, + selectionOnDrag = false, connectionLineComponent, connectionLineStyle, connectionLineContainerStyle, From 17a175791b2420b32d55a8fcbbb35649862b85cf Mon Sep 17 00:00:00 2001 From: peterkogo Date: Mon, 3 Nov 2025 12:42:18 +0100 Subject: [PATCH 149/167] chore(changeset) --- .changeset/dirty-readers-hide.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/dirty-readers-hide.md diff --git a/.changeset/dirty-readers-hide.md b/.changeset/dirty-readers-hide.md new file mode 100644 index 00000000..2ee6e044 --- /dev/null +++ b/.changeset/dirty-readers-hide.md @@ -0,0 +1,5 @@ +--- +'@xyflow/svelte': patch +--- + +Fix onPaneClick always firing when moving the viewport From 39e4589d5e00ccc0e0fa3ee738c508d8392c25de Mon Sep 17 00:00:00 2001 From: peterkogo Date: Tue, 11 Nov 2025 14:06:40 +0100 Subject: [PATCH 150/167] fix node resizer change position not parsed correctly --- .../lib/plugins/NodeResizer/ResizeControl.svelte | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/packages/svelte/src/lib/plugins/NodeResizer/ResizeControl.svelte b/packages/svelte/src/lib/plugins/NodeResizer/ResizeControl.svelte index 8c6874e9..dc6eb47a 100644 --- a/packages/svelte/src/lib/plugins/NodeResizer/ResizeControl.svelte +++ b/packages/svelte/src/lib/plugins/NodeResizer/ResizeControl.svelte @@ -68,14 +68,11 @@ }, onChange: (change: XYResizerChange, childChanges: XYResizerChildChange[]) => { // eslint-disable-next-line svelte/prefer-svelte-reactivity - const changes = new Map>(); - let position = change.x && change.y ? { x: change.x, y: change.y } : undefined; - changes.set(id, { ...change, position }); + const changes = new Map(); + changes.set(id, change); for (const childChange of childChanges) { - changes.set(childChange.id, { - position: childChange.position - }); + changes.set(childChange.id, {x: childChange.position.x, y: childChange.position.y }); } store.nodes = store.nodes.map((node) => { @@ -84,11 +81,11 @@ const vertical = !resizeDirection || resizeDirection === 'vertical'; if (change) { - return { + return { ...node, position: { - x: horizontal ? (change.position?.x ?? node.position.x) : node.position.x, - y: vertical ? (change.position?.y ?? node.position.y) : node.position.y + x: horizontal ? (change.x ?? node.position.x) : node.position.x, + y: vertical ? (change.y ?? node.position.y) : node.position.y }, width: horizontal ? (change.width ?? node.width) : node.width, height: vertical ? (change.height ?? node.height) : node.height From 8b35c77ebe5a4f6cba33c597d94eba1ead64fdfc Mon Sep 17 00:00:00 2001 From: peterkogo Date: Tue, 11 Nov 2025 15:45:32 +0100 Subject: [PATCH 151/167] chore(changeset) --- .changeset/plenty-lobsters-thank.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/plenty-lobsters-thank.md diff --git a/.changeset/plenty-lobsters-thank.md b/.changeset/plenty-lobsters-thank.md new file mode 100644 index 00000000..49779920 --- /dev/null +++ b/.changeset/plenty-lobsters-thank.md @@ -0,0 +1,5 @@ +--- +'@xyflow/svelte': patch +--- + +Fix wrong positions when resizing nodes with a non-default node-origin From 8598b6bc2a9d052b12d5215706382da0aa84827b Mon Sep 17 00:00:00 2001 From: Adam Krebs Date: Tue, 11 Nov 2025 18:53:21 -0500 Subject: [PATCH 152/167] Allow getEdgeId to be overridden --- .changeset/custom-edge-id-generator.md | 5 +++ packages/system/src/utils/edges/general.ts | 37 +++++++++++++++++++--- 2 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 .changeset/custom-edge-id-generator.md diff --git a/.changeset/custom-edge-id-generator.md b/.changeset/custom-edge-id-generator.md new file mode 100644 index 00000000..65947b75 --- /dev/null +++ b/.changeset/custom-edge-id-generator.md @@ -0,0 +1,5 @@ +--- +'@xyflow/system': minor +--- + +Allow custom `getEdgeId` function in `addEdge` and `reconnectEdge`. You can now pass a custom edge ID generator via the options parameter, enabling custom edge ID schemes. Closes #5180. diff --git a/packages/system/src/utils/edges/general.ts b/packages/system/src/utils/edges/general.ts index 58139394..515a285a 100644 --- a/packages/system/src/utils/edges/general.ts +++ b/packages/system/src/utils/edges/general.ts @@ -84,7 +84,19 @@ export function isEdgeVisible({ sourceNode, targetNode, width, height, transform return getOverlappingArea(viewRect, boxToRect(edgeBox)) > 0; } -const getEdgeId = ({ source, sourceHandle, target, targetHandle }: Connection | EdgeBase): string => +/** + * Type for a custom edge ID generator function. + * @public + */ +export type GetEdgeId = (params: Connection | EdgeBase) => string; + +/** + * The default edge ID generator function. Generates an ID based on the source, target, and handles. + * @public + * @param params - The connection or edge to generate an ID for. + * @returns The generated edge ID. + */ +export const getEdgeId = ({ source, sourceHandle, target, targetHandle }: Connection | EdgeBase): string => `xy-edge__${source}${sourceHandle || ''}-${target}${targetHandle || ''}`; const connectionExists = (edge: EdgeBase, edges: EdgeBase[]) => { @@ -97,11 +109,19 @@ const connectionExists = (edge: EdgeBase, edges: EdgeBase[]) => { ); }; +export type AddEdgeOptions = { + /** + * Custom function to generate edge IDs. If not provided, the default `getEdgeId` function is used. + */ + getEdgeId?: GetEdgeId; +}; + /** * This util is a convenience function to add a new Edge to an array of edges. It also performs some validation to make sure you don't add an invalid edge or duplicate an existing one. * @public * @param edgeParams - Either an `Edge` or a `Connection` you want to add. * @param edges - The array of all current edges. + * @param options - Optional configuration object. * @returns A new array of edges with the new edge added. * * @remarks If an edge with the same `target` and `source` already exists (and the same @@ -111,7 +131,8 @@ const connectionExists = (edge: EdgeBase, edges: EdgeBase[]) => { */ export const addEdge = ( edgeParams: EdgeType | Connection, - edges: EdgeType[] + edges: EdgeType[], + options: AddEdgeOptions = {} ): EdgeType[] => { if (!edgeParams.source || !edgeParams.target) { devWarn('006', errorMessages['error006']()); @@ -119,13 +140,15 @@ export const addEdge = ( return edges; } + const edgeIdGenerator = options.getEdgeId || getEdgeId; + let edge: EdgeType; if (isEdgeBase(edgeParams)) { edge = { ...edgeParams }; } else { edge = { ...edgeParams, - id: getEdgeId(edgeParams), + id: edgeIdGenerator(edgeParams), } as EdgeType; } @@ -150,6 +173,10 @@ export type ReconnectEdgeOptions = { * @default true */ shouldReplaceId?: boolean; + /** + * Custom function to generate edge IDs. If not provided, the default `getEdgeId` function is used. + */ + getEdgeId?: GetEdgeId; }; /** @@ -190,10 +217,12 @@ export const reconnectEdge = ( return edges; } + const edgeIdGenerator = options.getEdgeId || getEdgeId; + // Remove old edge and create the new edge with parameters of old edge. const edge = { ...rest, - id: options.shouldReplaceId ? getEdgeId(newConnection) : oldEdgeId, + id: options.shouldReplaceId ? edgeIdGenerator(newConnection) : oldEdgeId, source: newConnection.source, target: newConnection.target, sourceHandle: newConnection.sourceHandle, From cb2db9f2d223944d7710a0fc17cd9954be77f2c0 Mon Sep 17 00:00:00 2001 From: moklick Date: Wed, 12 Nov 2025 15:50:20 +0100 Subject: [PATCH 153/167] chore(reactflow): paneClickDistance default 1 --- packages/react/src/container/ReactFlow/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react/src/container/ReactFlow/index.tsx b/packages/react/src/container/ReactFlow/index.tsx index bd20242f..f01af831 100644 --- a/packages/react/src/container/ReactFlow/index.tsx +++ b/packages/react/src/container/ReactFlow/index.tsx @@ -104,7 +104,7 @@ function ReactFlow( onPaneMouseLeave, onPaneScroll, onPaneContextMenu, - paneClickDistance = 0, + paneClickDistance = 1, nodeClickDistance = 0, children, onReconnect, From c1304dba7a20bb8d74c7aceb23cd80b56e4c0482 Mon Sep 17 00:00:00 2001 From: moklick Date: Wed, 12 Nov 2025 15:51:03 +0100 Subject: [PATCH 154/167] chore(changesets): add --- .changeset/serious-peaches-fry.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/serious-peaches-fry.md diff --git a/.changeset/serious-peaches-fry.md b/.changeset/serious-peaches-fry.md new file mode 100644 index 00000000..2978ec99 --- /dev/null +++ b/.changeset/serious-peaches-fry.md @@ -0,0 +1,5 @@ +--- +'@xyflow/react': patch +--- + +Set `paneClickDistance` default value to `1`. From 26940018a805033bd6c0a46409616f4ae1b19a39 Mon Sep 17 00:00:00 2001 From: moklick Date: Thu, 13 Nov 2025 09:52:18 +0100 Subject: [PATCH 155/167] chore(changesets): update --- .changeset/sharp-toys-cheer.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/sharp-toys-cheer.md b/.changeset/sharp-toys-cheer.md index 885eebcc..36bbcdfc 100644 --- a/.changeset/sharp-toys-cheer.md +++ b/.changeset/sharp-toys-cheer.md @@ -1,6 +1,6 @@ --- -'@xyflow/react': minor -'@xyflow/svelte': minor +'@xyflow/react': patch +'@xyflow/svelte': patch '@xyflow/system': patch --- From 12c516d5f2c7b99849733fab031be02f788657fe Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 13 Nov 2025 08:53:39 +0000 Subject: [PATCH 156/167] chore(packages): bump --- .changeset/dirty-readers-hide.md | 5 ----- .changeset/plenty-lobsters-thank.md | 5 ----- .changeset/serious-peaches-fry.md | 5 ----- .changeset/sharp-toys-cheer.md | 7 ------- packages/react/CHANGELOG.md | 11 +++++++++++ packages/react/package.json | 2 +- packages/svelte/CHANGELOG.md | 13 +++++++++++++ packages/svelte/package.json | 2 +- packages/system/CHANGELOG.md | 6 ++++++ packages/system/package.json | 2 +- 10 files changed, 33 insertions(+), 25 deletions(-) delete mode 100644 .changeset/dirty-readers-hide.md delete mode 100644 .changeset/plenty-lobsters-thank.md delete mode 100644 .changeset/serious-peaches-fry.md delete mode 100644 .changeset/sharp-toys-cheer.md diff --git a/.changeset/dirty-readers-hide.md b/.changeset/dirty-readers-hide.md deleted file mode 100644 index 2ee6e044..00000000 --- a/.changeset/dirty-readers-hide.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@xyflow/svelte': patch ---- - -Fix onPaneClick always firing when moving the viewport diff --git a/.changeset/plenty-lobsters-thank.md b/.changeset/plenty-lobsters-thank.md deleted file mode 100644 index 49779920..00000000 --- a/.changeset/plenty-lobsters-thank.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@xyflow/svelte': patch ---- - -Fix wrong positions when resizing nodes with a non-default node-origin diff --git a/.changeset/serious-peaches-fry.md b/.changeset/serious-peaches-fry.md deleted file mode 100644 index 2978ec99..00000000 --- a/.changeset/serious-peaches-fry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@xyflow/react': patch ---- - -Set `paneClickDistance` default value to `1`. diff --git a/.changeset/sharp-toys-cheer.md b/.changeset/sharp-toys-cheer.md deleted file mode 100644 index 36bbcdfc..00000000 --- a/.changeset/sharp-toys-cheer.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@xyflow/react': patch -'@xyflow/svelte': patch -'@xyflow/system': patch ---- - -Pass current pointer position to connection diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md index 700256b5..9197dd9f 100644 --- a/packages/react/CHANGELOG.md +++ b/packages/react/CHANGELOG.md @@ -1,5 +1,16 @@ # @xyflow/react +## 12.9.3 + +### Patch Changes + +- [#5621](https://github.com/xyflow/xyflow/pull/5621) [`c1304dba7`](https://github.com/xyflow/xyflow/commit/c1304dba7a20bb8d74c7aceb23cd80b56e4c0482) Thanks [@moklick](https://github.com/moklick)! - Set `paneClickDistance` default value to `1`. + +- [#5578](https://github.com/xyflow/xyflow/pull/5578) [`00bcb9f5f`](https://github.com/xyflow/xyflow/commit/00bcb9f5f45f49814b9ac19b3f55cfe069ee3773) Thanks [@peterkogo](https://github.com/peterkogo)! - Pass current pointer position to connection + +- Updated dependencies [[`00bcb9f5f`](https://github.com/xyflow/xyflow/commit/00bcb9f5f45f49814b9ac19b3f55cfe069ee3773)]: + - @xyflow/system@0.0.73 + ## 12.9.2 ### Patch Changes diff --git a/packages/react/package.json b/packages/react/package.json index 18074467..0514e3b4 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@xyflow/react", - "version": "12.9.2", + "version": "12.9.3", "description": "React Flow - A highly customizable React library for building node-based editors and interactive flow charts.", "keywords": [ "react", diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index fc6877d6..5cfe8b02 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,18 @@ # @xyflow/svelte +## 1.4.2 + +### Patch Changes + +- [#5603](https://github.com/xyflow/xyflow/pull/5603) [`17a175791`](https://github.com/xyflow/xyflow/commit/17a175791b2420b32d55a8fcbbb35649862b85cf) Thanks [@peterkogo](https://github.com/peterkogo)! - Fix onPaneClick always firing when moving the viewport + +- [#5615](https://github.com/xyflow/xyflow/pull/5615) [`8b35c77eb`](https://github.com/xyflow/xyflow/commit/8b35c77ebe5a4f6cba33c597d94eba1ead64fdfc) Thanks [@peterkogo](https://github.com/peterkogo)! - Fix wrong positions when resizing nodes with a non-default node-origin + +- [#5578](https://github.com/xyflow/xyflow/pull/5578) [`00bcb9f5f`](https://github.com/xyflow/xyflow/commit/00bcb9f5f45f49814b9ac19b3f55cfe069ee3773) Thanks [@peterkogo](https://github.com/peterkogo)! - Pass current pointer position to connection + +- Updated dependencies [[`00bcb9f5f`](https://github.com/xyflow/xyflow/commit/00bcb9f5f45f49814b9ac19b3f55cfe069ee3773)]: + - @xyflow/system@0.0.73 + ## 1.4.1 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index f78663de..c2dd3ba6 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -1,6 +1,6 @@ { "name": "@xyflow/svelte", - "version": "1.4.1", + "version": "1.4.2", "description": "Svelte Flow - A highly customizable Svelte library for building node-based editors, workflow systems, diagrams and more.", "keywords": [ "svelte", diff --git a/packages/system/CHANGELOG.md b/packages/system/CHANGELOG.md index ea065959..5bbc621e 100644 --- a/packages/system/CHANGELOG.md +++ b/packages/system/CHANGELOG.md @@ -1,5 +1,11 @@ # @xyflow/system +## 0.0.73 + +### Patch Changes + +- [#5578](https://github.com/xyflow/xyflow/pull/5578) [`00bcb9f5f`](https://github.com/xyflow/xyflow/commit/00bcb9f5f45f49814b9ac19b3f55cfe069ee3773) Thanks [@peterkogo](https://github.com/peterkogo)! - Pass current pointer position to connection + ## 0.0.72 ### Patch Changes diff --git a/packages/system/package.json b/packages/system/package.json index 55b2e164..6ee8e327 100644 --- a/packages/system/package.json +++ b/packages/system/package.json @@ -1,6 +1,6 @@ { "name": "@xyflow/system", - "version": "0.0.72", + "version": "0.0.73", "description": "xyflow core system that powers React Flow and Svelte Flow.", "keywords": [ "node-based UI", From 5161cf327991d690b69700ff506c9b8a27841bc4 Mon Sep 17 00:00:00 2001 From: Alaric Baraou Date: Mon, 17 Nov 2025 01:10:00 +0900 Subject: [PATCH 157/167] prevent re-render on every store update by using shallow in FlowRendererComponent useStore --- packages/react/src/container/FlowRenderer/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/react/src/container/FlowRenderer/index.tsx b/packages/react/src/container/FlowRenderer/index.tsx index d5083d80..5ab094ea 100644 --- a/packages/react/src/container/FlowRenderer/index.tsx +++ b/packages/react/src/container/FlowRenderer/index.tsx @@ -8,6 +8,7 @@ import { ZoomPane } from '../ZoomPane'; import { Pane } from '../Pane'; import { NodesSelection } from '../../components/NodesSelection'; import type { ReactFlowState, Node } from '../../types'; +import { shallow } from 'zustand/shallow'; export type FlowRendererProps = Omit< GraphViewProps, @@ -72,7 +73,7 @@ function FlowRendererComponent({ onViewportChange, isControlledViewport, }: FlowRendererProps) { - const { nodesSelectionActive, userSelectionActive } = useStore(selector); + const { nodesSelectionActive, userSelectionActive } = useStore(selector, shallow); const selectionKeyPressed = useKeyPress(selectionKeyCode, { target: win }); const panActivationKeyPressed = useKeyPress(panActivationKeyCode, { target: win }); From fb4d081364b8c6d00bf09ed6dca008f4b40c7e35 Mon Sep 17 00:00:00 2001 From: Stephan Eggermont Date: Sun, 16 Nov 2025 19:31:47 +0100 Subject: [PATCH 158/167] Typo README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3ed80c4b..116e99c7 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,7 @@ For releasing packages we are using [changesets](https://github.com/changesets/c 1. create PRs for new features, updates and fixes (with a changeset if relevant for changelog) 2. merge into main -3. changset creates a PR that bumps all packages based on the changesets +3. changeset creates a PR that bumps all packages based on the changesets 4. merge changeset PR if you want to release to Github and npm ## Built by [xyflow](https://xyflow.com) From 9030fab2df8285fdfb649bda6e1e885dfd228d45 Mon Sep 17 00:00:00 2001 From: moklick Date: Mon, 17 Nov 2025 10:08:43 +0100 Subject: [PATCH 159/167] chore(changesets): add --- .changeset/curly-snails-do.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/curly-snails-do.md diff --git a/.changeset/curly-snails-do.md b/.changeset/curly-snails-do.md new file mode 100644 index 00000000..5560b33b --- /dev/null +++ b/.changeset/curly-snails-do.md @@ -0,0 +1,5 @@ +--- +'@xyflow/react': patch +--- + +Prevent unnecessary re-render in `FlowRenderer` From 0102be3babc11ed1a5fdb883393bfdaa9bfbef45 Mon Sep 17 00:00:00 2001 From: moklick Date: Mon, 17 Nov 2025 10:15:42 +0100 Subject: [PATCH 160/167] chore(flow-renderer): cleanup imports --- packages/react/src/container/FlowRenderer/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react/src/container/FlowRenderer/index.tsx b/packages/react/src/container/FlowRenderer/index.tsx index 5ab094ea..2198ce5e 100644 --- a/packages/react/src/container/FlowRenderer/index.tsx +++ b/packages/react/src/container/FlowRenderer/index.tsx @@ -1,4 +1,5 @@ import { memo, type ReactNode } from 'react'; +import { shallow } from 'zustand/shallow'; import { useStore } from '../../hooks/useStore'; import { useGlobalKeyHandler } from '../../hooks/useGlobalKeyHandler'; @@ -8,7 +9,6 @@ import { ZoomPane } from '../ZoomPane'; import { Pane } from '../Pane'; import { NodesSelection } from '../../components/NodesSelection'; import type { ReactFlowState, Node } from '../../types'; -import { shallow } from 'zustand/shallow'; export type FlowRendererProps = Omit< GraphViewProps, From 99453f0993fdaceed3888ecb2e334bcb0e14eb2f Mon Sep 17 00:00:00 2001 From: Tornado Softwares Date: Tue, 18 Nov 2025 16:24:11 +0100 Subject: [PATCH 161/167] Fix #5633 --- packages/react/src/store/index.ts | 15 +++++++++++---- packages/svelte/src/lib/store/index.ts | 13 +++++++++++-- packages/system/src/xyhandle/XYHandle.ts | 7 +++++-- 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/packages/react/src/store/index.ts b/packages/react/src/store/index.ts index 3254bdfa..baef953c 100644 --- a/packages/react/src/store/index.ts +++ b/packages/react/src/store/index.ts @@ -14,6 +14,8 @@ import { NodeOrigin, CoordinateExtent, fitViewport, + getHandlePosition, + Position, } from '@xyflow/system'; import { applyEdgeChanges, applyNodeChanges, createSelectionChange, getSelectionChanges } from '../utils/changes'; @@ -176,7 +178,7 @@ const createStore = ({ updateNodePositions: (nodeDragItems, dragging = false) => { const parentExpandChildren: ParentExpandChild[] = []; const changes = []; - const { nodeLookup, triggerNodeChanges } = get(); + const { nodeLookup, triggerNodeChanges, connection, updateConnection } = get(); for (const [id, dragItem] of nodeDragItems) { // we are using the nodelookup to be sure to use the current expandParent and parentId value @@ -188,13 +190,18 @@ const createStore = ({ type: 'position', position: expandParent ? { - x: Math.max(0, dragItem.position.x), - y: Math.max(0, dragItem.position.y), - } + x: Math.max(0, dragItem.position.x), + y: Math.max(0, dragItem.position.y), + } : dragItem.position, dragging, }; + if (node && connection.inProgress && connection.fromNode.id == node.id) { + const updatedFrom = getHandlePosition(node, connection.fromHandle, Position.Left, true); + updateConnection({...connection,from: updatedFrom}) + } + if (expandParent && node.parentId) { parentExpandChildren.push({ id, diff --git a/packages/svelte/src/lib/store/index.ts b/packages/svelte/src/lib/store/index.ts index 798e87d8..4619a27e 100644 --- a/packages/svelte/src/lib/store/index.ts +++ b/packages/svelte/src/lib/store/index.ts @@ -15,7 +15,9 @@ import { updateAbsolutePositions, snapPosition, calculateNodePosition, - type SetCenterOptions + type SetCenterOptions, + getHandlePosition, + Position } from '@xyflow/system'; import type { EdgeTypes, NodeTypes, Node, Edge, FitViewOptions } from '$lib/types'; @@ -50,7 +52,14 @@ export function createStore { - store.nodes = store.nodes.map((node) => { + store.nodes = store.nodes.map((node) => { + if (store.connection.inProgress && store.connection.fromNode.id === node.id) { + + store.connection = { + ...store.connection, + from: getHandlePosition(store.nodeLookup.get(node.id), store.connection.fromHandle, Position.Left, true) + }; + } const dragItem = nodeDragItems.get(node.id); return dragItem ? { ...node, position: dragItem.position, dragging } : node; }); diff --git a/packages/system/src/xyhandle/XYHandle.ts b/packages/system/src/xyhandle/XYHandle.ts index 2e714e8b..40d04dff 100644 --- a/packages/system/src/xyhandle/XYHandle.ts +++ b/packages/system/src/xyhandle/XYHandle.ts @@ -171,10 +171,13 @@ function onPointerDown( resultHandleDomNode = result.handleDomNode; connection = result.connection; isValid = isConnectionValid(!!closestHandle, result.isValid); + + const fromNodeInternal = nodeLookup.get(nodeId)!; + const from = getHandlePosition(fromNodeInternal, fromHandle, Position.Left, true); - const newConnection: ConnectionInProgress = { - // from stays the same + const newConnection: ConnectionInProgress = { ...previousConnection, + from, isValid, to: result.toHandle && isValid From 9f7884b1bfd43dae1da81bf30af8eddad226d51e Mon Sep 17 00:00:00 2001 From: Moritz Klack Date: Tue, 25 Nov 2025 10:42:15 +0100 Subject: [PATCH 162/167] Update custom-edge-id-generator.md --- .changeset/custom-edge-id-generator.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/custom-edge-id-generator.md b/.changeset/custom-edge-id-generator.md index 65947b75..9b2e1bb8 100644 --- a/.changeset/custom-edge-id-generator.md +++ b/.changeset/custom-edge-id-generator.md @@ -2,4 +2,4 @@ '@xyflow/system': minor --- -Allow custom `getEdgeId` function in `addEdge` and `reconnectEdge`. You can now pass a custom edge ID generator via the options parameter, enabling custom edge ID schemes. Closes #5180. +Allow custom `getEdgeId` function in `addEdge` and `reconnectEdge` options to enable custom edge ID schemes. From 487c3470f997fd405fea1cc55013a3c88bf7e116 Mon Sep 17 00:00:00 2001 From: Moritz Klack Date: Tue, 25 Nov 2025 10:42:49 +0100 Subject: [PATCH 163/167] Change version from minor to patch for custom edge ID Allow custom `getEdgeId` function in `addEdge` and `reconnectEdge` options to enable custom edge ID schemes. --- .changeset/custom-edge-id-generator.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/custom-edge-id-generator.md b/.changeset/custom-edge-id-generator.md index 9b2e1bb8..439d729c 100644 --- a/.changeset/custom-edge-id-generator.md +++ b/.changeset/custom-edge-id-generator.md @@ -1,5 +1,5 @@ --- -'@xyflow/system': minor +'@xyflow/system': patch --- Allow custom `getEdgeId` function in `addEdge` and `reconnectEdge` options to enable custom edge ID schemes. From f617616dc9e9647c99c48fdb38a090c2d46390a2 Mon Sep 17 00:00:00 2001 From: moklick Date: Tue, 25 Nov 2025 11:01:27 +0100 Subject: [PATCH 164/167] chore(xyhandle): cleanup --- packages/react/src/store/index.ts | 10 +++++----- packages/svelte/src/lib/store/index.ts | 14 ++++++++------ packages/system/src/xyhandle/XYHandle.ts | 16 +++++++++------- 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/packages/react/src/store/index.ts b/packages/react/src/store/index.ts index baef953c..d6ff3b60 100644 --- a/packages/react/src/store/index.ts +++ b/packages/react/src/store/index.ts @@ -190,16 +190,16 @@ const createStore = ({ type: 'position', position: expandParent ? { - x: Math.max(0, dragItem.position.x), - y: Math.max(0, dragItem.position.y), - } + x: Math.max(0, dragItem.position.x), + y: Math.max(0, dragItem.position.y), + } : dragItem.position, dragging, }; - if (node && connection.inProgress && connection.fromNode.id == node.id) { + if (node && connection.inProgress && connection.fromNode.id === node.id) { const updatedFrom = getHandlePosition(node, connection.fromHandle, Position.Left, true); - updateConnection({...connection,from: updatedFrom}) + updateConnection({ ...connection, from: updatedFrom }); } if (expandParent && node.parentId) { diff --git a/packages/svelte/src/lib/store/index.ts b/packages/svelte/src/lib/store/index.ts index 4619a27e..30bcafc1 100644 --- a/packages/svelte/src/lib/store/index.ts +++ b/packages/svelte/src/lib/store/index.ts @@ -52,13 +52,15 @@ export function createStore { - store.nodes = store.nodes.map((node) => { + store.nodes = store.nodes.map((node) => { if (store.connection.inProgress && store.connection.fromNode.id === node.id) { - - store.connection = { - ...store.connection, - from: getHandlePosition(store.nodeLookup.get(node.id), store.connection.fromHandle, Position.Left, true) - }; + const internalNode = store.nodeLookup.get(node.id); + if (internalNode) { + store.connection = { + ...store.connection, + from: getHandlePosition(internalNode, store.connection.fromHandle, Position.Left, true) + }; + } } const dragItem = nodeDragItems.get(node.id); return dragItem ? { ...node, position: dragItem.position, dragging } : node; diff --git a/packages/system/src/xyhandle/XYHandle.ts b/packages/system/src/xyhandle/XYHandle.ts index 40d04dff..b8edf767 100644 --- a/packages/system/src/xyhandle/XYHandle.ts +++ b/packages/system/src/xyhandle/XYHandle.ts @@ -93,8 +93,8 @@ function onPointerDown( position: fromHandleInternal.position, }; - const fromNodeInternal = nodeLookup.get(nodeId)!; - const from = getHandlePosition(fromNodeInternal, fromHandle, Position.Left, true); + const fromInternalNode = nodeLookup.get(nodeId)!; + const from = getHandlePosition(fromInternalNode, fromHandle, Position.Left, true); let previousConnection: ConnectionInProgress = { inProgress: true, @@ -103,7 +103,7 @@ function onPointerDown( from, fromHandle, fromPosition: fromHandle.position, - fromNode: fromNodeInternal, + fromNode: fromInternalNode, to: position, toHandle: null, @@ -171,11 +171,13 @@ function onPointerDown( resultHandleDomNode = result.handleDomNode; connection = result.connection; isValid = isConnectionValid(!!closestHandle, result.isValid); - - const fromNodeInternal = nodeLookup.get(nodeId)!; - const from = getHandlePosition(fromNodeInternal, fromHandle, Position.Left, true); - const newConnection: ConnectionInProgress = { + const fromInternalNode = nodeLookup.get(nodeId); + const from = fromInternalNode + ? getHandlePosition(fromInternalNode, fromHandle, Position.Left, true) + : previousConnection.from; + + const newConnection: ConnectionInProgress = { ...previousConnection, from, isValid, From 2d7fa40e2684a0fcdd4eca7800ccf2c34338e549 Mon Sep 17 00:00:00 2001 From: moklick Date: Tue, 25 Nov 2025 11:04:35 +0100 Subject: [PATCH 165/167] chore(changesets): add --- .changeset/young-roses-shout.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/young-roses-shout.md diff --git a/.changeset/young-roses-shout.md b/.changeset/young-roses-shout.md new file mode 100644 index 00000000..f1f6a888 --- /dev/null +++ b/.changeset/young-roses-shout.md @@ -0,0 +1,7 @@ +--- +'@xyflow/react': patch +'@xyflow/svelte': patch +'@xyflow/system': patch +--- + +Update an ongoing connection when user moves node with keyboard. From f858cbb404ccb3ddcaf55563aec73f85e44d58bf Mon Sep 17 00:00:00 2001 From: moklick Date: Tue, 25 Nov 2025 12:34:49 +0100 Subject: [PATCH 166/167] chore(apply-changes): cleanup) --- packages/react/src/utils/changes.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react/src/utils/changes.ts b/packages/react/src/utils/changes.ts index 422832f2..de05236a 100644 --- a/packages/react/src/utils/changes.ts +++ b/packages/react/src/utils/changes.ts @@ -125,9 +125,9 @@ function applyChange(change: any, element: any): any { case 'dimensions': { if (typeof change.dimensions !== 'undefined') { - element.measured = {}; - element.measured.width = change.dimensions.width; - element.measured.height = change.dimensions.height; + element.measured = { + ...change.dimensions, + }; if (change.setAttributes) { if (change.setAttributes === true || change.setAttributes === 'width') { From 38dbf41c464550cc803b946a4ad1f46982385a03 Mon Sep 17 00:00:00 2001 From: moklick Date: Tue, 25 Nov 2025 12:35:50 +0100 Subject: [PATCH 167/167] chore(changesets): add --- .changeset/dry-seahorses-deliver.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/dry-seahorses-deliver.md diff --git a/.changeset/dry-seahorses-deliver.md b/.changeset/dry-seahorses-deliver.md new file mode 100644 index 00000000..1125ea3e --- /dev/null +++ b/.changeset/dry-seahorses-deliver.md @@ -0,0 +1,5 @@ +--- +'@xyflow/react': patch +--- + +Always create a new measured object in apply changes.