fix(core,viewport): allow panning on scroll when selection is active

This commit is contained in:
braks
2024-01-21 14:35:35 +01:00
committed by Braks
parent d09e46fb2e
commit e7fb6813cd
+104 -95
View File
@@ -2,7 +2,7 @@
import type { D3ZoomEvent, ZoomTransform } from 'd3-zoom' import type { D3ZoomEvent, ZoomTransform } from 'd3-zoom'
import { zoom, zoomIdentity } from 'd3-zoom' import { zoom, zoomIdentity } from 'd3-zoom'
import { pointer, select } from 'd3-selection' import { pointer, select } from 'd3-selection'
import { onMounted, ref, watchEffect } from 'vue' import { onMounted, ref, watch } from 'vue'
import { toRef, useEventListener, useResizeObserver } from '@vueuse/core' import { toRef, useEventListener, useResizeObserver } from '@vueuse/core'
import type { CoordinateExtent, D3ZoomHandler, FlowOptions, ViewportTransform } from '../../types' import type { CoordinateExtent, D3ZoomHandler, FlowOptions, ViewportTransform } from '../../types'
import { PanOnScrollMode } from '../../types' import { PanOnScrollMode } from '../../types'
@@ -66,7 +66,9 @@ const selectionKeyPressed = useKeyPress(selectionKeyCode)
const zoomKeyPressed = useKeyPress(zoomActivationKeyCode) const zoomKeyPressed = useKeyPress(zoomActivationKeyCode)
const shouldPanOnDrag = toRef(() => !selectionKeyPressed.value && (panKeyPressed.value || panOnDrag.value)) const shouldPanOnDrag = toRef(() => panKeyPressed.value || panOnDrag.value)
const shouldPanOnScroll = toRef(() => panKeyPressed.value || panOnScroll.value)
const isSelecting = toRef(() => selectionKeyPressed.value || (selectionKeyCode.value === true && shouldPanOnDrag.value !== true)) const isSelecting = toRef(() => selectionKeyPressed.value || (selectionKeyCode.value === true && shouldPanOnDrag.value !== true))
@@ -168,7 +170,7 @@ onMounted(() => {
} }
// if all interactions are disabled, we prevent all zoom events // if all interactions are disabled, we prevent all zoom events
if (!shouldPanOnDrag.value && !zoomScroll && !panOnScroll.value && !zoomOnDoubleClick.value && !zoomOnPinch.value) { if (!shouldPanOnDrag.value && !zoomScroll && !shouldPanOnScroll.value && !zoomOnDoubleClick.value && !zoomOnPinch.value) {
return false return false
} }
@@ -190,7 +192,7 @@ onMounted(() => {
// if the target element is inside an element with the nopan class, we prevent panning // if the target element is inside an element with the nopan class, we prevent panning
if ( if (
isWrappedWithClass(event, noPanClassName.value) && isWrappedWithClass(event, noPanClassName.value) &&
(event.type !== 'wheel' || (panOnScroll.value && event.type === 'wheel' && !zoomKeyPressed.value)) (event.type !== 'wheel' || (shouldPanOnScroll.value && event.type === 'wheel' && !zoomKeyPressed.value))
) { ) {
return false return false
} }
@@ -200,7 +202,7 @@ onMounted(() => {
} }
// when there is no scroll handling enabled, we prevent all wheel events // when there is no scroll handling enabled, we prevent all wheel events
if (!zoomScroll && !panOnScroll.value && !pinchZoom && event.type === 'wheel') { if (!zoomScroll && !shouldPanOnScroll.value && !pinchZoom && event.type === 'wheel') {
return false return false
} }
@@ -226,112 +228,119 @@ onMounted(() => {
return (!event.ctrlKey || event.type === 'wheel') && buttonAllowed return (!event.ctrlKey || event.type === 'wheel') && buttonAllowed
}) })
watchEffect(() => { watch(
if (selectionKeyPressed.value && userSelectionActive.value && !isZoomingOrPanning.value) { [userSelectionActive, panOnDrag],
d3Zoom.on('zoom', null) () => {
} else if (!selectionKeyPressed.value && !userSelectionActive.value) { if (userSelectionActive.value && !isZoomingOrPanning.value) {
d3Zoom.on('zoom', (event: D3ZoomEvent<HTMLDivElement, any>) => { d3Zoom.on('zoom', null)
viewport.value = { x: event.transform.x, y: event.transform.y, zoom: event.transform.k } } else if (!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) const flowTransform = eventToFlowTransform(event.transform)
zoomedWithRightMouseButton = isRightClickPan(shouldPanOnDrag.value, mouseButton ?? 0) zoomedWithRightMouseButton = isRightClickPan(shouldPanOnDrag.value, mouseButton ?? 0)
emits.viewportChange(flowTransform) emits.viewportChange(flowTransform)
emits.move({ event, flowTransform }) emits.move({ event, flowTransform })
}) })
} }
}) },
{ immediate: true },
)
watchEffect(() => { watch(
if (panKeyPressed.value || (panOnScroll.value && !zoomKeyPressed.value && !userSelectionActive.value)) { [userSelectionActive, panOnScroll, panOnScrollMode, zoomKeyPressed, zoomOnPinch, preventScrolling, noWheelClassName],
d3Selection.on( () => {
'wheel.zoom', if (shouldPanOnScroll.value && !zoomKeyPressed.value && !userSelectionActive.value) {
(event: WheelEvent) => { d3Selection.on(
if (isWrappedWithClass(event, noWheelClassName.value)) { 'wheel.zoom',
return false (event: WheelEvent) => {
} if (isWrappedWithClass(event, noWheelClassName.value)) {
return false
}
event.preventDefault() event.preventDefault()
event.stopImmediatePropagation() event.stopImmediatePropagation()
const currentZoom = d3Selection.property('__zoom').k || 1 const currentZoom = d3Selection.property('__zoom').k || 1
const _isMacOs = isMacOs() const _isMacOs = isMacOs()
// macOS sets ctrlKey=true for pinch gesture on a trackpad // macOS sets ctrlKey=true for pinch gesture on a trackpad
if (event.ctrlKey && zoomOnPinch && _isMacOs) { if (event.ctrlKey && zoomOnPinch.value && _isMacOs) {
const point = pointer(event) const point = pointer(event)
const pinchDelta = wheelDelta(event) const pinchDelta = wheelDelta(event)
const zoom = currentZoom * 2 ** pinchDelta const zoom = currentZoom * 2 ** pinchDelta
// @ts-expect-error d3-zoom types are not up to date // @ts-expect-error d3-zoom types are not up to date
d3Zoom.scaleTo(d3Selection, zoom, point, event) d3Zoom.scaleTo(d3Selection, zoom, point, event)
return return
} }
// increase scroll speed in firefox // increase scroll speed in firefox
// firefox: deltaMode === 1; chrome: deltaMode === 0 // firefox: deltaMode === 1; chrome: deltaMode === 0
const deltaNormalize = event.deltaMode === 1 ? 20 : 1 const deltaNormalize = event.deltaMode === 1 ? 20 : 1
let deltaX = panOnScrollMode.value === PanOnScrollMode.Vertical ? 0 : event.deltaX * deltaNormalize let deltaX = panOnScrollMode.value === PanOnScrollMode.Vertical ? 0 : event.deltaX * deltaNormalize
let deltaY = panOnScrollMode.value === PanOnScrollMode.Horizontal ? 0 : event.deltaY * deltaNormalize let deltaY = panOnScrollMode.value === PanOnScrollMode.Horizontal ? 0 : event.deltaY * deltaNormalize
// this enables vertical scrolling with shift + scroll on windows // this enables vertical scrolling with shift + scroll on windows
if (!_isMacOs && event.shiftKey && panOnScrollMode.value !== PanOnScrollMode.Vertical && !deltaX && deltaY) { if (!_isMacOs && event.shiftKey && panOnScrollMode.value !== PanOnScrollMode.Vertical && !deltaX && deltaY) {
deltaX = deltaY deltaX = deltaY
deltaY = 0 deltaY = 0
} }
d3Zoom.translateBy( d3Zoom.translateBy(
d3Selection, d3Selection,
-(deltaX / currentZoom) * panOnScrollSpeed.value, -(deltaX / currentZoom) * panOnScrollSpeed.value,
-(deltaY / currentZoom) * panOnScrollSpeed.value, -(deltaY / currentZoom) * panOnScrollSpeed.value,
) )
const nextViewport = eventToFlowTransform(d3Selection.property('__zoom')) const nextViewport = eventToFlowTransform(d3Selection.property('__zoom'))
if (panScrollTimeout) { if (panScrollTimeout) {
clearTimeout(panScrollTimeout) clearTimeout(panScrollTimeout)
} }
// for pan on scroll we need to handle the event calls on our own // for pan on scroll we need to handle the event calls on our own
// we can't use the start, zoom and end events from d3-zoom // we can't use the start, zoom and end events from d3-zoom
// because start and move gets called on every scroll event and not once at the beginning // because start and move gets called on every scroll event and not once at the beginning
if (!isPanScrolling.value) { if (!isPanScrolling.value) {
isPanScrolling.value = true isPanScrolling.value = true
emits.moveStart({ event, flowTransform: nextViewport }) emits.moveStart({ event, flowTransform: nextViewport })
emits.viewportChangeStart(nextViewport) emits.viewportChangeStart(nextViewport)
} else { } else {
emits.move({ event, flowTransform: nextViewport }) emits.move({ event, flowTransform: nextViewport })
emits.viewportChange(nextViewport) emits.viewportChange(nextViewport)
panScrollTimeout = setTimeout(() => { panScrollTimeout = setTimeout(() => {
emits.moveEnd({ event, flowTransform: nextViewport }) emits.moveEnd({ event, flowTransform: nextViewport })
emits.viewportChangeEnd(nextViewport) emits.viewportChangeEnd(nextViewport)
isPanScrolling.value = false isPanScrolling.value = false
}, 150) }, 150)
} }
}, },
{ passive: false }, { passive: false },
) )
} else if (typeof d3ZoomHandler !== 'undefined') { } else if (typeof d3ZoomHandler !== 'undefined') {
d3Selection.on( d3Selection.on(
'wheel.zoom', 'wheel.zoom',
function (event: WheelEvent, d) { function (this: any, event: WheelEvent, d: any) {
if (!preventScrolling.value || isWrappedWithClass(event, noWheelClassName.value)) { if (!preventScrolling.value || isWrappedWithClass(event, noWheelClassName.value)) {
return null return null
} }
event.preventDefault() event.preventDefault()
// eslint-disable-next-line @typescript-eslint/no-invalid-this d3ZoomHandler.call(this, event, d)
d3ZoomHandler.call(this, event, d) },
}, { passive: false },
{ passive: false }, )
) }
} },
}) { immediate: true },
)
}) })
function isRightClickPan(pan: FlowOptions['panOnDrag'], usedButton: number) { function isRightClickPan(pan: FlowOptions['panOnDrag'], usedButton: number) {
@@ -346,9 +355,9 @@ function wheelDelta(event: any) {
function viewChanged(prevViewport: ViewportTransform, eventTransform: ZoomTransform) { function viewChanged(prevViewport: ViewportTransform, eventTransform: ZoomTransform) {
return ( return (
(prevViewport.x !== eventTransform.x && !isNaN(eventTransform.x)) || (prevViewport.x !== eventTransform.x && !Number.isNaN(eventTransform.x)) ||
(prevViewport.y !== eventTransform.y && !isNaN(eventTransform.y)) || (prevViewport.y !== eventTransform.y && !Number.isNaN(eventTransform.y)) ||
(prevViewport.zoom !== eventTransform.k && !isNaN(eventTransform.k)) (prevViewport.zoom !== eventTransform.k && !Number.isNaN(eventTransform.k))
) )
} }