glam
Version:
inline css for your jsx
141 lines (122 loc) • 3.8 kB
JavaScript
// @flow
// import type { AST } from './types';
import { createMarkupForStyles } from './CSSPropertyOperations';
const isBrowser = typeof window !== 'undefined';
import { prefixes, fallbacks, contentWrap } from './plugins';
// a flag to enable simulation meta tags on dom nodes
// defaults to true in dev mode. recommend *not* to
// toggle often.
let canSimulate = process.env.NODE_ENV !== 'production';
// we use these flags for issuing warnings when simulate is called
// in prod / in incorrect order
let warned1 = false,
warned2 = false;
// toggles simulation activity. shouldn't be needed in most cases
export function simulations(bool: boolean = true) {
canSimulate = !!bool;
}
// takes a string, converts to lowercase, strips out nonalphanumeric.
function simple(str: string, replace: string = '') {
return str.toLowerCase().replace(/[^a-z0-9]/g, replace);
}
// from https://github.com/j2css/j2c/blob/5d381c2d721d04b54fabe6a165d587247c3087cb/src/helpers.js#L28-L61
// "Tokenizes" the selectors into parts relevant for the next function.
// Strings and comments are matched, but ignored afterwards.
// This is not a full tokenizers. It only recognizes comas, parentheses,
// strings and comments.
// regexp generated by scripts/regexps.js then trimmed by hand
var selectorTokenizer = /[(),]|"(?:\\.|[^"\n])*"|'(?:\\.|[^'\n])*'|\/\*[\s\S]*?\*\//g;
/**
* This will split a coma-separated selector list into individual selectors,
* ignoring comas in strings, comments and in :pseudo-selectors(parameter, lists).
*
* @param {string} selector
* @return {string[]}
*/
function splitSelector(selector: string): Array<string> {
if (selector.indexOf(',') === -1) {
return [selector];
}
var indices = [],
res = [],
inParen = 0,
o;
/*eslint-disable no-cond-assign*/
while ((o = selectorTokenizer.exec(selector))) {
/*eslint-enable no-cond-assign*/
switch (o[0]) {
case '(':
inParen++;
break;
case ')':
inParen--;
break;
case ',':
if (inParen) break;
indices.push(o.index);
}
}
for (o = indices.length; o--; ) {
res.unshift(selector.slice(indices[o] + 1));
selector = selector.slice(0, indices[o]);
}
res.unshift(selector);
return res;
}
function selector(id: string, path?: string = '') {
if (!id && path) {
return path.replace(/\&/g, '');
}
if (id && !path) return `.${id}`;
let x = splitSelector(path)
.map(
x =>
x.indexOf('&') >= 0
? x.replace(/\&/gm, `.${id}`) // todo - make sure each sub selector has an &
: `.${id}${x}`,
)
.join(',');
if (canSimulate && /^\&\:/.exec(path) && !/\s/.exec(path)) {
x += `,.${id}[data-simulate-${simple(path)}]`;
}
return x;
}
function toCSS(node) {
const result = prefixes(fallbacks(contentWrap(node)));
return `${result.selector}{${createMarkupForStyles(result.style)}}`;
}
function toCSSArray(id: string, parsed: Object) {
let css = [];
// plugins here
let { plain, selects, medias, supports } = parsed;
// todo - :host?
if (plain) {
css.push(toCSS({ style: plain, selector: selector(id) }));
}
if (selects) {
Object.keys(selects).forEach(key =>
css.push(toCSS({ style: selects[key], selector: selector(id, key) })),
);
}
if (medias) {
Object.keys(medias).forEach(key =>
css.push(`${key}{${toCSSArray(id, medias[key]).join('')}}`),
);
}
if (supports) {
Object.keys(supports).forEach(key =>
css.push(`${key}{${toCSSArray(id, supports[key]).join('')}}`),
);
}
return css;
}
// todo - if server side, then cache on classname
export default function generate({
className,
parsed,
}: {
className: string,
parsed: Object,
}) {
return toCSSArray(className, parsed);
}