feat: release design system engine, studio and component contracts
This commit is contained in:
1 parent
ff1065fb27
commit
3943b946d6
693 files changed
+175546
-3894
No files matched your search
@@ -0,0 +1,92 @@
|
||||
import type { DesignSystem, LegacyTheme, ResolveOptions, ResolvedDesignSystem } from "./types.ts";
|
||||
import { resolveDesignSystem, fail } from "./resolve.ts";
|
||||
import { validName } from "./validation.ts";
|
||||
import { compileComponentRecipes } from "./recipes.ts";
|
||||
|
||||
function declarations(system: ResolvedDesignSystem, legacyOnly = false): Record<string, string> {
|
||||
const result: Record<string, string> = {};
|
||||
const owners = new Map<string, string>();
|
||||
for (const [name, token] of Object.entries(system.tokens)) {
|
||||
const keys = legacyOnly
|
||||
? token.legacy
|
||||
? [token.legacy]
|
||||
: []
|
||||
: [
|
||||
`--wrn-ds-${name.replace(/\./g, "-")}`,
|
||||
...(token.legacy
|
||||
? [token.legacy === "color-scheme" ? "color-scheme" : `--wrn-${token.legacy}`]
|
||||
: []),
|
||||
];
|
||||
for (const key of keys) {
|
||||
const previous = owners.get(key);
|
||||
if (previous && previous !== name)
|
||||
fail("WRN-DS-COLLISION", name, `Output '${key}' is already bound to '${previous}'.`);
|
||||
owners.set(key, name);
|
||||
result[key] = String(token.value);
|
||||
}
|
||||
}
|
||||
return Object.fromEntries(
|
||||
Object.entries(result).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)),
|
||||
);
|
||||
}
|
||||
|
||||
/** Compile deterministic tokens and versioned core-component recipes. */
|
||||
export function compileDesignSystem(
|
||||
system: DesignSystem,
|
||||
options: ResolveOptions & { scope?: string } = {},
|
||||
): { css: string; recipeCss: string; resolved: ResolvedDesignSystem } {
|
||||
if (options.scope !== undefined && !validName(options.scope))
|
||||
fail("WRN-DS-NAME", "scope", "Invalid scope identifier.");
|
||||
const resolved = resolveDesignSystem(system, options);
|
||||
const selector = options.scope ? `[data-wrn-design="${options.scope}"]` : ":root";
|
||||
const block = (target: string, value: ResolvedDesignSystem) =>
|
||||
`${target}{${Object.entries(declarations(value))
|
||||
.map(([key, val]) => `${key}:${val};`)
|
||||
.join("")}}`;
|
||||
const blocks = [block(selector, resolved)];
|
||||
const defaultRecipes = compileComponentRecipes(resolved, selector, options.scope);
|
||||
const recipeBlocks = [defaultRecipes];
|
||||
for (const mode of resolved.modes) {
|
||||
blocks.push(
|
||||
block(`${selector}[data-theme="${mode}"]`, resolveDesignSystem(system, { ...options, mode })),
|
||||
);
|
||||
const modeResolved = resolveDesignSystem(system, { ...options, mode });
|
||||
// Legacy-bound references already follow mode variables. Avoid repeating identical rules.
|
||||
const normalized = compileComponentRecipes(modeResolved, selector, options.scope);
|
||||
if (normalized !== defaultRecipes)
|
||||
recipeBlocks.push(
|
||||
compileComponentRecipes(modeResolved, `${selector}[data-theme="${mode}"]`, options.scope),
|
||||
);
|
||||
}
|
||||
const recipeCss = recipeBlocks.filter(Boolean).join("\n");
|
||||
return {
|
||||
css: blocks.join("\n") + "\n" + (recipeCss ? recipeCss + "\n" : ""),
|
||||
recipeCss,
|
||||
resolved,
|
||||
};
|
||||
}
|
||||
|
||||
/** Return existing ThemeConfig-compatible data, retaining explicit app overrides. */
|
||||
export function adaptLegacyTheme<T extends LegacyTheme>(
|
||||
system: DesignSystem,
|
||||
legacy: T,
|
||||
options: ResolveOptions = {},
|
||||
): T & LegacyTheme {
|
||||
const resolved = resolveDesignSystem(system, options);
|
||||
const base = declarations(resolved, true);
|
||||
const names = [
|
||||
...new Set(["light", "dark", ...resolved.modes, ...Object.keys(legacy.themes ?? {})]),
|
||||
].sort();
|
||||
const themes: Record<string, Record<string, string>> = {};
|
||||
for (const name of names) {
|
||||
const mapped = resolved.modes.includes(name)
|
||||
? declarations(resolveDesignSystem(system, { ...options, mode: name }), true)
|
||||
: base;
|
||||
themes[name] = { ...mapped, ...(legacy.themes?.[name] ?? {}) };
|
||||
}
|
||||
return {
|
||||
...legacy,
|
||||
...(legacy.default ? {} : resolved.mode ? { default: resolved.mode } : {}),
|
||||
themes,
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user