From aac09d3cb7e2097f5247602e1705ab513d10f5a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E4=B8=AB=E8=AE=B2=E6=A2=B5?= Date: Wed, 26 Aug 2026 22:04:42 +0800 Subject: [PATCH] fix(grid): keep canvas hover state across drawing surface flips --- apps/desktop/src/components/grid/DataGrid.vue | 11 +++++++++-- .../__tests__/DataGridCellDetailSelection.spec.ts | 12 ++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/apps/desktop/src/components/grid/DataGrid.vue b/apps/desktop/src/components/grid/DataGrid.vue index b759011f28..b1ee05cea6 100644 --- a/apps/desktop/src/components/grid/DataGrid.vue +++ b/apps/desktop/src/components/grid/DataGrid.vue @@ -6893,6 +6893,11 @@ function inactiveCanvasSurface(): HTMLCanvasElement | null { return (canvasUsingBackSurface.value ? canvasRef.value : canvasBackRef.value) ?? null; } const canvasOverlayRef = ref(); + +function isCanvasGridInteractionTarget(target: Node): boolean { + return canvasOverlayRef.value?.contains(target) === true || canvasRef.value?.contains(target) === true || canvasBackRef.value?.contains(target) === true; +} + const canvasViewportWidth = ref(0); const canvasViewportHeight = ref(0); const canvasScrollTop = ref(0); @@ -7247,7 +7252,9 @@ function onCanvasMouseMove(event: MouseEvent) { function onCanvasMouseLeave(event?: MouseEvent) { const relatedTarget = event?.relatedTarget; - if (relatedTarget instanceof Node && canvasOverlayRef.value?.contains(relatedTarget)) return; + // A draw swaps the visible canvas surface. Treat that as an in-grid move so + // the action overlay does not disappear between the two surfaces. + if (relatedTarget instanceof Node && isCanvasGridInteractionTarget(relatedTarget)) return; const previous = canvasHoverCell.value; if (previous) { const previousActualColIdx = visibleColumnIndexes.value[previous.visibleColIdx]; @@ -7270,7 +7277,7 @@ function keepCanvasDetailHover() { function clearCanvasDetailHover(event?: MouseEvent) { const relatedTarget = event?.relatedTarget; - if (relatedTarget instanceof Node && (canvasOverlayRef.value?.contains(relatedTarget) || activeCanvasSurface()?.contains(relatedTarget) || canvasBackRef.value?.contains(relatedTarget))) { + if (relatedTarget instanceof Node && isCanvasGridInteractionTarget(relatedTarget)) { return; } onCanvasMouseLeave(); diff --git a/apps/desktop/src/components/grid/__tests__/DataGridCellDetailSelection.spec.ts b/apps/desktop/src/components/grid/__tests__/DataGridCellDetailSelection.spec.ts index 02b52d873d..d961fe6042 100644 --- a/apps/desktop/src/components/grid/__tests__/DataGridCellDetailSelection.spec.ts +++ b/apps/desktop/src/components/grid/__tests__/DataGridCellDetailSelection.spec.ts @@ -4,6 +4,18 @@ import { describe, expect, it } from "vitest"; const dataGridSource = readFileSync(new URL("../DataGrid.vue", import.meta.url), "utf8"); describe("DataGrid cell detail selection", () => { + it("keeps Canvas hover state while the renderer swaps drawing surfaces", () => { + expect(dataGridSource).toContain("function isCanvasGridInteractionTarget(target: Node): boolean"); + expect(dataGridSource).toContain("canvasOverlayRef.value?.contains(target) === true"); + expect(dataGridSource).toContain("canvasRef.value?.contains(target) === true"); + expect(dataGridSource).toContain("canvasBackRef.value?.contains(target) === true"); + + const canvasLeave = dataGridSource.match(/function onCanvasMouseLeave[\s\S]*?\n\}/)?.[0]; + const detailLeave = dataGridSource.match(/function clearCanvasDetailHover[\s\S]*?\n\}/)?.[0]; + expect(canvasLeave).toContain("isCanvasGridInteractionTarget(relatedTarget)"); + expect(detailLeave).toContain("isCanvasGridInteractionTarget(relatedTarget)"); + }); + it("gates only the three hover Info buttons with the visibility setting", () => { expect(dataGridSource).toContain("const cellDetailButtonEnabled = computed(() => settingsStore.editorSettings.dataGridCellDetailButtonVisible);"); expect(dataGridSource.match(/v-if="cellDetailButtonEnabled"/g)).toHaveLength(3);