blob: 73f42a10e8420ca884786c4be41bbd4507654965 [file] [log] [blame]
// Contains the interpretation of CSS properties, as used by the property optimizer
var breakUp = require('./break-up');
var canOverride = require('./can-override');
var restore = require('./restore');
var override = require('../../utils/override');
// 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
// 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.
//
// * restore: function (Only specify for shorthand properties.)
// Puts the shorthand together from its components.
//
var compactable = {
'animation': {
canOverride: canOverride.generic.components([
canOverride.generic.time,
canOverride.generic.timingFunction,
canOverride.generic.time,
canOverride.property.animationIterationCount,
canOverride.property.animationDirection,
canOverride.property.animationFillMode,
canOverride.property.animationPlayState,
canOverride.property.animationName
]),
components: [
'animation-duration',
'animation-timing-function',
'animation-delay',
'animation-iteration-count',
'animation-direction',
'animation-fill-mode',
'animation-play-state',
'animation-name'
],
breakUp: breakUp.multiplex(breakUp.animation),
defaultValue: 'none',
restore: restore.multiplex(restore.withoutDefaults),
shorthand: true,
vendorPrefixes: [
'-moz-',
'-o-',
'-webkit-'
]
},
'animation-delay': {
canOverride: canOverride.generic.time,
componentOf: [
'animation'
],
defaultValue: '0s',
intoMultiplexMode: 'real',
vendorPrefixes: [
'-moz-',
'-o-',
'-webkit-'
]
},
'animation-direction': {
canOverride: canOverride.property.animationDirection,
componentOf: [
'animation'
],
defaultValue: 'normal',
intoMultiplexMode: 'real',
vendorPrefixes: [
'-moz-',
'-o-',
'-webkit-'
]
},
'animation-duration': {
canOverride: canOverride.generic.time,
componentOf: [
'animation'
],
defaultValue: '0s',
intoMultiplexMode: 'real',
keepUnlessDefault: 'animation-delay',
vendorPrefixes: [
'-moz-',
'-o-',
'-webkit-'
]
},
'animation-fill-mode': {
canOverride: canOverride.property.animationFillMode,
componentOf: [
'animation'
],
defaultValue: 'none',
intoMultiplexMode: 'real',
vendorPrefixes: [
'-moz-',
'-o-',
'-webkit-'
]
},
'animation-iteration-count': {
canOverride: canOverride.property.animationIterationCount,
componentOf: [
'animation'
],
defaultValue: '1',
intoMultiplexMode: 'real',
vendorPrefixes: [
'-moz-',
'-o-',
'-webkit-'
]
},
'animation-name': {
canOverride: canOverride.property.animationName,
componentOf: [
'animation'
],
defaultValue: 'none',
intoMultiplexMode: 'real',
vendorPrefixes: [
'-moz-',
'-o-',
'-webkit-'
]
},
'animation-play-state': {
canOverride: canOverride.property.animationPlayState,
componentOf: [
'animation'
],
defaultValue: 'running',
intoMultiplexMode: 'real',
vendorPrefixes: [
'-moz-',
'-o-',
'-webkit-'
]
},
'animation-timing-function': {
canOverride: canOverride.generic.timingFunction,
componentOf: [
'animation'
],
defaultValue: 'ease',
intoMultiplexMode: 'real',
vendorPrefixes: [
'-moz-',
'-o-',
'-webkit-'
]
},
'background': {
canOverride: canOverride.generic.components([
canOverride.generic.image,
canOverride.property.backgroundPosition,
canOverride.property.backgroundSize,
canOverride.property.backgroundRepeat,
canOverride.property.backgroundAttachment,
canOverride.property.backgroundOrigin,
canOverride.property.backgroundClip,
canOverride.generic.color
]),
components: [
'background-image',
'background-position',
'background-size',
'background-repeat',
'background-attachment',
'background-origin',
'background-clip',
'background-color'
],
breakUp: breakUp.multiplex(breakUp.background),
defaultValue: '0 0',
restore: restore.multiplex(restore.background),
shortestValue: '0',
shorthand: true
},
'background-attachment': {
canOverride: canOverride.property.backgroundAttachment,
componentOf: [
'background'
],
defaultValue: 'scroll',
intoMultiplexMode: 'real'
},
'background-clip': {
canOverride: canOverride.property.backgroundClip,
componentOf: [
'background'
],
defaultValue: 'border-box',
intoMultiplexMode: 'real',
shortestValue: 'border-box'
},
'background-color': {
canOverride: canOverride.generic.color,
componentOf: [
'background'
],
defaultValue: 'transparent',
intoMultiplexMode: 'real', // otherwise real color will turn into default since color appears in last multiplex only
multiplexLastOnly: true,
nonMergeableValue: 'none',
shortestValue: 'red'
},
'background-image': {
canOverride: canOverride.generic.image,
componentOf: [
'background'
],
defaultValue: 'none',
intoMultiplexMode: 'default'
},
'background-origin': {
canOverride: canOverride.property.backgroundOrigin,
componentOf: [
'background'
],
defaultValue: 'padding-box',
intoMultiplexMode: 'real',
shortestValue: 'border-box'
},
'background-position': {
canOverride: canOverride.property.backgroundPosition,
componentOf: [
'background'
],
defaultValue: ['0', '0'],
doubleValues: true,
intoMultiplexMode: 'real',
shortestValue: '0'
},
'background-repeat': {
canOverride: canOverride.property.backgroundRepeat,
componentOf: [
'background'
],
defaultValue: ['repeat'],
doubleValues: true,
intoMultiplexMode: 'real'
},
'background-size': {
canOverride: canOverride.property.backgroundSize,
componentOf: [
'background'
],
defaultValue: ['auto'],
doubleValues: true,
intoMultiplexMode: 'real',
shortestValue: '0 0'
},
'bottom': {
canOverride: canOverride.property.bottom,
defaultValue: 'auto'
},
'border': {
breakUp: breakUp.border,
canOverride: canOverride.generic.components([
canOverride.generic.unit,
canOverride.property.borderStyle,
canOverride.generic.color
]),
components: [
'border-width',
'border-style',
'border-color'
],
defaultValue: 'none',
overridesShorthands: [
'border-bottom',
'border-left',
'border-right',
'border-top'
],
restore: restore.withoutDefaults,
shorthand: true,
shorthandComponents: true
},
'border-bottom': {
breakUp: breakUp.border,
canOverride: canOverride.generic.components([
canOverride.generic.unit,
canOverride.property.borderStyle,
canOverride.generic.color
]),
components: [
'border-bottom-width',
'border-bottom-style',
'border-bottom-color'
],
defaultValue: 'none',
restore: restore.withoutDefaults,
shorthand: true
},
'border-bottom-color': {
canOverride: canOverride.generic.color,
componentOf: [
'border-bottom',
'border-color'
],
defaultValue: 'none'
},
'border-bottom-left-radius': {
canOverride: canOverride.generic.unit,
componentOf: [
'border-radius'
],
defaultValue: '0',
vendorPrefixes: [
'-moz-',
'-o-'
]
},
'border-bottom-right-radius': {
canOverride: canOverride.generic.unit,
componentOf: [
'border-radius'
],
defaultValue: '0',
vendorPrefixes: [
'-moz-',
'-o-'
]
},
'border-bottom-style': {
canOverride: canOverride.property.borderStyle,
componentOf: [
'border-bottom',
'border-style'
],
defaultValue: 'none'
},
'border-bottom-width': {
canOverride: canOverride.generic.unit,
componentOf: [
'border-bottom',
'border-width'
],
defaultValue: 'medium',
oppositeTo: 'border-top-width',
shortestValue: '0'
},
'border-collapse': {
canOverride: canOverride.property.borderCollapse,
defaultValue: 'separate'
},
'border-color': {
breakUp: breakUp.fourValues,
canOverride: canOverride.generic.components([
canOverride.generic.color,
canOverride.generic.color,
canOverride.generic.color,
canOverride.generic.color
]),
componentOf: [
'border'
],
components: [
'border-top-color',
'border-right-color',
'border-bottom-color',
'border-left-color'
],
defaultValue: 'none',
restore: restore.fourValues,
shortestValue: 'red',
shorthand: true
},
'border-left': {
breakUp: breakUp.border,
canOverride: canOverride.generic.components([
canOverride.generic.unit,
canOverride.property.borderStyle,
canOverride.generic.color
]),
components: [
'border-left-width',
'border-left-style',
'border-left-color'
],
defaultValue: 'none',
restore: restore.withoutDefaults,
shorthand: true
},
'border-left-color': {
canOverride: canOverride.generic.color,
componentOf: [
'border-color',
'border-left'
],
defaultValue: 'none'
},
'border-left-style': {
canOverride: canOverride.property.borderStyle,
componentOf: [
'border-left',
'border-style'
],
defaultValue: 'none'
},
'border-left-width': {
canOverride: canOverride.generic.unit,
componentOf: [
'border-left',
'border-width'
],
defaultValue: 'medium',
oppositeTo: 'border-right-width',
shortestValue: '0'
},
'border-radius': {
breakUp: breakUp.borderRadius,
canOverride: canOverride.generic.components([
canOverride.generic.unit,
canOverride.generic.unit,
canOverride.generic.unit,
canOverride.generic.unit
]),
components: [
'border-top-left-radius',
'border-top-right-radius',
'border-bottom-right-radius',
'border-bottom-left-radius'
],
defaultValue: '0',
restore: restore.borderRadius,
shorthand: true,
vendorPrefixes: [
'-moz-',
'-o-'
]
},
'border-right': {
breakUp: breakUp.border,
canOverride: canOverride.generic.components([
canOverride.generic.unit,
canOverride.property.borderStyle,
canOverride.generic.color
]),
components: [
'border-right-width',
'border-right-style',
'border-right-color'
],
defaultValue: 'none',
restore: restore.withoutDefaults,
shorthand: true
},
'border-right-color': {
canOverride: canOverride.generic.color,
componentOf: [
'border-color',
'border-right'
],
defaultValue: 'none'
},
'border-right-style': {
canOverride: canOverride.property.borderStyle,
componentOf: [
'border-right',
'border-style'
],
defaultValue: 'none'
},
'border-right-width': {
canOverride: canOverride.generic.unit,
componentOf: [
'border-right',
'border-width'
],
defaultValue: 'medium',
oppositeTo: 'border-left-width',
shortestValue: '0'
},
'border-style': {
breakUp: breakUp.fourValues,
canOverride: canOverride.generic.components([
canOverride.property.borderStyle,
canOverride.property.borderStyle,
canOverride.property.borderStyle,
canOverride.property.borderStyle
]),
componentOf: [
'border'
],
components: [
'border-top-style',
'border-right-style',
'border-bottom-style',
'border-left-style'
],
defaultValue: 'none',
restore: restore.fourValues,
shorthand: true
},
'border-top': {
breakUp: breakUp.border,
canOverride: canOverride.generic.components([
canOverride.generic.unit,
canOverride.property.borderStyle,
canOverride.generic.color
]),
components: [
'border-top-width',
'border-top-style',
'border-top-color'
],
defaultValue: 'none',
restore: restore.withoutDefaults,
shorthand: true
},
'border-top-color': {
canOverride: canOverride.generic.color,
componentOf: [
'border-color',
'border-top'
],
defaultValue: 'none'
},
'border-top-left-radius': {
canOverride: canOverride.generic.unit,
componentOf: [
'border-radius'
],
defaultValue: '0',
vendorPrefixes: [
'-moz-',
'-o-'
]
},
'border-top-right-radius': {
canOverride: canOverride.generic.unit,
componentOf: [
'border-radius'
],
defaultValue: '0',
vendorPrefixes: [
'-moz-',
'-o-'
]
},
'border-top-style': {
canOverride: canOverride.property.borderStyle,
componentOf: [
'border-style',
'border-top'
],
defaultValue: 'none'
},
'border-top-width': {
canOverride: canOverride.generic.unit,
componentOf: [
'border-top',
'border-width'
],
defaultValue: 'medium',
oppositeTo: 'border-bottom-width',
shortestValue: '0'
},
'border-width': {
breakUp: breakUp.fourValues,
canOverride: canOverride.generic.components([
canOverride.generic.unit,
canOverride.generic.unit,
canOverride.generic.unit,
canOverride.generic.unit
]),
componentOf: [
'border'
],
components: [
'border-top-width',
'border-right-width',
'border-bottom-width',
'border-left-width'
],
defaultValue: 'medium',
restore: restore.fourValues,
shortestValue: '0',
shorthand: true
},
'clear': {
canOverride: canOverride.property.clear,
defaultValue: 'none'
},
'color': {
canOverride: canOverride.generic.color,
defaultValue: 'transparent',
shortestValue: 'red'
},
'cursor': {
canOverride: canOverride.property.cursor,
defaultValue: 'auto'
},
'display': {
canOverride: canOverride.property.display,
},
'float': {
canOverride: canOverride.property.float,
defaultValue: 'none'
},
'font': {
breakUp: breakUp.font,
canOverride: canOverride.generic.components([
canOverride.property.fontStyle,
canOverride.property.fontVariant,
canOverride.property.fontWeight,
canOverride.property.fontStretch,
canOverride.generic.unit,
canOverride.generic.unit,
canOverride.property.fontFamily
]),
components: [
'font-style',
'font-variant',
'font-weight',
'font-stretch',
'font-size',
'line-height',
'font-family'
],
restore: restore.font,
shorthand: true
},
'font-family': {
canOverride: canOverride.property.fontFamily,
defaultValue: 'user|agent|specific'
},
'font-size': {
canOverride: canOverride.generic.unit,
defaultValue: 'medium',
shortestValue: '0'
},
'font-stretch': {
canOverride: canOverride.property.fontStretch,
defaultValue: 'normal'
},
'font-style': {
canOverride: canOverride.property.fontStyle,
defaultValue: 'normal'
},
'font-variant': {
canOverride: canOverride.property.fontVariant,
defaultValue: 'normal'
},
'font-weight': {
canOverride: canOverride.property.fontWeight,
defaultValue: 'normal',
shortestValue: '400'
},
'height': {
canOverride: canOverride.generic.unit,
defaultValue: 'auto',
shortestValue: '0'
},
'left': {
canOverride: canOverride.property.left,
defaultValue: 'auto'
},
'line-height': {
canOverride: canOverride.generic.unitOrNumber,
defaultValue: 'normal',
shortestValue: '0'
},
'list-style': {
canOverride: canOverride.generic.components([
canOverride.property.listStyleType,
canOverride.property.listStylePosition,
canOverride.property.listStyleImage
]),
components: [
'list-style-type',
'list-style-position',
'list-style-image'
],
breakUp: breakUp.listStyle,
restore: restore.withoutDefaults,
defaultValue: 'outside', // can't use 'disc' because that'd override default 'decimal' for <ol>
shortestValue: 'none',
shorthand: true
},
'list-style-image' : {
canOverride: canOverride.generic.image,
componentOf: [
'list-style'
],
defaultValue: 'none'
},
'list-style-position' : {
canOverride: canOverride.property.listStylePosition,
componentOf: [
'list-style'
],
defaultValue: 'outside',
shortestValue: 'inside'
},
'list-style-type' : {
canOverride: canOverride.property.listStyleType,
componentOf: [
'list-style'
],
// 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
defaultValue: 'decimal|disc',
shortestValue: 'none'
},
'margin': {
breakUp: breakUp.fourValues,
canOverride: canOverride.generic.components([
canOverride.generic.unit,
canOverride.generic.unit,
canOverride.generic.unit,
canOverride.generic.unit
]),
components: [
'margin-top',
'margin-right',
'margin-bottom',
'margin-left'
],
defaultValue: '0',
restore: restore.fourValues,
shorthand: true
},
'margin-bottom': {
canOverride: canOverride.generic.unit,
componentOf: [
'margin'
],
defaultValue: '0',
oppositeTo: 'margin-top'
},
'margin-left': {
canOverride: canOverride.generic.unit,
componentOf: [
'margin'
],
defaultValue: '0',
oppositeTo: 'margin-right'
},
'margin-right': {
canOverride: canOverride.generic.unit,
componentOf: [
'margin'
],
defaultValue: '0',
oppositeTo: 'margin-left'
},
'margin-top': {
canOverride: canOverride.generic.unit,
componentOf: [
'margin'
],
defaultValue: '0',
oppositeTo: 'margin-bottom'
},
'outline': {
canOverride: canOverride.generic.components([
canOverride.generic.color,
canOverride.property.outlineStyle,
canOverride.generic.unit
]),
components: [
'outline-color',
'outline-style',
'outline-width'
],
breakUp: breakUp.outline,
restore: restore.withoutDefaults,
defaultValue: '0',
shorthand: true
},
'outline-color': {
canOverride: canOverride.generic.color,
componentOf: [
'outline'
],
defaultValue: 'invert',
shortestValue: 'red'
},
'outline-style': {
canOverride: canOverride.property.outlineStyle,
componentOf: [
'outline'
],
defaultValue: 'none'
},
'outline-width': {
canOverride: canOverride.generic.unit,
componentOf: [
'outline'
],
defaultValue: 'medium',
shortestValue: '0'
},
'overflow': {
canOverride: canOverride.property.overflow,
defaultValue: 'visible'
},
'overflow-x': {
canOverride: canOverride.property.overflow,
defaultValue: 'visible'
},
'overflow-y': {
canOverride: canOverride.property.overflow,
defaultValue: 'visible'
},
'padding': {
breakUp: breakUp.fourValues,
canOverride: canOverride.generic.components([
canOverride.generic.unit,
canOverride.generic.unit,
canOverride.generic.unit,
canOverride.generic.unit
]),
components: [
'padding-top',
'padding-right',
'padding-bottom',
'padding-left'
],
defaultValue: '0',
restore: restore.fourValues,
shorthand: true
},
'padding-bottom': {
canOverride: canOverride.generic.unit,
componentOf: [
'padding'
],
defaultValue: '0',
oppositeTo: 'padding-top'
},
'padding-left': {
canOverride: canOverride.generic.unit,
componentOf: [
'padding'
],
defaultValue: '0',
oppositeTo: 'padding-right'
},
'padding-right': {
canOverride: canOverride.generic.unit,
componentOf: [
'padding'
],
defaultValue: '0',
oppositeTo: 'padding-left'
},
'padding-top': {
canOverride: canOverride.generic.unit,
componentOf: [
'padding'
],
defaultValue: '0',
oppositeTo: 'padding-bottom'
},
'position': {
canOverride: canOverride.property.position,
defaultValue: 'static'
},
'right': {
canOverride: canOverride.property.right,
defaultValue: 'auto'
},
'text-align': {
canOverride: canOverride.property.textAlign,
// NOTE: we can't tell the real default value here, as it depends on default text direction
// this is a hack, but it doesn't matter because this value will be either overridden or
// it will disappear anyway
defaultValue: 'left|right'
},
'text-decoration': {
canOverride: canOverride.property.textDecoration,
defaultValue: 'none'
},
'text-overflow': {
canOverride: canOverride.property.textOverflow,
defaultValue: 'none'
},
'text-shadow': {
canOverride: canOverride.property.textShadow,
defaultValue: 'none'
},
'top': {
canOverride: canOverride.property.top,
defaultValue: 'auto'
},
'transform': {
canOverride: canOverride.property.transform,
vendorPrefixes: [
'-moz-',
'-ms-',
'-webkit-'
]
},
'transition': {
breakUp: breakUp.multiplex(breakUp.transition),
canOverride: canOverride.generic.components([
canOverride.property.transitionProperty,
canOverride.generic.time,
canOverride.generic.timingFunction,
canOverride.generic.time
]),
components: [
'transition-property',
'transition-duration',
'transition-timing-function',
'transition-delay'
],
defaultValue: 'none',
restore: restore.multiplex(restore.withoutDefaults),
shorthand: true,
vendorPrefixes: [
'-moz-',
'-o-',
'-webkit-'
]
},
'transition-delay': {
canOverride: canOverride.generic.time,
componentOf: [
'transition'
],
defaultValue: '0s',
intoMultiplexMode: 'real',
vendorPrefixes: [
'-moz-',
'-o-',
'-webkit-'
]
},
'transition-duration': {
canOverride: canOverride.generic.time,
componentOf: [
'transition'
],
defaultValue: '0s',
intoMultiplexMode: 'real',
vendorPrefixes: [
'-moz-',
'-o-',
'-webkit-'
]
},
'transition-property': {
canOverride: canOverride.generic.propertyName,
componentOf: [
'transition'
],
defaultValue: 'all',
intoMultiplexMode: 'placeholder',
placeholderValue: '_', // it's a short value that won't match any property and still be a valid `transition-property`
vendorPrefixes: [
'-moz-',
'-o-',
'-webkit-'
]
},
'transition-timing-function': {
canOverride: canOverride.generic.timingFunction,
componentOf: [
'transition'
],
defaultValue: 'ease',
intoMultiplexMode: 'real',
vendorPrefixes: [
'-moz-',
'-o-',
'-webkit-'
]
},
'vertical-align': {
canOverride: canOverride.property.verticalAlign,
defaultValue: 'baseline'
},
'visibility': {
canOverride: canOverride.property.visibility,
defaultValue: 'visible'
},
'white-space': {
canOverride: canOverride.property.whiteSpace,
defaultValue: 'normal'
},
'width': {
canOverride: canOverride.generic.unit,
defaultValue: 'auto',
shortestValue: '0'
},
'z-index': {
canOverride: canOverride.property.zIndex,
defaultValue: 'auto'
}
};
function cloneDescriptor(propertyName, prefix) {
var clonedDescriptor = override(compactable[propertyName], {});
if ('componentOf' in clonedDescriptor) {
clonedDescriptor.componentOf = clonedDescriptor.componentOf.map(function (shorthandName) {
return prefix + shorthandName;
});
}
if ('components' in clonedDescriptor) {
clonedDescriptor.components = clonedDescriptor.components.map(function (longhandName) {
return prefix + longhandName;
});
}
if ('keepUnlessDefault' in clonedDescriptor) {
clonedDescriptor.keepUnlessDefault = prefix + clonedDescriptor.keepUnlessDefault;
}
return clonedDescriptor;
}
// generate vendor-prefixed properties
var vendorPrefixedCompactable = {};
for (var propertyName in compactable) {
var descriptor = compactable[propertyName];
if (!('vendorPrefixes' in descriptor)) {
continue;
}
for (var i = 0; i < descriptor.vendorPrefixes.length; i++) {
var prefix = descriptor.vendorPrefixes[i];
var clonedDescriptor = cloneDescriptor(propertyName, prefix);
delete clonedDescriptor.vendorPrefixes;
vendorPrefixedCompactable[prefix + propertyName] = clonedDescriptor;
}
delete descriptor.vendorPrefixes;
}
module.exports = override(compactable, vendorPrefixedCompactable);