UNPKG

j2c

Version:

A tiny CSS in JS solution.

397 lines (356 loc) 12.7 kB
define(function () { 'use strict'; var emptyObject = {}; var emptyArray = []; var type = emptyObject.toString; var own = emptyObject.hasOwnProperty; var OBJECT = type.call(emptyObject); var ARRAY = type.call(emptyArray); var STRING = type.call(''); /*/-inline-/*/ // function cartesian(a, b, res, i, j) { // res = []; // for (j in b) if (own.call(b, j)) // for (i in a) if (own.call(a, i)) // res.push(a[i] + b[j]); // return res; // } /*/-inline-/*/ /* /-statements-/*/ function cartesian(a,b, selectorP, res, i, j) { res = [] for (j in b) if(own.call(b, j)) for (i in a) if(own.call(a, i)) res.push(concat(a[i], b[j], selectorP)) return res } function concat(a, b, selectorP) { // `b.replace(/&/g, a)` is never falsy, since the // 'a' of cartesian can't be the empty string // in selector mode. return selectorP && ( /^[-\w$]+$/.test(b) && ':-error-bad-sub-selector-' + b || /&/.test(b) && /* never falsy */ b.replace(/&/g, a) ) || a + b } function decamelize(match) { return '-' + match.toLowerCase() } /** * Handles the property:value; pairs. * * @param {array|object|string} o - the declarations. * @param {string[]} buf - the buffer in which the final style sheet is built. * @param {string} prefix - the current property or a prefix in case of nested * sub-properties. * @param {string} vendors - a list of vendor prefixes. * @Param {boolean} local - are we in @local or in @global scope. * @param {object} ns - helper functions to populate or create the @local namespace * and to @extend classes. * @param {function} ns.e - @extend helper. * @param {function} ns.l - @local helper. */ function declarations(o, buf, prefix, vendors, local, ns, /*var*/ k, v, kk) { if (o==null) return if (/\$/.test(prefix)) { for (kk in (prefix = prefix.split('$'))) if (own.call(prefix, kk)) { declarations(o, buf, prefix[kk], vendors, local, ns) } return } switch ( type.call(o = o.valueOf()) ) { case ARRAY: for (k = 0; k < o.length; k++) declarations(o[k], buf, prefix, vendors, local, ns) break case OBJECT: // prefix is falsy iif it is the empty string, which means we're at the root // of the declarations list. prefix = (prefix && prefix + '-') for (k in o) if (own.call(o, k)){ v = o[k] if (/\$/.test(k)) { for (kk in (k = k.split('$'))) if (own.call(k, kk)) declarations(v, buf, prefix + k[kk], vendors, local, ns) } else { declarations(v, buf, prefix + k, vendors, local, ns) } } break default: // prefix is falsy when it is "", which means that we're // at the top level. // `o` is then treated as a `property:value` pair. // otherwise, `prefix` is the property name, and // `o` is the value. k = prefix.replace(/_/g, '-').replace(/[A-Z]/g, decamelize) if (local && (k == 'animation-name' || k == 'animation')) { o = o.split(',').map(function (o) { return o.replace(/()(?::global\(\s*([-\w]+)\s*\)|()([-\w]+))/, ns.l) }).join(',') } if (/^animation|^transition/.test(k)) vendors = ['webkit'] // '@' in properties also triggers the *ielte7 hack // Since plugins dispatch on the /^@/ for at-rules // we swap the at for an asterisk // http://browserhacks.com/#hack-6d49e92634f26ae6d6e46b3ebc10019a k = k.replace(/^@/, '*') /*/-statements-/*/ // vendorify for (kk = 0; kk < vendors.length; kk++) buf.push('-', vendors[kk], '-', k, k ? ':': '', o, ';\n') /*/-statements-/*/ buf.push(k, k ? ':': '', o, ';\n') } } var findClass = /()(?::global\(\s*(\.[-\w]+)\s*\)|(\.)([-\w]+))/g /** * Hanldes at-rules * * @param {string} k - The at-rule name, and, if takes both parameters and a * block, the parameters. * @param {string[]} buf - the buffer in which the final style sheet is built * @param {string[]} v - Either parameters for block-less rules or their block * for the others. * @param {string} prefix - the current selector or a prefix in case of nested rules * @param {string} rawPrefix - as above, but without localization transformations * @param {string} vendors - a list of vendor prefixes * @Param {boolean} local - are we in @local or in @global scope? * @param {object} ns - helper functions to populate or create the @local namespace * and to @extend classes * @param {function} ns.e - @extend helper * @param {function} ns.l - @local helper */ function at(k, v, buf, prefix, rawPrefix, vendors, local, ns){ var kk if (/^@(?:namespace|import|charset)$/.test(k)) { if(type.call(v) == ARRAY){ for (kk = 0; kk < v.length; kk++) { buf.push(k, ' ', v[kk], ';\n') } } else { buf.push(k, ' ', v, ';\n') } } else if (/^@keyframes /.test(k)) { k = local ? k.replace( // generated by script/regexps.js /( )(?::global\(\s*([-\w]+)\s*\)|()([-\w]+))/, ns.l ) : k // add a @-webkit-keyframes block too. buf.push('@-webkit-', k.slice(1), ' {\n') sheet(v, buf, '', '', ['webkit']) buf.push('}\n') buf.push(k, ' {\n') sheet(v, buf, '', '', vendors, local, ns) buf.push('}\n') } else if (/^@extends?$/.test(k)) { /*eslint-disable no-cond-assign*/ // pick the last class to be extended while (kk = findClass.exec(rawPrefix)) k = kk[4] /*eslint-enable no-cond-assign*/ if (k == null || !local) { // we're in a @global{} block buf.push('@-error-cannot-extend-in-global-context ', JSON.stringify(rawPrefix), ';\n') return } else if (/^@extends?$/.test(k)) { // no class in the selector buf.push('@-error-no-class-to-extend-in ', JSON.stringify(rawPrefix), ';\n') return } ns.e( type.call(v) == ARRAY ? v.map(function (parent) { return parent.replace(/()(?::global\(\s*(\.[-\w]+)\s*\)|()\.([-\w]+))/, ns.l) }).join(' ') : v.replace(/()(?::global\(\s*(\.[-\w]+)\s*\)|()\.([-\w]+))/, ns.l), k ) } else if (/^@(?:font-face$|viewport$|page )/.test(k)) { sheet(v, buf, k, k, emptyArray) } else if (/^@global$/.test(k)) { sheet(v, buf, prefix, rawPrefix, vendors, 0, ns) } else if (/^@local$/.test(k)) { sheet(v, buf, prefix, rawPrefix, vendors, 1, ns) } else if (/^@(?:media |supports |document )./.test(k)) { buf.push(k, ' {\n') sheet(v, buf, prefix, rawPrefix, vendors, local, ns) buf.push('}\n') } else { buf.push('@-error-unsupported-at-rule ', JSON.stringify(k), ';\n') } } /** * Add rulesets and other CSS statements to the sheet. * * @param {array|string|object} statements - a source object or sub-object. * @param {string[]} buf - the buffer in which the final style sheet is built * @param {string} prefix - the current selector or a prefix in case of nested rules * @param {string} rawPrefix - as above, but without localization transformations * @param {string} vendors - a list of vendor prefixes * @Param {boolean} local - are we in @local or in @global scope? * @param {object} ns - helper functions to populate or create the @local namespace * and to @extend classes * @param {function} ns.e - @extend helper * @param {function} ns.l - @local helper */ function sheet(statements, buf, prefix, rawPrefix, vendors, local, ns) { var k, kk, v, inDeclaration switch (type.call(statements)) { case ARRAY: for (k = 0; k < statements.length; k++) sheet(statements[k], buf, prefix, rawPrefix, vendors, local, ns) break case OBJECT: for (k in statements) { v = statements[k] if (prefix && /^[-\w$]+$/.test(k)) { if (!inDeclaration) { inDeclaration = 1 buf.push(( prefix || '*' ), ' {\n') } declarations(v, buf, k, vendors, local, ns) } else if (/^@/.test(k)) { // Handle At-rules inDeclaration = (inDeclaration && buf.push('}\n') && 0) at(k, v, buf, prefix, rawPrefix, vendors, local, ns) } else { // selector or nested sub-selectors inDeclaration = (inDeclaration && buf.push('}\n') && 0) sheet(v, buf, (kk = /,/.test(prefix) || prefix && /,/.test(k)) ? cartesian(prefix.split(','), ( local ? k.replace( /()(?::global\(\s*(\.[-\w]+)\s*\)|(\.)([-\w]+))/g, ns.l ) : k ).split(','), prefix).join(',') : concat(prefix, ( local ? k.replace( /()(?::global\(\s*(\.[-\w]+)\s*\)|(\.)([-\w]+))/g, ns.l ) : k ), prefix), kk ? cartesian(rawPrefix.split(','), k.split(','), rawPrefix).join(',') : concat(rawPrefix, k, rawPrefix), vendors, local, ns ) } } if (inDeclaration) buf.push('}\n') break case STRING: buf.push( ( prefix || ':-error-no-selector' ) , ' {\n' ) declarations(statements, buf, '', vendors, local, ns) buf.push('}\n') } } var scope_root = '_j2c_' + Math.floor(Math.random() * 0x100000000).toString(36) + '_' + Math.floor(Math.random() * 0x100000000).toString(36) + '_' + Math.floor(Math.random() * 0x100000000).toString(36) + '_' + Math.floor(Math.random() * 0x100000000).toString(36) + '_'; var counter = 0; function j2c(res) { res = res || {} var extensions = [] function finalize(buf, i) { for (i = 0; i< extensions.length; i++) buf = extensions[i](buf) || buf return buf.join('') } res.use = function() { var args = arguments for (var i = 0; i < args.length; i++){ extensions.push(args[i]) } return res } /*/-statements-/*/ res.sheet = function(ns, statements) { if (arguments.length === 1) { statements = ns; ns = {} } var suffix = scope_root + counter++, locals = {}, k, buf = [] // pick only non-numeric keys since `(NaN != NaN) === true` for (k in ns) if (k-0 != k-0 && own.call(ns, k)) { locals[k] = ns[k] } sheet( statements, buf, '', '', emptyArray /*vendors*/, 1, // local { e: function extend(parent, child) { var nameList = locals[child] locals[child] = nameList.slice(0, nameList.lastIndexOf(' ') + 1) + parent + ' ' + nameList.slice(nameList.lastIndexOf(' ') + 1) }, l: function localize(match, space, global, dot, name) { if (global) { return space + global } if (!locals[name]) locals[name] = name + suffix return space + dot + locals[name].match(/\S+$/) } } ) /*jshint -W053 */ buf = new String(finalize(buf)) /*jshint +W053 */ for (k in locals) if (own.call(locals, k)) buf[k] = locals[k] return buf } /*/-statements-/*/ res.inline = function (locals, decl, buf) { if (arguments.length === 1) { decl = locals; locals = {} } declarations( decl, buf = [], '', // prefix emptyArray, // vendors 1, { l: function localize(match, space, global, dot, name) { if (global) return space + global if (!locals[name]) return name return space + dot + locals[name] } }) return finalize(buf) } res.prefix = function(val, vendors) { return cartesian( vendors.map(function(p){return '-' + p + '-'}).concat(['']), [val] ) } return res } j2c.global = function(x) { return ':global(' + x + ')' } j2c.kv = kv function kv (k, v, o) { o = {} o[k] = v return o } j2c.at = function at (rule, params, block) { if ( arguments.length < 3 ) { var _at = at.bind.apply(at, [null].concat([].slice.call(arguments,0))) _at.toString = function(){return '@' + rule + ' ' + params} return _at } else return kv('@' + rule + ' ' + params, block) } j2c(j2c) delete j2c.use return j2c; });