UNPKG

@lithiumjs/ngx-material-theming

Version:

Dynamic and customizable Angular Material theming made easy.

227 lines (221 loc) 10.8 kB
import chroma from 'chroma-js'; import darkThemeTemplate from '@lithiumjs/ngx-material-theming-templates/standard-light-theme.json'; // @dynamic class ThemeLoader { static { this.THEME_NAME_PARSER = /\[li-theme="([^\]\s]+)"\]/; } /** * @description Loads the given compiled theme in for use. * @param compiledThemeData The pre-compiled theme data. * @param labelElement [Optional] If `true`, will attach a `data-theme-name` attribute to the `<style>` element set to the theme's name. */ static loadCompiled(compiledThemeData, labelElement) { const element = document.createElement("style"); element.type = "text/css"; if (labelElement === undefined ? true : labelElement) { const nameParse = this.THEME_NAME_PARSER.exec(compiledThemeData); if (nameParse && nameParse.length > 1) { element.setAttribute("data-theme-name", "" + nameParse?.[1]); } } element.innerHTML = compiledThemeData; this.getHeadElement().appendChild(element); return element; } /** * @description Unloads the given compiled theme from the DOM. * @param name The name of the theme to unload. */ static unloadCompiled(name) { const head = this.getHeadElement(); head.querySelectorAll(`style[data-theme-name=${name}]`) .forEach(style => head.removeChild(style)); } static getHeadElement() { const heads = document.getElementsByTagName("head"); if (!heads || heads.length === 0) { const head = document.createElement("head"); document.appendChild(head); return head; } else { return heads[0]; } } } var ThemeTemplateOptions; (function (ThemeTemplateOptions) { ThemeTemplateOptions.defaultValues = { nameMatcher: /%theme-name%/g, primaryHexMatcher: /#a099(\d{2})/g, primaryRgbaMatcher: /rgba\s?\(\s?160\s?,\s?153\s?,\s?(\d+)\s?,\s?([\d.]+)\s?\)/g, accentHexMatcher: /#b099(\d{2})/g, accentRgbaMatcher: /rgba\s?\(\s?176\s?,\s?153\s?,\s?(\d+)\s?,\s?([\d.]+)\s?\)/g, warnHexMatcher: /#c099(\d{2})/g, warnRgbaMatcher: /rgba\s?\(\s?192\s?,\s?153\s?,\s?(\d+)\s?,\s?([\d.]+)\s?\)/g, primaryContrastHexMatcher: /#ac99(\d{2})/g, primaryContrastRgbaMatcher: /rgba\s?\(\s?172\s?,\s?153\s?,\s?(\d+)\s?,\s?([\d.]+)\s?\)/g, accentContrastHexMatcher: /#bc99(\d{2})/g, accentContrastRgbaMatcher: /rgba\s?\(\s?188\s?,\s?153\s?,\s?(\d+)\s?,\s?([\d.]+)\s?\)/g, warnContrastHexMatcher: /#cc99(\d{2})/g, warnContrastRgbaMatcher: /rgba\s?\(\s?204\s?,\s?153\s?,\s?(\d+)\s?,\s?([\d.]+)\s?\)/g }; })(ThemeTemplateOptions || (ThemeTemplateOptions = {})); class ThemeCreator { /** * @description Creates a new theme from the given template theme CSS and theme palettes and loads it for use with the given name. * @param options The options to use to create the new theme. * @return The compiled theme CSS. */ static createFromTemplate(options) { // Generate the theme data from the given template data: let themeData = options.templateData; let templateOptions = Object.assign({}, ThemeTemplateOptions.defaultValues, options.templateOptions || {}); themeData = themeData.replace(templateOptions.nameMatcher, options.name); themeData = this.substituteThemeHex(themeData, templateOptions.primaryHexMatcher, options.primaryPalette); themeData = this.substituteThemeHex(themeData, templateOptions.accentHexMatcher, options.accentPalette); themeData = this.substituteThemeHex(themeData, templateOptions.warnHexMatcher, options.warnPalette); themeData = this.substituteThemeRgba(themeData, templateOptions.primaryRgbaMatcher, options.primaryPalette); themeData = this.substituteThemeRgba(themeData, templateOptions.accentRgbaMatcher, options.accentPalette); themeData = this.substituteThemeRgba(themeData, templateOptions.warnRgbaMatcher, options.warnPalette); themeData = this.substituteThemeHex(themeData, templateOptions.primaryContrastHexMatcher, options.primaryPalette.contrast); themeData = this.substituteThemeHex(themeData, templateOptions.accentContrastHexMatcher, options.accentPalette.contrast); themeData = this.substituteThemeHex(themeData, templateOptions.warnContrastHexMatcher, options.warnPalette.contrast); themeData = this.substituteThemeRgba(themeData, templateOptions.primaryContrastRgbaMatcher, options.primaryPalette.contrast); themeData = this.substituteThemeRgba(themeData, templateOptions.accentContrastRgbaMatcher, options.accentPalette.contrast); themeData = this.substituteThemeRgba(themeData, templateOptions.warnContrastRgbaMatcher, options.warnPalette.contrast); const themeElement = ThemeLoader.loadCompiled(themeData); // Store the color info for external programmatic use themeElement.setAttribute("data-color-primary", options.primaryPalette[500]); themeElement.setAttribute("data-color-accent", options.accentPalette[500]); themeElement.setAttribute("data-color-warn", options.warnPalette[500]); return themeData; } static substituteThemeHex(themeData, matcher, palette) { return themeData.replace(matcher, (_match, $1) => palette[this.hexOffset($1)]); } static substituteThemeRgba(themeData, matcher, palette) { return themeData.replace(matcher, (_match, $1, $2) => { return `rgba(${palette[this.rgbaOffset($1)]},${this.rgbaAlpha($2)})`; }); } /** @description Compute the palette offset from an associated hex color regex match. */ static hexOffset(match) { match = match.toLowerCase(); return String(Number.parseInt(`${match}${match.startsWith("a") ? "00" : "0"}`)); } /** @description Compute the palette offset from an associated rgba color regex match. */ static rgbaOffset(match) { const decOffset = Number.parseInt(match); return this.hexOffset(decOffset.toString(16)); } /** @description Compute the alpha channel from an associated rgba alpha regex match. */ static rgbaAlpha(match) { return Number.parseFloat(match); } } class ThemeGenerator { /** * @see ThemeCreator.createFromTemplate */ static createFromTemplate(options) { return ThemeCreator.createFromTemplate(options); } /** * @description Creates a new theme from the given theme palettes and loads it for use with the given name. * @param options The options to use to create the new theme. * @return The compiled theme. */ static create(options) { return this.createFromTemplate({ templateData: this.loadStandardThemeTemplate(!!options.isDark), ...options }); } /** * @description Computes a Material color palette for the given `baseColor`. * @param baseColor The color that the palette will be based off of. * @requires chroma-js */ static createPalette(baseColor) { const basePalette = { "50": this.paletteColor(baseColor, "50"), "100": this.paletteColor(baseColor, "100"), "200": this.paletteColor(baseColor, "200"), "300": this.paletteColor(baseColor, "300"), "400": this.paletteColor(baseColor, "400"), "500": this.paletteColor(baseColor, "500"), "600": this.paletteColor(baseColor, "600"), "700": this.paletteColor(baseColor, "700"), "800": this.paletteColor(baseColor, "800"), "900": this.paletteColor(baseColor, "900"), "A100": this.paletteColor(baseColor, "A100"), "A200": this.paletteColor(baseColor, "A200"), "A400": this.paletteColor(baseColor, "A400"), "A700": this.paletteColor(baseColor, "A700"), }; return Object.assign(basePalette, { contrast: { "50": this.contrastColor(basePalette["50"]), "100": this.contrastColor(basePalette["100"]), "200": this.contrastColor(basePalette["200"]), "300": this.contrastColor(basePalette["300"]), "400": this.contrastColor(basePalette["400"]), "500": this.contrastColor(basePalette["500"]), "600": this.contrastColor(basePalette["600"]), "700": this.contrastColor(basePalette["700"]), "800": this.contrastColor(basePalette["800"]), "900": this.contrastColor(basePalette["900"]), "A100": this.contrastColor(basePalette["A100"]), "A200": this.contrastColor(basePalette["A200"]), "A400": this.contrastColor(basePalette["A400"]), "A700": this.contrastColor(basePalette["A700"]), } }); } /** * @description Computes a color for a Material theme palette using `baseColor` and the given `paletteOffset`. * @param baseColor The base color. * @param paletteOffset The offset in the palette. * @return The new color to be used for the given palette offset. * @requires chroma-js */ static paletteColor(baseColor, paletteOffset) { const graduation = 0.3; const aColor = paletteOffset.startsWith("A"); const numericOffset = Number.parseInt(aColor ? paletteOffset.slice(1) : paletteOffset); const offset = (numericOffset < 500) ? (500 - numericOffset) : (numericOffset - 500); const graduatedOffset = 0.2 + (offset * 0.01 * graduation); // Darken or lighten the base color based on the palette offset number if (numericOffset < 500) { return chroma(baseColor).brighten(graduatedOffset ** 2).hex(); } else if (numericOffset > 500) { return chroma(baseColor).darken(graduatedOffset ** 2).hex(); } else { return baseColor; } } /** * @description Computes an appropriate contrast color (either black or white) that contrasts the given `color`. * @param color The color to be contrasted. * @return The contrast color. * @requires chroma-js */ static contrastColor(color) { if (chroma(color).luminance() < 0.45) { return "white"; } else { return "black"; } } static loadStandardThemeTemplate(isDark) { return isDark ? darkThemeTemplate.data : darkThemeTemplate.data; } } /** * Generated bundle index. Do not edit. */ export { ThemeGenerator }; //# sourceMappingURL=lithiumjs-ngx-material-theming-dynamic.mjs.map