diff --git a/lib/less/functions/index.js b/lib/less/functions/index.js index 1b778319b..a9f8aaffa 100644 --- a/lib/less/functions/index.js +++ b/lib/less/functions/index.js @@ -10,6 +10,7 @@ module.exports = function(environment) { require('./color'); require('./color-blending'); require('./data-uri')(environment); + require('./list'); require('./math'); require('./number'); require('./string'); diff --git a/lib/less/functions/list.js b/lib/less/functions/list.js new file mode 100644 index 000000000..9cfd61f23 --- /dev/null +++ b/lib/less/functions/list.js @@ -0,0 +1,101 @@ +var Dimension = require('../tree/dimension'), + Declaration = require('../tree/declaration'), + Ruleset = require('../tree/ruleset'), + Selector = require('../tree/selector'), + Element = require('../tree/element'), + functionRegistry = require('./function-registry'); + +var getItemsFromNode = function(node) { + // handle non-array values as an array of length 1 + // return 'undefined' if index is invalid + var items = Array.isArray(node.value) ? + node.value : Array(node); + + return items; +}; + +functionRegistry.addMultiple({ + _SELF: function(n) { + return n; + }, + extract: function(values, index) { + index = index.value - 1; // (1-based index) + + return getItemsFromNode(values)[index]; + }, + length: function(values) { + return new Dimension(getItemsFromNode(values).length); + }, + each: function(list, rs) { + var i = 0, rules = [], newRules, iterator; + + if (list.value) { + if (Array.isArray(list.value)) { + iterator = list.value; + } else { + iterator = [list.value]; + } + } else if (list.ruleset) { + iterator = list.ruleset.rules; + } else if (Array.isArray(list)) { + iterator = list; + } else { + iterator = [list]; + } + + var valueName = '@value', + keyName = '@key', + indexName = '@index'; + + if (rs.params) { + valueName = rs.params[0] && rs.params[0].name; + keyName = rs.params[1] && rs.params[1].name; + indexName = rs.params[2] && rs.params[2].name; + rs = rs.rules; + } else { + rs = rs.ruleset; + } + + iterator.forEach(function(item) { + i = i + 1; + var key, value; + if (item instanceof Declaration) { + key = typeof item.name === 'string' ? item.name : item.name[0].value; + value = item.value; + } else { + key = new Dimension(i); + value = item; + } + + newRules = rs.rules.slice(0); + if (valueName) { + newRules.push(new Declaration(valueName, + value, + false, false, this.index, this.currentFileInfo)); + } + if (indexName) { + newRules.push(new Declaration(indexName, + new Dimension(i), + false, false, this.index, this.currentFileInfo)); + } + if (keyName) { + newRules.push(new Declaration(keyName, + key, + false, false, this.index, this.currentFileInfo)); + } + + rules.push(new Ruleset([ new(Selector)([ new Element("", '&') ]) ], + newRules, + rs.strictImports, + rs.visibilityInfo() + )); + }); + + return new Ruleset([ new(Selector)([ new Element("", '&') ]) ], + rules, + rs.strictImports, + rs.visibilityInfo() + ).eval(this.context); + + } +}); diff --git a/lib/less/functions/types.js b/lib/less/functions/types.js index f1706d3b2..219bcf23e 100644 --- a/lib/less/functions/types.js +++ b/lib/less/functions/types.js @@ -20,15 +20,8 @@ var isa = function (n, Type) { throw { type: 'Argument', message: 'Second argument to isunit should be a unit or a string.' }; } return (n instanceof Dimension) && n.unit.is(unit) ? Keyword.True : Keyword.False; - }, - getItemsFromNode = function(node) { - // handle non-array values as an array of length 1 - // return 'undefined' if index is invalid - var items = Array.isArray(node.value) ? - node.value : Array(node); - - return items; }; + functionRegistry.addMultiple({ isruleset: function (n) { return isa(n, DetachedRuleset); @@ -77,16 +70,5 @@ functionRegistry.addMultiple({ }, 'get-unit': function (n) { return new Anonymous(n.unit); - }, - extract: function(values, index) { - index = index.value - 1; // (1-based index) - - return getItemsFromNode(values)[index]; - }, - length: function(values) { - return new Dimension(getItemsFromNode(values).length); - }, - _SELF: function(n) { - return n; } }); diff --git a/lib/less/parser/parser.js b/lib/less/parser/parser.js index f63d6ba56..ad83d5538 100644 --- a/lib/less/parser/parser.js +++ b/lib/less/parser/parser.js @@ -1377,10 +1377,33 @@ var Parser = function Parser(context, imports, fileInfo) { }, detachedRuleset: function() { + var argInfo, params, variadic; + + parserInput.save(); + if (parserInput.$re(/^[.#]\(/)) { + /** + * DR args currently only implemented for each() function, and not + * yet settable as `@dr: #(@arg) {}` + * This should be done when DRs are merged with mixins. + * See: https://github.com/less/less-meta/issues/16 + */ + argInfo = this.mixin.args(false); + params = argInfo.args; + variadic = argInfo.variadic; + if (!parserInput.$char(')')) { + parserInput.restore(); + return; + } + } var blockRuleset = this.blockRuleset(); if (blockRuleset) { + parserInput.forget(); + if (params) { + return new tree.mixin.Definition(null, params, blockRuleset, null, variadic); + } return new tree.DetachedRuleset(blockRuleset); } + parserInput.restore(); }, // diff --git a/lib/less/tree/mixin-definition.js b/lib/less/tree/mixin-definition.js index b2b5661d6..3d0cc6252 100644 --- a/lib/less/tree/mixin-definition.js +++ b/lib/less/tree/mixin-definition.js @@ -8,7 +8,7 @@ var Selector = require('./selector'), utils = require('../utils'); var Definition = function (name, params, rules, condition, variadic, frames, visibilityInfo) { - this.name = name; + this.name = name || 'anonymous mixin'; this.selectors = [new Selector([new Element(null, name, false, this._index, this._fileInfo)])]; this.params = params; this.condition = condition; diff --git a/test/css/functions-each.css b/test/css/functions-each.css new file mode 100644 index 000000000..4d9bb9786 --- /dev/null +++ b/test/css/functions-each.css @@ -0,0 +1,48 @@ +.sel-blue { + a: b; +} +.sel-green { + a: b; +} +.sel-red { + a: b; +} +.each { + index: 1, 2, 3, 4; + item1: a; + item2: b; + item3: c; + item4: d; + nest-1-1: 10px 1; + nest-2-1: 15px 2; + nest-1-2: 20px 1; + nest-2-2: 25px 2; + padding: 10px 20px 30px 40px; +} +.each .nest-anon { + nest-1-1: a c; + nest-1-2: a d; + nest-2-1: b c; + nest-2-2: b d; +} +.set { + one: blue; + two: green; + three: red; +} +.set-2 { + one-1: blue; + two-2: green; + three-3: red; +} +.single { + val: true; + val2: 2; + val3: 4; +} +.box { + -less-log: a; + -less-log: b; + -less-log: c; + -less-log: d; +} diff --git a/test/less/functions-each.less b/test/less/functions-each.less new file mode 100644 index 000000000..eebaf1b05 --- /dev/null +++ b/test/less/functions-each.less @@ -0,0 +1,81 @@ +@selectors: blue, green, red; +@list: a b c d; + +each(@selectors, { + .sel-@{value} { + a: b; + } +}); + +.each { + each(@list, { + index+: @index; + item@{index}: @value; + }); + + // nested each + each(10px 15px, 20px 25px; { + // demonstrates nesting of each() + each(@value; #(@v, @k, @i) { + nest-@{i}-@{index}: @v @k; + }); + }); + + // nested anonymous mixin + .nest-anon { + each(a b, .(@v;@i) { + each(c d, .(@vv;@ii) { + nest-@{i}-@{ii}: @v @vv; + }); + }); + } + + // vector math + each(1 2 3 4, { + padding+_: (@value * 10px); + }); +} + +@set: { + one: blue; + two: green; + three: red; +} +.set { + each(@set, { + @{key}: @value; + }); +} +.set-2() { + one: blue; + two: green; + three: red; +} +.set-2 { + each(.set-2(), .(@v, @k, @i) { + @{k}-@{i}: @v; + }); +} + +.pick(@a) when (@a = 4) { + val3: @a; +} +.single { + each(true, { + val: @value; + }); + @exp: 1 + 1; + each(@exp, { + val2: @value; + }); + each(1 2 3 4, { + .pick(@value); + }); +} + +@list: a b c d; +.box { + each(@list, { + -less-log: extract(@list, @index); + }) +}