tw-themes
Version:
powerful tailwind color themes (dynamically selectable at run-time)
297 lines (248 loc) • 12.7 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = initTwThemes;
var _colors = _interopRequireDefault(require("tailwindcss/colors"));
var _check = _interopRequireDefault(require("./util/check.js"));
var _typeCheck = require("./util/typeCheck");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
const prefix = 'twt';
const shades = ['50', '100', '200', '300', '400', '500', '600', '700', '800', '900'];
const runtimeDefault = 'runtimeDefault';
function initTwThemes(schema, themes, initialThemeName = runtimeDefault, initialInvertShade = false) {
const _schema = schema;
const _themes = themes;
let _activeThemeName;
let _activeInvertShade;
const checkParam = _check.default.prefix('initTwThemes() parameter violation: ');
checkParam(schema, 'schema is required');
checkParam((0, _typeCheck.isArray)(schema), 'schema must be an array of strings (context color names)');
checkParam(themes, 'themes is required');
checkParam((0, _typeCheck.isPlainObject)(themes), 'themes must be a JSON structure');
const _totalContextColors = schema.length;
const _schemaStruct = schema.reduce((accum, contextColor) => {
if ((0, _typeCheck.isString)(contextColor)) {
checkParam(!accum[contextColor], `schema contains duplicate contextColor: ${contextColor}`);
accum[contextColor] = {
multiColorViaShades: false
};
} else if ((0, _typeCheck.isArray)(contextColor)) {
checkParam(contextColor.length === 1, `schema element for shaded contextColor must be a single element "inner" array, NOT: ${contextColor.length} element(s) ... EX: ['primary', 'secondary', ['error']]`);
checkParam((0, _typeCheck.isString)(contextColor[0]), `schema element for shaded contextColor must be a single element "inner" array of type string, NOT: ${contextColor[0]} ... EX: ['primary', 'secondary', ['error']]`);
checkParam(!accum[contextColor[0]], `schema contains duplicate contextColor: ${contextColor[0]}`);
accum[contextColor[0]] = {
multiColorViaShades: true
};
} else {
checkParam(false, `invalid schema element: ${contextColor} ... expecting a string -or- a string wrapped in an "inner" array ... EX: ['primary', 'secondary', ['error']]`);
}
return accum;
}, {});
checkParam(schema.length > 0, `schema must contain at least one context color`);
const _themesArr = Object.entries(themes).map(([themeName, themeStruct]) => {
const checkWithTheme = checkParam.prefix(`theme: '${themeName}' `);
checkWithTheme((0, _typeCheck.isPlainObject)(themeStruct), `must reference a JSON structure`);
themeStruct.themeName = themeName;
checkWithTheme(themeStruct.contextColors, `must contain a contextColors property`);
checkWithTheme((0, _typeCheck.isPlainObject)(themeStruct.contextColors), `contextColors field must reference a JSON structure`);
const resolvedRealColors = themeStruct.resolvedRealColors = {};
let numContextColorsDefined = 0;
Object.entries(themeStruct.contextColors).forEach(([contextColor, realColor]) => {
const checkWithContextColor = checkWithTheme.prefix(`contextColor: '${contextColor}' `);
const realColorStruct = resolvedRealColors[contextColor] = {};
checkWithContextColor((0, _typeCheck.isString)(realColor), `must reference a string-based realColor`);
const checkWithRealColor = checkWithContextColor.prefix(`realColor: '${realColor}' `);
realColor = realColor.trim();
checkWithRealColor(_schemaStruct[contextColor], `the contextColor is NOT defined in the schema`);
numContextColorsDefined++;
const [colorName, shade, tooManyDashes] = realColor.split('-');
const checkWithInvalidRealColor = checkWithRealColor.prefix(`invalid realColor: `);
if (tooManyDashes) {
checkWithInvalidRealColor(false, `only a single suffix dash is supported (for a color shade)`);
} else if (shade) {
checkWithInvalidRealColor(_schemaStruct[contextColor].multiColorViaShades === false, `references a single tailwind shaded color (with a dash -), but the schema requires a multi-color shaded context color (without a dash)`);
const twColor = _colors.default[colorName];
checkWithInvalidRealColor(twColor, `references an invalid tailwind color ... ${colorName} does NOT exist`);
const twColorShade = twColor[shade];
checkWithInvalidRealColor(twColorShade, `references an invalid tailwind color shade ... ${shade} does NOT exist`);
realColorStruct.twColorName = colorName;
realColorStruct.twColorShade = shade;
} else if (colorName) {
const twColor = _colors.default[colorName];
if (!twColor) {
checkWithInvalidRealColor(_schemaStruct[contextColor].multiColorViaShades === false, `references a single CSS color: '${colorName}', but the schema requires a multi-color shaded context color (which can only be supplied by a tailwind color)`);
realColorStruct.customColor = colorName;
} else if ((0, _typeCheck.isString)(twColor)) {
checkWithInvalidRealColor(_schemaStruct[contextColor].multiColorViaShades === false, `references a single tailwind color (black/white), but the schema requires a multi-color shaded context color`);
realColorStruct.twColorName = colorName;
realColorStruct.twColorShade = 'dummy-black-white';
} else {
checkWithInvalidRealColor(_schemaStruct[contextColor].multiColorViaShades === true, `references multiple tailwind shaded colors (without a dash -), but the schema requires a single-color non-shaded context color (with a dash)`);
realColorStruct.twColorName = colorName;
}
} else {
checkWithInvalidRealColor(false, `the realColor has NO content`);
}
});
if (numContextColorsDefined !== schema.length) {
const themeContextColors = Object.keys(themeStruct.contextColors);
const missingContextColors = schema.filter(e => !themeContextColors.includes(e));
checkWithTheme(false, `theme is missing the following context color definitions: ${missingContextColors}`);
}
return themeStruct;
});
checkParam(_themesArr.length > 0, `themes must contain at least one theme`);
if (initialThemeName === runtimeDefault) {
initialThemeName = _themesArr[0].themeName;
}
checkParam((0, _typeCheck.isString)(initialThemeName), `initialThemeName (when supplied) must be a string`);
checkParam(_themes[initialThemeName], `supplied initialThemeName: '${initialThemeName}' IS NOT defined in themes`);
checkParam((0, _typeCheck.isBoolean)(initialInvertShade), `initialInvertShade (when supplied) must be a boolean`);
function getRealTWColor(twColorName, shade = '500') {
const twColor = _colors.default[twColorName];
const twSingleColor = (0, _typeCheck.isString)(twColor) ? twColor : undefined;
if (_activeInvertShade) {
if (twSingleColor) {
const realColor = invertTWSingleColors[twSingleColor];
if (!realColor) {
console.warn(`tw-themes: UNEXPECTED CONDITION in getRealTWColor(twColorName: '${twColorName}', shade: '${shade}) when inverting color ` + `... expecting to invert black/white, but there is apparently an additional tailwind single color in the mix ` + '... PUNT and use the un-inverted single color');
return twSingleColor;
}
return realColor;
} else {
const realColor = twColor[invertTWColorShades[shade]];
return realColor;
}
} else {
const realColor = twSingleColor || twColor[shade];
return realColor;
}
}
if (typeof window !== 'undefined') {
window.addEventListener('load', e => {
activateTheme({
themeName: initialThemeName,
invertShade: initialInvertShade
});
});
}
function activateTheme(namedParams = {}) {
const checkParam = _check.default.prefix('activateTheme() parameter violation: ');
checkParam((0, _typeCheck.isPlainObject)(namedParams), `uses named parameters (check the API)`);
let {
themeName = runtimeDefault,
invertShade = runtimeDefault,
...unknownNamedArgs
} = namedParams;
if (themeName === runtimeDefault) {
themeName = _activeThemeName;
}
checkParam((0, _typeCheck.isString)(themeName), `themeName (when supplied) must be a string`);
checkParam(_themes[themeName], `supplied themeName: '${themeName}' IS NOT defined in themes`);
if (invertShade === runtimeDefault) {
invertShade = _activeInvertShade;
}
checkParam((0, _typeCheck.isBoolean)(invertShade), `invertShade (when supplied) must be a boolean`);
const unknownArgKeys = Object.keys(unknownNamedArgs);
checkParam(unknownArgKeys.length === 0, `unrecognized named parameter(s): ${unknownArgKeys}`);
checkParam(arguments.length <= 1, `unrecognized positional parameters (only named parameters may be specified) ... ${arguments.length} positional parameters were found`);
if (themeName === _activeThemeName && invertShade === _activeInvertShade) {
return [_activeThemeName, _activeInvertShade];
}
_activeThemeName = themeName;
_activeInvertShade = invertShade;
const theme = _themes[themeName];
const style = document.body.style;
Object.entries(theme.resolvedRealColors).forEach(([contextColorName, realColorStruct]) => {
if (realColorStruct.customColor) {
style.setProperty(`--${prefix}-${contextColorName}`, realColorStruct.customColor);
} else if (realColorStruct.twColorShade) {
style.setProperty(`--${prefix}-${contextColorName}`, getRealTWColor(realColorStruct.twColorName, realColorStruct.twColorShade));
} else {
style.setProperty(`--${prefix}-${contextColorName}`, getRealTWColor(realColorStruct.twColorName));
shades.forEach(shade => {
style.setProperty(`--${prefix}-${contextColorName}-${shade}`, getRealTWColor(realColorStruct.twColorName, shade));
});
}
});
return [_activeThemeName, _activeInvertShade];
}
function activateNextTheme() {
const activeThemeIndx = _themesArr.findIndex(theme => theme.themeName === _activeThemeName);
const nextThemeIndx = (activeThemeIndx + 1) % _themesArr.length;
const nextThemeName = _themesArr[nextThemeIndx].themeName;
activateTheme({
themeName: nextThemeName
});
return _activeThemeName;
}
function activatePriorTheme() {
const activeThemeIndx = _themesArr.findIndex(theme => theme.themeName === _activeThemeName);
const priorThemeIndx = activeThemeIndx === 0 ? _themesArr.length - 1 : activeThemeIndx - 1;
const priorThemeName = _themesArr[priorThemeIndx].themeName;
activateTheme({
themeName: priorThemeName
});
return _activeThemeName;
}
function toggleInvertShade() {
activateTheme({
invertShade: !_activeInvertShade
});
return _activeInvertShade;
}
function getThemes() {
return _themesArr;
}
function getActiveThemeName() {
return _activeThemeName === undefined ? initialThemeName : _activeThemeName;
}
function getActiveInvertShade() {
return _activeInvertShade === undefined ? initialInvertShade : _activeInvertShade;
}
function colorConfig() {
const colors = {};
_schema.forEach(contextColorRef => {
const contextColorName = (0, _typeCheck.isString)(contextColorRef) ? contextColorRef : contextColorRef[0];
const contextColorStruct = _schemaStruct[contextColorName];
if (contextColorStruct.multiColorViaShades) {
const colorNode = colors[contextColorName] = {
DEFAULT: `var(--${prefix}-${contextColorName})`
};
shades.forEach(shade => {
colorNode[shade] = `var(--${prefix}-${contextColorName}-${shade})`;
});
} else {
colors[contextColorName] = `var(--${prefix}-${contextColorName})`;
}
});
return colors;
}
return {
activateTheme,
activateNextTheme,
activatePriorTheme,
toggleInvertShade,
getThemes,
getActiveThemeName,
getActiveInvertShade,
colorConfig
};
}
const invertTWColorShades = {
"50": "900",
"100": "900",
"200": "800",
"300": "700",
"400": "600",
"500": "500",
"600": "400",
"700": "500",
"800": "600",
"900": "100"
};
const invertTWSingleColors = {
[_colors.default.black]: _colors.default.white,
[_colors.default.white]: _colors.default.black
};