LESS variable name for mixin with parameters - less

What I want:
a mixin with parameters named by a variable.
Without parameters it works like a charm:
#foo: test;
.#{foo} {
length: 20px;
}
.bar {
.test;
}
puts out:
.bar {
length: 20px;
}
Perfetct so far.
Now I want to do this:
#foo: test;
.#{foo}(#var) {
length: #var;
}
.bar {
.test(20px);
}
unfortunately outputs ..
ParseError: Missing closing ')' on line 3, column 9:
2
3 .#{foo}(#var) {
4 length: #var;
I've already tried all crazy sorts of escaping. No luck so far.
Any ideas?

Related

Nesting Variables within a Variable

Is it somehow possible to nest several variables within one variable?
The code below doesn't throw an error, but I have no clue how to call the inner "first" or "second"
$themes: (
light: (
text: (
first: black,
second: grey
),
button: blue
),
);
map_get doesn't seem to work because it's too deep nested.
EDIT: The provided solution by Arkellys is basically working. The Problem I got now is that i want to automate this with the simple call of a function. The function should call a mixin where the theming should style my components.
The mixin looks like that:
#mixin theme($themes) {
#each $theme, $map in $themes {
.theme-#{$theme} & {
$theme-map: () !global;
#each $key, $submap in $map {
$value: map-get(map-get($themes, $theme), '#{$key}');
$theme-map: map-merge($theme-map, ($key: $value)) !global;
}
#content;
$theme-map: null !global;
}
}
}
and the function that calls the mixin looks like that:
#function theme($key) {
#return map-get($theme-map, $key);
}
In my scss I call it like that:
.past {
#include theme($themes) {
color: theme('text-second');
}
}
So how do I detect if i use a simple 1st level nesting or already a 2nd level nesting?
Yes it is possible, and to get a value in your map you can use a #function such as this one by Hugo Giraudel.
As an example:
#function getDeepMapValue($map, $keys...) {
#each $key in $keys { $map: map-get($map, $key); }
#return $map;
}
$themes: (
light: (
text: (
first: black,
second: grey
)
)
);
.class {
color: getDeepMapValue($themes, light, text, first);
}
Will return:
.class {
color: black;
}
Edit: If I understand your #mixin correctly, doing something along these lines should work. First, edit your #function theme to use getDeepMapValue:
#function theme($keys...) {
#return getDeepMapValue($theme-map, $keys...);
}
And then:
.past {
#include theme($themes) {
color: theme(text, second); // grey
background: theme(button); // blue
}
}

Less variables in comments

I need to generate some CSS with some comments right above the classes and in those comments, I need to evaluate some variables. I've been successful in doing this in Sass but Less doesn't seem to have the same functionality.
Here's what I need:
/**Header*/
.Header {
font-size: 1.5em;
}
Here's my attempt in Sass:
#function str-replace($string, $search, $replace: '') {
$index: str-index($string, $search);
#if $index {
#return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace);
}
#return $string;
}
#mixin rte_property($name) {
/**#{$name}*/
.#{str-replace($name, ' ', '')} {
#content;
}
}
#include rte_property(Header) {
font-size: 1.5em;
}
Here's my attempt in Less:
.rte_element (#name, #rules) {
#className: e(replace(#name, " ", ""));
/**#{name}*/
.#{className} {
#rules();
}
}
.rte_element("Header 2", {
font-size: 1.5em;
});
Is it possible for Less to interpolate/evaluate variables in comments? If so, how?
There is no straight-forward (non-hacky) way to achieve this in Less. Less compiler does not evaluate any variable that is present within comments and so it would continue to be printed as #{var} instead of the evaluated value.
However, that doesn't mean there is no way at all. There is a way of achieving something close. That would be to put the entire comment text into a temporary variable and print it before the selector using selector interpolation technique.
The comment would not cause any impact to how the compiled CSS works (because the UA will just ignore the comments, refer the snippet at the end - it uses the compiled CSS produced by this code) but it doesn't have a line-break.
Note: I would definitely not recommend implementing such hacky solutions. I have given it here just to show that it can be done in a different way.
Less Code:
.rte_element(#name, #rules) {
#className: e(replace(#name, " ", ""));
#comment: ~"/* #{name} */"; /* store the comment structure as a variable */
#{comment} .#{className} { /* print it before the selector */
#rules();
}
}
.rte_element("Header 2", {
font-size: 1.5em;
color: red;
});
.rte_element("Header 3", {
font-size: 1.75em;
color: blue;
});
Demo with compiled CSS:
/* Header 2 */ .Header2 {
font-size: 1.5em;
color: red;
}
/* Header 3 */ .Header3 {
font-size: 1.75em;
color: blue;
}
<div class="Header2">Header 2 text</div>
<div class="Header3">Header 3 text</div>
Code for a line break after comment:
This is even more hacky but it seems to work in the latest compiler.
.rte_element(#name, #rules) {
#className: e(replace(#name, " ", ""));
#comment: ~"/* #{name} */
" ; /* note how there is a line break inside the quotes */
#{comment} .#{className} {
#rules();
}
}

