UNPKG

tw-themes

Version:

powerful tailwind color themes (dynamically selectable at run-time)

297 lines (248 loc) 12.7 kB
"use strict"; 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 };