fix: sync native titlebar window state

This commit is contained in:
purocean
2026-09-19 12:46:48 +08:00
parent 1515131253
commit b761fa7964
2 changed files with 62 additions and 4 deletions
@@ -6,8 +6,15 @@ const mocks = vi.hoisted(() => ({
isSaved: undefined as any,
win: undefined as any,
listeners: new Map<string, Function>(),
hooks: new Map<string, Function>(),
isElectron: true,
isMacOS: true,
colorScheme: 'light',
}))
vi.mock('@fe/core/hook', () => ({
registerHook: (name: string, handler: Function) => mocks.hooks.set(name, handler),
removeHook: (name: string) => mocks.hooks.delete(name),
}))
vi.mock('@fe/support/store', () => ({
@@ -32,6 +39,10 @@ vi.mock('@fe/services/i18n', () => ({
useI18n: () => ({ t: (key: string) => key }),
}))
vi.mock('@fe/services/theme', () => ({
getColorScheme: () => mocks.colorScheme,
}))
import { isWindowAlwaysOnTop, toggleWindowAlwaysOnTop, useWindowState } from '../window-state'
const TestComponent = defineComponent({
@@ -48,11 +59,14 @@ beforeEach(() => {
})
mocks.isSaved = ref(false)
mocks.listeners.clear()
mocks.hooks.clear()
mocks.isElectron = true
mocks.isMacOS = true
mocks.colorScheme = 'light'
mocks.win = {
isAlwaysOnTop: vi.fn(() => false),
setAlwaysOnTop: vi.fn(),
setTitleBarOverlay: vi.fn(),
setDocumentEdited: vi.fn(),
on: vi.fn((name: string, handler: Function) => mocks.listeners.set(name, handler)),
removeListener: vi.fn((name: string) => mocks.listeners.delete(name)),
@@ -81,6 +95,7 @@ describe('window state', () => {
mocks.listeners.get('enter-full-screen')?.()
expect(mocks.storeState.isFullscreen).toBe(true)
expect(mocks.win.setAlwaysOnTop).toHaveBeenLastCalledWith(false)
mocks.listeners.get('leave-full-screen')?.()
expect(mocks.storeState.isFullscreen).toBe(false)
@@ -94,6 +109,28 @@ describe('window state', () => {
expect(mocks.win.removeListener).toHaveBeenCalledWith('leave-full-screen', expect.any(Function))
})
test('keeps native window controls legible when the app theme changes', () => {
mocks.isMacOS = false
const wrapper = mount(TestComponent)
expect(mocks.win.setTitleBarOverlay).toHaveBeenLastCalledWith({
color: '#00000000',
symbolColor: '#151518',
height: 30,
})
mocks.colorScheme = 'dark'
mocks.hooks.get('THEME_CHANGE')?.()
expect(mocks.win.setTitleBarOverlay).toHaveBeenLastCalledWith({
color: '#00000000',
symbolColor: '#e6e6e6',
height: 30,
})
wrapper.unmount()
expect(mocks.hooks.has('THEME_CHANGE')).toBe(false)
})
test('keeps the browser unsaved-document prompt', () => {
mocks.isElectron = false
const wrapper = mount(TestComponent)
+25 -4
View File
@@ -1,7 +1,9 @@
import { computed, onBeforeUnmount, onMounted, ref, toRefs, watch } from 'vue'
import { registerHook, removeHook } from '@fe/core/hook'
import { getElectronRemote, isElectron, isMacOS, nodeRequire } from '@fe/support/env'
import store from '@fe/support/store'
import { useI18n } from '@fe/services/i18n'
import { getColorScheme } from '@fe/services/theme'
let win: ReturnType<ReturnType<typeof getElectronRemote>['getCurrentWindow']> | null = null
@@ -11,6 +13,23 @@ function updateAlwaysOnTop () {
isWindowAlwaysOnTop.value = !!win?.isAlwaysOnTop()
}
function clearAlwaysOnTop () {
if (win?.isAlwaysOnTop()) {
win.setAlwaysOnTop(false)
}
updateAlwaysOnTop()
}
function updateTitleBarOverlay () {
if (win && !isMacOS) {
win.setTitleBarOverlay({
color: '#00000000',
symbolColor: getColorScheme() === 'dark' ? '#e6e6e6' : '#151518',
height: 30,
})
}
}
export function toggleWindowAlwaysOnTop () {
if (!win) {
return
@@ -27,6 +46,7 @@ export function useWindowState () {
function handleFullscreenEnter () {
store.state.isFullscreen = true
clearAlwaysOnTop()
}
function handleFullscreenLeave () {
@@ -34,10 +54,7 @@ export function useWindowState () {
}
function handleMaximize () {
if (win?.isAlwaysOnTop()) {
win.setAlwaysOnTop(false)
}
updateAlwaysOnTop()
clearAlwaysOnTop()
}
function clean () {
@@ -54,6 +71,8 @@ export function useWindowState () {
win.removeListener('leave-full-screen', handleFullscreenLeave)
}
removeHook('THEME_CHANGE', updateTitleBarOverlay)
win = null
isWindowAlwaysOnTop.value = false
}
@@ -67,6 +86,8 @@ export function useWindowState () {
if (nodeRequire) {
win = getElectronRemote().getCurrentWindow()
updateAlwaysOnTop()
updateTitleBarOverlay()
registerHook('THEME_CHANGE', updateTitleBarOverlay)
win.on('maximize', handleMaximize)
win.on('always-on-top-changed', updateAlwaysOnTop)
win.on('enter-full-screen', handleFullscreenEnter)