Less adds unexpected space to variable

For some reason, the output of nth child is rendered with un unexpected space. Can anyone help?
Renders:
// Part of render
body.domain-bsci-fta-local #block-domain-switcher ul li:nth-child( 3) {
background-color: #e14313;
}
From code:
// Variables
#a-primary: #018f9e;
#b-primary: #2b6a7c;
#c-primary: #e14313;
#d-primary: #009966;
#domain-a: 'a-local';
#domain-b: 'b-fta-local';
#domain-c: 'c-fta-local';
#domain-d: 'd-fta-local';
#domains: #domain-a #a-primary 1, #domain-b #b-primary 2, #domain-c #c-primary 3, #domain-d #d-primary 4;
// Call
body {
.generate-menus();
}
// Functions
.generate-menus() {
.for(#domains);
.-each(#domain) {
#dn: e(extract(#domain, 1));
#dc: extract(#domain, 2);
#dr: extract(#domain,3);
.generate-menu(#dn, #dc, #dr);
}
}
.generate-menu(#domainname, #domaincolor, #domaincount) {
&.domain-#{domainname} {
#block-domain-switcher {
ul {
li {
&:nth-child(#{domaincount}) {
background-color: #domaincolor;
a {
border-bottom: 5px solid;
color: white !important;
}
}
}
}
}
.navigation .submenu {
background-color: #domaincolor;
}
}
}
// ............................................................
// .for
.for(#i, #n) {
.-each(#i)
}
.for(#n) when (isnumber(#n)) {
.for(1, #n)
}
.for(#i, #n) when not (#i = #n) {
.for((#i + (#n - #i) / abs(#n - #i)), #n);
}
// ............................................................
// .for-each
.for(#array) when (default()) {
.for-impl_(length(#array))
}
.for-impl_(#i) when (#i > 1) {
.for-impl_((#i - 1))
}
.for-impl_(#i) when (#i > 0) {
.-each(extract(#array, #i))
}
Note: As mentioned by seven-phases-max in his comments to the question, this was a bug which has already been fixed in v2.x. Leaving this answer (with the work-around solution) as-is to help future readers who can't upgrade their compiler for whatever reason.
The problem happens only for selectors which use selector interpolation and are nested within one or more parent selectors. It can be solved by using a temporary variable which contains the pseudo-selector like below: (it uses escaped string feature)
Option 1:
ul {
li {
#selector: ~":nth-child(#{domaincount})"; /* the selector is formed here */
&#{selector} { /* and used here */
background-color: #domaincolor;
a {
border-bottom: 5px solid;
color: white !important;
}
}
}
}
Option 2:
li {
#count: ~"(#{domaincount})";
&:nth-child#{count} { /* and used here */
background-color: #domaincolor;
a {
border-bottom: 5px solid;
color: white !important;
}
}
}
Sample Compiled Output:
body.domain-a-local #block-domain-switcher ul li:nth-child(1) {
background-color: #018f9e;
}
Related Links:
concatenate values in less (css) without a space
Redudant space in interpolated selectors like nth(...)
As mentioned above and in the linked issue thread, the issue happens only when the selector is formed using selector interpolation and is nested under one or more parents.
This works
// Variables
#list: a 1;
#num: extract(#list, 2);
// Usage
body div:nth-child(#{num}) {
color: #444;
}
But this doesnt
// Variables
#list: a 1;
#num: extract(#list, 2);
// Usage
body {
div:nth-child(#{num}) {
color: #444;
}
}

LESS: Guarded Mixins like MediaQuery

I would like to use LESS Guarded Mixins in a way similar to MediaQuery concept, so declaring a condition that, if verified, contains a set of css rules and declaration.
For the moment I wrote this example code:
.color-theme(#color-type) when (#color-type='cold')
{
color:gren;
}
.color-theme(#color-type) when (#color-type='hot')
{
color:red;
}
text-container
{
.color-theme('hot');
width:960px;
}
My intention is to write a set of classes that must be used only if a particular condition is satisfied, in a way very similar to MediaQueries logic.
This lines of code runs... but in this wa I should repeat the parameter value 'hot' for each new css class.
I would like to have something like
when (#color-type='hot')
{
body { ... }
.myclass { ... }
...
}
How could I obtain this?
This is not really possible exactly like that (as you can not pass a block into a mixin you are calling ... what you are trying to do is possible in Sass with the #content directive). What you could do in Less instead, is define a mixin that outputs a particular block (cold or hot) depending on the #color-type variable passed.
a) Mixins with specific arguments:
First you can make a general output mixin, that does not show anything, no
matter what the #color-type is (so you don't get an error if an undefined block is called):
.show(#color-type) { }
Then you define the blocks (similarly to how you would do with media
queries, except here you will need an extra mixin call):
.show('cold') {
body {
color: blu;
}
.myclass {
background: url(cold.png);
}
}
.show('hot') {
body {
color: red;
}
.myclass {
background: url(hot.png);
}
}
Now you just need to call the mixin. And depending on what the variable you pass, the right block will be
shown (or if no block with that variable has been defined, there
will be no output). For example now you can call show(), passing a
variable that you defined somewhere earlier:
#color-type: 'hot';
.show(#color-type);
or directly
.show('hot');
and the CSS output will be:
body {
color: red;
}
.myclass {
background: url(hot.png);
}
b) Guards:
Instead of defining the mixins with particlular arguments (e.g. .show('hot'){ ... }), you can use guards, like so: .show(#color-type) when (#color-type = 'hot') { ... }), or alternatively like so: .show() when (#color-type = 'hot') { ... } if you define the variable somewhere earlier and the just have to call the mixin .show() to return the respective block:
.show() when (#color-type = 'cold') {
body {
color: blue;
}
.myclass {
background: url(cold.png);
}
}
.show() when (#color-type = 'hot') {
body {
color: red;
}
.myclass {
background: url(hot.png);
}
}
// setting the variable
#color-type: 'hot';
// calling the mixin
.show();
Maybe also of interest - some discussion connected to this topic:
Issue #965: Mixins should accept LESS blocks

Strange operations behaviour with LESS

This one compiles:
.myclass {
.mymixin(2);
}
.mymixin(#parameter) {
width: ((#parameter*1)*12px);
}
This one does not:
.myclass {
.mymixin(2);
}
.mymixin(#parameter) {
width: ((#parameter-1)*12px);
}
Does anyone have a clue what's the problem with the second one?
Less treats #parameter-1 as a variable as you can tell from the compiler error:
Error line 2: variable #parameter-1 is undefined
If you insert spaces it works as expected:
.mymixin(#parameter) {
width: ((#parameter - 1)*12px);
}