//// /// @group BEM /// /// @access public //// /// /// Generate a new BEM element. /// /// The element will be generated according to the BEM naming convention. /// If the parent selector doesn't match the block selector, the element will be /// nested inside the parent selector. This means, you may nest elements inside /// other elements, modifiers or any kind of selector such as &:hover. /// /// @param {string} $name - First element name /// @param {string} $names - More element names /// /// @content /// /// @throw If the element is not preceded by a block, element, modifier or suffix. /// /// @example scss - Element for a block /// @include iro-bem-component('block') { /// /* some block definitions */ /// /// @include iro-bem-element('elem') { /// /* some element definitions */ /// } /// } /// /// // Generates: /// /// .c-block { /// /* some block definitions */ /// } /// /// .c-block__elem { /// /* some element definitions */ /// } /// /// @example scss - Element that is affected by the user hovering the block /// @include iro-bem-component('block') { /// /* some block definitions */ /// /// @include iro-bem-element('elem') { /// background-color: #eee; /// } /// /// &:hover { /// @include iro-bem-element('elem') { /// background-color: #000; /// } /// } /// } /// /// // Generates: /// /// .c-block { /// /* some block definitions */ /// } /// /// .c-block__elem { /// background-color: #eee; /// } /// /// .c-block:hover .c-block__elem { /// background-color: #000; /// } /// /// @example scss - Multiple elements /// @include iro-bem-component('block') { /// /* some block definitions */ /// /// @include iro-bem-element('elem1', 'elem2') { /// /* some element definitions */ /// } /// } /// /// // Generates: /// /// .c-block { /// /* some block definitions */ /// } /// /// .c-block__elem1, .c-block__elem2 { /// /* some element definitions */ /// } /// @mixin iro-bem-element($name, $names...) { $result: iro-bem-element($name, $names...); $selector: nth($result, 1); $context: nth($result, 2); @include iro-bem-validate( 'element', (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 element. 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-element /// @function iro-bem-element($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'); $selector: (); $parts-data: (); @if nth($parent-context, 1) == 'element' { @if $iro-bem-element-nesting-policy == 'disallow' { @error 'Element nesting is forbidden.'; } @if $iro-bem-element-nesting-policy == 'append' { $element-selector: map-get(nth($parent-context, 2), 'selector'); @if not iro-selector-suffix-match(&, $element-selector) { @error 'A nested element must be an immediate children of the parent element.'; } // // Possible outcomes: // - {e}__element // - [manual selector] {e}__element // @each $name in join($name, $names) { $sel: selector-append(&, $iro-bem-element-separator + $name); $selector: join($selector, $sel, comma); $parts-data: append($parts-data, ( 'name': $name, 'selector': $sel )); } } $parent-context: iro-context-get($iro-bem-context-id, 'block'); } @if length($selector) == 0 { $parent-selector: map-get(nth($parent-context, 2), 'selector'); @if iro-selector-suffix-match(&, $parent-selector) { // // Possible outcomes: // - {b}__element // - [manual selector] {b}__element // @each $name in join($name, $names) { $sel: selector-append(&, $iro-bem-element-separator + $name); $selector: join($selector, $sel, comma); $parts-data: append($parts-data, ( 'name': $name, 'selector': $sel )); } } @else { // // Possible outcomes: // - {b} [manual selector] {b}__element // - {e,m,s} ([manual selector]) {b}__element // @if nth($parent-context, 1) != 'block' { $parent-context: iro-context-get($iro-bem-context-id, 'block'); } $block-base-selector: map-get(nth($parent-context, 2), 'base-selector'); @each $name in join($name, $names) { $sel: selector-nest(&, selector-append($block-base-selector, $iro-bem-element-separator + $name)); $selector: join($selector, $sel, comma); $parts-data: append($parts-data, ( 'name': $name, 'selector': $sel )); } } } $context: 'element', ( 'parts': $parts-data, 'selector': $selector ); @return $selector $context; } /// /// Generate a BEM element that is related to the current element. /// /// The generated element selector is appended to the current element selector. The $sign /// determines the relationship. /// /// @param {string} $sign - Relationshop sign, either '+' or '~' /// @param {string} $name - First element name /// @param {string} $names - More element names /// /// @content /// /// @throw If the element is not preceded by an element. /// /// @example scss - A sibling element to a single element /// @include iro-bem-component('block') { /// @include iro-bem-element('elem') { /// /* some element definitions */ /// /// @include iro-bem-related-element('~', 'sibling') { /// /* some sibling element definitions */ /// } /// } /// } /// /// // Generates: /// /// .c-block__elem { /// /* some element definitions */ /// } /// /// .c-block__elem ~ .c-block__sibling { /// /* some sibling element definitions */ /// } /// /// @example scss - A successor element to a single element /// @include iro-bem-component('block') { /// @include iro-bem-element('elem') { /// /* some element definitions */ /// /// @include iro-bem-related-element('+', 'successor') { /// /* some successor element definitions */ /// } /// } /// } /// /// // Generates: /// /// .c-block__elem { /// /* some element definitions */ /// } /// /// .c-block__elem + .c-block__successor { /// /* some successor element definitions */ /// } /// /// @example scss - A successor element to multiple elements /// @include iro-bem-component('block') { /// @include iro-bem-element('elem1', 'elem2') { /// /* some element definitions */ /// /// @include iro-bem-related-element('+', 'successor') { /// /* some successor element definitions */ /// } /// } /// } /// /// // Generates: /// /// .c-block__elem1, .c-block__elem2 { /// /* some element definitions */ /// } /// /// .c-block__elem1 + .c-block__successor, .c-block__elem2 + .c-block__successor { /// /* some successor element definitions */ /// } /// @mixin iro-bem-related-element($sign, $name, $names...) { $result: iro-bem-related-element($sign, $name, $names...); $selector: nth($result, 1); $context: nth($result, 2); @include iro-bem-validate( 'related-element', (sign: $sign, 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 element that is related to the current element. /// 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-related-element /// @function iro-bem-related-element($sign, $name, $names...) { // // Generating this selector is simple: Take the latest block context, use it // to generate the element part, and insert it at the end of the current selector. // Possible outcomes: // - {e} ({m,s}) ([manual selector]) + {e} // - {e} ({m,s}) ([manual selector]) ~ {e} // $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, 'element'); @if $sign != '+' and $sign != '~' { @error 'Invalid relationship sign #{inspect($sign)}.'; } $block-context: iro-context-get($iro-bem-context-id, 'block'); $block-base-selector: map-get(nth($block-context, 2), 'base-selector'); $selector: (); $parts-data: (); @each $name in join($name, $names) { $sel: selector-nest(&, $sign, selector-append($block-base-selector, $iro-bem-element-separator + $name)); $selector: join($selector, $sel, comma); $parts-data: append($parts-data, ( 'name': $name, 'selector': $sel )); } $context: 'element', ( 'parts': $parts-data, 'selector': $selector ); @return $selector $context; } /// /// Generate a BEM element that is a sibling of the current element. /// /// It's a shorthand for iro-bem-related-element('~', $name). /// /// @param {string} $name - First element name /// @param {list} $names - List of more element names /// /// @content /// @mixin iro-bem-sibling-element($name, $names...) { @include iro-bem-related-element('~', $name, $names...) { @content; } } /// /// Generate a new BEM element that is a sibling of the current element. /// 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-sibling-element /// @function iro-bem-sibling-element($name, $names...) { @return iro-bem-related-element('~', $name, $names...); } /// /// Generate a BEM element that is the successor of the current element. /// /// It's a shorthand for iro-bem-related-element('+', $name). /// /// @param {string} $name - First element name /// @param {string} $names - More element names /// /// @content /// @mixin iro-bem-next-element($name, $names...) { @include iro-bem-related-element('+', $name, $names...) { @content; } } /// /// Generate a new BEM element that is the successor of the current element. /// 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-next-element /// @function iro-bem-next-element($name, $names...) { @return iro-bem-related-element('+', $name, $names...); } /// /// Generate the current BEM element as a successor of itself. /// /// If this is applied to a single element, it behaves exactly the same as /// iro-bem-related-element('+', name); /// However, if it is applied to multiple elements, each twin element only will influence /// their other twin, which is not replicable with iro-bem-related-element. /// /// @content /// /// @example scss - Two twin elements /// @include iro-bem-component('block') { /// @include iro-bem-element('elem') { /// /* some element definitions */ /// /// @include iro-bem-next-twin-element { /// /* some twin element definitions */ /// } /// } /// } /// /// // Generates: /// /// .c-block__elem { /// /* some element definitions */ /// } /// /// .c-block__elem + .c-block__elem { /// /* some twin element definitions */ /// } /// /// @example scss - Multiple twin elements /// @include iro-bem-component('block') { /// @include iro-bem-element('elem1', 'elem2') { /// /* some element definitions */ /// /// @include iro-bem-next-twin-element { /// /* some twin element definitions */ /// } /// } /// } /// /// // Generates: /// /// .c-block__elem1, .c-block__elem2 { /// /* some element definitions */ /// } /// /// .c-block__elem1 + .c-block__elem1, .c-block__elem2 + .c-block__elem2 { /// /* some twin element definitions */ /// } /// @mixin iro-bem-related-twin-element($sign) { $result: iro-bem-related-twin-element($sign); $selector: nth($result, 1); $context: nth($result, 2); @include iro-bem-validate( 'next-twin-element', (), $selector, $context ); @include iro-context-push($iro-bem-context-id, $context...); @at-root #{$selector} { @content; } @include iro-context-pop($iro-bem-context-id); } /// /// Generate the current BEM element as a successor of itself. /// 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-next-twin-element /// @function iro-bem-related-twin-element($sign) { $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, 'element'); $element-context: iro-context-get($iro-bem-context-id, 'element'); $element-selector: map-get(nth($element-context, 2), 'selector'); $block-context: iro-context-get($iro-bem-context-id, 'block'); $block-base-selector: map-get(nth($block-context, 2), 'base-selector'); $selector: (); $parts-data: (); // // To determine the twin for each element, iterate the sub-selectors from the current selector // and check if it contains the currently inspected element. This has to be done with string // comparison since none of Sass selector functions is of use here. // Finally, the current twin will be appended to the extracted sub-selector as a successor // element. // @each $part-data in map-get(nth($element-context, 2), 'parts') { $part-selector: map-get($part-data, 'selector'); $part-name: map-get($part-data, 'name'); $sel: (); @if iro-selector-suffix-match(&, $element-selector) { // // This mixin is included in the selector the last element mixin created. // Possible outcomes: // - {e} + {e} // - [manual selector] {e} + {e} // @each $s in & { @each $ps in $part-selector { @if nth($s, -1) == nth($ps, -1) { $sel-ent: selector-nest($s, $sign, selector-append($block-base-selector, $iro-bem-element-separator + $part-name)); $sel: join($sel, $sel-ent, comma); } } } } @else { // // This mixin is NOT included in the selector the last element mixin created. // Possible outcomes: // - {e} {m,s} + {e} // - {e} [manual selector] + {e} // - {e} {m,s} [manual selector] + {e} // @each $s in & { @each $ps in $part-selector { @if str-index(inspect($s), inspect($ps)) { $char-index: str-length(inspect($ps)) + 1; $match: index(' ' ':' ',', str-slice(inspect($s), $char-index, $char-index)) != null; @if not $match { @each $separator in $iro-bem-element-separator $iro-bem-modifier-separator $iro-bem-suffix-separator { @if str-slice(inspect($s), $char-index, $char-index + str-length($separator) - 1) == $separator { $match: true; } } } @if $match { $sel-ent: selector-nest($s, '+', selector-append($block-base-selector, $iro-bem-element-separator + $part-name)); $sel: join($sel, $sel-ent, comma); } } } } } @if length($sel) != length($part-selector) { @error 'Could not generate twin element selector.'; } $selector: join($selector, $sel, comma); $parts-data: append($parts-data, ( 'name': $part-name, 'selector': $sel )); } $context: 'element', ( 'parts': $parts-data, 'selector': $selector ); @return $selector $context; } /// /// Generate the current BEM element as a sibling of itself. /// /// It's a shorthand for iro-bem-related-twin-element('~'). /// /// @content /// @mixin iro-bem-sibling-twin-element { @include iro-bem-related-twin-element('~') { @content; } } /// /// Generate the current BEM element as a sibling of itself. /// 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-sibling-twin-element /// @function iro-bem-sibling-twin-element() { @return iro-bem-related-twin-element('~'); } /// /// Generate the current BEM element as a next sibling of itself. /// /// It's a shorthand for iro-bem-related-twin-element('+', $name). /// /// @content /// @mixin iro-bem-next-twin-element { @include iro-bem-related-twin-element('+') { @content; } } /// /// Generate the current BEM element as a next sibling of itself. /// 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-next-twin-element /// @function iro-bem-next-twin-element() { @return iro-bem-related-twin-element('+'); }