aboutsummaryrefslogtreecommitdiffstats
path: root/src/bem/_element.scss
diff options
context:
space:
mode:
Diffstat (limited to 'src/bem/_element.scss')
-rw-r--r--src/bem/_element.scss622
1 files changed, 622 insertions, 0 deletions
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 @@
1////
2/// @group BEM
3///
4/// @access public
5////
6
7///
8/// Generate a new BEM element.
9///
10/// The element will be generated according to the BEM naming convention.
11/// If the parent selector doesn't match the block selector, the element will be
12/// nested inside the parent selector. This means, you may nest elements inside
13/// other elements, modifiers or any kind of selector such as &:hover.
14///
15/// @param {string} $name - First element name
16/// @param {string} $names - More element names
17///
18/// @content
19///
20/// @throw If the element is not preceded by a block, element, modifier or suffix.
21///
22/// @example scss - Element for a block
23/// @include iro-bem-component('block') {
24/// /* some block definitions */
25///
26/// @include iro-bem-element('elem') {
27/// /* some element definitions */
28/// }
29/// }
30///
31/// // Generates:
32///
33/// .c-block {
34/// /* some block definitions */
35/// }
36///
37/// .c-block__elem {
38/// /* some element definitions */
39/// }
40///
41/// @example scss - Element that is affected by the user hovering the block
42/// @include iro-bem-component('block') {
43/// /* some block definitions */
44///
45/// @include iro-bem-element('elem') {
46/// background-color: #eee;
47/// }
48///
49/// &:hover {
50/// @include iro-bem-element('elem') {
51/// background-color: #000;
52/// }
53/// }
54/// }
55///
56/// // Generates:
57///
58/// .c-block {
59/// /* some block definitions */
60/// }
61///
62/// .c-block__elem {
63/// background-color: #eee;
64/// }
65///
66/// .c-block:hover .c-block__elem {
67/// background-color: #000;
68/// }
69///
70/// @example scss - Multiple elements
71/// @include iro-bem-component('block') {
72/// /* some block definitions */
73///
74/// @include iro-bem-element('elem1', 'elem2') {
75/// /* some element definitions */
76/// }
77/// }
78///
79/// // Generates:
80///
81/// .c-block {
82/// /* some block definitions */
83/// }
84///
85/// .c-block__elem1, .c-block__elem2 {
86/// /* some element definitions */
87/// }
88///
89@mixin iro-bem-element($name, $names...) {
90 $result: iro-bem-element($name, $names...);
91 $selector: nth($result, 1);
92 $context: nth($result, 2);
93
94 @include iro-bem-validate(
95 'element',
96 (name: $name, names: $names),
97 $selector,
98 $context
99 );
100
101 @include iro-context-push($iro-bem-context-id, $context...);
102 @at-root #{$selector} {
103 @content;
104 }
105 @include iro-context-pop($iro-bem-context-id);
106}
107
108///
109/// Generate a new BEM element. Check the respective mixin documentation for more information.
110///
111/// @return {list} A list with two items: 1. selector, 2. context or `null`
112///
113/// @see {mixin} iro-bem-element
114///
115@function iro-bem-element($name, $names...) {
116 $noop: iro-context-assert-stack-count($iro-bem-context-id, $iro-bem-max-depth);
117 $noop: iro-context-assert-stack-must-contain($iro-bem-context-id, 'block');
118
119 $parent-context: iro-context-get($iro-bem-context-id, 'block' 'element');
120
121 $selector: ();
122 $parts-data: ();
123
124 @if nth($parent-context, 1) == 'element' {
125 @if $iro-bem-element-nesting-policy == 'disallow' {
126 @error 'Element nesting is forbidden.';
127 }
128
129 @if $iro-bem-element-nesting-policy == 'append' {
130 $element-selector: map-get(nth($parent-context, 2), 'selector');
131
132 @if not iro-selector-suffix-match(&, $element-selector) {
133 @error 'A nested element must be an immediate children of the parent element.';
134 }
135
136 //
137 // Possible outcomes:
138 // - {e}__element
139 // - [manual selector] {e}__element
140 //
141
142 @each $name in join($name, $names) {
143 $sel: selector-append(&, $iro-bem-element-separator + $name);
144 $selector: join($selector, $sel, comma);
145 $parts-data: append($parts-data, (
146 'name': $name,
147 'selector': $sel
148 ));
149 }
150 }
151
152 $parent-context: iro-context-get($iro-bem-context-id, 'block');
153 }
154
155 @if length($selector) == 0 {
156 $parent-selector: map-get(nth($parent-context, 2), 'selector');
157
158 @if iro-selector-suffix-match(&, $parent-selector) {
159 //
160 // Possible outcomes:
161 // - {b}__element
162 // - [manual selector] {b}__element
163 //
164
165 @each $name in join($name, $names) {
166 $sel: selector-append(&, $iro-bem-element-separator + $name);
167 $selector: join($selector, $sel, comma);
168 $parts-data: append($parts-data, (
169 'name': $name,
170 'selector': $sel
171 ));
172 }
173 } @else {
174 //
175 // Possible outcomes:
176 // - {b} [manual selector] {b}__element
177 // - {e,m,s} ([manual selector]) {b}__element
178 //
179
180 @if nth($parent-context, 1) != 'block' {
181 $parent-context: iro-context-get($iro-bem-context-id, 'block');
182 }
183
184 $block-base-selector: map-get(nth($parent-context, 2), 'base-selector');
185
186 @each $name in join($name, $names) {
187 $sel: selector-nest(&, selector-append($block-base-selector, $iro-bem-element-separator + $name));
188 $selector: join($selector, $sel, comma);
189 $parts-data: append($parts-data, (
190 'name': $name,
191 'selector': $sel
192 ));
193 }
194 }
195 }
196
197 $context: 'element', (
198 'parts': $parts-data,
199 'selector': $selector
200 );
201
202 @return $selector $context;
203}
204
205///
206/// Generate a BEM element that is related to the current element.
207///
208/// The generated element selector is appended to the current element selector. The $sign
209/// determines the relationship.
210///
211/// @param {string} $sign - Relationshop sign, either '+' or '~'
212/// @param {string} $name - First element name
213/// @param {string} $names - More element names
214///
215/// @content
216///
217/// @throw If the element is not preceded by an element.
218///
219/// @example scss - A sibling element to a single element
220/// @include iro-bem-component('block') {
221/// @include iro-bem-element('elem') {
222/// /* some element definitions */
223///
224/// @include iro-bem-related-element('~', 'sibling') {
225/// /* some sibling element definitions */
226/// }
227/// }
228/// }
229///
230/// // Generates:
231///
232/// .c-block__elem {
233/// /* some element definitions */
234/// }
235///
236/// .c-block__elem ~ .c-block__sibling {
237/// /* some sibling element definitions */
238/// }
239///
240/// @example scss - A successor element to a single element
241/// @include iro-bem-component('block') {
242/// @include iro-bem-element('elem') {
243/// /* some element definitions */
244///
245/// @include iro-bem-related-element('+', 'successor') {
246/// /* some successor element definitions */
247/// }
248/// }
249/// }
250///
251/// // Generates:
252///
253/// .c-block__elem {
254/// /* some element definitions */
255/// }
256///
257/// .c-block__elem + .c-block__successor {
258/// /* some successor element definitions */
259/// }
260///
261/// @example scss - A successor element to multiple elements
262/// @include iro-bem-component('block') {
263/// @include iro-bem-element('elem1', 'elem2') {
264/// /* some element definitions */
265///
266/// @include iro-bem-related-element('+', 'successor') {
267/// /* some successor element definitions */
268/// }
269/// }
270/// }
271///
272/// // Generates:
273///
274/// .c-block__elem1, .c-block__elem2 {
275/// /* some element definitions */
276/// }
277///
278/// .c-block__elem1 + .c-block__successor, .c-block__elem2 + .c-block__successor {
279/// /* some successor element definitions */
280/// }
281///
282@mixin iro-bem-related-element($sign, $name, $names...) {
283 $result: iro-bem-related-element($sign, $name, $names...);
284 $selector: nth($result, 1);
285 $context: nth($result, 2);
286
287 @include iro-bem-validate(
288 'related-element',
289 (sign: $sign, name: $name, names: $names),
290 $selector,
291 $context
292 );
293
294 @include iro-context-push($iro-bem-context-id, $context...);
295 @at-root #{$selector} {
296 @content;
297 }
298 @include iro-context-pop($iro-bem-context-id);
299}
300
301///
302/// Generate a new BEM element that is related to the current element.
303/// Check the respective mixin documentation for more information.
304///
305/// @return {list} A list with two items: 1. selector, 2. context or `null`
306///
307/// @see {mixin} iro-bem-related-element
308///
309@function iro-bem-related-element($sign, $name, $names...) {
310 //
311 // Generating this selector is simple: Take the latest block context, use it
312 // to generate the element part, and insert it at the end of the current selector.
313 // Possible outcomes:
314 // - {e} ({m,s}) ([manual selector]) + {e}
315 // - {e} ({m,s}) ([manual selector]) ~ {e}
316 //
317
318 $noop: iro-context-assert-stack-count($iro-bem-context-id, $iro-bem-max-depth);
319 $noop: iro-context-assert-stack-must-contain($iro-bem-context-id, 'element');
320
321 @if $sign != '+' and $sign != '~' {
322 @error 'Invalid relationship sign #{inspect($sign)}.';
323 }
324
325 $block-context: iro-context-get($iro-bem-context-id, 'block');
326 $block-base-selector: map-get(nth($block-context, 2), 'base-selector');
327
328 $selector: ();
329 $parts-data: ();
330
331 @each $name in join($name, $names) {
332 $sel: selector-nest(&, $sign, selector-append($block-base-selector, $iro-bem-element-separator + $name));
333 $selector: join($selector, $sel, comma);
334 $parts-data: append($parts-data, (
335 'name': $name,
336 'selector': $sel
337 ));
338 }
339
340 $context: 'element', (
341 'parts': $parts-data,
342 'selector': $selector
343 );
344
345 @return $selector $context;
346}
347
348///
349/// Generate a BEM element that is a sibling of the current element.
350///
351/// It's a shorthand for iro-bem-related-element('~', $name).
352///
353/// @param {string} $name - First element name
354/// @param {list} $names - List of more element names
355///
356/// @content
357///
358@mixin iro-bem-sibling-element($name, $names...) {
359 @include iro-bem-related-element('~', $name, $names...) {
360 @content;
361 }
362}
363
364///
365/// Generate a new BEM element that is a sibling of the current element.
366/// Check the respective mixin documentation for more information.
367///
368/// @return {list} A list with two items: 1. selector, 2. context or `null`
369///
370/// @see {mixin} iro-bem-sibling-element
371///
372@function iro-bem-sibling-element($name, $names...) {
373 @return iro-bem-related-element('~', $name, $names...);
374}
375
376///
377/// Generate a BEM element that is the successor of the current element.
378///
379/// It's a shorthand for iro-bem-related-element('+', $name).
380///
381/// @param {string} $name - First element name
382/// @param {string} $names - More element names
383///
384/// @content
385///
386@mixin iro-bem-next-element($name, $names...) {
387 @include iro-bem-related-element('+', $name, $names...) {
388 @content;
389 }
390}
391
392///
393/// Generate a new BEM element that is the successor of the current element.
394/// Check the respective mixin documentation for more information.
395///
396/// @return {list} A list with two items: 1. selector, 2. context or `null`
397///
398/// @see {mixin} iro-bem-next-element
399///
400@function iro-bem-next-element($name, $names...) {
401 @return iro-bem-related-element('+', $name, $names...);
402}
403
404///
405/// Generate the current BEM element as a successor of itself.
406///
407/// If this is applied to a single element, it behaves exactly the same as
408/// iro-bem-related-element('+', name);
409/// However, if it is applied to multiple elements, each twin element only will influence
410/// their other twin, which is not replicable with iro-bem-related-element.
411///
412/// @content
413///
414/// @example scss - Two twin elements
415/// @include iro-bem-component('block') {
416/// @include iro-bem-element('elem') {
417/// /* some element definitions */
418///
419/// @include iro-bem-next-twin-element {
420/// /* some twin element definitions */
421/// }
422/// }
423/// }
424///
425/// // Generates:
426///
427/// .c-block__elem {
428/// /* some element definitions */
429/// }
430///
431/// .c-block__elem + .c-block__elem {
432/// /* some twin element definitions */
433/// }
434///
435/// @example scss - Multiple twin elements
436/// @include iro-bem-component('block') {
437/// @include iro-bem-element('elem1', 'elem2') {
438/// /* some element definitions */
439///
440/// @include iro-bem-next-twin-element {
441/// /* some twin element definitions */
442/// }
443/// }
444/// }
445///
446/// // Generates:
447///
448/// .c-block__elem1, .c-block__elem2 {
449/// /* some element definitions */
450/// }
451///
452/// .c-block__elem1 + .c-block__elem1, .c-block__elem2 + .c-block__elem2 {
453/// /* some twin element definitions */
454/// }
455///
456@mixin iro-bem-related-twin-element($sign) {
457 $result: iro-bem-related-twin-element($sign);
458 $selector: nth($result, 1);
459 $context: nth($result, 2);
460
461 @include iro-bem-validate(
462 'next-twin-element',
463 (),
464 $selector,
465 $context
466 );
467
468 @include iro-context-push($iro-bem-context-id, $context...);
469 @at-root #{$selector} {
470 @content;
471 }
472 @include iro-context-pop($iro-bem-context-id);
473}
474
475///
476/// Generate the current BEM element as a successor of itself.
477/// Check the respective mixin documentation for more information.
478///
479/// @return {list} A list with two items: 1. selector, 2. context or `null`
480///
481/// @see {mixin} iro-bem-next-twin-element
482///
483@function iro-bem-related-twin-element($sign) {
484 $noop: iro-context-assert-stack-count($iro-bem-context-id, $iro-bem-max-depth);
485 $noop: iro-context-assert-stack-must-contain($iro-bem-context-id, 'element');
486
487 $element-context: iro-context-get($iro-bem-context-id, 'element');
488 $element-selector: map-get(nth($element-context, 2), 'selector');
489
490 $block-context: iro-context-get($iro-bem-context-id, 'block');
491 $block-base-selector: map-get(nth($block-context, 2), 'base-selector');
492
493 $selector: ();
494 $parts-data: ();
495
496 //
497 // To determine the twin for each element, iterate the sub-selectors from the current selector
498 // and check if it contains the currently inspected element. This has to be done with string
499 // comparison since none of Sass selector functions is of use here.
500 // Finally, the current twin will be appended to the extracted sub-selector as a successor
501 // element.
502 //
503 @each $part-data in map-get(nth($element-context, 2), 'parts') {
504 $part-selector: map-get($part-data, 'selector');
505 $part-name: map-get($part-data, 'name');
506
507 $sel: ();
508 @if iro-selector-suffix-match(&, $element-selector) {
509 //
510 // This mixin is included in the selector the last element mixin created.
511 // Possible outcomes:
512 // - {e} + {e}
513 // - [manual selector] {e} + {e}
514 //
515
516 @each $s in & {
517 @each $ps in $part-selector {
518 @if nth($s, -1) == nth($ps, -1) {
519 $sel-ent: selector-nest($s, $sign, selector-append($block-base-selector, $iro-bem-element-separator + $part-name));
520 $sel: join($sel, $sel-ent, comma);
521 }
522 }
523 }
524 } @else {
525 //
526 // This mixin is NOT included in the selector the last element mixin created.
527 // Possible outcomes:
528 // - {e} {m,s} + {e}
529 // - {e} [manual selector] + {e}
530 // - {e} {m,s} [manual selector] + {e}
531 //
532
533 @each $s in & {
534 @each $ps in $part-selector {
535 @if str-index(inspect($s), inspect($ps)) {
536 $char-index: str-length(inspect($ps)) + 1;
537 $match: index(' ' ':' ',', str-slice(inspect($s), $char-index, $char-index)) != null;
538
539 @if not $match {
540 @each $separator in $iro-bem-element-separator $iro-bem-modifier-separator $iro-bem-suffix-separator {
541 @if str-slice(inspect($s), $char-index, $char-index + str-length($separator) - 1) == $separator {
542 $match: true;
543 }
544 }
545 }
546
547 @if $match {
548 $sel-ent: selector-nest($s, '+', selector-append($block-base-selector, $iro-bem-element-separator + $part-name));
549 $sel: join($sel, $sel-ent, comma);
550 }
551 }
552 }
553 }
554 }
555 @if length($sel) != length($part-selector) {
556 @error 'Could not generate twin element selector.';
557 }
558
559 $selector: join($selector, $sel, comma);
560 $parts-data: append($parts-data, (
561 'name': $part-name,
562 'selector': $sel
563 ));
564 }
565
566 $context: 'element', (
567 'parts': $parts-data,
568 'selector': $selector
569 );
570
571 @return $selector $context;
572}
573
574///
575/// Generate the current BEM element as a sibling of itself.
576///
577/// It's a shorthand for iro-bem-related-twin-element('~').
578///
579/// @content
580///
581@mixin iro-bem-sibling-twin-element {
582 @include iro-bem-related-twin-element('~') {
583 @content;
584 }
585}
586
587///
588/// Generate the current BEM element as a sibling of itself.
589/// Check the respective mixin documentation for more information.
590///
591/// @return {list} A list with two items: 1. selector, 2. context or `null`
592///
593/// @see {mixin} iro-bem-sibling-twin-element
594///
595@function iro-bem-sibling-twin-element() {
596 @return iro-bem-related-twin-element('~');
597}
598
599///
600/// Generate the current BEM element as a next sibling of itself.
601///
602/// It's a shorthand for iro-bem-related-twin-element('+', $name).
603///
604/// @content
605///
606@mixin iro-bem-next-twin-element {
607 @include iro-bem-related-twin-element('+') {
608 @content;
609 }
610}
611
612///
613/// Generate the current BEM element as a next sibling of itself.
614/// Check the respective mixin documentation for more information.
615///
616/// @return {list} A list with two items: 1. selector, 2. context or `null`
617///
618/// @see {mixin} iro-bem-next-twin-element
619///
620@function iro-bem-next-twin-element() {
621 @return iro-bem-related-twin-element('+');
622}