Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
116 lines
4.3 KiB
TypeScript
116 lines
4.3 KiB
TypeScript
import type { TerminalTheme } from '../../types';
|
|
import { resolveReadableForegroundForHsl } from '../../domain/colorContrast';
|
|
|
|
function hexToHslToken(hex: string): string {
|
|
const normalized = hex.startsWith('#') ? hex : `#${hex}`;
|
|
const r = parseInt(normalized.slice(1, 3), 16) / 255;
|
|
const g = parseInt(normalized.slice(3, 5), 16) / 255;
|
|
const b = parseInt(normalized.slice(5, 7), 16) / 255;
|
|
const max = Math.max(r, g, b);
|
|
const min = Math.min(r, g, b);
|
|
let h = 0;
|
|
let s = 0;
|
|
const l = (max + min) / 2;
|
|
|
|
if (max !== min) {
|
|
const d = max - min;
|
|
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
switch (max) {
|
|
case r:
|
|
h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
|
|
break;
|
|
case g:
|
|
h = ((b - r) / d + 2) / 6;
|
|
break;
|
|
default:
|
|
h = ((r - g) / d + 4) / 6;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return `${Math.round(h * 3600) / 10} ${Math.round(s * 1000) / 10}% ${Math.round(l * 1000) / 10}%`;
|
|
}
|
|
|
|
function adjustLightnessToken(hsl: string, delta: number): string {
|
|
const parts = hsl.split(/\s+/);
|
|
const newL = Math.max(0, Math.min(100, parseFloat(parts[2]) + delta));
|
|
return `${parts[0]} ${parts[1]} ${Math.round(newL * 10) / 10}%`;
|
|
}
|
|
|
|
function adjustSaturationToken(hsl: string, factor: number): string {
|
|
const parts = hsl.split(/\s+/);
|
|
const newS = Math.max(0, Math.min(100, parseFloat(parts[1]) * factor));
|
|
return `${parts[0]} ${Math.round(newS * 10) / 10}% ${parts[2]}`;
|
|
}
|
|
|
|
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);
|
|
};
|
|
|
|
const TOP_TABS_THEME_PROPERTIES = [
|
|
'--top-tabs-bg',
|
|
'--top-tabs-fg',
|
|
'--top-tabs-muted',
|
|
'--top-tabs-active-bg',
|
|
'--top-tabs-accent',
|
|
'--background',
|
|
'--foreground',
|
|
'--accent',
|
|
'--accent-foreground',
|
|
'--primary',
|
|
'--primary-foreground',
|
|
'--secondary',
|
|
'--border',
|
|
'--muted-foreground',
|
|
] as const;
|
|
|
|
let topTabsChromeThemeVarsApplied = false;
|
|
|
|
export function clearTopTabsChromeThemeVars(): void {
|
|
if (typeof document === 'undefined') return;
|
|
if (!topTabsChromeThemeVarsApplied) return;
|
|
const tabsRoot = document.querySelector<HTMLElement>('[data-top-tabs-root]');
|
|
if (!tabsRoot) return;
|
|
for (const property of TOP_TABS_THEME_PROPERTIES) {
|
|
removeStylePropertyIfSet(tabsRoot, property);
|
|
}
|
|
topTabsChromeThemeVarsApplied = false;
|
|
}
|
|
|
|
export function applyTopTabsChromeThemeVars(theme: TerminalTheme): void {
|
|
if (typeof document === 'undefined') return;
|
|
const tabsRoot = document.querySelector<HTMLElement>('[data-top-tabs-root]');
|
|
if (!tabsRoot) return;
|
|
|
|
const bg = hexToHslToken(theme.colors.background);
|
|
const fg = hexToHslToken(theme.colors.foreground);
|
|
const accent = hexToHslToken(theme.colors.cursor);
|
|
const accentForeground = resolveReadableForegroundForHsl(accent);
|
|
const isDark = theme.type === 'dark';
|
|
const secondary = adjustLightnessToken(bg, isDark ? 6 : -5);
|
|
const border = adjustLightnessToken(bg, isDark ? 12 : -10);
|
|
const mutedFg = adjustSaturationToken(adjustLightnessToken(fg, isDark ? -20 : 20), 0.5);
|
|
|
|
setStylePropertyIfChanged(tabsRoot, '--background', bg);
|
|
setStylePropertyIfChanged(tabsRoot, '--foreground', fg);
|
|
setStylePropertyIfChanged(tabsRoot, '--accent', accent);
|
|
setStylePropertyIfChanged(tabsRoot, '--accent-foreground', accentForeground);
|
|
setStylePropertyIfChanged(tabsRoot, '--primary', accent);
|
|
setStylePropertyIfChanged(tabsRoot, '--primary-foreground', accentForeground);
|
|
setStylePropertyIfChanged(tabsRoot, '--secondary', secondary);
|
|
setStylePropertyIfChanged(tabsRoot, '--border', border);
|
|
setStylePropertyIfChanged(tabsRoot, '--muted-foreground', mutedFg);
|
|
setStylePropertyIfChanged(tabsRoot, '--top-tabs-bg', 'hsl(var(--secondary))');
|
|
setStylePropertyIfChanged(tabsRoot, '--top-tabs-fg', 'hsl(var(--foreground))');
|
|
setStylePropertyIfChanged(tabsRoot, '--top-tabs-muted', 'hsl(var(--muted-foreground))');
|
|
setStylePropertyIfChanged(tabsRoot, '--top-tabs-active-bg', 'hsl(var(--background))');
|
|
setStylePropertyIfChanged(tabsRoot, '--top-tabs-accent', 'hsl(var(--accent))');
|
|
topTabsChromeThemeVarsApplied = true;
|
|
}
|