Nested mixins in LESS behaviour - css

Would it be possible in LESS to have a mixin nested inside another one so that the former gets called only when the element is child of an element with the latter mixin?
I know, confusing, here is a simple example (not working code, just concept):
LESS
.foo(#x) {
width: #x;
.foo(#y) {
width: #y/#x;
}
}
.a {
.foo(20px);
.b {
.foo(2);
}
}
Output CSS
.a {
width: 20px;
}
.a .b {
width: 10px;
}
When I do this, calling .foo(2) on .b gives compiles to width: 2.
Is this supposed to be like this by design, or am I getting something wrong in the syntax? Also, am I approaching the problem from a completely wrong angle and there is perhaps a much simpler solution that I am not considering?
EDIT
Ok, apparently that was fixed with the newest versions of LESS, what I am trying to achieve, though, is slightly more complicated than the minimal example I gave above.
Basically what I would like to happen is that every .foo which is a child of another element with the .foo mixin would take its parent variable for calculation, so, ideally
LESS
.foo(#x) {
width: #x;
.foo(#y) {
width: (#x/#y);
}
}
.a {
.foo(100px);
.b {
.foo(2px);
.c {
.foo(5px);
/* ...and so on */
}
}
}
Output CSS
.a {
width: 100px;
}
.a .b {
width: 50px;
}
.a .b .c {
width: 10px;
}
What I get is, instead:
.a .b .c {
width: 50px;
}
I tried to modify the LESS as follows:
.foo(#x) {
width: #x;
.foo(#y) {
#x: (#x/#y)
width: (#x);
}
}
But I get a syntax error for recursive variable definition. Apparently LESS doesn't allow for definitions like:
#a: 1;
#a: (#a+1);

I think the main problem here would be the #y/#x.
Try #x/#y and it should give something more expected ;-)
As the nested mixins get interpreted different throughout different less implementations I will now split the answer in two parts:
1. Sollution that worked on less2css.org with LESS >1.3.1 (using your nested mixins)
Otherwise I think the above code actually does what you want on less2css.org.
Just as a notion in LESS 1.4, you need to be careful with the math as by default it needs to be in brackets.
If you now just call such a mixin on nonnested rules, like
.b {
.foo(#y);
}
you neen to use units in the input variable, or ad unit() into your mixin, otherwise you will only get the number you put in, 2 for example:
.foo(#x) {
width: unit(#x,px);
.foo(#y) {
width: (#x/#y);
}
}
.a {
.foo(20px);
.b{
.foo(2);
}
}
.c {
.foo(2);
}
will output CSS:
.a {
width: 20px;
}
.a .b {
width: 10px;
}
.c {
width: 2px;
}
You could get even fancier, and check with guards if the attribute in the subclass has pixles as a unit, so that you can also nest classes where you don't pass a factor:
.foo(#x) {
width: #x;
.foo(#y) when (ispixel(#y)) {
width: #y;
}
.foo(#y) when not (ispixel(#y)) {
width: (#x/#y);
}
}
However testing this nested mixin solution appears to work only on less2css.org, but not on jsbin.com, lesstester.com and some other services [where you need to call the nested (second) level of mixin with .foo .too, to apply the second level of styling from the mixin].
So I propose an alternative approach, that I tested and it seems to work on all mentioned pages using less-css compilers with less >1.2.
2. Solution using guards with ispixel that should work on most LESS >1.2 installations
Instead of your nested mixin you could build two mixins, that are based on guards checking for pixels as the unit.
if attribute #x is in pixels => return width:#x; and assign #x to variable #width
if attribute #x is not in pixels => return width:#width/#x; (note: #width needs to be previously assigned by calling the mixin with the #x in px first)
example LESS:
.foo(#x) when (ispixel(#x)) {
width:#x;
#width:#x;
}
.foo(#x) when not (ispixel(#x)) {
width: (#width/#x);
}
.a, .b {
background-color: #ddd;
height: 100px;
}
.a {
.foo(100px);
.b {
background-color: red;
.foo(2);
}
}
th output CSS:
.a, .b {
background-color: #ddd;
height: 100px;
}
.a {
width: 100px;
}
.a .b {
background-color: red;
width: 50px;
}
Differs from your approach but is perhaps a bit more straight forward, and seems to work well.
Edit:
So as you don't want to distinguish between input with unit and input without unit, I can only think of calling a two parametric mixin, where one parameter is used for the base (width in your case) and the second as a factor that defaults to 1. And this you can call now recursively as many times as you want.
LESS:
.foo(#x,#y:1){
width:unit(#parent,px);
#parent:(#x/#y);
}
.a {
.foo(100px);
.b{
.foo(#parent,2px);
.c{
.foo(#parent,5px);
.d{
.foo(#parent,0.05);
}
}
}
}
output CSS:
.a {
width: 100px;
}
.a .b {
width: 50px;
}
.a .b .c {
width: 10px;
}
.a .b .c .d {
width: 200px;
}
So now it does not matter what unit the input has because you can assign the desired unit for the output in the mixin. When you call the mixin it overwrites the #parent variable for the current scope, which gets then inherited to nested rules, where you can use it as the width parameter #x when calling the mixin again. This should give you the desired result.

Related

What does `&#my-id` mean in CSS or SASS?

I inherited some CSS code, which is making use of the & character prior to the id name to style it. It looks something like this:
&#my-id {
// Content and attributes
}
There are also other instances of it, such as:
&:before {
// content and attributes
}
and
&:hover {
// content and attributes
}
What do those mean? I can't find a good way to express this in a search, so I can't find anything. My apologies if this is a duplicate.
It refers to the parent selector.
Input:
.parent {
&.child {
color: red;
}
}
Output:
.parent.child { color: red }
It's really helpful if you're writing CSS in BEM format, something like:
.block {
&__element {
width: 100px;
height: 100px;
&--modifier {
width: 200px;
}
}
}
.block__element { width: 100px; height: 100px;}
.block__element--modifier { width: 200px;}
<div class="block__element"></div>
<div class="block__element block__element--modifier"></div>
And finally, all examples I've shared have been concatenating the class names, but you can also use it as a reference, like:
.parent {
& .child {
color: red;
}
}
.parent {
.child & {
color: blue;
}
}
.parent .child { color: red }
.child .parent { color: blue }
Additional references:
http://lesscss.org/features/#parent-selectors-feature
https://blog.slaks.net/2013-09-29/less-css-secrets-of-the-ampersand/
Using the ampersand (SASS parent selector) inside nested selectors
It's a built-in feature of Sass: https://css-tricks.com/the-sass-ampersand/
You can use it when you're nesting selectors and you need a more specific selector, like an element that has both of two classes:
If your CSS looks like this:
.some-class.another-class { }
And you wanted to nest, the Sass equivalent is:
.some-class {
&.another-class {}
}

Use sass parent (ampersand) selector with fixed root class

In the following code example I generate two squares that ideally should turn red.
The first div .with-root currently stays blue, the second div .without-root turns red. I expect this behaviour, but don't see a proper solution to turn the .with-root div red as well.
Note the difference in the scss file: the first div works with a fixed parent selector, the second one doesn't have a parent. For CSS specificity I need to work with the .with-root {} wrapper.
.with-root {
.with-root__element {
display: block;
width: 5rem;
height: 5rem;
background: blue;
&--red & {
&__item {
background: red;
}
}
}
}
.without-root {
&__element {
display: block;
width: 5rem;
height: 5rem;
background: blue;
&--red & {
&__item {
display: block;
width: 5rem;
height: 5rem;
background: red;
}
}
}
}
The codepen can be found here: https://codepen.io/studiotwist/pen/OzMOmr
Well now that I hopefully understood your question I deleted my wrong idea before and the following solution should work.
Maybe there could be a logic erorr. You have actually three class definitions of .with-root__element and two of them are extended with --red and __item, but the 3rd one is however an extra class which comes in conflict with the other two. You're basically concatenating the endings --red and __item with the parent selector *__element. Also, the --red class is nested inside the *__element one without ending in your CSS but in HTML it is not. *__element and *__element--red are attached in the same HTML tag.
DEBUG
Only showing the first DIV.
.with-root {
.with-root__element {
display: block;
width: 5rem;
height: 5rem;
background: blue;
&--red {
//#error &; // this reference contains the entire document root including the root element .with-root which is wrong
#{&} &__item {
//#error #{&} &__item; // this is a wrong concatenation plus it takes the entire root with it
background: red; // thus, this won't render
}
}
}
}
Debug in action # Sassmeister
POSSIBLE FIX
#mixin bg($bg) {
width: 5rem;
height: 5rem;
background: $bg;
}
.with-root__element {
#include bg(blue);
$this: &;
#at-root {
.with-root {
#{$this}--red #{$this}__item {
#include bg(red);
}
}
}
}
.without-root {
&__element {
#include bg(blue);
&--red &__item {
#include bg(red);
}
}
}
Fork
#at-root is a directive which is useful for your issue as it basically crops the nesting level of the selector and styles can be defined inside the root-body by referencing the parent selector instead of the entire root. So I added a variable $this which will cache the reference. display: block is not needed as div elements have it by default. Sorry about the mixin, it's a habit. --red and __item have now the refence selector *__element.
#at-root Documentation

LESS selectors: how to apply styling only for one of the elements from inside mixin?

I have a code that I can't change:
item.left,
item.centre,
item.right{
.MIXIN();
}
.MIXIN(){
width: 100px;
}
I need to apply width only to .right element. I can only change contents of MIXIN(). I was thinking of using &but it will result either in .right item.right or item.right .right which is not what I want. Is there a way to apply styling only for .right element using contents of MIXIN()?
You can use the negation CSS pseudo-class :not().
item.left,
item.centre,
item.right{
width: 20px;
&:not(.left):not(.centre) {
width: 100px;
}
}
Fiddle: https://jsfiddle.net/e0nd7pk4
You can not do it. The only way is to override the first declaration.
item.left,
item.centre {
width: inherit;
}
How about & but without the space:
.MIXIN() {
width: 100px;
&.right { color: red; }
}
It compiles down to item.right.right which is a bit weird but won't match left and center.
Fiddle: http://jsfiddle.net/c0634wg2/

Importing a LESS file into a scope produces incorrect selector order

I want to wrap the contents of a LESS file into a scope. The recent version of LESS supports this, but some selectors in the resulting CSS are out of expected order. I have identified that parent selectors standing at the end of expression cause this problem.
3rd-party.less
.mixin() {
.container > & {
position: absolute;
}
}
.class1 {
width: 200px;
}
.class2 {
.mixin();
}
wrapped.less
.wrapper {
#import "3rd-party.less";
}
Building wrapped.less produces the following CSS:
.wrapper .class1 {
width: 200px;
}
.container > .wrapper .class2 {
position: absolute;
}
while I want to get this:
.wrapper .class1 {
width: 200px;
}
.wrapper .container > .class2 {
position: absolute;
}
Is it possible to get the desired result without modifying
3rd-party.less?
I have achieved the correct selector order by building 3rd-party.less and importing the result:
.wrapper {
#import (less) "3rd-party.css";
}
This solution produces an intermediate file, but the final output is exactly as expected.

Less - How to insert an #variable into property (as opposed to the value)

In less.js, I'm able to replace values with variables with no problems.
#gutter: 20px;
margin-left:e(%("-%d"), #gutter);
When trying to replace properties with variables, I get errors. How would I perform the following in Less?
#gutter: 20px;
#direction: left;
e(%("margin-%d"), #direction):e(%("-%d"), #gutter);
Thanks to Alvivi for the solution and research (you get the reward for that). I decided to add the following as the actual answer since this is a real way to set it up instead of looking at .blah() pseudo code..
Here's a real strategy for setting it up:
#gutter: 20px;
#dir: left;
#dirOp: right;
then create mixins to enhance margin and padding like so:
.margin(left, #dist:#gutter) {
margin-left:#dist;
}
.margin(right, #dist:#gutter) {
margin-right:#dist;
}
.padding(left, #dist:#gutter) {
padding-left:#dist;
}
.padding(right, #dist:#gutter) {
padding-right:#dist;
}
.lr(left, #dist: 0) {
left: #dist;
}
.lr(right, #dist: 0) {
right: #dist;
}
.. then you can just
#selector {
.margin(#dir);
}
or
#selector {
.margin(#dirOp, 10px);
}
all together:
#selector {
.margin(#dir);
.margin(#dirOp, 50px);
.padding(#dir, 10px);
.padding(#dirOp);
float:#dir;
text-align:#dirOp;
position:absolute;
.lr(#dir);
}
Easy breezy LTR/RTL with LESS! Woot!
Escaping, as says the documentation, is used to create CSS values (not properties).
There is a discussion with some workarounds here. One would be using parametric mixins. For example:
.g () { /* Common properties */ }
.g (right) { margin-right: e(...) }
.g (left) { margin-left: e(...) }

Resources