SASS loop to generate chained :not() from variables string - css

I have a long list of classes I wish to use in a couple of ways.
The list looks something like this (but much longer):
$my-components: '.some-component', '.some-other-component', '.another-component';
One of the ways I need to use this list of class names in SASS (scss), which I can't figure out, is to create a long chained selector of :not()s. The final rendered output should look like this:
.parent {
> * {
&:last-of-type:not(.some-component):not(.some-other-component):not(.another-component):not(etc) {
// style rules
}
}
}
(The goal being to select the last child element of .parent that doesn't have one of the classes in the list).
Question: How can I make the above code DRY by using the $my-components variable?
Note 1: The loop's output needs to be able to be appended to that &:last-of-type, as in above example.
Note 2: I'm using the $my-components variable already in a different function, so I'd like to keep it in the same format if possible.
Note 3: I know this seems hacky and stupid, and that I should just give all of those elements a common shared class instead. But unfortunately I can not currently modify that part of the DOM.

Use a #each loop
scss:
$my-components: '.some-component', '.some-other-component', '.another-component';
.parent {
> * {
$selector: '';
#each $component in $my-components {
$selector: $selector + ":not(#{$component})"
}
&:last-of-type#{$selector} {
color: blue;
}
}
}
css:
.parent > *:last-of-type:not(.some-component):not(.some-other-component):not(.another-component) {
color: blue;
}
What's happening ?
I define a new string variable $selector.
During the #each loop, I'm concatening the string with :not(#{$component}) to add your new selector.

Related

How I exclude more than one element with CSS selector group? [duplicate]

I'm trying to select input elements of all types except radio and checkbox.
Many people have shown that you can put multiple arguments in :not, but using type doesn't seem to work anyway I try it.
form input:not([type="radio"], [type="checkbox"]) {
/* css here */
}
Any ideas?
Why :not just use two :not:
input:not([type="radio"]):not([type="checkbox"])
Yes, it is intentional
If you're using SASS in your project, I've built this mixin to make it work the way we all want it to:
#mixin not($ignorList...) {
//if only a single value given
#if (length($ignorList) == 1){
//it is probably a list variable so set ignore list to the variable
$ignorList: nth($ignorList,1);
}
//set up an empty $notOutput variable
$notOutput: '';
//for each item in the list
#each $not in $ignorList {
//generate a :not([ignored_item]) segment for each item in the ignore list and put them back to back
$notOutput: $notOutput + ':not(#{$not})';
}
//output the full :not() rule including all ignored items
&#{$notOutput} {
#content;
}
}
it can be used in 2 ways:
Option 1: list the ignored items inline
input {
/*non-ignored styling goes here*/
#include not('[type="radio"]','[type="checkbox"]'){
/*ignored styling goes here*/
}
}
Option 2: list the ignored items in a variable first
$ignoredItems:
'[type="radio"]',
'[type="checkbox"]'
;
input {
/*non-ignored styling goes here*/
#include not($ignoredItems){
/*ignored styling goes here*/
}
}
Outputted CSS for either option
input {
/*non-ignored styling goes here*/
}
input:not([type="radio"]):not([type="checkbox"]) {
/*ignored styling goes here*/
}
Starting from CSS Selectors 4 using multiple arguments in the :not selector becomes possible (see here).
In CSS3, the :not selector only allows 1 selector as an argument. In level 4 selectors, it can take a selector list as an argument.
Example:
/* In this example, all p elements will be red, except for
the first child and the ones with the class special. */
p:not(:first-child, .special) {
color: red;
}
Unfortunately, browser support is somewhat new.
I was having some trouble with this, and the "X:not():not()" method wasn't working for me.
I ended up resorting to this strategy:
INPUT {
/* styles */
}
INPUT[type="radio"], INPUT[type="checkbox"] {
/* styles that reset previous styles */
}
It's not nearly as fun, but it worked for me when :not() was being pugnacious. It's not ideal, but it's solid.
If you install the "cssnext" Post CSS plugin, then you can safely start using the syntax that you want to use right now.
Using cssnext will turn this:
input:not([type="radio"], [type="checkbox"]) {
/* css here */
}
Into this:
input:not([type="radio"]):not([type="checkbox"]) {
/* css here */
}
https://cssnext.github.io/features/#not-pseudo-class

CSS - How to select multiple attribute values?

If I have multiple div tags with an attribute containing different numbered values, and I would like to select only number 1 through 10, what is the most efficient way to do this in css?
Is there anything like e.g. .div[line-number=1-10] ?
This is not possible in the standard CSS. It is convenient to use a CSS preprocessor like SASS or LESS which allow you creating loops among many other features. An example with SASS:
$selector: '.div';
#for $i from 1 to 10 {
$selector: $selector + '[line-number=' + $i + ']';
}
#{$selector} {
// style
}
In pure CSS you are doomed to use this solution instead:
.div[line-number=1], .div[line-number=2], .div[line-number=3], .div[line-number=4], .div[line-number=5], .div[line-number=6], .div[line-number=7], .div[line-number=8], .div[line-number=9], .div[line-number=10] {
}
if you have the ability to modify the line-number attribute starting with 0 (01,02,03,04...10) you can do this:
div[line-number^="0"], div[line-number="10"] {
// css properties
}
if not see the answer from #jackBauer
You cannot specify line-number in range (1-10).
This is not available in attribute selector - https://www.w3.org/TR/css3-selectors/#attribute-selectors.
But alternatively you can apply css on each attribute value with something like below
div[line-number="1"] {
color: red;
}
div[line-number="10"] {
color: green;
}
<p>Use attribute selectors</p>
<div line-number="1">one</div>
<div line-number="2">two</div>
<div line-number="10">ten</div>
Hope this will help in some way(y).

