From e78a510485694200ba7fffc102464eab9dc38e60 Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Thu, 30 Jul 2026 08:48:58 +0800 Subject: [PATCH] fix(desktop): scope dark styles and restore dialog blur --- apps/desktop/src/App.vue | 3 +++ .../grid/DataGridConditionEditor.vue | 14 +++++------ .../__tests__/DataGridConditionEditor.spec.ts | 8 +++++++ apps/desktop/src/lib/app/dialogBackdrop.ts | 24 +++++++++++++++++++ .../__tests__/legacyWebviewFallback.spec.ts | 10 +++++++- apps/desktop/src/styles/globals.css | 4 ++++ 6 files changed, 55 insertions(+), 8 deletions(-) create mode 100644 apps/desktop/src/lib/app/dialogBackdrop.ts diff --git a/apps/desktop/src/App.vue b/apps/desktop/src/App.vue index df4cd4364..78837e6bd 100644 --- a/apps/desktop/src/App.vue +++ b/apps/desktop/src/App.vue @@ -34,6 +34,7 @@ import { useVisibilityChange } from "@/composables/useVisibilityChange"; import { useWebDavAutoUpload } from "@/composables/useWebDavAutoUpload"; import { useScheduledDatabaseBackups } from "@/composables/useScheduledDatabaseBackups"; import { shouldDrawDesktopWindowFrame } from "@/composables/useWindowControls"; +import { startDialogBackdropSync, stopDialogBackdropSync } from "@/lib/app/dialogBackdrop"; import { useSaveSqlFolderSelection } from "@/composables/useSaveSqlFolderSelection"; import "@/i18n"; import { translateBackendError } from "@/i18n/backend-errors"; @@ -2087,6 +2088,7 @@ onMounted(async () => { }); applyTheme(); void applyUiScale(settingsStore.editorSettings.uiScale); + startDialogBackdropSync(); window.addEventListener("keydown", handleKeydown); window.addEventListener("dbx-open-driver-store", openDriverStoreFromEvent); if (isDesktop) { @@ -2147,6 +2149,7 @@ onMounted(async () => { }); onUnmounted(() => { + stopDialogBackdropSync(); cleanupTauriListeners(); cleanupCloseActionPromptListener(); if (updateCheckTimer) { diff --git a/apps/desktop/src/components/grid/DataGridConditionEditor.vue b/apps/desktop/src/components/grid/DataGridConditionEditor.vue index 0bb3137a7..dd7a2255d 100644 --- a/apps/desktop/src/components/grid/DataGridConditionEditor.vue +++ b/apps/desktop/src/components/grid/DataGridConditionEditor.vue @@ -570,15 +570,15 @@ defineExpose({ focus, dismiss: editor.dismiss, rememberHistory: editor.rememberH 0 1px color-mix(in oklab, var(--background) 96%, var(--muted) 4%); } -:global(.dark) .data-grid-topbar-condition-label--where { +:global(.dark .data-grid-topbar-condition-label--where) { color: rgb(96 165 250); } -:global(.dark) .data-grid-topbar-condition-label--order { +:global(.dark .data-grid-topbar-condition-label--order) { color: rgb(251 146 60); } -:global(.dark) .data-grid-topbar-condition-label--floating { +:global(.dark .data-grid-topbar-condition-label--floating) { text-shadow: -1px 0 rgb(24, 24, 27), 1px 0 rgb(24, 24, 27), @@ -617,7 +617,7 @@ defineExpose({ focus, dismiss: editor.dismiss, rememberHistory: editor.rememberH line-height: 1.5rem; } -:global(.dark) .data-grid-topbar-condition-input { +:global(.dark .data-grid-topbar-condition-input) { color: rgb(244, 244, 245); background-color: transparent !important; } @@ -657,7 +657,7 @@ defineExpose({ focus, dismiss: editor.dismiss, rememberHistory: editor.rememberH --data-grid-condition-suffix-width: 0px; } -:global(.dark) .data-grid-topbar-condition-pane--expanded { +:global(.dark .data-grid-topbar-condition-pane--expanded) { background: rgb(24, 24, 27) !important; color: rgb(244, 244, 245); box-shadow: @@ -706,11 +706,11 @@ defineExpose({ focus, dismiss: editor.dismiss, rememberHistory: editor.rememberH color: rgb(249 115 22 / 70%); } -:global(.dark) .data-grid-topbar-condition-input--where.data-grid-topbar-condition-input--compact::placeholder { +:global(.dark .data-grid-topbar-condition-input--where.data-grid-topbar-condition-input--compact::placeholder) { color: rgb(147 197 253 / 70%); } -:global(.dark) .data-grid-topbar-condition-input--order.data-grid-topbar-condition-input--compact::placeholder { +:global(.dark .data-grid-topbar-condition-input--order.data-grid-topbar-condition-input--compact::placeholder) { color: rgb(253 186 116 / 70%); } diff --git a/apps/desktop/src/components/grid/__tests__/DataGridConditionEditor.spec.ts b/apps/desktop/src/components/grid/__tests__/DataGridConditionEditor.spec.ts index 1d11fe095..dca6b1cc3 100644 --- a/apps/desktop/src/components/grid/__tests__/DataGridConditionEditor.spec.ts +++ b/apps/desktop/src/components/grid/__tests__/DataGridConditionEditor.spec.ts @@ -124,6 +124,14 @@ describe("DataGridConditionEditor quote completion", () => { expect(floatingLabelCss).not.toContain("box-shadow:"); }); + it("keeps dark condition styles scoped to their target elements", () => { + const source = readFileSync(resolve(process.cwd(), "apps/desktop/src/components/grid/DataGridConditionEditor.vue"), "utf8"); + + expect(source).not.toContain(":global(.dark) .data-grid"); + expect(source).toContain(":global(.dark .data-grid-topbar-condition-label--floating)"); + expect(source).toContain(":global(.dark .data-grid-topbar-condition-pane--expanded)"); + }); + it("scrolls the caret into view after accepting a long completion", () => { const source = readFileSync(resolve(process.cwd(), "apps/desktop/src/components/grid/DataGridConditionEditor.vue"), "utf8"); diff --git a/apps/desktop/src/lib/app/dialogBackdrop.ts b/apps/desktop/src/lib/app/dialogBackdrop.ts new file mode 100644 index 000000000..2b83a01ab --- /dev/null +++ b/apps/desktop/src/lib/app/dialogBackdrop.ts @@ -0,0 +1,24 @@ +const DIALOG_BACKDROP_CLASS = "dbx-dialog-backdrop-active"; + +let dialogBackdropObserver: MutationObserver | undefined; + +function hasDialogOverlay(): boolean { + return Array.from(document.body.children).some((child) => child.matches('[data-slot="dialog-overlay"]') || child.querySelector('[data-slot="dialog-overlay"]')); +} + +function syncDialogBackdrop() { + document.documentElement.classList.toggle(DIALOG_BACKDROP_CLASS, hasDialogOverlay()); +} + +export function startDialogBackdropSync() { + if (typeof document === "undefined" || dialogBackdropObserver) return; + dialogBackdropObserver = new MutationObserver(syncDialogBackdrop); + dialogBackdropObserver.observe(document.body, { childList: true }); + syncDialogBackdrop(); +} + +export function stopDialogBackdropSync() { + dialogBackdropObserver?.disconnect(); + dialogBackdropObserver = undefined; + document.documentElement.classList.remove(DIALOG_BACKDROP_CLASS); +} diff --git a/apps/desktop/src/styles/__tests__/legacyWebviewFallback.spec.ts b/apps/desktop/src/styles/__tests__/legacyWebviewFallback.spec.ts index 07b44ee23..0f7ec63c5 100644 --- a/apps/desktop/src/styles/__tests__/legacyWebviewFallback.spec.ts +++ b/apps/desktop/src/styles/__tests__/legacyWebviewFallback.spec.ts @@ -6,6 +6,8 @@ const dialogContentSource = readFileSync(new URL("../../components/ui/dialog/Dia const dialogScrollContentSource = readFileSync(new URL("../../components/ui/dialog/DialogScrollContent.vue", import.meta.url), "utf8"); const dialogOverlaySource = readFileSync(new URL("../../components/ui/dialog/DialogOverlay.vue", import.meta.url), "utf8"); const connectionDialogSource = readFileSync(new URL("../../components/connection/ConnectionDialog.vue", import.meta.url), "utf8"); +const dialogBackdropSource = readFileSync(new URL("../../lib/app/dialogBackdrop.ts", import.meta.url), "utf8"); +const appSource = readFileSync(new URL("../../App.vue", import.meta.url), "utf8"); const desktopIndexSource = readFileSync(new URL("../../../index.html", import.meta.url), "utf8"); const connectionDialogLegacyCss = readFileSync(new URL("../../../public/connection-dialog-legacy.css", import.meta.url), "utf8"); @@ -44,9 +46,15 @@ describe("legacy WebView CSS fallbacks", () => { expect(connectionDialogSource).toContain("max-height: calc(var(--dbx-viewport-height) - 2rem);"); }); - it("avoids full-window backdrop filters that can blank WebKit after a dialog closes", () => { + it("blurs the stable app root instead of the transient dialog overlay", () => { expect(dialogOverlaySource).not.toContain("backdrop-filter"); expect(dialogOverlaySource).toContain("bg-black/10"); + expect(globalsCss).toContain("html.dbx-dialog-backdrop-active #root"); + expect(globalsCss).toContain("filter: blur(4px);"); + expect(dialogBackdropSource).toContain("dialogBackdropObserver.observe(document.body, { childList: true })"); + expect(dialogBackdropSource).not.toContain("subtree: true"); + expect(appSource).toContain("startDialogBackdropSync();"); + expect(appSource).toContain("stopDialogBackdropSync();"); }); it("keeps legacy tab triggers connected to the configured corner style", () => { diff --git a/apps/desktop/src/styles/globals.css b/apps/desktop/src/styles/globals.css index c4bb950c5..d2dcb458f 100644 --- a/apps/desktop/src/styles/globals.css +++ b/apps/desktop/src/styles/globals.css @@ -270,6 +270,10 @@ } } +html.dbx-dialog-backdrop-active #root { + filter: blur(4px); +} + .dark { --background: rgb(19 20 22); --foreground: rgb(215 215 219);