This question already has answers here:
How to assign to a global variable in Sass?
(2 answers)
Closed 6 years ago.
Is it possible to update a value in a sass map from within a mixin so that the change is saved globally?
Eg
$obj: (
init: false
)
#mixin set($map) {
#if map-get($obj, init) != true {
// mixin hasn't been called before
$map: map-set($map, init, true);
}
#else {
// mixin has been called before
}
}
.test {
#include set($obj);
// sets the init value to true
}
.test-2 {
#include set($obj);
// init value has already been set to true
}
I'm not sure if I understood what you are trying to do, but your code seems to be fine (haven't tested it though), excepting that there is no map-set function, but you can create one or just use map-merge (check here: http://oddbird.net/2013/10/19/map-merge/). I hope that helps.
#update 1: I think I got your question now, you want to pass the reference through the mixin, so if you have multiple maps, you can send the one you want to update to the mixin, I don't think this is possible though, because no reference is kept, if you need to update the variable you have to link directly to it, for exemple, this works (tested):
$obj: (
init: false
);
#mixin set($map) {
#if map_get($map, init) != true {
$obj: map-set($map, init, true) !global;
body {
background-color: #000;
}
} #else {
body {
background-color: #ff0000;
}
}
}
#include set($obj);
#include set($obj);
But if you reference to $map instead of $obj (in this line $obj: map-set($map, init, true) !global;), then a new global map (called $map), will be created. And every time you call the mixin again, it will be replaced by the map you sent as a parameter.
#update 2: I found a way to do it, but you have to keep a global 'map of maps', and every time you update this guy, you send the name of the map you want to update as parameter, so I came up with the following code, it's tested and working fine :)
#function map-set($map, $key, $value) {
$new: ($key: $value);
#return map-merge($map, $new);
}
$maps: (
obj1: (
init: false
),
obj2: (
init: false
),
);
#mixin set($prop) {
#if map_get(map_get($maps, $prop), init) != true {
$obj: map-set(map_get($maps, $prop), init, true);
$maps: map-set($maps, $prop, $obj) !global;
body {
background-color: #000;
}
} #else {
body {
background-color: #ff0000;
}
}
}
#include set(obj1); //black
#include set(obj2); //black
#include set(obj1); //red
#include set(obj2); //red
source: myself
Following on from #Paulo Munoz
Here is the solution
#function map-set($map, $key, $value) {
$new: ($key: $value);
#return map-merge($map, $new);
}
$extend : ();
$obj : (
margin: 0,
padding: 10
);
#mixin set($map, $name) {
#if map-has-key($extend, $name) {
map: has-key;
// call placeholder class
} #else {
$extend: map-set($extend, $name, true) !global;
map: does-not-have-key;
// create placeholder class
// call placeholder class
}
}
.test {
#include set($obj, test);
}
.test-2 {
#include set($obj, test);
}
which generates
.test {
map: does-not-have-key;
}
.test-2 {
map: has-key;
}
Related
I have a bunch of variables that are defined for use in a light/dark theme (two separate files):
$primary-1: red;
$primary-2: green;
and I don't want to declare them all twice when consuming them. I wrote a mixin that does the assignments for me:
#mixin assign-vars {
--primary-1: #{$primary-1};
--primary-2: #{$primary-2};
}
and I would like to use it like this:
#import 'assign-vars';
:root,
:root[data-theme='light'] {
#import 'light-theme-variables';
#include assign-vars;
}
:root[data-theme='dark'] {
#import 'dark-theme-variables';
#include assign-vars;
}
but this does not work, as I get an error saying that $primary-1 is an undefined variable. How can I accomplish this without having to do all of the declarations twice?
I ended up moving the variables into a map:
$light-vars: (
$font-name: 'Some Font',
$primary-1: red
);
$dark-vars: (
$font-name: 'Some Other Font',
$primary-1: green
);
and created a helper mixin to assign them to CSS vars of the same name:
#mixin assign-map-properties($map) {
#each $key, $value in $map {
#if (type-of($value) == 'string') {
--#{$key}: '#{$value}';
}
#else {
--#{$key}: #{$value};
}
}
}
which allowed me to accomplish what I need:
#import 'assign-map-properties';
:root,
:root[data-theme='light'] {
#import 'light-vars';
#include assign-map-properties($light-vars);
}
:root[data-theme='dark'] {
#import 'dark-vars';
#include assign-map-properties($dark-vars);
}
Generated CSS:
:root,
:root[data-theme='light'] {
--font-name: 'Some Font';
--primary-1: red;
}
:root[data-theme='dark'] {
--font-name: 'Some Other Font';
--primary-1: green;
}
I am trying to generate css class names using an #each loop in SASS and I am facing some confusion regarding the scope of a variable declared within the loop.
The variable $infix is meant to prefix the generated class name with a unique break-point-name ('sm','md','lg'...etc), only if the $break-point-width is not = 0
The problem is, the value of the $infix variable, ie, the break-point-name, is the same on each iteration of the loop, although I expected it would be unique per each iteration.
SCSS
$config: (
phone: (
break-point-width:0px,
break-point-name: xs
),
tablet: (
break-point-width:600px,
break-point-name: sm
),
laptop: (
break-point-width:900px,
break-point-name: md
),
desktop: (
break-point-width:1200px,
break-point-name:lg
),
);
#each $key, $map in $config {
$break-point-width: map_get($map, break-point-width);
$break-point-name: map_get($map, break-point-name);
$infix: if($break-point-width == 0px, null, -$break-point-name);
#media (min-width: $break-point-width) {
#for $i from 1 through 2 {
.foo#{$infix}-#{$i} {
content: 'bar';
}
}
}
}
Compiled CSS
#media (min-width: 0px) {
.foo-1 {
content: 'bar'; }
.foo-2 {
content: 'bar'; } }
#media (min-width: 600px) {
.foo-sm-1 {
content: 'bar'; }
.foo-sm-2 {
content: 'bar'; } }
#media (min-width: 900px) {
.foo-sm-1 {
content: 'bar'; }
.foo-sm-2 {
content: 'bar'; } }
#media (min-width: 1200px) {
.foo-sm-1 {
content: 'bar'; }
.foo-sm-2 {
content: 'bar'; } }
As seen in the compiled css output, the first iteration of the loop does not prefix the class name with the value of the $infix variable as was expected. In the remaining iterations all class names are prefixed with the 'sm' break-point-name. This is confusing, as it is seen the value of the $break-point-width variable is unique in each iteration, shown in the generated #media queries, but the value of the $break-point-name variable is the same in each iteration.
I am assuming the $infix variable, hence its value, is not scoped to each iteration of the loop and is only declared once.
I would really appreciate if someone could help me understand the cause of this behavior and how I could solve this problem.
With much thanks and appreciation,
i am using the "vanilla" bootstrap 4 sass media query in my scss files:
#include media-breakpoint-up(xs){}
#include media-breakpoint-up(sm){}
#include media-breakpoint-up(lg){}
#include media-breakpoint-up(xl){}
i know that if i use the css width media query i can couple it with the orientation media query, but i want to use the sass framework.
I want to add the orientation media query in on of them, the XS one. thus it is specific. Because as you know bootsrap 4 is not supporting orientation query for now (strangely).
i tried to concatenat the "orientation query" with the "SASS bootstrap media query (xs)" in different way but i always have a sass error.
Thus What i did is to nest it in the SASS bootstrap media query (xs):
#include media-breakpoint-up(xs){
... some SCSS rules
#media (orientation: landscape){
header{
display:none !important;
}
.navbar{
display:none !important;
}
}
}
The problem i have even tought it is nested into the XS query is that it apply to all breakpoint. it s like it does nt take into account to be nested.
My question: how to concatenate the "orientation query" with the "SASS bootstrap media query (xs)"? Or how to make it specific to the XS breakpoint by nesting it.
Thank you
I've found the solution.
It's possible to combine sass mixin by nesting them, thus I've created the following mixin in my _mixins.scss file:
#mixin orientation($direction) {
$orientation-landscape: "(orientation:landscape)";
$orientation-portrait: "(orientation:portrait)";
#if $direction == landscape {
#media #{$orientation-landscape} { #content; }
}
#if $direction == portrait {
#media #{$orientation-portrait} { #content; }
}
}
Note: i didn't put the "and" in the variable value: "and (orientation:landscape)". SASS or bootstrap put it automatically i suppose.
Then in my SCCS file I've added the following rules:
#include media-breakpoint-down(sm) {
#include orientation(landscape) {
.path-frontpage header {
display: none !important;
}
.path-frontpage .navbar {
display: none !important;
}
}
}
Note: in my first post i was saying that the CSS rules I've nested was applied to all breakpoints, it s because when the CSS is generated the SASS Bootstrap 4 XS breakpoint of is not written, i suppose it's because the value is 0. thus the orientation media query was not combines with a min-width value. So i changed the value to a max-width instead of a min-width, as the Bootstrap 4 SM breakpoint have the 576px value.
The result in the CSS file is what i wanted:
#media (max-width: 767.98px) and (orientation: landscape) {
.path-frontpage header {
display: none !important;
}
.path-frontpage .navbar {
display: none !important;
}
}
I hope it will help the community.
I use this outside of Bootstrap. You should be able to use it with Bootstrap or any other framework, giving you more flexibility in your media queries.
// Extra map functions by Hugo Giraudel
#function map-deep-get($map, $keys...) {
#each $key in $keys {
$map: map-get($map, $key);
}
#return $map;
}
#function map-has-keys($map, $keys...) {
#each $key in $keys {
#if not map-has-key($map, $key) {
#return false;
}
}
#return true;
}
#function map-has-nested-keys($map, $keys...) {
#each $key in $keys {
#if not map-has-key($map, $key) {
#return false;
}
$map: map-get($map, $key);
}
#return true;
}
These are extra map functions Hugo Giraudel wrote up. map-deep-get is basically a simplified nested map-get function. map-has-keys is just like map-has-key, which is built-in to sass, but checks for multiple keys. map-has-nested-keys expands on that by checking for nested keys. This is crucial for this method. I'd definitely look into the extra Sass functions he's built. I've quite easily found use for just about all of them.
// Map
$sizes: (
null: (
breakpoint: 0,
container: 100%
),
xs: (
breakpoint: 480px,
container: 464px
),
sm: (
breakpoint: 768px,
container: 750px
),
md: (
breakpoint: 992px,
container: 970px
),
lg: (
breakpoint: 1200px,
container: 1170px
)
);
This is a simple breakpoint map. I usually use this as a base map for all settings on my projects, so I'll include things like base font-sizes and whatnot in it.
// Breakpoint mixin
#mixin break($screen-min: null, $screen-max: null, $orientation: null) {
$min: $screen-min;
$max: $screen-max;
$o: $orientation;
$query: unquote("only screen");
#if $min != null and $min != "" {
#if map-has-nested-keys($base, sizes, $screen-min) {
$min: map-deep-get($base, sizes, $screen-min, breakpoint);
}
#else {
$min: $screen-min;
}
#if is-number($min) {
$query: append($query, unquote("and (min-width: #{$min})"));
}
}
#if $max != null and $max != "" {
#if map-has-nested-keys($base, sizes, $screen-max) {
$max: map-deep-get($base, sizes, $screen-max, breakpoint);
}
#else {
$max: $screen-max;
}
#if is-number($max) {
$query: append($query, unquote("and (max-width: #{$max})"));
}
}
#if $orientation == landscape or $orientation == portrait {
$o: $orientation;
$query: append($query, unquote("and (orientation: #{$o})"));
}
#else {
$o: null;
}
#media #{$query} {
#content;
}
};
Here's the mixin. You can use the keys from the sizes map (xs, sm, md, lg) for the first two arguments, or you can use custom values (like 30em). The third argument accepts either landscape or portrait. You could even customize the mixin the make l = landscape and p = portrait if you wanted.
Additionally, if you only wanted, for example, an orientation, you could pass the arguments (null, null, landscape).
For clarity, here's some examples:
#include break(null, md, landscape) {
...
}
#include break(null, null, landscape) {
...
}
#include break(md) {
...
}
#include break(null, md) {
...
}
#include break(480px) {
...
}
Output:
#media only screen and (max-width: 992px) and (orientation: landscape) {
...
}
#media only screen and (orientation: landscape) {
...
}
#media only screen and (min-width: 992px) {
...
}
#media only screen and (max-width: 992px) {
...
}
#media only screen and (min-width: 480px) {
...
}
I have a scss function
#function darken($color,$percent) {
...
#return $calculated-color;
}
This function overrides the darken() function, but adds some extra features.
Now, I wonder if it is possible to somehow store all calls to this function in some map and then after all function calls has been made run that map trough a mixin such as:
$calc-colors:() !global;
$calc-colors:map-merge(('thisvaluewillbeexported':1),$calc-colors);
#function test($color,$percent) {
$col: darken($color,$percent);
// $calc-colors: append($calc-colors,'--'$color); --not working
// $calc-colors:map-merge(('--'+$color:$col),$calc-colors); --not working
#return $col;
}
.test {
color:test(pink,24%);
}
.test2 {
color:test(red,24%);
}
:export{
#each $bp, $value in $calc-colors {
#{$bp}: #{$value};
}
}
//gives only thisvaluewillbeexported:1
My goal would to somehow get all calls to my test function recorded into the :export{} attribute in order to be able to fetch the values from javascript.
// My preferred output would be:
{
'thisvaluewillbeexported':1,
'--pink':'#ff4666',
'--red':'#850000'
}
You should set variable !global inside function.
Sassmeister demo.
Article about variable scope in Sass.
#function set-color($color-name, $darken-ration) {
$darken-color: darken($color-name, $darken-ration);
$calc-colors: map-merge($calc-colors, ('--' + $color-name: $darken-color)) !global;
#return $darken-color;
}
$calc-colors: map-merge(('thisvaluewillbeexported': 1), ());
a {
color: set-color(green, 10%);
}
b {
color: set-color(red, 10%);
}
c {
#each $name, $value in $calc-colors {
#{$name}: #{$value};
}
}
Css output:
a {
color: #004d00;
}
b {
color: #cc0000;
}
c {
thisvaluewillbeexported: 1;
--green: #004d00;
--red: #cc0000;
}
I'd like to handle some errors in my scss code.
Imagine this code.
$color: 12;
a {
#if (type-of($color) != color) {
// trow an error
}
}
Now I use a mixin, that takes some params and calls #error or #warn.
#mixin log($type, $message) {
#if ($type == error) {
#error $message;
} #else {
//
}
}
But I don't want to call it every time via #include: #include log(error, "message");.
I'd wont something like this:
a {
#if (type-of($color) != color) {
log(error, "message");
}
}
So, is there a way to write a function (or not a function) to call it inside a selector?
Ideally mixins should be used to create property and value pairs. Functions are expected to return values so can be used any where you Sass / CSS expects a value
#function log($type, $message) {
#if ($type == error) {
#return $message;
} #else {
//return something else
}
}
a {
#if (type-of($color) != color) {
#error log(error, 'message');
// $type == error so log(error, 'message') returns 'message'
// so entire line is interpreted as #error 'message'
}
}
In this example the only difference was using #error for the function instead of #include for the mixin.
However imagine if you needed to change a colour based on a certain value like a width and log some errors at the same time.
#function get-colour($width) {
$color: green;
#if ($width < 10) {
#warn 'This size is too small';
$color: red;
}
#return $color;
}
div {
background-color: get-colour(12); //returns green colour for div
}
p {
background-color: get-colour(5); //logs warning and returns red colour for p
}
While a mixin would return property: value, a function returns ONLY a value which can be used on different properties.
I googled a bit. There is no way to handle errors without #mixins. #include log(error, "message"); is the only solution.