Created
March 11, 2015 10:56
-
-
Save apfelbox/26605e0d3d2adafd55d9 to your computer and use it in GitHub Desktop.
Revisions
-
apfelbox renamed this gist
Mar 11, 2015 . 1 changed file with 0 additions and 0 deletions.There are no files selected for viewing
File renamed without changes. -
apfelbox created this gist
Mar 11, 2015 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,219 @@ The double ampersand -- or as A list apart called it ["lobotomized owl selector"](http://alistapart.com/article/axiomatic-css-and-lobotomized-owls) -- is a CSS rule that looks like the following: ```css * + * { /* some declarations */ } ``` You can also use it with a specific selector: ```css p + p { margin-top: 1.5rem; } ``` This article now looks at implementing this functionality in Sass using a mixin. # The simple implementation ```scss @mixin double-ampersand { & + & { @content; } } ``` Usage is like this: ```scss p { @include double-ampersand { margin-top: 1.5rem; } } ``` ## The problem The issue with this mixin will be apparent as soon as you use nested selectors. In a nested selector the CSS should look like this: ```css .content p + p { margin-top: 1.5rem; } ``` If you now go to your Sass file and write: ```scss .content p { @include double-ampersand { margin-top: 1.5rem; } } ``` it will not produce the desired output. Instead it will produce ```css .content p + .content p { margin-top: 1.5rem; } ``` # Improved mixin The improved mixin will only duplicate the last selector. So let's first look at the desired output and afterwards discuss the implementation It should support simple selectors, ... ```scss p { @include better-double-ampersand { margin-top: 1.5rem; } } // ... should produce ... p + p { margin-top: 1.5rem; } ``` ... multiple selectors, ... ```scss a, p { @include better-double-ampersand { margin-top: 1.5rem; } } // ... should produce ... a + a, p + p { margin-top: 1.5rem; } ``` ... nested selectors, ... ```scss .content p { @include better-double-ampersand { margin-top: 1.5rem; } } // ... should produce ... .content p + p { margin-top: 1.5rem; } ``` ... multiple nested selectors, ... ```scss .test p, .content p { @include better-double-ampersand { margin-top: 1.5rem; } } // ... should produce ... .test p + p, .content p + p { margin-top: 1.5rem; } ``` ... and multiple nested selectors with different last selectors. ```scss .test p, .content a { @include better-double-ampersand { margin-top: 1.5rem; } } // ... should produce ... .test p + p, .content a + a { margin-top: 1.5rem; } ``` So, here goes. This is the commented code that solves all requests (it is a bit verbose, sorry about that). It tries to use the existing `&` behaviour and only breaks out of it, if it would produce invalid selectors. ```scss // This function will return the last item in a list @function last ($list) { @return nth($list, length($list)); } // This function implements the improved double ampersand algoithm @mixin better-double-ampersand { // at first we need to reference the list of selectors for which // this mixin is called (we will call that "caller selectors" from now on) // // For // p .test, a { @include better-double-ampersand { /* ... */ }; } // this will be // (p .test, a) $caller-selectors: &; // We need to track whether the last selector for all caller selectors // is the same. If it isn't we need to perform some special handling $has-same-last-caller-selector: true; // For checking whether all last selectors are the same. Store the first one // and compare all other last selectors to it. $previous-last-selector: last(nth($caller-selectors, 1)); // A list of prepared separators. If the last selectors are not the same, // we need to create our own block with the prepared selectors. $prepared-selectors: (); // Loop through all caller selectors to // - check for the last // - generate prepared selectors @each $selector in $caller-selectors { $last: last($selector); @if ($previous-last-selector != $last) { $has-same-last-caller-selector: false; } // generate prepared selector $prepared-selectors: append($prepared-selectors, #{$selector} + #{$last}, comma); } @if ($has-same-last-caller-selector) { // if all selectors have the same last selector // we can just use the regular `&` functionality & + #{$previous-last-selector} { @content; } } @else { // If not all selectors are the same, we need to render a completely // own block @at-root #{$prepared-selectors} { @content; } } } ``` Please note that this code currently doesn't work in libsass. :( You can inline the `last()` function, if you want to keep it all inside of one mixin.