> & { id: Node['id'] }) | Rect,
partially?: boolean,
diff --git a/packages/svelte/src/lib/container/SvelteFlow/SvelteFlow.svelte b/packages/svelte/src/lib/container/SvelteFlow/SvelteFlow.svelte
index ef5d547b..d9eb867d 100644
--- a/packages/svelte/src/lib/container/SvelteFlow/SvelteFlow.svelte
+++ b/packages/svelte/src/lib/container/SvelteFlow/SvelteFlow.svelte
@@ -71,13 +71,14 @@
export { className as class };
let domNode: HTMLDivElement;
+ let clientWidth: number;
+ let clientHeight: number;
const store = hasContext(key) ? useStore() : createStoreContext();
onMount(() => {
- const { width, height } = domNode.getBoundingClientRect();
- store.width.set(width);
- store.height.set(height);
+ store.width.set(clientWidth);
+ store.height.set(clientHeight);
store.domNode.set(domNode);
store.syncNodeStores(nodes);
@@ -141,6 +142,8 @@
void;
getViewport: () => Viewport;
fitView: (options?: FitViewOptions) => void;
- project: Project;
+ getIntersectingNodes: (
+ nodeOrRect: (Partial & { id: Node['id'] }) | Rect,
+ partially?: boolean,
+ nodesToIntersect?: Node[]
+ ) => Node[];
+ isNodeIntersecting: (
+ nodeOrRect: (Partial & { id: Node['id'] }) | Rect,
+ area: Rect,
+ partially?: boolean
+ ) => boolean;
+ fitBounds: (bounds: Rect, options?: FitBoundsOptions) => void;
+ deleteElements: (
+ nodesToRemove?: Partial & { id: string }[],
+ edgesToRemove?: Partial & { id: string }[]
+ ) => { deletedNodes: Node[]; deletedEdges: Edge[] };
+ screenToFlowCoordinate: (position: XYPosition) => XYPosition;
+ flowToScreenCoordinate: (position: XYPosition) => XYPosition;
viewport: Writable;
- nodes: SvelteFlowStore['nodes'];
- edges: SvelteFlowStore['edges'];
getConnectedEdges: (id: string | (Partial & { id: Node['id'] })[]) => Edge[];
getIncomers: (node: string | (Partial & { id: Node['id'] })) => Node[];
getOutgoers: (node: string | (Partial & { id: Node['id'] })) => Node[];
@@ -40,12 +60,29 @@ export function useSvelteFlow(): {
viewport,
width,
height,
+ minZoom,
maxZoom,
panZoom,
nodes,
- edges
+ edges,
+ domNode
} = useStore();
+ const getNodeRect = (
+ nodeOrRect: (Partial & { 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,
@@ -67,34 +104,145 @@ export function useSvelteFlow(): {
},
getViewport: () => get(viewport),
setCenter: (x, y, options) => {
- const _width = get(width);
- const _height = get(height);
- const _maxZoom = get(maxZoom);
-
- const nextZoom = typeof options?.zoom !== 'undefined' ? options.zoom : _maxZoom;
+ const nextZoom = typeof options?.zoom !== 'undefined' ? options.zoom : get(maxZoom);
get(panZoom)?.setViewport(
{
- x: _width / 2 - x * nextZoom,
- y: _height / 2 - y * nextZoom,
+ x: get(width) / 2 - x * nextZoom,
+ y: get(height) / 2 - y * nextZoom,
zoom: nextZoom
},
{ duration: options?.duration }
);
},
fitView,
- project: (position: XYPosition) => {
- const _snapGrid = get(snapGrid);
- const { x, y, zoom } = get(viewport);
+ fitBounds: (bounds: Rect, options?: FitBoundsOptions) => {
+ const [x, y, zoom] = getTransformForBounds(
+ bounds,
+ get(width),
+ get(height),
+ get(minZoom),
+ get(maxZoom),
+ options?.padding ?? 0.1
+ );
- return pointToRendererPoint(position, [x, y, zoom], _snapGrid !== null, _snapGrid || [1, 1]);
+ get(panZoom)?.setViewport(
+ {
+ x,
+ y,
+ zoom
+ },
+ { duration: options?.duration }
+ );
},
- nodes,
- edges,
- getConnectedEdges: (node) => {
- const _edges = get(edges);
+ getIntersectingNodes: (
+ nodeOrRect: (Partial & { 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 & { 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 & { id: string }[] = [],
+ edgesToRemove: Partial & { id: string }[] = []
+ ) => {
+ const _nodes = get(nodes);
+ const _edges = get(edges);
+ const { matchingNodes, matchingEdges } = getElementsToRemove({
+ nodesToRemove,
+ edgesToRemove,
+ nodes: _nodes,
+ edges: _edges
+ });
+
+ if (matchingNodes) {
+ nodes.set(_nodes.filter((node) => !matchingNodes.some(({ id }) => id === node.id)));
+ }
+
+ if (matchingEdges) {
+ edges.set(_edges.filter((edge) => !matchingEdges.some(({ id }) => id === edge.id)));
+ }
+
+ return {
+ deletedNodes: matchingNodes,
+ deletedEdges: matchingEdges
+ };
+ },
+ screenToFlowCoordinate: (position: XYPosition) => {
+ const _domNode = get(domNode);
+
+ if (_domNode) {
+ const _snapGrid = get(snapGrid);
+ const { x, y, zoom } = get(viewport);
+ const { x: domX, y: domY } = _domNode.getBoundingClientRect();
+
+ const correctedPosition = {
+ x: position.x - domX,
+ y: position.y - domY
+ };
+
+ return pointToRendererPoint(
+ correctedPosition,
+ [x, y, zoom],
+ _snapGrid !== null,
+ _snapGrid || [1, 1]
+ );
+ }
+
+ return { x: 0, y: 0 };
+ },
+ flowToScreenCoordinate: (position: XYPosition) => {
+ const _domNode = get(domNode);
+
+ if (_domNode) {
+ const { x, y, zoom } = get(viewport);
+ const { x: domX, y: domY } = _domNode.getBoundingClientRect();
+
+ const rendererPosition = rendererPointToPoint(position, [x, y, zoom]);
+
+ return {
+ x: rendererPosition.x + domX,
+ y: rendererPosition.y + domY
+ };
+ }
+
+ return { x: 0, y: 0 };
+ },
+ getConnectedEdges: (node) => {
const nodeIds = new Set();
+
if (typeof node === 'string') {
nodeIds.add(node);
} else if (node.length >= 1) {
@@ -103,28 +251,18 @@ export function useSvelteFlow(): {
});
}
- return _edges.filter((edge) => nodeIds.has(edge.source) || nodeIds.has(edge.target));
+ return get(edges).filter((edge) => nodeIds.has(edge.source) || nodeIds.has(edge.target));
},
getIncomers: (node) => {
- const _edges = get(edges);
- const _nodes = get(nodes);
+ const _node = typeof node === 'string' ? { id: node } : node;
- if (typeof node === 'string') {
- return getIncomersBase({ id: node }, _nodes, _edges);
- }
-
- return getIncomersBase(node, _nodes, _edges);
+ return getIncomersBase(_node, get(nodes), get(edges));
},
getOutgoers: (node) => {
- const _edges = get(edges);
- const _nodes = get(nodes);
+ const _node = typeof node === 'string' ? { id: node } : node;
- if (typeof node == 'string') {
- return getOutgoersBase({ id: node }, _nodes, _edges);
- }
-
- return getOutgoersBase(node, _nodes, _edges);
+ return getOutgoersBase(_node, get(nodes), get(edges));
},
- viewport: viewport
+ viewport
};
}
diff --git a/packages/svelte/src/lib/index.ts b/packages/svelte/src/lib/index.ts
index f1af3791..74a0ec7f 100644
--- a/packages/svelte/src/lib/index.ts
+++ b/packages/svelte/src/lib/index.ts
@@ -19,9 +19,12 @@ export { useStore } from '$lib/store';
// utils
export * from '$lib/utils';
+
+//hooks
export * from '$lib/hooks/useSvelteFlow';
export * from '$lib/hooks/useUpdateNodeInternals';
export * from '$lib/hooks/useConnection';
+export * from '$lib/hooks/useNodesEdges';
// types
export type { Edge, EdgeProps, EdgeTypes, DefaultEdgeOptions } from '$lib/types/edges';
diff --git a/packages/svelte/src/styles/style.css b/packages/svelte/src/styles/style.css
index b3f57e6f..8d45911a 100644
--- a/packages/svelte/src/styles/style.css
+++ b/packages/svelte/src/styles/style.css
@@ -5,4 +5,5 @@
.svelte-flow__edge-label {
text-align: center;
position: absolute;
+ font-size: 10px;
}