diff --git a/apps/desktop/src/App.vue b/apps/desktop/src/App.vue
index 75bed0b06..652fa13b9 100644
--- a/apps/desktop/src/App.vue
+++ b/apps/desktop/src/App.vue
@@ -2613,6 +2613,7 @@ onUnmounted(() => {
@saved="onQueryEditorObjectSourceSaved"
/>
+
diff --git a/apps/desktop/src/components/editor/QueryEditor.vue b/apps/desktop/src/components/editor/QueryEditor.vue
index 20df08697..912879a92 100644
--- a/apps/desktop/src/components/editor/QueryEditor.vue
+++ b/apps/desktop/src/components/editor/QueryEditor.vue
@@ -3857,7 +3857,9 @@ onMounted(async () => {
{ decorations: (v) => v.decorations },
);
- const tooltipParent = editorRef.value.closest("#root") ?? editorRef.value;
+ const editorElement = editorRef.value;
+ if (!editorElement) return;
+ const tooltipParent = editorElement.closest("#root")?.querySelector("#dbx-query-editor-tooltip-root") ?? editorElement;
const state = EditorState.create({
doc: props.modelValue,
selection: normalizedEditorSelection(props.initialSelection, props.modelValue.length),
@@ -4189,7 +4191,7 @@ onMounted(async () => {
],
});
- view.value = new EditorView({ state, parent: editorRef.value });
+ view.value = new EditorView({ state, parent: editorElement });
registerEditorScrollbarPointerGuard(view.value);
view.value.scrollDOM.addEventListener("scroll", scheduleEditorViewportEmit, {
passive: true,
diff --git a/apps/desktop/src/lib/__tests__/editor/queryEditorTooltipParent.dom.spec.ts b/apps/desktop/src/lib/__tests__/editor/queryEditorTooltipParent.dom.spec.ts
index e2054bcbc..bf8b4035b 100644
--- a/apps/desktop/src/lib/__tests__/editor/queryEditorTooltipParent.dom.spec.ts
+++ b/apps/desktop/src/lib/__tests__/editor/queryEditorTooltipParent.dom.spec.ts
@@ -3,6 +3,7 @@
import { EditorState } from "@codemirror/state";
import { EditorView, showTooltip, tooltips, type Tooltip } from "@codemirror/view";
import { describe, expect, it } from "vitest";
+import { editorFontTheme } from "@/lib/editor/editorThemes";
function staticTooltip(label: string): Tooltip {
return {
@@ -15,28 +16,31 @@ function staticTooltip(label: string): Tooltip {
};
}
-describe("CodeMirror tooltip app-root portal", () => {
+describe("CodeMirror tooltip host", () => {
it("keeps containers independent and removes them with each editor", () => {
- document.body.innerHTML = '';
+ document.body.innerHTML = '';
const root = document.querySelector("#root")!;
const splitPane = document.querySelector("[data-split-pane]")!;
+ const tooltipHost = document.querySelector("#dbx-query-editor-tooltip-root")!;
const firstEditor = document.querySelector('[data-editor="first"]')!;
const secondEditor = document.querySelector('[data-editor="second"]')!;
root.style.overflow = "hidden";
splitPane.style.overflow = "hidden";
+ const editorTheme = EditorView.theme({ "&": { backgroundColor: "rgb(255, 255, 255)" } });
+ const editorExtensions = [editorTheme, editorFontTheme(EditorView, 13, "monospace", { fixedHeight: true })];
const firstView = new EditorView({
parent: firstEditor,
state: EditorState.create({
doc: "select first",
- extensions: [tooltips({ parent: root }), showTooltip.of(staticTooltip("first"))],
+ extensions: [...editorExtensions, tooltips({ parent: tooltipHost }), showTooltip.of(staticTooltip("first"))],
}),
});
const secondView = new EditorView({
parent: secondEditor,
state: EditorState.create({
doc: "select second",
- extensions: [tooltips({ parent: root }), showTooltip.of(staticTooltip("second"))],
+ extensions: [...editorExtensions, tooltips({ parent: tooltipHost }), showTooltip.of(staticTooltip("second"))],
}),
});
@@ -46,8 +50,13 @@ describe("CodeMirror tooltip app-root portal", () => {
const secondContainer = secondTooltip.parentElement!;
expect(splitPane.contains(firstTooltip)).toBe(false);
- expect(firstContainer.parentElement).toBe(root);
- expect(secondContainer.parentElement).toBe(root);
+ expect(firstContainer.parentElement).toBe(tooltipHost);
+ expect(secondContainer.parentElement).toBe(tooltipHost);
+ expect(root.children).toHaveLength(1);
+ expect(tooltipHost.style.width).toBe("0px");
+ expect(tooltipHost.style.height).toBe("0px");
+ expect(getComputedStyle(firstContainer).height).toBe("100%");
+ expect(getComputedStyle(firstContainer).backgroundColor).toBe("rgb(255, 255, 255)");
expect(firstContainer).not.toBe(secondContainer);
expect(firstTooltip.style.position).toBe("fixed");
expect(secondTooltip.style.position).toBe("fixed");
diff --git a/apps/desktop/src/lib/__tests__/editor/queryEditorTooltipParent.spec.ts b/apps/desktop/src/lib/__tests__/editor/queryEditorTooltipParent.spec.ts
index 15fa10ee0..3c5b3c6c2 100644
--- a/apps/desktop/src/lib/__tests__/editor/queryEditorTooltipParent.spec.ts
+++ b/apps/desktop/src/lib/__tests__/editor/queryEditorTooltipParent.spec.ts
@@ -2,13 +2,19 @@ import { readFileSync } from "node:fs";
import { describe, expect, it } from "vitest";
const queryEditorSource = readFileSync(new URL("../../../components/editor/QueryEditor.vue", import.meta.url), "utf8");
+const appSource = readFileSync(new URL("../../../App.vue", import.meta.url), "utf8");
const globalStylesSource = readFileSync(new URL("../../../styles/globals.css", import.meta.url), "utf8");
describe("QueryEditor tooltip container", () => {
- it("portals CodeMirror tooltips to the stable app root without using document.body", () => {
- expect(queryEditorSource).toContain('const tooltipParent = editorRef.value.closest("#root") ?? editorRef.value;');
+ it("portals CodeMirror tooltips to a zero-sized app host", () => {
+ expect(queryEditorSource).toContain("const editorElement = editorRef.value;");
+ expect(queryEditorSource).toContain("if (!editorElement) return;");
+ expect(queryEditorSource).toContain('querySelector("#dbx-query-editor-tooltip-root") ?? editorElement');
expect(queryEditorSource).toContain("tooltips({ parent: tooltipParent })");
+ expect(queryEditorSource).toContain("new EditorView({ state, parent: editorElement })");
expect(queryEditorSource).not.toContain("tooltips({ parent: document.body })");
+ expect(appSource).toContain('id="dbx-query-editor-tooltip-root"');
+ expect(appSource).toContain('class="fixed left-0 top-0 z-[70] h-0 w-0 overflow-visible"');
});
it("keeps the app root viewport-sized without a transformed containing block", () => {