const DECL_REGEX = /.([^:{]+)(:[^{]+)?{([^:]+):([^}]+)}/g;
const StyletronCore = require('styletron-core');
/**
* A Styletron class for rendering styles in the browser
* @extends StyletronCore
* @packagename styletron-client
* @example
* const elements = document.getElementsByClassName('_styletron_hydrate_');
* const styletron = new StyletronClient(elements);
*/
class StyletronClient extends StyletronCore {
/**
* Create a new StyletronClient instance
* @param {NodeList|HTMLCollection|HTMLStyleElement[]} [serverStyles] - List of server style elements
* @param {object} [opts] - StyletronCore options
*/
constructor(serverStyles, opts) {
super(opts);
this.uniqueCount = 0;
this.mediaSheets = {};
if (serverStyles) {
for (let i = 0; i < serverStyles.length; i++) {
const element = serverStyles[i];
if (element.media) {
this.mediaSheets[element.media] = element;
} else {
this.mainSheet = element;
}
this.hydrateCacheFromCssString(element.textContent, element.media);
}
} else {
const styleSheet = document.createElement('style');
document.head.appendChild(styleSheet);
this.mainSheet = styleSheet;
}
}
/*
* Hydrate the cache from a css string and media string
* @param {string} css - The stylesheet css content
* @param {string} media - The stylesheet media string
*/
hydrateCacheFromCssString(css, media) {
let decl;
// {
// 1: className,
// 2: pseudo,
// 3: prop,
// 4: val
// }
while (decl = DECL_REGEX.exec(css)) {
super.incrementVirtualCount();
StyletronCore.assignDecl(this.cache, {
pseudo: decl[2],
prop: decl[3],
val: decl[4],
media
}, decl[1]);
}
}
/**
* Inject declaration into the stylesheet and return the unique class name
* @return {string} class name
* @example
* // <style id="styletron">.a{color:red}</style>
* const styletron = new StyletronClient(document.getElementsByClassName('_styletron_hydrate_'));
* styletron.injectDeclaration({prop: 'color', val: 'blue'});
* // → 'b'
* styletron.injectDeclaration({prop: 'color', val: 'red', media: '(min-width: 800px)'});
* // → 'c'
* styletron.injectDeclaration({prop: 'color', val: 'red'});
* // → 'a'
*/
injectDeclaration(decl) {
const oldCount = this.uniqueCount;
const className = super.injectDeclaration(decl);
if (oldCount !== this.uniqueCount) {
const rule = declarationToRule(className, decl);
let sheet;
if (decl.media) {
if (!this.mediaSheets[decl.media]) {
const mediaSheet = document.createElement('style');
mediaSheet.media = decl.media;
this.mediaSheets[decl.media] = mediaSheet;
this.mainSheet.parentNode.appendChild(mediaSheet);
}
sheet = this.mediaSheets[decl.media].sheet;
} else {
sheet = this.mainSheet.sheet;
}
sheet.insertRule(rule, sheet.cssRules.length);
}
return className;
}
}
module.exports = StyletronClient;
/*
* Injection helpers
*/
function declarationToRule(className, {prop, val, pseudo}) {
const decl = `${prop}:${val}`;
let selector = `.${className}`;
if (pseudo) {
selector += pseudo;
}
return `${selector}{${decl}}`;
}