LESSCSS - use calculation and return value - css

H i,
Hoping you can help.
Is there a way for LESS to return just a value - feel like I'm missing something very obvious
Say I have:
#unit:em;
#basevalue:1;
Can I use something to give me a shorthand return for -
.someClass { padding: ~'#{basevalue}#{unit}'; }
Like say:
.returnUnit() { ~'#{basevalue}#{unit}'; }
.someClass { padding: returnUnit(); }
because what I'm ultimately hoping for is:
.returnUnit(#val) { #basevalue*#val#{unit}; }
.someClass { padding:returnUnit(0.5); }
Using a mixing I have to define the style property, however the value of this return function would be used for many different css properties.
Hope I made sense and I am just lacking deeper rtfm.
Many Thanks if you can.
Update as #Chococrocs pointer to the docs, thanks.
.average(#x, #y) {
#average: ((#x + #y) / 2);
}
div {
.average(16px, 50px); // "call" the mixin
padding: #average; // use its "return" value
}
Looks like what I need ? - just seeing if I can always tag on the unit variable to it....
Update: That gets part way ...
.unitRelative(#val) {
#value : #basevalue*#val;
#relative: ~'#{value}#{unit}';
}
/* usage */
.someClass {
.unitRelative(2);
padding: #relative;
}
But not when
.someClass {
.unitRelative(2);
padding:#relative;
.unitRelative(3);
margin:#relative;
}
Is there another way ?

LESS has no way as of yet to create a true "function," so we cope with it.
First
You can just use the unit function, like so:
LESS
.someClass { padding: unit(#basevalue, #unit); }
.someOtherClass { padding: unit(#basevalue*0.5, #unit); }
CSS
.someClass {
padding: 1em;
}
.someOtherClass {
padding: 0.5em;
}
Second
The mixins as functions is okay in some situations, but as you discovered, has the limitation of only setting the value once on the first call (and that is assuming a variable of the same name does not exist in that scope already).
LESS (first works right, second doesn't)
.returnUnit(#val:1) {
#return: unit(#basevalue*#val, #unit);
}
.someThirdClass {
.returnUnit(0.4);
padding: #return;
}
.someOoopsClass {
.returnUnit(0.4);
padding: #return;
.returnUnit(0.3);
margin: #return;
}
CSS Output
.someThirdClass {
padding: 0.4em;
}
.someOoopsClass {
padding: 0.4em;
margin: 0.4em; /* Ooops! Not 0.3em! */
}
Third
Limitation of the Second idea can be avoided by a second wrapping, as it isolates the scope for each variable returned by .returnUnit(), like so:
LESS
.someAccurateClass {
& {
.returnUnit(0.4);
padding: #return;
}
& {
.returnUnit(0.3);
margin: #return;
}
}
CSS Output
.someAccurateClass {
padding: 0.4em;
margin: 0.3em; /* Yes! */
}
Fourth
It may be better to merge ideas from the First and Third by adding some global variables and doing this:
LESS
#unit:em;
#basevalue:1;
#val: 1;
#setUnit: unit(#basevalue*#val, #unit);
.someAwesomeClass {
& {
#val: .2;
padding: #setUnit;
}
& {
#val: .1;
margin: #setUnit;
}
}
CSS Output
.someAwesomeClass {
padding: 0.2em;
margin: 0.1em;
}
So here we are using the unit function still as the First idea, but have assigned it to the variable #setUnit, so each time the variable is called, it runs the function. We still isolate our property blocks using the & {} syntax like in the Third solution, but now we just set the #val to what we want and call the #setUnit where we want.

There is a hack that is mentioned here by fabienevain using a global js function. Seems to be good option if you want a function with actual return value.
#fn: ~`fn = function(a) { return a; }`;
#arg: 8px;
p {
font-size: ~`fn("#{arg}")`;
}

I think you look for this, Mixin as a function
http://lesscss.org/features/#mixins-as-functions-feature
Reading your question, I think is what you're wishing, ;D

// mixin
.parseInt(#string) {
#parseInt: unit(#string, );
}
Usage:
.selector {
.parseInt(100px);
width: #parseInt + 10; // px will automatically be appended
}
Result:
.selector {
width: 110px;
}

one of the simplest work around would be to pass the property and the value.
mixin.less
.lighter(#property, #color) {
#{property}: multiply(white, fade(#color, 10%));
}
use.less
.my-class{
.lighter(background-color, #FF0000);
}
Results:
.my-class{
background-color: #fbe8eb;
}

Related

Split variables in SASS / SCSS

I have a SASS variable like the following:
$surrounding-margin: 0 40px;
And I'm using it like this (the irrelevant properties have been removed):
#content {
margin: $surrounding-margin;
& #close {
margin-right: -$surrounding-margin[1]; // If this was JS.
}
}
Obviously, -$surrounding-margin[1] won't work. What will? I need the second value of the variable, in the negative. How can I do this?
Just use for example nth because it's just a list:
$surrounding-margin: 0 40px;
#content {
margin: $surrounding-margin;
& #close {
margin-right: -(nth($surrounding-margin, 2));
}
}

Name clash between Less mixin and CSS selector

I have this simplified Less script
.placeholder(#color: #333333) {
&::-webkit-input-placeholder { color: #color; }
}
input {
.placeholder();
}
.placeholder {
margin-top: 20px;
}
The output when I run this through my local compiler or winless online less compiler is
input {
margin-top: 20px;
}
input::-webkit-input-placeholder {
color: #333333;
}
.placeholder {
margin-top: 20px;
}
Insted of the desired output
input::-webkit-input-placeholder {
color: #333333;
}
.placeholder {
margin-top: 20px;
}
Is this a bug or am I missing something here?
By the result it looks to me like I can't have CSS-selectors with the same name as mixins with default values.
I'm running into this problem when compiling Bootstrap with my site specific code. In this particular case I can work around it, but as the project grows and I include other projects I can't imaging I have to keep track of any mixins with default values?
Edit: I see now that I should have read the manual and pretty much seen on the first page of the docs that everything can be treated as a mixin.
In Less, everything is technically a mixin irrespective of whether we write it with parantheses (as in with parameters) or without parantheses (as in like a CSS class selector). The only difference between the two is that when the parantheses are present, the properties present within it are not output unless called from within a selector block.
Quoting the Less Website:
It is legal to define multiple mixins with the same name and number of parameters. Less will use properties of all that can apply.
In this case, since the other mixin has a default value for its only parameter, both the properties can apply when called without any parameter and hence there is no way to avoid it from happening.
Workaround Solution: One possible solution to work-around this problem is to enclose all such conflicting rules within a parent selector (like body).
.placeholder(#color: #333333) {
&::-webkit-input-placeholder { color: #color; }
}
input {
.placeholder();
}
body{
.placeholder{
margin-top: 20px;
}
}
Compiled CSS:
input::-webkit-input-placeholder {
color: #333333;
}
body .placeholder {
margin-top: 20px;
}
Option 2: Extracted from the solution posted by seven-phases-max in the Less GitHub Issue thread.
For the particular use-case one of possible workarounds is to isolate conflicting classes in unnamed scope so they won't interfere with external names:
.placeholder(#color: #333333) {
&::-webkit-input-placeholder { color: #color; }
}
input {
.placeholder();
}
& { // unnamed namespace
.placeholder {
background: #ffffff;
}
} // ~ end of unnamed namespace
Note: The above is a straight copy/paste from the GitHub thread without any modifications so as to not tamper with the information.
#mixin placeholder(#color: #333333) {
&::-webkit-input-placeholder { color: #color; }
}
input {
#include placeholder();
}
.placeholder {
margin-top: 20px;
}
that should work.
So if i understood right, you just want to add 20px on top of the placeholder ? Add padding-top to input instead.
input {
padding-top: 20px;
}

LESS combine ruleset into two with different variables

I'm trying to combine one ruleset into two different rulesets with variable values swapped. Main purpose is LTR/RTL internationalization.
Usage:
h1 {
margin-top: 10px;
.directions({
margin-#{left}: 5px;
});
}
Expected output:
h1 {
margin-top: 10px;
}
.ltr h1 {
margin-left: 5px;
}
.rtl h1 {
margin-right: 5px;
}
I was able to get some results with the Passing Rulesets to Mixins function available in Less 1.7
.directions(#rules) {
#left: left;
.ltr & { #rules(); }
#left: right;
.rtl & { #rules(); }
}
The problem is that the #left variable is always set to the last value used in .directions() mixin (right in this case). Is there any way how to swap variable or pass it back outside of the mixin?
Note: I do not want to output LTR/RTL to two separate files, I'm trying to combine them into one file.
To understand Less variables scope and life-time see:
Lazy Evaluation (aka Lazy Loading).
Variable Semantics
Most Misunderstood
Scope
Last Declaration Wins
The solution for your particular case is as simple as:
.directions(#styles) {
.ltr & {
#left: left;
#styles();
}
.rtl & {
#left: right;
#styles();
}
}

SASS CSS: Target Parent Class from Child

I am using SASS and found an inconvenience. This is an example of what I am trying to do:
.message-error {
background-color: red;
p& {
background-color: yellow
}
}
Expected CSS:
.message-error {
background-color: red;
}
p.message-error {
background-color: yellow ;
}
The idea: all elements with .message-error will be red, except if it is p.message-error. This is not real-life situation, just to show an example.
SASS is not able to compile this, I even tried string concatenation. Is there some plugin that will do exactly the same?
NOTE:
I know I can put another CSS definition like:
p.message-error{....}
...under, but I would like to avoid that and use one place for all .message-error definitions.
Thanks.
As of Sass 3.4, this is now supported. The syntax looks like this:
.message-error {
background-color: red;
#at-root p#{&} {
background-color: yellow
}
}
Note the #at-root directive and the interpolation syntax on the ampersand. Failure to include the #at-root directive will result in a selector like .message-error p.message-error rather than p.message-error.
You can assign the current selector to a variable and then use it at any depth:
.Parent {
$p: &;
&-Child {
#{$p}:focus & {
border: 1px solid red;
}
#{$p}--disabled & {
background-color: grey;
}
}
}
Natalie Weizenbaum (the lead designer and developer of Sass) says it will never be supported:
Currently, & is syntactically the same as an element selector, so it
can't appear alongside one. I think this helps clarify where it can be
used; for example, foo&bar would never be a valid selector (or would
perhaps be equivalent to foo& bar or foo &bar). I don't think this use
case is strong enough to warrant changing that.
Source: #282 – Element.parent selector
To my knowledge, there is no possible workaround.
The best thing to do would be probably this (assuming you have a little more in your .message-error class than just background color.
.message-error {
background-color: red;
}
p.message-error {
#extend .message-error;
background-color: yellow
}
This approach doesn't offer that close grouping, but you can just keep them close to each other.
I had the same problem so I made a mixin for that.
#mixin tag($tag) {
$ampersand: & + '';
$selectors: simple-selectors(str-replace($ampersand, ' ', ''));
$main-selector: nth($selectors, -1);
$previous-selectors: str-replace($ampersand, $main-selector, '');
#at-root {
#{$previous-selectors}#{$tag}#{$main-selector} {
#content;
}
}
}
To make it work, you will need a string replacement function as well (from Hugo Giraudel):
#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;
}
How it works:
SCSS
.foo {
color: blue;
#include tag(p) {
color: red;
}
}
Output
.foo {
color: blue;
}
p.foo {
color: red;
}
Use case
This method works with nested selectors but not whit compound ones.
#Zeljko It is no possible to do what you want via SASS.
See Nex3 comment: https://github.com/nex3/sass/issues/286#issuecomment-7496412
The key is the space before the '&':
.message-error {
background-color: red;
p & {
background-color: yellow
}
}
instead of:
.message-error {
background-color: red;
p& {
background-color: yellow
}
}
I think if you want to keep them grouped by parent selector, you might need to add a common parent:
body {
& .message-error {background-color: red;}
& p.message-error {background-color: yellow}
}
Of course, body could be replaced with some other common parent, such as #Content or another div name that will contain all the error messages.
UPDATE (Another Idea)
If you leverage #for and lists then it seems like this should work (what I don't know for sure is if the list will allow the . (period).
#for $i from 1 to 3 {
nth(. p. ul., #{$i})message-error {
background-color: nth(red yellow cyan, #{$i}));
}
}
Should compile to something like:
.message-error {
background-color: red;}
p.message-error {
background-color: yellow;}
ul.message-error {
background-color: cyan;}
I made a mixin that solves this problem.
Github: https://github.com/imkremen/sass-parent-append
Example: https://codepen.io/imkremen/pen/RMVBvq
Usage (scss):
.ancestor {
display: inline-flex;
.grandparent {
padding: 32px;
background-color: lightgreen;
.parent {
padding: 32px;
background-color: blue;
.elem {
padding: 16px;
background-color: white;
#include parent-append(":focus", 3) {
box-shadow: inset 0 0 0 8px aqua;
}
#include parent-append(":hover") {
background-color: fuchsia;
}
#include parent-append("p", 0, true) {
background-color: green;
}
}
}
}
}
Result (css):
.ancestor {
display: inline-flex;
}
.ancestor .grandparent {
padding: 32px;
background-color: lightgreen;
}
.ancestor .grandparent .parent {
padding: 32px;
background-color: blue;
}
.ancestor .grandparent .parent .elem {
padding: 16px;
background-color: white;
}
.ancestor:focus .grandparent .parent .elem {
box-shadow: inset 0 0 0 8px aqua;
}
.ancestor .grandparent .parent:hover .elem {
background-color: fuchsia;
}
.ancestor .grandparent .parent p.elem {
background-color: green;
}
I created package/mixin with a similar solution :) (Maybe it will help U)
https://github.com/Darex1991/BEM-parent-selector
so writing:
.calendar-container--theme-second-2 {
.calendar-reservation {
#include BEM-parent-selector('&__checkout-wrapper:not(&--modifier):before') {
content: 'abc';
}
}
}
This mixin will add selector only for the last parent:
.calendar-container--theme-second-2 .calendar-reservation__checkout-wrapper:not(.calendar-reservation--modifier):before {
content: 'abc';
}
More info on the repo.
I have ran into this before as well. Bootstrap 3 handles this using a parent selector hack. I've tweaked it slightly for my own purposes...
#mixin message-error() {
$class: '.message-error';
#{$class} {
background-color: red;
}
p#{$class} {
background-color: yellow;
}
}
#include message-error();
wheresrhys uses a similar approach above, but with some sass errors. The code above allows you to manage it as one block and collapse it in your editor. Nesting the variable also makes it local so you can reuse $class for all instances where you need to apply this hack. See below for a working sample...
http://sassmeister.com/gist/318dce458a9eb3991b13
I use an #mixin function like this, when i need change some element in middle
of a sass big tree.
The first parameters is the parent element, the target, and the second the class that should have.
SASS
#mixin parentClass($parentTarget, $aditionalCLass) {
#at-root #{selector-replace(&, $parentTarget, $parentTarget + $aditionalCLass)} {
#content;
}
}
Sample,
like i need to improve font size in a strong tag, when .txt-target had .txt-strong too
HTML
<section class="sample">
<h1 class="txt-target txt-bold">Sample<strong>Bold</strong>Text</h1>
</section>
SASS
section{
.txt-target{
strong{
#include parentClass('.txt-target','.txt-bold'){
font-weight:bold;
font-size:30px;
}
}
}
}
Font:
https://sass-lang.com/documentation/at-rules/at-root
Here you can see a function called #mixin unify-parent($child) that looks like this
This cheat might work
{
$and: .message-error;
#{$and} {
background-color: red;
}
p#{$and} {
background-color: yellow
}
}
You may even be able to use $& as your variable name but I'm not 100% sure it won't throw an error.
And SASS has inbuilt scoping, which removes having to worry about the value of $and leaking out to the rest of your stylesheet
Variables are only available within the level of nested selectors
where they’re defined. If they’re defined outside of any nested
selectors, they’re available everywhere.
In the Current Release: Selective Steve (3.4.14) this is now possible, just need to update a little bit your code:
.message-error {
background-color: red;
p &{
background-color: yellow
}
}
this only works if you are one level nested, for instance it does not work if you have something like this:
.messages{
.message-error {
background-color: red;
p &{
background-color: yellow
}
}
}

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