diff --git a/packages/core/src/container/Viewport/Viewport.vue b/packages/core/src/container/Viewport/Viewport.vue index 5860b167..3a7fc5fb 100644 --- a/packages/core/src/container/Viewport/Viewport.vue +++ b/packages/core/src/container/Viewport/Viewport.vue @@ -48,6 +48,10 @@ const selectionKeyPressed = ref(false) const isZoomingOrPanning = ref(false) +const isPanScrolling = ref(false) + +const panScrollTimeout = ref>() + let zoomedWithRightMouseButton = false let mouseButton = 0 @@ -183,12 +187,13 @@ onMounted(() => { event.stopImmediatePropagation() const currentZoom = d3Selection.property('__zoom').k || 1 + const _isMacOs = isMacOs() - if (event.ctrlKey && zoomOnPinch.value && isMacOs()) { + // macOS sets ctrlKey=true for pinch gesture on a trackpad + if (event.ctrlKey && zoomOnPinch && _isMacOs) { const point = pointer(event) const pinchDelta = wheelDelta(event) const zoom = currentZoom * 2 ** pinchDelta - // @ts-expect-error d3-zoom types are not up to date d3Zoom.scaleTo(d3Selection, zoom, point, event) @@ -198,14 +203,47 @@ onMounted(() => { // increase scroll speed in firefox // firefox: deltaMode === 1; chrome: deltaMode === 0 const deltaNormalize = event.deltaMode === 1 ? 20 : 1 - const deltaX = panOnScrollMode.value === PanOnScrollMode.Vertical ? 0 : event.deltaX * deltaNormalize - const deltaY = panOnScrollMode.value === PanOnScrollMode.Horizontal ? 0 : event.deltaY * deltaNormalize + + let deltaX = panOnScrollMode.value === PanOnScrollMode.Vertical ? 0 : event.deltaX * deltaNormalize + let deltaY = panOnScrollMode.value === PanOnScrollMode.Horizontal ? 0 : event.deltaY * deltaNormalize + + // this enables vertical scrolling with shift + scroll on windows + if (!_isMacOs && event.shiftKey && panOnScrollMode.value !== PanOnScrollMode.Vertical && !deltaX && deltaY) { + deltaX = deltaY + deltaY = 0 + } d3Zoom.translateBy( d3Selection, -(deltaX / currentZoom) * panOnScrollSpeed.value, -(deltaY / currentZoom) * panOnScrollSpeed.value, ) + + const nextViewport = eventToFlowTransform(d3Selection.property('__zoom')) + + clearTimeout(panScrollTimeout.value) + + // 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 + // because start and move gets called on every scroll event and not once at the beginning + if (!isPanScrolling.value) { + isPanScrolling.value = true + + emits.moveStart({ event, flowTransform: nextViewport }) + emits.viewportChangeStart(nextViewport) + } + + if (isPanScrolling.value) { + emits.move({ event, flowTransform: nextViewport }) + emits.viewportChange(nextViewport) + + panScrollTimeout.value = setTimeout(() => { + emits.moveEnd({ event, flowTransform: nextViewport }) + emits.viewportChangeEnd(nextViewport) + + isPanScrolling.value = false + }, 150) + } }, { passive: false }, ) diff --git a/packages/core/src/types/hooks.ts b/packages/core/src/types/hooks.ts index 2246c8f3..15fa28ea 100644 --- a/packages/core/src/types/hooks.ts +++ b/packages/core/src/types/hooks.ts @@ -64,9 +64,9 @@ export interface FlowEvents { } & OnConnectStartParams clickConnectEnd: MouseEvent | TouchEvent | undefined paneReady: VueFlowStore - move: { event: D3ZoomEvent; flowTransform: ViewportTransform } - moveStart: { event: D3ZoomEvent; flowTransform: ViewportTransform } - moveEnd: { event: D3ZoomEvent; flowTransform: ViewportTransform } + move: { event: D3ZoomEvent | WheelEvent; flowTransform: ViewportTransform } + moveStart: { event: D3ZoomEvent | WheelEvent; flowTransform: ViewportTransform } + moveEnd: { event: D3ZoomEvent | WheelEvent; flowTransform: ViewportTransform } selectionDragStart: NodeDragEvent selectionDrag: NodeDragEvent selectionDragStop: NodeDragEvent