/** * Turning a design system's RECORDED values into ones you can look at. * * Replaces `designTokens.ts` and `designDrift.ts`, which between them read the * running app's own stylesheet — names out of a bundled `theme.css`, values out * of `getComputedStyle(document.documentElement)`. That could only ever describe * the install serving the page, and the design surface is for the projects an * install TRACKS (#274). What is left here works on any system's record, * including one for an app this browser has never loaded. * * Nothing in this module reads the document's own tokens or mutates the page. */ /** The base mode's key in `value_by_mode`, mirroring services/design_stylesheet. */ export const BASE_MODE = "base"; /** * Which declared value applies in `mode`. * * Falls back to base, which is the storage model rather than a convenience: a * mode block is an OVERRIDE layer, so a token with no entry for the current * mode is not missing — it is inheriting, exactly as the generated sheet has it. */ export function valueForMode( valueByMode: Record, mode: string, ): string { const own = valueByMode[mode]; if (own !== undefined && own !== "") return own; return valueByMode[BASE_MODE] ?? ""; } /** Every mode any token in the set declares, base first then the rest by name. */ export function modesPresent( tokens: { value_by_mode: Record }[], ): string[] { const modes = new Set(); for (const token of tokens) { for (const [mode, value] of Object.entries(token.value_by_mode)) { if (value) modes.add(mode); } } const rest = [...modes].filter((m) => m !== BASE_MODE).sort(); return modes.has(BASE_MODE) ? [BASE_MODE, ...rest] : rest; } /** * Resolve declared values the way a browser would, without applying them. * * A record holds `color-mix(in srgb, var(--fs-accent) 15%, transparent)`. Shown * as text that is a string; shown as a swatch it needs `var()` substituted and * the mix evaluated. Rather than write a CSS parser, set the declarations on an * offscreen probe and read them back — the substitution is done by the * implementation that would do it for real. * * Custom properties INHERIT, and `all: initial` does not reset them — so a probe * sitting in this page would resolve any reference the record leaves undeclared * against the surrounding app's own tokens. Previewing another project's system * would then quietly borrow this one's palette wherever that system was * incomplete, and a token the record already knows is broken (it shows up under * `unknown_refs`) would render as though it were fine. * * So every name referenced but not declared is blanked on the probe first. It * resolves to nothing, which is what the record says it is. */ const VAR_REFERENCE = /var\(\s*(--[A-Za-z0-9_-]+)/g; export function resolveDeclared(declared: Map): Map { const probe = document.createElement("div"); probe.style.display = "none"; for (const value of declared.values()) { for (const match of value.matchAll(VAR_REFERENCE)) { if (!declared.has(match[1])) probe.style.setProperty(match[1], " "); } } for (const [name, value] of declared) probe.style.setProperty(name, value); document.body.appendChild(probe); try { const computed = getComputedStyle(probe); const out = new Map(); for (const name of declared.keys()) { out.set(name, computed.getPropertyValue(name).trim()); } return out; } finally { probe.remove(); } }