|
|
var List = require('css-tree').List; var generate = require('css-tree').generate; var walk = require('css-tree').walk;
var REPLACE = 1; var REMOVE = 2; var TOP = 0; var RIGHT = 1; var BOTTOM = 2; var LEFT = 3; var SIDES = ['top', 'right', 'bottom', 'left']; var SIDE = { 'margin-top': 'top', 'margin-right': 'right', 'margin-bottom': 'bottom', 'margin-left': 'left',
'padding-top': 'top', 'padding-right': 'right', 'padding-bottom': 'bottom', 'padding-left': 'left',
'border-top-color': 'top', 'border-right-color': 'right', 'border-bottom-color': 'bottom', 'border-left-color': 'left', 'border-top-width': 'top', 'border-right-width': 'right', 'border-bottom-width': 'bottom', 'border-left-width': 'left', 'border-top-style': 'top', 'border-right-style': 'right', 'border-bottom-style': 'bottom', 'border-left-style': 'left' }; var MAIN_PROPERTY = { 'margin': 'margin', 'margin-top': 'margin', 'margin-right': 'margin', 'margin-bottom': 'margin', 'margin-left': 'margin',
'padding': 'padding', 'padding-top': 'padding', 'padding-right': 'padding', 'padding-bottom': 'padding', 'padding-left': 'padding',
'border-color': 'border-color', 'border-top-color': 'border-color', 'border-right-color': 'border-color', 'border-bottom-color': 'border-color', 'border-left-color': 'border-color', 'border-width': 'border-width', 'border-top-width': 'border-width', 'border-right-width': 'border-width', 'border-bottom-width': 'border-width', 'border-left-width': 'border-width', 'border-style': 'border-style', 'border-top-style': 'border-style', 'border-right-style': 'border-style', 'border-bottom-style': 'border-style', 'border-left-style': 'border-style' };
function TRBL(name) { this.name = name; this.loc = null; this.iehack = undefined; this.sides = { 'top': null, 'right': null, 'bottom': null, 'left': null }; }
TRBL.prototype.getValueSequence = function(declaration, count) { var values = []; var iehack = ''; var hasBadValues = declaration.value.type !== 'Value' || declaration.value.children.some(function(child) { var special = false;
switch (child.type) { case 'Identifier': switch (child.name) { case '\\0': case '\\9': iehack = child.name; return;
case 'inherit': case 'initial': case 'unset': case 'revert': special = child.name; break; } break;
case 'Dimension': switch (child.unit) { // is not supported until IE11
case 'rem':
// v* units is too buggy across browsers and better
// don't merge values with those units
case 'vw': case 'vh': case 'vmin': case 'vmax': case 'vm': // IE9 supporting "vm" instead of "vmin".
special = child.unit; break; } break;
case 'Hash': // color
case 'Number': case 'Percentage': break;
case 'Function': if (child.name === 'var') { return true; }
special = child.name; break;
case 'WhiteSpace': return false; // ignore space
default: return true; // bad value
}
values.push({ node: child, special: special, important: declaration.important }); });
if (hasBadValues || values.length > count) { return false; }
if (typeof this.iehack === 'string' && this.iehack !== iehack) { return false; }
this.iehack = iehack; // move outside
return values; };
TRBL.prototype.canOverride = function(side, value) { var currentValue = this.sides[side];
return !currentValue || (value.important && !currentValue.important); };
TRBL.prototype.add = function(name, declaration) { function attemptToAdd() { var sides = this.sides; var side = SIDE[name];
if (side) { if (side in sides === false) { return false; }
var values = this.getValueSequence(declaration, 1);
if (!values || !values.length) { return false; }
// can mix only if specials are equal
for (var key in sides) { if (sides[key] !== null && sides[key].special !== values[0].special) { return false; } }
if (!this.canOverride(side, values[0])) { return true; }
sides[side] = values[0]; return true; } else if (name === this.name) { var values = this.getValueSequence(declaration, 4);
if (!values || !values.length) { return false; }
switch (values.length) { case 1: values[RIGHT] = values[TOP]; values[BOTTOM] = values[TOP]; values[LEFT] = values[TOP]; break;
case 2: values[BOTTOM] = values[TOP]; values[LEFT] = values[RIGHT]; break;
case 3: values[LEFT] = values[RIGHT]; break; }
// can mix only if specials are equal
for (var i = 0; i < 4; i++) { for (var key in sides) { if (sides[key] !== null && sides[key].special !== values[i].special) { return false; } } }
for (var i = 0; i < 4; i++) { if (this.canOverride(SIDES[i], values[i])) { sides[SIDES[i]] = values[i]; } }
return true; } }
if (!attemptToAdd.call(this)) { return false; }
// TODO: use it when we can refer to several points in source
// if (this.loc) {
// this.loc = {
// primary: this.loc,
// merged: declaration.loc
// };
// } else {
// this.loc = declaration.loc;
// }
if (!this.loc) { this.loc = declaration.loc; }
return true; };
TRBL.prototype.isOkToMinimize = function() { var top = this.sides.top; var right = this.sides.right; var bottom = this.sides.bottom; var left = this.sides.left;
if (top && right && bottom && left) { var important = top.important + right.important + bottom.important + left.important;
return important === 0 || important === 4; }
return false; };
TRBL.prototype.getValue = function() { var result = new List(); var sides = this.sides; var values = [ sides.top, sides.right, sides.bottom, sides.left ]; var stringValues = [ generate(sides.top.node), generate(sides.right.node), generate(sides.bottom.node), generate(sides.left.node) ];
if (stringValues[LEFT] === stringValues[RIGHT]) { values.pop(); if (stringValues[BOTTOM] === stringValues[TOP]) { values.pop(); if (stringValues[RIGHT] === stringValues[TOP]) { values.pop(); } } }
for (var i = 0; i < values.length; i++) { if (i) { result.appendData({ type: 'WhiteSpace', value: ' ' }); }
result.appendData(values[i].node); }
if (this.iehack) { result.appendData({ type: 'WhiteSpace', value: ' ' }); result.appendData({ type: 'Identifier', loc: null, name: this.iehack }); }
return { type: 'Value', loc: null, children: result }; };
TRBL.prototype.getDeclaration = function() { return { type: 'Declaration', loc: this.loc, important: this.sides.top.important, property: this.name, value: this.getValue() }; };
function processRule(rule, shorts, shortDeclarations, lastShortSelector) { var declarations = rule.block.children; var selector = rule.prelude.children.first().id;
rule.block.children.eachRight(function(declaration, item) { var property = declaration.property;
if (!MAIN_PROPERTY.hasOwnProperty(property)) { return; }
var key = MAIN_PROPERTY[property]; var shorthand; var operation;
if (!lastShortSelector || selector === lastShortSelector) { if (key in shorts) { operation = REMOVE; shorthand = shorts[key]; } }
if (!shorthand || !shorthand.add(property, declaration)) { operation = REPLACE; shorthand = new TRBL(key);
// if can't parse value ignore it and break shorthand children
if (!shorthand.add(property, declaration)) { lastShortSelector = null; return; } }
shorts[key] = shorthand; shortDeclarations.push({ operation: operation, block: declarations, item: item, shorthand: shorthand });
lastShortSelector = selector; });
return lastShortSelector; }
function processShorthands(shortDeclarations, markDeclaration) { shortDeclarations.forEach(function(item) { var shorthand = item.shorthand;
if (!shorthand.isOkToMinimize()) { return; }
if (item.operation === REPLACE) { item.item.data = markDeclaration(shorthand.getDeclaration()); } else { item.block.remove(item.item); } }); }
module.exports = function restructBlock(ast, indexer) { var stylesheetMap = {}; var shortDeclarations = [];
walk(ast, { visit: 'Rule', reverse: true, enter: function(node) { var stylesheet = this.block || this.stylesheet; var ruleId = (node.pseudoSignature || '') + '|' + node.prelude.children.first().id; var ruleMap; var shorts;
if (!stylesheetMap.hasOwnProperty(stylesheet.id)) { ruleMap = { lastShortSelector: null }; stylesheetMap[stylesheet.id] = ruleMap; } else { ruleMap = stylesheetMap[stylesheet.id]; }
if (ruleMap.hasOwnProperty(ruleId)) { shorts = ruleMap[ruleId]; } else { shorts = {}; ruleMap[ruleId] = shorts; }
ruleMap.lastShortSelector = processRule.call(this, node, shorts, shortDeclarations, ruleMap.lastShortSelector); } });
processShorthands(shortDeclarations, indexer.declaration); };
|