From 7a879f3c54a8bbd306d044d7864b123b84fa23c2 Mon Sep 17 00:00:00 2001 From: moklick Date: Mon, 27 Feb 2023 18:01:18 +0100 Subject: [PATCH] feat(svelte): add connectionline and connection functionality --- packages/svelte/src/lib/actions/drag/index.ts | 12 +- .../ConnectionLine/ConnectionLine.svelte | 46 ++++ .../lib/components/ConnectionLine/index.ts | 1 + .../src/lib/components/Handle/handler.ts | 210 ++++++++++++++++++ .../src/lib/components/Handle/index.svelte | 81 ++++++- .../svelte/src/lib/components/Handle/utils.ts | 188 ++++++++++++++++ .../src/lib/container/SvelteFlow.svelte | 5 +- packages/svelte/src/lib/store/index.ts | 180 ++++++++++++++- packages/svelte/src/lib/types/general.ts | 12 +- 9 files changed, 707 insertions(+), 28 deletions(-) create mode 100644 packages/svelte/src/lib/components/ConnectionLine/ConnectionLine.svelte create mode 100644 packages/svelte/src/lib/components/ConnectionLine/index.ts create mode 100644 packages/svelte/src/lib/components/Handle/handler.ts create mode 100644 packages/svelte/src/lib/components/Handle/utils.ts diff --git a/packages/svelte/src/lib/actions/drag/index.ts b/packages/svelte/src/lib/actions/drag/index.ts index 0a647225..a8e24226 100644 --- a/packages/svelte/src/lib/actions/drag/index.ts +++ b/packages/svelte/src/lib/actions/drag/index.ts @@ -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; diff --git a/packages/svelte/src/lib/components/ConnectionLine/ConnectionLine.svelte b/packages/svelte/src/lib/components/ConnectionLine/ConnectionLine.svelte new file mode 100644 index 00000000..a84a023e --- /dev/null +++ b/packages/svelte/src/lib/components/ConnectionLine/ConnectionLine.svelte @@ -0,0 +1,46 @@ + + +{#if $connectionPathStore} + + + + + +{/if} + + \ No newline at end of file diff --git a/packages/svelte/src/lib/components/ConnectionLine/index.ts b/packages/svelte/src/lib/components/ConnectionLine/index.ts new file mode 100644 index 00000000..45de356d --- /dev/null +++ b/packages/svelte/src/lib/components/ConnectionLine/index.ts @@ -0,0 +1 @@ +export { default as ConnectionLine } from './ConnectionLine.svelte'; diff --git a/packages/svelte/src/lib/components/Handle/handler.ts b/packages/svelte/src/lib/components/Handle/handler.ts new file mode 100644 index 00000000..beb473a9 --- /dev/null +++ b/packages/svelte/src/lib/components/Handle/handler.ts @@ -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; + updateConnection: (connection: Partial) => 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); +} diff --git a/packages/svelte/src/lib/components/Handle/index.svelte b/packages/svelte/src/lib/components/Handle/index.svelte index 3658bce5..496c0581 100644 --- a/packages/svelte/src/lib/components/Handle/index.svelte +++ b/packages/svelte/src/lib/components/Handle/index.svelte @@ -1,21 +1,77 @@
+ class:connectable={isConnectable} + on:mousedown={onPointerDown} + on:touchstart={onPointerDown} +>
@@ -46,6 +106,11 @@ border-radius: 100%; } + .connectable { + pointer-events: all; + cursor: crosshair; + } + .bottom { top: 100%; left: 50%; diff --git a/packages/svelte/src/lib/components/Handle/utils.ts b/packages/svelte/src/lib/components/Handle/utils.ts new file mode 100644 index 00000000..adcc231d --- /dev/null +++ b/packages/svelte/src/lib/components/Handle/utils.ts @@ -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((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 | 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((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; +} diff --git a/packages/svelte/src/lib/container/SvelteFlow.svelte b/packages/svelte/src/lib/container/SvelteFlow.svelte index c0c23433..e49ec925 100644 --- a/packages/svelte/src/lib/container/SvelteFlow.svelte +++ b/packages/svelte/src/lib/container/SvelteFlow.svelte @@ -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 @@ +
diff --git a/packages/svelte/src/lib/store/index.ts b/packages/svelte/src/lib/store/index.ts index 8839d3b9..6856f03e 100644 --- a/packages/svelte/src/lib/store/index.ts +++ b/packages/svelte/src/lib/store/index.ts @@ -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; nodeTypesStore: Writable; edgeTypesStore: Writable; + domNodeStore: Writable; + connectionRadiusStore: Writable; + connectionModeStore: Writable; + connectionStore: Writable; + connectionPathStore: Readable; 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) => 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(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 | 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 }; } diff --git a/packages/svelte/src/lib/types/general.ts b/packages/svelte/src/lib/types/general.ts index f19e96d1..e723c5e4 100644 --- a/packages/svelte/src/lib/types/general.ts +++ b/packages/svelte/src/lib/types/general.ts @@ -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[];