ruby-clean-css 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. data/.gitmodules +3 -0
  2. data/EXAMPLE.md +25 -0
  3. data/Gemfile +4 -0
  4. data/Gemfile.lock +30 -0
  5. data/LICENSE +19 -0
  6. data/README.md +106 -0
  7. data/lib/javascript/clean-css/.gitignore +4 -0
  8. data/lib/javascript/clean-css/.jshintignore +3 -0
  9. data/lib/javascript/clean-css/.jshintrc +14 -0
  10. data/lib/javascript/clean-css/.travis.yml +11 -0
  11. data/lib/javascript/clean-css/History.md +556 -0
  12. data/lib/javascript/clean-css/LICENSE +19 -0
  13. data/lib/javascript/clean-css/README.md +218 -0
  14. data/lib/javascript/clean-css/bin/cleancss +157 -0
  15. data/lib/javascript/clean-css/index.js +1 -0
  16. data/lib/javascript/clean-css/lib/clean.js +426 -0
  17. data/lib/javascript/clean-css/lib/colors/hsl-to-hex.js +64 -0
  18. data/lib/javascript/clean-css/lib/colors/long-to-short-hex.js +12 -0
  19. data/lib/javascript/clean-css/lib/colors/rgb-to-hex.js +14 -0
  20. data/lib/javascript/clean-css/lib/colors/shortener.js +174 -0
  21. data/lib/javascript/clean-css/lib/images/url-rebase.js +32 -0
  22. data/lib/javascript/clean-css/lib/images/url-rewriter.js +60 -0
  23. data/lib/javascript/clean-css/lib/imports/inliner.js +319 -0
  24. data/lib/javascript/clean-css/lib/properties/optimizer.js +276 -0
  25. data/lib/javascript/clean-css/lib/properties/override-compactor.js +116 -0
  26. data/lib/javascript/clean-css/lib/properties/processable.js +859 -0
  27. data/lib/javascript/clean-css/lib/properties/scanner.js +20 -0
  28. data/lib/javascript/clean-css/lib/properties/shorthand-compactor.js +244 -0
  29. data/lib/javascript/clean-css/lib/properties/token.js +184 -0
  30. data/lib/javascript/clean-css/lib/properties/validator.js +140 -0
  31. data/lib/javascript/clean-css/lib/selectors/empty-removal.js +30 -0
  32. data/lib/javascript/clean-css/lib/selectors/optimizer.js +341 -0
  33. data/lib/javascript/clean-css/lib/selectors/tokenizer.js +168 -0
  34. data/lib/javascript/clean-css/lib/text/comments.js +83 -0
  35. data/lib/javascript/clean-css/lib/text/escape-store.js +32 -0
  36. data/lib/javascript/clean-css/lib/text/expressions.js +73 -0
  37. data/lib/javascript/clean-css/lib/text/free.js +26 -0
  38. data/lib/javascript/clean-css/lib/text/name-quotes.js +37 -0
  39. data/lib/javascript/clean-css/lib/text/quote-scanner.js +91 -0
  40. data/lib/javascript/clean-css/lib/text/splitter.js +35 -0
  41. data/lib/javascript/clean-css/lib/text/urls.js +41 -0
  42. data/lib/javascript/clean-css/package.json +50 -0
  43. data/lib/javascript/clean-css/test/batch-test.js +56 -0
  44. data/lib/javascript/clean-css/test/bench.js +11 -0
  45. data/lib/javascript/clean-css/test/binary-test.js +323 -0
  46. data/lib/javascript/clean-css/test/data-bench/_partial.css +1 -0
  47. data/lib/javascript/clean-css/test/data-bench/complex.css +4 -0
  48. data/lib/javascript/clean-css/test/data/129-assets/assets/ui.css +2 -0
  49. data/lib/javascript/clean-css/test/data/129-assets/components/bootstrap/css/bootstrap.css +3 -0
  50. data/lib/javascript/clean-css/test/data/129-assets/components/bootstrap/images/glyphs.gif +0 -0
  51. data/lib/javascript/clean-css/test/data/129-assets/components/jquery-ui/css/style.css +6 -0
  52. data/lib/javascript/clean-css/test/data/129-assets/components/jquery-ui/images/next.gif +0 -0
  53. data/lib/javascript/clean-css/test/data/129-assets/components/jquery-ui/images/prev.gif +0 -0
  54. data/lib/javascript/clean-css/test/data/960-min.css +125 -0
  55. data/lib/javascript/clean-css/test/data/960.css +602 -0
  56. data/lib/javascript/clean-css/test/data/big-min.css +2984 -0
  57. data/lib/javascript/clean-css/test/data/big.css +13794 -0
  58. data/lib/javascript/clean-css/test/data/blueprint-min.css +245 -0
  59. data/lib/javascript/clean-css/test/data/blueprint.css +556 -0
  60. data/lib/javascript/clean-css/test/data/charset-mixed-with-fonts-min.css +3 -0
  61. data/lib/javascript/clean-css/test/data/charset-mixed-with-fonts.css +14 -0
  62. data/lib/javascript/clean-css/test/data/font-awesome-ie7-min.css +392 -0
  63. data/lib/javascript/clean-css/test/data/font-awesome-ie7.css +1203 -0
  64. data/lib/javascript/clean-css/test/data/font-awesome-min.css +319 -0
  65. data/lib/javascript/clean-css/test/data/font-awesome.css +540 -0
  66. data/lib/javascript/clean-css/test/data/imports-min.css +5 -0
  67. data/lib/javascript/clean-css/test/data/imports.css +4 -0
  68. data/lib/javascript/clean-css/test/data/issue-117-snippet-min.css +3 -0
  69. data/lib/javascript/clean-css/test/data/issue-117-snippet.css +19 -0
  70. data/lib/javascript/clean-css/test/data/issue-159-snippet-min.css +7 -0
  71. data/lib/javascript/clean-css/test/data/issue-159-snippet.css +7 -0
  72. data/lib/javascript/clean-css/test/data/issue-192-min.css +1 -0
  73. data/lib/javascript/clean-css/test/data/issue-192.css +8 -0
  74. data/lib/javascript/clean-css/test/data/issue-198-min.css +3 -0
  75. data/lib/javascript/clean-css/test/data/issue-198.css +4 -0
  76. data/lib/javascript/clean-css/test/data/issue-232-min.css +2 -0
  77. data/lib/javascript/clean-css/test/data/issue-232.css +17 -0
  78. data/lib/javascript/clean-css/test/data/issue-241-min.css +2 -0
  79. data/lib/javascript/clean-css/test/data/issue-241.css +2 -0
  80. data/lib/javascript/clean-css/test/data/issue-304-min.css +1 -0
  81. data/lib/javascript/clean-css/test/data/issue-304.css +4 -0
  82. data/lib/javascript/clean-css/test/data/issue-305-min.css +1 -0
  83. data/lib/javascript/clean-css/test/data/issue-305.css +3 -0
  84. data/lib/javascript/clean-css/test/data/issue-308-min.css +1 -0
  85. data/lib/javascript/clean-css/test/data/issue-308.css +5 -0
  86. data/lib/javascript/clean-css/test/data/issue-312-min.css +1 -0
  87. data/lib/javascript/clean-css/test/data/issue-312.css +7 -0
  88. data/lib/javascript/clean-css/test/data/issue-337-min.css +1 -0
  89. data/lib/javascript/clean-css/test/data/issue-337.css +4 -0
  90. data/lib/javascript/clean-css/test/data/line-breaks-in-attributes-min.css +2 -0
  91. data/lib/javascript/clean-css/test/data/line-breaks-in-attributes.css +8 -0
  92. data/lib/javascript/clean-css/test/data/partials-absolute/base.css +3 -0
  93. data/lib/javascript/clean-css/test/data/partials-absolute/base2.css +1 -0
  94. data/lib/javascript/clean-css/test/data/partials-absolute/extra/sub.css +3 -0
  95. data/lib/javascript/clean-css/test/data/partials-relative/base.css +3 -0
  96. data/lib/javascript/clean-css/test/data/partials-relative/extra/included.css +1 -0
  97. data/lib/javascript/clean-css/test/data/partials/comment.css +2 -0
  98. data/lib/javascript/clean-css/test/data/partials/extra/down.gif +0 -0
  99. data/lib/javascript/clean-css/test/data/partials/extra/four.css +3 -0
  100. data/lib/javascript/clean-css/test/data/partials/extra/three.css +1 -0
  101. data/lib/javascript/clean-css/test/data/partials/five.css +1 -0
  102. data/lib/javascript/clean-css/test/data/partials/four.css +1 -0
  103. data/lib/javascript/clean-css/test/data/partials/one.css +1 -0
  104. data/lib/javascript/clean-css/test/data/partials/three.css +1 -0
  105. data/lib/javascript/clean-css/test/data/partials/two.css +5 -0
  106. data/lib/javascript/clean-css/test/data/reset-min.css +12 -0
  107. data/lib/javascript/clean-css/test/data/reset.css +64 -0
  108. data/lib/javascript/clean-css/test/data/sample1-min.css +4 -0
  109. data/lib/javascript/clean-css/test/data/sample1.css +12 -0
  110. data/lib/javascript/clean-css/test/data/unsupported/selectors-ie7.css +20 -0
  111. data/lib/javascript/clean-css/test/data/unsupported/selectors-ie8.css +17 -0
  112. data/lib/javascript/clean-css/test/module-test.js +185 -0
  113. data/lib/javascript/clean-css/test/protocol-imports-test.js +409 -0
  114. data/lib/javascript/clean-css/test/text/splitter-test.js +20 -0
  115. data/lib/javascript/clean-css/test/unit-test.js +2071 -0
  116. data/lib/ruby-clean-css.rb +6 -0
  117. data/lib/ruby-clean-css/compressor.rb +142 -0
  118. data/lib/ruby-clean-css/exports.rb +258 -0
  119. data/lib/ruby-clean-css/railtie.rb +27 -0
  120. data/lib/ruby-clean-css/sprockets.rb +19 -0
  121. data/lib/ruby-clean-css/version.rb +5 -0
  122. data/ruby-clean-css.gemspec +30 -0
  123. data/test/test_compressor.rb +84 -0
  124. metadata +203 -0
