@aesthetic/core
Version:
Aesthetic is a powerful type-safe, framework agnostic, CSS-in-JS library for styling components.
552 lines (428 loc) • 15.7 kB
JavaScript
// Bundled with Packemon: https://packemon.dev
// Platform: browser, Support: stable, Format: lib
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true
});
const system = require('@aesthetic/system');
const utils = require('@aesthetic/utils');
const types = require('@aesthetic/types');
function createCacheKey(name, params) {
let key = name; // Since all other values are scalars, we can just join the values.
// This is 3x faster than JSON.stringify(), and 1.5x faster than Object.values()!
utils.objectLoop(params, value => {
key += String(value);
});
return key;
}
class Sheet {
constructor(factory, renderer) {
this.factory = void 0;
this.renderer = void 0;
this.cache = {};
this.factory = this.validateFactory(factory);
this.renderer = renderer;
}
compose(params) {
return this.factory;
}
render(engine, theme, baseParams) {
const params = {
contrast: theme.contrast,
deterministic: false,
direction: 'ltr',
scheme: theme.scheme,
theme: theme.name,
unit: 'px',
vendor: false,
...baseParams
};
const key = createCacheKey(engine.name, params);
const cache = this.cache[key];
if (cache) {
return cache;
}
const composer = this.compose(params);
const result = this.renderer(engine, composer(theme), params);
this.cache[key] = result;
return result;
}
validateFactory(factory) {
if (process.env.NODE_ENV !== "production") {
const typeOf = typeof factory;
if (typeOf !== 'function') {
throw new TypeError(`A style sheet factory function is required, found "${typeOf}".`);
}
}
return factory;
}
}
class FeatureSheet extends Sheet {
constructor(...args) {
super(...args);
this.contrastOverrides = {};
this.overrides = [];
this.schemeOverrides = {};
this.themeOverrides = {};
}
addColorSchemeOverride(scheme, factory) {
var _this$schemeOverrides;
if (process.env.NODE_ENV !== "production" && scheme !== 'light' && scheme !== 'dark') {
throw new Error('Color scheme override must be one of "light" or "dark".');
}
((_this$schemeOverrides = this.schemeOverrides)[scheme] || (_this$schemeOverrides[scheme] = [])).push(this.validateFactory(factory));
return this;
}
addContrastOverride(contrast, factory) {
var _this$contrastOverrid;
if (process.env.NODE_ENV !== "production" && contrast !== 'normal' && contrast !== 'high' && contrast !== 'low') {
throw new Error('Contrast level override must be one of "high", "low", or "normal".');
}
((_this$contrastOverrid = this.contrastOverrides)[contrast] || (_this$contrastOverrid[contrast] = [])).push(this.validateFactory(factory));
return this;
}
addOverride(factory) {
this.overrides.push(this.validateFactory(factory));
return this;
}
addThemeOverride(theme, factory) {
var _this$themeOverrides;
((_this$themeOverrides = this.themeOverrides)[theme] || (_this$themeOverrides[theme] = [])).push(this.validateFactory(factory));
return this;
}
compose(params) {
const factories = [this.factory, ...this.overrides];
if (params.scheme && this.schemeOverrides[params.scheme]) {
factories.push(...this.schemeOverrides[params.scheme]);
}
if (params.contrast && this.contrastOverrides[params.contrast]) {
factories.push(...this.contrastOverrides[params.contrast]);
}
if (params.theme && this.themeOverrides[params.theme]) {
factories.push(...this.themeOverrides[params.theme]);
}
if (factories.length === 1) {
return factories[0];
}
const composer = utils$1 => utils.deepMerge(...factories.map(factory => factory(utils$1)));
return composer;
}
}
function renderTheme(engine, theme, options) {
const resultSheet = {
root: {}
};
utils.objectLoop(theme['@font-face'], (fontFaces, fontFamily) => {
utils.arrayLoop(utils.toArray(fontFaces), fontFace => engine.renderFontFace({ ...fontFace,
fontFamily
}, options));
});
utils.arrayLoop(utils.toArray(theme['@import']), importPath => engine.renderImport(importPath, options));
utils.objectLoop(theme['@keyframes'], (keyframes, animationName) => {
engine.renderKeyframes(keyframes, animationName, options);
});
if (theme['@root']) {
resultSheet.root.result = engine.renderRuleGrouped(theme['@root'], { ...options,
type: 'global'
}).result;
}
if (theme['@variables']) {
engine.setRootVariables(theme['@variables']);
}
return resultSheet;
}
function renderComponent(engine, styles, options) {
const resultSheet = {};
const rankings = {};
utils.objectLoop(styles, (style, selector) => {
// eslint-disable-next-line no-multi-assign
const meta = resultSheet[selector] || (resultSheet[selector] = {}); // At-rule
if (selector.startsWith('@')) {
if (process.env.NODE_ENV !== "production") {
throw new SyntaxError(`At-rules may not be defined at the root of a style sheet, found "${selector}".`);
} // Rule
} else if (utils.isObject(style)) {
const result = engine.renderRule(style, { ...options,
debugName: selector,
rankings
});
meta.result = result.result;
if (result.variants.length > 0) {
const types = new Set();
utils.arrayLoop(result.variants, variant => {
utils.arrayLoop(variant.types, type => {
types.add(type.split(':')[0]);
});
});
meta.variants = result.variants;
meta.variantTypes = types;
} // Unknown
} else if (process.env.NODE_ENV !== "production") {
throw new Error(`Invalid rule for "${selector}". Must be an object (style declaration) or string (class name).`);
}
});
return resultSheet;
}
/* eslint-disable no-param-reassign, unicorn/prefer-prototype-methods */
class Aesthetic {
constructor(_options) {
this.activeDirection = void 0;
this.activeTheme = void 0;
this.globalSheetRegistry = new Map();
this.listeners = new Map();
this.options = {};
this.styleEngine = void 0;
this.themeRegistry = new system.ThemeRegistry();
this.changeDirection = (direction, propagate = true) => {
if (direction === this.activeDirection) {
return;
}
const engine = this.getEngine(); // Set the active direction
this.activeDirection = direction; // Update engine direction
engine.direction = direction;
engine.setDirection(direction); // Let consumers know about the change
if (propagate) {
this.emit('change:direction', [direction]);
}
};
this.changeTheme = (name, propagate = true) => {
if (name === this.activeTheme) {
return;
}
const theme = this.getTheme(name);
const engine = this.getEngine(); // Set the active theme
this.activeTheme = name; // Set root variables
if (this.options.rootVariables) {
engine.setRootVariables(theme.toVariables());
} // Render theme styles
const results = this.renderThemeSheet(theme); // Update engine theme
engine.setTheme(results); // Let consumers know about the change
if (propagate) {
this.emit('change:theme', [name, results]);
}
};
this.configure = options => {
Object.assign(this.options, options); // Configure the engine with itself to reapply options
if (this.styleEngine) {
this.configureEngine(this.styleEngine);
}
};
this.configureEngine = engine => {
const {
options
} = this;
if (options.customProperties) {
engine.customProperties = options.customProperties;
}
if (options.directionConverter) {
engine.directionConverter = options.directionConverter;
}
if (options.defaultUnit) {
engine.unitSuffixer = options.defaultUnit;
}
if (options.vendorPrefixer) {
engine.vendorPrefixer = options.vendorPrefixer;
}
this.styleEngine = engine;
};
this.createStyleSheet = factory => new FeatureSheet(factory, renderComponent);
this.createScopedStyleSheet = (factory, selector) => this.createStyleSheet(utils => ({
[selector !== null && selector !== void 0 ? selector : 'element']: typeof factory === 'function' ? factory(utils) : factory
}));
this.createThemeSheet = factory => new Sheet(factory, renderTheme);
this.generateResults = (args, variants, renderResult) => {
const output = [];
utils.arrayLoop(args, arg => {
if (!arg) {
return;
} // Custom results being passes
if (Array.isArray(arg)) {
output.push(...arg.filter(Boolean));
return;
}
const item = renderResult[arg];
if (item !== null && item !== void 0 && item.result) {
output.push(item === null || item === void 0 ? void 0 : item.result);
}
utils.arrayLoop(item === null || item === void 0 ? void 0 : item.variants, ({
types,
result
}) => {
if (types.every(type => variants.has(type))) {
output.push(result);
}
});
});
return output;
};
this.getActiveDirection = () => {
if (this.activeDirection) {
return this.activeDirection;
} // Detect theme from engine
const direction = this.getEngine().direction || 'ltr';
this.changeDirection(direction);
return direction;
};
this.getActiveTheme = () => {
if (this.activeTheme) {
return this.getTheme(this.activeTheme);
} // Detect theme from browser preferences
const engine = this.getEngine();
const theme = this.themeRegistry.getPreferredTheme({
matchColorScheme: engine.prefersColorScheme,
matchContrastLevel: engine.prefersContrastLevel
});
this.changeTheme(theme.name);
return theme;
};
this.getEngine = () => {
const current = typeof global !== 'undefined' && global.AESTHETIC_CUSTOM_ENGINE || this.styleEngine;
if (current) {
return current;
}
throw new Error('No style engine defined. Have you configured one with `configureEngine()`?');
};
this.getListeners = type => {
var _this$listeners$get;
const set = (_this$listeners$get = this.listeners.get(type)) !== null && _this$listeners$get !== void 0 ? _this$listeners$get : new Set();
this.listeners.set(type, set);
return set;
};
this.getTheme = name => this.themeRegistry.getTheme(name);
this.mergeStyleSheets = (...sheets) => {
if (sheets.length === 1) {
return sheets[0];
}
const mergedSheet = new FeatureSheet(sheets[0].factory, sheets[0].renderer);
utils.arrayLoop(sheets, (sheet, index) => {
if (index > 0) {
mergedSheet.addOverride(sheet.factory);
} // @ts-expect-error Allow access
utils.arrayLoop(sheet.overrides, override => {
mergedSheet.addOverride(override);
}); // @ts-expect-error Allow access
utils.objectLoop(sheet.contrastOverrides, (overrides, contrast) => {
utils.arrayLoop(overrides, override => {
mergedSheet.addContrastOverride(contrast, override);
});
}); // @ts-expect-error Allow access
utils.objectLoop(sheet.schemeOverrides, (overrides, scheme) => {
utils.arrayLoop(overrides, override => {
mergedSheet.addColorSchemeOverride(scheme, override);
});
}); // @ts-expect-error Allow access
utils.objectLoop(sheet.themeOverrides, (overrides, theme) => {
utils.arrayLoop(overrides, override => {
mergedSheet.addThemeOverride(theme, override);
});
});
});
return mergedSheet;
};
this.registerTheme = (name, theme, sheet = null, isDefault = false) => {
this.themeRegistry.register(name, theme, isDefault);
if (sheet) {
if (process.env.NODE_ENV !== "production" && !(sheet instanceof Sheet)) {
throw new TypeError('Rendering theme styles require a `Sheet` instance.');
}
this.globalSheetRegistry.set(name, sheet);
}
};
this.registerDefaultTheme = (name, theme, sheet = null) => {
this.registerTheme(name, theme, sheet, true);
};
this.renderFontFace = (fontFace, fontFamily, params) => this.getEngine().renderFontFace({
fontFamily,
...fontFace
}, params);
this.renderImport = (path, params) => this.getEngine().renderImport(path, params);
this.renderKeyframes = (keyframes, animationName, params) => this.getEngine().renderKeyframes(keyframes, animationName, params);
this.renderStyles = rule => this.getEngine().renderRule(rule, this.getRenderOptions());
this.renderStyleSheet = (sheet, params = {}) => {
if (process.env.NODE_ENV !== "production" && !(sheet instanceof Sheet)) {
throw new TypeError('Rendering component styles require a `Sheet` instance.');
}
return this.renderSheet(sheet, params.theme ? this.getTheme(params.theme) : this.getActiveTheme(), params);
};
this.renderThemeSheet = (theme, params = {}) => {
const sheet = this.globalSheetRegistry.get(theme.name);
const results = []; // Render theme CSS variables
if (utils.isDOM()) {
results.push(this.getEngine().renderRuleGrouped({
'@variables': theme.toVariables()
}, {
type: 'global'
}).result);
} // Render theme styles
if (sheet) {
var _this$renderSheet$roo;
const result = (_this$renderSheet$roo = this.renderSheet(sheet, theme, params).root) === null || _this$renderSheet$roo === void 0 ? void 0 : _this$renderSheet$roo.result;
if (result) {
results.push(result);
}
}
return results;
};
if (_options) {
this.configure(_options);
} // Must be bound manually because of method overloading
this.emit = this.emit.bind(this);
this.subscribe = this.subscribe.bind(this);
this.unsubscribe = this.unsubscribe.bind(this);
}
/**
* Change the active direction.
*/
emit(type, args) {
this.getListeners(type).forEach(listener => {
listener(...args);
});
}
/**
* Generate a list of results using the selectors of a style sheet.
* If a set is provided, it will be used to check for variants.
*/
subscribe(type, listener) {
this.getListeners(type).add(listener);
return () => {
this.unsubscribe(type, listener);
};
}
/**
* Unsubscribe from an event by name.
*/
unsubscribe(type, listener) {
this.getListeners(type).delete(listener);
}
getRenderOptions(options) {
return {
deterministic: !!this.options.deterministicClasses,
direction: this.getActiveDirection(),
vendor: !!this.options.vendorPrefixer,
...options
};
}
renderSheet(sheet, theme, params) {
return sheet.render(this.getEngine(), theme, this.getRenderOptions(params));
}
}
exports.Aesthetic = Aesthetic;
exports.FeatureSheet = FeatureSheet;
exports.Sheet = Sheet;
Object.keys(system).forEach(function (k) {
if (k !== 'default' && !exports.hasOwnProperty(k)) Object.defineProperty(exports, k, {
enumerable: true,
get: function () {
return system[k];
}
});
});
Object.keys(types).forEach(function (k) {
if (k !== 'default' && !exports.hasOwnProperty(k)) Object.defineProperty(exports, k, {
enumerable: true,
get: function () {
return types[k];
}
});
});
//# sourceMappingURL=index.js.map