aboutsummaryrefslogtreecommitdiffstats
path: root/src/bem/_modifier.scss
diff options
context:
space:
mode:
Diffstat (limited to 'src/bem/_modifier.scss')
-rw-r--r--src/bem/_modifier.scss246
1 files changed, 246 insertions, 0 deletions
diff --git a/src/bem/_modifier.scss b/src/bem/_modifier.scss
new file mode 100644
index 0000000..e1f9507
--- /dev/null
+++ b/src/bem/_modifier.scss
@@ -0,0 +1,246 @@
1////
2/// @group BEM
3///
4/// @access public
5////
6
7///
8/// Generate a new BEM modifier.
9///
10/// If the parent context is block or element, the modifier will modify said block or element according
11/// to the BEM naming convention.
12///
13/// If the parent context is a modifier or suffix, then the modifier will depend on said modifier or suffix.
14/// Depending on $extend, the meaning of this dependency (and the resulting selector) varies:
15/// If it's false (default), you signalize that the modifier also exists by itself, but it changes its
16/// behavior when the parent modifier or suffix is set.
17/// If it's true, you signalize that the modifier extends the parent modifier or suffix and can only be
18/// used in conjunction with it.
19///
20/// @param {string | list} $name - First element name or list with two items: 1. first element name, 2. bool indicating if the modifier is extending
21/// @param {string | list} $names - More element names or lists with two items: 1. element name, 2. bool indicating if the modifier is extending
22///
23/// @content
24///
25/// @throw If the element is not preceded by a block, element, modifier or suffix.
26///
27/// @example scss - Modifier that modifies a block or element
28/// @include iro-bem-component('block') {
29/// @include iro-bem-modifier('mod') {
30/// background-color: #eee;
31/// }
32///
33/// @include iro-bem-element('elem') {
34/// @include iro-bem-modifier('mod') {
35/// background-color: #222;
36/// }
37/// }
38/// }
39///
40/// // Generates:
41///
42/// .c-block--mod {
43/// background-color: #eee;
44/// }
45///
46/// .c-block__elem--mod {
47/// background-color: #222;
48/// }
49///
50/// @example scss - Modifier nested in another modifier, not extending
51/// @include iro-bem-component('block') {
52/// @include iro-bem-modifier('mod') {
53/// background-color: #eee;
54/// }
55///
56/// @include iro-bem-modifier('dark') {
57/// /* some definitions */
58///
59/// @include iro-bem-modifier('mod') {
60/// background-color: #222;
61/// }
62/// }
63/// }
64///
65/// // Generates:
66///
67/// .c-block--mod {
68/// background-color: #eee;
69/// }
70///
71/// .c-block--dark {
72/// /* some definitions */
73/// }
74///
75/// .c-block--dark.c-block--mod {
76/// background-color: #222;
77/// }
78///
79/// @example scss - Modifier nested in another modifier, extending
80/// @include iro-bem-component('block') {
81/// @include iro-bem-modifier('mod') {
82/// background-color: #eee;
83/// }
84///
85/// @include iro-bem-modifier('dark') {
86/// /* some definitions */
87///
88/// @include iro-bem-modifier('mod' true) {
89/// background-color: #222;
90/// }
91/// }
92/// }
93///
94/// // Generates:
95///
96/// .c-block--mod {
97/// background-color: #eee;
98/// }
99///
100/// .c-block--dark {
101/// /* some definitions */
102/// }
103///
104/// .c-block--dark--mod {
105/// background-color: #222;
106/// }
107///
108@mixin iro-bem-modifier($name, $names...) {
109 $result: iro-bem-modifier($name, $names...);
110 $selector: nth($result, 1);
111 $context: nth($result, 2);
112
113 @include iro-bem-validate(
114 'modifier',
115 (name: $name, names: $names),
116 $selector,
117 $context
118 );
119
120 @include iro-context-push($iro-bem-context-id, $context...);
121 @at-root #{$selector} {
122 @content;
123 }
124 @include iro-context-pop($iro-bem-context-id);
125}
126
127///
128/// Generate a new BEM modifier. Check the respective mixin documentation for more information.
129///
130/// @return {list} A list with two items: 1. selector, 2. context or `null`
131///
132/// @see {mixin} iro-bem-modifier
133///
134@function iro-bem-modifier($name, $names...) {
135 $noop: iro-context-assert-stack-count($iro-bem-context-id, $iro-bem-max-depth);
136 $noop: iro-context-assert-stack-must-contain($iro-bem-context-id, 'block');
137
138 $parent-context: iro-context-get($iro-bem-context-id, 'block' 'element' 'modifier' 'suffix');
139 $parent-selector: map-get(nth($parent-context, 2), 'selector');
140 $selector: ();
141 $parts-data: ();
142
143 @if not iro-selector-suffix-match(&, $parent-selector) {
144 //
145 // The current selector doesn't match the parent selector.
146 // The user manually added a selector between parent context and this modifier call.
147 // This case is forbidden because any outcome semantically wouldn't make sense:
148 // - {b,e,m,s} [manual selector] {b,e,m,s}--modifier
149 // - {b,e,m,s}--modifier [manual selector]
150 // The first case would make the modifier behave like an element.
151 // The second case is unintuitive, the code would be more clear by nesting the manual
152 // selector in the modifier instead.
153 //
154
155 @error 'A modifier must be an immediate child of the parent context';
156 }
157
158 @each $name in iro-list-prepend($names, $name) {
159 $extend: false;
160 @if type-of($name) == list {
161 $extend: nth($name, 2);
162 $name: nth($name, 1);
163 }
164
165 @if index('block' 'element', nth($parent-context, 1)) or $extend == true {
166 //
167 // Either the parent context is block or element, or a modifier or suffix
168 // is to be extended. The modifier part can simply be appended.
169 // Possible outcomes:
170 // - {b,e,m,s}--modifier
171 //
172
173 $sel: selector-append(&, $iro-bem-modifier-separator + $name);
174 $selector: join($selector, $sel, comma);
175 $parts-data: append($parts-data, (
176 'name': $name,
177 'selector': $sel
178 ));
179 } @else {
180 //
181 // Parent context is modifier, suffix or state and $extend is false.
182 //
183
184 $be-context: iro-context-get($iro-bem-context-id, 'block' 'element');
185
186 @if nth($be-context, 1) == 'element' {
187 //
188 // Latest context is element. Since element contexts can consist of multiple single
189 // elements, inspect all elements and append its selector with the suffix "--$name".
190 // This has to be done with string comparison since none of Sass selector functions
191 // is of use here.
192 // Possible outcomes:
193 // - {m,s}.{e}--modifier
194 //
195
196 $nsel: ();
197
198 @each $elem-part-data in map-get(nth($be-context, 2), 'parts') {
199 $elem-part-selector: map-get($elem-part-data, 'selector');
200
201 $sel: ();
202 @each $s in & {
203 @each $ps in $elem-part-selector {
204 @if str-index(inspect($s), inspect($ps) + $iro-bem-modifier-separator) or str-index(inspect($s), inspect($ps) + $iro-bem-suffix-separator) {
205 $sel: join($sel, selector-unify($s, selector-append($ps, $iro-bem-modifier-separator + $name)), comma);
206 }
207 }
208 }
209 @if length($sel) == 0 {
210 @error 'Could not generate modifier selector.';
211 }
212
213 $nsel: join($nsel, $sel, comma);
214 }
215
216 $selector: join($selector, $nsel, comma);
217 $parts-data: append($parts-data, (
218 'name': $name,
219 'selector': $nsel
220 ));
221 } @else {
222 //
223 // Latest context is block. Just append the modifier part.
224 // Possible outcomes:
225 // - {m,s}.{b}--modifier
226 //
227
228 $block-base-selector: map-get(nth($be-context, 2), 'base-selector');
229
230 $sel: selector-append(&, $block-base-selector, $iro-bem-modifier-separator + $name);
231 $selector: join($selector, $sel, comma);
232 $parts-data: append($parts-data, (
233 'name': $name,
234 'selector': $sel
235 ));
236 }
237 }
238 }
239
240 $context: 'modifier', (
241 'parts': $parts-data,
242 'selector': $selector
243 );
244
245 @return $selector $context;
246}