diff --git a/packages/editor-ui/src/components/canvas/Canvas.spec.ts b/packages/editor-ui/src/components/canvas/Canvas.spec.ts index ef93b95282..256377a8f3 100644 --- a/packages/editor-ui/src/components/canvas/Canvas.spec.ts +++ b/packages/editor-ui/src/components/canvas/Canvas.spec.ts @@ -28,6 +28,7 @@ beforeEach(() => { afterEach(() => { vi.clearAllMocks(); + vi.useRealTimers(); }); describe('Canvas', () => { @@ -36,8 +37,8 @@ describe('Canvas', () => { expect(getByTestId('canvas')).toBeVisible(); expect(getByTestId('canvas-background')).toBeVisible(); - expect(getByTestId('canvas-minimap')).toBeVisible(); expect(getByTestId('canvas-controls')).toBeVisible(); + expect(getByTestId('canvas-minimap')).toBeInTheDocument(); }); it('should render nodes and edges', async () => { @@ -142,4 +143,79 @@ describe('Canvas', () => { ], ]); }); + + describe('minimap', () => { + const minimapVisibilityDelay = 1000; + const minimapTransitionDuration = 300; + + it('should show minimap for 1sec after panning', async () => { + vi.useFakeTimers(); + + const nodes = [createCanvasNodeElement()]; + const { getByTestId, container } = renderComponent({ + props: { + nodes, + }, + }); + + await waitFor(() => expect(container.querySelectorAll('.vue-flow__node')).toHaveLength(1)); + + const canvas = getByTestId('canvas'); + const pane = canvas.querySelector('.vue-flow__pane'); + if (!pane) throw new Error('VueFlow pane not found'); + + await fireEvent.keyDown(pane, { view: window, key: 'Shift' }); + await fireEvent.mouseDown(pane, { view: window }); + await fireEvent.mouseMove(pane, { + view: window, + clientX: 100, + clientY: 100, + }); + await fireEvent.mouseUp(pane, { view: window }); + await fireEvent.keyUp(pane, { view: window, key: 'Shift' }); + + vi.advanceTimersByTime(minimapTransitionDuration); + await waitFor(() => expect(getByTestId('canvas-minimap')).toBeVisible()); + vi.advanceTimersByTime(minimapVisibilityDelay + minimapTransitionDuration); + await waitFor(() => expect(getByTestId('canvas-minimap')).not.toBeVisible()); + }); + + it('should keep minimap visible when hovered', async () => { + vi.useFakeTimers(); + + const nodes = [createCanvasNodeElement()]; + const { getByTestId, container } = renderComponent({ + props: { + nodes, + }, + }); + + await waitFor(() => expect(container.querySelectorAll('.vue-flow__node')).toHaveLength(1)); + + const canvas = getByTestId('canvas'); + const pane = canvas.querySelector('.vue-flow__pane'); + if (!pane) throw new Error('VueFlow pane not found'); + + await fireEvent.keyDown(pane, { view: window, key: 'Shift' }); + await fireEvent.mouseDown(pane, { view: window }); + await fireEvent.mouseMove(pane, { + view: window, + clientX: 100, + clientY: 100, + }); + await fireEvent.mouseUp(pane, { view: window }); + await fireEvent.keyUp(pane, { view: window, key: 'Shift' }); + + vi.advanceTimersByTime(minimapTransitionDuration); + await waitFor(() => expect(getByTestId('canvas-minimap')).toBeVisible()); + + await fireEvent.mouseEnter(getByTestId('canvas-minimap')); + vi.advanceTimersByTime(minimapVisibilityDelay + minimapTransitionDuration); + await waitFor(() => expect(getByTestId('canvas-minimap')).toBeVisible()); + + await fireEvent.mouseLeave(getByTestId('canvas-minimap')); + vi.advanceTimersByTime(minimapVisibilityDelay + minimapTransitionDuration); + await waitFor(() => expect(getByTestId('canvas-minimap')).not.toBeVisible()); + }); + }); }); diff --git a/packages/editor-ui/src/components/canvas/Canvas.vue b/packages/editor-ui/src/components/canvas/Canvas.vue index 2f9d73d02b..2b0272777b 100644 --- a/packages/editor-ui/src/components/canvas/Canvas.vue +++ b/packages/editor-ui/src/components/canvas/Canvas.vue @@ -309,6 +309,7 @@ function emitWithLastSelectedNode(emitFn: (id: string) => void) { const defaultZoom = 1; const zoom = ref(defaultZoom); +const isPaneMoving = ref(false); function getProjectedPosition(event?: MouseEvent) { const bounds = viewportRef.value?.getBoundingClientRect() ?? { left: 0, top: 0 }; @@ -354,6 +355,14 @@ function setReadonly(value: boolean) { elementsSelectable.value = true; } +function onPaneMoveStart() { + isPaneMoving.value = true; +} + +function onPaneMoveEnd() { + isPaneMoving.value = false; +} + /** * Context menu */ @@ -414,10 +423,45 @@ function onContextMenuAction(action: ContextMenuAction, nodeIds: string[]) { * Minimap */ +const minimapVisibilityDelay = 1000; +const minimapHideTimeout = ref(null); +const isMinimapVisible = ref(false); + function minimapNodeClassnameFn(node: CanvasNode) { return `minimap-node-${node.data?.render.type.replace(/\./g, '-') ?? 'default'}`; } +watch(isPaneMoving, (value) => { + if (value) { + showMinimap(); + } else { + hideMinimap(); + } +}); + +function showMinimap() { + if (minimapHideTimeout.value) { + clearTimeout(minimapHideTimeout.value); + minimapHideTimeout.value = null; + } + + isMinimapVisible.value = true; +} + +function hideMinimap() { + minimapHideTimeout.value = setTimeout(() => { + isMinimapVisible.value = false; + }, minimapVisibilityDelay); +} + +function onMinimapMouseEnter() { + showMinimap(); +} + +function onMinimapMouseLeave() { + hideMinimap(); +} + /** * Lifecycle */ @@ -474,6 +518,8 @@ provide(CanvasKey, { @contextmenu="onOpenContextMenu" @viewport-change="onViewportChange" @nodes-change="onNodesChange" + @move-start="onPaneMoveStart" + @move-end="onPaneMoveEnd" >