feat(styles): share accent cookies across app domains
Quality / quality (ubuntu-latest) (push) Failing after 9m48s
Quality / quality (windows-latest) (push) Canceled after 0s

This commit is contained in:
2026-08-12 20:40:35 +05:30
parent 32187a425c
commit 6133d33b8c
5 changed files with 50 additions and 11 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/cli", "name": "@wrnexus/cli",
"version": "0.8.25", "version": "0.8.26",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/dev-server", "name": "@wrnexus/dev-server",
"version": "0.8.24", "version": "0.8.25",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@wrnexus/styles", "name": "@wrnexus/styles",
"version": "0.8.10", "version": "0.8.11",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"exports": { "exports": {
+34 -8
View File
@@ -98,6 +98,19 @@ export interface ThemeAccentConfig {
default?: ThemePaletteName | false; default?: ThemePaletteName | false;
/** Runtime-selectable accent names. Defaults to every built-in THEME_PALETTE. */ /** Runtime-selectable accent names. Defaults to every built-in THEME_PALETTE. */
options?: ThemePaletteName[]; options?: ThemePaletteName[];
/** Cookie scope used to persist the selected accent across related applications. */
cookie?: ThemeCookieConfig;
}
export interface ThemeCookieConfig {
/** Parent domain shared by related applications, for example `localhost` or `.example.com`. */
domain?: string;
/** Cookie path. Defaults to `/`. */
path?: string;
/** SameSite policy. Defaults to `Lax`. */
sameSite?: "Strict" | "Lax" | "None";
/** Force or disable Secure. When omitted it follows the current protocol. */
secure?: boolean;
} }
export interface ThemeConfig { export interface ThemeConfig {
@@ -117,6 +130,7 @@ export interface ResolvedTheme {
themes: Record<string, ThemeTokens>; themes: Record<string, ThemeTokens>;
defaultAccent?: ThemePaletteName; defaultAccent?: ThemePaletteName;
accentNames: ThemePaletteName[]; accentNames: ThemePaletteName[];
accentCookie?: ThemeCookieConfig;
} }
/** Cookies used by the SSR renderer and client runtime. */ /** Cookies used by the SSR renderer and client runtime. */
@@ -494,6 +508,7 @@ export function resolveThemeConfig(config?: ThemeConfig): ResolvedTheme {
themes, themes,
defaultAccent, defaultAccent,
accentNames, accentNames,
accentCookie: config?.accent?.cookie,
}; };
} }
@@ -622,6 +637,7 @@ export function renderThemeRuntime(theme: ResolvedTheme): string {
const accentNames = JSON.stringify(theme.accentNames); const accentNames = JSON.stringify(theme.accentNames);
const defaultAccent = JSON.stringify(theme.defaultAccent ?? null); const defaultAccent = JSON.stringify(theme.defaultAccent ?? null);
const cssPrefix = JSON.stringify(THEME_CSS_PREFIX); const cssPrefix = JSON.stringify(THEME_CSS_PREFIX);
const accentCookie = JSON.stringify(theme.accentCookie ?? {});
return `(function(){ return `(function(){
var THEME_COOKIE=${JSON.stringify(THEME_COOKIE)}; var THEME_COOKIE=${JSON.stringify(THEME_COOKIE)};
@@ -631,6 +647,7 @@ export function renderThemeRuntime(theme: ResolvedTheme): string {
var DEFAULT_THEME=${JSON.stringify(theme.default)}; var DEFAULT_THEME=${JSON.stringify(theme.default)};
var DEFAULT_ACCENT=${defaultAccent}; var DEFAULT_ACCENT=${defaultAccent};
var THEME_CSS_PREFIX=${cssPrefix}; var THEME_CSS_PREFIX=${cssPrefix};
var ACCENT_COOKIE_OPTIONS=${accentCookie};
var MAX_AGE=31536000; var MAX_AGE=31536000;
var el=document.documentElement; var el=document.documentElement;
@@ -646,15 +663,22 @@ export function renderThemeRuntime(theme: ResolvedTheme): string {
return null; return null;
} }
function writeCookie(name,value){ function cookieOptions(options){
var secure=location.protocol==="https:"?";secure":""; options=options||{};
document.cookie=encodeURIComponent(name)+"="+encodeURIComponent(value)+ var path=options.path||"/";
";path=/;max-age="+MAX_AGE+";samesite=lax"+secure; var sameSite=options.sameSite||"Lax";
var domain=options.domain?";domain="+options.domain:"";
var secure=options.secure===true||(options.secure!==false&&location.protocol==="https:")?";secure":"";
return ";path="+path+";samesite="+sameSite+domain+secure;
} }
function deleteCookie(name){ function writeCookie(name,value,options){
var secure=location.protocol==="https:"?";secure":""; document.cookie=encodeURIComponent(name)+"="+encodeURIComponent(value)+
document.cookie=encodeURIComponent(name)+"=;path=/;max-age=0;samesite=lax"+secure; ";max-age="+MAX_AGE+cookieOptions(options);
}
function deleteCookie(name,options){
document.cookie=encodeURIComponent(name)+"=;max-age=0"+cookieOptions(options);
} }
function syncStylesheet(themeName,accentName){ function syncStylesheet(themeName,accentName){
@@ -705,7 +729,8 @@ export function renderThemeRuntime(theme: ResolvedTheme): string {
function setAccent(name){ function setAccent(name){
if(ACCENTS.indexOf(name)<0)return; if(ACCENTS.indexOf(name)<0)return;
el.setAttribute("data-accent",name); el.setAttribute("data-accent",name);
writeCookie(ACCENT_COOKIE,name); deleteCookie(ACCENT_COOKIE);
writeCookie(ACCENT_COOKIE,name,ACCENT_COOKIE_OPTIONS);
syncStylesheet(getTheme(),name); syncStylesheet(getTheme(),name);
syncAccentButtons(document); syncAccentButtons(document);
window.dispatchEvent(new CustomEvent("wrnexus:accent-changed",{detail:{accent:name}})); window.dispatchEvent(new CustomEvent("wrnexus:accent-changed",{detail:{accent:name}}));
@@ -714,6 +739,7 @@ export function renderThemeRuntime(theme: ResolvedTheme): string {
function clearAccent(){ function clearAccent(){
el.removeAttribute("data-accent"); el.removeAttribute("data-accent");
deleteCookie(ACCENT_COOKIE); deleteCookie(ACCENT_COOKIE);
deleteCookie(ACCENT_COOKIE,ACCENT_COOKIE_OPTIONS);
syncStylesheet(getTheme(),null); syncStylesheet(getTheme(),null);
syncAccentButtons(document); syncAccentButtons(document);
window.dispatchEvent(new CustomEvent("wrnexus:accent-changed",{detail:{accent:null}})); window.dispatchEvent(new CustomEvent("wrnexus:accent-changed",{detail:{accent:null}}));
+13
View File
@@ -46,6 +46,19 @@ describe("theme accents", () => {
expect(runtime).not.toContain("localStorage"); expect(runtime).not.toContain("localStorage");
expect(runtime).not.toContain("style.setProperty"); expect(runtime).not.toContain("style.setProperty");
}); });
test("runtime scopes accent cookies to a configured parent domain", () => {
const theme = resolveThemeConfig({
palette: "rose",
accent: { cookie: { domain: "localhost", path: "/", sameSite: "Lax", secure: false } },
});
const runtime = renderThemeRuntime(theme);
expect(theme.accentCookie?.domain).toBe("localhost");
expect(runtime).toContain('"domain":"localhost"');
expect(runtime).toContain("deleteCookie(ACCENT_COOKIE);");
expect(runtime).toContain("writeCookie(ACCENT_COOKIE,name,ACCENT_COOKIE_OPTIONS)");
});
}); });
test("live document accent has priority over cookie and default", () => { test("live document accent has priority over cookie and default", () => {
const runtime = renderThemeRuntime( const runtime = renderThemeRuntime(