From 0ca0c7cf434b404b4b4c350c9a13c3ea647dae88 Mon Sep 17 00:00:00 2001 From: StroepWafel Date: Mon, 20 Apr 2026 21:43:05 +0930 Subject: [PATCH] add handlers for individual Channels --- .gitignore | 3 +- src/seqta/ui/colors/Manager.ts | 37 ++++++++++- .../ui/colors/customThemeAdaptiveBindings.ts | 64 ++++++++++++++----- 3 files changed, 85 insertions(+), 19 deletions(-) diff --git a/.gitignore b/.gitignore index e24ea199..46e7ae3b 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,5 @@ betterseqtaplus-safari/ .parcel-cache .env .env.submit -dependency-graph.svg \ No newline at end of file +dependency-graph.svg +/src/resources/themes diff --git a/src/seqta/ui/colors/Manager.ts b/src/seqta/ui/colors/Manager.ts index d72c8844..2adccfbf 100644 --- a/src/seqta/ui/colors/Manager.ts +++ b/src/seqta/ui/colors/Manager.ts @@ -5,7 +5,7 @@ import { lightenAndPaleColor } from "./lightenAndPaleColor"; import ColorLuminance from "./ColorLuminance"; import { settingsState } from "@/seqta/utils/listeners/SettingsState"; import { getAdaptiveColour } from "@/seqta/utils/adaptiveThemeColour"; -import { getCustomThemeAdaptiveCssVariables } from "@/seqta/ui/colors/customThemeAdaptiveBindings"; +import { getCustomThemeAdaptiveCssVariableBindings } from "@/seqta/ui/colors/customThemeAdaptiveBindings"; import darkLogo from "@/resources/icons/betterseqta-light-full.png"; import lightLogo from "@/resources/icons/betterseqta-dark-full.png"; @@ -84,6 +84,21 @@ function cancelColorTransition() { } } +function getRepresentativeRgbChannels(s: string): { r: number; g: number; b: number } | null { + const parsedHex = parseRepresentativeHex(s); + if (!parsedHex) return null; + try { + const [r, g, b] = Color(parsedHex).rgb().array(); + return { + r: Math.round(r), + g: Math.round(g), + b: Math.round(b), + }; + } catch { + return null; + } +} + function applyColorsWith(selectedColor: string) { if (settingsState.transparencyEffects) { document.documentElement.classList.add("transparencyEffects"); @@ -129,8 +144,24 @@ function applyColorsWith(selectedColor: string) { applyProperties({ ...commonProps, ...modeProps, ...dynamicProps }); if (settingsState.selectedTheme) { - for (const name of getCustomThemeAdaptiveCssVariables()) { - setCSSVar(name, selectedColor); + const channels = getRepresentativeRgbChannels(selectedColor); + for (const binding of getCustomThemeAdaptiveCssVariableBindings()) { + if (!binding.channel) { + setCSSVar(binding.cssVarName, selectedColor); + continue; + } + + if (!channels) { + continue; + } + + if (binding.channel === "r") { + setCSSVar(binding.cssVarName, String(channels.r)); + } else if (binding.channel === "g") { + setCSSVar(binding.cssVarName, String(channels.g)); + } else { + setCSSVar(binding.cssVarName, String(channels.b)); + } } } diff --git a/src/seqta/ui/colors/customThemeAdaptiveBindings.ts b/src/seqta/ui/colors/customThemeAdaptiveBindings.ts index dba35e3f..cdd0c823 100644 --- a/src/seqta/ui/colors/customThemeAdaptiveBindings.ts +++ b/src/seqta/ui/colors/customThemeAdaptiveBindings.ts @@ -1,20 +1,49 @@ /** Tracks which author-declared CSS variables mirror the effective accent; not persisted in settings storage. */ const VALID_CUSTOM_PROP = /^--[a-zA-Z0-9_-]{1,120}$/; +const VALID_CHANNEL = /^(r|g|b)$/; -let boundNames: string[] = []; +export type AdaptiveChannel = "r" | "g" | "b"; -export function normalizeAdaptiveCssVariableNames( +export type AdaptiveCssVariableBinding = { + cssVarName: string; + channel?: AdaptiveChannel; +}; + +let boundBindings: AdaptiveCssVariableBinding[] = []; + +function parseAdaptiveBinding( + rawBinding: string, +): AdaptiveCssVariableBinding | null { + const trimmed = rawBinding.trim(); + if (!trimmed) return null; + + const [rawName, rawChannel] = trimmed.split(":", 2); + const cssVarName = rawName?.trim() ?? ""; + if (!VALID_CUSTOM_PROP.test(cssVarName)) return null; + + if (!rawChannel) return { cssVarName }; + + const channel = rawChannel.trim().toLowerCase(); + if (!VALID_CHANNEL.test(channel)) return null; + + return { cssVarName, channel: channel as AdaptiveChannel }; +} + +export function normalizeAdaptiveCssVariableBindings( names: string[] | undefined, -): string[] { +): AdaptiveCssVariableBinding[] { if (!names?.length) return []; - const out: string[] = []; + const out: AdaptiveCssVariableBinding[] = []; const seen = new Set(); + for (const raw of names) { - const s = raw.trim(); - if (!VALID_CUSTOM_PROP.test(s) || seen.has(s)) continue; - seen.add(s); - out.push(s); + const parsed = parseAdaptiveBinding(raw); + if (!parsed) continue; + const key = `${parsed.cssVarName}:${parsed.channel ?? "full"}`; + if (seen.has(key)) continue; + seen.add(key); + out.push(parsed); } return out; } @@ -22,19 +51,24 @@ export function normalizeAdaptiveCssVariableNames( export function setCustomThemeAdaptiveCssVariables( names: string[] | undefined, ): void { - for (const n of boundNames) { - document.documentElement.style.removeProperty(n); + for (const binding of boundBindings) { + document.documentElement.style.removeProperty(binding.cssVarName); } - boundNames = normalizeAdaptiveCssVariableNames(names); + boundBindings = normalizeAdaptiveCssVariableBindings(names); } +export function getCustomThemeAdaptiveCssVariableBindings(): AdaptiveCssVariableBinding[] { + return boundBindings; +} + +// Backward-compatible helper for existing callsites. export function getCustomThemeAdaptiveCssVariables(): string[] { - return boundNames; + return boundBindings.map((b) => b.cssVarName); } export function clearCustomThemeAdaptiveCssVariables(): void { - for (const n of boundNames) { - document.documentElement.style.removeProperty(n); + for (const binding of boundBindings) { + document.documentElement.style.removeProperty(binding.cssVarName); } - boundNames = []; + boundBindings = []; }