@@ -0,0 +1,276 @@
1
+
2
+ var processableInfo = require('./processable');
3
+ var overrideCompactor = require('./override-compactor');
4
+ var shorthandCompactor = require('./shorthand-compactor');
5
+
6
+ module.exports = function Optimizer(compatibility, aggressiveMerging) {
7
+ var overridable = {
8
+ 'animation-delay': ['animation'],
9
+ 'animation-direction': ['animation'],
10
+ 'animation-duration': ['animation'],
11
+ 'animation-fill-mode': ['animation'],
12
+ 'animation-iteration-count': ['animation'],
13
+ 'animation-name': ['animation'],
14
+ 'animation-play-state': ['animation'],
15
+ 'animation-timing-function': ['animation'],
16
+ '-moz-animation-delay': ['-moz-animation'],
17
+ '-moz-animation-direction': ['-moz-animation'],
18
+ '-moz-animation-duration': ['-moz-animation'],
19
+ '-moz-animation-fill-mode': ['-moz-animation'],
20
+ '-moz-animation-iteration-count': ['-moz-animation'],
21
+ '-moz-animation-name': ['-moz-animation'],
22
+ '-moz-animation-play-state': ['-moz-animation'],
23
+ '-moz-animation-timing-function': ['-moz-animation'],
24
+ '-o-animation-delay': ['-o-animation'],
25
+ '-o-animation-direction': ['-o-animation'],
26
+ '-o-animation-duration': ['-o-animation'],
27
+ '-o-animation-fill-mode': ['-o-animation'],
28
+ '-o-animation-iteration-count': ['-o-animation'],
29
+ '-o-animation-name': ['-o-animation'],
30
+ '-o-animation-play-state': ['-o-animation'],
31
+ '-o-animation-timing-function': ['-o-animation'],
32
+ '-webkit-animation-delay': ['-webkit-animation'],
33
+ '-webkit-animation-direction': ['-webkit-animation'],
34
+ '-webkit-animation-duration': ['-webkit-animation'],
35
+ '-webkit-animation-fill-mode': ['-webkit-animation'],
36
+ '-webkit-animation-iteration-count': ['-webkit-animation'],
37
+ '-webkit-animation-name': ['-webkit-animation'],
38
+ '-webkit-animation-play-state': ['-webkit-animation'],
39
+ '-webkit-animation-timing-function': ['-webkit-animation'],
40
+ 'background-attachment': ['background'],
41
+ 'background-clip': ['background'],
42
+ 'background-color': ['background'],
43
+ 'background-image': ['background'],
44
+ 'background-origin': ['background'],
45
+ 'background-position': ['background'],
46
+ 'background-repeat': ['background'],
47
+ 'background-size': ['background'],
48
+ 'border-color': ['border'],
49
+ 'border-style': ['border'],
50
+ 'border-width': ['border'],
51
+ 'border-bottom': ['border'],
52
+ 'border-bottom-color': ['border-bottom', 'border-color', 'border'],
53
+ 'border-bottom-style': ['border-bottom', 'border-style', 'border'],
54
+ 'border-bottom-width': ['border-bottom', 'border-width', 'border'],
55
+ 'border-left': ['border'],
56
+ 'border-left-color': ['border-left', 'border-color', 'border'],
57
+ 'border-left-style': ['border-left', 'border-style', 'border'],
58
+ 'border-left-width': ['border-left', 'border-width', 'border'],
59
+ 'border-right': ['border'],
60
+ 'border-right-color': ['border-right', 'border-color', 'border'],
61
+ 'border-right-style': ['border-right', 'border-style', 'border'],
62
+ 'border-right-width': ['border-right', 'border-width', 'border'],
63
+ 'border-top': ['border'],
64
+ 'border-top-color': ['border-top', 'border-color', 'border'],
65
+ 'border-top-style': ['border-top', 'border-style', 'border'],
66
+ 'border-top-width': ['border-top', 'border-width', 'border'],
67
+ 'font-family': ['font'],
68
+ 'font-size': ['font'],
69
+ 'font-style': ['font'],
70
+ 'font-variant': ['font'],
71
+ 'font-weight': ['font'],
72
+ 'list-style-image': ['list-style'],
73
+ 'list-style-position': ['list-style'],
74
+ 'list-style-type': ['list-style'],
75
+ 'margin-bottom': ['margin'],
76
+ 'margin-left': ['margin'],
77
+ 'margin-right': ['margin'],
78
+ 'margin-top': ['margin'],
79
+ 'outline-color': ['outline'],
80
+ 'outline-style': ['outline'],
81
+ 'outline-width': ['outline'],
82
+ 'padding-bottom': ['padding'],
83
+ 'padding-left': ['padding'],
84
+ 'padding-right': ['padding'],
85
+ 'padding-top': ['padding'],
86
+ 'transition-delay': ['transition'],
87
+ 'transition-duration': ['transition'],
88
+ 'transition-property': ['transition'],
89
+ 'transition-timing-function': ['transition'],
90
+ '-moz-transition-delay': ['-moz-transition'],
91
+ '-moz-transition-duration': ['-moz-transition'],
92
+ '-moz-transition-property': ['-moz-transition'],
93
+ '-moz-transition-timing-function': ['-moz-transition'],
94
+ '-o-transition-delay': ['-o-transition'],
95
+ '-o-transition-duration': ['-o-transition'],
96
+ '-o-transition-property': ['-o-transition'],
97
+ '-o-transition-timing-function': ['-o-transition'],
98
+ '-webkit-transition-delay': ['-webkit-transition'],
99
+ '-webkit-transition-duration': ['-webkit-transition'],
100
+ '-webkit-transition-property': ['-webkit-transition'],
101
+ '-webkit-transition-timing-function': ['-webkit-transition']
102
+ };
103
+
104
+ var IE_BACKSLASH_HACK = '\\9';
105
+
106
+ var overrides = {};
107
+ for (var granular in overridable) {
108
+ for (var i = 0; i < overridable[granular].length; i++) {
109
+ var coarse = overridable[granular][i];
110
+ var list = overrides[coarse];
111
+
112
+ if (list)
113
+ list.push(granular);
114
+ else
115
+ overrides[coarse] = [granular];
116
+ }
117
+ }
118
+
119
+ var tokenize = function(body) {
120
+ var tokens = body.split(';');
121
+ var keyValues = [];
122
+
123
+ if (tokens.length === 0 || (tokens.length == 1 && tokens[0].indexOf(IE_BACKSLASH_HACK) == -1))
124
+ return;
125
+
126
+ for (var i = 0, l = tokens.length; i < l; i++) {
127
+ var token = tokens[i];
128
+ if (token === '')
129
+ continue;
130
+
131
+ var firstColon = token.indexOf(':');
132
+ keyValues.push([
133
+ token.substring(0, firstColon),
134
+ token.substring(firstColon + 1),
135
+ token.indexOf('!important') > -1,
136
+ token.indexOf(IE_BACKSLASH_HACK, firstColon + 1) === token.length - IE_BACKSLASH_HACK.length
137
+ ]);
138
+ }
139
+
140
+ return keyValues;
141
+ };
142
+
143
+ var optimize = function(tokens, allowAdjacent) {
144
+ var merged = [];
145
+ var properties = [];
146
+ var lastProperty = null;
147
+ var rescanTrigger = {};
148
+
149
+ var removeOverridenBy = function(property, isImportant) {
150
+ var overrided = overrides[property];
151
+ for (var i = 0, l = overrided.length; i < l; i++) {
152
+ for (var j = 0; j < properties.length; j++) {
153
+ if (properties[j] != overrided[i] || (merged[j][2] && !isImportant))
154
+ continue;
155
+
156
+ merged.splice(j, 1);
157
+ properties.splice(j, 1);
158
+ j -= 1;
159
+ }
160
+ }
161
+ };
162
+
163
+ var mergeablePosition = function(position) {
164
+ if (allowAdjacent === false || allowAdjacent === true)
165
+ return allowAdjacent;
166
+
167
+ return allowAdjacent.indexOf(position) > -1;
168
+ };
169
+
170
+ tokensLoop:
171
+ for (var i = 0, l = tokens.length; i < l; i++) {
172
+ var token = tokens[i];
173
+ var property = token[0];
174
+ var value = token[1];
175
+ var isImportant = token[2];
176
+ var isIEHack = token[3];
177
+ var _property = (property == '-ms-filter' || property == 'filter') ?
178
+ (lastProperty == 'background' || lastProperty == 'background-image' ? lastProperty : property) :
179
+ property;
180
+ var toOverridePosition = 0;
181
+
182
+ if (!compatibility && isIEHack)
183
+ continue;
184
+
185
+ // comment is necessary - we assume that if two properties are one after another
186
+ // then it is intentional way of redefining property which may not be widely supported
187
+ // e.g. a{display:inline-block;display:-moz-inline-box}
188
+ // however if `mergeablePosition` yields true then the rule does not apply
189
+ // (e.g merging two adjacent selectors: `a{display:block}a{display:block}`)
190
+ if (aggressiveMerging && _property != lastProperty || mergeablePosition(i)) {
191
+ while (true) {
192
+ toOverridePosition = properties.indexOf(_property, toOverridePosition);
193
+ if (toOverridePosition == -1)
194
+ break;
195
+
196
+ var lastToken = merged[toOverridePosition];
197
+ var wasImportant = lastToken[2];
198
+ var wasIEHack = lastToken[3];
199
+
200
+ if (wasImportant && !isImportant)
201
+ continue tokensLoop;
202
+
203
+ if (compatibility && !wasIEHack && isIEHack)
204
+ break;
205
+
206
+ var _info = processableInfo.processable[_property];
207
+ if (!isIEHack && !wasIEHack && _info && _info.canOverride && !_info.canOverride(tokens[toOverridePosition][1], value))
208
+ break;
209
+
210
+ merged.splice(toOverridePosition, 1);
211
+ properties.splice(toOverridePosition, 1);
212
+ }
213
+ }
214
+
215
+ merged.push(token);
216
+ properties.push(_property);
217
+
218
+ // certain properties (see values of `overridable`) should trigger removal of
219
+ // more granular properties (see keys of `overridable`)
220
+ if (rescanTrigger[_property])
221
+ removeOverridenBy(_property, isImportant);
222
+
223
+ // add rescan triggers - if certain property appears later in the list a rescan needs
224
+ // to be triggered, e.g 'border-top' triggers a rescan after 'border-top-width' and
225
+ // 'border-top-color' as they can be removed
226
+ for (var j = 0, list = overridable[_property] || [], m = list.length; j < m; j++)
227
+ rescanTrigger[list[j]] = true;
228
+
229
+ lastProperty = _property;
230
+ }
231
+
232
+ return merged;
233
+ };
234
+
235
+ var rebuild = function(tokens) {
236
+ var flat = [];
237
+
238
+
239
+ for (var i = 0, l = tokens.length; i < l; i++) {
240
+ flat.push(tokens[i][0] + ':' + tokens[i][1]);
241
+ }
242
+
243
+ return flat.join(';');
244
+ };
245
+
246
+ var compact = function (input) {
247
+ var processable = processableInfo.processable;
248
+ var Token = processableInfo.Token;
249
+
250
+ var tokens = Token.tokenize(input);
251
+
252
+ tokens = overrideCompactor.compactOverrides(tokens, processable);
253
+ tokens = shorthandCompactor.compactShorthands(tokens, false, processable, Token);
254
+ tokens = shorthandCompactor.compactShorthands(tokens, true, processable, Token);
255
+
256
+ return Token.detokenize(tokens);
257
+ };
258
+
259
+ return {
260
+ process: function(body, allowAdjacent, skipCompacting) {
261
+ var result = body;
262
+
263
+ var tokens = tokenize(body);
264
+ if (tokens) {
265
+ var optimized = optimize(tokens, allowAdjacent);
266
+ result = rebuild(optimized);
267
+ }
268
+
269
+ if (!skipCompacting && processableInfo.implementedFor.test(result)) {
270
+ result = compact(result);
271
+ }
272
+
273
+ return result;
274
+ }
275
+ };
276
+ };
@@ -0,0 +1,116 @@
1
+
2
+ // Compacts the given tokens according to their ability to override each other.
3
+
4
+ module.exports = (function () {
5
+ // Default override function: only allow overrides when the two values are the same
6
+ var sameValue = function (val1, val2) {
7
+ return val1 === val2;
8
+ };
9
+
10
+ var compactOverrides = function (tokens, processable) {
11
+ var result, can, token, t, i, ii, oldResult, matchingComponent;
12
+
13
+ // Used when searching for a component that matches token
14
+ var nameMatchFilter1 = function (x) {
15
+ return x.prop === token.prop;
16
+ };
17
+ // Used when searching for a component that matches t
18
+ var nameMatchFilter2 = function (x) {
19
+ return x.prop === t.prop;
20
+ };
21
+
22
+ // Go from the end and always take what the current token can't override as the new result set
23
+ // NOTE: can't cache result.length here because it will change with every iteration
24
+ for (result = tokens, i = 0; (ii = result.length - 1 - i) >= 0; i++) {
25
+ token = result[ii];
26
+ can = (processable[token.prop] && processable[token.prop].canOverride) || sameValue;
27
+ oldResult = result;
28
+ result = [];
29
+
30
+ // Special flag which indicates that the current token should be removed
31
+ var removeSelf = false;
32
+ var oldResultLength = oldResult.length;
33
+
34
+ for (var iii = 0; iii < oldResultLength; iii++) {
35
+ t = oldResult[iii];
36
+
37
+ // A token can't override itself (checked by reference, not by value)
38
+ // NOTE: except when we explicitly tell it to remove itself
39
+ if (t === token && !removeSelf) {
40
+ result.push(t);
41
+ continue;
42
+ }
43
+
44
+ // Only an important token can even try to override tokens that come after it
45
+ if (iii > ii && !token.isImportant) {
46
+ result.push(t);
47
+ continue;
48
+ }
49
+
50
+ // A nonimportant token can never override an important one
51
+ if (t.isImportant && !token.isImportant) {
52
+ result.push(t);
53
+ continue;
54
+ }
55
+
56
+ if (token.isShorthand && !t.isShorthand && t.isComponentOf(token)) {
57
+ // token (a shorthand) is trying to override t (a component)
58
+
59
+ // Find the matching component in the shorthand
60
+ matchingComponent = token.components.filter(nameMatchFilter2)[0];
61
+ can = (processable[t.prop] && processable[t.prop].canOverride) || sameValue;
62
+ if (!can(t.value, matchingComponent.value)) {
63
+ // The shorthand can't override the component
64
+ result.push(t);
65
+ }
66
+ } else if (t.isShorthand && !token.isShorthand && token.isComponentOf(t)) {
67
+ // token (a component) is trying to override a component of t (a shorthand)
68
+
69
+ // Find the matching component in the shorthand
70
+ matchingComponent = t.components.filter(nameMatchFilter1)[0];
71
+ if (can(matchingComponent.value, token.value)) {
72
+ // The component can override the matching component in the shorthand
73
+
74
+ if (!token.isImportant || token.isImportant && matchingComponent.isImportant) {
75
+ // The overriding component is non-important which means we can simply include it into the shorthand
76
+ // NOTE: stuff that can't really be included, like inherit, is taken care of at the final step, not here
77
+ matchingComponent.value = token.value;
78
+ // We use the special flag to get rid of the component
79
+ removeSelf = true;
80
+ } else {
81
+ // The overriding component is important; sadly we can't get rid of it,
82
+ // but we can still mark the matching component in the shorthand as irrelevant
83
+ matchingComponent.isIrrelevant = true;
84
+ }
85
+ t.isDirty = true;
86
+ }
87
+ result.push(t);
88
+ } else if (token.isShorthand && t.isShorthand && token.prop === t.prop) {
89
+ // token is a shorthand and is trying to override another instance of the same shorthand
90
+
91
+ // Can only override other shorthand when each of its components can override each of the other's components
92
+ for (var iiii = 0; iiii < t.components.length; iiii++) {
93
+ can = (processable[t.components[iiii].prop] && processable[t.components[iiii].prop].canOverride) || sameValue;
94
+ if (!can(t.components[iiii].value, token.components[iiii].value)) {
95
+ result.push(t);
96
+ break;
97
+ }
98
+ }
99
+ } else if (t.prop !== token.prop || !can(t.value, token.value)) {
100
+ // in every other case, use the override mechanism
101
+ result.push(t);
102
+ }
103
+ }
104
+ if (removeSelf) {
105
+ i--;
106
+ }
107
+ }
108
+
109
+ return result;
110
+ };
111
+
112
+ return {
113
+ compactOverrides: compactOverrides
114
+ };
115
+
116
+ })();
@@ -0,0 +1,859 @@
1
+
2
+ // Contains the interpretation of CSS properties, as used by the property optimizer
3
+
4
+ module.exports = (function () {
5
+
6
+ var tokenModule = require('./token');
7
+ var validator = require('./validator');
8
+ var Splitter = require('../text/splitter');
9
+
10
+ // Functions that decide what value can override what.
11
+ // The main purpose is to disallow removing CSS fallbacks.
12
+ // A separate implementation is needed for every different kind of CSS property.
13
+ // -----
14
+ // The generic idea is that properties that have wider browser support are 'more understandable'
15
+ // than others and that 'less understandable' values can't override more understandable ones.
16
+ var canOverride = {
17
+ // Use when two tokens of the same property can always be merged
18
+ always: function () {
19
+ // NOTE: We could have (val1, val2) parameters here but jshint complains because we don't use them
20
+ return true;
21
+ },
22
+ // Use when two tokens of the same property can only be merged if they have the same value
23
+ sameValue: function(val1, val2) {
24
+ return val1 === val2;
25
+ },
26
+ sameFunctionOrValue: function(val1, val2) {
27
+ // Functions with the same name can override each other
28
+ if (validator.areSameFunction(val1, val2)) {
29
+ return true;
30
+ }
31
+
32
+ return val1 === val2;
33
+ },
34
+ // Use for properties containing CSS units (margin-top, padding-left, etc.)
35
+ unit: function(val1, val2) {
36
+ // The idea here is that 'more understandable' values override 'less understandable' values, but not vice versa
37
+ // Understandability: (unit without functions) > (same functions | standard functions) > anything else
38
+ // NOTE: there is no point in having different vendor-specific functions override each other or standard functions,
39
+ // or having standard functions override vendor-specific functions, but standard functions can override each other
40
+ // NOTE: vendor-specific property values are not taken into consideration here at the moment
41
+
42
+ if (validator.isValidUnitWithoutFunction(val2))
43
+ return true;
44
+ if (validator.isValidUnitWithoutFunction(val1))
45
+ return false;
46
+
47
+ // Standard non-vendor-prefixed functions can override each other
48
+ if (validator.isValidFunctionWithoutVendorPrefix(val2) && validator.isValidFunctionWithoutVendorPrefix(val1)) {
49
+ return true;
50
+ }
51
+
52
+ // Functions with the same name can override each other; same values can override each other
53
+ return canOverride.sameFunctionOrValue(val1, val2);
54
+ },
55
+ // Use for color properties (color, background-color, border-color, etc.)
56
+ color: function(val1, val2) {
57
+ // The idea here is that 'more understandable' values override 'less understandable' values, but not vice versa
58
+ // Understandability: (hex | named) > (rgba | hsla) > (same function name) > anything else
59
+ // NOTE: at this point rgb and hsl are replaced by hex values by clean-css
60
+
61
+ // (hex | named)
62
+ if (validator.isValidNamedColor(val2) || validator.isValidHexColor(val2))
63
+ return true;
64
+ if (validator.isValidNamedColor(val1) || validator.isValidHexColor(val1))
65
+ return false;
66
+
67
+ // (rgba|hsla)
68
+ if (validator.isValidRgbaColor(val2) || validator.isValidHslaColor(val2))
69
+ return true;
70
+ if (validator.isValidRgbaColor(val1) || validator.isValidHslaColor(val1))
71
+ return false;
72
+
73
+ // Functions with the same name can override each other; same values can override each other
74
+ return canOverride.sameFunctionOrValue(val1, val2);
75
+ },
76
+ // Use for background-image
77
+ backgroundImage: function(val1, val2) {
78
+ // The idea here is that 'more understandable' values override 'less understandable' values, but not vice versa
79
+ // Understandability: (none | url | inherit) > (same function) > (same value)
80
+
81
+ // (none | url)
82
+ if (val2 === 'none' || val2 === 'inherit' || validator.isValidUrl(val2))
83
+ return true;
84
+ if (val1 === 'none' || val1 === 'inherit' || validator.isValidUrl(val1))
85
+ return false;
86
+
87
+ // Functions with the same name can override each other; same values can override each other
88
+ return canOverride.sameFunctionOrValue(val1, val2);
89
+ },
90
+ border: function(val1, val2) {
91
+ var brokenUp1 = breakUp.border(Token.tokenizeOne(val1));
92
+ var brokenUp2 = breakUp.border(Token.tokenizeOne(val2));
93
+
94
+ return canOverride.color(brokenUp1[2].value, brokenUp2[2].value);
95
+ }
96
+ };
97
+ canOverride = Object.freeze(canOverride);
98
+
99
+ // Functions for breaking up shorthands to components
100
+ var breakUp = {};
101
+ breakUp.takeCareOfFourValues = function (splitfunc) {
102
+ return function (token) {
103
+ var descriptor = processable[token.prop];
104
+ var result = [];
105
+ var splitval = splitfunc(token.value);
106
+
107
+ if (splitval.length === 0 || (splitval.length < descriptor.components.length && descriptor.components.length > 4)) {
108
+ // This token is malformed and we have no idea how to fix it. So let's just keep it intact
109
+ return [token];
110
+ }
111
+
112
+ // Fix those that we do know how to fix
113
+ if (splitval.length < descriptor.components.length && splitval.length < 2) {
114
+ // foo{margin:1px} -> foo{margin:1px 1px}
115
+ splitval[1] = splitval[0];
116
+ }
117
+ if (splitval.length < descriptor.components.length && splitval.length < 3) {
118
+ // foo{margin:1px 2px} -> foo{margin:1px 2px 1px}
119
+ splitval[2] = splitval[0];
120
+ }
121
+ if (splitval.length < descriptor.components.length && splitval.length < 4) {
122
+ // foo{margin:1px 2px 3px} -> foo{margin:1px 2px 3px 2px}
123
+ splitval[3] = splitval[1];
124
+ }
125
+
126
+ // Now break it up to its components
127
+ for (var i = 0; i < descriptor.components.length; i++) {
128
+ var t = new Token(descriptor.components[i], splitval[i], token.isImportant);
129
+ result.push(t);
130
+ }
131
+
132
+ return result;
133
+ };
134
+ };
135
+ // Use this when you simply want to break up four values along spaces
136
+ breakUp.fourBySpaces = breakUp.takeCareOfFourValues(function (val) {
137
+ return new Splitter(' ').split(val).filter(function (v) { return v; });
138
+ });
139
+ // Breaks up a background property value
140
+ breakUp.commaSeparatedMulitpleValues = function (splitfunc) {
141
+ return function (token) {
142
+ if (token.value.indexOf(',') === -1)
143
+ return splitfunc(token);
144
+
145
+ var values = new Splitter(',').split(token.value);
146
+ var components = [];
147
+
148
+ for (var i = 0, l = values.length; i < l; i++) {
149
+ token.value = values[i];
150
+ components.push(splitfunc(token));
151
+ }
152
+
153
+ for (var j = 0, m = components[0].length; j < m; j++) {
154
+ for (var k = 0, n = components.length, newValues = []; k < n; k++) {
155
+ newValues.push(components[k][j].value);
156
+ }
157
+
158
+ components[0][j].value = newValues.join(',');
159
+ }
160
+
161
+ return components[0];
162
+ };
163
+ };
164
+ breakUp.background = function (token) {
165
+ // Default values
166
+ var result = Token.makeDefaults(['background-image', 'background-position', 'background-size', 'background-repeat', 'background-attachment', 'background-color'], token.isImportant);
167
+ var image = result[0];
168
+ var position = result[1];
169
+ var size = result[2];
170
+ var repeat = result[3];
171
+ var attachment = result[4];
172
+ var color = result[5];
173
+
174
+ // Take care of inherit
175
+ if (token.value === 'inherit') {
176
+ // NOTE: 'inherit' is not a valid value for background-attachment so there we'll leave the default value
177
+ color.value = image.value = repeat.value = position.value = size.value = attachment.value = 'inherit';
178
+ return result;
179
+ }
180
+
181
+ // Break the background up into parts
182
+ var parts = new Splitter(' ').split(token.value);
183
+ if (parts.length === 0)
184
+ return result;
185
+
186
+ // Iterate over all parts and try to fit them into positions
187
+ for (var i = parts.length - 1; i >= 0; i--) {
188
+ var currentPart = parts[i];
189
+
190
+ if (validator.isValidBackgroundAttachment(currentPart)) {
191
+ attachment.value = currentPart;
192
+ } else if (validator.isValidBackgroundRepeat(currentPart)) {
193
+ repeat.value = currentPart;
194
+ } else if (validator.isValidBackgroundPositionPart(currentPart) || validator.isValidBackgroundSizePart(currentPart)) {
195
+ if (i > 0) {
196
+ var previousPart = parts[i - 1];
197
+
198
+ if (previousPart.indexOf('/') > 0) {
199
+ var twoParts = new Splitter('/').split(previousPart);
200
+ size.value = twoParts.pop() + ' ' + currentPart;
201
+ parts[i - 1] = twoParts.pop();
202
+ } else if (i > 1 && parts[i - 2] == '/') {
203
+ size.value = previousPart + ' ' + currentPart;
204
+ i -= 2;
205
+ } else if (parts[i - 1] == '/') {
206
+ size.value = currentPart;
207
+ position.value = previousPart;
208
+ i--;
209
+ } else {
210
+ position.value = previousPart + ' ' + currentPart;
211
+ i--;
212
+ }
213
+ } else {
214
+ position.value = currentPart;
215
+ }
216
+ } else if (validator.isValidBackgroundPositionAndSize(currentPart)) {
217
+ var sizeValue = new Splitter('/').split(currentPart);
218
+ size.value = sizeValue.pop();
219
+ position.value = sizeValue.pop();
220
+ } else if ((color.value == processable[color.prop].defaultValue || color.value == 'none') && validator.isValidColor(currentPart)) {
221
+ color.value = currentPart;
222
+ } else if (validator.isValidUrl(currentPart) || validator.isValidFunction(currentPart)) {
223
+ image.value = currentPart;
224
+ }
225
+ }
226
+
227
+ return result;
228
+ };
229
+ // Breaks up a list-style property value
230
+ breakUp.listStyle = function (token) {
231
+ // Default values
232
+ var result = Token.makeDefaults(['list-style-type', 'list-style-position', 'list-style-image'], token.isImportant);
233
+ var type = result[0], position = result[1], image = result[2];
234
+
235
+ if (token.value === 'inherit') {
236
+ type.value = position.value = image.value = 'inherit';
237
+ return result;
238
+ }
239
+
240
+ var parts = new Splitter(' ').split(token.value);
241
+ var ci = 0;
242
+
243
+ // Type
244
+ if (ci < parts.length && validator.isValidListStyleType(parts[ci])) {
245
+ type.value = parts[ci];
246
+ ci++;
247
+ }
248
+ // Position
249
+ if (ci < parts.length && validator.isValidListStylePosition(parts[ci])) {
250
+ position.value = parts[ci];
251
+ ci++;
252
+ }
253
+ // Image
254
+ if (ci < parts.length) {
255
+ image.value = parts.splice(ci, parts.length - ci + 1).join(' ');
256
+ }
257
+
258
+ return result;
259
+ };
260
+
261
+ breakUp._widthStyleColor = function(token, prefix, order) {
262
+ // Default values
263
+ var components = order.map(function(prop) {
264
+ return prefix + '-' + prop;
265
+ });
266
+ var result = Token.makeDefaults(components, token.isImportant);
267
+ var color = result[order.indexOf('color')];
268
+ var style = result[order.indexOf('style')];
269
+ var width = result[order.indexOf('width')];
270
+
271
+ // Take care of inherit
272
+ if (token.value === 'inherit' || token.value === 'inherit inherit inherit') {
273
+ color.value = style.value = width.value = 'inherit';
274
+ return result;
275
+ }
276
+
277
+ // NOTE: usually users don't follow the required order of parts in this shorthand,
278
+ // so we'll try to parse it caring as little about order as possible
279
+
280
+ var parts = new Splitter(' ').split(token.value), w;
281
+
282
+ if (parts.length === 0) {
283
+ return result;
284
+ }
285
+
286
+ if (parts.length >= 1) {
287
+ // Try to find -width, excluding inherit because that can be anything
288
+ w = parts.filter(function(p) { return p !== 'inherit' && validator.isValidOutlineWidth(p); });
289
+ if (w.length) {
290
+ width.value = w[0];
291
+ parts.splice(parts.indexOf(w[0]), 1);
292
+ }
293
+ }
294
+ if (parts.length >= 1) {
295
+ // Try to find -style, excluding inherit because that can be anything
296
+ w = parts.filter(function(p) { return p !== 'inherit' && validator.isValidOutlineStyle(p); });
297
+ if (w.length) {
298
+ style.value = w[0];
299
+ parts.splice(parts.indexOf(w[0]), 1);
300
+ }
301
+ }
302
+ if (parts.length >= 1) {
303
+ // Find -color but this time can catch inherit
304
+ w = parts.filter(function(p) { return validator.isValidOutlineColor(p); });
305
+ if (w.length) {
306
+ color.value = w[0];
307
+ parts.splice(parts.indexOf(w[0]), 1);
308
+ }
309
+ }
310
+
311
+ return result;
312
+ };
313
+
314
+ breakUp.outline = function(token) {
315
+ return breakUp._widthStyleColor(token, 'outline', ['color', 'style', 'width']);
316
+ };
317
+
318
+ breakUp.border = function(token) {
319
+ return breakUp._widthStyleColor(token, 'border', ['width', 'style', 'color']);
320
+ };
321
+
322
+ breakUp.borderRadius = function(token) {
323
+ var parts = token.value.split('/');
324
+ if (parts.length == 1)
325
+ return breakUp.fourBySpaces(token);
326
+
327
+ var horizontalPart = token.clone();
328
+ var verticalPart = token.clone();
329
+
330
+ horizontalPart.value = parts[0];
331
+ verticalPart.value = parts[1];
332
+
333
+ var horizontalBreakUp = breakUp.fourBySpaces(horizontalPart);
334
+ var verticalBreakUp = breakUp.fourBySpaces(verticalPart);
335
+
336
+ for (var i = 0; i < 4; i++) {
337
+ horizontalBreakUp[i].value = [horizontalBreakUp[i].value, verticalBreakUp[i].value];
338
+ }
339
+
340
+ return horizontalBreakUp;
341
+ };
342
+
343
+ // Contains functions that can put together shorthands from their components
344
+ // NOTE: correct order of tokens is assumed inside these functions!
345
+ var putTogether = {
346
+ // Use this for properties which have four unit values (margin, padding, etc.)
347
+ // NOTE: optimizes to shorter forms too (that only specify 1, 2, or 3 values)
348
+ fourUnits: function (prop, tokens, isImportant) {
349
+ // See about irrelevant tokens
350
+ // NOTE: This will enable some crazy optimalizations for us.
351
+ if (tokens[0].isIrrelevant)
352
+ tokens[0].value = tokens[2].value;
353
+ if (tokens[2].isIrrelevant)
354
+ tokens[2].value = tokens[0].value;
355
+ if (tokens[1].isIrrelevant)
356
+ tokens[1].value = tokens[3].value;
357
+ if (tokens[3].isIrrelevant)
358
+ tokens[3].value = tokens[1].value;
359
+
360
+ if (tokens[0].isIrrelevant && tokens[2].isIrrelevant) {
361
+ if (tokens[1].value === tokens[3].value)
362
+ tokens[0].value = tokens[2].value = tokens[1].value;
363
+ else
364
+ tokens[0].value = tokens[2].value = '0';
365
+ }
366
+ if (tokens[1].isIrrelevant && tokens[3].isIrrelevant) {
367
+ if (tokens[0].value === tokens[2].value)
368
+ tokens[1].value = tokens[3].value = tokens[0].value;
369
+ else
370
+ tokens[1].value = tokens[3].value = '0';
371
+ }
372
+
373
+ var result = new Token(prop, tokens[0].value, isImportant);
374
+ result.granularValues = [];
375
+ result.granularValues[tokens[0].prop] = tokens[0].value;
376
+ result.granularValues[tokens[1].prop] = tokens[1].value;
377
+ result.granularValues[tokens[2].prop] = tokens[2].value;
378
+ result.granularValues[tokens[3].prop] = tokens[3].value;
379
+
380
+ // If all of them are irrelevant
381
+ if (tokens[0].isIrrelevant && tokens[1].isIrrelevant && tokens[2].isIrrelevant && tokens[3].isIrrelevant) {
382
+ result.value = processable[prop].shortestValue || processable[prop].defaultValue;
383
+ return result;
384
+ }
385
+
386
+ // 1-value short form: all four components are equal
387
+ if (tokens[0].value === tokens[1].value && tokens[0].value === tokens[2].value && tokens[0].value === tokens[3].value) {
388
+ return result;
389
+ }
390
+ result.value += ' ' + tokens[1].value;
391
+ // 2-value short form: first and third; second and fourth values are equal
392
+ if (tokens[0].value === tokens[2].value && tokens[1].value === tokens[3].value) {
393
+ return result;
394
+ }
395
+ result.value += ' ' + tokens[2].value;
396
+ // 3-value short form: second and fourth values are equal
397
+ if (tokens[1].value === tokens[3].value) {
398
+ return result;
399
+ }
400
+ // 4-value form (none of the above optimalizations could be accomplished)
401
+ result.value += ' ' + tokens[3].value;
402
+ return result;
403
+ },
404
+ // Puts together the components by spaces and omits default values (this is the case for most shorthands)
405
+ bySpacesOmitDefaults: function (prop, tokens, isImportant, meta) {
406
+ var result = new Token(prop, '', isImportant);
407
+
408
+ // Get irrelevant tokens
409
+ var irrelevantTokens = tokens.filter(function (t) { return t.isIrrelevant; });
410
+
411
+ // If every token is irrelevant, return shortest possible value, fallback to default value
412
+ if (irrelevantTokens.length === tokens.length) {
413
+ result.isIrrelevant = true;
414
+ result.value = processable[prop].shortestValue || processable[prop].defaultValue;
415
+ return result;
416
+ }
417
+
418
+ // This will be the value of the shorthand if all the components are default
419
+ var valueIfAllDefault = processable[prop].defaultValue;
420
+
421
+ // Go through all tokens and concatenate their values as necessary
422
+ for (var i = 0; i < tokens.length; i++) {
423
+ var token = tokens[i];
424
+
425
+ // Set granular value so that other parts of the code can use this for optimalization opportunities
426
+ result.granularValues = result.granularValues || { };
427
+ result.granularValues[token.prop] = token.value;
428
+
429
+ // Use irrelevant tokens for optimalization opportunity
430
+ if (token.isIrrelevant) {
431
+ // Get shortest possible value, fallback to default value
432
+ var tokenShortest = processable[token.prop].shortestValue || processable[token.prop].defaultValue;
433
+ // If the shortest possible value of this token is shorter than the default value of the shorthand, use it instead
434
+ if (tokenShortest.length < valueIfAllDefault.length) {
435
+ valueIfAllDefault = tokenShortest;
436
+ }
437
+ }
438
+
439
+ // Omit default / irrelevant value
440
+ if (token.isIrrelevant || (processable[token.prop] && processable[token.prop].defaultValue === token.value)) {
441
+ continue;
442
+ }
443
+
444
+ if (meta && meta.partsCount && meta.position < meta.partsCount - 1 && processable[token.prop].multiValueLastOnly)
445
+ continue;
446
+
447
+ var requiresPreceeding = processable[token.prop].shorthandFollows;
448
+ if (requiresPreceeding && (tokens[i - 1].value == processable[requiresPreceeding].defaultValue)) {
449
+ result.value += ' ' + tokens[i - 1].value;
450
+ }
451
+
452
+ result.value += (processable[token.prop].prefixShorthandValueWith || ' ') + token.value;
453
+ }
454
+
455
+ result.value = result.value.trim();
456
+ if (!result.value) {
457
+ result.value = valueIfAllDefault;
458
+ }
459
+
460
+ return result;
461
+ },
462
+ commaSeparatedMulitpleValues: function (assembleFunction) {
463
+ return function(prop, tokens, isImportant) {
464
+ var tokenSplitLengths = tokens.map(function (token) {
465
+ return new Splitter(',').split(token.value).length;
466
+ });
467
+ var partsCount = Math.max.apply(Math, tokenSplitLengths);
468
+
469
+ if (partsCount == 1)
470
+ return assembleFunction(prop, tokens, isImportant);
471
+
472
+ var merged = [];
473
+
474
+ for (var i = 0; i < partsCount; i++) {
475
+ merged.push([]);
476
+
477
+ for (var j = 0; j < tokens.length; j++) {
478
+ var split = new Splitter(',').split(tokens[j].value);
479
+ merged[i].push(split[i] || split[0]);
480
+ }
481
+ }
482
+
483
+ var mergedValues = [];
484
+ var firstProcessed;
485
+ for (i = 0; i < partsCount; i++) {
486
+ for (var k = 0, n = merged[i].length; k < n; k++) {
487
+ tokens[k].value = merged[i][k];
488
+ }
489
+
490
+ var meta = {
491
+ partsCount: partsCount,
492
+ position: i
493
+ };
494
+ var processed = assembleFunction(prop, tokens, isImportant, meta);
495
+ mergedValues.push(processed.value);
496
+
497
+ if (!firstProcessed)
498
+ firstProcessed = processed;
499
+ }
500
+
501
+ firstProcessed.value = mergedValues.join(',');
502
+ return firstProcessed;
503
+ };
504
+ },
505
+ // Handles the cases when some or all the fine-grained properties are set to inherit
506
+ takeCareOfInherit: function (innerFunc) {
507
+ return function (prop, tokens, isImportant, meta) {
508
+ // Filter out the inheriting and non-inheriting tokens in one iteration
509
+ var inheritingTokens = [];
510
+ var nonInheritingTokens = [];
511
+ var result2Shorthandable = [];
512
+ var i;
513
+ for (i = 0; i < tokens.length; i++) {
514
+ if (tokens[i].value === 'inherit') {
515
+ inheritingTokens.push(tokens[i]);
516
+
517
+ // Indicate that this property is irrelevant and its value can safely be set to anything else
518
+ var r2s = new Token(tokens[i].prop, tokens[i].isImportant);
519
+ r2s.isIrrelevant = true;
520
+ result2Shorthandable.push(r2s);
521
+ } else {
522
+ nonInheritingTokens.push(tokens[i]);
523
+ result2Shorthandable.push(tokens[i]);
524
+ }
525
+ }
526
+
527
+ if (nonInheritingTokens.length === 0) {
528
+ // When all the tokens are 'inherit'
529
+ return new Token(prop, 'inherit', isImportant);
530
+ } else if (inheritingTokens.length > 0) {
531
+ // When some (but not all) of the tokens are 'inherit'
532
+
533
+ // Result 1. Shorthand just the inherit values and have it overridden with the non-inheriting ones
534
+ var result1 = [new Token(prop, 'inherit', isImportant)].concat(nonInheritingTokens);
535
+
536
+ // Result 2. Shorthand every non-inherit value and then have it overridden with the inheriting ones
537
+ var result2 = [innerFunc(prop, result2Shorthandable, isImportant, meta)].concat(inheritingTokens);
538
+
539
+ // Return whichever is shorter
540
+ var dl1 = Token.getDetokenizedLength(result1);
541
+ var dl2 = Token.getDetokenizedLength(result2);
542
+
543
+ return dl1 < dl2 ? result1 : result2;
544
+ } else {
545
+ // When none of tokens are 'inherit'
546
+ return innerFunc(prop, tokens, isImportant, meta);
547
+ }
548
+ };
549
+ },
550
+ borderRadius: function (prop, tokens, isImportant) {
551
+ var verticalTokens = [];
552
+
553
+ for (var i = 0, l = tokens.length; i < l; i++) {
554
+ var token = tokens[i];
555
+ if (!Array.isArray(token.value))
556
+ continue;
557
+
558
+ if (token.value.length > 1) {
559
+ verticalTokens.push({
560
+ prop: token.prop,
561
+ value: token.value[1],
562
+ isImportant: token.isImportant
563
+ });
564
+ }
565
+
566
+ token.value = token.value[0];
567
+ }
568
+
569
+ var result = putTogether.takeCareOfInherit(putTogether.fourUnits)(prop, tokens, isImportant);
570
+ if (verticalTokens.length > 0) {
571
+ var verticalResult = putTogether.takeCareOfInherit(putTogether.fourUnits)(prop, verticalTokens, isImportant);
572
+ if (result.value != verticalResult.value)
573
+ result.value += '/' + verticalResult.value;
574
+ }
575
+
576
+ return result;
577
+ }
578
+ };
579
+
580
+ // Properties to process
581
+ // Extend this object in order to add support for more properties in the optimizer.
582
+ //
583
+ // Each key in this object represents a CSS property and should be an object.
584
+ // Such an object contains properties that describe how the represented CSS property should be handled.
585
+ // Possible options:
586
+ //
587
+ // * components: array (Only specify for shorthand properties.)
588
+ // Contains the names of the granular properties this shorthand compacts.
589
+ //
590
+ // * canOverride: function (Default is canOverride.sameValue - meaning that they'll only be merged if they have the same value.)
591
+ // Returns whether two tokens of this property can be merged with each other.
592
+ // This property has no meaning for shorthands.
593
+ //
594
+ // * defaultValue: string
595
+ // Specifies the default value of the property according to the CSS standard.
596
+ // 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.
597
+ //
598
+ // * shortestValue: string
599
+ // Specifies the shortest possible value the property can possibly have.
600
+ // (Falls back to defaultValue if unspecified.)
601
+ //
602
+ // * breakUp: function (Only specify for shorthand properties.)
603
+ // Breaks the shorthand up to its components.
604
+ //
605
+ // * putTogether: function (Only specify for shorthand properties.)
606
+ // Puts the shorthand together from its components.
607
+ //
608
+ var processable = {
609
+ 'color': {
610
+ canOverride: canOverride.color,
611
+ defaultValue: 'transparent',
612
+ shortestValue: 'red'
613
+ },
614
+ // background ------------------------------------------------------------------------------
615
+ 'background': {
616
+ components: [
617
+ 'background-image',
618
+ 'background-position',
619
+ 'background-size',
620
+ 'background-repeat',
621
+ 'background-attachment',
622
+ 'background-color'
623
+ ],
624
+ breakUp: breakUp.commaSeparatedMulitpleValues(breakUp.background),
625
+ putTogether: putTogether.commaSeparatedMulitpleValues(
626
+ putTogether.takeCareOfInherit(putTogether.bySpacesOmitDefaults)
627
+ ),
628
+ defaultValue: '0 0',
629
+ shortestValue: '0'
630
+ },
631
+ 'background-color': {
632
+ canOverride: canOverride.color,
633
+ defaultValue: 'transparent',
634
+ multiValueLastOnly: true,
635
+ shortestValue: 'red'
636
+ },
637
+ 'background-image': {
638
+ canOverride: canOverride.backgroundImage,
639
+ defaultValue: 'none'
640
+ },
641
+ 'background-repeat': {
642
+ canOverride: canOverride.always,
643
+ defaultValue: 'repeat'
644
+ },
645
+ 'background-position': {
646
+ canOverride: canOverride.always,
647
+ defaultValue: '0 0',
648
+ shortestValue: '0'
649
+ },
650
+ 'background-size': {
651
+ canOverride: canOverride.always,
652
+ defaultValue: 'auto',
653
+ shortestValue: '0 0',
654
+ prefixShorthandValueWith: '/',
655
+ shorthandFollows: 'background-position'
656
+ },
657
+ 'background-attachment': {
658
+ canOverride: canOverride.always,
659
+ defaultValue: 'scroll'
660
+ },
661
+ 'border': {
662
+ breakUp: breakUp.border,
663
+ canOverride: canOverride.border,
664
+ components: [
665
+ 'border-width',
666
+ 'border-style',
667
+ 'border-color'
668
+ ],
669
+ defaultValue: 'none',
670
+ putTogether: putTogether.takeCareOfInherit(putTogether.bySpacesOmitDefaults)
671
+ },
672
+ 'border-color': {
673
+ canOverride: canOverride.color,
674
+ defaultValue: 'none'
675
+ },
676
+ 'border-style': {
677
+ canOverride: canOverride.always,
678
+ defaultValue: 'none'
679
+ },
680
+ 'border-width': {
681
+ canOverride: canOverride.unit,
682
+ defaultValue: 'medium',
683
+ shortestValue: '0'
684
+ },
685
+ // list-style ------------------------------------------------------------------------------
686
+ 'list-style': {
687
+ components: [
688
+ 'list-style-type',
689
+ 'list-style-position',
690
+ 'list-style-image'
691
+ ],
692
+ canOverride: canOverride.always,
693
+ breakUp: breakUp.listStyle,
694
+ putTogether: putTogether.takeCareOfInherit(putTogether.bySpacesOmitDefaults),
695
+ defaultValue: 'outside', // can't use 'disc' because that'd override default 'decimal' for <ol>
696
+ shortestValue: 'none'
697
+ },
698
+ 'list-style-type' : {
699
+ canOverride: canOverride.always,
700
+ shortestValue: 'none',
701
+ defaultValue: '__hack'
702
+ // NOTE: we can't tell the real default value here, it's 'disc' for <ul> and 'decimal' for <ol>
703
+ // -- 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
704
+ },
705
+ 'list-style-position' : {
706
+ canOverride: canOverride.always,
707
+ defaultValue: 'outside',
708
+ shortestValue: 'inside'
709
+ },
710
+ 'list-style-image' : {
711
+ canOverride: canOverride.always,
712
+ defaultValue: 'none'
713
+ },
714
+ // outline ------------------------------------------------------------------------------
715
+ 'outline': {
716
+ components: [
717
+ 'outline-color',
718
+ 'outline-style',
719
+ 'outline-width'
720
+ ],
721
+ breakUp: breakUp.outline,
722
+ putTogether: putTogether.takeCareOfInherit(putTogether.bySpacesOmitDefaults),
723
+ defaultValue: '0'
724
+ },
725
+ 'outline-color': {
726
+ canOverride: canOverride.color,
727
+ defaultValue: 'invert',
728
+ shortestValue: 'red'
729
+ },
730
+ 'outline-style': {
731
+ canOverride: canOverride.always,
732
+ defaultValue: 'none'
733
+ },
734
+ 'outline-width': {
735
+ canOverride: canOverride.unit,
736
+ defaultValue: 'medium',
737
+ shortestValue: '0'
738
+ },
739
+ // transform
740
+ '-moz-transform': {
741
+ canOverride: canOverride.sameFunctionOrValue
742
+ },
743
+ '-ms-transform': {
744
+ canOverride: canOverride.sameFunctionOrValue
745
+ },
746
+ '-webkit-transform': {
747
+ canOverride: canOverride.sameFunctionOrValue
748
+ },
749
+ 'transform': {
750
+ canOverride: canOverride.sameFunctionOrValue
751
+ }
752
+ };
753
+
754
+ var addFourValueShorthand = function (prop, components, options) {
755
+ options = options || {};
756
+ processable[prop] = {
757
+ components: components,
758
+ breakUp: options.breakUp || breakUp.fourBySpaces,
759
+ putTogether: options.putTogether || putTogether.takeCareOfInherit(putTogether.fourUnits),
760
+ defaultValue: options.defaultValue || '0',
761
+ shortestValue: options.shortestValue
762
+ };
763
+ for (var i = 0; i < components.length; i++) {
764
+ processable[components[i]] = {
765
+ breakUp: options.breakUp || breakUp.fourBySpaces,
766
+ canOverride: options.canOverride || canOverride.unit,
767
+ defaultValue: options.defaultValue || '0',
768
+ shortestValue: options.shortestValue
769
+ };
770
+ }
771
+ };
772
+
773
+ ['', '-moz-', '-o-', '-webkit-'].forEach(function (prefix) {
774
+ addFourValueShorthand(prefix + 'border-radius', [
775
+ prefix + 'border-top-left-radius',
776
+ prefix + 'border-top-right-radius',
777
+ prefix + 'border-bottom-right-radius',
778
+ prefix + 'border-bottom-left-radius'
779
+ ], {
780
+ breakUp: breakUp.borderRadius,
781
+ putTogether: putTogether.borderRadius
782
+ });
783
+ });
784
+
785
+ addFourValueShorthand('border-color', [
786
+ 'border-top-color',
787
+ 'border-right-color',
788
+ 'border-bottom-color',
789
+ 'border-left-color'
790
+ ], {
791
+ breakUp: breakUp.fourBySpaces,
792
+ canOverride: canOverride.color,
793
+ defaultValue: 'currentColor',
794
+ shortestValue: 'red'
795
+ });
796
+
797
+ addFourValueShorthand('border-style', [
798
+ 'border-top-style',
799
+ 'border-right-style',
800
+ 'border-bottom-style',
801
+ 'border-left-style'
802
+ ], {
803
+ breakUp: breakUp.fourBySpaces,
804
+ canOverride: canOverride.always,
805
+ defaultValue: 'none'
806
+ });
807
+
808
+ addFourValueShorthand('border-width', [
809
+ 'border-top-width',
810
+ 'border-right-width',
811
+ 'border-bottom-width',
812
+ 'border-left-width'
813
+ ], {
814
+ defaultValue: 'medium',
815
+ shortestValue: '0'
816
+ });
817
+
818
+ addFourValueShorthand('padding', [
819
+ 'padding-top',
820
+ 'padding-right',
821
+ 'padding-bottom',
822
+ 'padding-left'
823
+ ]);
824
+
825
+ addFourValueShorthand('margin', [
826
+ 'margin-top',
827
+ 'margin-right',
828
+ 'margin-bottom',
829
+ 'margin-left'
830
+ ]);
831
+
832
+ // Set some stuff iteratively
833
+ for (var proc in processable) {
834
+ if (!processable.hasOwnProperty(proc))
835
+ continue;
836
+
837
+ var currDesc = processable[proc];
838
+
839
+ if (!(currDesc.components instanceof Array) || currDesc.components.length === 0)
840
+ continue;
841
+
842
+ currDesc.isShorthand = true;
843
+
844
+ for (var cI = 0; cI < currDesc.components.length; cI++) {
845
+ if (!processable[currDesc.components[cI]]) {
846
+ throw new Error('"' + currDesc.components[cI] + '" is defined as a component of "' + proc + '" but isn\'t defined in processable.');
847
+ }
848
+ processable[currDesc.components[cI]].componentOf = proc;
849
+ }
850
+ }
851
+
852
+ var Token = tokenModule.createTokenPrototype(processable);
853
+
854
+ return {
855
+ implementedFor: /background|border|color|list|margin|outline|padding|transform/,
856
+ processable: processable,
857
+ Token: Token
858
+ };
859
+ })();