|
1 | 1 | (function (tree) {
|
2 | 2 |
|
3 | 3 | tree.Media = function (value, features) {
|
4 |
| - var selectors; |
| 4 | + var el = new(tree.Element)('&', null, 0), |
| 5 | + selectors = [new(tree.Selector)([el])]; |
5 | 6 |
|
6 |
| - this.features = features && new(tree.Value)(features); |
7 |
| - |
8 |
| - if (Array.isArray(value)) { |
9 |
| - selectors = [new(tree.Selector)([new(tree.Element)('&', null, 0)])]; |
10 |
| - this.ruleset = new(tree.Ruleset)(selectors, value); |
11 |
| - this.ruleset.allowImports = true; |
12 |
| - } else { |
13 |
| - this.value = value; |
14 |
| - } |
| 7 | + this.features = new(tree.Value)(features); |
| 8 | + this.ruleset = new(tree.Ruleset)(selectors, value); |
| 9 | + this.ruleset.allowImports = true; |
15 | 10 | };
|
16 | 11 | tree.Media.prototype = {
|
17 | 12 | toCSS: function (ctx, env) {
|
18 |
| - var features = this.features ? ' ' + this.features.toCSS(env) : ''; |
| 13 | + var features = this.features.toCSS(env); |
19 | 14 |
|
20 |
| - if (this.ruleset) { |
21 |
| - this.ruleset.root = (ctx.length === 0); |
22 |
| - return '@media' + features + (env.compress ? '{' : ' {\n ') + |
23 |
| - this.ruleset.toCSS(ctx, env).trim().replace(/\n/g, '\n ') + |
24 |
| - (env.compress ? '}': '\n}\n'); |
25 |
| - } else { |
26 |
| - return '@media ' + this.value.toCSS() + ';\n'; |
27 |
| - } |
| 15 | + this.ruleset.root = (ctx.length === 0 || ctx[0].multiMedia); |
| 16 | + return '@media ' + features + (env.compress ? '{' : ' {\n ') + |
| 17 | + this.ruleset.toCSS(ctx, env).trim().replace(/\n/g, '\n ') + |
| 18 | + (env.compress ? '}': '\n}\n'); |
28 | 19 | },
|
29 | 20 | eval: function (env) {
|
30 |
| - this.features = this.features && this.features.eval(env); |
| 21 | + if (!env.mediaBlocks) { |
| 22 | + env.mediaBlocks = []; |
| 23 | + env.mediaPath = []; |
| 24 | + } |
| 25 | + |
| 26 | + env.mediaBlocks.push(this); |
| 27 | + env.mediaPath.push(this); |
| 28 | + |
| 29 | + this.features = this.features.eval(env); |
31 | 30 | env.frames.unshift(this);
|
32 |
| - this.ruleset = this.ruleset && this.ruleset.eval(env); |
| 31 | + this.ruleset = this.ruleset.eval(env); |
33 | 32 | env.frames.shift();
|
34 |
| - return this; |
| 33 | + |
| 34 | + env.mediaPath.pop(); |
| 35 | + |
| 36 | + if (env.mediaPath.length === 0) { |
| 37 | + return this.evalTop(env); |
| 38 | + } else { |
| 39 | + return this.evalNested(env); |
| 40 | + } |
35 | 41 | },
|
36 | 42 | variable: function (name) { return tree.Ruleset.prototype.variable.call(this.ruleset, name) },
|
37 | 43 | find: function () { return tree.Ruleset.prototype.find.apply(this.ruleset, arguments) },
|
38 |
| - rulesets: function () { return tree.Ruleset.prototype.rulesets.apply(this.ruleset) } |
| 44 | + rulesets: function () { return tree.Ruleset.prototype.rulesets.apply(this.ruleset) }, |
| 45 | + |
| 46 | + evalTop: function (env) { |
| 47 | + var result = this; |
| 48 | + |
| 49 | + // Render all dependent Media blocks. |
| 50 | + if (env.mediaBlocks.length > 1) { |
| 51 | + var el = new(tree.Element)('&', null, 0); |
| 52 | + var selectors = [new(tree.Selector)([el])]; |
| 53 | + result = new(tree.Ruleset)(selectors, env.mediaBlocks); |
| 54 | + result.multiMedia = true; |
| 55 | + } |
| 56 | + |
| 57 | + delete env.mediaBlocks; |
| 58 | + delete env.mediaPath; |
| 59 | + |
| 60 | + return result; |
| 61 | + }, |
| 62 | + evalNested: function (env) { |
| 63 | + var i, value, |
| 64 | + path = env.mediaPath.concat([this]); |
| 65 | + |
| 66 | + // Extract the media-query conditions separated with `,` (OR). |
| 67 | + for (i = 0; i < path.length; i++) { |
| 68 | + value = path[i].features instanceof tree.Value ? |
| 69 | + path[i].features.value : path[i].features; |
| 70 | + path[i] = Array.isArray(value) ? value : [value]; |
| 71 | + } |
| 72 | + |
| 73 | + // Trace all permutations to generate the resulting media-query. |
| 74 | + // |
| 75 | + // (a, b and c) with nested (d, e) -> |
| 76 | + // a and d |
| 77 | + // a and e |
| 78 | + // b and c and d |
| 79 | + // b and c and e |
| 80 | + this.features = new(tree.Value)(this.permute(path).map(function (path) { |
| 81 | + path = path.map(function (fragment) { |
| 82 | + return fragment.toCSS ? fragment : new(tree.Anonymous)(fragment); |
| 83 | + }); |
| 84 | + |
| 85 | + for(i = path.length - 1; i > 0; i--) { |
| 86 | + path.splice(i, 0, new(tree.Anonymous)("and")); |
| 87 | + } |
| 88 | + |
| 89 | + return new(tree.Expression)(path); |
| 90 | + })); |
| 91 | + |
| 92 | + // Fake a tree-node that doesn't output anything. |
| 93 | + return new(tree.Ruleset)([], []); |
| 94 | + }, |
| 95 | + permute: function (arr) { |
| 96 | + if (arr.length == 0) { |
| 97 | + return []; |
| 98 | + } else if (arr.length == 1) { |
| 99 | + return arr[0]; |
| 100 | + } else { |
| 101 | + var result = []; |
| 102 | + var rest = this.permute(arr.slice(1)); |
| 103 | + for (var i = 0; i < rest.length; i++) { |
| 104 | + for (var j = 0; j < arr[0].length; j++) { |
| 105 | + result.push([arr[0][j]].concat(rest[i])); |
| 106 | + } |
| 107 | + } |
| 108 | + return result; |
| 109 | + } |
| 110 | + } |
39 | 111 | };
|
40 | 112 |
|
41 | 113 | })(require('../tree'));
|
0 commit comments