Files
vue-flow/packages/core/src/container/Pane/Pane.vue
T

272 lines
6.1 KiB
Vue

<script lang="ts" setup>
import { ref } from 'vue'
import { toRef } from '@vueuse/core'
import UserSelection from '../../components/UserSelection/UserSelection.vue'
import NodesSelection from '../../components/NodesSelection/NodesSelection.vue'
import type { GraphNode } from '../../types'
import { SelectionMode } from '../../types'
import { useKeyPress, useVueFlow } from '../../composables'
import { getConnectedEdges, getNodesInside } from '../../utils'
import { getMousePosition } from './utils'
const { isSelecting } = defineProps<{ isSelecting: boolean }>()
const {
id,
vueFlowRef,
getNodes,
getEdges,
viewport,
emits,
userSelectionActive,
removeSelectedElements,
panOnDrag,
userSelectionRect,
elementsSelectable,
nodesSelectionActive,
addSelectedElements,
getSelectedEdges,
getSelectedNodes,
removeNodes,
removeEdges,
selectionMode,
deleteKeyCode,
multiSelectionKeyCode,
multiSelectionActive,
} = useVueFlow()
const container = ref<HTMLDivElement | null>(null)
const prevSelectedNodesCount = ref(0)
const prevSelectedEdgesCount = ref(0)
const containerBounds = ref<DOMRect>()
const hasActiveSelection = toRef(() => elementsSelectable.value && (isSelecting || userSelectionActive.value))
useKeyPress(deleteKeyCode, (keyPressed) => {
if (!keyPressed) {
return
}
const nodesToRemove = getNodes.value.reduce<GraphNode[]>((res, node) => {
if (!node.selected && node.parentNode && res.find((n) => n.id === node.parentNode)) {
res.push(node)
} else if (node.selected) {
res.push(node)
}
return res
}, [])
if (nodesToRemove || getSelectedEdges.value) {
if (getSelectedEdges.value.length > 0) {
removeEdges(getSelectedEdges.value)
}
if (nodesToRemove.length > 0) {
removeNodes(nodesToRemove)
}
nodesSelectionActive.value = false
removeSelectedElements()
}
})
useKeyPress(multiSelectionKeyCode, (keyPressed) => {
multiSelectionActive.value = keyPressed
})
function resetUserSelection() {
userSelectionActive.value = false
userSelectionRect.value = null
prevSelectedNodesCount.value = 0
prevSelectedEdgesCount.value = 0
}
function onClick(event: MouseEvent) {
if (event.target !== container.value || hasActiveSelection.value) {
return
}
emits.paneClick(event)
removeSelectedElements()
nodesSelectionActive.value = false
}
function onContextMenu(event: MouseEvent) {
if (event.target !== container.value) {
return
}
if (Array.isArray(panOnDrag.value) && panOnDrag.value?.includes(2)) {
event.preventDefault()
return
}
emits.paneContextMenu(event)
}
function onWheel(event: WheelEvent) {
if (event.target !== container.value) {
return
}
emits.paneScroll(event)
}
function onMouseDown(event: MouseEvent) {
containerBounds.value = vueFlowRef.value!.getBoundingClientRect()
if (
!hasActiveSelection.value ||
!elementsSelectable ||
!isSelecting ||
event.button !== 0 ||
event.target !== container.value ||
!containerBounds.value
) {
return
}
const { x, y } = getMousePosition(event, containerBounds.value)
removeSelectedElements()
userSelectionRect.value = {
width: 0,
height: 0,
startX: x,
startY: y,
x,
y,
}
userSelectionActive.value = true
emits.selectionStart(event)
}
function onMouseMove(event: MouseEvent) {
if (!hasActiveSelection.value) {
return emits.paneMouseMove(event)
}
if (!isSelecting || !containerBounds.value || !userSelectionRect.value) {
return
}
if (!userSelectionActive.value) {
userSelectionActive.value = true
}
if (nodesSelectionActive.value) {
nodesSelectionActive.value = false
}
const mousePos = getMousePosition(event, containerBounds.value)
const startX = userSelectionRect.value.startX ?? 0
const startY = userSelectionRect.value.startY ?? 0
const nextUserSelectRect = {
...userSelectionRect.value,
x: mousePos.x < startX ? mousePos.x : startX,
y: mousePos.y < startY ? mousePos.y : startY,
width: Math.abs(mousePos.x - startX),
height: Math.abs(mousePos.y - startY),
}
const selectedNodes = getNodesInside(
getNodes.value,
userSelectionRect.value,
viewport.value,
selectionMode.value === SelectionMode.Partial,
)
const selectedEdges = getConnectedEdges(selectedNodes, getEdges.value)
prevSelectedNodesCount.value = selectedNodes.length
prevSelectedEdgesCount.value = selectedEdges.length
userSelectionRect.value = nextUserSelectRect
addSelectedElements([...selectedNodes, ...selectedEdges])
}
function onMouseUp(event: MouseEvent) {
if (!hasActiveSelection.value) {
return
}
if (event.button !== 0) {
return
}
// 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)
}
nodesSelectionActive.value = prevSelectedNodesCount.value > 0
resetUserSelection()
emits.selectionEnd(event)
}
function onMouseLeave(event: MouseEvent) {
if (!hasActiveSelection.value) {
return emits.paneMouseLeave(event)
}
if (userSelectionActive.value) {
nodesSelectionActive.value = prevSelectedNodesCount.value > 0
emits.selectionEnd?.(event)
}
resetUserSelection()
}
function onMouseEnter(event: MouseEvent) {
if (hasActiveSelection.value) {
return
}
emits.paneMouseEnter(event)
}
</script>
<script lang="ts">
export default {
name: 'Pane',
compatConfig: { MODE: 3 },
}
</script>
<template>
<div
ref="container"
:key="`pane-${id}`"
class="vue-flow__pane vue-flow__container"
:class="{ selection: isSelecting }"
@click="onClick"
@contextmenu="onContextMenu"
@wheel.passive="onWheel"
@mouseenter="onMouseEnter"
@mousedown="onMouseDown"
@mousemove="onMouseMove"
@mouseup="onMouseUp"
@mouseleave="onMouseLeave"
>
<slot />
<UserSelection v-if="userSelectionActive && userSelectionRect" :user-selection-rect="userSelectionRect" />
<NodesSelection v-if="nodesSelectionActive && getSelectedNodes.length" />
</div>
</template>