UNPKG

@amaui/style

Version:
437 lines (355 loc) 14.4 kB
import _defineProperty from "@babel/runtime/helpers/defineProperty"; import copy from '@amaui/utils/copy'; import hash from '@amaui/utils/hash'; import isEnvironment from '@amaui/utils/isEnvironment'; import getEnvironment from '@amaui/utils/getEnvironment'; import merge from '@amaui/utils/merge'; import AmauiStyleRule from './AmauiStyleRule'; import { dynamic, getID, is } from './utils'; const env = getEnvironment(); const optionsDefault = { style: { attributes: {} }, rule: { sort: true, prefix: true, rtl: true } }; class AmauiStyleSheet { constructor(value) { var _this = this; let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : copy(optionsDefault); _defineProperty(this, "version", 'static'); _defineProperty(this, "mode", 'regular'); _defineProperty(this, "pure", false); _defineProperty(this, "priority", 'upper'); _defineProperty(this, "status", 'idle'); _defineProperty(this, "props_", {}); _defineProperty(this, "values", { css: '' }); _defineProperty(this, "rules", []); _defineProperty(this, "names", { classNames: {}, classes: {}, keyframes: {}, styles: function () { const value = []; for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } args.forEach(arg => { if (_this.names.classes[arg]) value.push(_this.names.classes[arg]); }); return value.join(' '); } }); this.value = value; this.options = options; this.options = merge(options, optionsDefault, { copy: true }); this.init(); } get props() { return this.props_; } set props(props) { if (this.propsAreNew(props)) { this.props_ = copy(props); // Update if new props are set this.updateProps(); } } get response() { // Response this.values.css = ""; this.rules.filter(rule => !rule.value.ref).forEach(rule => { const css = rule.value.css; if (css) { this.values.css += "\n".concat(css, "\n"); } }); return this.values; } get css() { return this.response.css; } get sort() { // Sort // Native sort based on levels first // for making, updating & refs this.rules.sort((a, b) => { if (a.value.level === b.value.level) return 0; return a.value.level < b.value.level ? -1 : 1; }); // and then based on pure // pure rules have lower priority, are on top // more specific rules are on bottom have higher specificity this.rules.sort((a, b) => { if (a.value.pure && !b.value.pure) return -1; if (b.value.pure && !a.value.pure) return 1; return 0; }); return this.rules; } init() { var _this$amauiStyleSheet; // Options this.version = this.options.version || this.version; this.mode = this.options.mode || this.mode; this.pure = this.options.pure || this.pure; this.priority = this.options.priority || this.priority; this.amauiTheme = this.options.amauiTheme; this.amauiStyleSheetManager = this.options.amauiStyleSheetManager; this.amauiStyle = this.options.amauiStyle; this.props = this.options.props; this.id = getID(); // Inherits first from amauiStyle this.mode = this.amauiStyle.mode || this.mode; // Reset rules this.rules = []; // If value is an object if (is('object', this.value)) { // Sort all properties so at-rules are at the start (ie. @keyframes for animation makeSelector value) // but @media rules go at the bottom const props = Object.keys(this.value).sort((a, b) => { if (a.indexOf('@keyframes') > -1) return -1; if (b.indexOf('@') > -1) return 1; return 0; }); const ignore = ['@pure', '@p']; // Make an AmauiStyleRule for all lvl 0 props const propsAll = props.filter(prop => ignore.indexOf(prop) === -1).flatMap(prop => is('array', this.value[prop]) ? this.value[prop].map(item => ({ property: prop, value: item })) : { property: prop, value: this.value[prop] }); propsAll.forEach((item, index) => this.makeRule(item.property, item.value, { index })); // Pure const pure = { ...(this.value['@p'] || {}), ...(this.value['@pure'] || {}) }; const pureAll = Object.keys(pure).flatMap(prop => ({ property: prop, value: pure[prop] })); pureAll.forEach((item, index) => this.makeRule(item.property, item.value, { index: props.length + index, pure: true })); // Sort this.sort; // Make selectors // on init so they are // available on init in node for // critical css extraction this.rules.forEach(rule => { // Update values rule.value.updateValues(false); // Update owned rules css // to use for allCss and hash value rule.value.rules_owned.filter(rule_ => rule_ instanceof AmauiStyleRule).forEach(rule_ => rule_.updateValues()); // Make selectors rule.value.makeSelector(); }); } // Add to amauiStyle and amauiStyleSheetManager if (this.amauiStyleSheetManager) (_this$amauiStyleSheet = this.amauiStyleSheetManager.sheets[this.version]) === null || _this$amauiStyleSheet === void 0 ? void 0 : _this$amauiStyleSheet.push(this); if (this.amauiStyleSheetManager && this.amauiStyleSheetManager.options.amaui_style_cache) this.amauiStyle.sheets.push(this); // Update inited status this.status = 'inited'; } addRule(value, property_) { let add = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; const isDynamic = dynamic(value); if (value !== undefined && (this.version === 'static' && !isDynamic || this.version === 'dynamic' && isDynamic)) { let property = property_ !== undefined ? property_ : env.amaui_methods.makeName.next().value; const props = is('object', this.value) && Object.keys(this.value) || []; if (!!props.length) while (props.indexOf(property) > -1) property = env.amaui_methods.makeName.next().value; const isPure = ['@pure', '@p']; if (isPure.indexOf(property) > -1 && is('object', value)) { Object.keys(value).forEach(item => this.makeRule(item, value[item])); } else { const rule = this.makeRule(property, value); // Add if (add) rule.add(); if (rule.status === 'active') { const response = { className: rule.className, classNames: rule.classNames, keyframeName: rule.keyframesName }; return response; } } } } add(props) { // Update props if (props !== undefined) this.props = props; if (this.status !== 'active' && !!this.rules.length && this.rules.some(rule => rule.value && !!rule.value.rules.length)) { // If in browser only if (isEnvironment('browser')) { var _this$options$style; // Make a style tag const attributes = { element: { type: 'text/css', id: 'a' + this.id }, data: { ...(((_this$options$style = this.options.style) === null || _this$options$style === void 0 ? void 0 : _this$options$style.attributes) || {}), amaui: true, mode: this.mode, pure: this.pure, version: this.version, name: this.options.name } }; this.element = this.amauiStyle.renderer.make(attributes); // Add to the DOM this.amauiStyle.renderer.add(this.element, this.priority, attributes); // Add this.rules.filter(item => !item.value.ref).forEach(item => { item.value.add(); }); // Make css // Only if it's not a ref rule // this.rules.filter(item => !item.value.ref).forEach(item => item.value.addRuleToCss()); this.element.innerHTML = this.response.css; // Make a sheet ref this.sheet = this.element.sheet; // Move through rules this.rules.forEach(rule => rule.value.addRuleRef()); // Update active status this.status = 'active'; this.amauiStyle.subscriptions.sheet.add.emit(this); } // Node only make names else { this.rules.filter(item => !item.value.ref).forEach(item => { // Add item.value.add(); }); } // Dom this.domElementForTesting = isEnvironment('browser') && window.document.createElement('div'); } } update(value) { if (is('object', value)) { // Update active status if (this.status === 'remove') this.status = 'active'; // Update all the items to pure if (this.pure) Object.keys(value).forEach(item => { if (is('object', value[item])) value[item]['@p'] = true; }); const properties = { add: [], update: [], remove: [] }; const items = { previous: this.rules, new: [] }; const pure = { ...(value['@pure'] || {}), ...(value['@p'] || {}) }; // Props Object.keys(value).filter(item => ['@pure', '@p'].indexOf(item) === -1).forEach(prop => { if (is('array', value[prop])) value[prop].forEach(item => items.new.push({ property: prop, value: item, parents: prop }));else items.new.push({ property: prop, value: value[prop], parents: prop }); }); // Pure Object.keys(pure).forEach(prop => { items.new.push({ property: prop, value: pure[prop], parents: prop }); }); // Extract any & ref rules from new and add 'em to new const add = []; const refValues = (item, parents_) => { if (is('object', item)) Object.keys(item).forEach(key => { if (key !== null && key !== void 0 && key.includes('&')) add.push({ property: key, value: item[key], parents: parents_ }); refValues(item[key], parents_ + ' ' + key); }); }; items.new.forEach(item => refValues(item.value, item.property)); items.new.push(...add); const parents = item => { const parents_ = item.value.parents.filter(item_ => !(item_ instanceof AmauiStyleSheet)); return parents_.map(item_ => item_.property).join(' ') || item.value.property; }; // To update, add items.new.forEach(itemNew => { const previouses = items.previous.filter(itemPrevious => (itemPrevious.value.pure === !!itemNew.value['@pure'] || itemNew.value['@p']) && itemPrevious.property === itemNew.property && parents(itemPrevious) === itemNew.parents); // Add or update if (!previouses.length) properties.add.push(itemNew);else if (previouses.some(item => parents(item) === itemNew.parents && hash(item.value.values.value) !== hash(itemNew.value))) properties.update.push(itemNew); }); // To remove items.previous.forEach(itemPrevious => { const newItem = items.new.find(itemNew => itemPrevious.value.pure === !!(itemNew.value['@pure'] || itemNew.value['@p']) && itemPrevious.property === itemNew.property && parents(itemPrevious) === itemNew.parents); // Remove if (!newItem) properties.remove.push(itemPrevious); }); // Activity Object.keys(properties).forEach(activity => { // Activity items properties[activity].forEach(item => { const rule = this.rules.find(rule_ => rule_ === item || rule_.value.pure === !!(item.value['@pure'] || item.value['@p']) && rule_.value.property === item.property && item.parents === parents(rule_)); switch (activity) { case 'add': this.addRule(item.value, item.property); break; case 'remove': if (rule) rule.value.remove(); break; case 'update': if (rule) rule.value.update(item.value); break; default: break; } }); }); this.amauiStyle.subscriptions.sheet.update.emit(this); } } remove() { // Remove all the rules const rules = this.rules.map(item => item.value); rules.forEach(rule => rule.remove()); // Remove the style tag, only if all the rules are removed if (!this.rules.length) { var _this$element; if (is('function', (_this$element = this.element) === null || _this$element === void 0 ? void 0 : _this$element.remove)) this.amauiStyle.renderer.remove(this.element); // Remove from amauistyle let index = this.amauiStyle.sheets.findIndex(sheet => sheet.id === this.id); if (index > -1) this.amauiStyle.sheets.splice(index, 1); // Remove from amauiStyleSheetManager index = this.amauiStyleSheetManager.sheets[this.version].findIndex(sheet => sheet.id === this.id); if (index > -1) this.amauiStyleSheetManager.sheets[this.version].splice(index, 1); // Update idle status this.status = 'idle'; this.element = undefined; this.sheet = undefined; this.amauiStyle.subscriptions.sheet.remove.emit(this); } else { // Update remove status this.status = 'remove'; } } updateProps() { this.rules.forEach(rule => rule.value.updateProps()); this.amauiStyle.subscriptions.sheet.update_props.emit(this); } propsAreNew(props) { return (props && Object.keys(props).reduce((result, item) => result += item + String(props[item]), '')) !== (this.props && Object.keys(this.props).reduce((result, item) => result += item + String(this.props[item]), '')); } makeRule(property, value) { let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : { index: this.rules.length, pure: false }; // Pre this.amauiStyle.subscriptions.rule.pre.emit(); const rule = AmauiStyleRule.make(value, property, { mode: 'regular', version: 'property', pure: options.pure !== undefined ? options.pure : this.pure, index: options.index, owner: this, parents: [this], amauiStyleSheet: this, amauiStyle: this.amauiStyle }); // Post this.amauiStyle.subscriptions.rule.post.emit(rule); return rule; } } export default AmauiStyleSheet;