Build list of selectors with LESS
css, less
Solution
As already mentioned your attempt is almost there, it does not work because of variable visibility rules. Notice that each `.selector-list` iteration defines new `@selector-list` variable which have higher precedence in the current scope but does not override `@selector-list` variables for the outer scope (i.e. scopes of the previous `.selector-list` iterations and above). So when you use `@selector-list` after the initial `.selector-list` call you get the value set in the "highest" `.selector-list` iteration (i.e. the first one with `@i = 1`).
To "return" a value from the last iteration of a recursive loop you need to define a variable with this value only within that last iteration. Usually the simplest way to do this is to provide a "terminal" mixin (i.e. a specialization for the last call of the recursive mixin). In fact you would need such terminal anyway to handle the final comma of the list. E.g.:
#1
.selector-list(@parent, @children, @i: 1, @list: "") when (@i < length(@children)) {
@child: extract(@children, @i);
.selector-list(@parent, @children, (@i + 1), "@{list} @{parent} @{child},");
}
.selector-list(@parent, @children, @i, @list) when (@i = length(@children)) {
@child: extract(@children, @i);
@selector-list: e("@{list} @{parent} @{child}");
}
// usage:
@text-elements: p, ul, ol, table;
.selector-list("body.single .entry-content", @text-elements);
@{selector-list} {
line-height: 1.8;
}
#2 Same as above just slightly "optimized":
.selector-list(@parent, @children, @i: length(@children), @list...) when (@i > 1) {
.selector-list(@parent, @children, (@i - 1),
e(", @{parent}") extract(@children, @i) @list);
}
.selector-list(@parent, @children, 1, @list) {
@selector-list: e(@parent) extract(@children, 1) @list;
}
// usage:
@text-elements: p, ul, ol, table;
.selector-list("body.single .entry-content", @text-elements);
@{selector-list} {
line-height: 1.9;
}
#3 Speaking of the use-case in general, a "string-based selector manipulation" is not always a good idea in context of Less. The main problem is that Less does not treat such strings as "native" selectors and most of advanced Less features won't work with them (e.g. Less won't recognize `,`, `&` and similar elements there so such rules can't be nested, `extend` also can't see such selectors etc. etc.). An alternative, more "Less-friendly" approach is to define such list as a mixin rather than a variable, e.g.:
.text-elements(@-) {p, ul, ol, table {@-();}}
body.single .entry-content {
.text-elements({
line-height: 1.8;
});
}
and (when you also need to reuse `body.single .entry-content /.text-elements/`):
.text-elements(@-) {
p, ul, ol, table
{@-();}}
.selector-list(@-) {
body.single .entry-content {
.text-elements(@-);
}
}
.selector-list({
line-height: 1.9;
});
etc.
P.S. Also, speaking in even more general, don't miss that in Less a media query may be put into selector ruleset, so depending on a use-case it's also often more easy to write a list of selectors once and set media depended styles inside it (i.e. doing it in opposite to the standard CSS method where you have to repeat same selector(s) for each media query).
Problem
General problem I have media queries, where I change certain text elements like so: ``` body.single .entry-content p, body.single .entry-content ul, body.single .entry-content ol, body.single .entry-content table { line-height: 1.8; } ``` How can I use LESS to build just the list of selectors, not the corresponding CSS values? My attempt I'm not looking for this answer or this answer, which include the CSS in the function. Instead, I imagine it used like this: ``` /* .selector-list() { ... } */ @text-elements: p, ul, ol, table; .selector-list("body.single .entry-content", @text-elements); @selector-list { line-height: 1.8; } ``` It would essentially build a stringified list by prepending the parent ("body.category") and appending a comma to each element from a set. Here's what I've tried just to test output: ``` @text-elements: p, ul, ol, table; .selector-list(@parent, @children, @i:1, @list:"") when (@i <= length(@children)) { @child: extract(@children, @i); @selector-list: "@{list} @{parent} @{child},"; .selector-list(@parent, @children, (@i + 1), @selector-list); } .selector-list("body.single .entry-content", @text-elements); section { content: @selector-list; // yields only " body.single .entry-content p," } ``` Why doesn't @selector-list build past the first element? I don't entirely understand when something is printed/returned, so perhaps I'm going about this all the wrong way?