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