diff --git a/packages/svelte/src/lib/components/edges/BaseEdge.svelte b/packages/svelte/src/lib/components/edges/BaseEdge.svelte index 60e05403..1b798e41 100644 --- a/packages/svelte/src/lib/components/edges/BaseEdge.svelte +++ b/packages/svelte/src/lib/components/edges/BaseEdge.svelte @@ -5,9 +5,6 @@ export let sourceY: number = 0; export let targetX: number = 0; export let targetY: number = 0; - - const path = `M ${sourceX},${sourceY}L ${targetX},${targetY}`; - diff --git a/packages/svelte/src/lib/components/nodes/BaseNode.svelte b/packages/svelte/src/lib/components/nodes/BaseNode.svelte index 35b88312..8aed2c02 100644 --- a/packages/svelte/src/lib/components/nodes/BaseNode.svelte +++ b/packages/svelte/src/lib/components/nodes/BaseNode.svelte @@ -44,7 +44,7 @@ .react-flow__node { padding: 10px; border-radius: 3px; - width: var(--node-width); + width: 50px; font-size: 12px; color: #222; text-align: center; diff --git a/packages/svelte/src/lib/components/nodes/DefaultNode.svelte b/packages/svelte/src/lib/components/nodes/DefaultNode.svelte index 7357f82a..a724b243 100644 --- a/packages/svelte/src/lib/components/nodes/DefaultNode.svelte +++ b/packages/svelte/src/lib/components/nodes/DefaultNode.svelte @@ -10,6 +10,6 @@ - {data?.label} +{data?.label} diff --git a/packages/svelte/src/lib/container/EdgeRenderer.svelte b/packages/svelte/src/lib/container/EdgeRenderer.svelte deleted file mode 100644 index edff1360..00000000 --- a/packages/svelte/src/lib/container/EdgeRenderer.svelte +++ /dev/null @@ -1,171 +0,0 @@ - - - - {#each edgesWithData as edge} - {#if edge} - - {/if} - {/each} - - - \ No newline at end of file diff --git a/packages/svelte/src/lib/container/EdgeRenderer/index.svelte b/packages/svelte/src/lib/container/EdgeRenderer/index.svelte new file mode 100644 index 00000000..124b8223 --- /dev/null +++ b/packages/svelte/src/lib/container/EdgeRenderer/index.svelte @@ -0,0 +1,28 @@ + + + + {#each $edgesWithDataStore as edge} + + {/each} + + + \ No newline at end of file diff --git a/packages/svelte/src/lib/container/EdgeRenderer/utils.ts b/packages/svelte/src/lib/container/EdgeRenderer/utils.ts new file mode 100644 index 00000000..1c794f27 --- /dev/null +++ b/packages/svelte/src/lib/container/EdgeRenderer/utils.ts @@ -0,0 +1,105 @@ +import { + type NodeHandleBounds, + type Rect, + type Node, + type HandleElement, + type XYPosition, + Position, + internalsSymbol +} from '@reactflow/core'; + +export function getNodeData(node?: Node): [Rect, NodeHandleBounds | null, boolean] { + const handleBounds = node?.[internalsSymbol]?.handleBounds || null; + + const isValid = + handleBounds && + node?.width && + node?.height && + typeof node?.positionAbsolute?.x !== 'undefined' && + typeof node?.positionAbsolute?.y !== 'undefined'; + + return [ + { + x: node?.positionAbsolute?.x || 0, + y: node?.positionAbsolute?.y || 0, + width: node?.width || 0, + height: node?.height || 0 + }, + handleBounds, + !!isValid + ]; +} + +export function getHandlePosition( + position: Position, + nodeRect: Rect, + handle: HandleElement | null = null +): XYPosition { + const x = (handle?.x || 0) + nodeRect.x; + const y = (handle?.y || 0) + nodeRect.y; + const width = handle?.width || nodeRect.width; + const height = handle?.height || nodeRect.height; + + switch (position) { + case Position.Top: + return { + x: x + width / 2, + y + }; + case Position.Right: + return { + x: x + width, + y: y + height / 2 + }; + case Position.Bottom: + return { + x: x + width / 2, + y: y + height + }; + case Position.Left: + return { + x, + y: y + height / 2 + }; + } +} + +export function getHandle(bounds: HandleElement[], handleId?: string | null): HandleElement | null { + if (!bounds) { + return null; + } + + if (handleId) { + return bounds.find((d) => d.id === handleId)!; + } else if (bounds.length === 1) { + return bounds[0]; + } + + return null; +} + +export type EdgePosition = { + sourceX: number; + sourceY: number; + targetX: number; + targetY: number; +}; + +export function getEdgePositions( + sourceNodeRect: Rect, + sourceHandle: HandleElement, + sourcePosition: Position, + targetNodeRect: Rect, + targetHandle: HandleElement, + targetPosition: Position +): EdgePosition { + const sourceHandlePos = getHandlePosition(sourcePosition, sourceNodeRect, sourceHandle); + const targetHandlePos = getHandlePosition(targetPosition, targetNodeRect, targetHandle); + + return { + sourceX: sourceHandlePos.x, + sourceY: sourceHandlePos.y, + targetX: targetHandlePos.x, + targetY: targetHandlePos.y + }; +} diff --git a/packages/svelte/src/lib/container/SvelteFlow.svelte b/packages/svelte/src/lib/container/SvelteFlow.svelte index af7ea786..bda49141 100644 --- a/packages/svelte/src/lib/container/SvelteFlow.svelte +++ b/packages/svelte/src/lib/container/SvelteFlow.svelte @@ -6,7 +6,7 @@ import { key, createStore } from '$lib/store'; import Viewport from '$lib/container/Viewport.svelte'; import NodeRenderer from '$lib/container/NodeRenderer.svelte'; - import EdgeRenderer from '$lib/container/EdgeRenderer.svelte'; + import EdgeRenderer from '$lib/container/EdgeRenderer/index.svelte'; export let nodes: Node[]; export let edges: Edge[]; @@ -56,7 +56,4 @@ z-index: 0; } - :root { - --node-width: 150px; - } \ No newline at end of file diff --git a/packages/svelte/src/lib/store/index.ts b/packages/svelte/src/lib/store/index.ts index a7e7807f..7dde9e58 100644 --- a/packages/svelte/src/lib/store/index.ts +++ b/packages/svelte/src/lib/store/index.ts @@ -1,9 +1,22 @@ import { getContext } from 'svelte'; -import { get, writable, type Writable } from 'svelte/store'; -import type { Node, Transform, NodeDragItem, NodeDimensionUpdate, Edge } from '@reactflow/core'; +import { derived, writable, type Readable, type Writable } from 'svelte/store'; +import { + type Node, + type Transform, + type NodeDragItem, + type NodeDimensionUpdate, + type Edge, + Position +} from '@reactflow/core'; import { internalsSymbol } from '@reactflow/core'; -import { getDimensions, getHandleBounds, updateAbsoluteNodePositions } from '../../utils'; +import { getDimensions, getHandleBounds } from '../../utils'; +import { + getEdgePositions, + getHandle, + getNodeData, + type EdgePosition +} from '$lib/container/EdgeRenderer/utils'; export const key = Symbol(); @@ -13,12 +26,18 @@ type CreateStoreProps = { transform?: Transform; }; +export type EdgeWithData = EdgePosition & { + id: string; + type: string; +}; + type SvelteFlowStore = { nodesStore: Writable; edgesStore: Writable; heightStore: Writable; widthStore: Writable; transformStore: Writable; + edgesWithDataStore: Readable; updateNodePositions: ( nodeDragItems: NodeDragItem[], positionChanged?: boolean, @@ -37,6 +56,51 @@ export function createStore({ const heightStore = writable(500); const widthStore = writable(500); + const edgesWithDataStore = derived([edgesStore, nodesStore], ([$edges, $nodes]) => { + return $edges + .map((edge) => { + const sourceNode = $nodes.find((node) => node.id === edge.source); + const targetNode = $nodes.find((node) => node.id === edge.target); + const [sourceNodeRect, sourceHandleBounds, sourceIsValid] = getNodeData(sourceNode); + const [targetNodeRect, targetHandleBounds, targetIsValid] = getNodeData(targetNode); + + if (!sourceIsValid || !targetIsValid) { + return null; + } + + const edgeType = edge.type || 'default'; + + const targetNodeHandles = targetHandleBounds!.target; + const sourceHandle = getHandle(sourceHandleBounds!.source!, edge.sourceHandle); + const targetHandle = getHandle(targetNodeHandles!, edge.targetHandle); + const sourcePosition = sourceHandle?.position || Position.Bottom; + const targetPosition = targetHandle?.position || Position.Top; + + if (!sourceHandle || !targetHandle) { + return null; + } + + const { sourceX, sourceY, targetX, targetY } = getEdgePositions( + sourceNodeRect, + sourceHandle, + sourcePosition, + targetNodeRect, + targetHandle, + targetPosition + ); + + return { + id: edge.id, + type: edgeType, + sourceX, + sourceY, + targetX, + targetY + }; + }) + .filter((e) => e !== null) as EdgeWithData[]; + }); + const transformStore = writable(transform); function updateNodePositions(nodeDragItems: NodeDragItem[], dragging = false) { @@ -67,36 +131,40 @@ export function createStore({ const style = window.getComputedStyle(viewportNode); const { m22: zoom } = new window.DOMMatrixReadOnly(style.transform); - const nds = get(nodesStore); - updates.forEach((update) => { - const node = nds.find((n) => n.id === update.id); + nodesStore.update((nds) => { + const nextNodes = nds.map((node) => { + const update = updates.find((u) => u.id === node.id); - if (node) { - const dimensions = getDimensions(update.nodeElement); - const doUpdate = !!( - dimensions.width && - dimensions.height && - (node.width !== dimensions.width || - node.height !== dimensions.height || - update.forceUpdate) - ); + if (update) { + const dimensions = getDimensions(update.nodeElement); - if (doUpdate) { - node[internalsSymbol] = { - ...node[internalsSymbol], - handleBounds: { - source: getHandleBounds('.source', update.nodeElement, zoom), - target: getHandleBounds('.target', update.nodeElement, zoom) - } - }; - node.width = dimensions.width; - node.height = dimensions.height; + const doUpdate = !!( + dimensions.width && + dimensions.height && + (node.width !== dimensions.width || + node.height !== dimensions.height || + update.forceUpdate) + ); + + if (doUpdate) { + node[internalsSymbol] = { + ...node[internalsSymbol], + handleBounds: { + source: getHandleBounds('.source', update.nodeElement, zoom), + target: getHandleBounds('.target', update.nodeElement, zoom) + } + }; + node.width = dimensions.width; + node.height = dimensions.height; + } } - } - }); - updateAbsoluteNodePositions(nds); + return node; + }); + + return nextNodes; + }); } return { @@ -105,6 +173,7 @@ export function createStore({ transformStore, heightStore, widthStore, + edgesWithDataStore, updateNodePositions, updateNodeDimensions }; diff --git a/packages/svelte/src/routes/+page.svelte b/packages/svelte/src/routes/+page.svelte index 9591b599..d905ae62 100644 --- a/packages/svelte/src/routes/+page.svelte +++ b/packages/svelte/src/routes/+page.svelte @@ -48,6 +48,13 @@ // data: { label: 'Input Node 2' }, // position: { x: 150, y: 250 }, // }] + + // const edges = [{ + // id: '1-2', + // type: 'default', + // source: '1', + // target: '2' + // }] diff --git a/packages/svelte/src/utils/index.ts b/packages/svelte/src/utils/index.ts index c63d83af..dface916 100644 --- a/packages/svelte/src/utils/index.ts +++ b/packages/svelte/src/utils/index.ts @@ -82,10 +82,6 @@ export const getHandleBounds = ( const handlesArray = Array.from(handles) as HTMLDivElement[]; const nodeBounds = nodeElement.getBoundingClientRect(); - const nodeOffset = { - x: nodeBounds.width, - y: nodeBounds.height - }; return handlesArray.map((handle): HandleElement => { const handleBounds = handle.getBoundingClientRect(); @@ -93,8 +89,8 @@ export const getHandleBounds = ( return { id: handle.getAttribute('data-handleid'), position: handle.getAttribute('data-handlepos') as unknown as Position, - x: (handleBounds.left - nodeBounds.left - nodeOffset.x) / zoom, - y: (handleBounds.top - nodeBounds.top - nodeOffset.y) / zoom, + x: (handleBounds.left - nodeBounds.left) / zoom, + y: (handleBounds.top - nodeBounds.top) / zoom, ...getDimensions(handle) }; });