diff --git a/apps/desktop/src/components/editor/QueryEditor.vue b/apps/desktop/src/components/editor/QueryEditor.vue index ee6bfdc477..f0969184b3 100644 --- a/apps/desktop/src/components/editor/QueryEditor.vue +++ b/apps/desktop/src/components/editor/QueryEditor.vue @@ -80,6 +80,7 @@ import { type SqlObjectNavigationTarget, } from "@/lib/sql/sqlNavigation"; import { buildHoverTableSql, ddlForHoverPreview, hoverTableMatchesScope, quoteQualifiedName, reformatHoverDdl, scopeHoverTables, type HoverTableScope } from "@/lib/editor/hoverTableSql"; +import { constrainSqlHoverLayout } from "@/lib/editor/sqlHoverLayout"; import { lineColumnToOffset, sqlErrorDecorationRange as resolveSqlErrorDecorationRange } from "@/lib/sql/sqlDiagnostics"; import { DBX_TABLE_REFERENCE_MIME, @@ -2314,7 +2315,7 @@ async function ensureForeignKeysForTables(tables: Array<{ name: string; database } } -function createHoverDom(title: string, detail: string, sqlContent?: string, rows: string[] = []) { +function createHoverDom(title: string, detail: string, sqlContent?: string, rows: string[] = []): { dom: HTMLElement; mount?: () => void; destroy?: () => void } { const dom = document.createElement("div"); dom.className = "rounded-md border bg-popover px-3 py-2 text-xs text-popover-foreground shadow-md"; @@ -2328,13 +2329,15 @@ function createHoverDom(title: string, detail: string, sqlContent?: string, rows detailNode.textContent = detail; dom.appendChild(detailNode); + let layoutController: ReturnType | null = null; + if (sqlContent) { const separator = document.createElement("div"); separator.className = "mt-2 border-t border-border/60"; dom.appendChild(separator); const sqlContainer = document.createElement("div"); - sqlContainer.className = "mt-1.5 max-h-64 overflow-y-auto text-[11px] leading-5 whitespace-pre font-mono"; + sqlContainer.className = "mt-1.5 text-[11px] leading-5 whitespace-pre font-mono"; if (hoverSqlHighlighter) { sqlContainer.innerHTML = hoverSqlHighlighter(sqlContent, isDark.value ? "dark" : "light"); @@ -2344,6 +2347,9 @@ function createHoverDom(title: string, detail: string, sqlContent?: string, rows } dom.appendChild(sqlContainer); + // 返回 mount/destroy 给 CodeMirror TooltipView 生命周期钩子, + // 避免 MutationObserver 监听 body 全子树来兜底清理。 + layoutController = constrainSqlHoverLayout(dom, sqlContainer); } for (const row of rows) { @@ -2353,7 +2359,11 @@ function createHoverDom(title: string, detail: string, sqlContent?: string, rows dom.appendChild(rowNode); } - return dom; + return { + dom, + mount: layoutController ? () => layoutController?.mount() : undefined, + destroy: layoutController ? () => layoutController?.destroy() : undefined, + }; } async function resolveSqlHoverTooltip(currentView: EditorViewType, pos: number) { @@ -2476,9 +2486,7 @@ async function resolveSqlHoverTooltip(currentView: EditorViewType, pos: number) return { pos: range.from, end: range.to, - create: () => ({ - dom: createHoverDom(table.name, sqlObjectHoverDetail(table), sqlContent, metadataLoadFailed ? ["[DBX] Failed to load table structure — check connection"] : undefined), - }), + create: () => createHoverDom(table.name, sqlObjectHoverDetail(table), sqlContent, metadataLoadFailed ? ["[DBX] Failed to load table structure — check connection"] : undefined), }; } @@ -2502,9 +2510,7 @@ async function resolveSqlHoverTooltip(currentView: EditorViewType, pos: number) return { pos: range.from, end: range.to, - create: () => ({ - dom: createHoverDom(column.name, column.dataType || "column", undefined, [column.schema ? `${column.schema}.${column.table}` : column.table, ...(column.comment?.trim() ? [column.comment.trim()] : [])]), - }), + create: () => createHoverDom(column.name, column.dataType || "column", undefined, [column.schema ? `${column.schema}.${column.table}` : column.table, ...(column.comment?.trim() ? [column.comment.trim()] : [])]), }; } } catch { @@ -5802,6 +5808,66 @@ defineExpose({