import type { TerminalTheme } from '../../domain/models'; import { buildTerminalAppearanceCssVars, TERMINAL_APPEARANCE_VAR_KEYS, } from './terminalAppearanceTokens'; export const TERMINAL_APPEARANCE_ROOT_ATTR = 'data-terminal-appearance-root'; export const TERMINAL_APPEARANCE_ROOT_SELECTOR = `[${TERMINAL_APPEARANCE_ROOT_ATTR}]`; const setStylePropertyIfChanged = (element: HTMLElement, property: string, value: string) => { if (element.style.getPropertyValue(property) === value) return; element.style.setProperty(property, value); }; const removeStylePropertyIfSet = (element: HTMLElement, property: string) => { if (!element.style.getPropertyValue(property)) return; element.style.removeProperty(property); }; export function findTerminalAppearanceRoot(root?: ParentNode): HTMLElement | null { if (typeof document === 'undefined') return null; const scope = root ?? document; return scope.querySelector(TERMINAL_APPEARANCE_ROOT_SELECTOR); } const TERMINAL_CHROME_SURFACE_SELECTOR = [ '[data-section="app-host-tree-layer"]', '[data-section="terminal-host-tree-sidebar"]', '[data-section="terminal-side-panel"]', ].join(', '); const TERMINAL_LAYER_CHROME_SURFACE_SELECTOR = [ '[data-section="terminal-host-tree-sidebar"]', '[data-section="terminal-side-panel"]', ].join(', '); function collectTerminalChromeSurfaceElements(): HTMLElement[] { if (typeof document === 'undefined') return []; return Array.from(document.querySelectorAll(TERMINAL_CHROME_SURFACE_SELECTOR)); } function collectTerminalLayerChromeSurfaceElements(): HTMLElement[] { if (typeof document === 'undefined') return []; return Array.from(document.querySelectorAll(TERMINAL_LAYER_CHROME_SURFACE_SELECTOR)); } function applyTerminalAppearanceVarsToTargets(targets: HTMLElement[], theme: TerminalTheme): void { if (targets.length === 0) return; const vars = buildTerminalAppearanceCssVars(theme); for (const target of targets) { for (const key of TERMINAL_APPEARANCE_VAR_KEYS) { setStylePropertyIfChanged(target, key, vars[key]); } target.dataset.terminalAppearanceId = theme.id; } } export function injectTerminalChromeSurfaceVars(theme: TerminalTheme): void { applyTerminalAppearanceVarsToTargets(collectTerminalChromeSurfaceElements(), theme); } export function injectTerminalLayerChromeSurfaceVars(theme: TerminalTheme): void { applyTerminalAppearanceVarsToTargets(collectTerminalLayerChromeSurfaceElements(), theme); } function clearTerminalAppearanceVarsOnTargets(targets: HTMLElement[]): void { for (const target of targets) { for (const key of TERMINAL_APPEARANCE_VAR_KEYS) { removeStylePropertyIfSet(target, key); } delete target.dataset.terminalAppearanceId; } } export function clearTerminalChromeSurfaceVars(): void { if (typeof document === 'undefined') return; clearTerminalAppearanceVarsOnTargets(collectTerminalChromeSurfaceElements()); } export function clearTerminalLayerChromeSurfaceVars(): void { if (typeof document === 'undefined') return; clearTerminalAppearanceVarsOnTargets(collectTerminalLayerChromeSurfaceElements()); } export type InjectTerminalAppearanceVarsOptions = { root?: HTMLElement | null; includeChromeSurfaces?: boolean; }; export function injectTerminalAppearanceVars( theme: TerminalTheme, options?: InjectTerminalAppearanceVarsOptions | HTMLElement | null, ): void { if (typeof document === 'undefined') return; const normalizedOptions: InjectTerminalAppearanceVarsOptions = options && typeof options === 'object' && 'includeChromeSurfaces' in options ? options : { root: options instanceof HTMLElement || options === null ? options : undefined }; const includeChromeSurfaces = normalizedOptions.includeChromeSurfaces ?? true; const targets: HTMLElement[] = []; const appearanceRoot = normalizedOptions.root ?? findTerminalAppearanceRoot(); if (appearanceRoot) targets.push(appearanceRoot); const tabsRoot = document.querySelector('[data-top-tabs-root]'); if (tabsRoot && tabsRoot !== appearanceRoot) targets.push(tabsRoot); if (includeChromeSurfaces) { for (const chromeSurface of collectTerminalChromeSurfaceElements()) { if (!targets.includes(chromeSurface)) targets.push(chromeSurface); } } applyTerminalAppearanceVarsToTargets(targets, theme); } export function clearTerminalAppearanceVars(root?: HTMLElement | null): void { if (typeof document === 'undefined') return; const targets: HTMLElement[] = []; const appearanceRoot = root ?? findTerminalAppearanceRoot(); if (appearanceRoot) targets.push(appearanceRoot); const tabsRoot = document.querySelector('[data-top-tabs-root]'); if (tabsRoot && tabsRoot !== appearanceRoot) targets.push(tabsRoot); for (const hostTreeRoot of document.querySelectorAll( '[data-section="app-host-tree-layer"], [data-section="terminal-host-tree-sidebar"], [data-section="terminal-side-panel"]', )) { if (!targets.includes(hostTreeRoot)) targets.push(hostTreeRoot); } clearTerminalAppearanceVarsOnTargets(targets); } export function injectTerminalPaneAppearanceVars(sessionId: string, theme: TerminalTheme): void { if (typeof document === 'undefined') return; const pane = document.querySelector(`[data-session-id="${sessionId}"]`); if (!pane) return; const vars = buildTerminalAppearanceCssVars(theme); setStylePropertyIfChanged(pane, '--nc-term-bg', vars['--nc-term-bg']); setStylePropertyIfChanged(pane, '--nc-term-fg', vars['--nc-term-fg']); setStylePropertyIfChanged(pane, '--nc-term-border', vars['--nc-term-border']); setStylePropertyIfChanged(pane, '--nc-term-toolbar-btn', vars['--nc-term-toolbar-btn']); setStylePropertyIfChanged(pane, '--nc-term-toolbar-btn-hover', vars['--nc-term-toolbar-btn-hover']); setStylePropertyIfChanged(pane, '--nc-term-toolbar-btn-active', vars['--nc-term-toolbar-btn-active']); // Legacy aliases consumed by Terminal.tsx toolbar styles. setStylePropertyIfChanged(pane, '--terminal-preview-bg', vars['--nc-term-bg']); setStylePropertyIfChanged(pane, '--terminal-preview-fg', vars['--nc-term-fg']); setStylePropertyIfChanged(pane, '--terminal-preview-border', vars['--nc-term-border']); setStylePropertyIfChanged(pane, '--terminal-preview-toolbar-btn', vars['--nc-term-toolbar-btn']); setStylePropertyIfChanged(pane, '--terminal-preview-toolbar-btn-hover', vars['--nc-term-toolbar-btn-hover']); setStylePropertyIfChanged(pane, '--terminal-preview-toolbar-btn-active', vars['--nc-term-toolbar-btn-active']); } export function clearTerminalPaneAppearanceVars(sessionId: string): void { if (typeof document === 'undefined') return; const pane = document.querySelector(`[data-session-id="${sessionId}"]`); if (!pane) return; const keys = [ '--nc-term-bg', '--nc-term-fg', '--nc-term-border', '--nc-term-toolbar-btn', '--nc-term-toolbar-btn-hover', '--nc-term-toolbar-btn-active', '--terminal-preview-bg', '--terminal-preview-fg', '--terminal-preview-border', '--terminal-preview-toolbar-btn', '--terminal-preview-toolbar-btn-hover', '--terminal-preview-toolbar-btn-active', ]; for (const key of keys) { removeStylePropertyIfSet(pane, key); } }