feat(svelte): add intersection helpers

Svelte flow get intersecting nodes
This commit is contained in:
Moritz Klack
2023-10-05 20:15:27 +02:00
committed by GitHub
6 changed files with 193 additions and 0 deletions

View File

@@ -16,6 +16,7 @@
'usesvelteflow',
'useupdatenodeinternals',
'validation',
'intersections',
'add-node-on-drop'
];

View File

@@ -0,0 +1,8 @@
<script lang="ts">
import { SvelteFlowProvider } from '@xyflow/svelte';
import Flow from './Flow.svelte';
</script>
<SvelteFlowProvider>
<Flow />
</SvelteFlowProvider>

View File

@@ -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);
}

View File

@@ -0,0 +1,60 @@
<script lang="ts">
import { SvelteFlow, Background, Controls, useSvelteFlow } from '@xyflow/svelte';
import type { Edge, Node } from '@xyflow/svelte';
import { writable } from 'svelte/store';
import '@xyflow/svelte/dist/style.css';
import { initialNodes, initialEdges } from './nodes-and-edges';
const nodes = writable<Node[]>(initialNodes);
const edges = writable<Edge[]>(initialEdges);
const { getIntersectingNodes } = useSvelteFlow();
function onNodeDrag({ detail: { node } }) {
const intersections = getIntersectingNodes(node).map((n) => n.id);
$nodes.forEach((n) => {
n.class = intersections.includes(n.id) ? 'highlight' : '';
});
$nodes = $nodes;
}
</script>
<div style="height:100vh;">
<SvelteFlow {nodes} {edges} fitView class="intersection-flow" on:nodedrag={onNodeDrag}>
<Background />
<Controls />
</SvelteFlow>
</div>
<style>
:global(.svelte-flow__node.highlight) {
background-color: #ff0072 !important;
color: white;
}
:global(.intersection-flow .svelte-flow__node) {
display: flex;
justify-content: center;
align-items: center;
font-weight: 700;
border-radius: 1px;
border-width: 2px;
box-shadow: 6px 6px 0 1px rgba(0, 0, 0, 0.7);
}
:global(
.intersection-flow .svelte-flow__node.selected,
.intersection-flow .svelte-flow__node:hover,
.intersection-flow .svelte-flow__node:focus
) {
box-shadow: 6px 6px 0 1px rgba(0, 0, 0, 0.7);
background-color: #eee;
}
:global(.intersection-flow .svelte-flow__handle) {
display: none;
}
</style>

View File

@@ -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[] = [];

View File

@@ -1,5 +1,8 @@
import { get, type Writable } from 'svelte/store';
import {
getOverlappingArea,
isRectObject,
nodeToRect,
pointToRendererPoint,
type FitBoundsOptions,
type SetCenterOptions,
@@ -25,6 +28,16 @@ export function useSvelteFlow(): {
setViewport: (viewport: Viewport, options?: ViewportHelperFunctionOptions) => void;
getViewport: () => Viewport;
fitView: (options?: FitViewOptions) => void;
getIntersectingNodes: (
nodeOrRect: (Partial<Node> & { id: Node['id'] }) | Rect,
partially?: boolean,
nodesToIntersect?: Node[]
) => Node[];
isNodeIntersecting: (
nodeOrRect: (Partial<Node> & { id: Node['id'] }) | Rect,
area: Rect,
partially?: boolean
) => boolean;
fitBounds: (bounds: Rect, options?: FitBoundsOptions) => void;
deleteElements: (
nodesToRemove?: Partial<Node> & { id: string }[],
@@ -50,6 +63,21 @@ export function useSvelteFlow(): {
domNode
} = useStore();
const getNodeRect = (
nodeOrRect: (Partial<Node> & { id: Node['id'] }) | 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) {
return [null, null, isRect];
}
const nodeRect = isRect ? nodeOrRect : nodeToRect(node!);
return [nodeRect, node, isRect];
};
return {
zoomIn,
zoomOut,
@@ -111,6 +139,45 @@ export function useSvelteFlow(): {
{ duration: options?.duration }
);
},
getIntersectingNodes: (
nodeOrRect: (Partial<Node> & { id: Node['id'] }) | Rect,
partially = true,
nodesToIntersect?: Node[]
) => {
const [nodeRect, node, isRect] = getNodeRect(nodeOrRect);
if (!nodeRect || !node) {
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<Node> & { id: Node['id'] }) | 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!;
},
deleteElements: (
nodesToRemove: Partial<Node> & { id: string }[] = [],
edgesToRemove: Partial<Edge> & { id: string }[] = []