refactor(svelte): cleanup useSvelteFlow, add functions (#3474)
* chore(svelte) removed nodes, edges from useSvelteFlow * feat(svelte) added deleteElements to useSvelteFlow * fix(svelte) parent div gets resized correctly, got rid of getBoundingClient Rect * feat(svelte) added screenToFlowCoordinate * feat(svelte) added flowToScreenCoordinate to useSvelteFlow * chore(svelte) removed project from useSvelteFlow * Delete examples/svelte/src/routes/add-node-on-drop/+server.ts --------- Co-authored-by: Moritz Klack <info@moritzklack.com>
This commit is contained in:
@@ -15,7 +15,8 @@
|
||||
'two-way-viewport',
|
||||
'usesvelteflow',
|
||||
'useupdatenodeinternals',
|
||||
'validation'
|
||||
'validation',
|
||||
'add-node-on-drop'
|
||||
];
|
||||
|
||||
const onChange = (event: Event) => {
|
||||
|
||||
9
examples/svelte/src/routes/add-node-on-drop/+page.svelte
Normal file
9
examples/svelte/src/routes/add-node-on-drop/+page.svelte
Normal file
@@ -0,0 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { SvelteFlowProvider } from '@xyflow/svelte';
|
||||
import Flow from './Flow.svelte';
|
||||
</script>
|
||||
|
||||
<!-- You need the SvelteFlowProvider so you can useSvelteFlow -->
|
||||
<SvelteFlowProvider>
|
||||
<Flow />
|
||||
</SvelteFlowProvider>
|
||||
115
examples/svelte/src/routes/add-node-on-drop/Flow.svelte
Normal file
115
examples/svelte/src/routes/add-node-on-drop/Flow.svelte
Normal file
@@ -0,0 +1,115 @@
|
||||
<script lang="ts">
|
||||
import { SvelteFlow, useSvelteFlow } from '@xyflow/svelte';
|
||||
import type { Edge, Node } from '@xyflow/svelte';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
import '@xyflow/svelte/dist/style.css';
|
||||
|
||||
const initialNodes: Node[] = [
|
||||
{
|
||||
id: '0',
|
||||
type: 'input',
|
||||
data: { label: 'Node' },
|
||||
position: { x: 0, y: 50 }
|
||||
}
|
||||
];
|
||||
|
||||
const nodes = writable<Node[]>(initialNodes);
|
||||
const edges = writable<Edge[]>([]);
|
||||
|
||||
let connectingNodeId: string = '0';
|
||||
let rect: DOMRectReadOnly;
|
||||
let id = 1;
|
||||
const getId = () => `${id++}`;
|
||||
|
||||
const { screenToFlowCoordinate, flowToScreenCoordinate } = useSvelteFlow();
|
||||
|
||||
function handleConnectEnd({ detail: { event } }: { detail: { event: MouseEvent | TouchEvent } }) {
|
||||
// See of connection landed inside the flow pane
|
||||
const targetIsPane = event.target?.classList.contains('svelte-flow__pane');
|
||||
if (targetIsPane) {
|
||||
const id = getId();
|
||||
const position = {
|
||||
x: event.clientX,
|
||||
y: event.clientY
|
||||
};
|
||||
|
||||
const doubleTransformedPosition = flowToScreenCoordinate(screenToFlowCoordinate(position));
|
||||
console.log(
|
||||
'Is transforming in both directions (screen-flow, flow-screen) the same?',
|
||||
position.x === doubleTransformedPosition.x && position.y === doubleTransformedPosition.y
|
||||
);
|
||||
|
||||
const newNode: Node = {
|
||||
id,
|
||||
data: { label: `Node ${id}` },
|
||||
// project the screen coordinates to pane coordinates
|
||||
position: screenToFlowCoordinate(position),
|
||||
// set the origin of the new node so it is centered
|
||||
origin: [0.5, 0.0]
|
||||
};
|
||||
|
||||
$nodes.push(newNode);
|
||||
$edges.push({
|
||||
source: connectingNodeId,
|
||||
target: id,
|
||||
id: `${connectingNodeId}--${id}`
|
||||
});
|
||||
|
||||
$nodes = $nodes;
|
||||
$edges = $edges;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window />
|
||||
|
||||
<div class="wrapper" bind:contentRect={rect}>
|
||||
<SvelteFlow
|
||||
{nodes}
|
||||
{edges}
|
||||
fitView
|
||||
fitViewOptions={{ padding: 2 }}
|
||||
on:connectstart={({ detail: { nodeId } }) => {
|
||||
// Memorize the nodeId you start draggin a connection line from a node
|
||||
connectingNodeId = nodeId;
|
||||
}}
|
||||
on:connectend={handleConnectEnd}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
:global(.svelte-flow .svelte-flow__handle) {
|
||||
width: 30px;
|
||||
height: 14px;
|
||||
border-radius: 3px;
|
||||
background-color: #784be8;
|
||||
}
|
||||
|
||||
:global(.svelte-flow .svelte-flow__handle-top) {
|
||||
top: -10px;
|
||||
}
|
||||
|
||||
:global(.svelte-flow .svelte-flow__handle-bottom) {
|
||||
bottom: -10px;
|
||||
}
|
||||
|
||||
:global(.svelte-flow .svelte-flow__node) {
|
||||
height: 40px;
|
||||
width: 150px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
border-width: 2px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
:global(.svelte-flow .svelte-flow__edge path, .svelte-flow__connectionline path) {
|
||||
stroke-width: 2;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
}
|
||||
</style>
|
||||
@@ -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 @@
|
||||
|
||||
<div
|
||||
bind:this={domNode}
|
||||
bind:clientWidth
|
||||
bind:clientHeight
|
||||
{style}
|
||||
class={cc(['svelte-flow', className])}
|
||||
data-testid="svelte-flow__wrapper"
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import { get, type Writable } from 'svelte/store';
|
||||
import {
|
||||
pointToRendererPoint,
|
||||
type Project,
|
||||
type FitBoundsOptions,
|
||||
type SetCenterOptions,
|
||||
type Viewport,
|
||||
type ViewportHelperFunctionOptions,
|
||||
type XYPosition,
|
||||
type ZoomInOut
|
||||
type ZoomInOut,
|
||||
type Rect,
|
||||
getTransformForBounds,
|
||||
getElementsToRemove,
|
||||
rendererPointToPoint
|
||||
} from '@xyflow/system';
|
||||
|
||||
import { useStore } from '$lib/store';
|
||||
import type { FitViewOptions } from '$lib/types';
|
||||
import type { SvelteFlowStore } from '$lib/store/types';
|
||||
import type { Edge, FitViewOptions, Node } from '$lib/types';
|
||||
|
||||
export function useSvelteFlow(): {
|
||||
zoomIn: ZoomInOut;
|
||||
@@ -22,10 +25,14 @@ export function useSvelteFlow(): {
|
||||
setViewport: (viewport: Viewport, options?: ViewportHelperFunctionOptions) => void;
|
||||
getViewport: () => Viewport;
|
||||
fitView: (options?: FitViewOptions) => void;
|
||||
project: Project;
|
||||
fitBounds: (bounds: Rect, options?: FitBoundsOptions) => void;
|
||||
deleteElements: (
|
||||
nodesToRemove?: Partial<Node> & { id: string }[],
|
||||
edgesToRemove?: Partial<Edge> & { id: string }[]
|
||||
) => { deletedNodes: Node[]; deletedEdges: Edge[] };
|
||||
screenToFlowCoordinate: (position: XYPosition) => XYPosition;
|
||||
flowToScreenCoordinate: (position: XYPosition) => XYPosition;
|
||||
viewport: Writable<Viewport>;
|
||||
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<Node> & { id: string }[] = [],
|
||||
edgesToRemove: Partial<Edge> & { id: string }[] = []
|
||||
) => {
|
||||
const _nodes = get(nodes);
|
||||
const _edges = get(edges);
|
||||
const { matchingNodes, matchingEdges } = getElementsToRemove<Node, Edge>({
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user