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 @@ + +