fix(editor): let mouse hide/re-show the shared query results pane

This commit is contained in:
Lem26 authored and GitHub committed 2026-09-05 13:02:41 +08:00
1 parent b8d4388caf
commit 73c167bed3
5 files changed
+85 -10

No files matched your search

@@ -664,6 +664,21 @@ function toggleResultsPane(): boolean {
return true;
}
/**
* The "hide results" chevron. In the shared result surface (result-only) the
* local resultsPaneOpen ref is inert — the pane is always rendered there and
* the real collapse state lives in SqlEditorWorkspace.showResultPane — so the
* chevron bubbles a toggle event up instead. Per-tab surfaces (data tabs)
* keep the local collapse behavior.
*/
function handleHideResultsPane() {
if (props.resultOnly) {
emit("toggleResultsPane");
} else {
resultsPaneOpen.value = false;
}
}
function onResultsResized(payload: { panes: { size: number }[] }) {
const resultsPane = payload.panes[1];
if (resultsPane?.size != null && resultsPane.size >= 20 && resultsPane.size <= 85) {
@@ -1664,7 +1679,7 @@ defineExpose({
</PopoverContent>
</Popover>
<LightTooltip :text="t('editor.hideResultsPane')" side="bottom" :delay="0" :close-delay="0" nowrap>
<Button variant="ghost" size="icon" class="h-6 w-7 shrink-0 text-muted-foreground hover:text-foreground" :title="t('editor.hideResultsPane')" :aria-label="t('editor.hideResultsPane')" @click="resultsPaneOpen = false">
<Button variant="ghost" size="icon" class="h-6 w-7 shrink-0 text-muted-foreground hover:text-foreground" :title="t('editor.hideResultsPane')" :aria-label="t('editor.hideResultsPane')" @click="handleHideResultsPane">
<ChevronDown class="h-3.5 w-3.5" />
</Button>
</LightTooltip>
@@ -1,10 +1,12 @@
<script setup lang="ts">
import { computed, nextTick, onMounted, ref, useAttrs } from "vue";
import { computed, nextTick, onMounted, ref, useAttrs, watch } from "vue";
import { useI18n } from "vue-i18n";
import { Splitpanes, Pane } from "splitpanes";
import "splitpanes/dist/splitpanes.css";
import "./sqlEditorWorkspace.css";
import { useQueryStore } from "@/stores/queryStore";
import { safeLocalStorageGet, safeLocalStorageSet } from "@/lib/backend/safeStorage";
import { Button } from "@/components/ui/button";
import EditorGroup from "./EditorGroup.vue";
import QueryResultSurface from "./QueryResultSurface.vue";
import { createContentSurfaceEventForwarders } from "@/lib/tabs/contentSurfaceEvents";
@@ -71,13 +73,7 @@ defineExpose({
const group = groupForElement(element) ?? activeEditorGroup();
return group?.refreshData() ?? resultSurfaceRef.value?.refreshData() ?? false;
},
toggleResultsPane: () => {
if (!showSharedResult.value) {
return activeEditorGroup()?.toggleResultsPane() ?? false;
}
showResultPane.value = !showResultPane.value;
return true;
},
toggleResultsPane: () => toggleSharedResultsPane(),
refreshQueryEditorCompletionCache: () => activeEditorGroup()?.refreshQueryEditorCompletionCache() ?? false,
handleModRTarget: (target: Element) => {
// DataGrid, Elasticsearch JSON, and the cell-detail editor (Teleported
@@ -107,6 +103,7 @@ defineExpose({
executeRedisCommand: (command: string) => (groupForElement(commandTargetElement(null)) ?? activeEditorGroup())?.executeRedisCommand(command) ?? Promise.resolve(false),
});
const { t } = useI18n();
const queryStore = useQueryStore();
const activeTab = computed(() => queryStore.tabs.find((tab) => tab.id === queryStore.activeTabId));
const showSharedResult = computed(() => activeTab.value?.mode === "query");
@@ -124,6 +121,31 @@ const showResultPane = ref(true);
// (globals.css kills it for horizontal splitpanes).
const resultPaneTargetSize = computed(() => (showSharedResult.value && showResultPane.value ? resultPaneSize.value : 0));
const editorPaneSize = computed(() => 100 - resultPaneTargetSize.value);
/**
* Single toggle entry point for the shared result pane. Reached from the
* keyboard shortcut (exposed toggleResultsPane) and from the shared surface's
* "hide results" chevron, which bubbles a toggleResultsPane event up through
* ContentArea → QueryResultSurface. Non-query tabs (data etc.) render a plain
* ContentArea and keep their own per-tab resultsPaneOpen collapse behavior.
*/
function toggleSharedResultsPane(): boolean {
if (!showSharedResult.value) {
return activeEditorGroup()?.toggleResultsPane() ?? false;
}
showResultPane.value = !showResultPane.value;
return true;
}
// Restores the pre-split "running a query re-expands the results pane"
// behavior (see issue #6193): a collapsed shared pane comes back when the
// active tab starts executing.
watch(
() => activeTab.value?.isExecuting,
(isExecuting) => {
if (isExecuting) showResultPane.value = true;
},
);
// Entrance choreography is hydration-gated: groups present at first render
// never animate (no load choreography); only groups created later — by a
// split — materialize from their owner's side of the divider.
@@ -195,7 +217,7 @@ function handleFocusStatement(tabId: string, range: StatementRange | null): bool
</script>
<template>
<div class="sql-editor-workspace flex h-full min-h-0 flex-1 flex-col overflow-hidden" :class="workspaceClass">
<div class="sql-editor-workspace relative flex h-full min-h-0 flex-1 flex-col overflow-hidden" :class="workspaceClass">
<Splitpanes horizontal class="sql-editor-workspace-split flex-1 min-h-0" :class="{ 'result-pane-collapsed': resultPaneTargetSize === 0 }" @resized="onSharedResultResized">
<Pane class="min-h-0 min-w-0" :size="editorPaneSize" :min-size="100 - SHARED_RESULT_PANE_MAX_SIZE">
<Splitpanes
@@ -237,6 +259,7 @@ function handleFocusStatement(tabId: string, range: StatementRange | null): bool
ref="resultSurfaceRef"
v-bind="resultSurfaceBindings"
class="h-full"
@toggle-results-pane="toggleSharedResultsPane"
@preview-statement="
(tabId: string, range: { from: number; to: number } | null) => {
handlePreviewStatement(tabId, range);
@@ -252,5 +275,19 @@ function handleFocusStatement(tabId: string, range: StatementRange | null): bool
</Transition>
</Pane>
</Splitpanes>
<!-- The shared surface unmounts when collapsed, so the mouse re-show
affordance lives here, outside the collapsible pane. -->
<Button
v-if="showSharedResult && !showResultPane"
type="button"
variant="secondary"
size="sm"
class="absolute bottom-3 right-3 z-20 h-7 gap-1.5 rounded-full border bg-background/95 px-3 text-xs shadow-lg hover:bg-accent"
:title="t('editor.showResultsPane')"
:aria-label="t('editor.showResultsPane')"
@click="showResultPane = true"
>
{{ t("editor.showResultsPane") }}
</Button>
</div>
</template>
@@ -9,6 +9,7 @@ const editorSurfaceSource = readFileSync(new URL("../QueryEditorSurface.vue", im
const resultSurfaceSource = readFileSync(new URL("../QueryResultSurface.vue", import.meta.url), "utf8");
const contentAreaSource = readFileSync(new URL("../ContentArea.vue", import.meta.url), "utf8");
const surfaceContractSource = readFileSync(new URL("../querySurfaces.ts", import.meta.url), "utf8");
const surfaceEventsSource = readFileSync(new URL("../../../lib/tabs/contentSurfaceEvents.ts", import.meta.url), "utf8");
describe("SQL editor workspace single-group tracer bullet", () => {
it("composes splitpanes editor groups and one shared result surface", () => {
@@ -81,6 +82,26 @@ describe("SQL editor workspace single-group tracer bullet", () => {
expect(resultSurfaceSource).toContain('v-bind="bindings"');
});
it("lets the mouse hide and re-show the shared query results pane", () => {
// Regression (#8076 refactor): the shared result surface's "hide results"
// chevron wrote the inert per-instance resultsPaneOpen ref, so query-tab
// results could no longer be collapsed by mouse — the pane was pinned
// open. The chevron must bubble a toggleResultsPane event up through the
// surface contract to the workspace, which owns the real collapse state.
expect(surfaceContractSource).toContain("toggleResultsPane: []");
expect(surfaceEventsSource).toContain('"toggleResultsPane",');
expect(contentAreaSource).toContain('emit("toggleResultsPane")');
expect(contentAreaSource).toContain('@click="handleHideResultsPane"');
expect(workspaceSource).toContain('@toggle-results-pane="toggleSharedResultsPane"');
expect(workspaceSource).toContain("showResultPane.value = !showResultPane.value");
// Collapsing unmounts the shared surface, so the mouse re-show affordance
// must live at the workspace level, not inside the collapsible pane.
expect(workspaceSource).toContain("editor.showResultsPane");
expect(workspaceSource).toContain("showSharedResult && !showResultPane");
// Running a query re-expands a collapsed pane (issue #6193 promise).
expect(workspaceSource).toContain("if (isExecuting) showResultPane.value = true;");
});
it("animates layout transitions for split groups and the shared result surface", () => {
const workspaceCssSource = readFileSync(new URL("../sqlEditorWorkspace.css", import.meta.url), "utf8");
expect(workspaceSource).toContain('import "./sqlEditorWorkspace.css"');
@@ -95,4 +95,5 @@ export interface ContentAreaSurfaceEmits {
openSettings: [initialTab?: string, initialSection?: string];
openConnectionSettings: [connectionId: string, initialTab: "advanced"];
toggleZenMode: [];
toggleResultsPane: [];
}