UNPKG

@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
// 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