diff --git a/examples/react/src/examples/DragNDrop/index.tsx b/examples/react/src/examples/DragNDrop/index.tsx
index 2e844af4..83e39d88 100644
--- a/examples/react/src/examples/DragNDrop/index.tsx
+++ b/examples/react/src/examples/DragNDrop/index.tsx
@@ -49,9 +49,9 @@ const DnDFlow = () => {
if (reactFlowInstance) {
const type = event.dataTransfer.getData('application/reactflow');
- const position = reactFlowInstance.project({
+ const position = reactFlowInstance.screenToFlowCoordinate({
x: event.clientX,
- y: event.clientY - 40,
+ y: event.clientY,
});
const newNode: Node = {
id: getId(),
diff --git a/examples/svelte/src/components/Header/Header.svelte b/examples/svelte/src/components/Header/Header.svelte
index 6755fc2d..e842824e 100644
--- a/examples/svelte/src/components/Header/Header.svelte
+++ b/examples/svelte/src/components/Header/Header.svelte
@@ -15,7 +15,9 @@
'two-way-viewport',
'usesvelteflow',
'useupdatenodeinternals',
- 'validation'
+ 'validation',
+ 'intersections',
+ 'add-node-on-drop'
];
const onChange = (event: Event) => {
diff --git a/examples/svelte/src/routes/add-node-on-drop/+page.svelte b/examples/svelte/src/routes/add-node-on-drop/+page.svelte
new file mode 100644
index 00000000..338f964f
--- /dev/null
+++ b/examples/svelte/src/routes/add-node-on-drop/+page.svelte
@@ -0,0 +1,9 @@
+
+
+
+
+
+
diff --git a/examples/svelte/src/routes/add-node-on-drop/Flow.svelte b/examples/svelte/src/routes/add-node-on-drop/Flow.svelte
new file mode 100644
index 00000000..cd12c1a4
--- /dev/null
+++ b/examples/svelte/src/routes/add-node-on-drop/Flow.svelte
@@ -0,0 +1,115 @@
+
+
+
+
+
+ {
+ // Memorize the nodeId you start draggin a connection line from a node
+ connectingNodeId = nodeId;
+ }}
+ on:connectend={handleConnectEnd}
+ />
+
+
+
diff --git a/examples/svelte/src/routes/intersections/+page.svelte b/examples/svelte/src/routes/intersections/+page.svelte
new file mode 100644
index 00000000..8937b2c0
--- /dev/null
+++ b/examples/svelte/src/routes/intersections/+page.svelte
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/examples/svelte/src/routes/intersections/+server.ts b/examples/svelte/src/routes/intersections/+server.ts
new file mode 100644
index 00000000..ceca3ead
--- /dev/null
+++ b/examples/svelte/src/routes/intersections/+server.ts
@@ -0,0 +1,29 @@
+// Template for ALL endpoints serving the code snippets
+// No need to edit these inside /src/routes/**
+// Will be overwritten by "pnpm run create:endpoints"
+
+import { json } from '@sveltejs/kit';
+
+export function POST() {
+ const files = import.meta.glob(['./*.js', './*.ts', './*.svelte', './*css', '!**/+server.ts'], {
+ as: 'raw',
+ eager: true
+ });
+
+ // Loose ./ for each filename
+ // +page.svelte becomes App.svelte for correct display in Sandpack
+ const filesClean: { [key: string]: string } = Object.entries(files).reduce(
+ (filesCleanAcc: { [key: string]: string }, [filename, file]) => {
+ if (filename === './+page.svelte') {
+ filesCleanAcc['App.svelte'] = file;
+ } else {
+ filesCleanAcc[filename.replace('./', '')] = file;
+ }
+
+ return filesCleanAcc;
+ },
+ {}
+ );
+
+ return json(filesClean);
+}
diff --git a/examples/svelte/src/routes/intersections/Flow.svelte b/examples/svelte/src/routes/intersections/Flow.svelte
new file mode 100644
index 00000000..f3f80335
--- /dev/null
+++ b/examples/svelte/src/routes/intersections/Flow.svelte
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/svelte/src/routes/intersections/nodes-and-edges.ts b/examples/svelte/src/routes/intersections/nodes-and-edges.ts
new file mode 100644
index 00000000..fffb59b8
--- /dev/null
+++ b/examples/svelte/src/routes/intersections/nodes-and-edges.ts
@@ -0,0 +1,28 @@
+import type { Node, Edge } from '@xyflow/svelte';
+
+export const initialNodes: Node[] = [
+ {
+ id: '1',
+ data: { label: 'Node 1' },
+ position: { x: 0, y: 0 },
+ style: 'width: 200px; height: 100px;'
+ },
+ {
+ id: '2',
+ data: { label: 'Node 2' },
+ position: { x: 0, y: 150 }
+ },
+ {
+ id: '3',
+ data: { label: 'Node 3' },
+ position: { x: 250, y: 0 }
+ },
+ {
+ id: '4',
+ data: { label: 'Node' },
+ position: { x: 350, y: 150 },
+ style: 'width: 50px; height: 50px;'
+ }
+];
+
+export const initialEdges: Edge[] = [];
diff --git a/packages/react/src/hooks/useReactFlow.ts b/packages/react/src/hooks/useReactFlow.ts
index 45a8dbe1..da65a4e3 100644
--- a/packages/react/src/hooks/useReactFlow.ts
+++ b/packages/react/src/hooks/useReactFlow.ts
@@ -1,5 +1,13 @@
import { useCallback, useMemo } from 'react';
-import { getElementsToRemove, getOverlappingArea, isRectObject, nodeToRect, type Rect } from '@xyflow/system';
+import {
+ getElementsToRemove,
+ getIncomersBase,
+ getOutgoersBase,
+ getOverlappingArea,
+ isRectObject,
+ nodeToRect,
+ type Rect,
+} from '@xyflow/system';
import useViewportHelper from './useViewportHelper';
import { useStoreApi } from '../hooks/useStore';
@@ -164,6 +172,8 @@ export default function useReactFlow(): ReactFlo
}
}
}
+
+ return { deletedNodes: matchingNodes, deletedEdges: matchingEdges };
}, []);
const getNodeRect = useCallback(
@@ -223,6 +233,41 @@ export default function useReactFlow(): ReactFlo
[]
);
+ const getConnectedEdges = useCallback((node) => {
+ const { edges } = store.getState();
+
+ const nodeIds = new Set();
+ if (typeof node === 'string') {
+ nodeIds.add(node);
+ } else if (node.length >= 1) {
+ node.forEach((n) => {
+ nodeIds.add(n.id);
+ });
+ }
+
+ return edges.filter((edge) => nodeIds.has(edge.source) || nodeIds.has(edge.target));
+ }, []);
+
+ const getIncomers = useCallback((node) => {
+ const { nodes, edges } = store.getState();
+
+ if (typeof node === 'string') {
+ return getIncomersBase({ id: node }, nodes, edges);
+ }
+
+ return getIncomersBase(node, nodes, edges);
+ }, []);
+
+ const getOutgoers = useCallback((node) => {
+ const { nodes, edges } = store.getState();
+
+ if (typeof node == 'string') {
+ return getOutgoersBase({ id: node }, nodes, edges);
+ }
+
+ return getOutgoersBase(node, nodes, edges);
+ }, []);
+
return useMemo(() => {
return {
...viewportHelper,
@@ -238,6 +283,9 @@ export default function useReactFlow(): ReactFlo
deleteElements,
getIntersectingNodes,
isNodeIntersecting,
+ getConnectedEdges,
+ getIncomers,
+ getOutgoers,
};
}, [
viewportHelper,
@@ -253,5 +301,8 @@ export default function useReactFlow(): ReactFlo
deleteElements,
getIntersectingNodes,
isNodeIntersecting,
+ getConnectedEdges,
+ getIncomers,
+ getOutgoers,
]);
}
diff --git a/packages/react/src/hooks/useViewportHelper.ts b/packages/react/src/hooks/useViewportHelper.ts
index aded1f17..1872d414 100644
--- a/packages/react/src/hooks/useViewportHelper.ts
+++ b/packages/react/src/hooks/useViewportHelper.ts
@@ -1,5 +1,11 @@
import { useMemo } from 'react';
-import { pointToRendererPoint, getTransformForBounds, fitView, type XYPosition } from '@xyflow/system';
+import {
+ pointToRendererPoint,
+ getTransformForBounds,
+ fitView,
+ type XYPosition,
+ rendererPointToPoint,
+} from '@xyflow/system';
import { useStoreApi, useStore } from '../hooks/useStore';
import type { ViewportHelperFunctions, ReactFlowState } from '../types';
@@ -85,6 +91,36 @@ const useViewportHelper = (): ViewportHelperFunctions => {
const { transform, snapToGrid, snapGrid } = store.getState();
return pointToRendererPoint(position, transform, snapToGrid, snapGrid);
},
+ screenToFlowCoordinate: (position: XYPosition) => {
+ const { transform, snapToGrid, snapGrid, domNode } = store.getState();
+ if (domNode) {
+ const { x: domX, y: domY } = domNode.getBoundingClientRect();
+
+ const correctedPosition = {
+ x: position.x - domX,
+ y: position.y - domY,
+ };
+
+ return pointToRendererPoint(correctedPosition, transform, snapToGrid, snapGrid || [1, 1]);
+ }
+
+ return { x: 0, y: 0 };
+ },
+ flowToScreenCoordinate: (position: XYPosition) => {
+ const { transform, domNode } = store.getState();
+ if (domNode) {
+ const { x: domX, y: domY } = domNode.getBoundingClientRect();
+
+ const rendererPosition = rendererPointToPoint(position, transform);
+
+ return {
+ x: rendererPosition.x + domX,
+ y: rendererPosition.y + domY,
+ };
+ }
+
+ return { x: 0, y: 0 };
+ },
viewportInitialized: panZoomInitialized,
};
}, [panZoomInitialized]);
diff --git a/packages/react/src/types/general.ts b/packages/react/src/types/general.ts
index 8108c3dd..2b2945a6 100644
--- a/packages/react/src/types/general.ts
+++ b/packages/react/src/types/general.ts
@@ -55,5 +55,7 @@ export type ViewportHelperFunctions = {
setCenter: SetCenter;
fitBounds: FitBounds;
project: Project;
+ screenToFlowCoordinate: Project;
+ flowToScreenCoordinate: Project;
viewportInitialized: boolean;
};
diff --git a/packages/react/src/types/instance.ts b/packages/react/src/types/instance.ts
index 239d339f..3d123c1a 100644
--- a/packages/react/src/types/instance.ts
+++ b/packages/react/src/types/instance.ts
@@ -28,7 +28,10 @@ export namespace Instance {
export type GetEdge = (id: string) => Edge | undefined;
export type AddEdges = (payload: Edge[] | Edge) => void;
export type ToObject = () => ReactFlowJsonObject;
- export type DeleteElements = ({ nodes, edges }: DeleteElementsOptions) => void;
+ export type DeleteElements = ({ nodes, edges }: DeleteElementsOptions) => {
+ deletedNodes: Node[];
+ deletedEdges: Edge[];
+ };
export type GetIntersectingNodes = (
node: (Partial> & { id: Node['id'] }) | Rect,
partially?: boolean,
@@ -39,6 +42,9 @@ export namespace Instance {
area: Rect,
partially?: boolean
) => boolean;
+ export type getConnectedEdges = (id: string | (Partial & { id: Node['id'] })[]) => Edge[];
+ export type getIncomers = (node: string | (Partial & { id: Node['id'] })) => Node[];
+ export type getOutgoers = (node: string | (Partial & { id: Node['id'] })) => Node[];
}
export type ReactFlowInstance = {
diff --git a/packages/svelte/src/lib/components/ConnectionLine/ConnectionLine.svelte b/packages/svelte/src/lib/components/ConnectionLine/ConnectionLine.svelte
index eb30d3f7..904a45d3 100644
--- a/packages/svelte/src/lib/components/ConnectionLine/ConnectionLine.svelte
+++ b/packages/svelte/src/lib/components/ConnectionLine/ConnectionLine.svelte
@@ -12,11 +12,11 @@
{#if $connection.path}
-
+
{#if !isCustomComponent}
-
+
{/if}
diff --git a/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte b/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte
index 81d35e49..7d609447 100644
--- a/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte
+++ b/packages/svelte/src/lib/components/EdgeWrapper/EdgeWrapper.svelte
@@ -1,3 +1,5 @@
+
+