fix: keep plugin window visible after file dialogs#518
Conversation
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
This PR prevents the main window from auto-hiding due to queued blur/mouseup events emitted around native modal dialog close, by introducing a dedicated “blur-hide suppression” guard and using it when showing plugin dialogs.
Changes:
- Added modal-dialog-specific blur-hide suppression state and centralized
isBlurHideSuppressed()checks across blur/mouseup hide paths. - Introduced
withBlurHideSuppressed()helper to scope suppression around modal operations with a short delayed release. - Wrapped plugin file open/save dialogs with
withBlurHideSuppressed()to avoid unintended window hiding.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| src/main/managers/windowManager.ts | Adds modal-dialog blur-hide suppression state, helper methods, and integrates suppression checks into blur/mouseup handling. |
| src/main/api/plugin/dialog.ts | Wraps synchronous open/save dialogs with the new suppression helper to prevent unwanted auto-hide. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Code Review
This pull request introduces a mechanism to temporarily suppress window hiding on blur when native modal dialogs (such as file open/save dialogs) are displayed. This is achieved by wrapping synchronous dialog calls in a new withBlurHideSuppressed helper within WindowManager. The review feedback points out that withBlurHideSuppressed currently only supports synchronous callbacks, which would cause the suppression to release prematurely if an asynchronous callback (returning a Promise) is used. A robust implementation handling both synchronous and asynchronous callbacks was suggested.
| // Native modal dialogs can emit queued blur/mouseup events around close. | ||
| private modalDialogBlurHideSuppressed: boolean = false | ||
| private modalDialogBlurHideReleaseTimer: ReturnType<typeof setTimeout> | null = null | ||
| private modalDialogBlurHideSuppressionDepth: number = 0 |
| public withBlurHideSuppressed<T>( | ||
| callback: () => T | PromiseLike<T>, | ||
| releaseDelayMs: number = 500 | ||
| ): T | Promise<T> { |
| const result = windowManager.withBlurHideSuppressed(() => | ||
| dialog.showSaveDialogSync(targetWindow, options) | ||
| ) |
| public withBlurHideSuppressed<T>( | ||
| callback: () => PromiseLike<T>, | ||
| releaseDelayMs?: number | ||
| ): Promise<T> | ||
| public withBlurHideSuppressed<T>(callback: () => T, releaseDelayMs?: number): T | ||
| public withBlurHideSuppressed<T>( | ||
| callback: () => T | PromiseLike<T>, | ||
| releaseDelayMs: number = DEFAULT_MODAL_DIALOG_BLUR_HIDE_RELEASE_DELAY_MS | ||
| ): T | Promise<T> { | ||
| this.beginModalDialogBlurHideSuppression() | ||
| try { | ||
| const result = callback() | ||
| if (this.isPromiseLike(result)) { | ||
| return Promise.resolve(result).finally(() => { | ||
| this.endModalDialogBlurHideSuppression(releaseDelayMs) | ||
| }) | ||
| } | ||
|
|
||
| this.endModalDialogBlurHideSuppression(releaseDelayMs) | ||
| return result | ||
| } catch (error) { | ||
| this.endModalDialogBlurHideSuppression(releaseDelayMs) | ||
| throw error | ||
| } | ||
| } |
| // 原生模态对话框关闭前后可能发出排队的 blur/mouseup 事件。 | ||
| private modalDialogBlurHideSuppressed: boolean = false | ||
| private modalDialogBlurHideReleaseTimer: ReturnType<typeof setTimeout> | null = null | ||
| private modalDialogBlurHideSuppressionDepth: number = 0 |
| private endModalDialogBlurHideSuppression(releaseDelayMs: number): void { | ||
| this.modalDialogBlurHideSuppressionDepth = Math.max( | ||
| 0, | ||
| this.modalDialogBlurHideSuppressionDepth - 1 | ||
| ) | ||
| if (this.modalDialogBlurHideSuppressionDepth > 0) return | ||
|
|
||
| if (this.modalDialogBlurHideReleaseTimer) { | ||
| clearTimeout(this.modalDialogBlurHideReleaseTimer) | ||
| } | ||
|
|
||
| this.modalDialogBlurHideReleaseTimer = setTimeout(() => { | ||
| this.modalDialogBlurHideSuppressed = false | ||
| this.modalDialogBlurHideReleaseTimer = null | ||
| }, releaseDelayMs) | ||
| } |
Summary
Why
Plugin
showOpenDialog/showSaveDialogcalls can blur the main window and emit a trailing mouseup when the native dialog closes. ZTools treated those events like an outside click and hid the plugin window after selecting a file.Verification
node_modules/.bin/tsc.cmd --noEmit -p tsconfig.node.json --composite falsenode_modules/.bin/vue-tsc.cmd --noEmit -p tsconfig.web.json --composite falseinternal-plugins/setting/node_modules/.bin/vue-tsc.cmd --noEmitnode_modules/.bin/eslint.cmd --quiet src/main/api/plugin/dialog.ts src/main/managers/windowManager.tsnode_modules/.bin/electron-vite.cmd buildinternal-plugins/setting/node_modules/.bin/vite.cmd buildnode_modules/.bin/electron-builder.cmd --dir