UNPKG

glam

Version:

inline css for your jsx

228 lines (204 loc) 5.91 kB
// @flow import type { RuleGroup, AST } from './types'; // import clean from './clean'; import flatten from './flatten'; import hashify from './hash'; /**** labels ****/ // toggle for debug labels. // *shouldn't* have to mess with this manually let hasLabels = process.env.NODE_ENV !== 'production'; export function cssLabels(bool: boolean) { hasLabels = !!bool; } const prefixedPseudoSelectors = { '::placeholder': [ '::-webkit-input-placeholder', '::-moz-placeholder', '::-ms-input-placeholder', ], ':fullscreen': [ ':-webkit-full-screen', ':-moz-full-screen', ':-ms-fullscreen', ], }; function isSelector(key) { let possibles = [':', '.', '[', '>', ' '], found = false, ch = key.charAt(0); for (let i = 0; i < possibles.length; i++) { if (ch === possibles[i]) { found = true; break; } } return found || key.indexOf('&') >= 0; } // 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) { 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 joinSelectors(a, b) { let as = splitSelector(a).map(a => (!(a.indexOf('&') >= 0) ? '&' + a : a)); let bs = splitSelector(b).map(b => (!(b.indexOf('&') >= 0) ? '&' + b : b)); return bs .reduce((arr, b) => arr.concat(as.map(a => b.replace(/\&/g, a))), []) .join(','); } function joinMediaQueries(a, b) { return a ? `@media ${a.substring(6)} and ${b.substring(6)}` : b; } function isMediaQuery(key) { return key.indexOf('@media') === 0; } function isSupports(key) { return key.indexOf('@supports') === 0; } function joinSupports(a, b) { return a ? `@supports ${a.substring(9)} and ${b.substring(9)}` : b; } // mutable! modifies dest. function construct( dest: AST, { selector = '', mq = '', supp = '', inputs = {}, }: { selector?: string, mq?: string, supp?: string, inputs?: RuleGroup }, ): Object { const inputArray = !Array.isArray(inputs) ? [inputs] : flatten(inputs); inputArray.filter(x => !!x).forEach(input => { const src = input; Object.keys(src || {}).forEach(key => { if (isSelector(key)) { // todo - regex test the string to look for prefixedpseudos if (prefixedPseudoSelectors[key]) { prefixedPseudoSelectors[key].forEach(p => construct(dest, { selector: joinSelectors(selector, p), mq, supp, inputs: src[key], }), ); } construct(dest, { selector: joinSelectors(selector, key), mq, supp, inputs: src[key], }); } else if (isMediaQuery(key)) { construct(dest, { selector, mq: joinMediaQueries(mq, key), supp, inputs: src[key], }); } else if (isSupports(key)) { construct(dest, { selector, mq, supp: joinSupports(supp, key), inputs: src[key], }); } else { let _dest = dest; if (supp) { _dest[supp] = _dest[supp] || {}; _dest = _dest[supp]; } if (mq) { _dest[mq] = _dest[mq] || {}; _dest = _dest[mq]; } if (selector) { _dest[selector] = _dest[selector] || {}; _dest = _dest[selector]; } if (key === 'label') { if (hasLabels) { // concat at root of object dest.label = dest.label.concat(src.label); } } else { _dest[key] = src[key]; } } }); }); return dest; } function groupByType(style: Object): AST { // we can be sure it's not infinitely nested here let plain, selects, medias, supports; Object.keys(style).forEach(key => { if (key.indexOf('&') >= 0) { selects = selects || {}; selects[key] = style[key]; } else if (key.indexOf('@media') === 0) { medias = medias || {}; medias[key] = groupByType(style[key]); } else if (key.indexOf('@supports') === 0) { supports = supports || {}; supports[key] = groupByType(style[key]); } else if (key === 'label') { if (style.label.length > 0) { plain = plain || {}; plain.label = hasLabels ? style.label.join('.') : ''; } } else { plain = plain || {}; plain[key] = style[key]; } }); return { plain, selects, medias, supports }; } export default function parse( css: RuleGroup, ): { className: string, parsed: AST } { const parsed = groupByType(construct({ label: [] }, { inputs: css })); const className = 'css-' + hashify(parsed); return { className, parsed }; }