diff --git a/plugins/README.md b/plugins/README.md index c9d97c5f..4765e421 100644 --- a/plugins/README.md +++ b/plugins/README.md @@ -384,6 +384,23 @@ inline scripts and styles and `data:` images, no network, no IDE API — and talks to the plugin only through `openidea.postMessage(text)` / `openidea.onMessage(fn)` (`ViewEvent::Message`, `host::post_view_message`). +The frame follows the IDE theme without reloading: its root carries +`data-theme="light|dark"` and these CSS variables, with concrete colour +values (the body already uses the text colour, UI font and size): + +| Variable | Meaning | +| ----------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | +| `--openidea-bg`, `--openidea-bg-elevated`, `--openidea-bg-hover`, `--openidea-bg-selected`, `--openidea-bg-input` | Surfaces (the frame itself is transparent) | +| `--openidea-fg`, `--openidea-fg-strong`, `--openidea-fg-muted` | Text | +| `--openidea-border`, `--openidea-focus-ring`, `--openidea-link` | Lines, focus, links | +| `--openidea-accent`, `--openidea-fg-on-accent` | Accent and text on it | +| `--openidea-danger`, `--openidea-warning`, `--openidea-success`, `--openidea-info` | Status colours | +| `--openidea-font-ui`, `--openidea-font-mono`, `--openidea-font-size` | Fonts (web fonts the IDE bundles are not available in the frame) | + +Scripts get the same as `openidea.theme` (`{ dark, vars }`) and +`openidea.onThemeChange(fn)`, for drawing on a canvas, say. Variables are only +ever added. + ### Settings ```toml diff --git a/src/features/plugins/__tests__/views.test.tsx b/src/features/plugins/__tests__/views.test.tsx index a78fce47..8d197d15 100644 --- a/src/features/plugins/__tests__/views.test.tsx +++ b/src/features/plugins/__tests__/views.test.tsx @@ -9,7 +9,7 @@ import { defineBuiltinUiPoints } from "../points"; import { clearUiContributions, syncUiContributions } from "../projection"; import { pluginMenuItems, withPluginSetting } from "../uiPoints"; import { matchesPattern } from "../views/customEditor"; -import { FRAME_CSP, frameDocument } from "../views/frame"; +import { FRAME_CSP, FRAME_TAG, frameDocument, frameTheme, type FrameTheme } from "../views/frame"; import { PluginView, treeNodes, wantedViewport } from "../views/PluginView"; import { bumpView } from "../views/viewStore"; @@ -56,7 +56,7 @@ describe("view helpers", () => { }); it("confines HTML bodies: CSP without network, a message bridge", () => { - const doc = frameDocument("
hi
", true); + const doc = frameDocument("hi
", { dark: true, vars: {} }); expect(doc).toContain(`content="${FRAME_CSP}"`); expect(FRAME_CSP).toContain("default-src 'none'"); expect(FRAME_CSP).not.toMatch(/https?:|connect-src/); @@ -64,6 +64,71 @@ describe("view helpers", () => { expect(doc.indexOf("Content-Security-Policy")).toBeLessThan(doc.indexOf("hi
")); }); + it("reads the frame theme from the IDE tokens, resolved and sanitised", () => { + const root = document.documentElement; + const before = root.dataset.theme; + root.style.setProperty("--fg", "#112233"); + root.style.setProperty("--font-ui", "Inter, sans-serif"); + root.style.setProperty("--font-mono", "x}"); + try { + root.dataset.theme = "light"; + const light = frameTheme(); + expect(light.dark).toBe(false); + expect(light.vars["--openidea-fg"]).toBe("rgb(17, 34, 51)"); + expect(light.vars["--openidea-font-ui"]).toBe("Inter, sans-serif"); + expect(light.vars).not.toHaveProperty("--openidea-font-mono"); + root.dataset.theme = "dark"; + root.dataset.contrast = "high"; + expect(frameTheme().dark).toBe(true); + } finally { + for (const p of ["--fg", "--font-ui", "--font-mono"]) root.style.removeProperty(p); + delete root.dataset.contrast; + if (before === undefined) delete root.dataset.theme; + else root.dataset.theme = before; + } + }); + + it("puts the theme into the frame document without letting a value end the script", () => { + const doc = frameDocument("hi
", { dark: true, vars: { "--openidea-fg": "" } }); + expect(doc).toContain(''); + expect(doc).not.toContain(""); + expect(doc).toContain("\\u003c/script>"); + }); + + it("applies posted themes inside the frame, replacing the previous variables", () => { + const doc = frameDocument("", { + dark: false, + vars: { "--openidea-fg": "rgb(1, 2, 3)", "--openidea-accent": "red" }, + }); + const script = /`, + ``, + ``, "", html, "