diff --git a/src/css/injected.scss b/src/css/injected.scss index 57f7fc64..c65b093e 100644 --- a/src/css/injected.scss +++ b/src/css/injected.scss @@ -455,6 +455,58 @@ ul.magicDelete > li.deleting { top: 71.5px; margin-top: -2px; } + +/* Drill-in stack: only the current list + folder header stay clickable. + Class is toggled by updateSidebarAccessibility (never touches aria-hidden). */ +#menu .bsplus-sidebar-offscreen, +#menu .bsplus-sidebar-offscreen * { + pointer-events: none !important; + user-select: none !important; +} + +#menu > ul > .bsplus-sidebar-offscreen:not(.hasChildren.active) { + position: absolute !important; + left: -10000px !important; + width: 1px !important; + height: 1px !important; + margin: 0 !important; + padding: 0 !important; + overflow: hidden !important; + clip: rect(0, 0, 0, 0) !important; + opacity: 0 !important; +} + +#menu .sub .bsplus-sidebar-offscreen:not(.hasChildren.active) { + visibility: hidden !important; + position: absolute !important; + left: -10000px !important; + width: 1px !important; + height: 1px !important; + margin: 0 !important; + padding: 0 !important; + opacity: 0 !important; +} + +/* Only the frontmost open .sub panel receives pointer events */ +#menu .sub { + pointer-events: none; +} + +#menu li.hasChildren.active > .sub { + pointer-events: auto; +} + +#menu li.hasChildren.active > .sub:has(.hasChildren.active) { + pointer-events: none !important; +} + +#menu li.hasChildren.active .hasChildren.active > .sub { + pointer-events: auto !important; +} + +#menu:has(> ul > li.hasChildren.active) > ul > li:not(.hasChildren.active) { + pointer-events: none !important; +} #menu section > label { align-items: center; box-sizing: border-box; @@ -2326,6 +2378,10 @@ blurred { height: 64px; cursor: pointer; } +/* While a drill-in submenu is open, don't steal clicks meant for folder rows. */ +#menu:has(li.hasChildren.active) > .icon-cover { + pointer-events: none; +} .uiSlidePane > .pane > .header button { color: var(--text-color) !important; } diff --git a/src/plugins/built-in/themes/theme-manager.ts b/src/plugins/built-in/themes/theme-manager.ts index ccaeddf5..492315df 100644 --- a/src/plugins/built-in/themes/theme-manager.ts +++ b/src/plugins/built-in/themes/theme-manager.ts @@ -17,6 +17,15 @@ import { clearCustomThemeAdaptiveCssVariables, setCustomThemeAdaptiveCssVariables, } from "@/seqta/ui/colors/customThemeAdaptiveBindings"; +import { + clearThemeRuntime, + injectThemeDom, + runThemeScript, + type ThemeDomSpec, + type ThemeScriptSpec, + validateThemeDom, + validateThemeScript, +} from "./theme-runtime"; type ThemeContent = { id: string; @@ -31,6 +40,8 @@ type ThemeContent = { forceDark?: boolean; adaptiveCssVariables?: string[]; images?: { id: string; variableName: string; data: string }[]; // data: base64 + themeScript?: ThemeScriptSpec; + themeDom?: ThemeDomSpec; }; export type InstallThemeMeta = { @@ -53,6 +64,7 @@ export class ThemeManager { private imageUrlCache: Map = new Map(); private lastTransitionPoint: { x: number; y: number } = { x: 0, y: 0 }; private storeUpdateCheckRunning = false; + private headObserver: MutationObserver | null = null; private constructor() { console.debug("[ThemeManager] Initializing..."); @@ -307,6 +319,13 @@ export class ThemeManager { private async applyTheme(theme: CustomTheme): Promise { console.debug("[ThemeManager] Applying theme:", theme.name); try { + // Run the theme script BEFORE injecting CustomCSS so any state the + // script publishes (e.g. `data-city-state` and `--city-sky-color` for + // Noir City) is already on when the new CSS rules paint. + // Otherwise the CSS lands with var() unresolved and the page flashes + // its previous state before snapping to the right colour. + runThemeScript(theme.themeScript); + // Apply custom CSS if (theme.CustomCSS) { console.debug("[ThemeManager] Applying custom CSS"); @@ -348,6 +367,8 @@ export class ThemeManager { } setCustomThemeAdaptiveCssVariables(theme.adaptiveCssVariables ?? []); + + injectThemeDom(theme.themeDom); } catch (error) { console.error("[ThemeManager] Error applying theme:", error); } @@ -379,6 +400,13 @@ export class ThemeManager { ): Promise { console.debug("[ThemeManager] Removing theme:", theme.name); try { + clearThemeRuntime(); + + // Disconnect the head observer BEFORE removing the style element, + // otherwise the removal fires the observer and it would no-op only + // because the style is already gone — wasted work, but harmless. + this.disconnectStyleObserver(); + // Remove custom CSS if (this.styleElement) { console.debug("[ThemeManager] Removing custom CSS"); @@ -448,7 +476,13 @@ export class ThemeManager { } /** - * Apply custom CSS to the document + * Apply custom CSS to the document. The `