diff --git a/packages/core/src/components/ConnectionLine/ConnectionLine.vue b/packages/core/src/components/ConnectionLine/ConnectionLine.vue index db72d875..e457c682 100644 --- a/packages/core/src/components/ConnectionLine/ConnectionLine.vue +++ b/packages/core/src/components/ConnectionLine/ConnectionLine.vue @@ -8,6 +8,7 @@ const { sourceNode } = defineProps<{ sourceNode: GraphNode }>() const { connectionMode, connectionStartHandle, + connectionEndHandle, connectionPosition, connectionLineType, connectionLineStyle, @@ -30,9 +31,7 @@ const handleId = $computed(() => connectionStartHandle!.handleId) const type = computed(() => connectionStartHandle!.type) -const targetNode = $computed( - () => (connectionStartHandle?.result && findNode(connectionStartHandle.result.connection.target)) || null, -) +const targetNode = $computed(() => (connectionEndHandle?.handleId && findNode(connectionEndHandle.handleId)) || null) const sourceHandle = $computed( () => @@ -45,13 +44,13 @@ const sourceHandle = $computed( const targetHandle = $computed(() => { return ( (targetNode && - connectionStartHandle?.result?.handleId && + connectionEndHandle?.handleId && ((connectionMode === ConnectionMode.Strict ? targetNode.handleBounds[type.value === 'source' ? 'target' : 'source']?.find( - (d) => d.id === connectionStartHandle?.result?.handleId, + (d) => d.id === connectionEndHandle?.handleId, ) : [...(targetNode.handleBounds.source || []), ...(targetNode.handleBounds.target || [])]?.find( - (d) => d.id === connectionStartHandle?.result?.handleId, + (d) => d.id === connectionEndHandle?.handleId, )) || targetNode.handleBounds[type.value ?? 'target']?.[0])) || null diff --git a/packages/core/src/components/Handle/Handle.vue b/packages/core/src/components/Handle/Handle.vue index 687cef5c..82f6b228 100644 --- a/packages/core/src/components/Handle/Handle.vue +++ b/packages/core/src/components/Handle/Handle.vue @@ -21,7 +21,8 @@ const emits = defineEmits<{ const type = toRef(props, 'type', 'source') -const { connectionStartHandle, vueFlowRef, nodesConnectable, noDragClassName } = useVueFlow() +const { connectionStartHandle, connectionClickStartHandle, connectionEndHandle, vueFlowRef, nodesConnectable, noDragClassName } = + useVueFlow() const { id: nodeId, node, nodeEl, connectedEdges } = useNode() @@ -58,10 +59,19 @@ const isConnectable = computed(() => { const isConnecting = computed( () => - connectionStartHandle.value && - connectionStartHandle.value.nodeId === nodeId && - connectionStartHandle.value.handleId === handleId.value && - connectionStartHandle.value.type === type.value, + (connectionStartHandle.value?.nodeId === nodeId && + connectionStartHandle.value?.handleId === handleId.value && + connectionStartHandle.value?.type === type.value) || + (connectionEndHandle.value?.nodeId === nodeId && + connectionEndHandle.value?.handleId === handleId.value && + connectionEndHandle.value?.type === type.value), +) + +const isClickConnecting = computed( + () => + connectionClickStartHandle.value?.nodeId === nodeId && + connectionClickStartHandle.value?.handleId === handleId.value && + connectionClickStartHandle.value?.type === type.value, ) // set up handle bounds if they don't exist yet and the node has been initialized (i.e. the handle was added after the node has already been mounted) @@ -109,7 +119,7 @@ function onPointerDown(event: MouseEvent | TouchEvent) { } function onClick(event: MouseEvent) { - if (!connectionStartHandle && !connectableStart) { + if (!nodeId || (!connectionStartHandle && !connectableStart)) { return } @@ -141,10 +151,10 @@ export default { type, { connectable: isConnectable, - connecting: isConnecting, + connecting: isClickConnecting, connectablestart: connectableStart, connectableend: connectableEnd, - connectionindicator: (isConnectable && connectableStart && !isConnecting) || (connectableEnd && isConnecting), + connectionindicator: isConnectable && ((connectableStart && !isConnecting) || (connectableEnd && isConnecting)), }, ]" @mousedown="onPointerDown" diff --git a/packages/core/src/composables/useHandle.ts b/packages/core/src/composables/useHandle.ts index 7b1e976b..4bddf99b 100644 --- a/packages/core/src/composables/useHandle.ts +++ b/packages/core/src/composables/useHandle.ts @@ -60,7 +60,7 @@ export default function useHandle({ const doc = getHostForElement(event.target as HTMLElement) if ((isMouseTriggered && event.button === 0) || !isMouseTriggered) { - const node = findNode(unref(nodeId)) + const node = findNode(nodeId.value) let isValidConnectionHandler = isValidConnection || isValidConnectionProp.value || alwaysValid @@ -74,7 +74,7 @@ export default function useHandle({ const { x, y } = getEventPosition(event) const clickedHandle = doc?.elementFromPoint(x, y) - const handleType = getHandleType(unref(edgeUpdaterType), clickedHandle) + const handleType = getHandleType(edgeUpdaterType.value, clickedHandle) const containerBounds = vueFlowRef.value?.getBoundingClientRect() if (!containerBounds || !handleType) { @@ -160,7 +160,7 @@ export default function useHandle({ viewport.value, ) : connectionPosition, - result, + result.endHandle, getConnectionStatus(!!prevClosestHandle, isValid), ) @@ -229,11 +229,11 @@ export default function useHandle({ if (!connectionClickStartHandle.value) { emits.clickConnectStart({ event, nodeId: nodeId.value, handleId: handleId.value }) - startConnection({ nodeId: unref(nodeId), type: unref(type), handleId: unref(handleId) }, undefined, event, true) + startConnection({ nodeId: nodeId.value, type: unref(type), handleId: handleId.value }, undefined, event, true) } else { let isValidConnectionHandler = isValidConnection || isValidConnectionProp.value || alwaysValid - const node = findNode(unref(nodeId)) + const node = findNode(nodeId.value) if (!isValidConnectionHandler && node) { isValidConnectionHandler = (!isTarget ? node.isValidTargetPos : node.isValidSourcePos) || alwaysValid diff --git a/packages/core/src/store/actions.ts b/packages/core/src/store/actions.ts index e045037f..b2b7f528 100644 --- a/packages/core/src/store/actions.ts +++ b/packages/core/src/store/actions.ts @@ -483,15 +483,16 @@ export function useActions(state: State, getters: ComputedGetters): Actions { state.connectionStartHandle = startHandle } + state.connectionEndHandle = null state.connectionStatus = null if (position) state.connectionPosition = position } - const updateConnection: Actions['updateConnection'] = (position, result, status = null) => { + const updateConnection: Actions['updateConnection'] = (position, result = null, status = null) => { if (state.connectionStartHandle) { state.connectionPosition = position - state.connectionStartHandle.result = result + state.connectionEndHandle = result state.connectionStatus = status } } diff --git a/packages/core/src/store/state.ts b/packages/core/src/store/state.ts index c14729c6..11ea7816 100644 --- a/packages/core/src/store/state.ts +++ b/packages/core/src/store/state.ts @@ -85,6 +85,7 @@ function defaultState(): State { }, connectionMode: ConnectionMode.Loose, connectionStartHandle: null, + connectionEndHandle: null, connectionClickStartHandle: null, connectionPosition: { x: NaN, y: NaN }, connectionRadius: 20, diff --git a/packages/core/src/types/handle.ts b/packages/core/src/types/handle.ts index 3e95bcea..d89c376c 100644 --- a/packages/core/src/types/handle.ts +++ b/packages/core/src/types/handle.ts @@ -19,17 +19,16 @@ export interface ConnectionHandle { } export interface ValidHandleResult { - handleId: string | null + endHandle: ConnectingHandle | null handleDomNode: Element | null isValid: boolean connection: Connection } -export interface StartHandle { +export interface ConnectingHandle { nodeId: string type: HandleType handleId: string | null - result?: ValidHandleResult } /** A valid connection function can determine if an attempted connection is valid or not, i.e. abort creating a new edge */ diff --git a/packages/core/src/types/store.ts b/packages/core/src/types/store.ts index ca50c228..6523317a 100644 --- a/packages/core/src/types/store.ts +++ b/packages/core/src/types/store.ts @@ -27,7 +27,7 @@ import type { CoordinateExtent, GraphNode, Node } from './node' import type { D3Selection, D3Zoom, D3ZoomHandler, PanOnScrollMode, ViewportFunctions, ViewportTransform } from './zoom' import type { CustomEvent, FlowHooks, FlowHooksEmit, FlowHooksOn } from './hooks' import type { EdgeChange, NodeChange, NodeDragItem } from './changes' -import type { StartHandle, ValidConnectionFunc, ValidHandleResult } from './handle' +import type { ConnectingHandle, ValidConnectionFunc } from './handle' export interface UpdateNodeDimensionsParams { id: string @@ -84,8 +84,9 @@ export interface State extends Omit { connectionLineType: ConnectionLineType | null /** @deprecated use {@link ConnectionLineOptions.style} */ connectionLineStyle: CSSProperties | null - connectionStartHandle: StartHandle | null - connectionClickStartHandle: StartHandle | null + connectionStartHandle: ConnectingHandle | null + connectionEndHandle: ConnectingHandle | null + connectionClickStartHandle: ConnectingHandle | null connectionPosition: XYPosition connectionRadius: number connectionStatus: ConnectionStatus | null @@ -247,9 +248,14 @@ export interface Actions extends ViewportFunctions { /** force update node internal data, if handle bounds are incorrect, you might want to use this */ updateNodeInternals: UpdateNodeInternals /** start a connection */ - startConnection: (startHandle: StartHandle, position?: XYPosition, event?: MouseEvent | TouchEvent, isClick?: boolean) => void + startConnection: ( + startHandle: ConnectingHandle, + position?: XYPosition, + event?: MouseEvent | TouchEvent, + isClick?: boolean, + ) => void /** update connection position */ - updateConnection: (position: XYPosition, result?: ValidHandleResult, status?: ConnectionStatus | null) => void + updateConnection: (position: XYPosition, result?: ConnectingHandle | null, status?: ConnectionStatus | null) => void /** end (or cancel) a connection */ endConnection: (event?: MouseEvent | TouchEvent, isClick?: boolean) => void diff --git a/packages/core/src/utils/handle.ts b/packages/core/src/utils/handle.ts index 651eca74..85d890c6 100644 --- a/packages/core/src/utils/handle.ts +++ b/packages/core/src/utils/handle.ts @@ -78,7 +78,7 @@ export function isValidHandle( connectionMode: ConnectionMode, fromNodeId: string, fromHandleId: string | null, - fromType: string, + fromType: HandleType, isValidConnection: ValidConnectionFunc, doc: Document | ShadowRoot, edges: GraphEdge[], @@ -92,10 +92,10 @@ export function isValidHandle( const handleToCheck = handleBelow?.classList.contains('vue-flow__handle') ? handleBelow : handleDomNode const result: ValidHandleResult = { - handleId: null, handleDomNode: handleToCheck, isValid: false, connection: { source: '', target: '', sourceHandle: null, targetHandle: null }, + endHandle: null, } if (handleToCheck) { @@ -113,7 +113,6 @@ export function isValidHandle( } result.connection = connection - result.handleId = handleId const isConnectable = connectable && connectableEnd @@ -125,6 +124,12 @@ export function isValidHandle( : handleNodeId !== fromNodeId || handleId !== fromHandleId) if (isValid) { + result.endHandle = { + nodeId: handleNodeId, + handleId, + type: handleType as HandleType, + } + result.isValid = isValidConnection(connection, { edges, sourceNode: findNode(connection.source)!,