aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorFeuerfuchs <git@feuerfuchs.dev>2020-11-01 20:55:14 +0100
committerFeuerfuchs <git@feuerfuchs.dev>2020-11-01 20:55:14 +0100
commitd07f664450ddaaebb44127a4bd057763d13d3f82 (patch)
tree234cfd673ac527869a8dda4f32afbec48c87b512 /src
downloadiro-sass-d07f664450ddaaebb44127a4bd057763d13d3f82.tar.gz
iro-sass-d07f664450ddaaebb44127a4bd057763d13d3f82.tar.bz2
iro-sass-d07f664450ddaaebb44127a4bd057763d13d3f82.zip
Init
Diffstat (limited to 'src')
-rw-r--r--src/_bem.scss62
-rw-r--r--src/_contexts.scss315
-rw-r--r--src/_easing.scss483
-rw-r--r--src/_functions.scss328
-rw-r--r--src/_gradients.scss600
-rw-r--r--src/_harmony.scss94
-rw-r--r--src/_math.scss62
-rw-r--r--src/_props.scss281
-rw-r--r--src/_responsive.scss406
-rw-r--r--src/_vars.scss16
-rw-r--r--src/bem-shortcodes.scss349
-rw-r--r--src/bem/_block.scss392
-rw-r--r--src/bem/_debug.scss16
-rw-r--r--src/bem/_element.scss622
-rw-r--r--src/bem/_functions.scss26
-rw-r--r--src/bem/_modifier.scss246
-rw-r--r--src/bem/_multi.scss131
-rw-r--r--src/bem/_state.scss146
-rw-r--r--src/bem/_suffix.scss118
-rw-r--r--src/bem/_theme.scss61
-rw-r--r--src/bem/_validators.scss176
-rw-r--r--src/bem/_vars.scss108
-rw-r--r--src/harmony-shortcodes.scss35
-rw-r--r--src/main.scss10
-rw-r--r--src/prep.scss2
-rw-r--r--src/responsive-shortcodes.scss14
26 files changed, 5099 insertions, 0 deletions
diff --git a/src/_bem.scss b/src/_bem.scss
new file mode 100644
index 0000000..b6032ea
--- /dev/null
+++ b/src/_bem.scss
@@ -0,0 +1,62 @@
1////
2/// BEM.
3///
4/// BEM is a methodology for structuring websites and is mostly known for it's CSS naming convention.
5/// BEMIT is in extension of this methodology and allows you to give blocks a more fine-grained purpose
6/// than BEM alone would let you do.
7///
8/// Sass does support BEM quite well thanks to the ampersand (&) and the @at-root directive. However,
9/// there is no way to make sure users adhere to the BEM or BEMIT methodology.
10/// That's where the mixins in this file come into play: They automatically generate the right selectors
11/// and perform checks regarding the nesting order, nesting depth, and so on.
12///
13/// There are comments in the mixins explaining what selector is generated. The EBNF grammar is as follows:
14///
15/// (* Shorthands for block, element, modifier, suffix *)
16/// entity_shorthand = "b" "e" "m" "s" "t" ;
17///
18/// (* One or multiple BEMIT entities that were generated with an earlier mixin invocation *)
19/// existing_entities = "{" entity_shorthand { "," entity_shorthand } "}" ;
20///
21/// (* A BEM entity that doesn't depend on a parent entity *)
22/// generated_independent_entity = "block" ;
23///
24/// (* A BEM entity that is attached to a parent entity *)
25/// generated_attached_entity = existing_entities ( "__element" | "--modifier" | "@suffix" ) ;
26///
27/// (* A selector created by the user, such as "&:hover", "> a", and so on *)
28/// manual_selector_part = "[manual selector]" ;
29///
30/// (* A part of the selector that may or may not be in the generated result *)
31/// optional_selector_part = "(" ( existing_entities | manual_selector_part ) ")" ;
32///
33/// (* One part of the selector *)
34/// selector_part = existing_entities | manual_selector_part | optional_selector_part | generated_independent_entity | generated_attached_entity ;
35///
36/// (* How the left and right selector are related, i.e. space means right is a descendant of left, and dot means right specializes left *)
37/// selector_link = " " | "." ;
38///
39/// (* The whole selector *)
40/// selector = selector_part { ( selector_link ) selector_part } ;
41///
42/// @link https://en.bem.info/ Information about BEM
43/// @link https://csswizardry.com/2015/08/bemit-taking-the-bem-naming-convention-a-step-further/ Information about BEMIT
44///
45/// @group BEM
46///
47/// @access public
48////
49
50@import 'bem/vars';
51@import 'bem/functions';
52@import 'bem/validators';
53@import 'bem/block';
54@import 'bem/element';
55@import 'bem/modifier';
56@import 'bem/suffix';
57@import 'bem/state';
58@import 'bem/theme';
59@import 'bem/multi';
60@import 'bem/debug';
61
62@include iro-context-stack-create($iro-bem-context-id);
diff --git a/src/_contexts.scss b/src/_contexts.scss
new file mode 100644
index 0000000..556fde3
--- /dev/null
+++ b/src/_contexts.scss
@@ -0,0 +1,315 @@
1////
2/// Context handling.
3///
4/// Contexts allow you to pass data between mixins and let you enforce a certain nesting order.
5/// It's an essential part for the BEM-related mixins.
6///
7/// If you want to create a new context, the easiest pattern is to create a new mixin and wrap
8/// the @content between a pair of iro-context-push and iro-context-pop.
9/// From within the @content, you can access the context's data with iro-context-get.
10/// To make the compilation fail if a certain nesting order is violated, use
11/// iro-context-assert-stack-must-contain and iro-context-assert-stack-must-not-contain.
12///
13/// @group Contexts
14///
15/// @access public
16////
17
18///
19/// Map of all context stacks.
20///
21/// @type map
22///
23/// @access private
24///
25$iro-context-stacks: ();
26
27///
28/// Create a new context stack.
29///
30/// @param {string} $stack-id - ID of context stack
31///
32/// @throw If context stack already exists
33///
34@mixin iro-context-stack-create($stack-id) {
35 $noop: iro-context-stack-create($stack-id);
36}
37
38///
39/// Create a new context stack.
40///
41/// @param {string} $stack-id - ID of context stack
42///
43@function iro-context-stack-create($stack-id) {
44 @if map-has-key($iro-context-stacks, $stack-id) {
45 @error 'Context stack "#{inspect($stack-id)}" does not exist.';
46 }
47
48 $iro-context-stacks: map-merge($iro-context-stacks, ($stack-id: ())) !global;
49
50 @return null;
51}
52
53///
54/// Clear a context stack.
55///
56/// @param {string} $stack-id - ID of context stack
57///
58@mixin iro-context-stack-clear($stack-id) {
59 $noop: iro-context-stack-clear($stack-id);
60}
61
62///
63/// Clear a context stack.
64///
65/// @param {string} $stack-id - ID of context stack
66///
67@function iro-context-stack-clear($stack-id) {
68 @if not map-has-key($iro-context-stacks, $stack-id) {
69 @error 'Context stack "#{inspect($stack-id)}" does not exist.';
70 }
71
72 $context-stack: ();
73 $iro-context-stacks: map-merge($iro-context-stacks, ($stack-id: $context-stack)) !global;
74
75 @return null;
76}
77
78///
79/// Delete a context stack.
80///
81/// @param {string} $stack-id - ID of context stack
82///
83@mixin iro-context-stack-delete($stack-id) {
84 $noop: iro-context-stack-delete($stack-id);
85}
86
87///
88/// Delete a context stack.
89///
90/// @param {string} $stack-id - ID of context stack
91///
92@function iro-context-stack-delete($stack-id) {
93 @if not map-has-key($iro-context-stacks, $stack-id) {
94 @error 'Context stack "#{inspect($stack-id)}" does not exist.';
95 }
96
97 $iro-context-stacks: map-remove($iro-context-stacks, $stack-id) !global;
98
99 @return null;
100}
101
102///
103/// Push a new context to a context stack.
104///
105/// @param {string} $stack-id - ID of context stack to use
106/// @param {string} $id - ID of new context
107/// @param {any} $data [()] - Data that belongs to the context
108///
109@mixin iro-context-push($stack-id, $id, $data: ()) {
110 $noop: iro-context-push($stack-id, $id, $data);
111}
112
113///
114/// Push a new context to a context stack.
115///
116/// @param {string} $stack-id - ID of context stack to use
117/// @param {string} $id - ID of new context
118/// @param {any} $data [()] - Data that belongs to the context
119///
120/// @return {list} A list with two items: 1 = context id, 2 = context data
121///
122@function iro-context-push($stack-id, $id, $data: ()) {
123 @if not map-has-key($iro-context-stacks, $stack-id) {
124 @error 'Context stack "#{inspect($stack-id)}" does not exist.';
125 }
126
127 $context: $id $data;
128 $context-stack: map-get($iro-context-stacks, $stack-id);
129 $context-stack: append($context-stack, $context);
130 $iro-context-stacks: map-merge($iro-context-stacks, ($stack-id: $context-stack)) !global;
131
132 @return $context;
133}
134
135///
136/// Pop the latest context from a context stack.
137///
138/// @param {string} $stack-id - ID of context stack to use
139///
140/// @throw If context stack doesn't exist
141///
142@mixin iro-context-pop($stack-id) {
143 $noop: iro-context-pop($stack-id);
144}
145
146///
147/// Pop the latest context from a context stack.
148///
149/// @param {string} $stack-id - ID of context stack to use
150///
151/// @return {list} A list with two items: 1 = context id, 2 = context data
152///
153@function iro-context-pop($stack-id) {
154 @if not map-has-key($iro-context-stacks, $stack-id) {
155 @error 'Context stack "#{inspect($stack-id)}" does not exist.';
156 }
157
158 $context-stack: map-get($iro-context-stacks, $stack-id);
159
160 @if length($context-stack) == 0 {
161 @error 'Context stack "#{inspect($stack-id)}" is already empty.';
162 }
163
164 $popped-context: nth($context-stack, -1);
165
166 @if length($context-stack) == 1 {
167 $context-stack: ();
168 } @else {
169 $context-stack: iro-list-slice($context-stack, 1, length($context-stack) - 1);
170 }
171
172 $iro-context-stacks: map-merge($iro-context-stacks, ($stack-id: $context-stack)) !global;
173
174 @return $popped-context;
175}
176
177///
178/// Assert that a context stack must contain one of the given context IDs.
179///
180/// @param {string} $stack-id - ID of context stack to use
181/// @param {list} $context-ids - Context IDs
182/// @param {bool} $check-head-only [false] - If false, all items will be checked. If true, only the head will be checked.
183///
184/// @throw If assertion fails
185///
186@mixin iro-context-assert-stack-must-contain($stack-id, $context-ids, $check-head-only: false) {
187 @if not iro-context-stack-contains($stack-id, $context-ids, $check-head-only) {
188 @error 'Must be called inside of contexts "#{inspect($context-ids)}".';
189 }
190}
191
192///
193/// Assert that a context stack must not contain any of the given context IDs.
194///
195/// @param {string} $stack-id - ID of context stack to use
196/// @param {list} $context-ids - Context IDs
197/// @param {bool} $check-head-only [false] - If false, all items will be checked. If true, only the head will be checked.
198///
199/// @throw If assertion fails
200///
201@mixin iro-context-assert-stack-must-not-contain($stack-id, $context-ids, $check-head-only: false) {
202 @if iro-context-stack-contains($stack-id, $context-ids, $check-head-only) {
203 @error 'Must not be called inside of contexts "#{inspect($context-ids)}".';
204 }
205}
206
207///
208/// Check if a context stack contains one of the given context IDs.
209///
210/// @param {string} $stack-id - ID of context stack to use
211/// @param {list} $context-ids - Context IDs
212/// @param {bool} $check-head-only [false] - If false, all items will be checked. If true, only the head will be checked.
213///
214/// @return {bool} `true` if the context stack contains one of the context IDs, otherwise `false`
215///
216@function iro-context-stack-contains($stack-id, $context-ids, $check-head-only: false) {
217 @if not map-has-key($iro-context-stacks, $stack-id) {
218 @error 'Context stack "#{inspect($stack-id)}" does not exist.';
219 }
220
221 $context-stack: map-get($iro-context-stacks, $stack-id);
222
223 @if length($context-stack) == 0 {
224 @return false;
225 }
226
227 $end-idx: if($check-head-only, length($context-stack), 1);
228
229 @for $i from length($context-stack) through $end-idx {
230 $context: nth($context-stack, $i);
231
232 @each $chk-context in $context-ids {
233 @if nth($context, 1) == $chk-context {
234 @return true;
235 }
236 }
237 }
238
239 @return false;
240}
241
242///
243/// Assert that a context stack must contain a number of contexts smaller than $max-count.
244///
245/// @param {string} $stack-id - ID of context stack to use
246/// @param {number} $max-count - Maximum number ofg contexts in context stack
247///
248/// @throw If assertion fails
249///
250@mixin iro-context-assert-stack-count($stack-id, $max-count) {
251 @if iro-context-stack-count($stack-id) > $max-count {
252 @error 'Maximum context count "#{inspect($max-count)}" exceeded.';
253 }
254}
255
256///
257/// Get the number of contexts from a context stack.
258///
259/// @param {string} $stack-id - ID of context stack to use
260///
261/// @return {number} The number of contexts
262///
263@function iro-context-stack-count($stack-id) {
264 @if not map-has-key($iro-context-stacks, $stack-id) {
265 @error 'Context stack "#{inspect($stack-id)}" does not exist.';
266 }
267
268 $context-stack: map-get($iro-context-stacks, $stack-id);
269
270 @return length($context-stack);
271}
272
273///
274/// Get a specific context from the stack.
275///
276/// @param {string} $stack-id - ID of context stack to use
277/// @param {number | string | list} $type-or-level - If this is a number (!= 0), the nth context from the head will be returned. If it is a string, the first context with a matching ID will be returned. If it is a list, the first context that matches one of the IDs in the list will be returned.
278///
279/// @return {list} Null if no match was found, otherwise a list with two items: 1. context ID, 2. context data.
280///
281@function iro-context-get($stack-id, $type-or-level: null) {
282 @if not map-has-key($iro-context-stacks, $stack-id) {
283 @error 'Context stack "#{inspect($stack-id)}" does not exist.';
284 }
285
286 $context-stack: map-get($iro-context-stacks, $stack-id);
287
288 @if length($context-stack) == 0 {
289 @return null;
290 }
291
292 @if type-of($type-or-level) == number {
293 $context: nth($context-stack, -$type-or-level);
294
295 @return $context;
296 } @else {
297 @for $i from -1 through -(length($context-stack)) {
298 $context: nth($context-stack, $i);
299
300 @if type-of($type-or-level) == list {
301 @for $j from 1 through length($type-or-level) {
302 $ctx: nth($type-or-level, $j);
303
304 @if nth($context, 1) == $ctx {
305 @return $context;
306 }
307 }
308 } @else if nth($context, 1) == $type-or-level {
309 @return $context;
310 }
311 }
312 }
313
314 @return null;
315}
diff --git a/src/_easing.scss b/src/_easing.scss
new file mode 100644
index 0000000..c41635b
--- /dev/null
+++ b/src/_easing.scss
@@ -0,0 +1,483 @@
1////
2/// Easing.
3///
4/// A collection of easing functions which are commonly used for animations.
5/// This code is based on https://github.com/gre/bezier-easing.
6///
7/// @group Easing
8///
9/// @access public
10////
11
12///
13/// @access private
14///
15$iro-cubic-bezier-sample-pool: ();
16
17///
18/// Sample pool size for cubic bezier calculations.
19///
20$iro-cubic-bezier-sample-pool-size: 10 !default;
21
22///
23/// Minimum slope required to use the Newton-Raphson method for cubic bezier calculations.
24///
25$iro-cubic-bezier-newton-min-slope: 0.001 !default;
26
27///
28/// Number of iterations of the Newton-Raphson method.
29///
30$iro-cubic-bezier-newton-iters: 4 !default;
31
32///
33/// Precision of the subdivision method for cubic bezier calculations.
34///
35$iro-cubic-bezier-subdiv-precision: 0.0000001 !default;
36
37///
38/// Maximum iterations of the subdivision method for cubic bezier calculations.
39///
40$iro-cubic-bezier-subdiv-max-iters: 10 !default;
41
42///
43/// A cubic bezier function identical to the CSS cubic-bezier function.
44///
45/// @param {number} $x1 - X of first point
46/// @param {number} $y1 - Y of first point
47/// @param {number} $x2 - X of second point
48/// @param {number} $y2 - Y of second point
49/// @param {number} $x - Progress between 0 and 1 inclusive
50///
51/// @return {number}
52///
53@function iro-cubic-bezier($x1, $y1, $x2, $y2, $x) {
54 //
55 // Cover simple cases
56 //
57
58 @if ($x1 == $y1) and ($x2 == $y2) {
59 @return $x;
60 }
61 @if $x == 0 {
62 @return 0;
63 }
64 @if $x == 1 {
65 @return 1;
66 }
67
68 //
69 // Generate samples
70 //
71
72 $sample-pool-key: $x1 + '_' + $x2;
73
74 @if not map-has-key($iro-cubic-bezier-sample-pool, $sample-pool-key) {
75 $samples: ();
76
77 @for $i from 0 through $iro-cubic-bezier-sample-pool-size {
78 $samples: append($samples, iro-cubic-bezier-func($x1, $x2, $i / $iro-cubic-bezier-sample-pool-size));
79 }
80
81 $iro-cubic-bezier-sample-pool: map-merge($iro-cubic-bezier-sample-pool, ($sample-pool-key: $samples)) !global;
82 }
83
84 //
85 // Calculate cubic bezier
86 //
87
88 @return iro-cubic-bezier-func($y1, $y2, iro-cubic-bezier-t-for-x($x1, $x2, $x));
89}
90
91///
92/// @access private
93///
94@function iro-cubic-bezier-func-a($p1, $p2) {
95 @return 1 - 3 * $p2 + 3 * $p1;
96}
97
98///
99/// @access private
100///
101@function iro-cubic-bezier-func-b($p1, $p2) {
102 @return 3 * $p2 - 6 * $p1;
103}
104
105///
106/// @access private
107///
108@function iro-cubic-bezier-func-c($p1) {
109 @return 3 * $p1;
110}
111
112///
113/// One-dimensional cubic bezier function.
114///
115/// @access private
116///
117@function iro-cubic-bezier-func($p1, $p2, $t) {
118 @return ((iro-cubic-bezier-func-a($p1, $p2) * $t + iro-cubic-bezier-func-b($p1, $p2)) * $t + iro-cubic-bezier-func-c($p1)) * $t;
119}
120
121///
122/// Derivative of the one-dimensional cubic bezier function.
123///
124/// @access private
125///
126@function iro-cubic-bezier-func-slope($p1, $p2, $t) {
127 @return 3 * iro-cubic-bezier-func-a($p1, $p2) * $t * $t + 2 * iro-cubic-bezier-func-b($p1, $p2) * $t + iro-cubic-bezier-func-c($p1);
128}
129
130///
131/// Newton-Raphson method to calculate the t parameter for a given x parameter.
132///
133/// @access private
134///
135@function iro-cubic-bezier-newton-raphson($x1, $x2, $x, $t) {
136 @for $i from 1 through $iro-cubic-bezier-newton-iters {
137 $cur-slope: iro-cubic-bezier-func-slope($x1, $x2, $t);
138
139 @if $cur-slope == 0 {
140 @return $t;
141 }
142
143 $cur-x: iro-cubic-bezier-func($x1, $x2, $t) - $x;
144 $t: $t - $cur-x / $cur-slope;
145 }
146
147 @return $t;
148}
149
150///
151/// Subdivision method to calculate the t parameter for a given x parameter.
152///
153/// @access private
154///
155@function iro-cubic-bezier-binary-subdivide($x1, $x2, $x, $a, $b) {
156 $cur-x: 0;
157 $cur-t: 0;
158 $i: 0;
159
160 @while $i < $iro-cubic-bezier-subdiv-max-iters {
161 $cur-t: $a + ($b - $a) / 2;
162 $cur-x: iro-cubic-bezier-func($x1, $x2, $cur-t) - $x;
163
164 @if $cur-x > 0 {
165 $b: $cur-t;
166 } @else {
167 $a: $cur-t;
168 }
169
170 @if abs($cur-x) < $iro-cubic-bezier-subdiv-precision {
171 @return $cur-t;
172 }
173 }
174
175 @return $cur-t;
176}
177
178///
179/// Calculate the t parameter for a given x parameter.
180///
181/// @access private
182///
183@function iro-cubic-bezier-t-for-x($x1, $x2, $x) {
184 $sample-pool-key: $x1 + '_' + $x2;
185 $samples: map-get($iro-cubic-bezier-sample-pool, $sample-pool-key);
186
187 $intv-start: 0;
188 $cur-sample: 1;
189 $last-sample: $iro-cubic-bezier-sample-pool-size;
190
191 @while ($cur-sample != $last-sample) and (nth($samples, $cur-sample) <= $x) {
192 $intv-start: $intv-start + (1 / $iro-cubic-bezier-sample-pool-size);
193 $cur-sample: $cur-sample + 1;
194 }
195 $cur-sample: $cur-sample - 1;
196
197 $dist: ($x - nth($samples, $cur-sample)) / (nth($samples, $cur-sample + 1) - nth($samples, $cur-sample));
198 $guess-t: $intv-start + $dist / $iro-cubic-bezier-sample-pool-size;
199
200 $init-slope: iro-cubic-bezier-func-slope($x1, $x2, $guess-t);
201 @if $init-slope >= $iro-cubic-bezier-newton-min-slope {
202 @return iro-cubic-bezier-newton-raphson($x1, $x2, $x, $guess-t);
203 } @else if $init-slope == 0 {
204 @return $guess-t;
205 } @else {
206 @return iro-cubic-bezier-binary-subdivide($x1, $x2, $x, $intv-start, $intv-start + 1 / $iro-cubic-bezier-sample-pool-size);
207 }
208}
209
210///
211/// Sinusoidal easing function (in direction).
212///
213/// @param {number} $x - Progress between 0 and 1 inclusive
214///
215/// @return {number}
216///
217@function iro-ease($x) {
218 @return iro-cubic-bezier(0.25, 0.1, 0.25, 1, $x);
219}
220
221///
222/// Sinusoidal easing function (in direction).
223///
224/// @param {number} $x - Progress between 0 and 1 inclusive
225///
226/// @return {number}
227///
228@function iro-ease-in($x) {
229 @return iro-cubic-bezier(0.42, 0, 1, 1, $x);
230}
231
232///
233/// Sinusoidal easing function (out direction).
234///
235/// @param {number} $x - Progress between 0 and 1 inclusive
236///
237/// @return {number}
238///
239@function iro-ease-out($x) {
240 @return iro-cubic-bezier(0, 0, 0.58, 1, $x);
241}
242
243///
244/// Sinusoidal easing function (in-out direction).
245///
246/// @param {number} $x - Progress between 0 and 1 inclusive
247///
248/// @return {number}
249///
250@function iro-ease-in-out($x) {
251 @return iro-cubic-bezier(0.42, 0, 0.58, 1, $x);
252}
253
254///
255/// Sinusoidal easing function (in direction).
256///
257/// @param {number} $x - Progress between 0 and 1 inclusive
258///
259/// @return {number}
260///
261@function iro-ease-in-sine($x) {
262 @return iro-cubic-bezier(0.47, 0, 0.745, 0.715, $x);
263}
264
265///
266/// Sinusoidal easing function (out direction).
267///
268/// @param {number} $x - Progress between 0 and 1 inclusive
269///
270/// @return {number}
271///
272@function iro-ease-out-sine($x) {
273 @return iro-cubic-bezier(0.39, 0.575, 0.565, 1, $x);
274}
275
276///
277/// Sinusoidal easing function (in-out direction).
278///
279/// @param {number} $x - Progress between 0 and 1 inclusive
280///
281/// @return {number}
282///
283@function iro-ease-in-out-sine($x) {
284 @return iro-cubic-bezier(0.445, 0.05, 0.55, 0.95, $x);
285}
286
287///
288/// Quadratic easing function (in direction).
289///
290/// @param {number} $x - Progress between 0 and 1 inclusive
291///
292/// @return {number}
293///
294@function iro-ease-in-quad($x) {
295 @return iro-cubic-bezier(0.55, 0.085, 0.68, 0.53, $x);
296}
297
298///
299/// Quadratic easing function (out direction).
300///
301/// @param {number} $x - Progress between 0 and 1 inclusive
302///
303/// @return {number}
304///
305@function iro-ease-out-quad($x) {
306 @return iro-cubic-bezier(0.25, 0.46, 0.45, 0.94, $x);
307}
308
309///
310/// Quadratic easing function (in-out direction).
311///
312/// @param {number} $x - Progress between 0 and 1 inclusive
313///
314/// @return {number}
315///
316@function iro-ease-in-out-quad($x) {
317 @return iro-cubic-bezier(0.455, 0.03, 0.515, 0.955, $x);
318}
319
320///
321/// Cubic easing function (in direction).
322///
323/// @param {number} $x - Progress between 0 and 1 inclusive
324///
325/// @return {number}
326///
327@function iro-ease-in-cubic($x) {
328 @return iro-cubic-bezier(0.55, 0.055, 0.675, 0.19, $x);
329}
330
331///
332/// Cubic easing function (out direction).
333///
334/// @param {number} $x - Progress between 0 and 1 inclusive
335///
336/// @return {number}
337///
338@function iro-ease-out-cubic($x) {
339 @return iro-cubic-bezier(0.215, 0.61, 0.355, 1, $x);
340}
341
342///
343/// Cubic easing function (in-out direction).
344///
345/// @param {number} $x - Progress between 0 and 1 inclusive
346///
347/// @return {number}
348///
349@function iro-ease-in-out-cubic($x) {
350 @return iro-cubic-bezier(0.645, 0.045, 0.355, 1, $x);
351}
352
353///
354/// Quart easing function (in direction).
355///
356/// @param {number} $x - Progress between 0 and 1 inclusive
357///
358/// @return {number}
359///
360@function iro-ease-in-quart($x) {
361 @return iro-cubic-bezier(0.895, 0.03, 0.685, 0.22, $x);
362}
363
364///
365/// Quart easing function (out direction).
366///
367/// @param {number} $x - Progress between 0 and 1 inclusive
368///
369/// @return {number}
370///
371@function iro-ease-out-quart($x) {
372 @return iro-cubic-bezier(0.165, 0.84, 0.44, 1, $x);
373}
374
375///
376/// Quart easing function (in-out direction).
377///
378/// @param {number} $x - Progress between 0 and 1 inclusive
379///
380/// @return {number}
381///
382@function iro-ease-in-out-quart($x) {
383 @return iro-cubic-bezier(0.77, 0, 0.175, 1, $x);
384}
385
386///
387/// Quint easing function (in direction).
388///
389/// @param {number} $x - Progress between 0 and 1 inclusive
390///
391/// @return {number}
392///
393@function iro-ease-in-quint($x) {
394 @return iro-cubic-bezier(0.755, 0.05, 0.855, 0.06, $x);
395}
396
397///
398/// Quint easing function (out direction).
399///
400/// @param {number} $x - Progress between 0 and 1 inclusive
401///
402/// @return {number}
403///
404@function iro-ease-out-quint($x) {
405 @return iro-cubic-bezier(0.23, 1, 0.32, 1, $x);
406}
407
408///
409/// Quint easing function (in-out direction).
410///
411/// @param {number} $x - Progress between 0 and 1 inclusive
412///
413/// @return {number}
414///
415@function iro-ease-in-out-quint($x) {
416 @return iro-cubic-bezier(0.86, 0, 0.07, 1, $x);
417}
418
419///
420/// Exponential easing function (in direction).
421///
422/// @param {number} $x - Progress between 0 and 1 inclusive
423///
424/// @return {number}
425///
426@function iro-ease-in-expo($x) {
427 @return iro-cubic-bezier(0.95, 0.05, 0.795, 0.035, $x);
428}
429
430///
431/// Exponential easing function (out direction).
432///
433/// @param {number} $x - Progress between 0 and 1 inclusive
434///
435/// @return {number}
436///
437@function iro-ease-out-expo($x) {
438 @return iro-cubic-bezier(0.19, 1, 0.22, 1, $x);
439}
440
441///
442/// Exponential easing function (in-out direction).
443///
444/// @param {number} $x - Progress between 0 and 1 inclusive
445///
446/// @return {number}
447///
448@function iro-ease-in-out-expo($x) {
449 @return iro-cubic-bezier(1, 0, 0, 1, $x);
450}
451
452///
453/// Circular easing function (in direction).
454///
455/// @param {number} $x - Progress between 0 and 1 inclusive
456///
457/// @return {number}
458///
459@function iro-ease-in-circ($x) {
460 @return iro-cubic-bezier(0.6, 0.04, 0.98, 0.335, $x);
461}
462
463///
464/// Circular easing function (out direction).
465///
466/// @param {number} $x - Progress between 0 and 1 inclusive
467///
468/// @return {number}
469///
470@function iro-ease-out-circ($x) {
471 @return iro-cubic-bezier(0.075, 0.82, 0.165, 1, $x);
472}
473
474///
475/// Circular easing function (in-out direction).
476///
477/// @param {number} $x - Progress between 0 and 1 inclusive
478///
479/// @return {number}
480///
481@function iro-ease-in-out-circ($x) {
482 @return iro-cubic-bezier(0.785, 0.135, 0.15, 0.86, $x);
483}
diff --git a/src/_functions.scss b/src/_functions.scss
new file mode 100644
index 0000000..2f34dc4
--- /dev/null
+++ b/src/_functions.scss
@@ -0,0 +1,328 @@
1////
2/// Various functions.
3///
4/// This file contains various and mostly unrelated functions. Some of which
5/// are used in this framework, while others are just there and may be used.
6///
7/// @group Functions
8///
9/// @access public
10////
11
12///
13/// Replace a substring with a new string.
14///
15/// @param {string} $string - String to search
16/// @param {string} $search - Substring that gets replaced
17/// @param {string} $replace - String that replaces $search
18///
19/// @return {string} A string with all instances of $search replaced with $replace
20///
21@function iro-str-replace($string, $search, $replace) {
22 $index: str-index($string, $search);
23
24 @if $index {
25 @return str-slice($string, 1, $index - 1) + $replace + iro-str-replace(str-slice($string, $index + str-length($search)), $search, $replace);
26 }
27
28 @return $string;
29}
30
31///
32/// Concatenate all items from $list.
33///
34/// @param {list} $list
35/// @param {number} $glue - Delimiter
36///
37/// @return {string}
38///
39@function iro-str-implode($list, $glue: '') {
40 $result: '';
41
42 @each $item in $list {
43 $result: $result + if(length($item) > 1, iro-str-implode($item, $glue), $item);
44
45 @if $item != nth($list, length($list)) {
46 $result: $result + $glue;
47 }
48 }
49
50 @return $result;
51}
52
53///
54/// Extract a subset from the given list.
55///
56/// @param {list} $list
57/// @param {number} $start [1] - Indices before this value will be discarded
58/// @param {number} $end [length($list)] - Indices starting after this value will be discarded
59///
60/// @return {list} A slice of the list
61///
62@function iro-list-slice($list, $start: 1, $end: length($list)) {
63 $result: ();
64
65 @for $i from $start through $end {
66 @if $i != 0 {
67 $result: append($result, nth($list, $i), list-separator($list));
68 }
69 }
70
71 @return $result;
72}
73
74///
75/// Add a new item to the beginning of a list.
76///
77/// @param {list} $list
78/// @param {number} $value
79///
80/// @return {list} A list with $value at the beginning, followed by the other items
81///
82@function iro-list-prepend($list, $value) {
83 $result: append((), $value, list-separator($list));
84
85 @if length($list) > 0 {
86 @for $i from 1 through length($list) {
87 $result: append($result, nth($list, $i), list-separator($list));
88 }
89 }
90
91 @return $result;
92}
93
94///
95/// Sort numeric items in a list.
96///
97/// The implementation is based on the algorithm on the German Wikipedia article
98/// about quicksort: https://de.wikipedia.org/wiki/Quicksort#Pseudocode
99///
100/// @param {list} $l
101///
102/// @return {list} Sorted list
103///
104@function iro-quicksort($l, $left: 1, $right: length($l)) {
105 @if $left < $right {
106 $pvr: iro-quicksort-partition($l, $left, $right);
107 $pivot: nth($pvr, 1);
108 $l: nth($pvr, 2);
109 $l: iro-quicksort($l, $left, $pivot);
110 $l: iro-quicksort($l, $pivot + 1, $right);
111 }
112
113 @return $l;
114}
115
116///
117/// @access private
118///
119@function iro-quicksort-partition($l, $left, $right) {
120 $start: true;
121 $i: $left;
122 $j: $right - 1;
123 $pivot: nth($l, $right);
124
125 @while ($i < $j) or $start {
126 @while (nth($l, $i) < $pivot) and ($i < $right - 1) {
127 $i: $i + 1;
128 }
129
130 @while (nth($l, $j)>= $pivot) and ($j > $left) {
131 $j: $j - 1;
132 }
133
134 @if $i < $j {
135 $i-val: nth($l, $i);
136 $l: set-nth($l, $i, nth($l, $j));
137 $l: set-nth($l, $j, $i-val);
138 }
139
140 $start: false;
141 }
142
143 @if nth($l, $i) > $pivot {
144 $i-val: nth($l, $i);
145 $l: set-nth($l, $i, nth($l, $right));
146 $l: set-nth($l, $right, $i-val);
147 }
148
149 @return $i $l;
150}
151
152///
153/// Try to get the value for the given key from the given map. If it doesn't contain that key,
154/// return the provided default value instead.
155///
156/// @param {map} $map
157/// @param {string} $key
158/// @param {any} $default
159///
160/// @return {any} Either the value assigned to $key or $default
161///
162@function iro-map-get-default($map, $key, $default) {
163 @return if(map-has-key($map, $key), map-get($map, $key), $default);
164}
165
166///
167/// Get the value for a map within a map (or deeper).
168///
169/// @param {map} $map
170/// @param {string | list} $key
171/// @param {any} $default [null]
172///
173/// @return {any} Either the value assigned to $key or $default
174///
175@function iro-map-get-deep($map, $key, $default: null) {
176 $value: null;
177
178 @if type-of($key) == list {
179 $value: $map;
180
181 @each $k in $key {
182 $value: map-get($value, $k);
183
184 @if $value == null {
185 @return $default;
186 }
187 }
188 } @else {
189 $value: map-get($map, $key);
190
191 @if $value == null {
192 @return $default;
193 }
194 }
195
196 @return $value;
197}
198
199///
200/// Merge two maps recursively.
201///
202/// @param {map} $map1
203/// @param {map} $map2
204///
205/// @return {map} The result of a recursive merge of $map1 and $map2
206///
207@function iro-map-merge-recursive($map1, $map2) {
208 @if type-of($map1) != map or type-of($map2) != map {
209 @error 'Two maps expected.';
210 }
211
212 $result: $map1;
213
214 @each $key, $value in $map2 {
215 @if type-of(map-get($result, $key)) == map and type-of($value) == map {
216 $result: map-merge($result, ($key: iro-map-merge-recursive(map-get($result, $key), $value)));
217 } @else {
218 $result: map-merge($result, ($key: $value));
219 }
220 }
221
222 @return $result;
223}
224
225///
226/// Get the contents of a map as a user-friendly string representation.
227///
228/// @param {map} $map
229///
230/// @return {string}
231///
232@function iro-map-print($map) {
233 $output: '';
234
235 @each $key, $value in $map {
236 $value-str: '';
237
238 @if type-of($value) == map {
239 $value-str: '[ ' + iro-map-print($value) + ' ]';
240 } @else if type-of($value) == list {
241 $value-str: '[ ' + iro-str-implode($value, ', ') + ' ]';
242 } @else if type-of($value) == string {
243 $value-str: '\'' + $value + '\'';
244 } @else {
245 $value-str: $value;
246 }
247
248 @if $output == '' {
249 $output: $key + ': ' + $value-str;
250 } @else {
251 $output: $output + ', ' + $key + ': ' + $value-str;
252 }
253 }
254
255 @return $output;
256}
257
258///
259/// Check if the given selector ends with one of the provided suffixes.
260///
261/// @param {selector} $selector
262/// @param {selector} $suffixes
263///
264/// @return {bool} `true` if the selector matches at least one suffix, otherwise `false`.
265///
266@function iro-selector-suffix-match($selector, $suffixes) {
267 $match: true;
268
269 @each $sel in $selector {
270 @if $match {
271 $sel-match: false;
272
273 @each $suffix in $suffixes {
274 @if not $sel-match {
275 $suf-match: true;
276
277 @for $i from 1 through length($suffix) {
278 @if $suf-match and (nth($sel, -$i) != nth($suffix, -$i)) {
279 $suf-match: false;
280 }
281 }
282
283 @if $suf-match {
284 $sel-match: true;
285 }
286 }
287 }
288
289 @if not $sel-match {
290 $match: false;
291 }
292 }
293 }
294
295 @return $match;
296}
297
298///
299/// Remove the unit from any variable.
300///
301/// @param {any} $n
302///
303/// @return {number} Unit-less variable
304///
305@function iro-strip-unit($n) {
306 @return $n / ($n * 0 + 1);
307}
308
309///
310/// Convert a pixel value to a rem value.
311///
312/// @param {number} $size - Pixel value to convert
313/// @param {number} $base [$iro-root-size] - Reference base font size used for conversion
314///
315/// @return {number} Pixel value converted to rem
316///
317@function iro-px-to-rem($size, $base: $iro-root-size) {
318 @return $size / $base * 1rem;
319}
320
321///
322/// A mixin with the sole purpose of letting you use temporary variables without polluting the global namespace.
323///
324/// @content
325///
326@mixin iro-execute {
327 @content;
328}
diff --git a/src/_gradients.scss b/src/_gradients.scss
new file mode 100644
index 0000000..7c52d63
--- /dev/null
+++ b/src/_gradients.scss
@@ -0,0 +1,600 @@
1////
2/// Smoother background gradients.
3///
4/// The default background gradients produced by any browser have a quite harsh transition between
5/// colors. This is especially apparent if you, for example, use a strong fade-out gradient to make
6/// text in front of a background more readable.
7///
8/// The function in this file generates smoother gradients by using easing functions of the user's
9/// choice.
10/// It's essentially a more flexible alternative to the PostCSS plugin "PostCSS Easing Gradients":
11/// https://github.com/larsenwork/postcss-easing-gradients
12///
13/// @group Background gradients
14///
15/// @access public
16////
17
18///
19/// Number of intermediate color stops generated to achieve easing.
20/// A higher value results in better quality, but also much more generated code.
21///
22/// @type number
23///
24$iro-easing-gradient-steps: 10 !default;
25
26///
27/// Generate a new easing background gradient.
28/// This function is intended to be as similar as possible to the newly proposed syntax for
29/// linear-gradient and radial-gradient which includes easing hints.
30///
31/// @param {string} $type - Either 'linear' or 'radial', which means the gradient will be either a linear-gradient or a radial-gradient.
32/// @param {string} $dir - The direction of the gradient. Depending on $type, this value must be a valid direction for linear-gradient or radial-gradient.
33/// @param {color | list} $stop - A color stop as used for linear-gradient or radial-gradient.
34/// @param {arglist} $stops - More color stops as used for linear-gradient or radial-gradient. Between two color stops, you may also define an easing hint such as `ease-in-out`, `cubic-bezier 0.42 0 0.58 1`, `steps 3 jump-end`, and so on.
35///
36/// @return {string} A linear-gradient or radial-gradient with an alternative transitioning behavior.
37///
38/// @throw If $type is invalid
39///
40/// @link https://github.com/w3c/csswg-drafts/issues/1332 The new CSSWG proposal
41///
42/// @example scss - A smoother linear gradient
43/// .background {
44/// background-image: iro-easing-gradient(
45/// linear,
46/// to top,
47/// #000,
48/// in-out-sine,
49/// transparent
50/// );
51/// }
52///
53/// // Generates:
54///
55/// .background {
56/// background-image: linear-gradient(
57/// to top,
58/// black 0%,
59/// rgba(0, 0, 0, 0.975528) 10%,
60/// rgba(0, 0, 0, 0.904508) 20%,
61/// rgba(0, 0, 0, 0.793893) 30%,
62/// rgba(0, 0, 0, 0.654508) 40%,
63/// rgba(0, 0, 0, 0.5) 50%,
64/// rgba(0, 0, 0, 0.345492) 60%,
65/// rgba(0, 0, 0, 0.206107) 70%,
66/// rgba(0, 0, 0, 0.0954915) 80%,
67/// rgba(0, 0, 0, 0.0244717) 90%,
68/// rgba(0, 0, 0, 3.78257e-11) 100%
69/// );
70/// }
71///
72/// @example scss - A smoother radial gradient
73/// .background {
74/// background-image: iro-easing-gradient(
75/// radial,
76/// 50em 16em at 0 0,
77/// #000,
78/// in-out-sine,
79/// transparent
80/// );
81/// }
82///
83/// // Generates:
84///
85/// .background {
86/// background-image: radial-gradient(
87/// 50em 16em at 0 0,
88/// black 0%,
89/// rgba(0, 0, 0, 0.975528) 10%,
90/// rgba(0, 0, 0, 0.904508) 20%,
91/// rgba(0, 0, 0, 0.793893) 30%,
92/// rgba(0, 0, 0, 0.654508) 40%,
93/// rgba(0, 0, 0, 0.5) 50%,
94/// rgba(0, 0, 0, 0.345492) 60%,
95/// rgba(0, 0, 0, 0.206107) 70%,
96/// rgba(0, 0, 0, 0.0954915) 80%,
97/// rgba(0, 0, 0, 0.0244717) 90%,
98/// rgba(0, 0, 0, 3.78257e-11) 100%
99/// );
100/// }
101///
102/// @example scss - A smoother linear gradient with complex color positions
103/// .background {
104/// background-image: iro-easing-gradient(
105/// linear,
106/// to top,
107/// #000 20%,
108/// in-out-sine,
109/// transparent calc(20% + 25em)
110/// );
111/// }
112///
113/// // Generates:
114///
115/// .background {
116/// background-image: linear-gradient(
117/// to top,
118/// black 20%,
119/// rgba(0, 0, 0, 0.975528) calc(20% + (20% + 25em - 20%) * 0.1),
120/// rgba(0, 0, 0, 0.904508) calc(20% + (20% + 25em - 20%) * 0.2),
121/// rgba(0, 0, 0, 0.793893) calc(20% + (20% + 25em - 20%) * 0.3),
122/// rgba(0, 0, 0, 0.654508) calc(20% + (20% + 25em - 20%) * 0.4),
123/// rgba(0, 0, 0, 0.5) calc(20% + (20% + 25em - 20%) * 0.5),
124/// rgba(0, 0, 0, 0.345492) calc(20% + (20% + 25em - 20%) * 0.6),
125/// rgba(0, 0, 0, 0.206107) calc(20% + (20% + 25em - 20%) * 0.7),
126/// rgba(0, 0, 0, 0.0954915) calc(20% + (20% + 25em - 20%) * 0.8),
127/// rgba(0, 0, 0, 0.0244717) calc(20% + (20% + 25em - 20%) * 0.9),
128/// transparent calc(20% + 25em))
129/// );
130/// }
131///
132@function iro-easing-gradient($type, $dir, $stop, $stops...) {
133 $pos-template: null;
134 $stops: iro-list-prepend($stops, $stop);
135
136 $last-positioned-stop: 1;
137 $generated-stops: ();
138
139 //
140 // Generate gradient
141 //
142
143 @for $i from 1 through length($stops) {
144 $stop: nth($stops, $i);
145
146 @if $i == 1 {
147 @if not iro-easing-gradient-is-color-stop($stop) {
148 @error 'The first color stop argument must be a color stop.';
149 }
150
151 @if type-of($stop) == color {
152 //
153 // The first color stop is unpositioned. The default position for the first
154 // color stop is 0, which is explicitly added for easier calculations.
155 //
156
157 $stop: $stop 0;
158 $stops: set-nth($stops, $i, $stop);
159 }
160
161 $generated-stops: append($generated-stops, iro-str-implode($stop, ' '));
162 } @else if iro-easing-gradient-is-positioned-color-stop($stop) or ($i == length($stops)) {
163 @if not iro-easing-gradient-is-color-stop($stop) {
164 @error 'The last color stop argument must be a color stop.';
165 }
166
167 //
168 // Either the current stops list item is a positioned color stop, or the end of
169 // the stops list has been reached.
170 //
171
172 @if (type-of($stop) == color) and ($i == length($stops)) {
173 //
174 // The current stop is an unpositioned color stop, which means this is the end
175 // of the stops list. The default position for the last color stop is 100%, which
176 // is explicitly added for easier calculations.
177 //
178
179 $stop: $stop 100%;
180 $stops: set-nth($stops, $i, $stop);
181 }
182
183 //
184 // Now the current color stop is guaranteed to be a positioned color stop.
185 //
186
187 @if $i > $last-positioned-stop + 1 {
188 //
189 // There is at least one stops list item (unpositioned color stop or easing function)
190 // between the last positioned color stop and the current stops list item. Interpolate
191 // the positions of all stops list items that are color stops.
192 //
193
194 $interpolated-stops: iro-easing-gradient-interpolate-stop-positions(
195 nth($stops, $last-positioned-stop),
196 iro-list-slice($stops, $last-positioned-stop + 1, $i - 1),
197 $stop
198 );
199
200 $new-stops: join(
201 iro-list-slice($stops, 1, $last-positioned-stop),
202 $interpolated-stops
203 );
204 $new-stops: join(
205 $new-stops,
206 iro-list-slice($stops, $i)
207 );
208 $stops: $new-stops;
209 }
210
211 //
212 // Now all color stops between this one and the last positioned one have
213 // interpolated positions.
214 // Next task is to perform an easing transition between all color stops that
215 // have an easing function specified. The rest can be left alone since the
216 // browser will automatically apply a linear transition between them.
217 //
218
219 $j: $last-positioned-stop + 1;
220 @while $j <= $i {
221 $easing: null;
222 $prev-stop: nth($stops, $j - 1);
223 $next-stop: nth($stops, $j);
224
225 @if not iro-easing-gradient-is-color-stop($next-stop) {
226 $j: $j + 1;
227
228 $easing: $next-stop;
229 $next-stop: nth($stops, $j);
230
231 @if not iro-easing-gradient-is-color-stop($next-stop) {
232 @error 'There can be at most one interpolation hint between to color stops.';
233 }
234 }
235
236 @if $easing != null {
237 @if type-of($easing) == number {
238 @error 'Midpoint shifts are not supported.';
239 }
240
241 $easing-func: null;
242 $easing-args: ();
243
244 @if type-of($easing) == list {
245 $easing-args: iro-list-slice($easing, 2);
246 $easing: nth($easing, 1);
247 }
248
249 $generated-stops: join(
250 $generated-stops,
251 iro-easing-gradient-ease-stops($prev-stop, $next-stop, $easing, $easing-args)
252 );
253 } @else {
254 $generated-stops: append($generated-stops, iro-str-implode($next-stop, ' '));
255 }
256
257 $j: $j + 1;
258 }
259
260 $last-positioned-stop: $i;
261 }
262 }
263
264 @if $type == 'linear' {
265 @return linear-gradient($dir, unquote(iro-str-implode($generated-stops, ', ')));
266 } @else if $type == 'radial' {
267 @return radial-gradient($dir, unquote(iro-str-implode($generated-stops, ', ')));
268 } @else {
269 @error 'Invalid gradient type: #{inspect($type)}.';
270 }
271}
272
273///
274/// Alias for iro-easing-gradient('linear',...)
275///
276/// @see {function} iro-easing-gradient
277///
278@function iro-easing-linear-gradient($dir, $stop, $stops...) {
279 @return iro-easing-gradient('linear', $dir, $stop, $stops...);
280}
281
282///
283/// Alias for iro-easing-gradient('radial',...)
284///
285/// @see {function} iro-easing-gradient
286///
287@function iro-easing-radial-gradient($dir, $stop, $stops...) {
288 @return iro-easing-gradient('radial', $dir, $stop, $stops...);
289}
290
291///
292/// Generate a smooth transition from one color stop to another using the provided easing function.
293///
294/// @access private
295///
296@function iro-easing-gradient-ease-stops($prev-stop, $next-stop, $easing, $easing-args: ()) {
297 @if $easing == 'steps' {
298 $steps: null;
299 $jump: null;
300
301 @if length($easing-args) > 1 {
302 $steps: nth($easing-args, 1);
303 $jump: nth($easing-args, 2);
304 } @else {
305 $steps: nth($easing-args, 1);
306 $jump: jump-end;
307 }
308
309 @return iro-easing-gradient-steps-stops($prev-stop, $next-stop, $steps, $jump);
310 } @else {
311 $easing-func: null;
312 @if function-exists('iro-' + $easing) {
313 $easing-func: get-function('iro-' + $easing);
314 } @else {
315 $easing-func: get-function($easing);
316 }
317
318 @return iro-easing-gradient-bezier-stops($prev-stop, $next-stop, $easing-func, $easing-args);
319 }
320}
321
322///
323/// Generate a smooth transition from one color stop to another using the provided cubic-bezier function.
324///
325/// @access private
326///
327@function iro-easing-gradient-bezier-stops($prev-stop, $next-stop, $easing-func, $easing-args: ()) {
328 $prev-stop-color: nth($prev-stop, 1);
329 $prev-stop-pos: nth($prev-stop, 2);
330 $next-stop-color: nth($next-stop, 1);
331 $next-stop-pos: nth($next-stop, 2);
332
333 $stops: ();
334
335 @if ((type-of($prev-stop-pos) == number) and (type-of($next-stop-pos) == number) and (unit($prev-stop-pos) == unit($next-stop-pos))) or ($prev-stop-pos == 0) or ($next-stop-pos == 0) {
336 //
337 // The transition color stop positions can be statically calculated.
338 //
339
340 $distance: $next-stop-pos - $prev-stop-pos;
341
342 @for $i from 1 through $iro-easing-gradient-steps {
343 $perc: $i / $iro-easing-gradient-steps;
344
345 $color: null;
346 $pos: $prev-stop-pos + $perc * $distance;
347 @if $perc == 1 {
348 $color: $next-stop-color;
349 } @else {
350 $color: mix($next-stop-color, $prev-stop-color, call($easing-func, append($easing-args, $perc)...) * 100%);
351 }
352
353 $stops: append($stops, $color + ' ' + $pos);
354 }
355 } @else {
356 //
357 // The transition color stop positions have to be dynamically calculated with the calc() function.
358 //
359
360 @if type-of($prev-stop-pos) != number {
361 // must be calc()
362 @if (type-of($prev-stop-pos) != string) or (str-index($prev-stop-pos, 'calc(') != 1) {
363 @error 'Invalid color stop position: #{inspect($prev-stop-pos)}';
364 }
365
366 $prev-stop-pos: str-slice($prev-stop-pos, 6, str-length($prev-stop-pos) - 1);
367 }
368
369 @if type-of($next-stop-pos) != number {
370 // must be calc()
371 @if (type-of($next-stop-pos) != string) or (str-index($next-stop-pos, 'calc(') != 1) {
372 @error 'Invalid color stop position: #{inspect($next-stop-pos)}';
373 }
374
375 $next-stop-pos: str-slice($next-stop-pos, 6, str-length($next-stop-pos) - 1);
376 }
377
378 @for $i from 1 through $iro-easing-gradient-steps {
379 $perc: $i / $iro-easing-gradient-steps;
380
381 $color: null;
382 $pos: null;
383 @if $perc == 1 {
384 $color: $next-stop-color;
385 $pos: calc(#{$next-stop-pos});
386 } @else {
387 $color: mix($next-stop-color, $prev-stop-color, call($easing-func, append($easing-args, $perc)...) * 100%);
388 $pos: calc(#{$prev-stop-pos} + (#{$next-stop-pos} - #{$prev-stop-pos}) * #{$perc});
389 }
390
391 $stops: append($stops, $color + ' ' + $pos);
392 }
393 }
394
395 @return $stops;
396}
397
398///
399/// Generate a step transition from one color stop to another.
400///
401/// @access private
402///
403@function iro-easing-gradient-steps-stops($prev-stop, $next-stop, $steps, $jump: jump-end) {
404 $prev-stop-color: nth($prev-stop, 1);
405 $prev-stop-pos: nth($prev-stop, 2);
406 $next-stop-color: nth($next-stop, 1);
407 $next-stop-pos: nth($next-stop, 2);
408
409 $stops: ();
410
411 @if ((type-of($prev-stop-pos) == number) and (type-of($next-stop-pos) == number) and (unit($prev-stop-pos) == unit($next-stop-pos))) or ($prev-stop-pos == 0) or ($next-stop-pos == 0) {
412 //
413 // The transition color stop positions can be statically calculated.
414 //
415
416 $distance: $next-stop-pos - $prev-stop-pos;
417
418 @for $i from 1 through $steps {
419 $x1: ($i - 1) / $steps;
420 $x2: $i / $steps;
421 $y: null;
422
423 @if $jump == jump-start {
424 $y: $i / $steps;
425 } @else if $jump == jump-end {
426 $y: ($i - 1) / $steps;
427 } @else if $jump == jump-both {
428 $y: $i / ($steps + 1);
429 } @else if $jump == jump-none {
430 $y: ($i - 1) / ($steps - 1);
431 } @else {
432 @error 'Invalid $jump: #{inspect($jump)}';
433 }
434
435 $color: null;
436 $pos1: if($x1 == 0, $prev-stop-pos, $prev-stop-pos + $x1 * $distance);
437 $pos2: if($x2 == 1, $next-stop-pos, $prev-stop-pos + $x2 * $distance);
438
439 @if $y == 0 {
440 $color: $prev-stop-color;
441 } @else if $y == 1 {
442 $color: $next-stop-color;
443 } @else {
444 $color: mix($next-stop-color, $prev-stop-color, $y * 100%);
445 }
446
447 $stops: append($stops, $color + ' ' + $pos1);
448 $stops: append($stops, $color + ' ' + $pos2);
449 }
450 } @else {
451 //
452 // The transition color stop positions have to be dynamically calculated with the calc() function.
453 //
454
455 @if type-of($prev-stop-pos) != number {
456 // must be calc()
457 @if (type-of($prev-stop-pos) != string) or (str-index($prev-stop-pos, 'calc(') != 1) {
458 @error 'Invalid color stop position: #{inspect($prev-stop-pos)}';
459 }
460
461 $prev-stop-pos: str-slice($prev-stop-pos, 6, str-length($prev-stop-pos) - 1);
462 }
463
464 @if type-of($next-stop-pos) != number {
465 // must be calc()
466 @if (type-of($next-stop-pos) != string) or (str-index($next-stop-pos, 'calc(') != 1) {
467 @error 'Invalid color stop position: #{inspect($next-stop-pos)}';
468 }
469
470 $next-stop-pos: str-slice($next-stop-pos, 6, str-length($next-stop-pos) - 1);
471 }
472
473 @for $i from 1 through $steps {
474 $x1: ($i - 1) / $steps;
475 $x2: $i / $steps;
476 $y: null;
477
478 @if $jump == jump-start {
479 $y: $i / $steps;
480 } @else if $jump == jump-end {
481 $y: ($i - 1) / $steps;
482 } @else if $jump == jump-both {
483 $y: $i / ($steps + 1);
484 } @else if $jump == jump-none {
485 $y: ($i - 1) / ($steps - 1);
486 } @else {
487 @error 'Invalid $jump: #{inspect($jump)}';
488 }
489
490 $color: null;
491 $pos1: if($x1 == 0, $prev-stop-pos, calc(#{$prev-stop-pos} + (#{$next-stop-pos} - #{$prev-stop-pos}) * #{$x1}));
492 $pos2: if($x2 == 1, $next-stop-pos, calc(#{$prev-stop-pos} + (#{$next-stop-pos} - #{$prev-stop-pos}) * #{$x2}));
493
494 @if $y == 0 {
495 $color: $prev-stop-color;
496 } @else if $y == 1 {
497 $color: $next-stop-color;
498 } @else {
499 $color: mix($next-stop-color, $prev-stop-color, $y * 100%);
500 }
501
502 $stops: append($stops, $color + ' ' + $pos1);
503 $stops: append($stops, $color + ' ' + $pos2);
504 }
505 }
506
507 @return $stops;
508}
509
510///
511/// Interpolate the positions of multiple color stops between two color stops whose positions are set.
512///
513/// @access private
514///
515@function iro-easing-gradient-interpolate-stop-positions($prev-stop, $stops, $next-stop) {
516 $prev-stop-pos: nth($prev-stop, 2);
517 $next-stop-pos: nth($next-stop, 2);
518
519 $stops-num: 0;
520 @for $i from 1 through length($stops) {
521 $stop: nth($stops, $i);
522 @if iro-easing-gradient-is-color-stop($stop) {
523 $stops-num: $stops-num + 1;
524 }
525 }
526
527 $i: 1;
528 $cur-stop-num: 1;
529
530 @if ((type-of($prev-stop-pos) == number) and (type-of($next-stop-pos) == number) and (unit($prev-stop-pos) == unit($next-stop-pos))) or ($prev-stop-pos == 0) or ($next-stop-pos == 0) {
531 //
532 // The color stop positions can be statically calculated.
533 //
534
535 $distance: $next-stop-pos - $prev-stop-pos;
536
537 @for $i from 1 through length($stops) {
538 $stop: nth($stops, $i);
539 @if iro-easing-gradient-is-color-stop($stop) {
540 $pos: $prev-stop-pos + $distance / ($stops-num + 1) * $cur-stop-num;
541 $stops: set-nth($stops, $i, $stop $pos);
542
543 $cur-stop-num: $cur-stop-num + 1;
544 }
545 }
546 } @else {
547 //
548 // The color stop positions have to be dynamically calculated with the calc() function.
549 //
550
551 @if type-of($prev-stop-pos) != number {
552 // must be calc()
553 @if (type-of($prev-stop-pos) != string) or (str-index($prev-stop-pos, 'calc(') != 1) {
554 @error 'Invalid color stop position: #{inspect($prev-stop-pos)}';
555 }
556
557 $prev-stop-pos: str-slice($prev-stop-pos, 6, str-length($prev-stop-pos) - 1);
558 }
559
560 @if type-of($next-stop-pos) != number {
561 // must be calc()
562 @if (type-of($next-stop-pos) != string) or (str-index($next-stop-pos, 'calc(') != 1) {
563 @error 'Invalid color stop position: #{inspect($next-stop-pos)}';
564 }
565
566 $next-stop-pos: str-slice($next-stop-pos, 6, str-length($next-stop-pos) - 1);
567 }
568
569 @for $i from 1 through length($stops) {
570 $stop: nth($stops, $i);
571 @if iro-easing-gradient-is-color-stop($stop) {
572 $perc: $cur-stop-num / ($stops-num + 1);
573 $pos: calc(#{$prev-stop-pos} + (#{$next-stop-pos} - #{$prev-stop-pos}) * #{$perc});
574 $stops: set-nth($stops, $i, $stop $pos);
575
576 $cur-stop-num: $cur-stop-num + 1;
577 }
578 }
579 }
580
581 @return $stops;
582}
583
584///
585/// Check if the input is a valid color stop.
586///
587/// @access private
588///
589@function iro-easing-gradient-is-color-stop($input) {
590 @return (type-of($input) == color) or iro-easing-gradient-is-positioned-color-stop($input);
591}
592
593///
594/// Check if the input is a valid positioned color stop.
595///
596/// @access private
597///
598@function iro-easing-gradient-is-positioned-color-stop($input) {
599 @return (type-of($input) == list) and (type-of(nth($input, 1)) == color);
600}
diff --git a/src/_harmony.scss b/src/_harmony.scss
new file mode 100644
index 0000000..c3c8633
--- /dev/null
+++ b/src/_harmony.scss
@@ -0,0 +1,94 @@
1////
2/// Harmony.
3///
4/// Contains functions to make a design appear more harmonic.
5///
6/// @group Harmony
7///
8/// @access public
9////
10
11///
12/// Adjust a value to a modular scale.
13///
14/// For a more sophisticated solution, check out [modularscale-sass](https://github.com/modularscale/modularscale-sass).
15///
16/// @link http://alistapart.com/article/more-meaningful-typography An article about modular scales by Tim Brown
17///
18/// @param {number} $times - Number of iterations. If positive, $base will be multiplied with $ratio. If negative, $base will be divided by $ratio.
19/// @param {number | list} $base - Single base value or, for a multi-stranded modular scale, a list of base values
20/// @param {number} $ratio - Ratio
21///
22/// @return {number}
23///
24@function iro-harmony-modular-scale($times, $base, $ratio) {
25 @if type-of($base) == number {
26 @return $base * iro-math-pow($ratio, $times);
27 }
28
29 $main-base: nth($base, 1);
30 $norm-bases: ();
31
32 @each $b in iro-list-slice($base, 2) {
33 @if $b > $main-base {
34 @while $b > $main-base {
35 $b: $b / $ratio;
36 }
37 $b: $b * $ratio;
38 } @else if $b < $main-base {
39 @while $b < $main-base {
40 $b: $b * $ratio;
41 }
42 }
43
44 $norm-bases: append($norm-bases, $b);
45 }
46
47 $all-bases: append($norm-bases, $main-base);
48 $all-bases: iro-quicksort($all-bases);
49
50 $base-index: $times % length($all-bases) + 1;
51 $exp: floor($times / length($all-bases));
52
53 @return nth($all-bases, $base-index) * iro-math-pow($ratio, $exp);
54}
55
56///
57/// Combine responsive properties with modular scales to achieve responsive modular scales.
58///
59/// @param {string | list} $props - Property or list of properties to set
60/// @param {number} $times - Number of iterations. See iro-harmony-modular-scale for more information.
61/// @param {number} $responsive-map - A map with keys = viewports and values = modular scales
62/// @param {bool} $fluid [true] - If enabled, property values will smoothly transition from one viewport to the next
63///
64/// @see {function} iro-harmony-modular-scale
65///
66/// @example scss - Responsive font sizes between 2 viewports based on modular scales
67/// $ms: (
68/// 320px: (1rem 2rem, 1.1),
69/// 640px: (1rem 2rem, 1.2)
70/// );
71///
72/// h1 {
73/// @include iro-responsive-modular-scale(font-size, 3, $ms);
74/// }
75///
76/// h2 {
77/// @include iro-responsive-modular-scale(font-size, 2, $ms);
78/// }
79///
80/// h3 {
81/// @include iro-responsive-modular-scale(font-size, 1, $ms);
82/// }
83///
84@mixin iro-responsive-modular-scale($props, $times, $responsive-map, $fluid: true) {
85 $new-map: ();
86
87 @each $key, $value in $responsive-map {
88 $new-map: map-merge($new-map, (
89 $key: iro-harmony-modular-scale($times, $value...)
90 ));
91 }
92
93 @include iro-responsive-property($props, $new-map, $fluid);
94}
diff --git a/src/_math.scss b/src/_math.scss
new file mode 100644
index 0000000..9b71bf6
--- /dev/null
+++ b/src/_math.scss
@@ -0,0 +1,62 @@
1////
2/// Basic mathematical functions.
3///
4/// @group Math functions
5///
6/// @access public
7////
8
9///
10/// Perform exponentiation. Only integer exponents are supported.
11///
12/// @param {number} $base
13/// @param {number} $exp
14///
15/// @return {number}
16///
17/// @example scss - Exponentiation with a positive exponent
18/// $result: iro-math-pow(3, 2); // The value of $result is 3^2 = 9
19///
20/// @example scss - Exponentiation with a negative exponent
21/// $result: iro-math-pow(2, -3); // The value of $result is 1/(2^3) = 1/8
22///
23@function iro-math-pow($base, $exp) {
24 $value: 1;
25
26 @if $exp > 0 {
27 @for $i from 1 through $exp {
28 $value: $value * $base;
29 }
30 } @else if $exp < 0 {
31 @for $i from 1 through -$exp {
32 $value: $value / $base;
33 }
34 }
35
36 @return $value;
37}
38
39///
40/// Clamp a number between a minimum and maximum value.
41///
42/// @param {number} $value - Value to clamp
43/// @param {number} $min - Minimum value
44/// @param {number} $max - Maximum value
45///
46/// @return {number}
47///
48/// @example scss
49/// $result: iro-math-clamp(20, 0, 10); // The value of $result is 10
50///
51/// @example scss
52/// $result: iro-math-clamp(50, 20, 100); // The value of $result is 50
53///
54@function iro-math-clamp($value, $min, $max) {
55 @if $value < $min {
56 @return $min;
57 }
58 @if $value > $max {
59 @return $max;
60 }
61 @return $value;
62}
diff --git a/src/_props.scss b/src/_props.scss
new file mode 100644
index 0000000..7377c88
--- /dev/null
+++ b/src/_props.scss
@@ -0,0 +1,281 @@
1////
2/// Property trees.
3///
4/// Property trees allow you to organize properties in a tree structure (internally nested maps).
5/// The intended use is to store all your properties at the beginning and for the rest of the
6/// stylesheet you just get them.
7///
8/// @group Property trees
9///
10/// @access public
11////
12
13///
14/// The maximum depth of resolved iro-prop-ref() references.
15///
16/// @type number
17///
18$iro-props-native-assing-max-depth: 2 !default;
19
20///
21/// Indicate if property names must start with two dashes (--).
22/// This is required if property trees are also used for native CSS custom properties.
23///
24/// @type bool
25///
26$iro-props-enforce-double-dashes: true !default;
27
28///
29/// Default tree name to use if no name is specified.
30///
31/// @type string
32///
33$iro-props-default-tree: 'default' !default;
34
35///
36/// List of all created property trees.
37///
38/// @type list
39///
40/// @access private
41///
42$iro-props-trees: ();
43
44///
45/// Save a property tree. If a tree with the sane name already exists, the trees
46/// will be merged.
47///
48/// @param {map} $map - Map containing properties
49/// @param {string} $tree [$iro-props-default-tree] - ID the map is saved as
50/// @param {bool} $merge [false] - If a tree named $tree already exists and this value is set to true, they will be merged. Otherwise an error will be emitted.
51///
52@mixin iro-props-save($map, $tree: $iro-props-default-tree, $merge: false) {
53 $noop: iro-props-save($map, $tree, $merge);
54}
55
56///
57/// Save a property tree.
58///
59/// @param {map} $map - Map containing properties
60/// @param {string} $tree [$iro-props-default-tree] - ID the map is saved as
61/// @param {bool} $merge [false] - If a tree named $tree already exists and this value is set to true, they will be merged. Otherwise an error will be emitted.
62///
63@function iro-props-save($map, $tree: $iro-props-default-tree, $merge: false) {
64 $prop-map: null;
65
66 @if $iro-props-enforce-double-dashes {
67 @if not iro-props-validate($map) {
68 @error 'Property tree keys must start with two dashes (--). If you don\'t use property trees for native CSS custom properties, set $iro-props-enforce-double-dashes to false.';
69 }
70 }
71
72 @if map-has-key($iro-props-trees, $tree) {
73 @if $merge {
74 $map: iro-map-merge-recursive(map-get($iro-props-trees, $tree), $map);
75 } @else {
76 @error 'Property tree #{inspect($tree)} does already exist.';
77 }
78 }
79
80 $iro-props-trees: map-merge($iro-props-trees, ($tree: $map)) !global;
81
82 @return null;
83}
84
85///
86/// Delete a property tree.
87///
88/// @param {string} $tree [$iro-props-default-tree] - ID of the tree to be deleted
89///
90@mixin iro-props-delete($tree: $iro-props-default-tree) {
91 $noop: iro-props-delete($tree);
92}
93
94///
95/// Unset a property tree.
96///
97/// @param {string} $tree [$iro-props-default-tree] - ID of the tree to be deleted
98///
99/// @throw If the property tree does not exist
100///
101@function iro-props-delete($tree: $iro-props-default-tree) {
102 @if not map-has-key($iro-props-trees, $tree) {
103 @error 'Property tree "#{inspect($tree)}" does not exist.';
104 }
105
106 $iro-props-trees: map-remove($iro-props-trees, $tree) !global;
107
108 @return null;
109}
110
111///
112/// Access a whole property or a subsection (i.e. value) of it.
113///
114/// @param {string | list} $key [null] - Key of the property to read. If this is a list of keys, the map will be traversed in that order.
115/// @param {string} $tree [$iro-props-default-tree] - ID of the property tree to use
116/// @param {any} $default [null] - Default value to return of no match was found. If null, this function will throw an error instead.
117///
118/// @return {any} Value assigned to property or $default
119///
120/// @throw If there was no match for $key and $default is null
121///
122@function iro-props-get($key: (), $tree: $iro-props-default-tree, $default: null) {
123 @if not map-has-key($iro-props-trees, $tree) {
124 @error 'Unknown tree "#{$tree}".';
125 }
126
127 $result: map-get($iro-props-trees, $tree);
128
129 @if type-of($key) == list {
130 $stop: false;
131
132 @each $k in $key {
133 @if map-has-key($result, $k) and not $stop {
134 $result: map-get($result, $k);
135
136 @if type-of($result) == list and nth($result, 1) == 'iro-prop-ref' {
137 @if length($result) == 2 {
138 $result: iro-props-get($tree: nth($result, 2));
139 } @else {
140 $result: iro-props-get(nth($result, 3), nth($result, 2));
141 }
142 }
143 } @else {
144 $stop: true;
145 }
146 }
147
148 @if $stop {
149 $result: null;
150 }
151 } @else {
152 $result: map-get($result, $key);
153
154 @if type-of($result) == list and nth($result, 1) == 'iro-prop-ref' {
155 @if length($result) == 2 {
156 $result: iro-props-get($tree: nth($result, 2));
157 } @else {
158 $result: iro-props-get(nth($result, 3), nth($result, 2));
159 }
160 }
161 }
162
163 @if $result == null {
164 @if $default == null {
165 @error '"#{$key}" is null.';
166 } @else {
167 @return $default;
168 }
169 }
170
171 @return $result;
172}
173
174///
175/// Generate a var() function call to get native CSS custom property.
176///
177/// @param {string | list} $key - Key of the property to read. If this is a list of keys, the map will be traversed in that order.
178/// @param {string | null} $tree [null] - Optional tree to check if the property actually exists.
179/// @param {any} $default [null] - Default value to return of no match was found.
180///
181/// @return {string} var()
182///
183@function iro-props-get-native($key, $tree: null, $default: null) {
184 @if $tree != null {
185 $noop: iro-props-get($key, $tree, $default);
186 }
187
188 $native-var: '';
189
190 @if type-of($key) == list {
191 @each $subkey in $key {
192 $native-var: $native-var + $subkey;
193 }
194 } @else {
195 $native-var: $key;
196 }
197
198 @if $default == null {
199 @return var(#{$native-var});
200 } @else {
201 @return var(#{$native-var}, #{$default});
202 }
203}
204
205///
206/// Generate assignments for native CSS custom properties with the values from the specified tree.
207///
208/// @param {string} $tree [$iro-props-default-tree] - ID of the property tree to use
209/// @param {string} $root [()] - Sub-tree to use for assignment
210///
211@mixin iro-props-assign-native($tree: $iro-props-default-tree, $root: (), $skip: ()) {
212 $map: iro-props-get($root, $tree);
213 $map: map-remove($map, $skip...);
214
215 @include iro-props-assign-native-internal($map);
216}
217
218///
219/// @access private
220///
221@mixin iro-props-assign-native-internal($map, $prefix: '', $ref-depth: $iro-props-native-assing-max-depth) {
222 @each $key, $value in $map {
223 $rd: $ref-depth;
224 @if type-of($value) == list and nth($value, 1) == 'iro-prop-ref' {
225 @if $ref-depth != 0 {
226 $rd: $rd - 1;
227 @if length($value) == 2 {
228 $value: iro-props-get($tree: nth($value, 2));
229 } @else {
230 $value: iro-props-get(nth($value, 3), nth($value, 2));
231 }
232 } @else {
233 $value: null;
234 }
235 }
236 @if type-of($value) != map {
237 #{$prefix + $key}: #{$value};
238 } @else {
239 @include iro-props-assign-native-internal($value, $prefix + $key, $rd);
240 }
241 }
242}
243
244///
245/// Validate property names.
246///
247/// @access private
248///
249@function iro-props-validate($map) {
250 @each $key, $value in $map {
251 @if str-index($key, '--') != 1 {
252 @return false;
253 }
254
255 @if type-of($value) == map {
256 @if not iro-props-validate($value) {
257 @return false;
258 }
259 }
260 }
261
262 @return true;
263}
264
265///
266/// Generate a reference to another tree. Dereferencing is lazy, so you may specify a tree that hasn't been created yet.
267///
268/// @param {string} $tree [$iro-props-default-tree] - ID of the property tree to use
269/// @param {string | list} $key - Key of the property to read. If this is a list of keys, the map will be traversed in that order.
270///
271/// @return {list} A special list that let's Ignis know that this is a lazy value.
272///
273/// @throw If there was no match for $key and $default is null
274///
275@function iro-props-ref($tree: $iro-props-default-tree, $key: null) {
276 @if $key == null {
277 @return ('iro-prop-ref' $tree);
278 } @else {
279 @return ('iro-prop-ref' $tree $key);
280 }
281}
diff --git a/src/_responsive.scss b/src/_responsive.scss
new file mode 100644
index 0000000..6f2a416
--- /dev/null
+++ b/src/_responsive.scss
@@ -0,0 +1,406 @@
1////
2/// responsive properties.
3///
4/// The mixins and functions in this file allow you to scale any px- or rem-based value depending on
5/// the available viewport width. One popular use case is the dynamic scaling of fonts.
6///
7/// The code in this file is based on an article by Niklas Postulart:
8/// http://niklaspostulart.de/2015/10/sass-responsive-type-mixin
9///
10/// The following adjustments were made:
11/// - Support any property passed by the user, not just font-size
12/// - Allow multiple target viewports / values
13/// - Provide a variant of the mixin which integrates include-media for media queries
14///
15/// @group Responsive
16///
17/// @access public
18////
19
20///
21/// If true, named viewports will be supported if a compatible $breakpoints map exists.
22/// This is the case for [include-media](https://include-media.com/), for example.
23///
24/// @type bool
25///
26$iro-responsive-support-named-viewports: true !default;
27
28///
29/// Context ID used for responsive environment-related mixins.
30///
31/// @type string
32///
33$iro-responsive-context-id: 'responsive' !default;
34
35///
36/// Scale a property uniformly between a specific set of target viewports / values.
37///
38/// @param {string | list} $props - Property or list of properties to set
39/// @param {number} $responsive-map - A map with keys = viewports and values = target value
40/// @param {bool} $fluid [true] - If enabled, property values will smoothly transition from one viewport to the next
41/// @param {bool} $vertical [false] - If enabled, property viewport height will be used instead of viewport width
42///
43/// @example scss - Responsive font-size between 2 viewports
44/// .something {
45/// @include iro-responsive-property(font-size, ( 320px: 20px, 720px: 30px ));
46/// }
47///
48/// // Generates:
49///
50/// @media (min-width: 320px) and (max-width: 720px) {
51/// .something {
52/// font-size: calc(20px + 10 * ((100vw - 20px) / 400));
53/// }
54/// }
55///
56/// @media (max-width: 320px) {
57/// .something {
58/// font-size: 20px;
59/// }
60/// }
61///
62/// @media (min-width: 720px) {
63/// .something {
64/// font-size: 30px;
65/// }
66/// }
67///
68/// @example scss - Responsive font-size between 3 viewports
69/// .something {
70/// @include iro-responsive-property(font-size, ( 320px: 20px, 720px: 30px, 1280px: 40px ));
71/// }
72///
73/// // Generates:
74///
75/// @media (min-width: 320px) and (max-width: 720px) {
76/// .something {
77/// font-size: calc(20px + 10 * ((100vw - 20px) / 400));
78/// }
79/// }
80///
81/// @media (min-width: 720px) and (max-width: 1280px) {
82/// .something {
83/// font-size: calc(30px + 10 * ((100vw - 30px) / 400));
84/// }
85/// }
86///
87/// @media (max-width: 320px) {
88/// .something {
89/// font-size: 20px;
90/// }
91/// }
92///
93/// @media (min-width: 720px) {
94/// .something {
95/// font-size: 30px;
96/// }
97/// }
98///
99@mixin iro-responsive-property($props, $responsive-map, $fluid: true, $vertical: false) {
100 @include iro-responsive-env(map-keys($responsive-map), $fluid, $vertical) {
101 @if type-of($props) == list {
102 @each $prop in $props {
103 #{$prop}: iro-responsive-set(map-values($responsive-map));
104 }
105 } @else {
106 #{$props}: iro-responsive-set(map-values($responsive-map));
107 }
108 }
109}
110
111///
112/// Create a new responsive environment by specifying a set of viewports.
113/// Inside a responsive environment, use the iro-responsive-set function to make a property scale automatically.
114///
115/// @param {list} $viewports - Viewports sorted in ascending order
116/// @param {bool} $fluid [true] - If enabled, property values will smoothly transition from one viewport to the next
117/// @param {bool} $vertical [false] - If enabled, property viewport height will be used instead of viewport width
118///
119/// @content
120///
121/// @see {function} iro-responsive-set
122///
123/// @example scss - Responsive font-size between 2 viewports
124/// .something {
125/// @include iro-responsive-env((320px, 720px)) {
126/// font-size: iro-responsive-set(20px, 30px);
127/// }
128/// }
129///
130/// // Generates:
131///
132/// @media (min-width: 320px) and (max-width: 720px) {
133/// .something {
134/// font-size: calc(20px + 10 * ((100vw - 20px) / 400));
135/// }
136/// }
137///
138/// @media (max-width: 320px) {
139/// .something {
140/// font-size: 20px;
141/// }
142/// }
143///
144/// @media (min-width: 720px) {
145/// .something {
146/// font-size: 30px;
147/// }
148/// }
149///
150@mixin iro-responsive-env($viewports, $fluid: true, $vertical: false) {
151 @if length($viewports) <= 1 {
152 @error '$viewports must contain at least two viewports.';
153 }
154
155 $new-viewports: ();
156
157 @each $viewport in $viewports {
158 @if $iro-responsive-support-named-viewports and global-variable-exists(breakpoints) {
159 @if map-has-key($breakpoints, $viewport) {
160 $viewport: map-get($breakpoints, $viewport);
161 }
162 }
163
164 @if (type-of($viewport) != number) or unitless($viewport) {
165 @error '$viewports contains invalid viewports.';
166 }
167
168 $new-viewports: append($new-viewports, $viewport);
169 }
170
171 $viewports: iro-quicksort($new-viewports);
172
173 @if $new-viewports != $viewports {
174 @error '$viewports was not sorted in ascending order.';
175 }
176
177 @if $fluid {
178 $first-vp: nth($viewports, 1);
179 $last-vp: nth($viewports, length($viewports));
180
181 @include iro-context-push($iro-responsive-context-id, 'env', (
182 'viewports': $viewports,
183 'mode': set,
184 'index': 1,
185 'fluid': $fluid,
186 'vertical': $vertical,
187 ));
188
189 @content;
190
191 @include iro-context-pop($iro-responsive-context-id);
192
193 @for $i from 1 to length($viewports) {
194 $prev-vp: nth($viewports, $i);
195 $next-vp: nth($viewports, $i + 1);
196
197 @if not $vertical {
198 @media (min-width: $prev-vp) and (max-width: $next-vp) {
199 @include iro-context-push($iro-responsive-context-id, 'env', (
200 'viewports': $viewports,
201 'mode': transition,
202 'index': $i,
203 'fluid': $fluid,
204 'vertical': $vertical,
205 ));
206
207 @content;
208
209 @include iro-context-pop($iro-responsive-context-id);
210 }
211 } @else {
212 @media (min-height: $prev-vp) and (max-height: $next-vp) {
213 @include iro-context-push($iro-responsive-context-id, 'env', (
214 'viewports': $viewports,
215 'mode': transition,
216 'index': $i,
217 'fluid': $fluid,
218 'vertical': $vertical,
219 ));
220
221 @content;
222
223 @include iro-context-pop($iro-responsive-context-id);
224 }
225 }
226 }
227
228 @if not $vertical {
229 @media (min-width: $last-vp) {
230 @include iro-context-push($iro-responsive-context-id, 'env', (
231 'viewports': $viewports,
232 'mode': set,
233 'index': length($viewports),
234 'fluid': $fluid,
235 'vertical': $vertical,
236 ));
237
238 @content;
239
240 @include iro-context-pop($iro-responsive-context-id);
241 }
242 } @else {
243 @media (min-height: $last-vp) {
244 @include iro-context-push($iro-responsive-context-id, 'env', (
245 'viewports': $viewports,
246 'mode': set,
247 'index': length($viewports),
248 'fluid': $fluid,
249 'vertical': $vertical,
250 ));
251
252 @content;
253
254 @include iro-context-pop($iro-responsive-context-id);
255 }
256 }
257 } @else {
258 @include iro-context-push($iro-responsive-context-id, 'env', (
259 'viewports': $viewports,
260 'mode': set,
261 'index': 1,
262 'fluid': $fluid,
263 'vertical': $vertical,
264 ));
265
266 @content;
267
268 @include iro-context-pop($iro-responsive-context-id);
269
270 @for $i from 2 through length($viewports) {
271 $vp: nth($viewports, $i);
272
273 @if not $vertical {
274 @media (min-width: $vp) {
275 @include iro-context-push($iro-responsive-context-id, 'env', (
276 'viewports': $viewports,
277 'mode': set,
278 'index': $i
279 ));
280
281 @content;
282
283 @include iro-context-pop($iro-responsive-context-id);
284 }
285 } @else {
286 @media (min-height: $vp) {
287 @include iro-context-push($iro-responsive-context-id, 'env', (
288 'viewports': $viewports,
289 'mode': set,
290 'index': $i
291 ));
292
293 @content;
294
295 @include iro-context-pop($iro-responsive-context-id);
296 }
297 }
298 }
299 }
300}
301
302///
303/// Make a property scale automatically inside a responsive environment.
304///
305/// @param {list} $values - Value for each viewport in the responsive environment. The first value will be used for the first viewport, the second value for the second viewport, and so on.
306///
307/// @return {number|string}
308///
309@function iro-responsive-set($values, $without-calc: false) {
310 $noop: iro-context-assert-stack-must-contain($iro-responsive-context-id, 'env');
311
312 $data: nth(iro-context-get($iro-responsive-context-id, 'env'), 2);
313 $viewports: map-get($data, 'viewports');
314 $mode: map-get($data, 'mode');
315 $fluid: map-get($data, 'fluid');
316 $vertical: map-get($data, 'vertical');
317
318 @if length($values) != length($viewports) {
319 @error '$values must contain the same number of items as the responsive environment\'s $viewports.';
320 }
321
322 @if $mode == set {
323 @return nth($values, map-get($data, 'index'));
324 } @else {
325 $index: map-get($data, 'index');
326 $prev-vp: nth($viewports, $index);
327 $next-vp: nth($viewports, $index + 1);
328 $prev-value: nth($values, $index);
329 $next-value: nth($values, $index + 1);
330
331 @return iro-responsive-fluid-calc($prev-value, $next-value, $prev-vp, $next-vp, $vertical, $without-calc);
332 }
333}
334
335///
336/// Generate the calc() function that uniformly scales a value from $min-value to $max-value depending
337/// on the viewport width.
338///
339/// @param {number} $min-value - Minimum value
340/// @param {number} $max-value - Maximum value
341/// @param {number} $min-viewport - Minimum viewport size
342/// @param {number} $max-viewport - Maximum viewport size
343/// @param {bool} $vertical [false] - If enabled, property viewport height will be used instead of viewport width
344///
345/// @access private
346///
347@function iro-responsive-fluid-calc($min-value, $max-value, $min-viewport, $max-viewport, $vertical: false, $without-calc: false) {
348 $value-unit: unit($min-value);
349 $max-value-unit: unit($max-value);
350 $viewport-unit: unit($min-viewport);
351 $max-viewport-unit: unit($max-viewport);
352
353 @if $min-value == 0 {
354 $value-unit: $max-value-unit;
355 }
356 @if $max-value == 0 {
357 $max-value-unit: $value-unit;
358 }
359 @if $min-viewport == 0 {
360 $viewport-unit: $max-viewport-unit;
361 }
362 @if $max-viewport == 0 {
363 $max-viewport-unit: $viewport-unit;
364 }
365
366 @if ($value-unit != $max-value-unit) or ($viewport-unit != $max-viewport-unit) {
367 @error 'Units of $min-value and $max-value, $min-viewport and $max-viewport must match.';
368 }
369
370 @if ($value-unit == rem) and ($viewport-unit == px) {
371 $min-viewport: iro-px-to-rem($min-viewport);
372 $max-viewport: iro-px-to-rem($max-viewport);
373 $viewport-unit: rem;
374 } @else if ($value-unit == px) and ($viewport-unit == rem) {
375 $min-value: iro-px-to-rem($min-value);
376 $max-value: iro-px-to-rem($max-value);
377 $value-unit: rem;
378 }
379
380 @if $value-unit != $viewport-unit {
381 @error 'This combination of units is not supported.';
382 }
383
384 $value-diff: iro-strip-unit($max-value - $min-value);
385 $viewport-diff: iro-strip-unit($max-viewport - $min-viewport);
386
387 $calc: '';
388
389 @if $min-value != 0 {
390 $calc: '#{$min-value} + ';
391 }
392
393 @if not $vertical {
394 $calc: unquote('#{$calc}#{$value-diff} * ((100vw - #{$min-viewport}) / #{$viewport-diff})');
395 } @else {
396 $calc: unquote('#{$calc}#{$value-diff} * ((100vh - #{$min-viewport}) / #{$viewport-diff})');
397 }
398
399 @if $without-calc {
400 @return $calc;
401 } @else {
402 @return calc(#{$calc});
403 }
404}
405
406@include iro-context-stack-create($iro-responsive-context-id);
diff --git a/src/_vars.scss b/src/_vars.scss
new file mode 100644
index 0000000..ce6efda
--- /dev/null
+++ b/src/_vars.scss
@@ -0,0 +1,16 @@
1////
2/// Variables.
3///
4/// Global variables that are used throughout the framework.
5///
6/// @group Global variables
7///
8/// @access public
9////
10
11///
12/// Reference root font size in px that is used for px -> rem conversions.
13///
14/// @type number
15///
16$iro-root-size: 16px !default;
diff --git a/src/bem-shortcodes.scss b/src/bem-shortcodes.scss
new file mode 100644
index 0000000..11abeed
--- /dev/null
+++ b/src/bem-shortcodes.scss
@@ -0,0 +1,349 @@
1////
2/// Shorter version of the bem-related mixins. Useful to reduce clutter.
3///
4/// @group BEM shortcodes
5///
6/// @access public
7////
8
9///
10/// @alias iro-bem-block
11///
12@mixin block($name, $type: null) {
13 @include iro-bem-block($name, $type: null) {
14 @content;
15 }
16}
17
18///
19/// @alias block
20///
21@mixin b($name, $type: null) {
22 @include block($name, $type: null) {
23 @content;
24 }
25}
26
27///
28/// @alias iro-bem-object
29///
30@mixin object($name) {
31 @include iro-bem-object($name) {
32 @content;
33 }
34}
35
36///
37/// @alias object
38///
39@mixin ob($name) {
40 @include object($name) {
41 @content;
42 }
43}
44
45///
46/// @alias iro-bem-component
47///
48@mixin component($name) {
49 @include iro-bem-component($name) {
50 @content;
51 }
52}
53
54///
55/// @alias component
56///
57@mixin cb($name) {
58 @include component($name) {
59 @content;
60 }
61}
62
63///
64/// @alias iro-bem-layout
65///
66@mixin layout($name) {
67 @include iro-bem-layout($name) {
68 @content;
69 }
70}
71
72///
73/// @alias layout
74///
75@mixin lb($name) {
76 @include layout($name) {
77 @content;
78 }
79}
80
81///
82/// @alias iro-bem-utility
83///
84@mixin utility($name) {
85 @include iro-bem-utility($name) {
86 @content;
87 }
88}
89
90///
91/// @alias utility
92///
93@mixin ub($name) {
94 @include utility($name) {
95 @content;
96 }
97}
98
99///
100/// @alias iro-bem-scope
101///
102@mixin scope($name) {
103 @include iro-bem-scope($name) {
104 @content;
105 }
106}
107
108///
109/// @alias scope
110///
111@mixin sb($name) {
112 @include scope($name) {
113 @content;
114 }
115}
116
117///
118/// @alias iro-bem-theme
119///
120@mixin theme($name) {
121 @include iro-bem-theme($name) {
122 @content;
123 }
124}
125
126///
127/// @alias theme
128///
129@mixin tb($name) {
130 @include theme($name) {
131 @content;
132 }
133}
134
135///
136/// @alias iro-bem-js
137///
138@mixin js($name) {
139 @include iro-bem-js($name) {
140 @content;
141 }
142}
143
144///
145/// @alias iro-bem-qa
146///
147@mixin qa($name) {
148 @include iro-bem-qa($name) {
149 @content;
150 }
151}
152
153///
154/// @alias iro-bem-hack
155///
156@mixin hack($name) {
157 @include iro-bem-hack($name) {
158 @content;
159 }
160}
161
162///
163/// @alias iro-bem-composed-of
164///
165@mixin composed-of($block, $blocks...) {
166 @include iro-bem-composed-of($block, $blocks...) {
167 @content;
168 }
169}
170
171///
172/// @alias composed-of
173///
174@mixin co($block, $blocks...) {
175 @include composed-of($block, $blocks...) {
176 @content;
177 }
178}
179
180///
181/// @alias iro-bem-element
182///
183@mixin element($name, $names...) {
184 @include iro-bem-element($name, $names...) {
185 @content;
186 }
187}
188
189///
190/// @alias element
191///
192@mixin e($name, $names...) {
193 @include element($name, $names...) {
194 @content;
195 }
196}
197
198///
199/// @alias iro-bem-related-element
200///
201@mixin related-element($sign, $name, $names...) {
202 @include iro-bem-related-element($sign, $name, $names...) {
203 @content;
204 }
205}
206
207///
208/// @alias related-element
209///
210@mixin re($sign, $name, $names...) {
211 @include related-element($sign, $name, $names...) {
212 @content;
213 }
214}
215
216///
217/// @alias iro-bem-sibling-element
218///
219@mixin sibling-element($name, $names...) {
220 @include iro-bem-sibling-element($name, $names...) {
221 @content;
222 }
223}
224
225///
226/// @alias sibling-element
227///
228@mixin se($name, $names...) {
229 @include sibling-element($name, $names...) {
230 @content;
231 }
232}
233
234///
235/// @alias iro-bem-next-element
236///
237@mixin next-element($name, $names...) {
238 @include iro-bem-next-element($name, $names...) {
239 @content;
240 }
241}
242
243///
244/// @alias next-element
245///
246@mixin ne($name, $names...) {
247 @include next-element($name, $names...) {
248 @content;
249 }
250}
251
252///
253/// @alias iro-bem-next-twin-element
254///
255@mixin next-twin-element {
256 @include iro-bem-next-twin-element {
257 @content;
258 }
259}
260
261///
262/// @alias next-twin-element
263///
264@mixin te {
265 @include next-twin-element {
266 @content;
267 }
268}
269
270///
271/// @alias iro-bem-modifier
272///
273@mixin modifier($name, $names...) {
274 @include iro-bem-modifier($name, $names...) {
275 @content;
276 }
277}
278
279///
280/// @alias modifier
281///
282@mixin m($name, $names...) {
283 @include modifier($name, $names...) {
284 @content;
285 }
286}
287
288///
289/// @alias iro-bem-suffix
290///
291@mixin suffix($name) {
292 @include iro-bem-suffix($name) {
293 @content;
294 }
295}
296
297///
298/// @alias suffix
299///
300@mixin s($name) {
301 @include suffix($name) {
302 @content;
303 }
304}
305
306///
307/// @alias iro-bem-is
308///
309@mixin is($state, $states...) {
310 @include iro-bem-is($state, $states...) {
311 @content;
312 }
313}
314
315///
316/// @alias iro-bem-has
317///
318@mixin has($state, $states...) {
319 @include iro-bem-has($state, $states...) {
320 @content;
321 }
322}
323
324///
325/// @alias iro-bem-at-theme
326///
327@mixin at-theme($name, $names...) {
328 @include iro-bem-at-theme($name, $names...) {
329 @content;
330 }
331}
332
333///
334/// @alias theme
335///
336@mixin at($name, $names...) {
337 @include at-theme($name, $names...) {
338 @content;
339 }
340}
341
342///
343/// @alias iro-bem-multi
344///
345@mixin multi($first, $others...) {
346 @include iro-bem-multi($first, $others...) {
347 @content;
348 }
349}
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;
diff --git a/src/harmony-shortcodes.scss b/src/harmony-shortcodes.scss
new file mode 100644
index 0000000..3307a7e
--- /dev/null
+++ b/src/harmony-shortcodes.scss
@@ -0,0 +1,35 @@
1////
2/// Shorter version of the harmony-related functions. Useful to reduce clutter.
3///
4/// @group Harmony shortcodes
5///
6/// @access public
7////
8
9///
10/// @alias iro-harmony-modular-scale
11///
12@function modular-scale($times, $base, $ratio) {
13 @include iro-harmony-modular-scale($times, $base, $ratio);
14}
15
16///
17/// @alias modular-scale
18///
19@function ms($times, $base, $ratio) {
20 @include modular-scale($times, $base, $ratio);
21}
22
23///
24/// @alias iro-responsive-modular-scale
25///
26@mixin responsive-modular-scale($prop, $times, $responsive-map) {
27 @include iro-responsive-modular-scale($prop, $times, $responsive-map);
28}
29
30///
31/// @alias responsive-modular-scale
32///
33@mixin responsive-ms($prop, $times, $responsive-map) {
34 @include responsive-modular-scale($prop, $times, $responsive-map);
35}
diff --git a/src/main.scss b/src/main.scss
new file mode 100644
index 0000000..5b1b3d1
--- /dev/null
+++ b/src/main.scss
@@ -0,0 +1,10 @@
1@import 'functions';
2@import 'math';
3@import 'vars';
4@import 'contexts';
5@import 'bem';
6@import 'props';
7@import 'harmony';
8@import 'responsive';
9@import 'easing';
10@import 'gradients';
diff --git a/src/prep.scss b/src/prep.scss
new file mode 100644
index 0000000..616165d
--- /dev/null
+++ b/src/prep.scss
@@ -0,0 +1,2 @@
1@import 'functions';
2@import 'math';
diff --git a/src/responsive-shortcodes.scss b/src/responsive-shortcodes.scss
new file mode 100644
index 0000000..e43dfc0
--- /dev/null
+++ b/src/responsive-shortcodes.scss
@@ -0,0 +1,14 @@
1////
2/// Shorter version of the responsive-related mixins. Useful to reduce clutter.
3///
4/// @group Responsive shortcodes
5///
6/// @access public
7////
8
9///
10/// @alias iro-responsive-property
11///
12@mixin responsive($props, $responsive-map, $fluid: true, $vertical: false) {
13 @include iro-responsive-property($props, $responsive-map, $fluid, $vertical);
14}