theme-vir
Version:
Create an entire web theme.
193 lines (192 loc) • 8.44 kB
JavaScript
import { assert, check } from '@augment-vir/assert';
import { getObjectTypedEntries, log, } from '@augment-vir/common';
import { CSSResult } from 'element-vir';
import { defineCssVars, } from 'lit-css-vars';
/** @category Internal */
export function noRefColorInitToString(init) {
if (check.isPrimitive(init) || init instanceof CSSResult) {
return String(init);
}
else {
return init.default;
}
}
/**
* Handles a color init value.
*
* @category Internal
*/
export function createColorCssVarDefault(fromName, init, defaultInit, colorsInit) {
const defaultForegroundKey = `${defaultInit.prefix}-default-fg`;
const defaultBackgroundKey = `${defaultInit.prefix}-default-bg`;
if (check.isPrimitive(init) || init instanceof CSSResult) {
return init;
}
else if ('refDefaultBackground' in init) {
return `var(--${defaultBackgroundKey}, ${noRefColorInitToString(defaultInit.background)})`;
}
else if ('refDefaultForeground' in init) {
return `var(--${defaultForegroundKey}, ${noRefColorInitToString(defaultInit.foreground)})`;
}
else if ('refBackground' in init || 'refForeground' in init) {
const referenceKey = check.hasKey(init, 'refBackground')
? 'refBackground'
: check.hasKey(init, 'refForeground')
? 'refForeground'
: undefined;
const reference = referenceKey && check.hasKey(init, referenceKey) ? init[referenceKey] : undefined;
const layerKey = referenceKey === 'refBackground' ? 'background' : 'foreground';
const referenced = reference && colorsInit[reference];
if (!referenced) {
throw new Error(`Color theme ${referenceKey} reference '${reference}' does not exist. (Referenced from '${fromName}'.)`);
}
const colorValue = referenced[layerKey] ||
(layerKey === 'foreground'
? createColorCssVarDefault(defaultForegroundKey, defaultInit.foreground, defaultInit, colorsInit)
: createColorCssVarDefault(defaultBackgroundKey, defaultInit.background, defaultInit, colorsInit));
return `var(--${reference}-${layerKey === 'foreground' ? 'fg' : 'bg'}, ${createColorCssVarDefault(reference, colorValue, defaultInit, colorsInit)})`;
}
else {
return init.value;
}
}
/**
* Default foreground/background color theme used in {@link ColorTheme}. Do not define a theme color
* with this name!
*
* @category Internal
*/
export const themeDefaultKey = 'theme-default';
/**
* Define a color theme.
*
* @category Color Theme
*/
export function defineColorTheme(defaultInit, allColorsInit) {
try {
if (themeDefaultKey in allColorsInit) {
throw new Error(`Cannot define theme color by name '${themeDefaultKey}', it is used internally.`);
}
const defaultForegroundKey = `${defaultInit.prefix}-default-fg`;
const defaultBackgroundKey = `${defaultInit.prefix}-default-bg`;
const inverseDefaultForegroundKey = `${defaultInit.prefix}-default-inverse-fg`;
const inverseDefaultBackgroundKey = `${defaultInit.prefix}-default-inverse-bg`;
const defaultColorsInit = {
[defaultForegroundKey]: createColorCssVarDefault(defaultForegroundKey, defaultInit.foreground, defaultInit, allColorsInit),
[defaultBackgroundKey]: createColorCssVarDefault(defaultBackgroundKey, defaultInit.background, defaultInit, allColorsInit),
[inverseDefaultForegroundKey]: createColorCssVarDefault(inverseDefaultForegroundKey, defaultInit.background, defaultInit, allColorsInit),
[inverseDefaultBackgroundKey]: createColorCssVarDefault(inverseDefaultBackgroundKey, defaultInit.foreground, defaultInit, allColorsInit),
};
const defaultColors = defineCssVars(defaultColorsInit);
const cssVarsSetup = getObjectTypedEntries(allColorsInit).reduce((accum, [colorName, colorInit,]) => {
const names = createCssVarNames(colorName);
const foreground = colorInit.foreground
? createColorCssVarDefault([
colorName,
'foreground',
].join(' '), colorInit.foreground, defaultInit, allColorsInit)
: // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
`var(${defaultColors[defaultForegroundKey].name}, ${defaultColors[defaultForegroundKey].default})`;
const background = colorInit.background
? createColorCssVarDefault([
colorName,
'background',
].join(' '), colorInit.background, defaultInit, allColorsInit)
: // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
`var(${defaultColors[defaultBackgroundKey].name}, ${defaultColors[defaultBackgroundKey].default})`;
accum[names.foreground] = foreground;
accum[names.background] = background;
accum[names.foregroundInverse] = `var(--${names.background}, ${background})`;
accum[names.backgroundInverse] = `var(--${names.foreground}, ${foreground})`;
return accum;
}, {});
/**
* This has multiple `as` casts because `defineCssVars` complains that `cssVarsSetup` is too
* generic. That is indeed true, but in this use case we do not care because the resulting
* `cssVars` object is not directly exposed.
*/
const cssVars = defineCssVars(cssVarsSetup);
const colors = {};
const inverseColors = {};
getObjectTypedEntries(allColorsInit).forEach(([colorName, colorInit,]) => {
assert.isString(colorName);
const names = createCssVarNames(colorName);
const foreground = cssVars[names.foreground];
const background = cssVars[names.background];
const foregroundInverse = cssVars[names.foregroundInverse];
const backgroundInverse = cssVars[names.backgroundInverse];
assert.isDefined(foreground);
assert.isDefined(background);
assert.isDefined(foregroundInverse);
assert.isDefined(backgroundInverse);
colors[colorName] = {
foreground,
background,
init: colorInit,
name: colorName,
};
inverseColors[colorName] = {
foreground: foregroundInverse,
background: backgroundInverse,
init: colorInit,
name: colorName,
};
});
const themeDefaultColors = {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
foreground: defaultColors[defaultForegroundKey],
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
background: defaultColors[defaultBackgroundKey],
init: defaultInit,
name: themeDefaultKey,
};
const themeDefaultInverseColors = {
...themeDefaultColors,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
foreground: defaultColors[inverseDefaultForegroundKey],
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
background: defaultColors[inverseDefaultBackgroundKey],
};
return {
colors: {
[themeDefaultKey]: themeDefaultColors,
...colors,
},
inverse: {
[themeDefaultKey]: themeDefaultInverseColors,
...inverseColors,
},
init: {
colors: allColorsInit,
default: defaultInit,
},
prefix: defaultInit.prefix,
};
}
catch (error) {
globalThis.setTimeout(() => log.error(error));
throw error;
}
}
function createCssVarNames(colorName) {
return {
foreground: [
colorName,
'fg',
].join('-'),
background: [
colorName,
'bg',
].join('-'),
foregroundInverse: [
colorName,
'inverse',
'fg',
].join('-'),
backgroundInverse: [
colorName,
'inverse',
'bg',
].join('-'),
};
}