complete performance and reliability follow-ups
This commit is contained in:
@@ -61,10 +61,13 @@ export {
|
||||
THEME_PALETTE_NAMES,
|
||||
THEME_COOKIE,
|
||||
THEME_CSS_HREF,
|
||||
THEME_CSS_PREFIX,
|
||||
THEME_JS_HREF,
|
||||
activeThemeCssHref,
|
||||
resolveThemeConfig,
|
||||
resolveThemeName,
|
||||
renderThemeCss,
|
||||
renderActiveThemeCss,
|
||||
renderThemeRuntime,
|
||||
defineThemeTokens,
|
||||
themeVar,
|
||||
|
||||
@@ -121,6 +121,7 @@ export interface ResolvedTheme {
|
||||
export const THEME_COOKIE = "wire-theme";
|
||||
export const ACCENT_COOKIE = "wire-accent";
|
||||
export const THEME_CSS_HREF = "/__wrnexus/theme.css";
|
||||
export const THEME_CSS_PREFIX = "/__wrnexus/theme/";
|
||||
export const THEME_JS_HREF = "/__wrnexus/theme.js";
|
||||
|
||||
/**
|
||||
@@ -519,6 +520,44 @@ function selectorValue(value: string): string {
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
|
||||
function globalThemeCss(): string {
|
||||
return (
|
||||
'\nhtml,body{font-family:var(--wire-font-sans,"Plus Jakarta Sans",ui-sans-serif,system-ui,sans-serif);}\n' +
|
||||
":root{--wire-radius-sm:0.55rem;--wire-radius-md:0.9rem;--wire-radius-lg:1.35rem;" +
|
||||
"--wire-shadow-1:0 1px 2px color-mix(in srgb, black 22%, transparent)," +
|
||||
"0 10px 30px color-mix(in srgb, black 14%, transparent);}\n" +
|
||||
"wrn-slot{display:contents;}\n" +
|
||||
"[data-for]{display:none !important;}\n"
|
||||
);
|
||||
}
|
||||
|
||||
/** URL for the small stylesheet containing only one active theme/accent pair. */
|
||||
export function activeThemeCssHref(themeName: string, accentName?: string): string {
|
||||
return `${THEME_CSS_PREFIX}${encodeURIComponent(themeName)}/${accentName ? encodeURIComponent(accentName) : "_"}.css`;
|
||||
}
|
||||
|
||||
/** Render only the tokens needed for the current SSR-selected theme and accent. */
|
||||
export function renderActiveThemeCss(
|
||||
theme: ResolvedTheme,
|
||||
themeName: string,
|
||||
accentName?: string,
|
||||
): string {
|
||||
const selectedTheme = theme.themes[themeName];
|
||||
if (!selectedTheme) throw new Error(`Unknown theme '${themeName}'.`);
|
||||
const blocks = [`:root{${tokensToDeclarations(selectedTheme)}}`];
|
||||
if (accentName) {
|
||||
if (!theme.accentNames.includes(accentName as ThemePaletteName)) {
|
||||
throw new Error(`Unknown theme accent '${accentName}'.`);
|
||||
}
|
||||
blocks.push(
|
||||
`:root{${tokensToDeclarations(
|
||||
paletteTokens(THEME_PALETTES[accentName as ThemePaletteName], themeScheme(selectedTheme)),
|
||||
)}}`,
|
||||
);
|
||||
}
|
||||
return blocks.join("\n") + globalThemeCss();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the theme stylesheet.
|
||||
*
|
||||
@@ -559,36 +598,7 @@ export function renderThemeCss(theme: ResolvedTheme): string {
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
blocks.join("\n") +
|
||||
'\nhtml,body{font-family:var(--wire-font-sans,"Plus Jakarta Sans",ui-sans-serif,system-ui,sans-serif);}\n' +
|
||||
// <wrn-slot> wraps content spliced into a component's <slot> so the client
|
||||
// runtime can tell which scope authored it. It is a pure ownership marker
|
||||
// and must never introduce a box: an unknown element would otherwise
|
||||
// default to display:inline and break the component's own flex/grid layout.
|
||||
/*
|
||||
* Shape and elevation tokens the Wire UI stylesheet depends on.
|
||||
*
|
||||
* ui.css references --wire-radius-sm (51 times) and --wire-radius-md (21)
|
||||
* but nothing ever defined them, so every component using them fell back
|
||||
* to square corners in any app that did not declare them itself.
|
||||
* --wire-shadow-1 had the same problem: the showcase declared it
|
||||
* privately, so the library looked right there and flat everywhere else.
|
||||
* Defining them with the rest of the theme keeps a component looking the
|
||||
* same in every app; an app can still override them.
|
||||
*/
|
||||
":root{--wire-radius-sm:0.55rem;--wire-radius-md:0.9rem;--wire-radius-lg:1.35rem;" +
|
||||
"--wire-shadow-1:0 1px 2px color-mix(in srgb, black 22%, transparent)," +
|
||||
"0 10px 30px color-mix(in srgb, black 14%, transparent);}\n" +
|
||||
"wrn-slot{display:contents;}\n" +
|
||||
// A [data-for] element is the loop TEMPLATE, not a rendered row: the server
|
||||
// ships it with mustaches unresolved ({item.title}) and hydration replaces
|
||||
// it with a comment marker. Painted as-is it flashes one blank, literal
|
||||
// row on every page load before the runtime boots. Hiding it costs
|
||||
// nothing after hydration -- the runtime strips data-for from the clones
|
||||
// it renders, so this only ever matches the template itself.
|
||||
"[data-for]{display:none !important;}\n"
|
||||
);
|
||||
return blocks.join("\n") + globalThemeCss();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -603,6 +613,7 @@ export function renderThemeRuntime(theme: ResolvedTheme): string {
|
||||
const names = JSON.stringify(theme.names);
|
||||
const accentNames = JSON.stringify(theme.accentNames);
|
||||
const defaultAccent = JSON.stringify(theme.defaultAccent ?? null);
|
||||
const cssPrefix = JSON.stringify(THEME_CSS_PREFIX);
|
||||
|
||||
return `(function(){
|
||||
var THEME_COOKIE=${JSON.stringify(THEME_COOKIE)};
|
||||
@@ -611,6 +622,7 @@ export function renderThemeRuntime(theme: ResolvedTheme): string {
|
||||
var ACCENTS=${accentNames};
|
||||
var DEFAULT_THEME=${JSON.stringify(theme.default)};
|
||||
var DEFAULT_ACCENT=${defaultAccent};
|
||||
var THEME_CSS_PREFIX=${cssPrefix};
|
||||
var MAX_AGE=31536000;
|
||||
var el=document.documentElement;
|
||||
|
||||
@@ -637,6 +649,13 @@ export function renderThemeRuntime(theme: ResolvedTheme): string {
|
||||
document.cookie=encodeURIComponent(name)+"=;path=/;max-age=0;samesite=lax"+secure;
|
||||
}
|
||||
|
||||
function syncStylesheet(themeName,accentName){
|
||||
var link=document.querySelector("link[data-wrnexus-theme]");
|
||||
if(!link)return;
|
||||
var accent=accentName?encodeURIComponent(accentName):"_";
|
||||
link.href=THEME_CSS_PREFIX+encodeURIComponent(themeName)+"/"+accent+".css";
|
||||
}
|
||||
|
||||
function getTheme(){
|
||||
var current=el.getAttribute("data-theme")||readCookie(THEME_COOKIE)||DEFAULT_THEME;
|
||||
return THEMES.indexOf(current)>=0?current:DEFAULT_THEME;
|
||||
@@ -665,6 +684,7 @@ export function renderThemeRuntime(theme: ResolvedTheme): string {
|
||||
if(THEMES.indexOf(name)<0)return;
|
||||
el.setAttribute("data-theme",name);
|
||||
writeCookie(THEME_COOKIE,name);
|
||||
syncStylesheet(name,getAccent());
|
||||
syncThemeButtons(document);
|
||||
window.dispatchEvent(new CustomEvent("wrnexus:theme-changed",{detail:{theme:name}}));
|
||||
}
|
||||
@@ -678,6 +698,7 @@ export function renderThemeRuntime(theme: ResolvedTheme): string {
|
||||
if(ACCENTS.indexOf(name)<0)return;
|
||||
el.setAttribute("data-accent",name);
|
||||
writeCookie(ACCENT_COOKIE,name);
|
||||
syncStylesheet(getTheme(),name);
|
||||
syncAccentButtons(document);
|
||||
window.dispatchEvent(new CustomEvent("wrnexus:accent-changed",{detail:{accent:name}}));
|
||||
}
|
||||
@@ -685,6 +706,7 @@ export function renderThemeRuntime(theme: ResolvedTheme): string {
|
||||
function clearAccent(){
|
||||
el.removeAttribute("data-accent");
|
||||
deleteCookie(ACCENT_COOKIE);
|
||||
syncStylesheet(getTheme(),null);
|
||||
syncAccentButtons(document);
|
||||
window.dispatchEvent(new CustomEvent("wrnexus:accent-changed",{detail:{accent:null}}));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user