@lithiumjs/ngx-material-theming
Version:
Dynamic and customizable Angular Material theming made easy.
195 lines (189 loc) • 10.6 kB
JavaScript
import { __decorate } from 'tslib';
import * as i0 from '@angular/core';
import { forwardRef, Input, SkipSelf, Optional, Inject, Directive } from '@angular/core';
import * as i2 from '@lithiumjs/angular';
import { createDirectiveState, AutoPush, stateTokenFor, OnInit, OnDestroy } from '@lithiumjs/angular';
import { combineLatest } from 'rxjs';
import { filter } from 'rxjs/operators';
import * as i1 from '@angular/cdk/overlay';
const DEFAULT_THEME_NAME = "default";
const STATE_PROVIDER = createDirectiveState(forwardRef(() => ThemeContainer));
class ThemeContainer {
constructor(overlayContainer, cdRef, parentThemeContainer, stateRef) {
this.stateRef = stateRef;
this.theme = DEFAULT_THEME_NAME;
this.active = true;
this.manageOverlay = true;
this.theme$ = this.stateRef.get("theme");
this.active$ = this.stateRef.get("active");
this.manageOverlay$ = this.stateRef.get("manageOverlay");
let wasManagingOverlay = false;
AutoPush.enable(this, cdRef);
// Manage the overlay automatically if this is the root theme container and `manageOverlay` hasn't been defined
this.onInit$.pipe(filter(() => this.manageOverlay === undefined)).subscribe(() => this.manageOverlay = !parentThemeContainer && !overlayContainer.getContainerElement().getAttribute("li-theme"));
// Remove the `theme` overlay attribute if this container is being destroyed and is still managing it
this.onDestroy$.pipe(filter(() => this.manageOverlay && this.active)).subscribe(() => overlayContainer.getContainerElement().removeAttribute("li-theme"));
// Update the overlay if the state changes and we're managing the overlay
combineLatest(stateRef.getAll("active", "theme", "manageOverlay")).subscribe(([active, theme, manageOverlay]) => {
const overlay = overlayContainer.getContainerElement();
if (manageOverlay && active) {
overlay.setAttribute("li-theme", theme);
wasManagingOverlay = true;
}
else if (wasManagingOverlay) {
overlay.removeAttribute("li-theme");
wasManagingOverlay = false;
}
});
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: ThemeContainer, deps: [{ token: i1.OverlayContainer }, { token: i0.ChangeDetectorRef }, { token: ThemeContainer, optional: true, skipSelf: true }, { token: stateTokenFor(STATE_PROVIDER) }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.0", type: ThemeContainer, isStandalone: true, selector: "li-theme-container", inputs: { theme: "theme", active: "active", manageOverlay: "manageOverlay" }, host: { properties: { "attr.li-theme": "theme", "attr.active": "active" } }, providers: [STATE_PROVIDER], ngImport: i0 }); }
}
__decorate([
OnInit()
], ThemeContainer.prototype, "onInit$", void 0);
__decorate([
OnDestroy()
], ThemeContainer.prototype, "onDestroy$", void 0);
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: ThemeContainer, decorators: [{
type: Directive,
args: [{
selector: "li-theme-container",
providers: [STATE_PROVIDER],
host: {
'[attr.li-theme]': 'theme',
'[attr.active]': 'active'
}
}]
}], ctorParameters: () => [{ type: i1.OverlayContainer }, { type: i0.ChangeDetectorRef }, { type: ThemeContainer, decorators: [{
type: SkipSelf
}, {
type: Optional
}] }, { type: i2.DirectiveStateRef, decorators: [{
type: Inject,
args: [stateTokenFor(STATE_PROVIDER)]
}] }], propDecorators: { onInit$: [], onDestroy$: [], theme: [{
type: Input
}], active: [{
type: Input
}], manageOverlay: [{
type: Input
}] } });
// @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);
}
}
/**
* Generated bundle index. Do not edit.
*/
export { DEFAULT_THEME_NAME, ThemeContainer, ThemeCreator, ThemeLoader, ThemeTemplateOptions };
//# sourceMappingURL=lithiumjs-ngx-material-theming.mjs.map