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');