feat(svelte) added isNodeIntersecting and getIntersectingNodes to svelte-flow
This commit is contained in:
@@ -15,7 +15,8 @@
|
|||||||
'two-way-viewport',
|
'two-way-viewport',
|
||||||
'usesvelteflow',
|
'usesvelteflow',
|
||||||
'useupdatenodeinternals',
|
'useupdatenodeinternals',
|
||||||
'validation'
|
'validation',
|
||||||
|
'intersections'
|
||||||
];
|
];
|
||||||
|
|
||||||
const onChange = (event: Event) => {
|
const onChange = (event: Event) => {
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { SvelteFlowProvider } from '@xyflow/svelte';
|
||||||
|
import Flow from './Flow.svelte';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<SvelteFlowProvider>
|
||||||
|
<Flow />
|
||||||
|
</SvelteFlowProvider>
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
@@ -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[] = [];
|
||||||
@@ -1,7 +1,11 @@
|
|||||||
import { get, type Writable } from 'svelte/store';
|
import { get, type Writable } from 'svelte/store';
|
||||||
import {
|
import {
|
||||||
|
getOverlappingArea,
|
||||||
|
isRectObject,
|
||||||
|
nodeToRect,
|
||||||
pointToRendererPoint,
|
pointToRendererPoint,
|
||||||
type Project,
|
type Project,
|
||||||
|
type Rect,
|
||||||
type SetCenterOptions,
|
type SetCenterOptions,
|
||||||
type Viewport,
|
type Viewport,
|
||||||
type ViewportHelperFunctionOptions,
|
type ViewportHelperFunctionOptions,
|
||||||
@@ -10,7 +14,7 @@ import {
|
|||||||
} from '@xyflow/system';
|
} from '@xyflow/system';
|
||||||
|
|
||||||
import { useStore } from '$lib/store';
|
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';
|
import type { SvelteFlowStore } from '$lib/store/types';
|
||||||
|
|
||||||
export function useSvelteFlow(): {
|
export function useSvelteFlow(): {
|
||||||
@@ -22,6 +26,16 @@ export function useSvelteFlow(): {
|
|||||||
setViewport: (viewport: Viewport, options?: ViewportHelperFunctionOptions) => void;
|
setViewport: (viewport: Viewport, options?: ViewportHelperFunctionOptions) => void;
|
||||||
getViewport: () => Viewport;
|
getViewport: () => Viewport;
|
||||||
fitView: (options?: FitViewOptions) => void;
|
fitView: (options?: FitViewOptions) => void;
|
||||||
|
getIntersectingNodes: (
|
||||||
|
nodeOrRect: Partial<Node<any>> | Rect,
|
||||||
|
partially?: boolean,
|
||||||
|
nodesToIntersect?: Node[]
|
||||||
|
) => Node[];
|
||||||
|
isNodeIntersecting: (
|
||||||
|
nodeOrRect: Partial<Node<any>> | Rect,
|
||||||
|
area: Rect,
|
||||||
|
partially?: boolean
|
||||||
|
) => boolean;
|
||||||
project: Project;
|
project: Project;
|
||||||
viewport: Writable<Viewport>;
|
viewport: Writable<Viewport>;
|
||||||
nodes: SvelteFlowStore['nodes'];
|
nodes: SvelteFlowStore['nodes'];
|
||||||
@@ -41,6 +55,21 @@ export function useSvelteFlow(): {
|
|||||||
edges
|
edges
|
||||||
} = useStore();
|
} = useStore();
|
||||||
|
|
||||||
|
const getNodeRect = (
|
||||||
|
nodeOrRect: Partial<Node<any>> | Rect
|
||||||
|
): [Rect | null, Node<any> | 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 {
|
return {
|
||||||
zoomIn,
|
zoomIn,
|
||||||
zoomOut,
|
zoomOut,
|
||||||
@@ -78,6 +107,41 @@ export function useSvelteFlow(): {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
fitView,
|
fitView,
|
||||||
|
getIntersectingNodes: (
|
||||||
|
nodeOrRect: Partial<Node<any>> | 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<Node<any>> | 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) => {
|
project: (position: XYPosition) => {
|
||||||
const _snapGrid = get(snapGrid);
|
const _snapGrid = get(snapGrid);
|
||||||
const { x, y, zoom } = get(viewport);
|
const { x, y, zoom } = get(viewport);
|
||||||
|
|||||||
Reference in New Issue
Block a user