Files
vue-flow/packages/core/src/container/Pane/Pane.vue
T
Braks 410a59df5b fix(core): unwrap ref when checking if connection in progress (#1789)
* fix(core): unwrap ref when checking if connection in progress

Signed-off-by: braks <78412429+bcakmakoglu@users.noreply.github.com>

* chore(changeset): add

Signed-off-by: braks <78412429+bcakmakoglu@users.noreply.github.com>

---------

Signed-off-by: braks <78412429+bcakmakoglu@users.noreply.github.com>
2025-03-13 06:56:32 +01:00

257 lines
7.0 KiB
Vue

<script lang="ts" setup>
import { ref, toRef, watch } from 'vue'
import UserSelection from '../../components/UserSelection/UserSelection.vue'
import NodesSelection from '../../components/NodesSelection/NodesSelection.vue'
import type { EdgeChange, NodeChange } from '../../types'
import { SelectionMode } from '../../types'
import { useKeyPress, useVueFlow } from '../../composables'
import { areSetsEqual, getEventPosition, getNodesInside, getSelectionChanges } from '../../utils'
import { getMousePosition } from './utils'
const { isSelecting, selectionKeyPressed } = defineProps<{ isSelecting: boolean; selectionKeyPressed: boolean }>()
const {
vueFlowRef,
nodes,
viewport,
emits,
userSelectionActive,
removeSelectedElements,
userSelectionRect,
elementsSelectable,
nodesSelectionActive,
getSelectedEdges,
getSelectedNodes,
removeNodes,
removeEdges,
selectionMode,
deleteKeyCode,
multiSelectionKeyCode,
multiSelectionActive,
edgeLookup,
nodeLookup,
connectionLookup,
defaultEdgeOptions,
connectionStartHandle,
} = useVueFlow()
const container = ref<HTMLDivElement | null>(null)
const selectedNodeIds = ref<Set<string>>(new Set())
const selectedEdgeIds = ref<Set<string>>(new Set())
const containerBounds = ref<DOMRect>()
const hasActiveSelection = toRef(() => elementsSelectable.value && (isSelecting || userSelectionActive.value))
const connectionInProgress = toRef(() => connectionStartHandle.value !== null)
// Used to prevent click events when the user lets go of the selectionKey during a selection
let selectionInProgress = false
let selectionStarted = false
const deleteKeyPressed = useKeyPress(deleteKeyCode, { actInsideInputWithModifier: false })
const multiSelectKeyPressed = useKeyPress(multiSelectionKeyCode)
watch(deleteKeyPressed, (isKeyPressed) => {
if (!isKeyPressed) {
return
}
removeNodes(getSelectedNodes.value)
removeEdges(getSelectedEdges.value)
nodesSelectionActive.value = false
})
watch(multiSelectKeyPressed, (isKeyPressed) => {
multiSelectionActive.value = isKeyPressed
})
function wrapHandler(handler: Function, containerRef: HTMLDivElement | null) {
return (event: MouseEvent) => {
if (event.target !== containerRef) {
return
}
handler?.(event)
}
}
function onClick(event: MouseEvent) {
if (selectionInProgress || connectionInProgress.value) {
selectionInProgress = false
return
}
emits.paneClick(event)
removeSelectedElements()
nodesSelectionActive.value = false
}
function onContextMenu(event: MouseEvent) {
event.preventDefault()
event.stopPropagation()
emits.paneContextMenu(event)
}
function onWheel(event: WheelEvent) {
emits.paneScroll(event)
}
function onPointerDown(event: PointerEvent) {
containerBounds.value = vueFlowRef.value?.getBoundingClientRect()
if (
!elementsSelectable.value ||
!isSelecting ||
event.button !== 0 ||
event.target !== container.value ||
!containerBounds.value
) {
return
}
;(event.target as Element)?.setPointerCapture?.(event.pointerId)
const { x, y } = getMousePosition(event, containerBounds.value)
selectionStarted = true
selectionInProgress = false
removeSelectedElements()
userSelectionRect.value = {
width: 0,
height: 0,
startX: x,
startY: y,
x,
y,
}
emits.selectionStart(event)
}
function onPointerMove(event: PointerEvent) {
if (!containerBounds.value || !userSelectionRect.value) {
return
}
selectionInProgress = true
const { x: mouseX, y: mouseY } = getEventPosition(event, containerBounds.value)
const { startX = 0, startY = 0 } = userSelectionRect.value
const nextUserSelectRect = {
startX,
startY,
x: mouseX < startX ? mouseX : startX,
y: mouseY < startY ? mouseY : startY,
width: Math.abs(mouseX - startX),
height: Math.abs(mouseY - startY),
}
const prevSelectedNodeIds = selectedNodeIds.value
const prevSelectedEdgeIds = selectedEdgeIds.value
selectedNodeIds.value = new Set(
getNodesInside(nodes.value, nextUserSelectRect, viewport.value, selectionMode.value === SelectionMode.Partial, true).map(
(node) => node.id,
),
)
selectedEdgeIds.value = new Set()
const edgesSelectable = defaultEdgeOptions.value?.selectable ?? true
// We look for all edges connected to the selected nodes
for (const nodeId of selectedNodeIds.value) {
const connections = connectionLookup.value.get(nodeId)
if (!connections) {
continue
}
for (const { edgeId } of connections.values()) {
const edge = edgeLookup.value.get(edgeId)
if (edge && (edge.selectable ?? edgesSelectable)) {
selectedEdgeIds.value.add(edgeId)
}
}
}
if (!areSetsEqual(prevSelectedNodeIds, selectedNodeIds.value)) {
const changes = getSelectionChanges(nodeLookup.value, selectedNodeIds.value, true) as NodeChange[]
emits.nodesChange(changes)
}
if (!areSetsEqual(prevSelectedEdgeIds, selectedEdgeIds.value)) {
const changes = getSelectionChanges(edgeLookup.value, selectedEdgeIds.value) as EdgeChange[]
emits.edgesChange(changes)
}
userSelectionRect.value = nextUserSelectRect
userSelectionActive.value = true
nodesSelectionActive.value = false
}
function onPointerUp(event: PointerEvent) {
if (event.button !== 0 || !selectionStarted) {
return
}
;(event.target as Element)?.releasePointerCapture(event.pointerId)
// We only want to trigger click functions when in selection mode if
// the user did not move the mouse.
if (!userSelectionActive.value && userSelectionRect.value && event.target === container.value) {
onClick(event)
}
userSelectionActive.value = false
userSelectionRect.value = null
nodesSelectionActive.value = selectedNodeIds.value.size > 0
emits.selectionEnd(event)
// If the user kept holding the selectionKey during the selection,
// we need to reset the selectionInProgress, so the next click event is not prevented
if (selectionKeyPressed) {
selectionInProgress = false
}
selectionStarted = false
}
</script>
<script lang="ts">
export default {
name: 'Pane',
compatConfig: { MODE: 3 },
}
</script>
<template>
<div
ref="container"
class="vue-flow__pane vue-flow__container"
:class="{ selection: isSelecting }"
@click="(event) => (hasActiveSelection ? undefined : wrapHandler(onClick, container)(event))"
@contextmenu="wrapHandler(onContextMenu, container)($event)"
@wheel.passive="wrapHandler(onWheel, container)($event)"
@pointerenter="(event) => (hasActiveSelection ? undefined : emits.paneMouseEnter(event))"
@pointerdown="(event) => (hasActiveSelection ? onPointerDown(event) : emits.paneMouseMove(event))"
@pointermove="(event) => (hasActiveSelection ? onPointerMove(event) : emits.paneMouseMove(event))"
@pointerup="(event) => (hasActiveSelection ? onPointerUp(event) : undefined)"
@pointerleave="emits.paneMouseLeave($event)"
>
<slot />
<UserSelection v-if="userSelectionActive && userSelectionRect" :user-selection-rect="userSelectionRect" />
<NodesSelection v-if="nodesSelectionActive && getSelectedNodes.length" />
</div>
</template>