@amaui/style
Version:
CSS in JS styling solution
395 lines (394 loc) • 17 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const copy_1 = __importDefault(require("@amaui/utils/copy"));
const hash_1 = __importDefault(require("@amaui/utils/hash"));
const isEnvironment_1 = __importDefault(require("@amaui/utils/isEnvironment"));
const getEnvironment_1 = __importDefault(require("@amaui/utils/getEnvironment"));
const merge_1 = __importDefault(require("@amaui/utils/merge"));
const AmauiStyleRule_1 = __importDefault(require("./AmauiStyleRule"));
const utils_1 = require("./utils");
const env = (0, getEnvironment_1.default)();
const optionsDefault = {
style: {
attributes: {},
},
rule: {
sort: true,
prefix: true,
rtl: true,
}
};
class AmauiStyleSheet {
constructor(value, options = (0, copy_1.default)(optionsDefault)) {
this.value = value;
this.options = options;
this.version = 'static';
this.mode = 'regular';
this.pure = false;
this.priority = 'upper';
this.status = 'idle';
this.props_ = {};
this.values = {
css: '',
};
this.rules = [];
this.names = {
classNames: {},
classes: {},
keyframes: {},
styles: (...args) => {
const value = [];
args.forEach(arg => {
if (this.names.classes[arg])
value.push(this.names.classes[arg]);
});
return value.join(' ');
},
};
this.options = (0, merge_1.default)(options, optionsDefault, { copy: true });
this.init();
}
get props() {
return this.props_;
}
set props(props) {
if (this.propsAreNew(props)) {
this.props_ = (0, copy_1.default)(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${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 _a;
// 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 = (0, utils_1.getID)();
// Inherits first from amauiStyle
this.mode = this.amauiStyle.mode || this.mode;
// Reset rules
this.rules = [];
// If value is an object
if ((0, utils_1.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 => (0, utils_1.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 = Object.assign(Object.assign({}, (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_1.default).forEach(rule_ => rule_.updateValues());
// Make selectors
rule.value.makeSelector();
});
}
// Add to amauiStyle and amauiStyleSheetManager
if (this.amauiStyleSheetManager)
(_a = this.amauiStyleSheetManager.sheets[this.version]) === null || _a === void 0 ? void 0 : _a.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_, add = true) {
const isDynamic = (0, utils_1.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 = ((0, utils_1.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 && (0, utils_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) {
var _a;
// 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 ((0, isEnvironment_1.default)('browser')) {
// Make a style tag
const attributes = {
element: {
type: 'text/css',
id: 'a' + this.id,
},
data: Object.assign(Object.assign({}, (((_a = this.options.style) === null || _a === void 0 ? void 0 : _a.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 = (0, isEnvironment_1.default)('browser') && window.document.createElement('div');
}
}
update(value) {
if ((0, utils_1.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 ((0, utils_1.is)('object', value[item]))
value[item]['@p'] = true;
});
const properties = {
add: [],
update: [],
remove: [],
};
const items = {
previous: this.rules,
new: [],
};
const pure = Object.assign(Object.assign({}, (value['@pure'] || {})), (value['@p'] || {}));
// Props
Object.keys(value)
.filter(item => ['@pure', '@p'].indexOf(item) === -1)
.forEach(prop => {
if ((0, utils_1.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 ((0, utils_1.is)('object', item))
Object.keys(item).forEach(key => {
if (key === null || key === void 0 ? 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 &&
(0, hash_1.default)(item.value.values.value) !== (0, hash_1.default)(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() {
var _a;
// 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) {
if ((0, utils_1.is)('function', (_a = this.element) === null || _a === void 0 ? void 0 : _a.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, options = {
index: this.rules.length,
pure: false
}) {
// Pre
this.amauiStyle.subscriptions.rule.pre.emit();
const rule = AmauiStyleRule_1.default.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;
}
}
exports.default = AmauiStyleSheet;