diff options
author | Feuerfuchs <git@feuerfuchs.dev> | 2020-11-01 20:55:14 +0100 |
---|---|---|
committer | Feuerfuchs <git@feuerfuchs.dev> | 2020-11-01 20:55:14 +0100 |
commit | d07f664450ddaaebb44127a4bd057763d13d3f82 (patch) | |
tree | 234cfd673ac527869a8dda4f32afbec48c87b512 /src/bem | |
download | iro-sass-d07f664450ddaaebb44127a4bd057763d13d3f82.tar.gz iro-sass-d07f664450ddaaebb44127a4bd057763d13d3f82.tar.bz2 iro-sass-d07f664450ddaaebb44127a4bd057763d13d3f82.zip |
Init
Diffstat (limited to 'src/bem')
-rw-r--r-- | src/bem/_block.scss | 392 | ||||
-rw-r--r-- | src/bem/_debug.scss | 16 | ||||
-rw-r--r-- | src/bem/_element.scss | 622 | ||||
-rw-r--r-- | src/bem/_functions.scss | 26 | ||||
-rw-r--r-- | src/bem/_modifier.scss | 246 | ||||
-rw-r--r-- | src/bem/_multi.scss | 131 | ||||
-rw-r--r-- | src/bem/_state.scss | 146 | ||||
-rw-r--r-- | src/bem/_suffix.scss | 118 | ||||
-rw-r--r-- | src/bem/_theme.scss | 61 | ||||
-rw-r--r-- | src/bem/_validators.scss | 176 | ||||
-rw-r--r-- | src/bem/_vars.scss | 108 |
11 files changed, 2042 insertions, 0 deletions
diff --git a/src/bem/_block.scss b/src/bem/_block.scss new file mode 100644 index 0000000..d065891 --- /dev/null +++ b/src/bem/_block.scss | |||
@@ -0,0 +1,392 @@ | |||
1 | //// | ||
2 | /// @group BEM | ||
3 | /// | ||
4 | /// @access public | ||
5 | //// | ||
6 | |||
7 | /// | ||
8 | /// Generate a new block. | ||
9 | /// | ||
10 | /// This mixin simply creates a new block with the name {namespace}_{name}, | ||
11 | /// where {namespace} is the prefix assigned to $type and {name} is the | ||
12 | /// block's name. | ||
13 | /// | ||
14 | /// @param {string} $name - Block name | ||
15 | /// @param {string} $type [null] - BEMIT namespace of the block | ||
16 | /// | ||
17 | /// @content | ||
18 | /// | ||
19 | /// @throw If $type is invalid | ||
20 | /// @throw If the block is preceded by another block, element, modifier or suffix | ||
21 | /// | ||
22 | /// @example scss - Creating a new block | ||
23 | /// @include iro-bem-block('something', 'component') { | ||
24 | /// /* some definitions */ | ||
25 | /// } | ||
26 | /// | ||
27 | /// // Generates: | ||
28 | /// | ||
29 | /// .c-something { | ||
30 | /// /* some definitions */ | ||
31 | /// } | ||
32 | /// | ||
33 | @mixin iro-bem-block($name, $type: null) { | ||
34 | $result: iro-bem-block($name, $type); | ||
35 | $selector: nth($result, 1); | ||
36 | $context: nth($result, 2); | ||
37 | |||
38 | @include iro-bem-validate( | ||
39 | 'block', | ||
40 | (name: $name, type: $type), | ||
41 | $selector, | ||
42 | $context | ||
43 | ); | ||
44 | |||
45 | @if $type != null { | ||
46 | $iro-bem-blocks: append($iro-bem-blocks, $name + '_' + $type) !global; | ||
47 | } @else { | ||
48 | $iro-bem-blocks: append($iro-bem-blocks, $name) !global; | ||
49 | } | ||
50 | |||
51 | @include iro-context-push($iro-bem-context-id, $context...); | ||
52 | @at-root #{$selector} { | ||
53 | @content; | ||
54 | } | ||
55 | @include iro-context-pop($iro-bem-context-id); | ||
56 | } | ||
57 | |||
58 | /// | ||
59 | /// Generate a new block. Check the respective mixin documentation for more information. | ||
60 | /// | ||
61 | /// @return {list} A list with two items: 1. selector, 2. context or `null` | ||
62 | /// | ||
63 | /// @see {mixin} iro-bem-block | ||
64 | /// | ||
65 | @function iro-bem-block($name, $type: null) { | ||
66 | // | ||
67 | // Possible outcomes: | ||
68 | // - ({b,e,m,s}) block | ||
69 | // | ||
70 | |||
71 | $noop: iro-context-assert-stack-count($iro-bem-context-id, $iro-bem-max-depth); | ||
72 | |||
73 | $selector: null; | ||
74 | $base-selector: null; | ||
75 | |||
76 | @if $type != null { | ||
77 | $namespace: map-get($iro-bem-namespaces, $type); | ||
78 | |||
79 | @if not $namespace { | ||
80 | @error '"#{$type}" is not a valid type.'; | ||
81 | } | ||
82 | |||
83 | $base-selector: selector-parse('.' + $namespace + '-' + $name); | ||
84 | |||
85 | @if $type != 'theme' or & { | ||
86 | $selector: $base-selector; | ||
87 | } @else if not & { | ||
88 | $selector: iro-bem-theme-selector($name); | ||
89 | } | ||
90 | } @else { | ||
91 | $base-selector: selector-parse('.' + $name); | ||
92 | $selector: $base-selector; | ||
93 | } | ||
94 | |||
95 | @if & { | ||
96 | $selector: selector-nest(&, $selector); | ||
97 | } | ||
98 | |||
99 | $context: 'block', ( | ||
100 | 'name': $name, | ||
101 | 'type': $type, | ||
102 | 'selector': $selector, | ||
103 | 'base-selector': $base-selector | ||
104 | ); | ||
105 | |||
106 | @return $selector $context; | ||
107 | } | ||
108 | |||
109 | /// | ||
110 | /// Generate a new object block. It's a shorthand for iro-bem-block($name, 'object'). | ||
111 | /// | ||
112 | /// @param {string} $name - Object block name | ||
113 | /// | ||
114 | /// @content | ||
115 | /// | ||
116 | @mixin iro-bem-object($name) { | ||
117 | @include iro-bem-block($name, 'object') { | ||
118 | @content; | ||
119 | } | ||
120 | } | ||
121 | |||
122 | /// | ||
123 | /// Generate a new object block. Check the respective mixin documentation for more information. | ||
124 | /// | ||
125 | /// @return {list} A list with two items: 1. selector, 2. context or `null` | ||
126 | /// | ||
127 | /// @see {mixin} iro-bem-object | ||
128 | /// | ||
129 | @function iro-bem-object($name) { | ||
130 | @return iro-bem-block($name, 'object'); | ||
131 | } | ||
132 | |||
133 | /// | ||
134 | /// Generate a new component block. It's a shorthand for iro-bem-block($name, 'component'). | ||
135 | /// | ||
136 | /// @param {string} $name - Component block name | ||
137 | /// | ||
138 | /// @content | ||
139 | /// | ||
140 | @mixin iro-bem-component($name) { | ||
141 | @include iro-bem-block($name, 'component') { | ||
142 | @content; | ||
143 | } | ||
144 | } | ||
145 | |||
146 | /// | ||
147 | /// Generate a new component block. Check the respective mixin documentation for more information. | ||
148 | /// | ||
149 | /// @return {list} A list with two items: 1. selector, 2. context or `null` | ||
150 | /// | ||
151 | /// @see {mixin} iro-bem-component | ||
152 | /// | ||
153 | @function iro-bem-component($name) { | ||
154 | @return iro-bem-block($name, 'component'); | ||
155 | } | ||
156 | |||
157 | /// | ||
158 | /// Generate a new layout block. It's a shorthand for iro-bem-block($name, 'layout'). | ||
159 | /// | ||
160 | /// @param {string} $name - Layout block name | ||
161 | /// | ||
162 | /// @content | ||
163 | /// | ||
164 | @mixin iro-bem-layout($name) { | ||
165 | @include iro-bem-block($name, 'layout') { | ||
166 | @content; | ||
167 | } | ||
168 | } | ||
169 | |||
170 | /// | ||
171 | /// Generate a new layout block. Check the respective mixin documentation for more information. | ||
172 | /// | ||
173 | /// @return {list} A list with two items: 1. selector, 2. context or `null` | ||
174 | /// | ||
175 | /// @see {mixin} iro-bem-layout | ||
176 | /// | ||
177 | @function iro-bem-layout($name) { | ||
178 | @return iro-bem-block($name, 'layout'); | ||
179 | } | ||
180 | |||
181 | /// | ||
182 | /// Generate a new utility block. It's a shorthand for iro-bem-block($name, 'utility'). | ||
183 | /// | ||
184 | /// @param {string} $name - Utility block name | ||
185 | /// | ||
186 | /// @content | ||
187 | /// | ||
188 | @mixin iro-bem-utility($name) { | ||
189 | @include iro-bem-block($name, 'utility') { | ||
190 | @content; | ||
191 | } | ||
192 | } | ||
193 | |||
194 | /// | ||
195 | /// Generate a new utility block. Check the respective mixin documentation for more information. | ||
196 | /// | ||
197 | /// @return {list} A list with two items: 1. selector, 2. context or `null` | ||
198 | /// | ||
199 | /// @see {mixin} iro-bem-utility | ||
200 | /// | ||
201 | @function iro-bem-utility($name) { | ||
202 | @return iro-bem-block($name, 'utility'); | ||
203 | } | ||
204 | |||
205 | /// | ||
206 | /// Generate a new scope block. It's a shorthand for iro-bem-block($name, 'scope'). | ||
207 | /// | ||
208 | /// @param {string} $name - Scope block name | ||
209 | /// | ||
210 | /// @content | ||
211 | /// | ||
212 | @mixin iro-bem-scope($name) { | ||
213 | @include iro-bem-block($name, 'scope') { | ||
214 | @content; | ||
215 | } | ||
216 | } | ||
217 | |||
218 | /// | ||
219 | /// Generate a new scope block. Check the respective mixin documentation for more information. | ||
220 | /// | ||
221 | /// @return {list} A list with two items: 1. selector, 2. context or `null` | ||
222 | /// | ||
223 | /// @see {mixin} iro-bem-scope | ||
224 | /// | ||
225 | @function iro-bem-scope($name) { | ||
226 | @return iro-bem-block($name, 'scope'); | ||
227 | } | ||
228 | |||
229 | /// | ||
230 | /// Generate a new theme block. It's a shorthand for iro-bem-block($name, 'theme'). | ||
231 | /// | ||
232 | /// @param {string} $name - Theme block name | ||
233 | /// | ||
234 | /// @content | ||
235 | /// | ||
236 | @mixin iro-bem-theme($name) { | ||
237 | @include iro-bem-block($name, 'theme') { | ||
238 | @content; | ||
239 | } | ||
240 | } | ||
241 | |||
242 | /// | ||
243 | /// Generate a new theme block. Check the respective mixin documentation for more information. | ||
244 | /// | ||
245 | /// @return {list} A list with two items: 1. selector, 2. context or `null` | ||
246 | /// | ||
247 | /// @see {mixin} iro-bem-theme | ||
248 | /// | ||
249 | @function iro-bem-theme($name) { | ||
250 | @return iro-bem-block($name, 'theme'); | ||
251 | } | ||
252 | |||
253 | /// | ||
254 | /// Generate a new JS block. It's a shorthand for iro-bem-block($name, 'js'). | ||
255 | /// | ||
256 | /// @param {string} $name - JS block name | ||
257 | /// | ||
258 | /// @content | ||
259 | /// | ||
260 | @mixin iro-bem-js($name) { | ||
261 | @include iro-bem-block($name, 'js') { | ||
262 | @content; | ||
263 | } | ||
264 | } | ||
265 | |||
266 | /// | ||
267 | /// Generate a new JS block. Check the respective mixin documentation for more information. | ||
268 | /// | ||
269 | /// @return {list} A list with two items: 1. selector, 2. context or `null` | ||
270 | /// | ||
271 | /// @see {mixin} iro-bem-js | ||
272 | /// | ||
273 | @function iro-bem-js($name) { | ||
274 | @return iro-bem-block($name, 'js'); | ||
275 | } | ||
276 | |||
277 | /// | ||
278 | /// Generate a new QA block. It's a shorthand for iro-bem-block($name, 'qa'). | ||
279 | /// | ||
280 | /// @param {string} $name - QA block name | ||
281 | /// | ||
282 | /// @content | ||
283 | /// | ||
284 | @mixin iro-bem-qa($name) { | ||
285 | @include iro-bem-block($name, 'qa') { | ||
286 | @content; | ||
287 | } | ||
288 | } | ||
289 | |||
290 | /// | ||
291 | /// Generate a new QA block. Check the respective mixin documentation for more information. | ||
292 | /// | ||
293 | /// @return {list} A list with two items: 1. selector, 2. context or `null` | ||
294 | /// | ||
295 | /// @see {mixin} iro-bem-qa | ||
296 | /// | ||
297 | @function iro-bem-qa($name) { | ||
298 | @return iro-bem-block($name, 'qa'); | ||
299 | } | ||
300 | |||
301 | /// | ||
302 | /// Generate a new hack block. It's a shorthand for iro-bem-block($name, 'hack'). | ||
303 | /// | ||
304 | /// @param {string} $name - Hack block name | ||
305 | /// | ||
306 | /// @content | ||
307 | /// | ||
308 | @mixin iro-bem-hack($name) { | ||
309 | @include iro-bem-block($name, 'hack') { | ||
310 | @content; | ||
311 | } | ||
312 | } | ||
313 | |||
314 | /// | ||
315 | /// Generate a new hack block. Check the respective mixin documentation for more information. | ||
316 | /// | ||
317 | /// @return {list} A list with two items: 1. selector, 2. context or `null` | ||
318 | /// | ||
319 | /// @see {mixin} iro-bem-hack | ||
320 | /// | ||
321 | @function iro-bem-hack($name) { | ||
322 | @return iro-bem-block($name, 'hack'); | ||
323 | } | ||
324 | |||
325 | /// | ||
326 | /// Assert that a block or element is composed of another block. In BEM, such a relationship is referred to | ||
327 | /// as a "mix": https://en.bem.info/methodology/key-concepts/#mix | ||
328 | /// | ||
329 | /// Compilation will fail if the foreign block doesn't exist. This way, you can ensure that blocks are | ||
330 | /// defined in the right order so that composed blocks/elements will actually override the foreign | ||
331 | /// declarations without having to artificially increase the specificity. | ||
332 | /// | ||
333 | /// @param {string | list} $block - Either first block name, or list with two items: 1. block name, 2. block type | ||
334 | /// @param {string | list} $blocks - Either other block names, or list with two items: 1. block name, 2. block type | ||
335 | /// | ||
336 | /// @throw If a block type is invalid | ||
337 | /// @throw If a block doesn't exist | ||
338 | /// | ||
339 | /// @example scss - Successful assertion | ||
340 | /// @include iro-bem-component('someBlock') { | ||
341 | /// /* some definitions */ | ||
342 | /// } | ||
343 | /// | ||
344 | /// @include iro-bem-component('anotherBlock') { | ||
345 | /// /* some definitions */ | ||
346 | /// | ||
347 | /// @include iro-bem-element('elem') { | ||
348 | /// @include iro-bem-composed-of('someBlock' 'component'); | ||
349 | /// | ||
350 | /// /* some definitions */ | ||
351 | /// } | ||
352 | /// } | ||
353 | /// | ||
354 | /// // Intended use: <div class="c-anotherBlock__elem c-someBlock">...</div> | ||
355 | /// | ||
356 | /// @example scss - Failing assertion | ||
357 | /// @include iro-bem-component('anotherBlock') { | ||
358 | /// /* some definitions */ | ||
359 | /// | ||
360 | /// @include iro-bem-element('elem') { | ||
361 | /// @include iro-bem-composed-of('someBlock' 'component'); | ||
362 | /// | ||
363 | /// /* some definitions */ | ||
364 | /// } | ||
365 | /// } | ||
366 | /// | ||
367 | /// @include iro-bem-component('someBlock') { | ||
368 | /// /* some definitions */ | ||
369 | /// } | ||
370 | /// | ||
371 | /// // Compilation will fail because c-someBlock is defined after c-anotherBlock__elem | ||
372 | /// | ||
373 | @mixin iro-bem-composed-of($block, $blocks...) { | ||
374 | @each $block in iro-list-prepend($blocks, $block) { | ||
375 | @if type-of($block) == string { | ||
376 | @if not index($iro-bem-blocks, $block) { | ||
377 | @error 'Block "#{$block}" does not exist.'; | ||
378 | } | ||
379 | } @else { | ||
380 | $name: nth($block, 1); | ||
381 | $type: nth($block, 2); | ||
382 | |||
383 | @if not map-get($iro-bem-namespaces, $type) { | ||
384 | @error '"#{$type}" is not a valid type.'; | ||
385 | } | ||
386 | |||
387 | @if not index($iro-bem-blocks, $name + '_' + $type) { | ||
388 | @error 'Block "#{$name}" does not exist.'; | ||
389 | } | ||
390 | } | ||
391 | } | ||
392 | } | ||
diff --git a/src/bem/_debug.scss b/src/bem/_debug.scss new file mode 100644 index 0000000..e69083c --- /dev/null +++ b/src/bem/_debug.scss | |||
@@ -0,0 +1,16 @@ | |||
1 | //// | ||
2 | /// @group BEM | ||
3 | /// | ||
4 | /// @access public | ||
5 | //// | ||
6 | |||
7 | @if $iro-bem-debug { | ||
8 | @each $type, $color in $iro-bem-debug-colors { | ||
9 | $namespace: map-get($iro-bem-namespaces, $type); | ||
10 | |||
11 | [class^='#{$namespace}-'], | ||
12 | [class*=' #{$namespace}-'] { | ||
13 | outline: 5px solid $color; | ||
14 | } | ||
15 | } | ||
16 | } | ||
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 | } | ||
diff --git a/src/bem/_functions.scss b/src/bem/_functions.scss new file mode 100644 index 0000000..4bb95c4 --- /dev/null +++ b/src/bem/_functions.scss | |||
@@ -0,0 +1,26 @@ | |||
1 | //// | ||
2 | /// @group BEM | ||
3 | /// | ||
4 | /// @access public | ||
5 | //// | ||
6 | |||
7 | /// | ||
8 | /// @access private | ||
9 | /// | ||
10 | @function iro-bem-theme-selector($name, $names...) { | ||
11 | $namespace: map-get($iro-bem-namespaces, 'theme'); | ||
12 | $selector: null; | ||
13 | |||
14 | @each $name in join($name, $names) { | ||
15 | $sel: '.' + $namespace + '-' + $name; | ||
16 | |||
17 | @if $selector == null { | ||
18 | $selector: join(selector-parse($sel), selector-parse('[class*=\' t-\'] ' + $sel), comma); | ||
19 | $selector: join($selector, selector-parse('[class^=\'t-\'] ' + $sel), comma); | ||
20 | } @else { | ||
21 | $selector: selector-nest($selector, $sel); | ||
22 | } | ||
23 | } | ||
24 | |||
25 | @return $selector; | ||
26 | } | ||
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 | } | ||
diff --git a/src/bem/_multi.scss b/src/bem/_multi.scss new file mode 100644 index 0000000..9e47ce4 --- /dev/null +++ b/src/bem/_multi.scss | |||
@@ -0,0 +1,131 @@ | |||
1 | //// | ||
2 | /// @group BEM | ||
3 | /// | ||
4 | /// @access public | ||
5 | //// | ||
6 | |||
7 | /// | ||
8 | /// Generate multiple entities (BEM or not) at once. | ||
9 | /// | ||
10 | /// NOTE: This mixin does not generate perfectly optimized selectors in order to keep track of contexts. | ||
11 | /// | ||
12 | /// @param {string | list} $first - First selector. Either a string for a manual selector, or a list with the first items standing for a BEM selector function (optionally suffixed by a colon) and other items being passed as arguments to said function. | ||
13 | /// @param {string | list} $others - Other selectors. Either a string for a manual selector, or a list with the first items standing for a BEM selector function (optionally suffixed by a colon) and other items being passed as arguments to said function. | ||
14 | /// | ||
15 | /// @content | ||
16 | /// | ||
17 | /// @example scss - Creating multiple elements, a modifier and an anchor | ||
18 | /// @include iro-bem-object('buttonstrip') { | ||
19 | /// display: none; | ||
20 | /// | ||
21 | /// @include iro-bem-multi('modifier' 'mod', 'element' 'button' 'separator', '> a') { | ||
22 | /// display: block; | ||
23 | /// } | ||
24 | /// } | ||
25 | /// | ||
26 | /// // Generates: | ||
27 | /// | ||
28 | /// .o-buttonstrip { | ||
29 | /// display: none; | ||
30 | /// } | ||
31 | /// | ||
32 | /// .o-buttonstrip--mod { | ||
33 | /// display: block; | ||
34 | /// } | ||
35 | /// | ||
36 | /// .o-buttonstrip__button, { | ||
37 | /// .o-buttonstrip__separator { | ||
38 | /// display: block; | ||
39 | /// } | ||
40 | /// | ||
41 | /// .o-buttonstrip > a { | ||
42 | /// display: block; | ||
43 | /// } | ||
44 | /// | ||
45 | /// @example scss - Creating multiple elements, a modifier and an anchor - optional colons included | ||
46 | /// @include iro-bem-object('buttonstrip') { | ||
47 | /// display: none; | ||
48 | /// | ||
49 | /// @include iro-bem-multi('modifier:' 'mod', 'element:' 'button' 'separator', '> a') { | ||
50 | /// display: block; | ||
51 | /// } | ||
52 | /// } | ||
53 | /// | ||
54 | /// // Generates: | ||
55 | /// | ||
56 | /// .o-buttonstrip { | ||
57 | /// display: none; | ||
58 | /// } | ||
59 | /// | ||
60 | /// .o-buttonstrip--mod { | ||
61 | /// display: block; | ||
62 | /// } | ||
63 | /// | ||
64 | /// .o-buttonstrip__button, { | ||
65 | /// .o-buttonstrip__separator { | ||
66 | /// display: block; | ||
67 | /// } | ||
68 | /// | ||
69 | /// .o-buttonstrip > a { | ||
70 | /// display: block; | ||
71 | /// } | ||
72 | /// | ||
73 | @mixin iro-bem-multi($first, $others...) { | ||
74 | @include iro-context-assert-stack-count($iro-bem-context-id, $iro-bem-max-depth); | ||
75 | |||
76 | @each $entity in iro-list-prepend($others, $first) { | ||
77 | $is-manual-selector: false; | ||
78 | |||
79 | @if type-of($entity) == string and not function-exists('iro-bem-' + $entity) { | ||
80 | $is-manual-selector: true; | ||
81 | } | ||
82 | |||
83 | @if $is-manual-selector { | ||
84 | $sel: if(&, selector-nest(&, $entity), selector-parse($entity)); | ||
85 | |||
86 | @at-root #{$sel} { | ||
87 | @content; | ||
88 | } | ||
89 | } @else { | ||
90 | $entity-func-id: null; | ||
91 | |||
92 | @if type-of($entity) == list { | ||
93 | $entity-func-id: nth($entity, 1); | ||
94 | $entity: iro-list-slice($entity, 2); | ||
95 | } @else { | ||
96 | $entity-func-id: $entity; | ||
97 | $entity: (); | ||
98 | } | ||
99 | |||
100 | @if str-slice($entity-func-id, str-length($entity-func-id)) == ':' { | ||
101 | $entity-func-id: str-slice($entity-func-id, 1, str-length($entity-func-id) - 1); | ||
102 | } | ||
103 | |||
104 | $sel-func: null; | ||
105 | |||
106 | @if function-exists('iro-bem-' + $entity-func-id) { | ||
107 | $sel-func: get-function('iro-bem-' + $entity-func-id); | ||
108 | } @else if function-exists($entity-func-id) { | ||
109 | $sel-func: get-function($entity-func-id); | ||
110 | } | ||
111 | |||
112 | @if $sel-func == null { | ||
113 | @error 'Function "#{inspect($entity-func-id)}" was not found.'; | ||
114 | } | ||
115 | |||
116 | $entity-result: call($sel-func, $entity...); | ||
117 | $entity-result-selector: nth($entity-result, 1); | ||
118 | $entity-result-context: nth($entity-result, 2); | ||
119 | |||
120 | @if $entity-result-context != null { | ||
121 | @include iro-context-push($iro-bem-context-id, $entity-result-context...); | ||
122 | } | ||
123 | @at-root #{$entity-result-selector} { | ||
124 | @content; | ||
125 | } | ||
126 | @if $entity-result-context != null { | ||
127 | @include iro-context-pop($iro-bem-context-id); | ||
128 | } | ||
129 | } | ||
130 | } | ||
131 | } | ||
diff --git a/src/bem/_state.scss b/src/bem/_state.scss new file mode 100644 index 0000000..4a85bbb --- /dev/null +++ b/src/bem/_state.scss | |||
@@ -0,0 +1,146 @@ | |||
1 | //// | ||
2 | /// @group BEM | ||
3 | /// | ||
4 | /// @access public | ||
5 | //// | ||
6 | |||
7 | /// | ||
8 | /// Create a new state rule. | ||
9 | /// | ||
10 | /// @param {string} $state - First state name | ||
11 | /// @param {list} $states - List of more state names | ||
12 | /// | ||
13 | /// @content | ||
14 | /// | ||
15 | /// @example scss - Using single is-state | ||
16 | /// @include iro-bem-object('menu') { | ||
17 | /// display: none; | ||
18 | /// | ||
19 | /// @include iro-bem-state('is', open') { | ||
20 | /// display: block; | ||
21 | /// } | ||
22 | /// } | ||
23 | /// | ||
24 | /// // Generates: | ||
25 | /// | ||
26 | /// .o-menu { | ||
27 | /// display: none; | ||
28 | /// } | ||
29 | /// | ||
30 | /// .o-menu.is-open { | ||
31 | /// display: block; | ||
32 | /// } | ||
33 | /// | ||
34 | /// @example scss - Using multiple is-states | ||
35 | /// @include iro-bem-object('menu') { | ||
36 | /// display: none; | ||
37 | /// | ||
38 | /// @include iro-bem-state('is', open', 'visible') { | ||
39 | /// display: block; | ||
40 | /// } | ||
41 | /// } | ||
42 | /// | ||
43 | /// // Generates: | ||
44 | /// | ||
45 | /// .o-menu { | ||
46 | /// display: none; | ||
47 | /// } | ||
48 | /// | ||
49 | /// .o-menu.is-open, | ||
50 | /// .o-menu.is-visible { | ||
51 | /// display: block; | ||
52 | /// } | ||
53 | /// | ||
54 | @mixin iro-bem-state($prefix, $state, $states...) { | ||
55 | $result: iro-bem-state($prefix, $state, $states...); | ||
56 | $selector: nth($result, 1); | ||
57 | $context: nth($result, 2); | ||
58 | |||
59 | @include iro-bem-validate( | ||
60 | 'state', | ||
61 | (prefix: $prefix, state: $state, states: $states), | ||
62 | $selector, | ||
63 | $context | ||
64 | ); | ||
65 | |||
66 | @include iro-context-push($iro-bem-context-id, $context...); | ||
67 | @at-root #{$selector} { | ||
68 | @content; | ||
69 | } | ||
70 | @include iro-context-pop($iro-bem-context-id); | ||
71 | } | ||
72 | |||
73 | /// | ||
74 | /// Generate a new state. | ||
75 | /// | ||
76 | /// @return {list} A list with two items: 1. selector, 2. context or `null` | ||
77 | /// | ||
78 | /// @see {mixin} iro-bem-has | ||
79 | /// | ||
80 | @function iro-bem-state($prefix, $state, $states...) { | ||
81 | $selector: (); | ||
82 | $parts-data: (); | ||
83 | |||
84 | @each $state in join($state, $states) { | ||
85 | $sel: selector-parse('.#{$prefix}-#{$state}'); | ||
86 | @if & { | ||
87 | $sel: selector-append(&, $sel); | ||
88 | } | ||
89 | $selector: join($selector, $sel, comma); | ||
90 | $parts-data: append($parts-data, ( | ||
91 | 'name': $state, | ||
92 | 'selector': $sel | ||
93 | )); | ||
94 | } | ||
95 | |||
96 | $context: 'state', ( | ||
97 | 'parts': $parts-data, | ||
98 | 'selector': $selector | ||
99 | ); | ||
100 | |||
101 | @return $selector $context; | ||
102 | } | ||
103 | |||
104 | /// | ||
105 | /// Create a new has-state modifier. | ||
106 | /// | ||
107 | /// It's a shorthand for iro-bem-state('is', $state, $states...). | ||
108 | /// | ||
109 | @mixin iro-bem-is($state, $states...) { | ||
110 | @include iro-bem-state('is', $state, $states...) { | ||
111 | @content; | ||
112 | } | ||
113 | } | ||
114 | |||
115 | /// | ||
116 | /// Generate a new is-state modifier. Check the respective mixin documentation for more information. | ||
117 | /// | ||
118 | /// @return {list} A list with two items: 1. selector, 2. context or `null` | ||
119 | /// | ||
120 | /// @see {mixin} iro-bem-is | ||
121 | /// | ||
122 | @function iro-bem-is($state, $states...) { | ||
123 | @return iro-bem-state('is', $state, $states...); | ||
124 | } | ||
125 | |||
126 | /// | ||
127 | /// Create a new has-state modifier. | ||
128 | /// | ||
129 | /// It's a shorthand for iro-bem-state('has', $state, $states...). | ||
130 | /// | ||
131 | @mixin iro-bem-has($state, $states...) { | ||
132 | @include iro-bem-state('has', $state, $states...) { | ||
133 | @content; | ||
134 | } | ||
135 | } | ||
136 | |||
137 | /// | ||
138 | /// Generate a new has-state modifier. Check the respective mixin documentation for more information. | ||
139 | /// | ||
140 | /// @return {list} A list with two items: 1. selector, 2. context or `null` | ||
141 | /// | ||
142 | /// @see {mixin} iro-bem-has | ||
143 | /// | ||
144 | @function iro-bem-has($state, $states...) { | ||
145 | @return iro-bem-state('has', $state, $states...); | ||
146 | } | ||
diff --git a/src/bem/_suffix.scss b/src/bem/_suffix.scss new file mode 100644 index 0000000..b103c9f --- /dev/null +++ b/src/bem/_suffix.scss | |||
@@ -0,0 +1,118 @@ | |||
1 | //// | ||
2 | /// @group BEM | ||
3 | /// | ||
4 | /// @access public | ||
5 | //// | ||
6 | |||
7 | /// | ||
8 | /// Generate a new suffix. | ||
9 | /// | ||
10 | /// @param {string} $name - Suffix name | ||
11 | /// | ||
12 | /// @content | ||
13 | /// | ||
14 | /// @throw If the element is not preceded by a block or modifier. | ||
15 | /// | ||
16 | /// @example scss - Using a suffix | ||
17 | /// @include iro-bem-utility('hidden') { | ||
18 | /// display: none; | ||
19 | /// | ||
20 | /// @media (max-width: 320px) { | ||
21 | /// @include iro-bem-suffix('phone') { | ||
22 | /// display: none; | ||
23 | /// } | ||
24 | /// } | ||
25 | /// | ||
26 | /// @media (max-width: 768px) { | ||
27 | /// @include iro-bem-suffix('tablet') { | ||
28 | /// display: none; | ||
29 | /// } | ||
30 | /// } | ||
31 | /// } | ||
32 | /// | ||
33 | /// // Generates: | ||
34 | /// | ||
35 | /// .u-hidden { | ||
36 | /// display: none; | ||
37 | /// } | ||
38 | /// | ||
39 | /// @media (max-width: 320px) { | ||
40 | /// .u-hidden@phone { | ||
41 | /// display: none; | ||
42 | /// } | ||
43 | /// } | ||
44 | /// | ||
45 | /// @media (max-width: 768px) { | ||
46 | /// .u-hidden@tablet { | ||
47 | /// display: none; | ||
48 | /// } | ||
49 | /// } | ||
50 | /// | ||
51 | @mixin iro-bem-suffix($name) { | ||
52 | $result: iro-bem-suffix($name); | ||
53 | $selector: nth($result, 1); | ||
54 | $context: nth($result, 2); | ||
55 | |||
56 | @include iro-bem-validate( | ||
57 | 'suffix', | ||
58 | (name: $name), | ||
59 | $selector, | ||
60 | $context | ||
61 | ); | ||
62 | |||
63 | @include iro-context-push($iro-bem-context-id, $context...); | ||
64 | @at-root #{$selector} { | ||
65 | @content; | ||
66 | } | ||
67 | @include iro-context-pop($iro-bem-context-id); | ||
68 | } | ||
69 | |||
70 | /// | ||
71 | /// Generate a new suffix. Check the respective mixin documentation for more information. | ||
72 | /// | ||
73 | /// @return {list} A list with two items: 1. selector, 2. context or `null` | ||
74 | /// | ||
75 | /// @see {mixin} iro-bem-suffix | ||
76 | /// | ||
77 | @function iro-bem-suffix($name) { | ||
78 | // | ||
79 | // Suffixes can be used on block, element and modifier. | ||
80 | // | ||
81 | |||
82 | $noop: iro-context-assert-stack-count($iro-bem-context-id, $iro-bem-max-depth); | ||
83 | $noop: iro-context-assert-stack-must-contain($iro-bem-context-id, 'block'); | ||
84 | $noop: iro-context-assert-stack-must-not-contain($iro-bem-context-id, 'suffix'); | ||
85 | |||
86 | $parent-context: iro-context-get($iro-bem-context-id, 'block' 'element' 'modifier'); | ||
87 | $parent-selector: map-get(nth($parent-context, 2), 'selector'); | ||
88 | |||
89 | @if not iro-selector-suffix-match(&, $parent-selector) { | ||
90 | // | ||
91 | // The current selector doesn't match the parent selector. | ||
92 | // The user manually added a selector between parent context and this suffix call. | ||
93 | // This case is forbidden because any outcome semantically wouldn't make sense: | ||
94 | // - {b,e,m} [manual selector] {b,e,m}@suffix | ||
95 | // - {b,e,m}@suffix [manual selector] | ||
96 | // The first case would make the modifier behave like an element. | ||
97 | // The second case is unintuitive, the code would be more clear by nesting the manual | ||
98 | // selector in the suffix instead. | ||
99 | // | ||
100 | |||
101 | @error 'A suffix must be an immediate child of the parent context'; | ||
102 | } | ||
103 | |||
104 | // | ||
105 | // The suffix part can simply be appended. | ||
106 | // Possible outcomes: | ||
107 | // - {b,e,m}@suffix | ||
108 | // | ||
109 | |||
110 | $selector: selector-append(&, $iro-bem-suffix-separator + $name); | ||
111 | |||
112 | $context: 'suffix', ( | ||
113 | 'name': $name, | ||
114 | 'selector': $selector | ||
115 | ); | ||
116 | |||
117 | @return $selector $context; | ||
118 | } | ||
diff --git a/src/bem/_theme.scss b/src/bem/_theme.scss new file mode 100644 index 0000000..b4bcc76 --- /dev/null +++ b/src/bem/_theme.scss | |||
@@ -0,0 +1,61 @@ | |||
1 | //// | ||
2 | /// @group BEM | ||
3 | /// | ||
4 | /// @access public | ||
5 | //// | ||
6 | |||
7 | /// | ||
8 | /// Declare new rules for the current block for when this theme is active. | ||
9 | /// | ||
10 | /// @param {string} $name - First theme block name | ||
11 | /// @param {string} $names - More theme block names | ||
12 | /// | ||
13 | /// @content | ||
14 | /// | ||
15 | @mixin iro-bem-at-theme($name, $names...) { | ||
16 | $result: iro-bem-at-theme($name, $names...); | ||
17 | $selector: nth($result, 1); | ||
18 | $context: nth($result, 2); | ||
19 | |||
20 | @include iro-bem-validate( | ||
21 | 'at-theme', | ||
22 | (name: $name, names: $names), | ||
23 | $selector, | ||
24 | $context | ||
25 | ); | ||
26 | |||
27 | @include iro-context-push($iro-bem-context-id, $context...); | ||
28 | @at-root #{$selector} { | ||
29 | @content; | ||
30 | } | ||
31 | @include iro-context-pop($iro-bem-context-id); | ||
32 | } | ||
33 | |||
34 | /// | ||
35 | /// Declare new rules for the current block for when this theme is active. | ||
36 | /// Check the respective mixin documentation for more information. | ||
37 | /// | ||
38 | /// @return {list} A list with two items: 1. selector, 2. context or `null` | ||
39 | /// | ||
40 | /// @see {mixin} iro-bem-at-theme | ||
41 | /// | ||
42 | @function iro-bem-at-theme($name, $names...) { | ||
43 | $noop: iro-context-assert-stack-must-contain($iro-bem-context-id, 'block'); | ||
44 | |||
45 | $parent-context: iro-context-get($iro-bem-context-id, 'block'); | ||
46 | $parent-selector: map-get(nth($parent-context, 2), 'selector'); | ||
47 | |||
48 | @if not iro-selector-suffix-match(&, $parent-selector) { | ||
49 | @error 'An at-theme rule must be an immediate child of a block'; | ||
50 | } | ||
51 | |||
52 | $selector: iro-bem-theme-selector($name, $names...); | ||
53 | $selector: selector-nest($selector, &); | ||
54 | |||
55 | $context: 'at-theme', ( | ||
56 | 'name': join($name, $names), | ||
57 | 'selector': $selector | ||
58 | ); | ||
59 | |||
60 | @return $selector $context; | ||
61 | } | ||
diff --git a/src/bem/_validators.scss b/src/bem/_validators.scss new file mode 100644 index 0000000..eb09a60 --- /dev/null +++ b/src/bem/_validators.scss | |||
@@ -0,0 +1,176 @@ | |||
1 | //// | ||
2 | /// Validators are custom functions that will be called before a BEM entity is created. | ||
3 | /// They check if the current mixin usage is valid or not and thus they are a flexible way to | ||
4 | /// let you implement your own rules. | ||
5 | /// | ||
6 | /// Validator functions receive the following information: | ||
7 | /// - BEM entity type | ||
8 | /// - Arguments passed to the mixin | ||
9 | /// - The generated selector | ||
10 | /// - The generated context, if any | ||
11 | /// | ||
12 | /// Additionally, the context stack used by the BEM system can be examined. | ||
13 | /// | ||
14 | /// @group BEM | ||
15 | /// | ||
16 | /// @access public | ||
17 | //// | ||
18 | |||
19 | /// | ||
20 | /// A list of validator functions. | ||
21 | /// | ||
22 | /// @type list | ||
23 | /// | ||
24 | /// @access private | ||
25 | /// | ||
26 | $iro-bem-validators: (); | ||
27 | |||
28 | /// | ||
29 | /// Register one or multiple validator functions. | ||
30 | /// | ||
31 | /// A validator function is a function that accepts 4 arguments: | ||
32 | /// 1. BEM entity type (string) | ||
33 | /// 2. Arguments passed to the mixin (map) | ||
34 | /// 3. The generated selector (selector) | ||
35 | /// 4. The generated context (list, may be null) | ||
36 | /// | ||
37 | /// The function must return a list with two items: | ||
38 | /// 1. `true` if the mixin usage is valid, otherwise `false`, | ||
39 | /// 2. a string with a rejection reason (empty if the usage is valid). | ||
40 | /// | ||
41 | /// @param {string} $func-name - First function name. | ||
42 | /// @param {string} $func-names - Other function names. | ||
43 | /// | ||
44 | @mixin iro-bem-add-validator($func-name, $func-names...) { | ||
45 | $noop: iro-bem-add-validator($func-name, $func-names...); | ||
46 | } | ||
47 | |||
48 | /// | ||
49 | /// Register one or multiple validator functions. Check the respective mixin documentation for more information. | ||
50 | /// | ||
51 | /// @see {mixin} iro-bem-add-validator | ||
52 | /// | ||
53 | @function iro-bem-add-validator($func-name, $func-names...) { | ||
54 | @each $fn-name in join($func-name, $func-names) { | ||
55 | $fn: get-function($fn-name); | ||
56 | $iro-bem-validators: map-merge($iro-bem-validators, ($fn-name: $fn)) !global; | ||
57 | } | ||
58 | @return null; | ||
59 | } | ||
60 | |||
61 | /// | ||
62 | /// Unregister one or multiple validator functions. | ||
63 | /// | ||
64 | /// @param {string} $func-name - First function name. | ||
65 | /// @param {string} $func-names - Other function names. | ||
66 | /// | ||
67 | @mixin iro-bem-remove-validator($func-name, $func-names...) { | ||
68 | $noop: iro-bem-remove-validator($func-name, $func-names...); | ||
69 | } | ||
70 | |||
71 | /// | ||
72 | /// Unregister one or multiple validator functions. Check the respective mixin documentation for more information. | ||
73 | /// | ||
74 | /// @see {mixin} iro-bem-remove-validator | ||
75 | /// | ||
76 | @function iro-bem-remove-validator($func-name, $func-names...) { | ||
77 | $iro-bem-validators: map-remove($iro-bem-validators, $func-name, $func-names...) !global; | ||
78 | @return null; | ||
79 | } | ||
80 | |||
81 | /// | ||
82 | /// @access private | ||
83 | /// | ||
84 | @mixin iro-bem-validate($type, $args, $selector, $context) { | ||
85 | @each $id, $fn in $iro-bem-validators { | ||
86 | $result: call($fn, $type, $args, $selector, $context); | ||
87 | @if not nth($result, 1) { | ||
88 | @error 'A BEM validator rejected this mixin usage due to the following reason: #{nth($result, 2)}'; | ||
89 | } | ||
90 | } | ||
91 | } | ||
92 | |||
93 | // | ||
94 | // --------------------------------------------------------------------------------------------------------- | ||
95 | // Built-in validators | ||
96 | // --------------------------------------------------------------------------------------------------------- | ||
97 | // | ||
98 | |||
99 | /// | ||
100 | /// A validator that makes sure blocks are declared in the right order, determined by the | ||
101 | /// namespace used. | ||
102 | /// | ||
103 | @function iro-bem-validator--enforce-namespace-order($type, $args, $selector, $context) { | ||
104 | @if not global-variable-exists(iro-bem-namespace-order) { | ||
105 | $iro-bem-namespace-order: map-keys($iro-bem-namespaces) !global; | ||
106 | } | ||
107 | @if not global-variable-exists(iro-bem-cur-namespace-index) { | ||
108 | $iro-bem-cur-namespace-index: 1 !global; | ||
109 | } | ||
110 | |||
111 | @if $type == 'block' { | ||
112 | $block-type: map-get($args, 'type'); | ||
113 | |||
114 | @if $block-type != null { | ||
115 | $ns-index: index($iro-bem-namespace-order, $block-type); | ||
116 | |||
117 | @if $ns-index != null { | ||
118 | @if $ns-index < $iro-bem-cur-namespace-index { | ||
119 | @return false 'Namespace "#{$block-type}" comes before current namespace #{nth($iro-bem-namespace-order, $iro-bem-cur-namespace-index)}'; | ||
120 | } | ||
121 | |||
122 | $iro-bem-cur-namespace-index: $ns-index !global; | ||
123 | } | ||
124 | } | ||
125 | } | ||
126 | |||
127 | @return true ''; | ||
128 | } | ||
129 | |||
130 | /// | ||
131 | /// A validator that makes all BEM entities immutable. | ||
132 | /// | ||
133 | @function iro-bem-validator--immutable-entities($type, $args, $selector, $context) { | ||
134 | @if not global-variable-exists(iro-bem-generated-selectors) { | ||
135 | $iro-bem-generated-selectors: () !global; | ||
136 | } | ||
137 | |||
138 | $block-name: null; | ||
139 | $block-type: null; | ||
140 | $block-id: null; | ||
141 | |||
142 | @if $type == 'block' { | ||
143 | $block-name: map-get($args, 'name'); | ||
144 | $block-type: map-get($args, 'type'); | ||
145 | } @else { | ||
146 | $block-context: iro-context-get($iro-bem-context-id, 'block'); | ||
147 | $block-name: map-get(nth($block-context, 2), 'name'); | ||
148 | $block-type: map-get(nth($block-context, 2), 'type'); | ||
149 | } | ||
150 | |||
151 | @if $block-type != null { | ||
152 | $block-id: $block-name + '_' + $block-type; | ||
153 | } @else { | ||
154 | $block-id: $block-name; | ||
155 | } | ||
156 | |||
157 | @if $type == 'block' { | ||
158 | @if map-has-key($iro-bem-generated-selectors, $block-id) { | ||
159 | @return false 'Entity "#{$type}" with arguments [ #{iro-map-print($args)} ] was already defined.'; | ||
160 | } | ||
161 | |||
162 | $iro-bem-generated-selectors: map-merge($iro-bem-generated-selectors, ($block-id: ())) !global; | ||
163 | } @else { | ||
164 | $selectors: map-get($iro-bem-generated-selectors, $block-id); | ||
165 | |||
166 | @if index($selectors, $selector) { | ||
167 | @return false 'Entity "#{$type}" with arguments [ #{iro-map-print($args)} ] was already defined.'; | ||
168 | } | ||
169 | |||
170 | $selectors: append($selectors, $selector); | ||
171 | |||
172 | $iro-bem-generated-selectors: map-merge($iro-bem-generated-selectors, ($block-id: $selectors)) !global; | ||
173 | } | ||
174 | |||
175 | @return true ''; | ||
176 | } | ||
diff --git a/src/bem/_vars.scss b/src/bem/_vars.scss new file mode 100644 index 0000000..5942d4f --- /dev/null +++ b/src/bem/_vars.scss | |||
@@ -0,0 +1,108 @@ | |||
1 | //// | ||
2 | /// @group BEM | ||
3 | /// | ||
4 | /// @access public | ||
5 | //// | ||
6 | |||
7 | /// | ||
8 | /// Separating character sequence for elements. | ||
9 | /// | ||
10 | /// @type string | ||
11 | /// | ||
12 | $iro-bem-element-separator: '__' !default; | ||
13 | |||
14 | /// | ||
15 | /// Separating character sequence for modifiers. | ||
16 | /// | ||
17 | /// @type string | ||
18 | /// | ||
19 | $iro-bem-modifier-separator: '--' !default; | ||
20 | |||
21 | /// | ||
22 | /// Separating character sequence for BEMIT suffixes. | ||
23 | /// | ||
24 | /// @type string | ||
25 | /// | ||
26 | $iro-bem-suffix-separator: '\\@' !default; | ||
27 | |||
28 | /// | ||
29 | /// Prefixes for all BEMIT namespaces. | ||
30 | /// | ||
31 | /// @prop {string} utility ['u'] - Utility namespace | ||
32 | /// @prop {string} object ['o'] - Object namespace | ||
33 | /// @prop {string} component ['c'] - Component namespace | ||
34 | /// @prop {string} layout ['l'] - Layout namespace | ||
35 | /// @prop {string} scope ['s'] - Scope namespace | ||
36 | /// @prop {string} theme ['t'] - Theme namespace | ||
37 | /// @prop {string} js ['js'] - JS namespace | ||
38 | /// @prop {string} qa ['qa'] - QA namespace | ||
39 | /// @prop {string} hack ['_'] - Hack namespace | ||
40 | /// | ||
41 | /// @type map | ||
42 | /// | ||
43 | $iro-bem-namespaces: ( | ||
44 | object: 'o', | ||
45 | component: 'c', | ||
46 | layout: 'l', | ||
47 | scope: 's', | ||
48 | theme: 't', | ||
49 | utility: 'u', | ||
50 | js: 'js', | ||
51 | qa: 'qa', | ||
52 | hack: '_' | ||
53 | ) !default; | ||
54 | |||
55 | /// | ||
56 | /// A list of all generated blocks. | ||
57 | /// | ||
58 | /// @type list | ||
59 | /// | ||
60 | /// @access private | ||
61 | /// | ||
62 | $iro-bem-blocks: (); | ||
63 | |||
64 | /// | ||
65 | /// Maximum nesting depth of BEM mixins. The large default value means there is no effective limit. | ||
66 | /// | ||
67 | /// @type number | ||
68 | /// | ||
69 | $iro-bem-max-depth: 99 !default; | ||
70 | |||
71 | /// | ||
72 | /// Indicates how nested elements should be handled. | ||
73 | /// | ||
74 | /// 'allow' means elements will be nested, i.e. the result will be {e} {b}__element. | ||
75 | /// 'disallow' means an error will be emitted. | ||
76 | /// 'append' means the element name will be appended to the parent element, i.e. the result will be {e}__element. | ||
77 | /// Any other value is treated as 'allow'. | ||
78 | /// | ||
79 | /// @type string | ||
80 | /// | ||
81 | $iro-bem-element-nesting-policy: 'allow' !default; | ||
82 | |||
83 | /// | ||
84 | /// Context ID used for all BEM-related mixins. | ||
85 | /// | ||
86 | /// @type string | ||
87 | /// | ||
88 | $iro-bem-context-id: 'bem' !default; | ||
89 | |||
90 | /// | ||
91 | /// Debug mode. | ||
92 | /// | ||
93 | /// @type bool | ||
94 | /// | ||
95 | $iro-bem-debug: false !default; | ||
96 | |||
97 | /// | ||
98 | /// Colors assigned to namespaces. | ||
99 | /// | ||
100 | /// @type map | ||
101 | /// | ||
102 | $iro-bem-debug-colors: ( | ||
103 | object: #ffa500, | ||
104 | component: #00f, | ||
105 | layout: #ff0, | ||
106 | utility: #008000, | ||
107 | hack: #f00 | ||
108 | ) !default; | ||