theme-vir
Version:
Create an entire web theme.
151 lines (150 loc) • 7.31 kB
JavaScript
import { check } from '@augment-vir/assert';
import { getObjectTypedEntries, kebabCaseToCamelCase, } from '@augment-vir/common';
import { CSSResult } from 'element-vir';
/**
* Convert a color theme into code to define that color theme.
*
* @category Color Theme
*/
export function generateThemeCode(theme, options) {
const paletteVarName = options?.paletteVarName;
const defaultInitCode = colorInitToCode(theme.init.default, 1, undefined, paletteVarName);
const colorsInitCode = colorThemeInitToCode(theme.init.colors, 1, theme.init.default, paletteVarName);
const themeCode = `export const theme = defineColorTheme(\n${defaultInitCode},\n${colorsInitCode},\n);`;
const overridesCodes = (options?.overrides || []).map((override) => {
return generateOverrideCode(override, paletteVarName);
});
return [
themeCode,
...overridesCodes,
].join('\n\n');
}
function generateOverrideCode(override, paletteVarName) {
const parts = [];
// Check if default colors differ
const defaultOverrideEntries = [];
if (!colorInitValuesEqual(override.asTheme.init.default.foreground, override.originalTheme.init.default.foreground)) {
defaultOverrideEntries.push(`${tab(3)}foreground: ${colorInitValueToCode(override.asTheme.init.default.foreground, 3, paletteVarName)},`);
}
if (!colorInitValuesEqual(override.asTheme.init.default.background, override.originalTheme.init.default.background)) {
defaultOverrideEntries.push(`${tab(3)}background: ${colorInitValueToCode(override.asTheme.init.default.background, 3, paletteVarName)},`);
}
if (defaultOverrideEntries.length > 0) {
parts.push(`${tab(2)}defaultOverride: {\n${defaultOverrideEntries.join('\n')}\n${tab(2)}},`);
}
// Check for color overrides
const colorOverrideEntries = [];
getObjectTypedEntries(override.asTheme.init.colors).forEach(([colorName, colorInit,]) => {
const originalColorInit = override.originalTheme.init.colors[colorName];
if (!originalColorInit) {
return;
}
const colorEntries = [];
if ('foreground' in colorInit &&
(!('foreground' in originalColorInit) ||
!colorInitValuesEqual(colorInit.foreground, originalColorInit.foreground))) {
colorEntries.push(`${tab(4)}foreground: ${colorInitValueToCode(colorInit.foreground, 4, paletteVarName)},`);
}
if ('background' in colorInit &&
(!('background' in originalColorInit) ||
!colorInitValuesEqual(colorInit.background, originalColorInit.background))) {
colorEntries.push(`${tab(4)}background: ${colorInitValueToCode(colorInit.background, 4, paletteVarName)},`);
}
if (colorEntries.length > 0) {
colorOverrideEntries.push(`${tab(3)}'${colorName}': {\n${colorEntries.join('\n')}\n${tab(3)}},`);
}
});
if (colorOverrideEntries.length > 0) {
parts.push(`${tab(2)}colorOverrides: {\n${colorOverrideEntries.join('\n')}\n${tab(2)}},`);
}
const camelCaseName = kebabCaseToCamelCase(override.name);
return `export const ${camelCaseName}Override = defineColorThemeOverride(\n${tab(1)}theme,\n${tab(1)}'${override.name}',\n${tab(1)}{\n${parts.join('\n')}\n${tab(1)}},\n);`;
}
function tab(level) {
return ' '.repeat(level);
}
function colorInitValuesEqual(a, b) {
if (typeof a !== typeof b) {
return false;
}
const aString = check.isString(a) || a instanceof CSSResult ? String(a) : JSON.stringify(a);
const bString = check.isString(b) || b instanceof CSSResult ? String(b) : JSON.stringify(b);
return aString === bString;
}
function extractCssVarName(cssValue) {
const match = cssValue.match(/^var\(--([^,)]+)/);
return match ? match[1] : undefined;
}
function colorInitValueToCode(value, indentLevel, paletteVarName) {
if (typeof value === 'string') {
return `'${value}'`;
}
else if (typeof value === 'number') {
return String(value);
}
else if (value instanceof CSSResult) {
const cssText = String(value);
if (paletteVarName) {
const varName = extractCssVarName(cssText);
if (varName) {
return `${paletteVarName}['${varName}']`;
}
}
return `css\`${cssText}\``;
}
else if ('refBackground' in value ||
'refForeground' in value ||
'refDefaultBackground' in value ||
'refDefaultForeground' in value) {
const entries = [];
if ('refForeground' in value) {
entries.push(`${tab(indentLevel + 1)}refForeground: '${value.refForeground}',`);
}
if ('refBackground' in value) {
entries.push(`${tab(indentLevel + 1)}refBackground: '${value.refBackground}',`);
}
if ('refDefaultForeground' in value) {
entries.push(`${tab(indentLevel + 1)}refDefaultForeground: true,`);
}
if ('refDefaultBackground' in value) {
entries.push(`${tab(indentLevel + 1)}refDefaultBackground: true,`);
}
return `{\n${entries.join('\n')}\n${tab(indentLevel)}}`;
}
else {
// SingleCssVarDefinition
return `'${value.default}'`;
}
}
function colorInitToCode(colorInit, indentLevel, defaultInit, paletteVarName) {
const entries = [];
if ('foreground' in colorInit &&
(!defaultInit || !colorInitValuesEqual(colorInit.foreground, defaultInit.foreground)) &&
!check.hasKey(colorInit.foreground, 'refDefaultForeground')) {
// Check if foreground matches default background (use refDefaultBackground)
if (defaultInit && colorInitValuesEqual(colorInit.foreground, defaultInit.background)) {
entries.push(`${tab(indentLevel + 1)}foreground: {\n${tab(indentLevel + 2)}refDefaultBackground: true,\n${tab(indentLevel + 1)}},`);
}
else {
entries.push(`${tab(indentLevel + 1)}foreground: ${colorInitValueToCode(colorInit.foreground, indentLevel + 1, paletteVarName)},`);
}
}
if ('background' in colorInit &&
(!defaultInit || !colorInitValuesEqual(colorInit.background, defaultInit.background)) &&
!check.hasKey(colorInit.background, 'refDefaultBackground')) {
// Check if background matches default foreground (use refDefaultForeground)
if (defaultInit && colorInitValuesEqual(colorInit.background, defaultInit.foreground)) {
entries.push(`${tab(indentLevel + 1)}background: {\n${tab(indentLevel + 2)}refDefaultForeground: true,\n${tab(indentLevel + 1)}},`);
}
else {
entries.push(`${tab(indentLevel + 1)}background: ${colorInitValueToCode(colorInit.background, indentLevel + 1, paletteVarName)},`);
}
}
return `${tab(indentLevel)}{\n${entries.join('\n')}\n${tab(indentLevel)}}`;
}
function colorThemeInitToCode(colorsInit, indentLevel, defaultInit, paletteVarName) {
const entries = getObjectTypedEntries(colorsInit).map(([colorName, colorInit,]) => {
return `${tab(indentLevel + 1)}'${colorName}': ${colorInitToCode(colorInit, indentLevel + 1, defaultInit, paletteVarName).trimStart()},`;
});
return `${tab(indentLevel)}{\n${entries.join('\n')}\n${tab(indentLevel)}}`;
}