fix(editor): prevent tooltip portal from covering app
This commit is contained in:
parent
12373868ec
commit
4f9ea3f5cc
|
|
@ -2613,6 +2613,7 @@ onUnmounted(() => {
|
|||
@saved="onQueryEditorObjectSourceSaved"
|
||||
/>
|
||||
</TooltipProvider>
|
||||
<div id="dbx-query-editor-tooltip-root" class="fixed left-0 top-0 z-[70] h-0 w-0 overflow-visible" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -3857,7 +3857,9 @@ onMounted(async () => {
|
|||
{ decorations: (v) => v.decorations },
|
||||
);
|
||||
|
||||
const tooltipParent = editorRef.value.closest<HTMLElement>("#root") ?? editorRef.value;
|
||||
const editorElement = editorRef.value;
|
||||
if (!editorElement) return;
|
||||
const tooltipParent = editorElement.closest<HTMLElement>("#root")?.querySelector<HTMLElement>("#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,
|
||||
|
|
|
|||
|
|
@ -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 = '<div id="root"><div data-split-pane><div data-editor="first"></div><div data-editor="second"></div></div></div>';
|
||||
document.body.innerHTML = '<div id="root"><div data-app><div data-split-pane><div data-editor="first"></div><div data-editor="second"></div></div><div id="dbx-query-editor-tooltip-root" style="position: fixed; width: 0; height: 0; overflow: visible"></div></div></div>';
|
||||
const root = document.querySelector<HTMLElement>("#root")!;
|
||||
const splitPane = document.querySelector<HTMLElement>("[data-split-pane]")!;
|
||||
const tooltipHost = document.querySelector<HTMLElement>("#dbx-query-editor-tooltip-root")!;
|
||||
const firstEditor = document.querySelector<HTMLElement>('[data-editor="first"]')!;
|
||||
const secondEditor = document.querySelector<HTMLElement>('[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");
|
||||
|
|
|
|||
|
|
@ -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<HTMLElement>("#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<HTMLElement>("#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", () => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue