fix(core): allow panning when panKey is pressed

This commit is contained in:
braks
2023-10-30 13:36:07 +01:00
committed by Braks
parent 7fd28298ac
commit 049b1da0c6
2 changed files with 113 additions and 115 deletions
+108 -107
View File
@@ -7,7 +7,7 @@ import { toRef, useEventListener, useResizeObserver } from '@vueuse/core'
import type { CoordinateExtent, D3ZoomHandler, FlowOptions, ViewportTransform } from '../../types'
import { PanOnScrollMode } from '../../types'
import { useKeyPress, useVueFlow, useWindow } from '../../composables'
import { ErrorCode, VueFlowError, clamp, getDimensions, isMacOs } from '../../utils'
import { ErrorCode, VueFlowError, clamp, getDimensions, isMacOs, warn } from '../../utils'
import Pane from '../Pane/Pane.vue'
import Transform from './Transform.vue'
@@ -44,10 +44,6 @@ const {
viewportRef,
} = useVueFlow()
const viewportEl = ref<HTMLDivElement>()
const selectionKeyPressed = ref(false)
const isZoomingOrPanning = ref(false)
const isPanScrolling = ref(false)
@@ -66,6 +62,10 @@ let prevTransform: ViewportTransform = {
const panKeyPressed = useKeyPress(panActivationKeyCode)
const selectionKeyPressed = useKeyPress(selectionKeyCode)
const zoomKeyPressed = useKeyPress(zoomActivationKeyCode)
const shouldPanOnDrag = toRef(() => !selectionKeyPressed.value && panOnDrag.value && panKeyPressed.value)
const isSelecting = toRef(
@@ -73,13 +73,20 @@ const isSelecting = toRef(
(selectionKeyCode.value !== true && selectionKeyPressed.value) || (selectionKeyCode.value === true && !shouldPanOnDrag.value),
)
useResizeObserver(viewportEl, setDimensions)
useResizeObserver(viewportRef, setDimensions)
useEventListener(window, 'resize', setDimensions)
onMounted(() => {
const viewportElement = viewportEl.value!
if (!viewportRef.value) {
warn('Viewport element is missing')
return
}
const viewportElement = viewportRef.value
const bbox = viewportElement.getBoundingClientRect()
const d3Zoom = zoom<HTMLDivElement, any>().scaleExtent([minZoom.value, maxZoom.value]).translateExtent(translateExtent.value)
const d3Selection = select(viewportElement).call(d3Zoom)
const d3ZoomHandler = d3Selection.on('wheel.zoom')
@@ -100,31 +107,8 @@ onMounted(() => {
storeD3Zoom.value = d3Zoom
storeD3Selection.value = d3Selection
storeD3ZoomHandler.value = d3ZoomHandler as D3ZoomHandler
viewport.value = { x: constrainedTransform.x, y: constrainedTransform.y, zoom: constrainedTransform.k }
viewportRef.value = viewportElement
useKeyPress(selectionKeyCode, (keyPress) => {
selectionKeyPressed.value = keyPress
})
const zoomKeyPressed = useKeyPress(zoomActivationKeyCode)
watchEffect(() => {
if (selectionKeyPressed.value && userSelectionActive.value && !isZoomingOrPanning.value) {
d3Zoom.on('zoom', null)
} else if (!selectionKeyPressed.value && !userSelectionActive.value) {
d3Zoom.on('zoom', (event: D3ZoomEvent<HTMLDivElement, any>) => {
viewport.value = { x: event.transform.x, y: event.transform.y, zoom: event.transform.k }
const flowTransform = eventToFlowTransform(event.transform)
zoomedWithRightMouseButton = isRightClickPan(panOnDrag.value, mouseButton ?? 0)
emits.viewportChange(flowTransform)
emits.move({ event, flowTransform })
})
}
})
d3Zoom.on('start', (event: D3ZoomEvent<HTMLDivElement, any>) => {
if (!event.sourceEvent) {
@@ -173,8 +157,97 @@ onMounted(() => {
}
})
d3Zoom.filter((event: MouseEvent) => {
const zoomScroll = zoomKeyPressed.value || zoomOnScroll.value
const pinchZoom = zoomOnPinch.value && event.ctrlKey
if (
(panOnDrag.value === true || (Array.isArray(panOnDrag.value) && panOnDrag.value.includes(1))) &&
event.button === 1 &&
event.type === 'mousedown' &&
((event.target as HTMLElement)?.closest('.vue-flow__node') || (event.target as HTMLElement)?.closest('.vue-flow__edge'))
) {
return true
}
// if all interactions are disabled, we prevent all zoom events
if (!panOnDrag.value && !zoomScroll && !panOnScroll.value && !zoomOnDoubleClick.value && !zoomOnPinch.value) {
return false
}
// during a selection we prevent all other interactions
if (userSelectionActive.value) {
return false
}
// if zoom on double click is disabled, we prevent the double click event
if (!zoomOnDoubleClick.value && event.type === 'dblclick') {
return false
}
// if the target element is inside an element with the nowheel class, we prevent zooming
if (isWrappedWithClass(event, noWheelClassName.value) && event.type === 'wheel') {
return false
}
// if the target element is inside an element with the nopan class, we prevent panning
if (
isWrappedWithClass(event, noPanClassName.value) &&
(event.type !== 'wheel' || (panOnScroll.value && event.type === 'wheel'))
) {
return false
}
if (!zoomOnPinch.value && event.ctrlKey && event.type === 'wheel') {
return false
}
// when there is no scroll handling enabled, we prevent all wheel events
if (!zoomScroll && !panOnScroll.value && !pinchZoom && event.type === 'wheel') {
return false
}
// if the pane is not movable, we prevent dragging it with mousestart or touchstart
if (!panOnDrag.value && (event.type === 'mousedown' || event.type === 'touchstart')) {
return false
}
// if the pane is only movable using allowed clicks
if (
Array.isArray(panOnDrag.value) &&
!panOnDrag.value.includes(event.button) &&
(event.type === 'mousedown' || event.type === 'touchstart')
) {
return false
}
// We only allow right clicks if pan on drag is set to right-click
const buttonAllowed =
(Array.isArray(panOnDrag.value) && panOnDrag.value.includes(event.button)) || !event.button || event.button <= 1
// default filter for d3-zoom
return (!event.ctrlKey || event.type === 'wheel') && buttonAllowed
})
watchEffect(() => {
if (panOnScroll.value && !zoomKeyPressed.value && !userSelectionActive.value) {
if (selectionKeyPressed.value && userSelectionActive.value && !isZoomingOrPanning.value) {
d3Zoom.on('zoom', null)
} else if (!selectionKeyPressed.value && !userSelectionActive.value) {
d3Zoom.on('zoom', (event: D3ZoomEvent<HTMLDivElement, any>) => {
viewport.value = { x: event.transform.x, y: event.transform.y, zoom: event.transform.k }
const flowTransform = eventToFlowTransform(event.transform)
zoomedWithRightMouseButton = isRightClickPan(panOnDrag.value, mouseButton ?? 0)
emits.viewportChange(flowTransform)
emits.move({ event, flowTransform })
})
}
})
watchEffect(() => {
if (panKeyPressed.value || (panOnScroll.value && !zoomKeyPressed.value && !userSelectionActive.value)) {
d3Selection.on(
'wheel.zoom',
(event: WheelEvent) => {
@@ -262,78 +335,6 @@ onMounted(() => {
)
}
})
d3Zoom.filter((event: MouseEvent) => {
const zoomScroll = zoomKeyPressed.value || zoomOnScroll.value
const pinchZoom = zoomOnPinch.value && event.ctrlKey
if (
(panOnDrag.value === true || (Array.isArray(panOnDrag.value) && panOnDrag.value.includes(1))) &&
event.button === 1 &&
event.type === 'mousedown' &&
((event.target as HTMLElement)?.closest('.vue-flow__node') || (event.target as HTMLElement)?.closest('.vue-flow__edge'))
) {
return true
}
// if all interactions are disabled, we prevent all zoom events
if (!panOnDrag.value && !zoomScroll && !panOnScroll.value && !zoomOnDoubleClick.value && !zoomOnPinch.value) {
return false
}
// during a selection we prevent all other interactions
if (userSelectionActive.value) {
return false
}
// if zoom on double click is disabled, we prevent the double click event
if (!zoomOnDoubleClick.value && event.type === 'dblclick') {
return false
}
// if the target element is inside an element with the nowheel class, we prevent zooming
if (isWrappedWithClass(event, noWheelClassName.value) && event.type === 'wheel') {
return false
}
// if the target element is inside an element with the nopan class, we prevent panning
if (
isWrappedWithClass(event, noPanClassName.value) &&
(event.type !== 'wheel' || (panOnScroll.value && event.type === 'wheel'))
) {
return false
}
if (!zoomOnPinch.value && event.ctrlKey && event.type === 'wheel') {
return false
}
// when there is no scroll handling enabled, we prevent all wheel events
if (!zoomScroll && !panOnScroll.value && !pinchZoom && event.type === 'wheel') {
return false
}
// if the pane is not movable, we prevent dragging it with mousestart or touchstart
if (!panOnDrag.value && (event.type === 'mousedown' || event.type === 'touchstart')) {
return false
}
// if the pane is only movable using allowed clicks
if (
Array.isArray(panOnDrag.value) &&
!panOnDrag.value.includes(event.button) &&
(event.type === 'mousedown' || event.type === 'touchstart')
) {
return false
}
// We only allow right clicks if pan on drag is set to right-click
const buttonAllowed =
(Array.isArray(panOnDrag.value) && panOnDrag.value.includes(event.button)) || !event.button || event.button <= 1
// default filter for d3-zoom
return (!event.ctrlKey || event.type === 'wheel') && buttonAllowed
})
})
function isRightClickPan(pan: FlowOptions['panOnDrag'], usedButton: number) {
@@ -363,11 +364,11 @@ function eventToFlowTransform(eventTransform: ZoomTransform): ViewportTransform
}
function setDimensions() {
if (!viewportEl.value) {
if (!viewportRef.value) {
return
}
const { width, height } = getDimensions(viewportEl.value)
const { width, height } = getDimensions(viewportRef.value)
if (width === 0 || height === 0) {
emits.error(new VueFlowError(ErrorCode.MISSING_VIEWPORT_DIMENSIONS))
@@ -390,7 +391,7 @@ export default {
</script>
<template>
<div ref="viewportEl" :key="`viewport-${id}`" class="vue-flow__viewport vue-flow__container">
<div ref="viewportRef" :key="`viewport-${id}`" class="vue-flow__viewport vue-flow__container">
<Pane
:is-selecting="isSelecting"
:class="{ connecting: !!connectionStartHandle, dragging: paneDragging, draggable: !!panOnDrag }"