diff --git a/examples/svelte/src/components/Header/Header.svelte b/examples/svelte/src/components/Header/Header.svelte
index 6755fc2d..2b8e158e 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',
+ '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/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;
+ 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'];
} {
const {
zoomIn,
@@ -35,10 +42,12 @@ export function useSvelteFlow(): {
viewport,
width,
height,
+ minZoom,
maxZoom,
panZoom,
nodes,
- edges
+ edges,
+ domNode
} = useStore();
return {
@@ -78,14 +87,94 @@ export function useSvelteFlow(): {
);
},
fitView,
- project: (position: XYPosition) => {
- const _snapGrid = get(snapGrid);
- const { x, y, zoom } = get(viewport);
+ fitBounds: (bounds: Rect, options?: FitBoundsOptions) => {
+ const _width = get(width);
+ const _height = get(height);
+ const _maxZoom = get(maxZoom);
+ const _minZoom = get(minZoom);
- return pointToRendererPoint(position, [x, y, zoom], _snapGrid !== null, _snapGrid || [1, 1]);
+ const [x, y, zoom] = getTransformForBounds(
+ bounds,
+ _width,
+ _height,
+ _minZoom,
+ _maxZoom,
+ options?.padding ?? 0.1
+ );
+
+ get(panZoom)?.setViewport(
+ {
+ x,
+ y,
+ zoom
+ },
+ { duration: options?.duration }
+ );
+ },
+ 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 };
},
- nodes,
- edges,
viewport: viewport
};
}