diff --git a/packages/core/src/components/Handle/Handle.vue b/packages/core/src/components/Handle/Handle.vue index c1ee3412..8563ec94 100644 --- a/packages/core/src/components/Handle/Handle.vue +++ b/packages/core/src/components/Handle/Handle.vue @@ -6,7 +6,9 @@ import { ConnectionMode } from '../../types' import type { HandleProps } from '../../types/handle' import { getDimensions } from '../../utils' -const { type = 'source', position = 'top' as Position, connectable = true, id, isValidConnection } = defineProps() +const { position = 'top' as Position, connectable = true, id, isValidConnection, ...props } = defineProps() + +const type = toRef(props, 'type', 'source') const { connectionStartHandle, connectionMode, vueFlowRef, nodesConnectable } = $(useVueFlow()) @@ -16,6 +18,13 @@ const handle = ref() const handleId = $computed(() => id ?? (connectionMode === ConnectionMode.Strict ? null : `${nodeId}__handle-${position}`)) +const { onMouseDown, onClick } = useHandle({ + nodeId, + handleId, + isValidConnection, + type, +}) + const isConnectable = computed(() => { if (isString(connectable) && connectable === 'single') { return !connectedEdges.value.some((edge) => edge[type] === nodeId && edge[`${type}Handle`] === handleId) @@ -26,18 +35,8 @@ const isConnectable = computed(() => { return connectable }) -const { onMouseDown, onClick } = useHandle() - -const onMouseDownHandler = (event: MouseEvent) => { - onMouseDown(event, handleId, nodeId, type === 'target', isValidConnection, undefined) -} - -const onClickHandler = (event: MouseEvent) => { - onClick(event, handleId ?? null, nodeId, type, isValidConnection) -} - onMounted(() => { - const existingBounds = node.handleBounds[type]?.find((b) => b.id === handleId) + const existingBounds = node.handleBounds[type.value]?.find((b) => b.id === handleId) if (!vueFlowRef || existingBounds) return const viewportNode = vueFlowRef.querySelector('.vue-flow__transformationpane') @@ -59,7 +58,7 @@ onMounted(() => { ...getDimensions(handle.value), } - node.handleBounds[type] = [...(node.handleBounds[type] ?? []), nextBounds] + node.handleBounds[type.value] = [...(node.handleBounds[type.value] ?? []), nextBounds] }) @@ -90,8 +89,8 @@ export default { connectionStartHandle.type === type, }, ]" - @mousedown="onMouseDownHandler" - @click="onClickHandler" + @mousedown="onMouseDown" + @click="onClick" > diff --git a/packages/core/src/composables/useHandle.ts b/packages/core/src/composables/useHandle.ts index 8aed86aa..8c9134b4 100644 --- a/packages/core/src/composables/useHandle.ts +++ b/packages/core/src/composables/useHandle.ts @@ -1,6 +1,7 @@ +import type { MaybeRef } from '@vueuse/core' import { isFunction } from '@vueuse/core' import useVueFlow from './useVueFlow' -import { getHostForElement } from '~/utils' +import { connectionExists, getHostForElement } from '~/utils' import type { Connection, Getters, GraphEdge, HandleType, ValidConnectionFunc } from '~/types' import { ConnectionMode } from '~/types' @@ -11,6 +12,16 @@ interface Result { isHoveringHandle: boolean } +interface UseHandleProps { + handleId: MaybeRef + nodeId: MaybeRef + type: MaybeRef + isValidConnection?: ValidConnectionFunc + elementEdgeUpdaterType?: MaybeRef + onEdgeUpdate?: (connection: Connection) => void + onEdgeUpdateEnd?: () => void +} + // checks if element below mouse is a handle and returns connection in form of an object { source: 123, target: 312 } export const checkElementBelowIsValid = ( event: MouseEvent, @@ -76,7 +87,21 @@ const resetRecentHandle = (hoveredHandle: Element): void => { hoveredHandle?.classList.remove('vue-flow__handle-connecting') } -export default () => { +/** + * This composable can be used to create custom Handle components + * + * It provides an `onClick` and `onMouseDown` handler that you can bind to your custom handle, so it will behave like the default handle. + * + */ +export default function useHandle({ + handleId, + nodeId, + isValidConnection, + type, + elementEdgeUpdaterType, + onEdgeUpdateEnd, + onEdgeUpdate, +}: UseHandleProps) { const { edges, connectOnClick, @@ -91,24 +116,17 @@ export default () => { vueFlowRef, } = $(useVueFlow()) + const isTarget = $computed(() => unref(type) === 'target') + let recentHoveredHandle: Element - const onMouseDown = ( - event: MouseEvent, - handleId: string | null, - nodeId: string, - isTarget: boolean, - isValidConnection?: ValidConnectionFunc, - elementEdgeUpdaterType?: HandleType, - onEdgeUpdate?: (connection: Connection) => void, - onEdgeUpdateEnd?: () => void, - ) => { + const onMouseDown = (event: MouseEvent) => { const doc = getHostForElement(event.target as HTMLElement) if (!doc) return let validConnectFunc = isValidConnection - const node = getNode(nodeId) + const node = getNode(unref(nodeId)) if (node && (typeof node.connectable === 'undefined' ? nodesConnectable : node.connectable) === false) return @@ -128,9 +146,9 @@ export default () => { startConnection( { - nodeId, - handleId, - type: handleType, + nodeId: unref(nodeId), + handleId: unref(handleId), + type: unref(handleType), }, { x: event.clientX - containerBounds.left, @@ -148,9 +166,9 @@ export default () => { const { connection, elementBelow, isValid, isHoveringHandle } = checkElementBelowIsValid( event, connectionMode, - isTarget, - nodeId, - handleId, + unref(isTarget), + unref(nodeId), + unref(handleId), validConnectFunc, doc, edges, @@ -172,9 +190,9 @@ export default () => { const { connection, isValid } = checkElementBelowIsValid( event, connectionMode, - isTarget, - nodeId, - handleId, + unref(isTarget), + unref(nodeId), + unref(handleId), validConnectFunc, doc, edges, @@ -202,25 +220,19 @@ export default () => { doc.addEventListener('mouseup', onMouseUp as EventListenerOrEventListenerObject) } - const onClick = ( - event: MouseEvent, - handleId: string | null, - nodeId: string, - handleType: HandleType, - isValidConnection?: ValidConnectionFunc, - ) => { + const onClick = (event: MouseEvent) => { if (!connectOnClick) return if (!connectionStartHandle) { - startConnection({ nodeId, type: handleType, handleId }, undefined, event) + startConnection({ nodeId: unref(nodeId), type: unref(type), handleId: unref(handleId) }, undefined, event) } else { let validConnectFunc: ValidConnectionFunc = isValidConnection ?? (() => true) - const node = getNode(nodeId) + const node = getNode(unref(nodeId)) if (node && (typeof node.connectable === 'undefined' ? nodesConnectable : node.connectable) === false) return if (!isValidConnection) { - if (node) validConnectFunc = (handleType !== 'target' ? node.isValidTargetPos : node.isValidSourcePos) ?? (() => true) + if (node) validConnectFunc = (!isTarget ? node.isValidTargetPos : node.isValidSourcePos) ?? (() => true) } const doc = getHostForElement(event.target as HTMLElement) diff --git a/packages/core/src/container/EdgeRenderer/EdgeRenderer.vue b/packages/core/src/container/EdgeRenderer/EdgeRenderer.vue index 30d0a94e..b8ba2339 100644 --- a/packages/core/src/container/EdgeRenderer/EdgeRenderer.vue +++ b/packages/core/src/container/EdgeRenderer/EdgeRenderer.vue @@ -4,7 +4,7 @@ import EdgeWrapper from '../../components/Edges/Wrapper' import ConnectionLine from '../../components/ConnectionLine/ConnectionLine.vue' import { useEdgeHooks, useHandle, useVueFlow } from '../../composables' import { connectionExists, groupEdgesByZLevel } from '../../utils' -import type { EdgeComponent, EdgeUpdatable, GraphEdge } from '../../types' +import type { Connection, EdgeComponent, EdgeUpdatable, GraphEdge, HandleType } from '../../types' import { Slots } from '../../context' import MarkerDefinitions from './MarkerDefinitions.vue' @@ -153,29 +153,43 @@ const onEdgeUpdaterTargetMouseDown = (event: MouseEvent, edge: GraphEdge) => { handleEdgeUpdater(event, edge, false) } -const { onMouseDown } = useHandle() +const nodeId = ref('') +const handleId = ref(null) +const type = ref('source') +const elementEdgeUpdaterType = ref('source') +const toUpdate = ref() +const mouseEvent = ref() +const onEdgeUpdate = (connection: Connection) => { + if (!connectionExists(connection, getEdges) && toUpdate.value) + hooks[toUpdate.value.id].emit.update({ edge: toUpdate.value, connection }) +} +const onEdgeUpdateEnd = () => { + if (!toUpdate.value || !mouseEvent.value) return + hooks[toUpdate.value.id].emit.updateEnd({ event: mouseEvent.value, edge: toUpdate.value }) + updating.value = '' +} + +const { onMouseDown } = useHandle({ + nodeId, + handleId, + type, + isValidConnection: undefined, + elementEdgeUpdaterType, + onEdgeUpdate, + onEdgeUpdateEnd, +}) const handleEdgeUpdater = (event: MouseEvent, edge: GraphEdge, isSourceHandle: boolean) => { - const nodeId = isSourceHandle ? edge.target : edge.source - const handleId = (isSourceHandle ? edge.targetHandle : edge.sourceHandle) ?? '' + nodeId.value = isSourceHandle ? edge.target : edge.source + handleId.value = (isSourceHandle ? edge.targetHandle : edge.sourceHandle) ?? '' + type.value = isSourceHandle ? 'target' : 'source' + elementEdgeUpdaterType.value = type.value + toUpdate.value = edge + mouseEvent.value = event hooks[edge.id].emit.updateStart({ event, edge }) - onMouseDown( - event, - handleId, - nodeId, - isSourceHandle, - undefined, - isSourceHandle ? 'target' : 'source', - (connection) => { - if (!connectionExists(connection, getEdges)) hooks[edge.id].emit.update({ edge, connection }) - }, - () => { - hooks[edge.id].emit.updateEnd({ event, edge }) - updating.value = '' - }, - ) + onMouseDown(event) } const getClass = (edge: GraphEdge) => {