diff --git a/ui-html/webview2/settings/ime-settings/src/modules/sidebar.ts b/ui-html/webview2/settings/ime-settings/src/modules/sidebar.ts index 616c9e28..5183eeb1 100644 --- a/ui-html/webview2/settings/ime-settings/src/modules/sidebar.ts +++ b/ui-html/webview2/settings/ime-settings/src/modules/sidebar.ts @@ -33,15 +33,18 @@ const setupLoaders: Record Promise> = { }, skin: async () => { await (await import('./skin')).setupSkin(); + (await import('./theme')).reapplyThemeConfig(); }, voice: async () => { (await import('./voice')).setupVoiceInput(); }, 'screenkb-settings': async () => { (await import('./screenkb-settings')).setupScreenKeyboardSettings(); + (await import('./theme')).reapplyThemeConfig(); }, 'handwriting-settings': async () => { (await import('./handwriting-settings')).setupHandwritingSettings(); + (await import('./theme')).reapplyThemeConfig(); }, 'tools-settings': async () => { (await import('./tools-settings')).setupToolsSettings(); diff --git a/ui-html/webview2/settings/ime-settings/src/modules/theme.test.ts b/ui-html/webview2/settings/ime-settings/src/modules/theme.test.ts new file mode 100644 index 00000000..daad82a2 --- /dev/null +++ b/ui-html/webview2/settings/ime-settings/src/modules/theme.test.ts @@ -0,0 +1,47 @@ +import { afterEach, beforeEach, expect, it, vi } from 'vitest'; +import { applyThemeConfig, reapplyThemeConfig } from './theme'; + +class FakeImage { + src = '/assets/softkbd.png'; +} + +function fakeElement() { + return { + style: {} as Record, + setAttribute: vi.fn(), + classList: { toggle: vi.fn() } + }; +} + +let previewLoaded = false; +let image: FakeImage; + +beforeEach(() => { + previewLoaded = false; + image = new FakeImage(); + vi.stubGlobal('HTMLImageElement', FakeImage); + vi.stubGlobal('window', { matchMedia: () => ({ matches: true, onchange: null }) }); + vi.stubGlobal('document', { + documentElement: fakeElement(), + body: fakeElement(), + querySelector: (selector: string) => + selector === '.screenkb-preview' && previewLoaded ? fakeElement() : null, + querySelectorAll: () => [], + getElementById: (id: string) => (id === 'screenKeyboardPreviewImage' && previewLoaded ? image : null) + }); +}); + +afterEach(() => { + vi.unstubAllGlobals(); +}); + +it('themes a preview page that loads after the snapshot was applied', async () => { + applyThemeConfig({ theme_mode: 'system', theme_screen_keyboard: 'follow' }); + await vi.dynamicImportSettled(); + expect(image.src).toBe('/assets/softkbd.png'); + + previewLoaded = true; + reapplyThemeConfig(); + await vi.dynamicImportSettled(); + expect(image.src).toBe('/assets/softkbd_light.png'); +}); diff --git a/ui-html/webview2/settings/ime-settings/src/modules/theme.ts b/ui-html/webview2/settings/ime-settings/src/modules/theme.ts index 782eae8d..79bbb9e9 100644 --- a/ui-html/webview2/settings/ime-settings/src/modules/theme.ts +++ b/ui-html/webview2/settings/ime-settings/src/modules/theme.ts @@ -33,6 +33,7 @@ const SURFACE_THEME_LABELS: Record = { }; let mediaQuery: MediaQueryList | null = null; +let lastThemeConfig: ThemeConfig | null = null; function normalizeThemeMode(value: string | undefined): ThemeMode { if (value === 'light' || value === 'dark' || value === 'system') { @@ -99,6 +100,7 @@ function ensureSystemThemeListener(onChange: () => void): void { } export function applyThemeConfig(config: ThemeConfig | undefined): void { + lastThemeConfig = config ?? null; const mode = normalizeThemeMode(config?.theme_mode); const settingsTheme = normalizeSurfaceTheme(config?.theme_settings); const candTheme = normalizeSurfaceTheme(config?.theme_cand); @@ -159,6 +161,13 @@ export function applyThemeConfig(config: ThemeConfig | undefined): void { updateSkinThemeCard(mode, settingsResolved, candResolved); } +// Skin, screen keyboard and handwriting pages load after the first snapshot has been applied, so their previews only exist once the theme has already been resolved; they call this when their DOM is ready. +export function reapplyThemeConfig(): void { + if (lastThemeConfig) { + applyThemeConfig(lastThemeConfig); + } +} + export function applyFtbPreviewTheme(theme: ResolvedTheme): void { document.querySelectorAll('.ftb-preview-host:not(#skinToolbarPreview):not([data-skin-toolbar])').forEach((element) => { element.classList.toggle('theme-light', theme === 'light');