While Karol's answer is near perfect, it doesn't take into account pseudo-elements or pseudo-selectors. Furthermore, code is duplicated if using more than one complex selector. I came up with a simplified version:
@mixin parent {
$parents: ();
$parent: '';
@each $selector in & {
$length: length($selector);
$index: 0;
$last-selector: nth($selector, $length);
@if ($length == 1) {
@error "Used parent mixin on a top-level selector";
} @else {
$index: str-index($last-selector, '::');
@if ($index) {
$last-selector: str-slice($last-selector, 1, $index - 1);
} @else {
$last-selector: null;
}
// Inspect allows us to combine two selectors in one block.
$parent: inspect(set-nth($selector, $length, #{$last-selector}));
$parents: join($parents, $parent, comma);
}
}
@at-root #{$parents} {
@content;
}
}
There's a first loop to iterate over the selector list (selectors with commas at the end). Because complex selectors are also treated as a list, we just need to remove the last element of the list. There's no loop to iterate over the compound or simple selectors since we only need to discard the last one.
There's no function in Sass to remove an element of a list, but we can set the value of an element with set-nth
. By making the last element as an empty string and unquoting it, we can remove the last element from the printed representation (string) of the list. Since selectors can be strings, we simply use the new string as a selector.
When using the following:
.grandmother,
.grandfather {
.parent {
.child {
font-size: 10em;
@include parent {
font-size: 5em;
}
&::after {
font-size: 1px;
@include parent {
font-weight: bold;
}
}
}
}
}
We get the following:
.grandmother .parent .child,
.grandfather .parent .child {
font-size: 10em;
}
.grandmother .parent,
.grandfather .parent {
font-size: 5em;
}
.grandmother .parent .child::after,
.grandfather .parent .child::after {
font-size: 1px;
}
.grandmother .parent .child,
.grandfather .parent .child {
font-weight: bold;
}
Note: pseudo-elements and pseudo-selectors are not children of an element but are attached to it and have therefore no parents in themselves. I assumed parents would mean the parent in the sense of Sass nesting.