fix(edges): use derived store for edge handling
This commit is contained in:
@@ -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}`;
|
||||
|
||||
</script>
|
||||
|
||||
<g
|
||||
@@ -15,7 +12,7 @@
|
||||
data-id={id}
|
||||
>
|
||||
<path
|
||||
d={path}
|
||||
d={`M ${sourceX},${sourceY}L ${targetX},${targetY}`}
|
||||
fill="none"
|
||||
class="react-flow__edge-path"
|
||||
/>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -10,6 +10,6 @@
|
||||
</script>
|
||||
|
||||
<Handle type="target" position={targetPosition} />
|
||||
{data?.label}
|
||||
{data?.label}
|
||||
<Handle type="source" position={sourcePosition} />
|
||||
|
||||
|
||||
@@ -1,171 +0,0 @@
|
||||
<script lang="ts">
|
||||
type EdgePositions = {
|
||||
sourceX: number;
|
||||
sourceY: number;
|
||||
targetX: number;
|
||||
targetY: number;
|
||||
}
|
||||
|
||||
import Edge from '$lib/components/edges/BaseEdge.svelte';
|
||||
import { useStore } from '$lib/store';
|
||||
import { type NodeHandleBounds, type Rect, type Node, type HandleElement, Position, type XYPosition } from '@reactflow/core';
|
||||
import { internalsSymbol } from '@reactflow/core';
|
||||
|
||||
const { edgesStore, widthStore, heightStore, nodesStore } = useStore();
|
||||
|
||||
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,
|
||||
];
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
function getEdgePositions(
|
||||
sourceNodeRect: Rect,
|
||||
sourceHandle: HandleElement,
|
||||
sourcePosition: Position,
|
||||
targetNodeRect: Rect,
|
||||
targetHandle: HandleElement,
|
||||
targetPosition: Position
|
||||
): EdgePositions {
|
||||
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,
|
||||
};
|
||||
};
|
||||
|
||||
let edgesWithData: any = [];
|
||||
|
||||
$: {
|
||||
$nodesStore
|
||||
edgesWithData = $edgesStore.map((edge) => {
|
||||
const sourceNode = $nodesStore.find((node) => node.id === edge.source);
|
||||
const targetNode = $nodesStore.find((node) => node.id === edge.target);
|
||||
const [sourceNodeRect, sourceHandleBounds, sourceIsValid] = getNodeData(sourceNode);
|
||||
const [targetNodeRect, targetHandleBounds, targetIsValid] = getNodeData(targetNode);
|
||||
|
||||
if (!sourceIsValid || !targetIsValid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let 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
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<svg
|
||||
width={$widthStore}
|
||||
height={$heightStore}
|
||||
class="react-flow__edges react-flow__container"
|
||||
>
|
||||
{#each edgesWithData as edge}
|
||||
{#if edge}
|
||||
<Edge {...edge} />
|
||||
{/if}
|
||||
{/each}
|
||||
</svg>
|
||||
|
||||
<style>
|
||||
.react-flow__edges {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<script lang="ts">
|
||||
import Edge from '$lib/components/edges/BaseEdge.svelte';
|
||||
import { useStore } from '$lib/store';
|
||||
|
||||
const { edgesWithDataStore, widthStore, heightStore } = useStore();
|
||||
|
||||
</script>
|
||||
|
||||
<svg
|
||||
width={$widthStore}
|
||||
height={$heightStore}
|
||||
class="react-flow__edges react-flow__container"
|
||||
>
|
||||
{#each $edgesWithDataStore as edge}
|
||||
<Edge {...edge} />
|
||||
{/each}
|
||||
</svg>
|
||||
|
||||
<style>
|
||||
.react-flow__edges {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
pointer-events: none;
|
||||
overflow: visible;
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
</style>
|
||||
@@ -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<CreateStoreProps['nodes']>;
|
||||
edgesStore: Writable<CreateStoreProps['edges']>;
|
||||
heightStore: Writable<number>;
|
||||
widthStore: Writable<number>;
|
||||
transformStore: Writable<Transform>;
|
||||
edgesWithDataStore: Readable<EdgeWithData[]>;
|
||||
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
|
||||
};
|
||||
|
||||
@@ -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'
|
||||
// }]
|
||||
</script>
|
||||
|
||||
<SvelteFlow {nodes} {edges} />
|
||||
|
||||
@@ -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)
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user