From d07f664450ddaaebb44127a4bd057763d13d3f82 Mon Sep 17 00:00:00 2001 From: Feuerfuchs Date: Sun, 1 Nov 2020 20:55:14 +0100 Subject: Init --- src/bem/_element.scss | 622 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 622 insertions(+) create mode 100644 src/bem/_element.scss (limited to 'src/bem/_element.scss') diff --git a/src/bem/_element.scss b/src/bem/_element.scss new file mode 100644 index 0000000..b3d2fee --- /dev/null +++ b/src/bem/_element.scss @@ -0,0 +1,622 @@ +//// +/// @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('+'); +} -- cgit v1.3.1