866 lines
31 KiB
JavaScript
866 lines
31 KiB
JavaScript
|
|
// Contains the interpretation of CSS properties, as used by the property optimizer
|
|
|
|
module.exports = (function () {
|
|
|
|
var tokenModule = require('./token');
|
|
var validator = require('./validator');
|
|
var Splitter = require('../text/splitter');
|
|
|
|
// Functions that decide what value can override what.
|
|
// The main purpose is to disallow removing CSS fallbacks.
|
|
// A separate implementation is needed for every different kind of CSS property.
|
|
// -----
|
|
// The generic idea is that properties that have wider browser support are 'more understandable'
|
|
// than others and that 'less understandable' values can't override more understandable ones.
|
|
var canOverride = {
|
|
// Use when two tokens of the same property can always be merged
|
|
always: function () {
|
|
// NOTE: We could have (val1, val2) parameters here but jshint complains because we don't use them
|
|
return true;
|
|
},
|
|
// Use when two tokens of the same property can only be merged if they have the same value
|
|
sameValue: function(val1, val2) {
|
|
return val1 === val2;
|
|
},
|
|
sameFunctionOrValue: function(val1, val2) {
|
|
// Functions with the same name can override each other
|
|
if (validator.areSameFunction(val1, val2)) {
|
|
return true;
|
|
}
|
|
|
|
return val1 === val2;
|
|
},
|
|
// Use for properties containing CSS units (margin-top, padding-left, etc.)
|
|
unit: function(val1, val2) {
|
|
// The idea here is that 'more understandable' values override 'less understandable' values, but not vice versa
|
|
// Understandability: (unit without functions) > (same functions | standard functions) > anything else
|
|
// NOTE: there is no point in having different vendor-specific functions override each other or standard functions,
|
|
// or having standard functions override vendor-specific functions, but standard functions can override each other
|
|
// NOTE: vendor-specific property values are not taken into consideration here at the moment
|
|
|
|
if (validator.isValidUnitWithoutFunction(val2))
|
|
return true;
|
|
if (validator.isValidUnitWithoutFunction(val1))
|
|
return false;
|
|
|
|
// Standard non-vendor-prefixed functions can override each other
|
|
if (validator.isValidFunctionWithoutVendorPrefix(val2) && validator.isValidFunctionWithoutVendorPrefix(val1)) {
|
|
return true;
|
|
}
|
|
|
|
// Functions with the same name can override each other; same values can override each other
|
|
return canOverride.sameFunctionOrValue(val1, val2);
|
|
},
|
|
// Use for color properties (color, background-color, border-color, etc.)
|
|
color: function(val1, val2) {
|
|
// The idea here is that 'more understandable' values override 'less understandable' values, but not vice versa
|
|
// Understandability: (hex | named) > (rgba | hsla) > (same function name) > anything else
|
|
// NOTE: at this point rgb and hsl are replaced by hex values by clean-css
|
|
|
|
// (hex | named)
|
|
if (validator.isValidNamedColor(val2) || validator.isValidHexColor(val2))
|
|
return true;
|
|
if (validator.isValidNamedColor(val1) || validator.isValidHexColor(val1))
|
|
return false;
|
|
|
|
// (rgba|hsla)
|
|
if (validator.isValidRgbaColor(val2) || validator.isValidHslaColor(val2))
|
|
return true;
|
|
if (validator.isValidRgbaColor(val1) || validator.isValidHslaColor(val1))
|
|
return false;
|
|
|
|
// Functions with the same name can override each other; same values can override each other
|
|
return canOverride.sameFunctionOrValue(val1, val2);
|
|
},
|
|
// Use for background-image
|
|
backgroundImage: function(val1, val2) {
|
|
// The idea here is that 'more understandable' values override 'less understandable' values, but not vice versa
|
|
// Understandability: (none | url | inherit) > (same function) > (same value)
|
|
|
|
// (none | url)
|
|
if (val2 === 'none' || val2 === 'inherit' || validator.isValidUrl(val2))
|
|
return true;
|
|
if (val1 === 'none' || val1 === 'inherit' || validator.isValidUrl(val1))
|
|
return false;
|
|
|
|
// Functions with the same name can override each other; same values can override each other
|
|
return canOverride.sameFunctionOrValue(val1, val2);
|
|
},
|
|
border: function(val1, val2) {
|
|
var brokenUp1 = breakUp.border(Token.tokenizeOne(val1));
|
|
var brokenUp2 = breakUp.border(Token.tokenizeOne(val2));
|
|
|
|
return canOverride.color(brokenUp1[2].value, brokenUp2[2].value);
|
|
}
|
|
};
|
|
canOverride = Object.freeze(canOverride);
|
|
|
|
// Functions for breaking up shorthands to components
|
|
var breakUp = {};
|
|
breakUp.takeCareOfFourValues = function (splitfunc) {
|
|
return function (token) {
|
|
var descriptor = processable[token.prop];
|
|
var result = [];
|
|
var splitval = splitfunc(token.value);
|
|
|
|
if (splitval.length === 0 || (splitval.length < descriptor.components.length && descriptor.components.length > 4)) {
|
|
// This token is malformed and we have no idea how to fix it. So let's just keep it intact
|
|
return [token];
|
|
}
|
|
|
|
// Fix those that we do know how to fix
|
|
if (splitval.length < descriptor.components.length && splitval.length < 2) {
|
|
// foo{margin:1px} -> foo{margin:1px 1px}
|
|
splitval[1] = splitval[0];
|
|
}
|
|
if (splitval.length < descriptor.components.length && splitval.length < 3) {
|
|
// foo{margin:1px 2px} -> foo{margin:1px 2px 1px}
|
|
splitval[2] = splitval[0];
|
|
}
|
|
if (splitval.length < descriptor.components.length && splitval.length < 4) {
|
|
// foo{margin:1px 2px 3px} -> foo{margin:1px 2px 3px 2px}
|
|
splitval[3] = splitval[1];
|
|
}
|
|
|
|
// Now break it up to its components
|
|
for (var i = 0; i < descriptor.components.length; i++) {
|
|
var t = new Token(descriptor.components[i], splitval[i], token.isImportant);
|
|
result.push(t);
|
|
}
|
|
|
|
return result;
|
|
};
|
|
};
|
|
// Use this when you simply want to break up four values along spaces
|
|
breakUp.fourBySpaces = breakUp.takeCareOfFourValues(function (val) {
|
|
return new Splitter(' ').split(val).filter(function (v) { return v; });
|
|
});
|
|
// Breaks up a background property value
|
|
breakUp.commaSeparatedMulitpleValues = function (splitfunc) {
|
|
return function (token) {
|
|
if (token.value.indexOf(',') === -1)
|
|
return splitfunc(token);
|
|
|
|
var values = new Splitter(',').split(token.value);
|
|
var components = [];
|
|
|
|
for (var i = 0, l = values.length; i < l; i++) {
|
|
token.value = values[i];
|
|
components.push(splitfunc(token));
|
|
}
|
|
|
|
for (var j = 0, m = components[0].length; j < m; j++) {
|
|
for (var k = 0, n = components.length, newValues = []; k < n; k++) {
|
|
newValues.push(components[k][j].value);
|
|
}
|
|
|
|
components[0][j].value = newValues.join(',');
|
|
}
|
|
|
|
return components[0];
|
|
};
|
|
};
|
|
breakUp.background = function (token) {
|
|
// Default values
|
|
var result = Token.makeDefaults(['background-image', 'background-position', 'background-size', 'background-repeat', 'background-attachment', 'background-color'], token.isImportant);
|
|
var image = result[0];
|
|
var position = result[1];
|
|
var size = result[2];
|
|
var repeat = result[3];
|
|
var attachment = result[4];
|
|
var color = result[5];
|
|
var positionSet = false;
|
|
|
|
// Take care of inherit
|
|
if (token.value === 'inherit') {
|
|
// NOTE: 'inherit' is not a valid value for background-attachment so there we'll leave the default value
|
|
color.value = image.value = repeat.value = position.value = size.value = attachment.value = 'inherit';
|
|
return result;
|
|
}
|
|
|
|
// Break the background up into parts
|
|
var parts = new Splitter(' ').split(token.value);
|
|
if (parts.length === 0)
|
|
return result;
|
|
|
|
// Iterate over all parts and try to fit them into positions
|
|
for (var i = parts.length - 1; i >= 0; i--) {
|
|
var currentPart = parts[i];
|
|
|
|
if (validator.isValidBackgroundAttachment(currentPart)) {
|
|
attachment.value = currentPart;
|
|
} else if (validator.isValidBackgroundRepeat(currentPart)) {
|
|
repeat.value = currentPart;
|
|
} else if (validator.isValidBackgroundPositionPart(currentPart) || validator.isValidBackgroundSizePart(currentPart)) {
|
|
if (i > 0) {
|
|
var previousPart = parts[i - 1];
|
|
|
|
if (previousPart.indexOf('/') > 0) {
|
|
var twoParts = new Splitter('/').split(previousPart);
|
|
size.value = twoParts.pop() + ' ' + currentPart;
|
|
parts[i - 1] = twoParts.pop();
|
|
} else if (i > 1 && parts[i - 2] == '/') {
|
|
size.value = previousPart + ' ' + currentPart;
|
|
i -= 2;
|
|
} else if (parts[i - 1] == '/') {
|
|
size.value = currentPart;
|
|
} else {
|
|
position.value = currentPart + (positionSet ? ' ' + position.value : '');
|
|
positionSet = true;
|
|
}
|
|
} else {
|
|
position.value = currentPart + (positionSet ? ' ' + position.value : '');
|
|
positionSet = true;
|
|
}
|
|
} else if (validator.isValidBackgroundPositionAndSize(currentPart)) {
|
|
var sizeValue = new Splitter('/').split(currentPart);
|
|
size.value = sizeValue.pop();
|
|
position.value = sizeValue.pop();
|
|
} else if ((color.value == processable[color.prop].defaultValue || color.value == 'none') && validator.isValidColor(currentPart)) {
|
|
color.value = currentPart;
|
|
} else if (validator.isValidUrl(currentPart) || validator.isValidFunction(currentPart)) {
|
|
image.value = currentPart;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
};
|
|
// Breaks up a list-style property value
|
|
breakUp.listStyle = function (token) {
|
|
// Default values
|
|
var result = Token.makeDefaults(['list-style-type', 'list-style-position', 'list-style-image'], token.isImportant);
|
|
var type = result[0], position = result[1], image = result[2];
|
|
|
|
if (token.value === 'inherit') {
|
|
type.value = position.value = image.value = 'inherit';
|
|
return result;
|
|
}
|
|
|
|
var parts = new Splitter(' ').split(token.value);
|
|
var ci = 0;
|
|
|
|
// Type
|
|
if (ci < parts.length && validator.isValidListStyleType(parts[ci])) {
|
|
type.value = parts[ci];
|
|
ci++;
|
|
}
|
|
// Position
|
|
if (ci < parts.length && validator.isValidListStylePosition(parts[ci])) {
|
|
position.value = parts[ci];
|
|
ci++;
|
|
}
|
|
// Image
|
|
if (ci < parts.length) {
|
|
image.value = parts.splice(ci, parts.length - ci + 1).join(' ');
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
breakUp._widthStyleColor = function(token, prefix, order) {
|
|
// Default values
|
|
var components = order.map(function(prop) {
|
|
return prefix + '-' + prop;
|
|
});
|
|
var result = Token.makeDefaults(components, token.isImportant);
|
|
var color = result[order.indexOf('color')];
|
|
var style = result[order.indexOf('style')];
|
|
var width = result[order.indexOf('width')];
|
|
|
|
// Take care of inherit
|
|
if (token.value === 'inherit' || token.value === 'inherit inherit inherit') {
|
|
color.value = style.value = width.value = 'inherit';
|
|
return result;
|
|
}
|
|
|
|
// NOTE: usually users don't follow the required order of parts in this shorthand,
|
|
// so we'll try to parse it caring as little about order as possible
|
|
|
|
var parts = new Splitter(' ').split(token.value), w;
|
|
|
|
if (parts.length === 0) {
|
|
return result;
|
|
}
|
|
|
|
if (parts.length >= 1) {
|
|
// Try to find -width, excluding inherit because that can be anything
|
|
w = parts.filter(function(p) { return p !== 'inherit' && validator.isValidOutlineWidth(p); });
|
|
if (w.length) {
|
|
width.value = w[0];
|
|
parts.splice(parts.indexOf(w[0]), 1);
|
|
}
|
|
}
|
|
if (parts.length >= 1) {
|
|
// Try to find -style, excluding inherit because that can be anything
|
|
w = parts.filter(function(p) { return p !== 'inherit' && validator.isValidOutlineStyle(p); });
|
|
if (w.length) {
|
|
style.value = w[0];
|
|
parts.splice(parts.indexOf(w[0]), 1);
|
|
}
|
|
}
|
|
if (parts.length >= 1) {
|
|
// Find -color but this time can catch inherit
|
|
w = parts.filter(function(p) { return validator.isValidOutlineColor(p); });
|
|
if (w.length) {
|
|
color.value = w[0];
|
|
parts.splice(parts.indexOf(w[0]), 1);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
breakUp.outline = function(token) {
|
|
return breakUp._widthStyleColor(token, 'outline', ['color', 'style', 'width']);
|
|
};
|
|
|
|
breakUp.border = function(token) {
|
|
return breakUp._widthStyleColor(token, 'border', ['width', 'style', 'color']);
|
|
};
|
|
|
|
breakUp.borderRadius = function(token) {
|
|
var parts = token.value.split('/');
|
|
if (parts.length == 1)
|
|
return breakUp.fourBySpaces(token);
|
|
|
|
var horizontalPart = token.clone();
|
|
var verticalPart = token.clone();
|
|
|
|
horizontalPart.value = parts[0];
|
|
verticalPart.value = parts[1];
|
|
|
|
var horizontalBreakUp = breakUp.fourBySpaces(horizontalPart);
|
|
var verticalBreakUp = breakUp.fourBySpaces(verticalPart);
|
|
|
|
for (var i = 0; i < 4; i++) {
|
|
horizontalBreakUp[i].value = [horizontalBreakUp[i].value, verticalBreakUp[i].value];
|
|
}
|
|
|
|
return horizontalBreakUp;
|
|
};
|
|
|
|
// Contains functions that can put together shorthands from their components
|
|
// NOTE: correct order of tokens is assumed inside these functions!
|
|
var putTogether = {
|
|
// Use this for properties which have four unit values (margin, padding, etc.)
|
|
// NOTE: optimizes to shorter forms too (that only specify 1, 2, or 3 values)
|
|
fourUnits: function (prop, tokens, isImportant) {
|
|
// See about irrelevant tokens
|
|
// NOTE: This will enable some crazy optimalizations for us.
|
|
if (tokens[0].isIrrelevant)
|
|
tokens[0].value = tokens[2].value;
|
|
if (tokens[2].isIrrelevant)
|
|
tokens[2].value = tokens[0].value;
|
|
if (tokens[1].isIrrelevant)
|
|
tokens[1].value = tokens[3].value;
|
|
if (tokens[3].isIrrelevant)
|
|
tokens[3].value = tokens[1].value;
|
|
|
|
if (tokens[0].isIrrelevant && tokens[2].isIrrelevant) {
|
|
if (tokens[1].value === tokens[3].value)
|
|
tokens[0].value = tokens[2].value = tokens[1].value;
|
|
else
|
|
tokens[0].value = tokens[2].value = '0';
|
|
}
|
|
if (tokens[1].isIrrelevant && tokens[3].isIrrelevant) {
|
|
if (tokens[0].value === tokens[2].value)
|
|
tokens[1].value = tokens[3].value = tokens[0].value;
|
|
else
|
|
tokens[1].value = tokens[3].value = '0';
|
|
}
|
|
|
|
var result = new Token(prop, tokens[0].value, isImportant);
|
|
result.granularValues = [];
|
|
result.granularValues[tokens[0].prop] = tokens[0].value;
|
|
result.granularValues[tokens[1].prop] = tokens[1].value;
|
|
result.granularValues[tokens[2].prop] = tokens[2].value;
|
|
result.granularValues[tokens[3].prop] = tokens[3].value;
|
|
|
|
// If all of them are irrelevant
|
|
if (tokens[0].isIrrelevant && tokens[1].isIrrelevant && tokens[2].isIrrelevant && tokens[3].isIrrelevant) {
|
|
result.value = processable[prop].shortestValue || processable[prop].defaultValue;
|
|
return result;
|
|
}
|
|
|
|
// 1-value short form: all four components are equal
|
|
if (tokens[0].value === tokens[1].value && tokens[0].value === tokens[2].value && tokens[0].value === tokens[3].value) {
|
|
return result;
|
|
}
|
|
result.value += ' ' + tokens[1].value;
|
|
// 2-value short form: first and third; second and fourth values are equal
|
|
if (tokens[0].value === tokens[2].value && tokens[1].value === tokens[3].value) {
|
|
return result;
|
|
}
|
|
result.value += ' ' + tokens[2].value;
|
|
// 3-value short form: second and fourth values are equal
|
|
if (tokens[1].value === tokens[3].value) {
|
|
return result;
|
|
}
|
|
// 4-value form (none of the above optimalizations could be accomplished)
|
|
result.value += ' ' + tokens[3].value;
|
|
return result;
|
|
},
|
|
// Puts together the components by spaces and omits default values (this is the case for most shorthands)
|
|
bySpacesOmitDefaults: function (prop, tokens, isImportant, meta) {
|
|
var result = new Token(prop, '', isImportant);
|
|
|
|
// Get irrelevant tokens
|
|
var irrelevantTokens = tokens.filter(function (t) { return t.isIrrelevant; });
|
|
|
|
// If every token is irrelevant, return shortest possible value, fallback to default value
|
|
if (irrelevantTokens.length === tokens.length) {
|
|
result.isIrrelevant = true;
|
|
result.value = processable[prop].shortestValue || processable[prop].defaultValue;
|
|
return result;
|
|
}
|
|
|
|
// This will be the value of the shorthand if all the components are default
|
|
var valueIfAllDefault = processable[prop].defaultValue;
|
|
|
|
// Go through all tokens and concatenate their values as necessary
|
|
for (var i = 0; i < tokens.length; i++) {
|
|
var token = tokens[i];
|
|
|
|
// Set granular value so that other parts of the code can use this for optimalization opportunities
|
|
result.granularValues = result.granularValues || { };
|
|
result.granularValues[token.prop] = token.value;
|
|
|
|
// Use irrelevant tokens for optimalization opportunity
|
|
if (token.isIrrelevant) {
|
|
// Get shortest possible value, fallback to default value
|
|
var tokenShortest = processable[token.prop].shortestValue || processable[token.prop].defaultValue;
|
|
// If the shortest possible value of this token is shorter than the default value of the shorthand, use it instead
|
|
if (tokenShortest.length < valueIfAllDefault.length) {
|
|
valueIfAllDefault = tokenShortest;
|
|
}
|
|
}
|
|
|
|
// Omit default / irrelevant value
|
|
if (token.isIrrelevant || (processable[token.prop] && processable[token.prop].defaultValue === token.value)) {
|
|
continue;
|
|
}
|
|
|
|
if (meta && meta.partsCount && meta.position < meta.partsCount - 1 && processable[token.prop].multiValueLastOnly)
|
|
continue;
|
|
|
|
var requiresPreceeding = processable[token.prop].shorthandFollows;
|
|
if (requiresPreceeding && (tokens[i - 1].value == processable[requiresPreceeding].defaultValue)) {
|
|
result.value += ' ' + tokens[i - 1].value;
|
|
}
|
|
|
|
result.value += (processable[token.prop].prefixShorthandValueWith || ' ') + token.value;
|
|
}
|
|
|
|
result.value = result.value.trim();
|
|
if (!result.value) {
|
|
result.value = valueIfAllDefault;
|
|
}
|
|
|
|
return result;
|
|
},
|
|
commaSeparatedMulitpleValues: function (assembleFunction) {
|
|
return function(prop, tokens, isImportant) {
|
|
var tokenSplitLengths = tokens.map(function (token) {
|
|
return new Splitter(',').split(token.value).length;
|
|
});
|
|
var partsCount = Math.max.apply(Math, tokenSplitLengths);
|
|
|
|
if (partsCount == 1)
|
|
return assembleFunction(prop, tokens, isImportant);
|
|
|
|
var merged = [];
|
|
|
|
for (var i = 0; i < partsCount; i++) {
|
|
merged.push([]);
|
|
|
|
for (var j = 0; j < tokens.length; j++) {
|
|
var split = new Splitter(',').split(tokens[j].value);
|
|
merged[i].push(split[i] || split[0]);
|
|
}
|
|
}
|
|
|
|
var mergedValues = [];
|
|
var firstProcessed;
|
|
for (i = 0; i < partsCount; i++) {
|
|
var newTokens = [];
|
|
for (var k = 0, n = merged[i].length; k < n; k++) {
|
|
var newToken = tokens[k].clone();
|
|
newToken.value = merged[i][k];
|
|
newTokens.push(newToken);
|
|
}
|
|
|
|
var meta = {
|
|
partsCount: partsCount,
|
|
position: i
|
|
};
|
|
var processed = assembleFunction(prop, newTokens, isImportant, meta);
|
|
mergedValues.push(processed.value);
|
|
|
|
if (!firstProcessed)
|
|
firstProcessed = processed;
|
|
}
|
|
|
|
firstProcessed.value = mergedValues.join(',');
|
|
return firstProcessed;
|
|
};
|
|
},
|
|
// Handles the cases when some or all the fine-grained properties are set to inherit
|
|
takeCareOfInherit: function (innerFunc) {
|
|
return function (prop, tokens, isImportant, meta) {
|
|
// Filter out the inheriting and non-inheriting tokens in one iteration
|
|
var inheritingTokens = [];
|
|
var nonInheritingTokens = [];
|
|
var result2Shorthandable = [];
|
|
var i;
|
|
for (i = 0; i < tokens.length; i++) {
|
|
if (tokens[i].value === 'inherit') {
|
|
inheritingTokens.push(tokens[i]);
|
|
|
|
// Indicate that this property is irrelevant and its value can safely be set to anything else
|
|
var r2s = new Token(tokens[i].prop, tokens[i].isImportant);
|
|
r2s.isIrrelevant = true;
|
|
result2Shorthandable.push(r2s);
|
|
} else {
|
|
nonInheritingTokens.push(tokens[i]);
|
|
result2Shorthandable.push(tokens[i]);
|
|
}
|
|
}
|
|
|
|
if (nonInheritingTokens.length === 0) {
|
|
// When all the tokens are 'inherit'
|
|
return new Token(prop, 'inherit', isImportant);
|
|
} else if (inheritingTokens.length > 0) {
|
|
// When some (but not all) of the tokens are 'inherit'
|
|
|
|
// Result 1. Shorthand just the inherit values and have it overridden with the non-inheriting ones
|
|
var result1 = [new Token(prop, 'inherit', isImportant)].concat(nonInheritingTokens);
|
|
|
|
// Result 2. Shorthand every non-inherit value and then have it overridden with the inheriting ones
|
|
var result2 = [innerFunc(prop, result2Shorthandable, isImportant, meta)].concat(inheritingTokens);
|
|
|
|
// Return whichever is shorter
|
|
var dl1 = Token.getDetokenizedLength(result1);
|
|
var dl2 = Token.getDetokenizedLength(result2);
|
|
|
|
return dl1 < dl2 ? result1 : result2;
|
|
} else {
|
|
// When none of tokens are 'inherit'
|
|
return innerFunc(prop, tokens, isImportant, meta);
|
|
}
|
|
};
|
|
},
|
|
borderRadius: function (prop, tokens, isImportant) {
|
|
var verticalTokens = [];
|
|
var newTokens = [];
|
|
|
|
for (var i = 0, l = tokens.length; i < l; i++) {
|
|
var token = tokens[i];
|
|
var newToken = token.clone();
|
|
newTokens.push(newToken);
|
|
if (!Array.isArray(token.value))
|
|
continue;
|
|
|
|
if (token.value.length > 1) {
|
|
verticalTokens.push({
|
|
prop: token.prop,
|
|
value: token.value[1],
|
|
isImportant: token.isImportant
|
|
});
|
|
}
|
|
|
|
newToken.value = token.value[0];
|
|
}
|
|
|
|
var result = putTogether.takeCareOfInherit(putTogether.fourUnits)(prop, newTokens, isImportant);
|
|
if (verticalTokens.length > 0) {
|
|
var verticalResult = putTogether.takeCareOfInherit(putTogether.fourUnits)(prop, verticalTokens, isImportant);
|
|
if (result.value != verticalResult.value)
|
|
result.value += '/' + verticalResult.value;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
};
|
|
|
|
// Properties to process
|
|
// Extend this object in order to add support for more properties in the optimizer.
|
|
//
|
|
// Each key in this object represents a CSS property and should be an object.
|
|
// Such an object contains properties that describe how the represented CSS property should be handled.
|
|
// Possible options:
|
|
//
|
|
// * components: array (Only specify for shorthand properties.)
|
|
// Contains the names of the granular properties this shorthand compacts.
|
|
//
|
|
// * canOverride: function (Default is canOverride.sameValue - meaning that they'll only be merged if they have the same value.)
|
|
// Returns whether two tokens of this property can be merged with each other.
|
|
// This property has no meaning for shorthands.
|
|
//
|
|
// * defaultValue: string
|
|
// Specifies the default value of the property according to the CSS standard.
|
|
// For shorthand, this is used when every component is set to its default value, therefore it should be the shortest possible default value of all the components.
|
|
//
|
|
// * shortestValue: string
|
|
// Specifies the shortest possible value the property can possibly have.
|
|
// (Falls back to defaultValue if unspecified.)
|
|
//
|
|
// * breakUp: function (Only specify for shorthand properties.)
|
|
// Breaks the shorthand up to its components.
|
|
//
|
|
// * putTogether: function (Only specify for shorthand properties.)
|
|
// Puts the shorthand together from its components.
|
|
//
|
|
var processable = {
|
|
'color': {
|
|
canOverride: canOverride.color,
|
|
defaultValue: 'transparent',
|
|
shortestValue: 'red'
|
|
},
|
|
// background ------------------------------------------------------------------------------
|
|
'background': {
|
|
components: [
|
|
'background-image',
|
|
'background-position',
|
|
'background-size',
|
|
'background-repeat',
|
|
'background-attachment',
|
|
'background-color'
|
|
],
|
|
breakUp: breakUp.commaSeparatedMulitpleValues(breakUp.background),
|
|
putTogether: putTogether.commaSeparatedMulitpleValues(
|
|
putTogether.takeCareOfInherit(putTogether.bySpacesOmitDefaults)
|
|
),
|
|
defaultValue: '0 0',
|
|
shortestValue: '0'
|
|
},
|
|
'background-color': {
|
|
canOverride: canOverride.color,
|
|
defaultValue: 'transparent',
|
|
multiValueLastOnly: true,
|
|
shortestValue: 'red'
|
|
},
|
|
'background-image': {
|
|
canOverride: canOverride.backgroundImage,
|
|
defaultValue: 'none'
|
|
},
|
|
'background-repeat': {
|
|
canOverride: canOverride.always,
|
|
defaultValue: 'repeat'
|
|
},
|
|
'background-position': {
|
|
canOverride: canOverride.always,
|
|
defaultValue: '0 0',
|
|
shortestValue: '0'
|
|
},
|
|
'background-size': {
|
|
canOverride: canOverride.always,
|
|
defaultValue: 'auto',
|
|
shortestValue: '0 0',
|
|
prefixShorthandValueWith: '/',
|
|
shorthandFollows: 'background-position'
|
|
},
|
|
'background-attachment': {
|
|
canOverride: canOverride.always,
|
|
defaultValue: 'scroll'
|
|
},
|
|
'border': {
|
|
breakUp: breakUp.border,
|
|
canOverride: canOverride.border,
|
|
components: [
|
|
'border-width',
|
|
'border-style',
|
|
'border-color'
|
|
],
|
|
defaultValue: 'none',
|
|
putTogether: putTogether.takeCareOfInherit(putTogether.bySpacesOmitDefaults)
|
|
},
|
|
'border-color': {
|
|
canOverride: canOverride.color,
|
|
defaultValue: 'none'
|
|
},
|
|
'border-style': {
|
|
canOverride: canOverride.always,
|
|
defaultValue: 'none'
|
|
},
|
|
'border-width': {
|
|
canOverride: canOverride.unit,
|
|
defaultValue: 'medium',
|
|
shortestValue: '0'
|
|
},
|
|
// list-style ------------------------------------------------------------------------------
|
|
'list-style': {
|
|
components: [
|
|
'list-style-type',
|
|
'list-style-position',
|
|
'list-style-image'
|
|
],
|
|
canOverride: canOverride.always,
|
|
breakUp: breakUp.listStyle,
|
|
putTogether: putTogether.takeCareOfInherit(putTogether.bySpacesOmitDefaults),
|
|
defaultValue: 'outside', // can't use 'disc' because that'd override default 'decimal' for <ol>
|
|
shortestValue: 'none'
|
|
},
|
|
'list-style-type' : {
|
|
canOverride: canOverride.always,
|
|
shortestValue: 'none',
|
|
defaultValue: '__hack'
|
|
// NOTE: we can't tell the real default value here, it's 'disc' for <ul> and 'decimal' for <ol>
|
|
// -- this is a hack, but it doesn't matter because this value will be either overridden or it will disappear at the final step anyway
|
|
},
|
|
'list-style-position' : {
|
|
canOverride: canOverride.always,
|
|
defaultValue: 'outside',
|
|
shortestValue: 'inside'
|
|
},
|
|
'list-style-image' : {
|
|
canOverride: canOverride.always,
|
|
defaultValue: 'none'
|
|
},
|
|
// outline ------------------------------------------------------------------------------
|
|
'outline': {
|
|
components: [
|
|
'outline-color',
|
|
'outline-style',
|
|
'outline-width'
|
|
],
|
|
breakUp: breakUp.outline,
|
|
putTogether: putTogether.takeCareOfInherit(putTogether.bySpacesOmitDefaults),
|
|
defaultValue: '0'
|
|
},
|
|
'outline-color': {
|
|
canOverride: canOverride.color,
|
|
defaultValue: 'invert',
|
|
shortestValue: 'red'
|
|
},
|
|
'outline-style': {
|
|
canOverride: canOverride.always,
|
|
defaultValue: 'none'
|
|
},
|
|
'outline-width': {
|
|
canOverride: canOverride.unit,
|
|
defaultValue: 'medium',
|
|
shortestValue: '0'
|
|
},
|
|
// transform
|
|
'-moz-transform': {
|
|
canOverride: canOverride.sameFunctionOrValue
|
|
},
|
|
'-ms-transform': {
|
|
canOverride: canOverride.sameFunctionOrValue
|
|
},
|
|
'-webkit-transform': {
|
|
canOverride: canOverride.sameFunctionOrValue
|
|
},
|
|
'transform': {
|
|
canOverride: canOverride.sameFunctionOrValue
|
|
}
|
|
};
|
|
|
|
var addFourValueShorthand = function (prop, components, options) {
|
|
options = options || {};
|
|
processable[prop] = {
|
|
components: components,
|
|
breakUp: options.breakUp || breakUp.fourBySpaces,
|
|
putTogether: options.putTogether || putTogether.takeCareOfInherit(putTogether.fourUnits),
|
|
defaultValue: options.defaultValue || '0',
|
|
shortestValue: options.shortestValue
|
|
};
|
|
for (var i = 0; i < components.length; i++) {
|
|
processable[components[i]] = {
|
|
breakUp: options.breakUp || breakUp.fourBySpaces,
|
|
canOverride: options.canOverride || canOverride.unit,
|
|
defaultValue: options.defaultValue || '0',
|
|
shortestValue: options.shortestValue
|
|
};
|
|
}
|
|
};
|
|
|
|
['', '-moz-', '-o-', '-webkit-'].forEach(function (prefix) {
|
|
addFourValueShorthand(prefix + 'border-radius', [
|
|
prefix + 'border-top-left-radius',
|
|
prefix + 'border-top-right-radius',
|
|
prefix + 'border-bottom-right-radius',
|
|
prefix + 'border-bottom-left-radius'
|
|
], {
|
|
breakUp: breakUp.borderRadius,
|
|
putTogether: putTogether.borderRadius
|
|
});
|
|
});
|
|
|
|
addFourValueShorthand('border-color', [
|
|
'border-top-color',
|
|
'border-right-color',
|
|
'border-bottom-color',
|
|
'border-left-color'
|
|
], {
|
|
breakUp: breakUp.fourBySpaces,
|
|
canOverride: canOverride.color,
|
|
defaultValue: 'currentColor',
|
|
shortestValue: 'red'
|
|
});
|
|
|
|
addFourValueShorthand('border-style', [
|
|
'border-top-style',
|
|
'border-right-style',
|
|
'border-bottom-style',
|
|
'border-left-style'
|
|
], {
|
|
breakUp: breakUp.fourBySpaces,
|
|
canOverride: canOverride.always,
|
|
defaultValue: 'none'
|
|
});
|
|
|
|
addFourValueShorthand('border-width', [
|
|
'border-top-width',
|
|
'border-right-width',
|
|
'border-bottom-width',
|
|
'border-left-width'
|
|
], {
|
|
defaultValue: 'medium',
|
|
shortestValue: '0'
|
|
});
|
|
|
|
addFourValueShorthand('padding', [
|
|
'padding-top',
|
|
'padding-right',
|
|
'padding-bottom',
|
|
'padding-left'
|
|
]);
|
|
|
|
addFourValueShorthand('margin', [
|
|
'margin-top',
|
|
'margin-right',
|
|
'margin-bottom',
|
|
'margin-left'
|
|
]);
|
|
|
|
// Set some stuff iteratively
|
|
for (var proc in processable) {
|
|
if (!processable.hasOwnProperty(proc))
|
|
continue;
|
|
|
|
var currDesc = processable[proc];
|
|
|
|
if (!(currDesc.components instanceof Array) || currDesc.components.length === 0)
|
|
continue;
|
|
|
|
currDesc.isShorthand = true;
|
|
|
|
for (var cI = 0; cI < currDesc.components.length; cI++) {
|
|
if (!processable[currDesc.components[cI]]) {
|
|
throw new Error('"' + currDesc.components[cI] + '" is defined as a component of "' + proc + '" but isn\'t defined in processable.');
|
|
}
|
|
processable[currDesc.components[cI]].componentOf = proc;
|
|
}
|
|
}
|
|
|
|
var Token = tokenModule.createTokenPrototype(processable);
|
|
|
|
return {
|
|
implementedFor: /background|border|color|list|margin|outline|padding|transform/,
|
|
processable: processable,
|
|
Token: Token
|
|
};
|
|
})();
|