diff options
Diffstat (limited to 'src/bem/_modifier.scss')
-rw-r--r-- | src/bem/_modifier.scss | 246 |
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 | } | ||