diff --git a/examples/svelte/src/components/Header/Header.svelte b/examples/svelte/src/components/Header/Header.svelte
index 6755fc2d..26b26e1e 100644
--- a/examples/svelte/src/components/Header/Header.svelte
+++ b/examples/svelte/src/components/Header/Header.svelte
@@ -15,7 +15,8 @@
'two-way-viewport',
'usesvelteflow',
'useupdatenodeinternals',
- 'validation'
+ 'validation',
+ 'intersections'
];
const onChange = (event: Event) => {
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/svelte/src/lib/hooks/useSvelteFlow.ts b/packages/svelte/src/lib/hooks/useSvelteFlow.ts
index 366f104d..ced33dba 100644
--- a/packages/svelte/src/lib/hooks/useSvelteFlow.ts
+++ b/packages/svelte/src/lib/hooks/useSvelteFlow.ts
@@ -1,7 +1,11 @@
import { get, type Writable } from 'svelte/store';
import {
+ getOverlappingArea,
+ isRectObject,
+ nodeToRect,
pointToRendererPoint,
type Project,
+ type Rect,
type SetCenterOptions,
type Viewport,
type ViewportHelperFunctionOptions,
@@ -10,7 +14,7 @@ import {
} from '@xyflow/system';
import { useStore } from '$lib/store';
-import type { FitViewOptions } from '$lib/types';
+import type { FitViewOptions, Node } from '$lib/types';
import type { SvelteFlowStore } from '$lib/store/types';
export function useSvelteFlow(): {
@@ -22,6 +26,16 @@ export function useSvelteFlow(): {
setViewport: (viewport: Viewport, options?: ViewportHelperFunctionOptions) => void;
getViewport: () => Viewport;
fitView: (options?: FitViewOptions) => void;
+ getIntersectingNodes: (
+ nodeOrRect: Partial> | Rect,
+ partially?: boolean,
+ nodesToIntersect?: Node[]
+ ) => Node[];
+ isNodeIntersecting: (
+ nodeOrRect: Partial> | Rect,
+ area: Rect,
+ partially?: boolean
+ ) => boolean;
project: Project;
viewport: Writable;
nodes: SvelteFlowStore['nodes'];
@@ -41,6 +55,21 @@ export function useSvelteFlow(): {
edges
} = useStore();
+ const getNodeRect = (
+ nodeOrRect: Partial> | Rect
+ ): [Rect | null, Node | null | undefined, boolean] => {
+ const isRect = isRectObject(nodeOrRect);
+ const node = isRect ? null : get(nodes).find((n) => n.id === nodeOrRect.id);
+
+ if (!isRect && !node) {
+ [null, null, isRect];
+ }
+
+ const nodeRect = isRect ? nodeOrRect : nodeToRect(node!);
+
+ return [nodeRect, node, isRect];
+ };
+
return {
zoomIn,
zoomOut,
@@ -78,6 +107,41 @@ export function useSvelteFlow(): {
);
},
fitView,
+ getIntersectingNodes: (
+ nodeOrRect: Partial> | Rect,
+ partially = true,
+ nodesToIntersect?: Node[]
+ ) => {
+ const [nodeRect, node, isRect] = getNodeRect(nodeOrRect);
+
+ if (!nodeRect) {
+ return [];
+ }
+
+ return (nodesToIntersect || get(nodes)).filter((n) => {
+ if (!isRect && (n.id === node!.id || !n.positionAbsolute)) {
+ return false;
+ }
+
+ const currNodeRect = nodeToRect(n);
+ const overlappingArea = getOverlappingArea(currNodeRect, nodeRect);
+ const partiallyVisible = partially && overlappingArea > 0;
+
+ return partiallyVisible || overlappingArea >= nodeOrRect.width! * nodeOrRect.height!;
+ });
+ },
+ isNodeIntersecting: (nodeOrRect: Partial> | Rect, area: Rect, partially = true) => {
+ const [nodeRect] = getNodeRect(nodeOrRect);
+
+ if (!nodeRect) {
+ return false;
+ }
+
+ const overlappingArea = getOverlappingArea(nodeRect, area);
+ const partiallyVisible = partially && overlappingArea > 0;
+
+ return partiallyVisible || overlappingArea >= nodeOrRect.width! * nodeOrRect.height!;
+ },
project: (position: XYPosition) => {
const _snapGrid = get(snapGrid);
const { x, y, zoom } = get(viewport);