fix: theme plugin slot iframes with host font + color tokens

This commit is contained in:
Linus Rath
2026-05-31 16:28:22 +02:00
parent abb249e5df
commit 6ee3849463
6 changed files with 236 additions and 0 deletions
+31
View File
@@ -27,6 +27,7 @@ import type {
BackgroundInit,
SlotInit,
} from './protocol';
import { themeSnapshotToCSS, type ThemeSnapshot } from './host-theme';
import type { SlotName } from '../plugin-types';
// ─── Module-scope state ──────────────────────────────────────
@@ -67,6 +68,28 @@ function sendToHost(msg: SandboxToHost): void {
parentWindow.postMessage(msg, parentOrigin);
}
// ─── Theme replay ────────────────────────────────────────────
const THEME_STYLE_ID = '__plugin_host_theme';
/**
* Replay a host theme snapshot inside the iframe: inject the token + font CSS
* and mirror the `.dark` class onto <html> so plugin styles that key off
* `.dark` (or read `var(--color-*)`) behave like the host. Idempotent — safe
* to call again on every 'theme-change'.
*/
function applyHostTheme(theme: ThemeSnapshot): void {
if (typeof document === 'undefined') return;
let styleEl = document.getElementById(THEME_STYLE_ID) as HTMLStyleElement | null;
if (!styleEl) {
styleEl = document.createElement('style');
styleEl.id = THEME_STYLE_ID;
document.head.appendChild(styleEl);
}
styleEl.textContent = themeSnapshotToCSS(theme);
document.documentElement.classList.toggle('dark', theme.dark);
}
function uid(): string {
return Math.random().toString(36).slice(2) + Date.now().toString(36);
}
@@ -310,6 +333,10 @@ async function bootBackground(payload: BackgroundInit): Promise<void> {
}
function bootSlot(payload: SlotInit): void {
// Replay the host theme before first paint so the slot never flashes the UA
// default serif font or a light-on-light/dark mismatch.
applyHostTheme(payload.theme);
const api = buildPluginApi(payload.manifest);
const exports = evaluateBundle(payload.code, api);
pluginExports = exports;
@@ -462,6 +489,10 @@ function handleHostMessage(ev: MessageEvent): void {
(globalThis as unknown as { __PLUGIN_LOCALE__?: string }).__PLUGIN_LOCALE__ = msg.locale;
break;
case 'theme-change':
applyHostTheme(msg.theme);
break;
case 'props-update':
slotPropsUpdater?.(msg.props ?? {});
break;