UNPKG

cssobj

Version:

Runtime CSS manager, Turn CSS into dynamic JS module, Stylesheet CRUD (Create, Read, Update, Delete) in CSSOM, Solve common problems of CSS-in-JS, name space (local) class names

1,122 lines (964 loc) 33.3 kB
/* cssobj v1.3.6 Thu Mar 22 2018 09:41:25 GMT+0800 (CST) commit 5199e3a94197a9cfdd0ecc4576dcaed71b01458d https://github.com/cssobj/cssobj Released under the MIT License. Components version info: - cssobj-core@1.1.9 319d94d9d6c0ee455ed0dfe0c7f796298a145250 - cssobj-plugin-cssom@4.1.4 18b665ff6051ae754c0956ea1c278d0a4cda112c - cssobj-plugin-localize@3.3.2 90529430922b4b3bbb27c70264157f1fae71eb62 */ 'use strict'; // helper functions for cssobj // check n is numeric, or string of numeric function isNumeric(n) { return !isNaN(parseFloat(n)) && isFinite(n) } function isPrimitive(val) { return val == null || (typeof val !== 'function' && typeof val !== 'object') } function own(o, k) { return {}.hasOwnProperty.call(o, k) } // set default option (not deeply) function defaults(options, defaultOption) { options = options || {}; for (var i in defaultOption) { if (own(defaultOption, i) && !(i in options)) options[i] = defaultOption[i]; } return options } // Object.assgin polyfill function _assign (target, source) { var s, from, key; var to = Object(target); for (s = 1; s < arguments.length; s++) { from = Object(arguments[s]); for (key in from) { if (own(from, key)) { to[key] = from[key]; } } } return to } var assign = Object.assign || _assign; // console.log(assign({}, {a:1}, {a:2}, {b:3})) // convert js prop into css prop (dashified) function dashify(str) { return str.replace(/[A-Z]/g, function(m) { return '-' + m.toLowerCase() }) } // capitalize str function capitalize (str) { return str.charAt(0).toUpperCase() + str.substr(1) } // random string, should used across all cssobj plugins var random = (function () { var count = 0; return function (prefix) { count++; return '_' + (prefix||'') + Math.floor(Math.random() * Math.pow(2, 32)).toString(36) + count + '_' } })(); function isString(value) { return typeof value === 'string' } // var obj={a:{b:{c:1}}}; // objSet(obj, {} ,{x:1}); // objSet(obj,'a.b.c.d.e',{x:1}); // objSet(obj,'a.f.d.s'.split('.'), {y:1}); // console.log(JSON.stringify(obj)) // return object path with only object type function objGetObj(obj, _key) { var key = Array.isArray(_key) ? _key : String(_key).split('.'); var p, n, ok=1; var ret = {ok:ok, path:key, obj:obj}; for(p=0; p<key.length; p++) { n = key[p]; if(!obj.hasOwnProperty(n) || isPrimitive(obj[n])) { ok = 0; break } obj = obj[n]; } ret.ok= ok; ret.path = key.slice(0,p); ret.obj=obj; return ret } // var obj={a:{b:{c:1}}}; // console.log(objGetObj(obj)) // console.log(objGetObj(obj, [])) // console.log(objGetObj(obj, 'a')) // console.log(objGetObj(obj, 'a.b')) // console.log(objGetObj(obj, 'a.b.c.e')) // extend obj from source, if it's no key in obj, create one function extendObj (obj, key, source) { obj[key] = obj[key] || {}; for(var args = arguments, i = 2; i < args.length; i++) { source = args[i]; for (var k in source) if (own(source, k)) obj[key][k] = source[k]; } return obj[key] } // ensure obj[k] as array, then push v into it function arrayKV (obj, k, v, reverse, unique) { var d = obj[k]; d = obj[k] = k in obj ? (Array.isArray(d) ? d : [d]) : []; if(unique && d.indexOf(v)>-1) return reverse ? d.unshift(v) : d.push(v); } // get parents array from node (when it's passed the test) function getParents (node, test, key, childrenKey, parentKey) { var i, v, p = node, path = []; while (p) { if (test(p)) { if (childrenKey) { for (i = 0; i < path.length; i++) { arrayKV(p, childrenKey, path[i], false, true); } } if (path[0] && parentKey) { path[0][parentKey] = p; } path.unshift(p); } p = p.parent; } for (i = 0; i < path.length; i++) { v = path[i]; path[i] = key ? v[key] : v; } return path } // split selector with splitter, aware of css attributes function splitSelector (sel, splitter, inBracket) { if (sel.indexOf(splitter) < 0) return [sel] for (var c, i = 0, n = 0, instr = '', prev = 0, d = []; c = sel.charAt(i); i++) { if (instr) { if (c == instr && sel.charAt(i-1)!='\\') instr = ''; continue } if (c == '"' || c == '\'') instr = c; /* istanbul ignore if */ if(!inBracket){ if (c == '(' || c == '[') n++; if (c == ')' || c == ']') n--; } if (!n && c == splitter) d.push(sel.substring(prev, i)), prev = i + 1; } return d.concat(sel.substring(prev)) } // checking for valid css value function isValidCSSValue (val) { // falsy: '', NaN, Infinity, [], {} return typeof val=='string' && val || typeof val=='number' && isFinite(val) } // using var as iteral to help optimize var KEY_ID = '$id'; var KEY_ORDER = '$order'; var KEY_TEST = '$test'; var TYPE_GROUP = 'group'; // helper function var keys = Object.keys; // type check helpers var type = {}.toString; var ARRAY = type.call([]); var OBJECT = type.call({}); // only array, object now treated as iterable function isIterable (v) { return type.call(v) == OBJECT || type.call(v) == ARRAY } // check if it's function function isFunction (v) { return typeof v == 'function' } // regexp constants // @page rule: CSSOM: // IE returned: not implemented error // FF, Chrome actually is not groupRule(not cssRules), same as @font-face rule // https://developer.mozilla.org/en-US/docs/Web/API/CSSGroupingRule // CSSPageRule is listed as derived from CSSGroupingRule, but not implemented yet. // Here added @page as GroupRule, but plugin should take care of this. var reGroupRule = /^@(media|document|supports|page|[\w-]*keyframes)/i; var reAtRule = /^\s*@/i; /** * convert simple Object into node data * input data format: {"a":{"b":{"c":{"":[{color:1}]}}}, "abc":123, '@import':[2,3,4], '@media (min-width:320px)':{ d:{ok:1} }} * 1. every key is folder node * 2. "":[{rule1}, {rule2}] will split into several rules * 3. & will replaced by parent, \\& will escape * 4. all prop should be in dom.style camelCase * * @param {object|array} d - simple object data, or array * @param {object} result - the reulst object to store config and root node * @param {object} [previousNode] - also act as parent for next node * @param {boolean} init whether it's the root call * @returns {object} node data object */ function parseObj (d, result, node, init) { if (init) { result.nodes = []; result.ref = {}; if (node) result.diff = {}; } node = node || {}; node.obj = d; if (type.call(d) == ARRAY) { var nodes = []; /* for array type, each children have a parent that not on the virtual tree, see test case of @media-array for example, the array node obj=Array, but have node.selPart(no selText) So have to set the right node.at/node.type from the node.key, to get right selText for children */ node.at = reAtRule.exec(node.key); for(var i = 0; i < d.length; i++) { var prev = node[i]; var n = parseObj(d[i], result, node[i] || {parent: node, src: d, parentNode: nodes, index: i}); if(result.diff && prev!=n) arrayKV(result.diff, n ? 'added' : 'removed', n||prev); nodes.push(n); } return nodes } else { if (d[KEY_ID]) result.ref[d[KEY_ID]] = node; // it's no need to check (type.call(d) == OBJECT) // isIterable will filter only ARRAY/OBJECT // other types will goto parseProp function var prevVal = node.prevVal = node.lastVal; // at first stage check $test if (KEY_TEST in d) { var test = isFunction(d[KEY_TEST]) ? d[KEY_TEST](!node.disabled, node, result) : d[KEY_TEST]; // if test false, remove node completely // if it's return function, going to stage 2 where all prop rendered if(!test) { return } node.test = test; } var children = node.children = node.children || {}; node.lastRaw = node.rawVal || {}; node.lastVal = {}; node.rawVal = {}; node.prop = {}; node.diff = {}; var order = d[KEY_ORDER] | 0; var funcArr = []; var processObj = function (obj, k, nodeObj) { var haveOldChild = k in children; var newNode = extendObj(children, k, nodeObj); // don't overwrite selPart for previous node newNode.selPart = newNode.selPart || splitSelector(k, ','); var n = parseObj(obj, result, newNode); if(n) children[k] = n; // it's new added node if (prevVal) !haveOldChild ? n && arrayKV(result.diff, 'added', n) : !n && arrayKV(result.diff, 'removed', children[k]); // for first time check, remove from parent (no diff) if(!n) delete nodeObj.parent.children[k]; }; // only there's no selText, getSel if(!('selText' in node)) getSel(node, result); for (var k in d) { // here $key start with $ is special // k[0] == '$' ... but the core will calc it into node. // Plugins should take $ with care and mark as a special case. e.g. ignore it if (!own(d, k)) continue if (!isIterable(d[k]) || type.call(d[k]) == ARRAY && !isIterable(d[k][0])) { // it's inline at-rule: @import etc. if (k[0]=='@') { processObj( // map @import: [a,b,c] into {a:1, b:1, c:1} [].concat(d[k]).reduce(function(prev, cur) { prev[cur] = ';'; return prev }, {}), k, {parent: node, src: d, key: k, inline:true}); continue } var r = function (_k) { // skip $test key if(_k != KEY_TEST) parseProp(node, d, _k, result); }; order ? funcArr.push([r, k]) : r(k); } else { processObj(d[k], k, {parent: node, src: d, key: k}); } } // when it's second time visit node if (prevVal) { // children removed for (k in children) { if (!(k in d)) { arrayKV(result.diff, 'removed', children[k]); delete children[k]; } } // prop changed var diffProp = function () { var newKeys = keys(node.lastVal); var removed = keys(prevVal).filter(function (x) { return newKeys.indexOf(x) < 0 }); if (removed.length) node.diff.removed = removed; if (keys(node.diff).length) arrayKV(result.diff, 'changed', node); }; order ? funcArr.push([diffProp, null]) : diffProp(); } if (order) arrayKV(result, '_order', {order: order, func: funcArr}); result.nodes.push(node); return node } } function getSel(node, result) { var opt = result.config; // array index don't have key, // fetch parent key as ruleNode var ruleNode = getParents(node, function (v) { return v.key }).pop(); node.parentRule = getParents(node.parent, function (n) { return n.type == TYPE_GROUP }).pop() || null; if (ruleNode) { var isMedia, sel = ruleNode.key; var groupRule = sel.match(reGroupRule); if (groupRule) { node.type = TYPE_GROUP; node.at = groupRule.pop(); isMedia = node.at == 'media'; // only media allow nested and join, and have node.selPart if (isMedia) node.selPart = splitSelector(sel.replace(reGroupRule, ''), ','); // combinePath is array, 'str' + array instead of array.join(',') node.groupText = isMedia ? '@' + node.at + combinePath(getParents(node, function (v) { return v.type == TYPE_GROUP }, 'selPart', 'selChild', 'selParent'), '', ' and') : sel; node.selText = getParents(node, function (v) { return v.selText && !v.at }, 'selText').pop() || ''; } else if (reAtRule.test(sel)) { node.type = 'at'; node.selText = sel; } else { node.selText = '' + combinePath(getParents(ruleNode, function (v) { return v.selPart && !v.at }, 'selPart', 'selChild', 'selParent'), '', ' ', true), opt; } node.selText = applyPlugins(opt, 'selector', node.selText, node, result); if (node.selText) node.selTextPart = splitSelector(node.selText, ','); if (node !== ruleNode) node.ruleNode = ruleNode; } } /** * Parse property of object d's key, with propKey as a candidate key name * @param {} node: v-node of cssobj * @param {} d: source object * @param {} key: any numeric will be ignored, then converted to string * @param {} result: cssobj result object * @param {} propKey: candidate prop key name Accept only key as string, numeric will be ignored color: function(){return ['red', 'blue']} will expand color: function(){return {fontSize: '12px', float:'right'}} will be replaced */ function parseProp (node, d, key, result, propKey) { var prevVal = node.prevVal; var lastVal = node.lastVal; // the prop name get from object key or candidate key var propName = isNumeric(key) ? propKey : key; // NEXT: propName can be changed by user // now it's not used, since propName ensure exists // corner case: propKey==='' ?? below line will do wrong!! // if(!propName) return var raw = node.lastRaw[propName], prev = prevVal && prevVal[propName], argObj = {node:node, result:result}; if (raw) argObj.raw = raw[0]; ![].concat(d[key]).forEach(function (v) { // prepare value function args argObj.cooked = prev; // pass lastVal if it's function argObj.raw = raw = isFunction(v) ? v(argObj) : v; var val = applyPlugins(result.config, 'value', raw, propName, node, result, propKey); // check and merge only format as Object || Array of Object, other format not accepted! if (isIterable(val)) { for (var k in val) { if (own(val, k)) parseProp(node, val, k, result, propName); } } else { arrayKV ( node.rawVal, propName, raw, true ); if (isValidCSSValue(val)) { // only valid val can enter node.prop and lastVal // push every val to prop arrayKV( node.prop, propName, val, true ); prev = lastVal[propName] = val; } } }); if (prevVal) { if (!(propName in prevVal)) { arrayKV(node.diff, 'added', propName); } else if (prevVal[propName] != lastVal[propName]) { arrayKV(node.diff, 'changed', propName); } } } function combinePath (array, parentSel, seperator, replaceAmpersand) { return !array.length ? parentSel : array[0].reduce(function (result, value) { var part, str = parentSel ? parentSel + seperator : parentSel; if (replaceAmpersand) { part = splitSelector( value, '&' ); str = part.length > 1 ? part.join(parentSel) : str + value; } else { str += value; } return result.concat(combinePath(array.slice(1), str, seperator, replaceAmpersand)) }, []) } function applyPlugins (opt, type) { var args = [].slice.call(arguments, 2); var plugin = opt.plugins; // plugin is always Array, so here we don't check it return [].concat(plugin).reduce( function (pre, plugin) { return plugin[type] ? plugin[type].apply(null, [pre].concat(args)) : pre }, args.shift() ) } function applyOrder (opt) { if (opt._order==null) return opt._order .sort(function (a, b) { return a.order - b.order }) .forEach(function (v) { v.func.forEach(function (f) { f[0](f[1]); }); }); opt._order = []; } function cssobj (config) { config = defaults(config, { plugins: [], intros: [] }); return function (initObj, initState) { var updater = function (obj, state) { if (arguments.length>1) result.state = state || {}; if (obj) result.obj = isFunction(obj) ? obj() : obj; result.root = parseObj(extendObj({}, '', result.intro, result.obj), result, result.root, true); applyOrder(result); result = applyPlugins(config, 'post', result); isFunction(config.onUpdate) && config.onUpdate(result); return result }; var result = { intro: {}, update: updater, config: config }; ![].concat(config.intros).forEach( function(v) { extendObj(result, 'intro', isFunction(v) ? v(result) : v); } ); updater(initObj, initState || config.state); return result } } // plugin for cssobj function createDOM (rootDoc, id, option) { var el = rootDoc.getElementById(id); var head = rootDoc.getElementsByTagName('head')[0]; if(el) { if(option.append) return el el.parentNode && el.parentNode.removeChild(el); } el = rootDoc.createElement('style'); head.appendChild(el); el.setAttribute('id', id); if (option.attrs) for (var i in option.attrs) { el.setAttribute(i, option.attrs[i]); } return el } var addCSSRule = function (parent, selector, body, node) { var isImportRule = /@import/i.test(node.selText); var rules = parent.cssRules || parent.rules; var index=0; var omArr = []; var str = node.inline ? body.map(function(v) { return [node.selText, ' ', v] }) : [[selector, '{', body.join(''), '}']]; str.forEach(function(text) { if (parent.cssRules) { try { index = isImportRule ? 0 : rules.length; parent.appendRule ? parent.appendRule(text.join('')) // keyframes.appendRule return undefined : parent.insertRule(text.join(''), index); //firefox <16 also return undefined... omArr.push(rules[index]); } catch(e) { // modern browser with prefix check, now only -webkit- // http://shouldiprefix.com/#animations // if(selector && selector.indexOf('@keyframes')==0) for(var ret, i = 0, len = cssPrefixes.length; i < len; i++) { // ret = addCSSRule(parent, selector.replace('@keyframes', '@-'+cssPrefixes[i].toLowerCase()+'-keyframes'), body, node) // if(ret.length) return ret // } // the rule is not supported, fail silently // console.log(e, selector, body, pos) } } else if (parent.addRule) { [].concat(selector).forEach(function (sel) { try { // remove ALL @-rule support for old IE if(isImportRule) { index = parent.addImport(text[2]); omArr.push(parent.imports[index]); // IE addPageRule() return: not implemented!!!! // } else if (/@page/.test(sel)) { // index = parent.addPageRule(sel, text[2], -1) // omArr.push(rules[rules.length-1]) } else if (!/^\s*@/.test(sel)) { parent.addRule(sel, text[2], rules.length); // old IE have bug: addRule will always return -1!!! omArr.push(rules[rules.length-1]); } } catch(e) { // console.log(e, selector, body) } }); } }); return omArr }; function getBodyCss (node) { // get cssText from prop var prop = node.prop; return Object.keys(prop).map(function (k) { // skip $prop, e.g. $id, $order if(k[0]=='$') return '' for (var v, ret='', i = prop[k].length; i--;) { v = prop[k][i]; // value expand & merge should be done as value function/plugin in cssobj-core >=0.5.0 ret += node.inline ? k : prefixProp(k, true) + ':' + v + ';'; } return ret }) } // vendor prefix support // borrowed from jQuery 1.12 var cssPrefixes = [ "Webkit", "Moz", "ms", "O" ]; var cssPrefixesReg = new RegExp('^(?:' + cssPrefixes.join('|') + ')[A-Z]'); var emptyStyle = document.createElement( "div" ).style; var testProp = function (list) { for(var i = list.length; i--;) { if(list[i] in emptyStyle) return list[i] } }; // /** * cache cssProps * the value is JS format, will be used: * 1. diff & patch properties for CSSOM * 2. vendorPrefix property name checking */ var cssProps = { // normalize float css property 'float': testProp(['styleFloat', 'cssFloat', 'float']) }; // return a css property mapped to a potentially vendor prefixed property function vendorPropName( name ) { // shortcut for names that are not vendor prefixed // when name already have '-' as first char, don't prefix if ( name in emptyStyle || name[0] == '-') return // check for vendor prefixed names var preName, capName = capitalize(name); var i = cssPrefixes.length; while ( i-- ) { preName = cssPrefixes[ i ] + capName; if ( preName in emptyStyle ) return preName } } // apply prop to get right vendor prefix // inCSS false=camelcase; true=dashed function prefixProp (name, inCSS) { // $prop will skip if(name[0]=='$') return '' // find name and cache the name for next time use var retName = cssProps[ name ] || ( cssProps[ name ] = vendorPropName( name ) || name); return inCSS // if hasPrefix in prop ? dashify(cssPrefixesReg.test(retName) ? capitalize(retName) : name=='float' && name || retName) // fix float in CSS, avoid return cssFloat : retName } /** * Get value and important flag from value str * @param {CSSStyleRule} rule css style rule object * @param {string} prop prop to set * @param {string} val value string */ function setCSSProperty (styleObj, prop, val) { var value; var important = /^(.*)!(important)\s*$/i.exec(val); var propCamel = prefixProp(prop); var propDash = prefixProp(prop, true); if(important) { value = important[1]; important = important[2]; if(styleObj.setProperty) styleObj.setProperty(propDash, value, important); else { // for old IE, cssText is writable, and below is valid for contain !important // don't use styleObj.setAttribute since it's not set important // should do: delete styleObj[propCamel], but not affect result // only work on <= IE8: s.style['FONT-SIZE'] = '12px!important' styleObj[propDash.toUpperCase()] = val; // refresh cssText, the whole rule! styleObj.cssText = styleObj.cssText; } } else { styleObj[propCamel] = val; } } function cssobj_plugin_post_cssom (option) { option = option || {}; // prefixes array can change the global default vendor prefixes if(option.vendors) cssPrefixes = option.vendors; var id = option.id || 'cssobj' + random(); var frame = option.frame; var rootDoc = frame ? frame.contentDocument||frame.contentWindow.document : document; var dom = createDOM(rootDoc, id, option); var sheet = dom.sheet || dom.styleSheet; // sheet.insertRule ("@import url('test.css');", 0) // it's ok to insert @import, but only at top // sheet.insertRule ("@charset 'UTF-8';", 0) // throw SyntaxError https://www.w3.org/Bugs/Public/show_bug.cgi?id=22207 // IE has a bug, first comma rule not work! insert a dummy here // addCSSRule(sheet, 'html,body', [], {}) // helper regexp & function // @page in FF not allowed pseudo @page :first{}, with SyntaxError: An invalid or illegal string was specified var reWholeRule = /page/i; var atomGroupRule = function (node) { return !node ? false : reWholeRule.test(node.at) || node.parentRule && reWholeRule.test(node.parentRule.at) }; var getParent = function (node) { var p = 'omGroup' in node ? node : node.parentRule; return p && p.omGroup || sheet }; var validParent = function (node) { return !node.parentRule || node.parentRule.omGroup !== null }; var removeRule = function(parent, rule, index) { return parent.deleteRule ? parent.deleteRule(rule.keyText || index) : parent.removeRule(index) }; var clearRoot = function (root) { var rules = root.cssRules || root.rules; // console.log('clearRoot', sheet, rules) for(var i=rules.length; i--;){ removeRule(root, rules[i], i); // console.log('clear rule', i, rules[i]) } }; var removeOneRule = function (rule) { if (!rule) return var parent = rule.parentRule || sheet; var rules = parent.cssRules || parent.rules; var removeFunc = function (v, i) { if((v===rule)) { removeRule(parent, rule, i); return true } } // sheet.imports have bugs in IE: // > sheet.removeImport(0) it's work, then again // > sheet.removeImport(0) it's not work!!! // // parent.imports && [].some.call(parent.imports, removeFunc) ;[].some.call(rules, removeFunc); }; function removeNode (node) { // remove mediaStore for old IE var groupIdx = mediaStore.indexOf(node); if (groupIdx > -1) { // before remove from mediaStore // don't forget to remove all children, by a walk node.mediaEnabled = false; walk(node); mediaStore.splice(groupIdx, 1); } [node.omGroup].concat(node.omRule).forEach(removeOneRule); } // helper function for addNormalrule var addNormalRule = function (node, selText, cssText) { if(!cssText) return // get parent to add var parent = getParent(node); var parentRule = node.parentRule; if (validParent(node)) return node.omRule = addCSSRule(parent, selText, cssText, node) else if (parentRule) { // for old IE not support @media, check mediaEnabled, add child nodes if (parentRule.mediaEnabled) { [].concat(node.omRule).forEach(removeOneRule); return node.omRule = addCSSRule(parent, selText, cssText, node) } else if (node.omRule) { node.omRule.forEach(removeOneRule); delete node.omRule; } } }; var mediaStore = []; var checkMediaList = function () { mediaStore.forEach(function (v) { v.mediaEnabled = v.mediaTest(rootDoc); walk(v); }); }; if (window.attachEvent) { window.attachEvent('onresize', checkMediaList); } else if (window.addEventListener) { window.addEventListener('resize', checkMediaList, true); } var walk = function (node, store) { if (!node) return // cssobj generate vanilla Array, it's safe to use constructor, fast if (node.constructor === Array) return node.map(function (v) {walk(v, store);}) // skip $key node if(node.key && node.key[0]=='$' || !node.prop) return // nested media rule will pending proceed if(node.at=='media' && node.selParent && node.selParent.postArr) { return node.selParent.postArr.push(node) } node.postArr = []; var children = node.children; var isGroup = node.type == 'group'; if (atomGroupRule(node)) store = store || []; if (isGroup) { // if it's not @page, @keyframes (which is not groupRule in fact) if (!atomGroupRule(node)) { var $groupTest = node.obj.$groupTest; var presetMedia = node.at=='media' && option.media; if ($groupTest || presetMedia) { // console.log('start test media', presetMedia, $groupTest) node.omGroup = null; // when add media rule failed, build test function then check on window.resize // if (!old) { // build test function from @media rule var mediaTest = $groupTest || (presetMedia && function(doc) { var media = option.media; return media ? node.selPart.some(function(part){ return new RegExp(media, 'i').test(part.trim()) }) : true }) || function(){return true}; try { // first test if it's valid function var mediaEnabled = mediaTest(rootDoc); node.mediaTest = mediaTest; node.mediaEnabled = mediaEnabled; mediaStore.push(node); } catch(e) {} // } } else { [''].concat(cssPrefixes).some(function (v) { return node.omGroup = addCSSRule( // all groupRule will be added to root sheet sheet, '@' + (v ? '-' + v.toLowerCase() + '-' : v) + node.groupText.slice(1), [], node ).pop() || null }); } } } var selText = node.selTextPart; var cssText = getBodyCss(node); // it's normal css rule if (cssText.join('')) { if (!atomGroupRule(node)) { addNormalRule(node, selText, cssText); } store && store.push(selText ? selText + ' {' + cssText.join('') + '}' : cssText); } for (var c in children) { // empty key will pending proceed if (c === '') node.postArr.push(children[c]); else walk(children[c], store); } if (isGroup) { // if it's @page, @keyframes if (atomGroupRule(node) && validParent(node)) { addNormalRule(node, node.groupText, store); store = null; } } // media rules need a stand alone block var postArr = node.postArr; delete node.postArr; postArr.map(function (v) { walk(v, store); }); }; var prevMedia = option.media; return { post: function (result) { var mediaChanged = prevMedia!=option.media; prevMedia = option.media; checkMediaList(); result.set = function(cssPath, newObj){ var path = Array.isArray(cssPath) ? cssPath : [cssPath]; var srcObj = result.obj; if(isString(path[0]) && path[0][0]==='$') { srcObj = result.ref[path.shift().slice(1)].obj; } var ret = objGetObj( srcObj, path ); if(ret.ok){ assign(ret.obj, newObj); } result.update(); }; result.cssdom = dom; if (!result.diff || mediaChanged) { // it's first time render if(mediaChanged) { // if media changed, reinit, clear all rules // console.log('clearRoot', prevMedia, option.media) mediaStore = []; clearRoot(sheet); } walk(result.root); } else { // it's not first time, patch the diff result to CSSOM var diff = result.diff; // node added if (diff.added) diff.added.forEach(function (node) { walk(node); }); // node removed if (diff.removed) diff.removed.forEach(function (node) { // also remove all child group & sel node.selChild && node.selChild.forEach(removeNode); removeNode(node); }); // node changed, find which part should be patched if (diff.changed) diff.changed.forEach(function (node) { var om = node.omRule; var diff = node.diff; if (!om) om = addNormalRule(node, node.selTextPart, getBodyCss(node)) // added have same action as changed, can be merged... just for clarity ;[].concat(diff.added, diff.changed).forEach(function (v) { v && om && om.forEach(function (rule) { try{ setCSSProperty(rule.style, v, node.prop[v][0]); }catch(e){} }); }); diff.removed && diff.removed.forEach(function (v) { var prefixV = prefixProp(v, true); prefixV && om && om.forEach(function (rule) { try{ rule.style.removeProperty ? rule.style.removeProperty(prefixV) : rule.style.removeAttribute(prefixV); }catch(e){} }); }); }); } return result } } } // cssobj plugin var classNameRe = /[ \~\\@$%^&\*\(\)\+\=,/';\:"?><[\]\\{}|`]/; function cssobj_plugin_selector_localize(option) { option = option || {}; var space = option.space = typeof option.space!=='string' ? (typeof option.random == 'function' ? option.random() : random()) : option.space; var localNames = option.localNames = option.localNames || {}; var localize = function(name) { return name[0]=='!' ? name.substr(1) : (name in localNames ? localNames[name] : name + space) }; var parseSel = function(str) { if(!isString(str)) return str var part = splitSelector(str, '.', true); var sel=part[0]; for(var i = 1, p, pos, len = part.length; i < len; i++) { p = part[i]; if(!p) { sel += '.'; continue } pos = p.search(classNameRe); sel += '.' + (pos<0 ? localize(p) : localize(p.substr(0,pos)) + p.substr(pos)); } return sel }; var mapClass = function(str) { return isString(str) ? parseSel(str.replace(/\s+\.?/g, '.').replace(/^([^:\s.])/i, '.$1')).replace(/\./g, ' ').trim() : str }; var setResult = function(result) { result.space = space; result.localNames = localNames; result.mapSel = parseSel; result.mapClass = mapClass; return result }; return { selector: function localizeName (sel, node, result) { // don't touch at rule's selText // it's copied from parent, which already localized if(node.at) return sel if(!result.mapSel) setResult(result); return parseSel(sel) }, post: setResult } } // cssobj is simply an intergration for cssobj-core, cssom function cssobj$1 (obj, config, state) { config = config || {}; var local = config.local; config.local = !local ? {space: ''} : local && typeof local === 'object' ? local : {}; config.plugins = [].concat( config.plugins || [], cssobj_plugin_selector_localize(config.local), cssobj_plugin_post_cssom(config.cssom) ); return cssobj(config)(obj, state) } cssobj$1.version = '1.3.6'; module.exports = cssobj$1;