From f5633cff67fe723928523af17d487fbb08f4f7b4 Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Fri, 22 May 2026 17:16:36 +0800 Subject: [PATCH] fix(layout): prevent editor overflow over side panels --- apps/desktop/src/App.vue | 10 +- apps/desktop/src/components/grid/DataGrid.vue | 474 ++++++++++-------- .../app-tests/dataGridToolbarLayout.test.ts | 9 + .../app-tests/startupChunkBoundaries.test.ts | 6 + 4 files changed, 277 insertions(+), 222 deletions(-) diff --git a/apps/desktop/src/App.vue b/apps/desktop/src/App.vue index 7227ff00c..30c9efe97 100644 --- a/apps/desktop/src/App.vue +++ b/apps/desktop/src/App.vue @@ -817,7 +817,7 @@ onUnmounted(() => {
@@ -925,8 +925,8 @@ onUnmounted(() => { v-if="showAiPanel" :class=" isClassicLayout - ? 'h-full shrink-0 relative bg-background' - : 'h-full shrink-0 relative rounded-md border border-border/80 bg-background' + ? 'h-full shrink-0 relative z-30 isolate bg-background' + : 'h-full shrink-0 relative z-30 isolate rounded-md border border-border/80 bg-background' " :style="{ width: aiPanelWidth + 'px' }" > @@ -949,8 +949,8 @@ onUnmounted(() => { v-if="showHistory" :class=" isClassicLayout - ? 'h-full shrink-0 relative bg-background' - : 'h-full shrink-0 relative rounded-md border border-border/80 bg-background' + ? 'h-full shrink-0 relative z-30 isolate bg-background' + : 'h-full shrink-0 relative z-30 isolate rounded-md border border-border/80 bg-background' " :style="{ width: historyWidth + 'px' }" > diff --git a/apps/desktop/src/components/grid/DataGrid.vue b/apps/desktop/src/components/grid/DataGrid.vue index 5794b93c4..7c2abec5e 100644 --- a/apps/desktop/src/components/grid/DataGrid.vue +++ b/apps/desktop/src/components/grid/DataGrid.vue @@ -319,12 +319,14 @@ const whereSuggestionIndex = ref(-1); const whereFilterInputRef = ref(); const whereMeasureRef = ref(); const whereSuggestionLeft = ref(0); +const whereSuggestionPosition = ref({ left: 0, top: 0 }); const orderBySuggestions = ref([]); const orderBySuggestionIndex = ref(-1); const orderByInputRef = ref(); const orderByMeasureRef = ref(); const orderBySuggestionLeft = ref(0); +const orderBySuggestionPosition = ref({ left: 0, top: 0 }); const orderByInput = ref(""); const hasOrderByInput = computed(() => orderByInput.value.trim().length > 0); @@ -343,6 +345,16 @@ const whereSearchPaneStyle = computed(() => { }; }); +const whereSuggestionStyle = computed(() => ({ + left: `${whereSuggestionPosition.value.left}px`, + top: `${whereSuggestionPosition.value.top}px`, +})); + +const orderBySuggestionStyle = computed(() => ({ + left: `${orderBySuggestionPosition.value.left}px`, + top: `${orderBySuggestionPosition.value.top}px`, +})); + type LocalColumnFilterDraft = { columnIndex: number; values: Set; @@ -868,6 +880,11 @@ function updateWhereSuggestionPosition() { const cursorPos = input.selectionStart ?? 0; measure.textContent = whereFilterInput.value.slice(0, cursorPos); whereSuggestionLeft.value = measure.getBoundingClientRect().width; + const inputRect = input.getBoundingClientRect(); + whereSuggestionPosition.value = { + left: Math.max(0, Math.min(inputRect.left + whereSuggestionLeft.value, window.innerWidth - 180)), + top: inputRect.bottom + 2, + }; }); } @@ -988,6 +1005,11 @@ function updateOrderBySuggestionPosition() { const cursorPos = input.selectionStart ?? 0; measure.textContent = orderByInput.value.slice(0, cursorPos); orderBySuggestionLeft.value = measure.getBoundingClientRect().width; + const inputRect = input.getBoundingClientRect(); + orderBySuggestionPosition.value = { + left: Math.max(0, Math.min(inputRect.left + orderBySuggestionLeft.value, window.innerWidth - 180)), + top: inputRect.bottom + 2, + }; }); } @@ -3012,252 +3034,266 @@ defineExpose({
-
-
- -
- - - - + -
- - -
- {{ t("grid.queryEditReady") }} -
-
- - {{ t("grid.queryEditReadyHint", { table: tableMeta?.tableName }) }} - -
- - -
- - {{ t("grid.queryEditReadOnly") }} -
-
- - {{ queryEditabilityHint }} - -
- - - - - {{ t("grid.transactionActive") }} - - + + + + {{ t("grid.transactionActive") }} + + +
@@ -4756,6 +4792,10 @@ defineExpose({ min-width: 760px; } +.data-grid-topbar-scroll { + scrollbar-width: thin; +} + .data-grid-scroller { overflow-anchor: none; will-change: scroll-position; diff --git a/packages/app-tests/dataGridToolbarLayout.test.ts b/packages/app-tests/dataGridToolbarLayout.test.ts index 3246f765f..d7fcec92f 100644 --- a/packages/app-tests/dataGridToolbarLayout.test.ts +++ b/packages/app-tests/dataGridToolbarLayout.test.ts @@ -5,6 +5,15 @@ import test from "node:test"; const source = readFileSync("apps/desktop/src/components/grid/DataGrid.vue", "utf8"); test("data grid toolbar keeps a minimum content width", () => { + assert.match(source, /class="data-grid-topbar-scroll shrink-0 overflow-x-auto/); assert.match(source, /class="data-grid-topbar flex items-stretch/); assert.match(source, /\.data-grid-topbar\s*\{\s*min-width: 760px;/s); + assert.match(source, /\.data-grid-topbar-scroll\s*\{\s*scrollbar-width: thin;/s); +}); + +test("data grid toolbar suggestions render outside the scroll container", () => { + assert.match(source, //); + assert.match(source, /class="fixed z-50 min-w-\[180px\] rounded-md border bg-popover/); + assert.match(source, /:style="whereSuggestionStyle"/); + assert.match(source, /:style="orderBySuggestionStyle"/); }); diff --git a/packages/app-tests/startupChunkBoundaries.test.ts b/packages/app-tests/startupChunkBoundaries.test.ts index db036be24..91e19eee5 100644 --- a/packages/app-tests/startupChunkBoundaries.test.ts +++ b/packages/app-tests/startupChunkBoundaries.test.ts @@ -20,6 +20,12 @@ test("AI assistant panel stays closed on first launch to preserve startup memory assert.match(appSource, /const showAiPanel = ref\(localStorage\.getItem\("dbx-ai-panel-open"\) === "true"\)/); }); +test("dock side panels layer above the editor content", () => { + assert.match(appSource, /\? 'flex-1 min-w-0 overflow-hidden'/); + assert.match(appSource, /\? 'h-full shrink-0 relative z-30 isolate bg-background'/); + assert.match(appSource, /: 'h-full shrink-0 relative z-30 isolate rounded-md border border-border\/80 bg-background'/); +}); + test("app dialogs keep non-primary dialogs out of the startup chunk", () => { for (const component of ["ConnectionDialog", "EditorSettingsDialog", "DangerConfirmDialog"]) { assert.doesNotMatch(appDialogsSource, new RegExp(`import ${component} from`));