|
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