fix(theme): --ot-* 变量名 camelCase/kebab-case 不匹配导致浅色分支静默失效
CSS 自定义属性名大小写敏感,而 semanticVars() 的键是 camelCase、样式表读的是
kebab-case:chrome.ts 直接写 `--ot-${key}` 时,9 个多词变量(--ot-text-muted、
--ot-text-secondary、--ot-surface-raised、--ot-border-strong、--ot-success-fg、
--ot-danger-fg、--ot-warning-fg、--ot-info-fg、--ot-scrollbar-active、
--ot-focus-ring)从未真正生效,元素一律回落到 global.css :root 的深色兜底值——
浅色主题正是靠这些变量修可读性的,等于白改(--ot-text-muted 48 处引用仍是
#8a8a8a,白底 3.25:1;--ot-danger-fg 仍是 #f85149,3.15:1)。
- 键 `muted` 改名 `textMuted`(CSS 读的是 --ot-text-muted)
- 新增 semantic.ts 的 cssVarName()/semanticCssVars(),chrome.ts 只调用它们,
命名逻辑单一来源
- 新增 tests/ot-var-names.mjs:把「CSS 引用名 = JS 写入名 = :root 声明名」
三集合锁在一起(值测试看不出这类问题),已用「去掉转换」验证会失败
- AGENTS/STATE 同步该铁律;测试数 22/25 的陈旧计数修正为实际的 29
This commit is contained in:
1 parent
9e424138b5
commit
3b86d62111
7 files changed
+120
-17
No files matched your search
@@ -1,5 +1,5 @@
|
||||
import type { ThemeColors } from '@shared/theme'
|
||||
import { contrast, isLightTheme, mix, semanticVars } from './semantic'
|
||||
import { contrast, isLightTheme, mix, semanticCssVars } from './semantic'
|
||||
|
||||
// 兼容既有导入方(main.tsx 的 mix);新代码请直接从 ./semantic 导入。
|
||||
export { contrast, luminance, mix } from './semantic'
|
||||
@@ -60,12 +60,11 @@ export function applyChromeTheme(colors: ThemeColors): void {
|
||||
// styles. The dark branch locks the SEMANTIC anchors (zero visual change);
|
||||
// the light branch darkens them until the WCAG ratio holds against this
|
||||
// theme's background. `color-scheme` rides along so native scrollbars and
|
||||
// form controls follow the same switch.
|
||||
const vars = semanticVars(colors, chromeIsLight)
|
||||
for (const [key, value] of Object.entries(vars)) {
|
||||
if (key === 'colorScheme') style.setProperty('color-scheme', value)
|
||||
else set(`--ot-${key}`, value)
|
||||
}
|
||||
// form controls follow the same switch. semanticCssVars() owns the camelCase
|
||||
// -> kebab-case naming (custom property names are case-sensitive, so a miss
|
||||
// would leave the variable unset and silently fall back to the dark :root
|
||||
// literal); tests/ot-var-names.mjs guards it.
|
||||
for (const [name, value] of Object.entries(semanticCssVars(colors, chromeIsLight))) set(name, value)
|
||||
applyTabAccent(tabAccent)
|
||||
}
|
||||
|
||||
|
||||
@@ -88,7 +88,8 @@ export const SEMANTIC = {
|
||||
focusRing: 'rgba(59, 142, 234, 0.2)'
|
||||
} as const
|
||||
|
||||
/** Values for --ot-* variables (keys without the prefix). */
|
||||
/** Values for --ot-* variables (camelCase keys; chrome.ts converts them to
|
||||
* kebab-case, so `textMuted` lands as `--ot-text-muted`). */
|
||||
export function semanticVars(colors: ThemeLike, light: boolean): Record<string, string> {
|
||||
const bg = colors.background
|
||||
const fg = colors.foreground
|
||||
@@ -96,7 +97,7 @@ export function semanticVars(colors: ThemeLike, light: boolean): Record<string,
|
||||
return {
|
||||
text: fg,
|
||||
textSecondary: mix(bg, fg, 0.78) ?? fg,
|
||||
muted: SEMANTIC.muted,
|
||||
textMuted: SEMANTIC.muted,
|
||||
surface: mix(bg, fg, 0.05) ?? bg,
|
||||
surfaceRaised: SEMANTIC.surfaceRaised,
|
||||
hover: SEMANTIC.hover,
|
||||
@@ -120,7 +121,7 @@ export function semanticVars(colors: ThemeLike, light: boolean): Record<string,
|
||||
return {
|
||||
text: darkenToRatio(fg, bg, 7),
|
||||
textSecondary: darkenToRatio(mix(bg, fg, 0.75) ?? fg, bg, 4.5),
|
||||
muted: darkenToRatio(SEMANTIC.muted, bg, 4.5),
|
||||
textMuted: darkenToRatio(SEMANTIC.muted, bg, 4.5),
|
||||
surface: mix(bg, fg, 0.04) ?? bg,
|
||||
surfaceRaised: mix(bg, '#000000', 0.045) ?? bg,
|
||||
hover: 'rgba(0, 0, 0, 0.06)',
|
||||
@@ -141,3 +142,20 @@ export function semanticVars(colors: ThemeLike, light: boolean): Record<string,
|
||||
colorScheme: 'light'
|
||||
}
|
||||
}
|
||||
|
||||
/** Final CSS custom property name for a semanticVars() key. Custom property
|
||||
* names are case-sensitive, so this kebab conversion is the only thing tying
|
||||
* the camelCase keys to the --ot-* names the stylesheets read — a miss leaves
|
||||
* the variable unset and the element falls back to the dark :root literal.
|
||||
* Exported so chrome.ts and tests/ot-var-names.mjs share one implementation. */
|
||||
export function cssVarName(key: string): string {
|
||||
if (key === 'colorScheme') return 'color-scheme'
|
||||
return `--ot-${key.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`)}`
|
||||
}
|
||||
|
||||
/** semanticVars() keyed by their final CSS custom property names. */
|
||||
export function semanticCssVars(colors: ThemeLike, light: boolean): Record<string, string> {
|
||||
const out: Record<string, string> = {}
|
||||
for (const [key, value] of Object.entries(semanticVars(colors, light))) out[cssVarName(key)] = value
|
||||
return out
|
||||
}
|
||||
Reference in new issue
Block a user