From ac7bc4f1911f913233eeeae5d229432fdff332c4 Mon Sep 17 00:00:00 2001 From: Alex Grozav Date: Mon, 3 Feb 2025 14:41:03 +0200 Subject: [PATCH] fix(editor): Fix showing and hiding canvas edge toolbar when hovering (#13009) --- packages/editor-ui/package.json | 4 +- .../canvas/elements/edges/CanvasEdge.test.ts | 24 +++++++++++ .../canvas/elements/edges/CanvasEdge.vue | 23 ++++++++++- pnpm-lock.yaml | 40 +++++++++---------- 4 files changed, 67 insertions(+), 24 deletions(-) diff --git a/packages/editor-ui/package.json b/packages/editor-ui/package.json index 4c9fa14918..da8b74dfe8 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -45,8 +45,8 @@ "@typescript/vfs": "^1.6.0", "@vue-flow/background": "^1.3.2", "@vue-flow/controls": "^1.1.2", - "@vue-flow/core": "^1.41.6", - "@vue-flow/minimap": "^1.5.0", + "@vue-flow/core": "^1.42.1", + "@vue-flow/minimap": "^1.5.2", "@vue-flow/node-resizer": "^1.4.0", "@vueuse/components": "^10.11.0", "@vueuse/core": "^10.11.0", diff --git a/packages/editor-ui/src/components/canvas/elements/edges/CanvasEdge.test.ts b/packages/editor-ui/src/components/canvas/elements/edges/CanvasEdge.test.ts index c8cad29247..5c4b1c65e6 100644 --- a/packages/editor-ui/src/components/canvas/elements/edges/CanvasEdge.test.ts +++ b/packages/editor-ui/src/components/canvas/elements/edges/CanvasEdge.test.ts @@ -71,6 +71,30 @@ describe('CanvasEdge', () => { expect(() => getByTestId('delete-connection-button')).toThrow(); }); + it('should hide toolbar after delay', async () => { + vi.useFakeTimers(); + + const user = userEvent.setup({ + advanceTimers: vi.advanceTimersByTime, + }); + + const { rerender, getByTestId, queryByTestId } = renderComponent({ + props: { hovered: true }, + }); + + await user.hover(getByTestId('edge-label')); + expect(queryByTestId('canvas-edge-toolbar')).toBeInTheDocument(); + + await rerender({ hovered: false }); + + await user.unhover(getByTestId('edge-label')); + expect(getByTestId('canvas-edge-toolbar')).toBeInTheDocument(); + + await vi.advanceTimersByTimeAsync(300); + + expect(queryByTestId('canvas-edge-toolbar')).not.toBeInTheDocument(); + }); + it('should compute edgeStyle correctly', () => { const { container } = renderComponent(); diff --git a/packages/editor-ui/src/components/canvas/elements/edges/CanvasEdge.vue b/packages/editor-ui/src/components/canvas/elements/edges/CanvasEdge.vue index e3b8d178ac..6072bdd82a 100644 --- a/packages/editor-ui/src/components/canvas/elements/edges/CanvasEdge.vue +++ b/packages/editor-ui/src/components/canvas/elements/edges/CanvasEdge.vue @@ -5,7 +5,7 @@ import { isValidNodeConnectionType } from '@/utils/typeGuards'; import type { Connection, EdgeProps } from '@vue-flow/core'; import { BaseEdge, EdgeLabelRenderer } from '@vue-flow/core'; import { NodeConnectionType } from 'n8n-workflow'; -import { computed, toRef, useCssModule } from 'vue'; +import { computed, ref, toRef, useCssModule, watch } from 'vue'; import CanvasEdgeToolbar from './CanvasEdgeToolbar.vue'; import { getEdgeRenderData } from './utils'; @@ -33,7 +33,26 @@ const connectionType = computed(() => : NodeConnectionType.Main, ); -const renderToolbar = computed(() => props.hovered && !props.readOnly); +const delayedHovered = ref(props.hovered); +const delayedHoveredSetTimeoutRef = ref(null); +const delayedHoveredTimeout = 300; + +watch( + () => props.hovered, + (isHovered) => { + if (isHovered) { + if (delayedHoveredSetTimeoutRef.value) clearTimeout(delayedHoveredSetTimeoutRef.value); + delayedHovered.value = true; + } else { + delayedHoveredSetTimeoutRef.value = setTimeout(() => { + delayedHovered.value = false; + }, delayedHoveredTimeout); + } + }, + { immediate: true }, +); + +const renderToolbar = computed(() => (props.selected || delayedHovered.value) && !props.readOnly); const isMainConnection = computed(() => data.value.source.type === NodeConnectionType.Main); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6ff4f0cb41..a18c773a3b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1443,19 +1443,19 @@ importers: version: 1.6.0(typescript@5.7.2) '@vue-flow/background': specifier: ^1.3.2 - version: 1.3.2(@vue-flow/core@1.41.6(vue@3.5.13(typescript@5.7.2)))(vue@3.5.13(typescript@5.7.2)) + version: 1.3.2(@vue-flow/core@1.42.1(vue@3.5.13(typescript@5.7.2)))(vue@3.5.13(typescript@5.7.2)) '@vue-flow/controls': specifier: ^1.1.2 - version: 1.1.2(@vue-flow/core@1.41.6(vue@3.5.13(typescript@5.7.2)))(vue@3.5.13(typescript@5.7.2)) + version: 1.1.2(@vue-flow/core@1.42.1(vue@3.5.13(typescript@5.7.2)))(vue@3.5.13(typescript@5.7.2)) '@vue-flow/core': - specifier: ^1.41.6 - version: 1.41.6(vue@3.5.13(typescript@5.7.2)) + specifier: ^1.42.1 + version: 1.42.1(vue@3.5.13(typescript@5.7.2)) '@vue-flow/minimap': - specifier: ^1.5.0 - version: 1.5.0(@vue-flow/core@1.41.6(vue@3.5.13(typescript@5.7.2)))(vue@3.5.13(typescript@5.7.2)) + specifier: ^1.5.2 + version: 1.5.2(@vue-flow/core@1.42.1(vue@3.5.13(typescript@5.7.2)))(vue@3.5.13(typescript@5.7.2)) '@vue-flow/node-resizer': specifier: ^1.4.0 - version: 1.4.0(@vue-flow/core@1.41.6(vue@3.5.13(typescript@5.7.2)))(vue@3.5.13(typescript@5.7.2)) + version: 1.4.0(@vue-flow/core@1.42.1(vue@3.5.13(typescript@5.7.2)))(vue@3.5.13(typescript@5.7.2)) '@vueuse/components': specifier: ^10.11.0 version: 10.11.0(vue@3.5.13(typescript@5.7.2)) @@ -6288,13 +6288,13 @@ packages: '@vue-flow/core': ^1.23.0 vue: ^3.3.0 - '@vue-flow/core@1.41.6': - resolution: {integrity: sha512-8zxcGRqiudra0obDMLTg9L89WxdlV0QrDOdyPYOKWDcD/UK5aT0MIL3Br9TF9AJmHW2z8QZq4cmmgroREL0jgQ==} + '@vue-flow/core@1.42.1': + resolution: {integrity: sha512-QzzTxMAXfOeETKc+N3XMp5XpiPxKBHK5kq98avgTsE6MXyeU2E8EkANwwgSB/hvJ/k36RjU0Y7BOwCHiqiI1tw==} peerDependencies: vue: ^3.3.0 - '@vue-flow/minimap@1.5.0': - resolution: {integrity: sha512-JhxXDF+8uTc7sgkZHDIvFpHqSl4wsK9xp8Kz5OHwNcXlgGcwqj4yad6jcc1B6bGxm+huESpNmoPotQbpMn6rVw==} + '@vue-flow/minimap@1.5.2': + resolution: {integrity: sha512-XNSpWwwXfCWqJilc2eCW+3ry3r9vhF8HmUw5wrAsUTHiss4R9k5uZLABo7c3T3VdcVRJ8pTfUJ9vjpzb8H+FKg==} peerDependencies: '@vue-flow/core': ^1.23.0 vue: ^3.3.0 @@ -19361,17 +19361,17 @@ snapshots: path-browserify: 1.0.1 vscode-uri: 3.0.8 - '@vue-flow/background@1.3.2(@vue-flow/core@1.41.6(vue@3.5.13(typescript@5.7.2)))(vue@3.5.13(typescript@5.7.2))': + '@vue-flow/background@1.3.2(@vue-flow/core@1.42.1(vue@3.5.13(typescript@5.7.2)))(vue@3.5.13(typescript@5.7.2))': dependencies: - '@vue-flow/core': 1.41.6(vue@3.5.13(typescript@5.7.2)) + '@vue-flow/core': 1.42.1(vue@3.5.13(typescript@5.7.2)) vue: 3.5.13(typescript@5.7.2) - '@vue-flow/controls@1.1.2(@vue-flow/core@1.41.6(vue@3.5.13(typescript@5.7.2)))(vue@3.5.13(typescript@5.7.2))': + '@vue-flow/controls@1.1.2(@vue-flow/core@1.42.1(vue@3.5.13(typescript@5.7.2)))(vue@3.5.13(typescript@5.7.2))': dependencies: - '@vue-flow/core': 1.41.6(vue@3.5.13(typescript@5.7.2)) + '@vue-flow/core': 1.42.1(vue@3.5.13(typescript@5.7.2)) vue: 3.5.13(typescript@5.7.2) - '@vue-flow/core@1.41.6(vue@3.5.13(typescript@5.7.2))': + '@vue-flow/core@1.42.1(vue@3.5.13(typescript@5.7.2))': dependencies: '@vueuse/core': 10.11.0(vue@3.5.13(typescript@5.7.2)) d3-drag: 3.0.0 @@ -19381,16 +19381,16 @@ snapshots: transitivePeerDependencies: - '@vue/composition-api' - '@vue-flow/minimap@1.5.0(@vue-flow/core@1.41.6(vue@3.5.13(typescript@5.7.2)))(vue@3.5.13(typescript@5.7.2))': + '@vue-flow/minimap@1.5.2(@vue-flow/core@1.42.1(vue@3.5.13(typescript@5.7.2)))(vue@3.5.13(typescript@5.7.2))': dependencies: - '@vue-flow/core': 1.41.6(vue@3.5.13(typescript@5.7.2)) + '@vue-flow/core': 1.42.1(vue@3.5.13(typescript@5.7.2)) d3-selection: 3.0.0 d3-zoom: 3.0.0 vue: 3.5.13(typescript@5.7.2) - '@vue-flow/node-resizer@1.4.0(@vue-flow/core@1.41.6(vue@3.5.13(typescript@5.7.2)))(vue@3.5.13(typescript@5.7.2))': + '@vue-flow/node-resizer@1.4.0(@vue-flow/core@1.42.1(vue@3.5.13(typescript@5.7.2)))(vue@3.5.13(typescript@5.7.2))': dependencies: - '@vue-flow/core': 1.41.6(vue@3.5.13(typescript@5.7.2)) + '@vue-flow/core': 1.42.1(vue@3.5.13(typescript@5.7.2)) d3-drag: 3.0.0 d3-selection: 3.0.0 vue: 3.5.13(typescript@5.7.2)