Parent Selector nested inside &:hover

I am using the Less parent selector to shorten my selectors:
.module-name {
// styles go here
&__sub-module-1 {
//styles go here
}
}
Now my problem is how to continue to use the parent selector for adding the module-name inside a nested :hover statement. E.g. on hover of sub-module-1 I want to change something in sub-module-2. Inside the &:hover statement, what does the parent selector refer to?
.module-name {
&__sub-module1 {
&:hover {
&__sub-module2 {
// on hover of .module-name__sub-module1 change something in .module-name__sub-module2
}
}
}
}
If i write it like this it works, but it defeats the purpose of using the parent selector to automatically fill in the name of the module:
.module-name {
&__sub-module1 {
&:hover {
.module-name__sub-module2 {
// on hover of .module-name__sub-module1 change something in .module-name__sub-module2
}
}
}
}
I hope I could adequately express my problem; any help is appreciated.
The parent selector (&) will always refer to the full parent based on the level at which you are. For example at the first level of nesting, & refers to .module-name. In the second level, it refers to .module-name__sub-module1 and in the third level, it refers to .module-name__sub-module1:hover.
Extract from Less Website: Note that & represents all parent selectors (not just the nearest ancestor or the overall root ancestor)
The emphasised part in the above statement is my inclusion based on the context
For this particular case, you could assign the module-name to a variable and use selector interpolation like below to form the selectors.
The variable value would never change unlike the parent selector (&) irrespective of how many levels of nesting you have and at which level of nesting you are using it.
#module-name: mod-name;
.#{module-name} {
&__sub-module1 {
&:hover {
& .#{module-name}__sub-module2 {
// on hover of .module-name__sub-module1 change something in .module-name__sub-module2
color: blue;
}
}
}
}

SCSS setting a value based upon another

I'm just trying to work out if something is possible or not with SCSS.
Please feel free to ask me for more details if I'm not very clear in what I'm asking, but here's what I'm trying to achieve.
Pseudo code:
.class1 { width:100px; }
.class2 { margin-right:[.class1{width}] + 2 }
compiling into
.class1 {width:100px; }
.class2 { margin-right:102px; }
I believe to get what you want, a variable is best used:
$yourWidth: 100px;
.class1 { width: $yourWidth; }
.class2 { margin-right: ($yourWidth + 2); }
Update (based on comment info)
You might add a global variable below $ColCount that begins as an empty list, like so:
$WidthList: ();
Then inside #mixin columns($numCols) after $colWidth is calculated, add the width value for that column to the $WidthList by adding this function:
join($WidthList, $colWidth);
Then, once all the columns have calculated, you should have a list containing all the width values, so that you can access them if you desire elsewhere, and thus...
.class2 { margin-right: (nth($WidthList, 5) + 2); }
...should yield the .cl-col5 value you want for the margin.
Note that I did not test this. Nor have I actually ever used SASS. I am basing this strictly off the documentation found here and elsewhere on their site.

Can the :not() pseudo-class have multiple arguments?

I'm trying to select input elements of all types except radio and checkbox.
Many people have shown that you can put multiple arguments in :not, but using type doesn't seem to work anyway I try it.
form input:not([type="radio"], [type="checkbox"]) {
/* css here */
}
Any ideas?
Why :not just use two :not:
input:not([type="radio"]):not([type="checkbox"])
Yes, it is intentional
If you're using SASS in your project, I've built this mixin to make it work the way we all want it to:
#mixin not($ignorList...) {
//if only a single value given
#if (length($ignorList) == 1){
//it is probably a list variable so set ignore list to the variable
$ignorList: nth($ignorList,1);
}
//set up an empty $notOutput variable
$notOutput: '';
//for each item in the list
#each $not in $ignorList {
//generate a :not([ignored_item]) segment for each item in the ignore list and put them back to back
$notOutput: $notOutput + ':not(#{$not})';
}
//output the full :not() rule including all ignored items
&#{$notOutput} {
#content;
}
}
it can be used in 2 ways:
Option 1: list the ignored items inline
input {
/*non-ignored styling goes here*/
#include not('[type="radio"]','[type="checkbox"]'){
/*ignored styling goes here*/
}
}
Option 2: list the ignored items in a variable first
$ignoredItems:
'[type="radio"]',
'[type="checkbox"]'
;
input {
/*non-ignored styling goes here*/
#include not($ignoredItems){
/*ignored styling goes here*/
}
}
Outputted CSS for either option
input {
/*non-ignored styling goes here*/
}
input:not([type="radio"]):not([type="checkbox"]) {
/*ignored styling goes here*/
}
Starting from CSS Selectors 4 using multiple arguments in the :not selector becomes possible (see here).
In CSS3, the :not selector only allows 1 selector as an argument. In level 4 selectors, it can take a selector list as an argument.
Example:
/* In this example, all p elements will be red, except for
the first child and the ones with the class special. */
p:not(:first-child, .special) {
color: red;
}
Unfortunately, browser support is somewhat new.
I was having some trouble with this, and the "X:not():not()" method wasn't working for me.
I ended up resorting to this strategy:
INPUT {
/* styles */
}
INPUT[type="radio"], INPUT[type="checkbox"] {
/* styles that reset previous styles */
}
It's not nearly as fun, but it worked for me when :not() was being pugnacious. It's not ideal, but it's solid.
If you install the "cssnext" Post CSS plugin, then you can safely start using the syntax that you want to use right now.
Using cssnext will turn this:
input:not([type="radio"], [type="checkbox"]) {
/* css here */
}
Into this:
input:not([type="radio"]):not([type="checkbox"]) {
/* css here */
}
https://cssnext.github.io/features/#not-pseudo-class

Resources