diff --git a/packages/editor-ui/src/components/canvas/Canvas.spec.ts b/packages/editor-ui/src/components/canvas/Canvas.spec.ts index 8e534255327bf..aa23b7064aacc 100644 --- a/packages/editor-ui/src/components/canvas/Canvas.spec.ts +++ b/packages/editor-ui/src/components/canvas/Canvas.spec.ts @@ -220,4 +220,20 @@ describe('Canvas', () => { expect(container.querySelector('#diagonalHatch')).toBeInTheDocument(); }); }); + + describe('pane', () => { + describe('onPaneMouseDown', () => { + it('should enable panning when middle mouse button is pressed', async () => { + const { getByTestId } = renderComponent(); + const canvas = getByTestId('canvas'); + const pane = canvas.querySelector('.vue-flow__pane'); + + if (!pane) throw new Error('VueFlow pane not in the document'); + + await fireEvent.mouseDown(canvas, { button: 1, view: window }); + + expect(canvas).toHaveClass('draggable'); + }); + }); + }); }); diff --git a/packages/editor-ui/src/components/canvas/Canvas.vue b/packages/editor-ui/src/components/canvas/Canvas.vue index 424ac853e0940..b09302cbdd2cf 100644 --- a/packages/editor-ui/src/components/canvas/Canvas.vue +++ b/packages/editor-ui/src/components/canvas/Canvas.vue @@ -43,6 +43,7 @@ import { onKeyDown, onKeyUp, useDebounceFn } from '@vueuse/core'; import CanvasArrowHeadMarker from './elements/edges/CanvasArrowHeadMarker.vue'; import { CanvasNodeRenderType } from '@/types'; import CanvasBackgroundStripedPattern from './elements/CanvasBackgroundStripedPattern.vue'; +import { isMiddleMouseButton } from '@/utils/eventUtils'; const $style = useCssModule(); @@ -107,6 +108,7 @@ const props = withDefaults( ); const { + vueFlowRef, getSelectedNodes: selectedNodes, addSelectedNodes, removeSelectedNodes, @@ -143,18 +145,16 @@ const disableKeyBindings = computed(() => !props.keyBindings); * @see https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values#whitespace_keys */ -const isPanningEnabled = ref(false); const panningKeyCode = ' '; +const isPanningEnabled = ref(false); const selectionKeyCode = ref(true); onKeyDown(panningKeyCode, () => { - isPanningEnabled.value = true; - selectionKeyCode.value = null; + setPanningEnabled(true); }); onKeyUp(panningKeyCode, () => { - isPanningEnabled.value = false; - selectionKeyCode.value = true; + setPanningEnabled(false); }); const keyMap = computed(() => ({ @@ -186,6 +186,16 @@ const keyMap = computed(() => ({ useKeybindings(keyMap, { disabled: disableKeyBindings }); +function setPanningEnabled(value: boolean) { + if (value) { + isPanningEnabled.value = true; + selectionKeyCode.value = null; + } else { + isPanningEnabled.value = false; + selectionKeyCode.value = true; + } +} + /** * When the window is focused, the selection key code is lost. * We trigger a value refresh to ensure that the selection key code is set correctly again. @@ -384,12 +394,28 @@ function setReadonly(value: boolean) { elementsSelectable.value = true; } +function onPaneMouseDown(event: MouseEvent) { + if (isMiddleMouseButton(event)) { + setPanningEnabled(true); + + // Re-emit the event to start panning after setting the panning state to true + // This workaround is necessary because the Vue Flow library does not provide a way to + // start panning programmatically + void nextTick(() => + vueFlowRef.value + ?.querySelector('.vue-flow__pane') + ?.dispatchEvent(new MouseEvent('mousedown', event)), + ); + } +} + function onPaneMoveStart() { isPaneMoving.value = true; } function onPaneMoveEnd() { isPaneMoving.value = false; + setPanningEnabled(false); } /** @@ -559,6 +585,7 @@ provide(CanvasKey, { @nodes-change="onNodesChange" @move-start="onPaneMoveStart" @move-end="onPaneMoveEnd" + @mousedown="onPaneMouseDown" >