theme: terminal and editor follow the app's color scheme, re-theming in place

The app resolves light/dark from prefers-color-scheme and the user's
choice (body[data-ds-dark-theme]), but xterm and CodeMirror were pinned to
hardcoded dark themes — a light-mode user got a dark card inside a light
panel. New theme.ts reads the resolved scheme + token values and notifies
on flips (the presenter toggles the body attribute); the terminal now takes
its surface colors from the tokens and a curated one-dark/one-light ANSI
palette, and the editor gets a token-driven surface with the same curated
syntax palettes. Both re-theme in place on a scheme flip: the terminal
re-applies its theme, the editor reconfigures a Compartment holding only
the scheme-dependent extensions, so the document, undo history and scroll
survive. one-dark is dropped (superseded by the curated highlight style).
This commit is contained in:
Menghuan1918
2026-08-08 17:29:13 +08:00
parent d1cdca3324
commit 7aa24badf0
6 changed files with 255 additions and 24 deletions
+44
View File
@@ -0,0 +1,44 @@
/**
* Live theme access for surfaces that cannot consume the token colors
* directly — xterm's palette and CodeMirror's theme extensions need concrete
* values, but the app's scheme flips at runtime (ui-layout's ThemePresenter
* projects prefers-color-scheme and the user's choice onto
* body[data-ds-dark-theme] and html { color-scheme }). This module reads the
* resolved scheme and token values, and notifies subscribers on flips, so
* the terminal and the editor re-theme in place instead of freezing in the
* scheme they happened to be created under.
*/
/** Whether the app shell resolved to the dark scheme.
*
* The presenter sets `html { color-scheme }` together with the body palette
* attribute, so a set color-scheme means the decision is authoritative (an
* absent attribute is then LIGHT even when the OS prefers dark — the user
* chose light). Before the presenter has run, fall back to the OS media
* query as the best guess.
*/
export function isDarkScheme(): boolean {
if (typeof document === 'undefined') return true
const decided = document.documentElement.style.colorScheme !== ''
if (decided) return document.body.hasAttribute('data-ds-dark-theme')
return typeof matchMedia !== 'undefined' && matchMedia('(prefers-color-scheme: dark)').matches
}
/** One token's computed value on <body> ('' while the theme has not applied). */
export function tokenValue(name: string): string {
if (typeof document === 'undefined') return ''
return getComputedStyle(document.body).getPropertyValue(name).trim()
}
/**
* Subscribe to color-scheme flips (the presenter toggles the body
* attribute). The callback fires after the attribute changed; re-read the
* scheme inside it.
* @returns the disposer.
*/
export function subscribeColorScheme(callback: () => void): () => void {
if (typeof document === 'undefined') return () => {}
const observer = new MutationObserver(() => { callback() })
observer.observe(document.body, { attributes: true, attributeFilter: ['data-ds-dark-theme'] })
return () => { observer.disconnect() }
}