157 lines
4.0 KiB
Vue
157 lines
4.0 KiB
Vue
<script lang="ts" setup>
|
|
import { isFunction, isString } from '@vueuse/core'
|
|
import { Position } from '../../types'
|
|
import type { HandleProps } from '../../types/handle'
|
|
|
|
const {
|
|
position = Position.Top,
|
|
connectable,
|
|
connectableStart = true,
|
|
connectableEnd = true,
|
|
id,
|
|
isValidConnection,
|
|
...props
|
|
} = defineProps<HandleProps>()
|
|
|
|
const emits = defineEmits<{
|
|
(event: 'mousedown', e: MouseEvent): void
|
|
(event: 'touchstart', e: TouchEvent): void
|
|
(event: 'click', e: MouseEvent): void
|
|
}>()
|
|
|
|
const type = toRef(props, 'type', 'source')
|
|
|
|
const { connectionStartHandle, vueFlowRef, nodesConnectable, noDragClassName } = useVueFlow()
|
|
|
|
const { id: nodeId, node, nodeEl, connectedEdges } = useNode()
|
|
|
|
const handle = ref<HTMLDivElement>()
|
|
|
|
const handleId = computed(() => id ?? `${nodeId}__handle-${position}`)
|
|
|
|
const { handlePointerDown, handleClick } = useHandle({
|
|
nodeId,
|
|
handleId,
|
|
isValidConnection,
|
|
type,
|
|
})
|
|
|
|
const isConnectable = computed(() => {
|
|
if (isString(connectable) && connectable === 'single') {
|
|
return !connectedEdges.value.some((edge) => {
|
|
const id = edge[`${type.value}Handle`]
|
|
|
|
if (edge[type.value] !== nodeId) {
|
|
return false
|
|
}
|
|
|
|
return id ? id === handleId.value : true
|
|
})
|
|
}
|
|
|
|
if (isFunction(connectable)) {
|
|
return connectable(node, connectedEdges.value)
|
|
}
|
|
|
|
return isDef(connectable) ? connectable : nodesConnectable.value
|
|
})
|
|
|
|
const isConnecting = computed(
|
|
() =>
|
|
connectionStartHandle &&
|
|
connectionStartHandle.nodeId === nodeId &&
|
|
connectionStartHandle.handleId === handleId &&
|
|
connectionStartHandle.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)
|
|
until(() => node.initialized)
|
|
.toBe(true, { flush: 'post' })
|
|
.then(() => {
|
|
const existingBounds = node.handleBounds[type.value]?.find((b) => b.id === handleId.value)
|
|
|
|
if (!vueFlowRef.value || existingBounds) return
|
|
|
|
const viewportNode = vueFlowRef.value.querySelector('.vue-flow__transformationpane')
|
|
|
|
if (!nodeEl || !handle.value || !viewportNode || !handleId.value) return
|
|
|
|
const nodeBounds = nodeEl.value.getBoundingClientRect()
|
|
|
|
const handleBounds = handle.value.getBoundingClientRect()
|
|
|
|
const style = window.getComputedStyle(viewportNode)
|
|
const { m22: zoom } = new window.DOMMatrixReadOnly(style.transform)
|
|
|
|
const nextBounds = {
|
|
id: handleId.value,
|
|
position,
|
|
x: (handleBounds.left - nodeBounds.left) / zoom,
|
|
y: (handleBounds.top - nodeBounds.top) / zoom,
|
|
...getDimensions(handle.value),
|
|
}
|
|
|
|
node.handleBounds[type.value] = [...(node.handleBounds[type.value] ?? []), nextBounds]
|
|
})
|
|
|
|
function onPointerDown(event: MouseEvent | TouchEvent) {
|
|
const isMouseTriggered = isMouseEvent(event)
|
|
|
|
if (isConnectable.value && connectableStart && ((isMouseTriggered && event.button === 0) || !isMouseTriggered)) {
|
|
handlePointerDown(event)
|
|
}
|
|
|
|
if (isMouseTriggered) {
|
|
emits('mousedown', event)
|
|
} else {
|
|
emits('touchstart', event)
|
|
}
|
|
}
|
|
|
|
function onClick(event: MouseEvent) {
|
|
if (!connectionStartHandle && !connectableStart) {
|
|
return
|
|
}
|
|
|
|
if (isConnectable.value) {
|
|
handleClick(event)
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<script lang="ts">
|
|
export default {
|
|
name: 'Handle',
|
|
compatConfig: { MODE: 3 },
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div
|
|
ref="handle"
|
|
:data-id="`${nodeId}-${handleId}-${type}`"
|
|
:data-handleid="handleId"
|
|
:data-nodeid="nodeId"
|
|
:data-handlepos="position"
|
|
class="vue-flow__handle"
|
|
:class="[
|
|
`vue-flow__handle-${position}`,
|
|
`vue-flow__handle-${handleId}`,
|
|
noDragClassName,
|
|
type,
|
|
{
|
|
connectable: isConnectable,
|
|
connecting: isConnecting,
|
|
connectablestart: connectableStart,
|
|
connectableend: connectableEnd,
|
|
connectionindicator: (isConnectable && connectableStart && !isConnecting) || (connectableEnd && isConnecting),
|
|
},
|
|
]"
|
|
@mousedown="onPointerDown"
|
|
@touchstart.passive="onPointerDown"
|
|
@click="onClick"
|
|
>
|
|
<slot :id="id" />
|
|
</div>
|
|
</template>
|