//// /// @group BEM /// /// @access public //// /// /// Generate a new BEM modifier. /// /// If the parent context is block or element, the modifier will modify said block or element according /// to the BEM naming convention. /// /// If the parent context is a modifier or suffix, then the modifier will depend on said modifier or suffix. /// Depending on $extend, the meaning of this dependency (and the resulting selector) varies: /// If it's false (default), you signalize that the modifier also exists by itself, but it changes its /// behavior when the parent modifier or suffix is set. /// If it's true, you signalize that the modifier extends the parent modifier or suffix and can only be /// used in conjunction with it. /// /// @param {string | list} $name - First element name or list with two items: 1. first element name, 2. bool indicating if the modifier is extending /// @param {string | list} $names - More element names or lists with two items: 1. element name, 2. bool indicating if the modifier is extending /// /// @content /// /// @throw If the element is not preceded by a block, element, modifier or suffix. /// /// @example scss - Modifier that modifies a block or element /// @include iro-bem-component('block') { /// @include iro-bem-modifier('mod') { /// background-color: #eee; /// } /// /// @include iro-bem-element('elem') { /// @include iro-bem-modifier('mod') { /// background-color: #222; /// } /// } /// } /// /// // Generates: /// /// .c-block--mod { /// background-color: #eee; /// } /// /// .c-block__elem--mod { /// background-color: #222; /// } /// /// @example scss - Modifier nested in another modifier, not extending /// @include iro-bem-component('block') { /// @include iro-bem-modifier('mod') { /// background-color: #eee; /// } /// /// @include iro-bem-modifier('dark') { /// /* some definitions */ /// /// @include iro-bem-modifier('mod') { /// background-color: #222; /// } /// } /// } /// /// // Generates: /// /// .c-block--mod { /// background-color: #eee; /// } /// /// .c-block--dark { /// /* some definitions */ /// } /// /// .c-block--dark.c-block--mod { /// background-color: #222; /// } /// /// @example scss - Modifier nested in another modifier, extending /// @include iro-bem-component('block') { /// @include iro-bem-modifier('mod') { /// background-color: #eee; /// } /// /// @include iro-bem-modifier('dark') { /// /* some definitions */ /// /// @include iro-bem-modifier('mod' true) { /// background-color: #222; /// } /// } /// } /// /// // Generates: /// /// .c-block--mod { /// background-color: #eee; /// } /// /// .c-block--dark { /// /* some definitions */ /// } /// /// .c-block--dark--mod { /// background-color: #222; /// } /// @mixin iro-bem-modifier($name, $names...) { $result: iro-bem-modifier($name, $names...); $selector: nth($result, 1); $context: nth($result, 2); @include iro-bem-validate( 'modifier', (name: $name, names: $names), $selector, $context ); @include iro-context-push($iro-bem-context-id, $context...); @at-root #{$selector} { @content; } @include iro-context-pop($iro-bem-context-id); } /// /// Generate a new BEM modifier. Check the respective mixin documentation for more information. /// /// @return {list} A list with two items: 1. selector, 2. context or `null` /// /// @see {mixin} iro-bem-modifier /// @function iro-bem-modifier($name, $names...) { $noop: iro-context-assert-stack-count($iro-bem-context-id, $iro-bem-max-depth); $noop: iro-context-assert-stack-must-contain($iro-bem-context-id, 'block'); $parent-context: iro-context-get($iro-bem-context-id, 'block' 'element' 'modifier' 'suffix' 'state'); $parent-selector: map-get(nth($parent-context, 2), 'selector'); $selector: (); $parts-data: (); @if not iro-selector-suffix-match(&, $parent-selector) { // // The current selector doesn't match the parent selector. // The user manually added a selector between parent context and this modifier call. // This case is forbidden because any outcome semantically wouldn't make sense: // - {b,e,m,s} [manual selector] {b,e,m,s}--modifier // - {b,e,m,s}--modifier [manual selector] // The first case would make the modifier behave like an element. // The second case is unintuitive, the code would be more clear by nesting the manual // selector in the modifier instead. // @error 'A modifier must be an immediate child of the parent context'; } @each $name in iro-list-prepend($names, $name) { $extend: false; @if type-of($name) == list { $extend: nth($name, 2); $name: nth($name, 1); } @if index('block' 'element', nth($parent-context, 1)) or $extend == true { // // Either the parent context is block or element, or a modifier or suffix // is to be extended. The modifier part can simply be appended. // Possible outcomes: // - {b,e,m,s}--modifier // $sel: selector-append(&, $iro-bem-modifier-separator + $name); $selector: join($selector, $sel, comma); $parts-data: append($parts-data, ( 'name': $name, 'selector': $sel )); } @else { // // Parent context is modifier, suffix or state and $extend is false. // $be-context: iro-context-get($iro-bem-context-id, 'block' 'element'); @if nth($be-context, 1) == 'element' { // // Latest context is element. Since element contexts can consist of multiple single // elements, inspect all elements and append its selector with the suffix "--$name". // This has to be done with string comparison since none of Sass selector functions // is of use here. // Possible outcomes: // - {m,s}.{e}--modifier // $nsel: (); @each $elem-part-data in map-get(nth($be-context, 2), 'parts') { $elem-part-selector: map-get($elem-part-data, 'selector'); $sel: (); @each $s in & { @each $ps in $elem-part-selector { @if str-index(inspect($s), inspect($ps) + $iro-bem-modifier-separator) or str-index(inspect($s), inspect($ps) + $iro-bem-suffix-separator) { $sel: join($sel, selector-unify($s, selector-append($ps, $iro-bem-modifier-separator + $name)), comma); } } } @if length($sel) == 0 { @error 'Could not generate modifier selector.'; } $nsel: join($nsel, $sel, comma); } $selector: join($selector, $nsel, comma); $parts-data: append($parts-data, ( 'name': $name, 'selector': $nsel )); } @else { // // Latest context is block. Just append the modifier part. // Possible outcomes: // - {m,s}.{b}--modifier // $block-base-selector: map-get(nth($be-context, 2), 'base-selector'); $sel: selector-append(&, $block-base-selector, $iro-bem-modifier-separator + $name); $selector: join($selector, $sel, comma); $parts-data: append($parts-data, ( 'name': $name, 'selector': $sel )); } } } $context: 'modifier', ( 'parts': $parts-data, 'selector': $selector ); @return $selector $context; }