feat(svelte): add connectionline and connection functionality

This commit is contained in:
moklick
2023-02-27 18:01:18 +01:00
parent 1dd5613482
commit 7a879f3c54
9 changed files with 707 additions and 28 deletions
+2 -10
View File
@@ -22,7 +22,6 @@ export type NodeDragItem = {
};
type UseDragParams = {
noDragClassName?: string;
handleSelector?: string;
nodeId?: string;
updateNodePositions: (dragItems: NodeDragItem[], d: boolean, p: boolean) => void;
@@ -32,14 +31,7 @@ type UseDragParams = {
export default function drag(
nodeRef: Element,
{
noDragClassName,
handleSelector,
nodeId,
updateNodePositions,
nodesStore,
transformStore
}: UseDragParams
{ handleSelector, nodeId, updateNodePositions, nodesStore, transformStore }: UseDragParams
) {
let dragging = false;
let dragItems: NodeDragItem[] = [];
@@ -119,7 +111,7 @@ export default function drag(
const target = event.target as HTMLDivElement;
const isDraggable =
!event.button &&
(!noDragClassName || !hasSelector(target, `.${noDragClassName}`, nodeRef)) &&
!hasSelector(target, '.nodrag', nodeRef) &&
(!handleSelector || hasSelector(target, handleSelector, nodeRef));
return isDraggable;
@@ -0,0 +1,46 @@
<script lang="ts">
import cc from 'classcat';
import { useStore } from '$lib/store';
import type { ConnectionLineProps } from '$lib/types';
type $$Props = ConnectionLineProps;
const { connectionPathStore, widthStore, heightStore, connectionStore } = useStore();
</script>
{#if $connectionPathStore}
<svg
width={$widthStore}
height={$heightStore}
class="react-flow__connectionline"
>
<g class={cc(['react-flow__connection', $connectionStore.status])}>
<path d={$connectionPathStore} fill="none" class="react-flow__connection-path" />
</g>
</svg>
{/if}
<style>
.react-flow__connectionline {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
overflow: visible;
z-index: 1001;
}
.react-flow__connection {
pointer-events: none;
}
.react-flow__edge-path,
.react-flow__connection-path {
stroke: #b1b1b7;
stroke-width: 1;
fill: none;
}
</style>
@@ -0,0 +1 @@
export { default as ConnectionLine } from './ConnectionLine.svelte';
@@ -0,0 +1,210 @@
import {
getHostForElement,
calcAutoPan,
getEventPosition,
pointToRendererPoint,
rendererPointToPoint
} from '@reactflow/utils';
import type {
OnConnect,
HandleType,
Connection,
ConnectionMode,
Node,
XYPosition,
Transform
} from '@reactflow/system';
import {
getClosestHandle,
getConnectionStatus,
getHandleLookup,
getHandleType,
isValidHandle,
resetRecentHandle,
type ConnectionHandle,
type ValidConnectionFunc
} from './utils';
import { get, type Writable } from 'svelte/store';
import type { ConnectionData } from '$lib/types';
export function handlePointerDown({
event,
handleId,
nodeId,
onConnect,
domNode,
nodes,
connectionMode,
connectionRadius,
isTarget,
transformStore,
panBy,
updateConnection,
cancelConnection,
isValidConnection,
edgeUpdaterType,
onEdgeUpdateEnd
}: {
event: MouseEvent | TouchEvent;
handleId: string | null;
nodeId: string;
onConnect: OnConnect;
isTarget: boolean;
connectionMode: ConnectionMode;
domNode: HTMLDivElement | null;
nodes: Node[];
connectionRadius: number;
isValidConnection: ValidConnectionFunc;
transformStore: Writable<Transform>;
updateConnection: (connection: Partial<ConnectionData>) => void;
cancelConnection: () => void;
panBy: (delta: XYPosition) => void;
edgeUpdaterType?: HandleType;
onEdgeUpdateEnd?: (evt: MouseEvent | TouchEvent) => void;
}): void {
// when react-flow is used inside a shadow root we can't use document
const doc = getHostForElement(event.target as HTMLElement);
let autoPanId = 0;
let prevClosestHandle: ConnectionHandle | null;
const { x, y } = getEventPosition(event);
const clickedHandle = doc?.elementFromPoint(x, y);
const handleType = getHandleType(edgeUpdaterType, clickedHandle);
const containerBounds = domNode?.getBoundingClientRect();
if (!containerBounds || !handleType) {
return;
}
let prevActiveHandle: Element;
let connectionPosition = getEventPosition(event, containerBounds);
let autoPanStarted = false;
let connection: Connection | null = null;
let isValid = false;
let handleDomNode: Element | null = null;
const autoPanOnConnect = true;
const handleLookup = getHandleLookup({
nodes,
nodeId,
handleId,
handleType
});
// when the user is moving the mouse close to the edge of the canvas while connecting we move the canvas
const autoPan = (): void => {
// @todd add prop
if (!autoPanOnConnect) {
return;
}
const [xMovement, yMovement] = calcAutoPan(connectionPosition, containerBounds);
panBy({ x: xMovement, y: yMovement });
autoPanId = requestAnimationFrame(autoPan);
};
updateConnection({
position: connectionPosition,
nodeId,
handleId,
handleType,
status: null
});
// @todo add prop
// onConnectStart?.(event, { nodeId, handleId, handleType });
function onPointerMove(event: MouseEvent | TouchEvent) {
const transform = get(transformStore);
connectionPosition = getEventPosition(event, containerBounds);
prevClosestHandle = getClosestHandle(
pointToRendererPoint(connectionPosition, transform, false, [1, 1]),
connectionRadius,
handleLookup
);
if (!autoPanStarted) {
autoPan();
autoPanStarted = true;
}
const result = isValidHandle(
event,
prevClosestHandle,
connectionMode,
nodeId,
handleId,
isTarget ? 'target' : 'source',
isValidConnection,
doc
);
handleDomNode = result.handleDomNode;
connection = result.connection;
isValid = result.isValid;
updateConnection({
position:
prevClosestHandle && isValid
? rendererPointToPoint(
{
x: prevClosestHandle.x,
y: prevClosestHandle.y
},
transform
)
: connectionPosition,
status: getConnectionStatus(!!prevClosestHandle, isValid)
});
if (!prevClosestHandle && !isValid && !handleDomNode) {
return resetRecentHandle(prevActiveHandle);
}
if (connection.source !== connection.target && handleDomNode) {
resetRecentHandle(prevActiveHandle);
prevActiveHandle = handleDomNode;
// @todo: remove the old class names "react-flow__handle-" in the next major version
handleDomNode.classList.add('connecting');
handleDomNode.classList.toggle('valid', isValid);
}
}
function onPointerUp(event: MouseEvent | TouchEvent) {
if ((prevClosestHandle || handleDomNode) && connection && isValid) {
onConnect?.(connection);
}
// it's important to get a fresh reference from the store here
// in order to get the latest state of onConnectEnd
// @todo add onConnectEnd prop
// getState().onConnectEnd?.(event);
if (edgeUpdaterType) {
onEdgeUpdateEnd?.(event);
}
resetRecentHandle(prevActiveHandle);
cancelConnection();
cancelAnimationFrame(autoPanId);
autoPanStarted = false;
isValid = false;
connection = null;
handleDomNode = null;
doc.removeEventListener('mousemove', onPointerMove as EventListener);
doc.removeEventListener('mouseup', onPointerUp as EventListener);
doc.removeEventListener('touchmove', onPointerMove as EventListener);
doc.removeEventListener('touchend', onPointerUp as EventListener);
}
doc.addEventListener('mousemove', onPointerMove as EventListener);
doc.addEventListener('mouseup', onPointerUp as EventListener);
doc.addEventListener('touchmove', onPointerMove as EventListener);
doc.addEventListener('touchend', onPointerUp as EventListener);
}
@@ -1,21 +1,77 @@
<script lang="ts">
import { getContext } from 'svelte';
import cc from 'classcat';
import { type HandleType, Position } from '@reactflow/system';
import { Position, type Connection, type HandleProps } from '@reactflow/system';
import { isMouseEvent } from '@reactflow/utils';
export let type: HandleType = 'source';
export let position: Position = Position.Top;
export let id: string | null = null;
import { handlePointerDown } from './handler';
import { useStore } from '$lib/store';
type $$Props = HandleProps;
export let type: $$Props['type'] = 'source';
export let position: $$Props['position'] = Position.Top;
export let id: $$Props['id'] = undefined;
export let isConnectable: $$Props['isConnectable'] = true;
export let isValidConnection: $$Props['isValidConnection'] = (_: Connection) => true;
let className: string | null = null;
export { className as class };
const isTarget = type === 'target';
const nodeId = getContext('rf_nodeid');
const nodeId = getContext<string>('rf_nodeid');
const handleId = id || null;
const {
connectionModeStore,
domNodeStore,
nodesStore,
connectionRadiusStore,
transformStore,
addEdge,
panBy,
cancelConnection,
updateConnection
} = useStore();
function onConnectExtended(params: Connection) {
addEdge(params);
// @todo add props
// onConnectAction?.(edgeParams);
// onConnect?.(edgeParams);
};
function onPointerDown(event: MouseEvent | TouchEvent) {
const isMouseTriggered = isMouseEvent(event);
if ((isMouseTriggered && event.button === 0) || !isMouseTriggered) {
handlePointerDown({
event,
handleId,
nodeId,
isTarget,
connectionRadius: $connectionRadiusStore,
domNode: $domNodeStore,
nodes: $nodesStore,
connectionMode: $connectionModeStore,
transformStore,
isValidConnection: isValidConnection!,
onConnect: onConnectExtended,
updateConnection,
cancelConnection,
panBy,
});
}
// if (isMouseTriggered) {
// onMouseDown?.(event);
// } else {
// onTouchStart?.(event);
// }
};
</script>
<div
data-handleid={id}
data-handleid={handleId}
data-nodeid={nodeId}
data-handlepos={position}
data-id={`${nodeId}-${id}-${type}`}
@@ -23,12 +79,16 @@
'react-flow__handle',
`react-flow__handle-${position}`,
'nodrag',
'nopan',
className,
position
])}
class:source={!isTarget}
class:target={isTarget}
>
class:connectable={isConnectable}
on:mousedown={onPointerDown}
on:touchstart={onPointerDown}
>
<slot />
</div>
@@ -46,6 +106,11 @@
border-radius: 100%;
}
.connectable {
pointer-events: all;
cursor: crosshair;
}
.bottom {
top: 100%;
left: 50%;
@@ -0,0 +1,188 @@
import { internalsSymbol, ConnectionMode, type ConnectionStatus } from '@reactflow/system';
import type { Connection, HandleType, XYPosition, Node, NodeHandleBounds } from '@reactflow/system';
import { getEventPosition } from '@reactflow/utils';
export type ConnectionHandle = {
id: string | null;
type: HandleType;
nodeId: string;
x: number;
y: number;
};
export type ValidConnectionFunc = (connection: Connection) => boolean;
// this functions collects all handles and adds an absolute position
// so that we can later find the closest handle to the mouse position
export function getHandles(
node: Node,
handleBounds: NodeHandleBounds,
type: HandleType,
currentHandle: string
): ConnectionHandle[] {
return (handleBounds[type] || []).reduce<ConnectionHandle[]>((res, h) => {
if (`${node.id}-${h.id}-${type}` !== currentHandle) {
res.push({
id: h.id || null,
type,
nodeId: node.id,
x: (node.positionAbsolute?.x ?? 0) + h.x + h.width / 2,
y: (node.positionAbsolute?.y ?? 0) + h.y + h.height / 2
});
}
return res;
}, []);
}
export function getClosestHandle(
pos: XYPosition,
connectionRadius: number,
handles: ConnectionHandle[]
): ConnectionHandle | null {
let closestHandle: ConnectionHandle | null = null;
let minDistance = Infinity;
handles.forEach((handle) => {
const distance = Math.sqrt(Math.pow(handle.x - pos.x, 2) + Math.pow(handle.y - pos.y, 2));
if (distance <= connectionRadius && distance < minDistance) {
minDistance = distance;
closestHandle = handle;
}
});
return closestHandle;
}
type Result = {
handleDomNode: Element | null;
isValid: boolean;
connection: Connection;
};
const nullConnection: Connection = {
source: null,
target: null,
sourceHandle: null,
targetHandle: null
};
// checks if and returns connection in fom of an object { source: 123, target: 312 }
export function isValidHandle(
event: MouseEvent | TouchEvent,
handle: Pick<ConnectionHandle, 'nodeId' | 'id' | 'type'> | null,
connectionMode: ConnectionMode,
fromNodeId: string,
fromHandleId: string | null,
fromType: string,
isValidConnection: ValidConnectionFunc,
doc: Document | ShadowRoot
) {
const isTarget = fromType === 'target';
const handleDomNode = doc.querySelector(
`.react-flow__handle[data-id="${handle?.nodeId}-${handle?.id}-${handle?.type}"]`
);
const { x, y } = getEventPosition(event);
const handleBelow = doc.elementFromPoint(x, y);
const handleToCheck = handleBelow?.classList.contains('react-flow__handle')
? handleBelow
: handleDomNode;
const result: Result = {
handleDomNode: handleToCheck,
isValid: false,
connection: nullConnection
};
if (handleToCheck) {
const handleType = getHandleType(undefined, handleToCheck);
const handleNodeId = handleToCheck.getAttribute('data-nodeid');
const handleId = handleToCheck.getAttribute('data-handleid');
const connection: Connection = {
source: isTarget ? handleNodeId : fromNodeId,
sourceHandle: isTarget ? handleId : fromHandleId,
target: isTarget ? fromNodeId : handleNodeId,
targetHandle: isTarget ? fromHandleId : handleId
};
result.connection = connection;
// in strict mode we don't allow target to target or source to source connections
const isValid =
connectionMode === ConnectionMode.Strict
? (isTarget && handleType === 'source') || (!isTarget && handleType === 'target')
: handleNodeId !== fromNodeId || handleId !== fromHandleId;
if (isValid) {
result.isValid = isValidConnection(connection);
}
}
return result;
}
type GetHandleLookupParams = {
nodes: Node[];
nodeId: string;
handleId: string | null;
handleType: string;
};
export function getHandleLookup({ nodes, nodeId, handleId, handleType }: GetHandleLookupParams) {
return nodes.reduce<ConnectionHandle[]>((res, node) => {
if (node[internalsSymbol]) {
const { handleBounds } = node[internalsSymbol];
let sourceHandles: ConnectionHandle[] = [];
let targetHandles: ConnectionHandle[] = [];
if (handleBounds) {
sourceHandles = getHandles(
node,
handleBounds,
'source',
`${nodeId}-${handleId}-${handleType}`
);
targetHandles = getHandles(
node,
handleBounds,
'target',
`${nodeId}-${handleId}-${handleType}`
);
}
res.push(...sourceHandles, ...targetHandles);
}
return res;
}, []);
}
export function getHandleType(
edgeUpdaterType: HandleType | undefined,
handleDomNode: Element | null
): HandleType | null {
if (edgeUpdaterType) {
return edgeUpdaterType;
} else if (handleDomNode?.classList.contains('target')) {
return 'target';
} else if (handleDomNode?.classList.contains('source')) {
return 'source';
}
return null;
}
export function resetRecentHandle(handleDomNode: Element): void {
handleDomNode?.classList.remove('valid', 'connecting');
}
export function getConnectionStatus(isInsideConnectionRadius: boolean, isHandleValid: boolean) {
let connectionStatus = null;
if (isHandleValid) {
connectionStatus = 'valid';
} else if (isInsideConnectionRadius && !isHandleValid) {
connectionStatus = 'invalid';
}
return connectionStatus as ConnectionStatus;
}
@@ -11,6 +11,7 @@
import UserSelection from '$lib/components/UserSelection/index.svelte';
import NodeSelection from '$lib/components/NodeSelection/index.svelte';
import { KeyHandler } from '$lib/components/KeyHandler';
import { ConnectionLine } from '$lib/components/ConnectionLine';
import type { SvelteFlowProps } from '$lib/types';
type $$Props = SvelteFlowProps;
@@ -26,7 +27,7 @@
let className: $$Props['class'] = undefined;
export { className as class };
let domNode: Element;
let domNode: HTMLDivElement;
const store = createStore({
fitView,
@@ -41,6 +42,7 @@
const { width, height } = domNode.getBoundingClientRect();
store.widthStore.set(width);
store.heightStore.set(height);
store.domNodeStore.set(domNode);
// @todo: is this a svelte way for two way binding?
store.nodesStore.subscribe((ns) => {
@@ -69,6 +71,7 @@
<Pane>
<Viewport>
<EdgeRenderer />
<ConnectionLine />
<div class="react-flow__edgelabel-renderer" />
<NodeRenderer />
<NodeSelection />
+172 -8
View File
@@ -12,19 +12,32 @@ import {
type D3SelectionInstance,
type ViewportHelperFunctionOptions,
type SelectionRect,
type Node as RFNode
type Node as RFNode,
type Connection,
ConnectionMode,
type XYPosition,
type CoordinateExtent,
ConnectionLineType
} from '@reactflow/system';
import { fitView, getConnectedEdges, getD3Transition, getDimensions } from '@reactflow/utils';
import {
fitView as fitViewUtil,
getConnectedEdges,
getD3Transition,
getDimensions,
addEdge as addEdgeUtil
} from '@reactflow/utils';
import { zoomIdentity } from 'd3-zoom';
import { getHandleBounds } from '../../utils';
import { getEdgePositions, getHandle, getNodeData } from '$lib/container/EdgeRenderer/utils';
import DefaultNode from '$lib/components/nodes/DefaultNode.svelte';
import InputNode from '$lib/components/nodes/InputNode.svelte';
import OutputNode from '$lib/components/nodes/OutputNode.svelte';
import type { EdgeTypes, NodeTypes, Node, Edge, WrapEdgeProps } from '$lib/types';
import type { EdgeTypes, NodeTypes, Node, Edge, WrapEdgeProps, ConnectionData } from '$lib/types';
import BezierEdge from '$lib/components/edges/BezierEdge.svelte';
import StraightEdge from '$lib/components/edges/StraightEdge.svelte';
import SmoothStepEdge from '$lib/components/edges/SmoothStepEdge.svelte';
import { getBezierPath, getSmoothStepPath, getStraightPath } from '@reactflow/edge-utils';
export const key = Symbol();
@@ -58,8 +71,14 @@ type SvelteFlowStore = {
deleteKeyPressedStore: Writable<boolean>;
nodeTypesStore: Writable<NodeTypes>;
edgeTypesStore: Writable<EdgeTypes>;
domNodeStore: Writable<HTMLDivElement | null>;
connectionRadiusStore: Writable<number>;
connectionModeStore: Writable<ConnectionMode>;
connectionStore: Writable<ConnectionData>;
connectionPathStore: Readable<string | null>;
setNodes: (nodes: Node[]) => void;
setEdges: (edges: Edge[]) => void;
addEdge: (edge: Edge | Connection) => void;
zoomIn: (options?: ViewportHelperFunctionOptions) => void;
zoomOut: (options?: ViewportHelperFunctionOptions) => void;
fitView: (options?: ViewportHelperFunctionOptions) => boolean;
@@ -71,6 +90,17 @@ type SvelteFlowStore = {
updateNodeDimensions: (updates: NodeDimensionUpdate[]) => void;
resetSelectedElements: () => void;
addSelectedNodes: (ids: string[]) => void;
panBy: (delta: XYPosition) => void;
updateConnection: (connection: Partial<ConnectionData>) => void;
cancelConnection: () => void;
};
const initConnectionData = {
nodeId: null,
handleId: null,
handleType: null,
position: null,
status: null
};
export function createStore({
@@ -112,6 +142,11 @@ export function createStore({
default: edgeTypes.default || BezierEdge
});
const transformStore = writable(transform);
const connectionModeStore = writable(ConnectionMode.Strict);
const domNodeStore = writable(null);
const connectionStore = writable<ConnectionData>(initConnectionData);
const connectionRadiusStore = writable(25);
const connectionLineTypeStore = writable(ConnectionLineType.Bezier);
let fitViewOnInitDone = false;
@@ -172,10 +207,84 @@ export function createStore({
.filter((e) => e !== null) as WrapEdgeProps[];
});
const oppositePosition = {
[Position.Left]: Position.Right,
[Position.Right]: Position.Left,
[Position.Top]: Position.Bottom,
[Position.Bottom]: Position.Top
};
const connectionPathStore = derived(
[connectionStore, connectionLineTypeStore, connectionModeStore, nodesStore, transformStore],
([
$connectionStore,
$connectionLineTypeStore,
$connectionModeStore,
$nodesStore,
$transformStore
]) => {
if (!$connectionStore.nodeId) {
return null;
}
const fromNode = $nodesStore.find((n) => n.id === $connectionStore.nodeId);
const fromHandleBounds = fromNode?.[internalsSymbol]?.handleBounds;
const handleBoundsStrict = fromHandleBounds?.[$connectionStore.handleType || 'source'] || [];
const handleBoundsLoose = handleBoundsStrict
? handleBoundsStrict
: fromHandleBounds?.[$connectionStore.handleType === 'source' ? 'target' : 'source']!;
const handleBounds =
$connectionModeStore === ConnectionMode.Strict ? handleBoundsStrict : handleBoundsLoose;
const fromHandle = $connectionStore.handleId
? handleBounds.find((d) => d.id === $connectionStore.handleId)
: handleBounds[0];
const fromHandleX = fromHandle
? fromHandle.x + fromHandle.width / 2
: (fromNode?.width ?? 0) / 2;
const fromHandleY = fromHandle ? fromHandle.y + fromHandle.height / 2 : fromNode?.height ?? 0;
const fromX = (fromNode?.positionAbsolute?.x ?? 0) + fromHandleX;
const fromY = (fromNode?.positionAbsolute?.y ?? 0) + fromHandleY;
const fromPosition = fromHandle?.position;
const toPosition = fromPosition ? oppositePosition[fromPosition] : undefined;
const pathParams = {
sourceX: fromX,
sourceY: fromY,
sourcePosition: fromPosition,
targetX: (($connectionStore.position?.x ?? 0) - $transformStore[0]) / $transformStore[2],
targetY: (($connectionStore.position?.y ?? 0) - $transformStore[1]) / $transformStore[2],
targetPosition: toPosition
};
let path = '';
if ($connectionLineTypeStore === ConnectionLineType.Bezier) {
// we assume the destination position is opposite to the source position
[path] = getBezierPath(pathParams);
} else if ($connectionLineTypeStore === ConnectionLineType.Step) {
[path] = getSmoothStepPath({
...pathParams,
borderRadius: 0
});
} else if ($connectionLineTypeStore === ConnectionLineType.SmoothStep) {
[path] = getSmoothStepPath(pathParams);
} else {
[path] = getStraightPath(pathParams);
}
return path;
}
);
function setEdges(edges: Edge[]) {
edgesStore.set(edges);
}
function addEdge(edgeParams: Edge | Connection) {
const edges = get(edgesStore);
edgesStore.set(addEdgeUtil(edgeParams, edges));
}
function setNodes(nodes: Node[]) {
nodesStore.update((currentNodes) => {
const nextNodes = nodes.map((n) => {
@@ -254,7 +363,7 @@ export function createStore({
const { zoom: d3Zoom, selection: d3Selection } = get(d3Store);
fitViewOnInitDone =
fitViewOnInitDone || (fitViewOnInit && !!d3Zoom && !!d3Selection && _fitView());
fitViewOnInitDone || (fitViewOnInit && !!d3Zoom && !!d3Selection && fitView());
nodesStore.set(nextNodes);
}
@@ -274,14 +383,14 @@ export function createStore({
}
}
function _fitView() {
function fitView() {
const { zoom: d3Zoom, selection: d3Selection } = get(d3Store);
if (!d3Zoom || !d3Selection) {
return false;
}
return fitView(
return fitViewUtil(
{
nodes: get(nodesStore) as RFNode[],
width: get(widthStore),
@@ -373,6 +482,52 @@ export function createStore({
);
}
function panBy(delta: XYPosition) {
const { zoom: d3Zoom, selection: d3Selection } = get(d3Store);
const transform = get(transformStore);
const width = get(widthStore);
const height = get(heightStore);
if (!d3Zoom || !d3Selection || (!delta.x && !delta.y)) {
return;
}
const nextTransform = zoomIdentity
.translate(transform[0] + delta.x, transform[1] + delta.y)
.scale(transform[2]);
const extent: CoordinateExtent = [
[0, 0],
[width, height]
];
const constrainedTransform = d3Zoom?.constrain()(nextTransform, extent, [
[Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY],
[Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY]
]);
d3Zoom.transform(d3Selection, constrainedTransform);
}
function updateConnection(connectionUpdate: Partial<ConnectionData> | null) {
const currentConnectionData = get(connectionStore);
const nextConnectionData = currentConnectionData
? {
...initConnectionData,
...currentConnectionData,
...connectionUpdate
}
: {
...initConnectionData,
...connectionUpdate
};
connectionStore.set(nextConnectionData);
}
function cancelConnection() {
updateConnection(initConnectionData);
}
return {
nodesStore,
edgesStore,
@@ -391,15 +546,24 @@ export function createStore({
selectionMode,
nodeTypesStore,
edgeTypesStore,
connectionModeStore,
domNodeStore,
connectionStore,
connectionRadiusStore,
connectionPathStore,
setNodes,
setEdges,
addEdge,
updateNodePositions,
updateNodeDimensions,
zoomIn,
zoomOut,
fitView: _fitView,
fitView,
resetSelectedElements,
addSelectedNodes
addSelectedNodes,
panBy,
updateConnection,
cancelConnection
};
}
+11 -1
View File
@@ -1,11 +1,21 @@
import type { Node, NodeTypes } from './nodes';
import type { Edge } from './edges';
import type { ShortcutModifierDefinition } from '@svelte-put/shortcut';
import type { Edge, HandleType, XYPosition } from '@reactflow/system';
export type KeyModifier = ShortcutModifierDefinition;
export type KeyDefinitionObject = { key: string; modifier?: KeyModifier };
export type KeyDefinition = string | KeyDefinitionObject;
export type ConnectionData = {
position: XYPosition | null;
nodeId: string | null;
handleId: string | null;
handleType: HandleType | null;
status: string | null;
};
export type ConnectionLineProps = {};
export type SvelteFlowProps = {
nodes: Node[];
edges: Edge[];