refactor(tui): extract shared frontend helpers (#37868)
This commit is contained in:
parent
deee40c572
commit
0eb71d0fc7
52 changed files with 1655 additions and 1614 deletions
|
|
@ -1,6 +1,5 @@
|
|||
import { RGBA } from "@opentui/core"
|
||||
import { ansiToRgba } from "./color"
|
||||
import { DEFAULT_THEMES, type ColorValue, type Theme, type ThemeColor, type ThemeJson } from "./v1"
|
||||
import { resolveThemeColors } from "./resolve"
|
||||
import { DEFAULT_THEMES, type Theme, type ThemeJson } from "./v1"
|
||||
|
||||
export { DEFAULT_THEMES, generateSyntax, selectedForeground, type Theme, type ThemeJson } from "./v1"
|
||||
|
||||
|
|
@ -79,62 +78,11 @@ export function upsertTheme(name: string, theme: unknown) {
|
|||
return true
|
||||
}
|
||||
|
||||
export function resolveTheme(theme: ThemeJson, mode: "dark" | "light") {
|
||||
const defs = theme.defs ?? {}
|
||||
function resolveColor(c: ColorValue, chain: string[] = []): RGBA {
|
||||
if (c instanceof RGBA) return c
|
||||
if (typeof c === "string") {
|
||||
if (c === "transparent" || c === "none") return RGBA.fromInts(0, 0, 0, 0)
|
||||
|
||||
if (c.startsWith("#")) return RGBA.fromHex(c)
|
||||
|
||||
if (chain.includes(c)) {
|
||||
throw new Error(`Circular color reference: ${[...chain, c].join(" -> ")}`)
|
||||
}
|
||||
|
||||
const next = defs[c] ?? theme.theme[c as ThemeColor]
|
||||
if (next === undefined) {
|
||||
throw new Error(`Color reference "${c}" not found in defs or theme`)
|
||||
}
|
||||
return resolveColor(next, [...chain, c])
|
||||
}
|
||||
if (typeof c === "number") {
|
||||
return ansiToRgba(c)
|
||||
}
|
||||
return resolveColor(c[mode], chain)
|
||||
}
|
||||
|
||||
const resolved = Object.fromEntries(
|
||||
Object.entries(theme.theme)
|
||||
.filter(([key]) => key !== "selectedListItemText" && key !== "backgroundMenu" && key !== "thinkingOpacity")
|
||||
.map(([key, value]) => {
|
||||
return [key, resolveColor(value as ColorValue)]
|
||||
}),
|
||||
) as Partial<Record<ThemeColor, RGBA>>
|
||||
|
||||
// Handle selectedListItemText separately since it's optional
|
||||
const hasSelectedListItemText = theme.theme.selectedListItemText !== undefined
|
||||
if (hasSelectedListItemText) {
|
||||
resolved.selectedListItemText = resolveColor(theme.theme.selectedListItemText!)
|
||||
} else {
|
||||
// Backward compatibility: if selectedListItemText is not defined, use background color
|
||||
// This preserves the current behavior for all existing themes
|
||||
resolved.selectedListItemText = resolved.background
|
||||
}
|
||||
|
||||
// Handle backgroundMenu - optional with fallback to backgroundElement
|
||||
if (theme.theme.backgroundMenu !== undefined) {
|
||||
resolved.backgroundMenu = resolveColor(theme.theme.backgroundMenu)
|
||||
} else {
|
||||
resolved.backgroundMenu = resolved.backgroundElement
|
||||
}
|
||||
|
||||
// Handle thinkingOpacity - optional with default of 0.6
|
||||
const thinkingOpacity = theme.theme.thinkingOpacity ?? 0.6
|
||||
|
||||
export function resolveTheme(theme: ThemeJson, mode: "dark" | "light"): Theme {
|
||||
const resolved = resolveThemeColors(theme, mode)
|
||||
return {
|
||||
...resolved,
|
||||
_hasSelectedListItemText: hasSelectedListItemText,
|
||||
thinkingOpacity,
|
||||
} as Theme
|
||||
...resolved.theme,
|
||||
_hasSelectedListItemText: resolved.hasSelectedListItemText,
|
||||
thinkingOpacity: resolved.thinkingOpacity,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
51
packages/tui/src/theme/resolve.ts
Normal file
51
packages/tui/src/theme/resolve.ts
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import { RGBA } from "@opentui/core"
|
||||
import { ansiToRgba } from "./color"
|
||||
import type { ColorValue, Theme, ThemeColor, ThemeJson } from "./v1"
|
||||
|
||||
export function resolveThemeColors(
|
||||
theme: ThemeJson,
|
||||
mode: "dark" | "light",
|
||||
resolveAnsi: (code: number) => RGBA = ansiToRgba,
|
||||
) {
|
||||
const defs = theme.defs ?? {}
|
||||
function resolveColor(color: ColorValue, chain: string[] = []): RGBA {
|
||||
if (color instanceof RGBA) return color
|
||||
if (typeof color === "string") {
|
||||
if (color === "transparent" || color === "none") return RGBA.fromInts(0, 0, 0, 0)
|
||||
|
||||
if (color.startsWith("#")) return RGBA.fromHex(color)
|
||||
|
||||
if (chain.includes(color)) {
|
||||
throw new Error(`Circular color reference: ${[...chain, color].join(" -> ")}`)
|
||||
}
|
||||
|
||||
const next = defs[color] ?? theme.theme[color as ThemeColor]
|
||||
if (next === undefined) {
|
||||
throw new Error(`Color reference "${color}" not found in defs or theme`)
|
||||
}
|
||||
return resolveColor(next, [...chain, color])
|
||||
}
|
||||
if (typeof color === "number") return resolveAnsi(color)
|
||||
return resolveColor(color[mode], chain)
|
||||
}
|
||||
|
||||
const resolved = Object.fromEntries(
|
||||
Object.entries(theme.theme)
|
||||
.filter(([key]) => key !== "selectedListItemText" && key !== "backgroundMenu" && key !== "thinkingOpacity")
|
||||
.map(([key, value]) => [key, resolveColor(value as ColorValue)]),
|
||||
) as Partial<Record<ThemeColor, RGBA>>
|
||||
|
||||
const hasSelectedListItemText = theme.theme.selectedListItemText !== undefined
|
||||
return {
|
||||
theme: {
|
||||
...(resolved as Record<ThemeColor, RGBA>),
|
||||
selectedListItemText: hasSelectedListItemText
|
||||
? resolveColor(theme.theme.selectedListItemText!)
|
||||
: resolved.background!,
|
||||
backgroundMenu:
|
||||
theme.theme.backgroundMenu === undefined ? resolved.backgroundElement! : resolveColor(theme.theme.backgroundMenu),
|
||||
} satisfies Omit<Theme, "_hasSelectedListItemText" | "thinkingOpacity">,
|
||||
hasSelectedListItemText,
|
||||
thinkingOpacity: theme.theme.thinkingOpacity ?? 0.6,
|
||||
}
|
||||
}
|
||||
|
|
@ -97,7 +97,7 @@ export type Variant = {
|
|||
dark: HexColor | RefName
|
||||
light: HexColor | RefName
|
||||
}
|
||||
export type ColorValue = HexColor | RefName | Variant | RGBA
|
||||
export type ColorValue = HexColor | RefName | Variant | RGBA | number
|
||||
export type ThemeJson = {
|
||||
$schema?: string
|
||||
defs?: Record<string, HexColor | RefName>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue