diff --git a/src/components/layout/use-custom-theme.ts b/src/components/layout/use-custom-theme.ts index 7682b0a96..3e8e7c916 100644 --- a/src/components/layout/use-custom-theme.ts +++ b/src/components/layout/use-custom-theme.ts @@ -18,6 +18,61 @@ import { useVerge } from "@/hooks/use-verge"; import { defaultDarkTheme, defaultTheme } from "@/pages/_theme"; import { useSetThemeMode, useThemeMode } from "@/services/states"; +const CSS_INJECTION_SCOPE_ROOT = "[data-css-injection-root]"; +const CSS_INJECTION_SCOPE_LIMIT = + ':is(.monaco-editor .view-lines, .monaco-editor .view-line, .monaco-editor .margin, .monaco-editor .margin-view-overlays, .monaco-editor .view-overlays, .monaco-editor [class^="mtk"], .monaco-editor [class*=" mtk"])'; +const TOP_LEVEL_AT_RULES = [ + "@charset", + "@import", + "@namespace", + "@font-face", + "@keyframes", + "@counter-style", + "@page", + "@property", + "@font-feature-values", + "@color-profile", +]; +let cssScopeSupport: boolean | null = null; + +const canUseCssScope = () => { + if (cssScopeSupport !== null) { + return cssScopeSupport; + } + if (typeof document === "undefined") { + return false; + } + try { + const testStyle = document.createElement("style"); + testStyle.textContent = "@scope (:root) { }"; + document.head.appendChild(testStyle); + cssScopeSupport = !!testStyle.sheet?.cssRules?.length; + document.head.removeChild(testStyle); + } catch { + cssScopeSupport = false; + } + return cssScopeSupport; +}; + +const wrapCssInjectionWithScope = (css?: string) => { + if (!css?.trim()) { + return ""; + } + const lowerCss = css.toLowerCase(); + const hasTopLevelOnlyRule = TOP_LEVEL_AT_RULES.some((rule) => + lowerCss.includes(rule), + ); + if (hasTopLevelOnlyRule) { + return null; + } + const scopeRoot = CSS_INJECTION_SCOPE_ROOT; + const scopeLimit = CSS_INJECTION_SCOPE_LIMIT; + const scopedBlock = `@scope (${scopeRoot}) to (${scopeLimit}) { +${css} +}`; + return scopedBlock; +}; + const languagePackMap: Record = { zh: { ...zhXDataGrid }, fa: { ...faXDataGrid }, @@ -134,8 +189,27 @@ export const useCustomTheme = () => { }; const legacyQuery = mediaQuery as MediaQueryListLegacy; - legacyQuery.addListener?.(handleChange); - return () => legacyQuery.removeListener?.(handleChange); + const legacyAddListener = ( + legacyQuery as { + addListener?: ( + listener: (this: MediaQueryList, event: MediaQueryListEvent) => void, + ) => void; + } + ).addListener; + legacyAddListener?.call(legacyQuery, handleChange); + return () => { + const legacyRemoveListener = ( + legacyQuery as { + removeListener?: ( + listener: ( + this: MediaQueryList, + event: MediaQueryListEvent, + ) => void, + ) => void; + } + ).removeListener; + legacyRemoveListener?.call(legacyQuery, handleChange); + }; }, [theme_mode, setMode]); useEffect(() => { @@ -264,6 +338,7 @@ export const useCustomTheme = () => { ? String(setting.background_opacity) : "1", ); + rootEle.setAttribute("data-css-injection-root", "true"); } let styleElement = document.querySelector("style#verge-theme"); @@ -274,6 +349,11 @@ export const useCustomTheme = () => { } if (styleElement) { + let scopedCss: string | null = null; + if (canUseCssScope() && setting.css_injection) { + scopedCss = wrapCssInjectionWithScope(setting.css_injection); + } + const effectiveInjectedCss = scopedCss ?? setting.css_injection ?? ""; const globalStyles = ` /* 修复滚动条样式 */ ::-webkit-scrollbar { @@ -323,7 +403,7 @@ export const useCustomTheme = () => { } `; - styleElement.innerHTML = (setting.css_injection || "") + globalStyles; + styleElement.innerHTML = effectiveInjectedCss + globalStyles; } const { palette } = muiTheme;