aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/_contexts.scss217
-rw-r--r--src/_easing.scss217
-rw-r--r--src/_functions.scss316
-rw-r--r--src/_gradients.scss643
-rw-r--r--src/_harmony.scss65
-rw-r--r--src/_iro-sass.scss (renamed from src/index.scss)10
-rw-r--r--src/_props.scss508
-rw-r--r--src/_responsive.scss391
-rw-r--r--src/bem/_block.scss216
-rw-r--r--src/bem/_debug.scss15
-rw-r--r--src/bem/_element.scss483
-rw-r--r--src/bem/_functions.scss27
-rw-r--r--src/bem/_modifier.scss227
-rw-r--r--src/bem/_multi.scss102
-rw-r--r--src/bem/_state.scss84
-rw-r--r--src/bem/_suffix.scss95
-rw-r--r--src/bem/_theme.scss57
-rw-r--r--src/bem/_validators.scss137
-rw-r--r--src/bem/_vars.scss28
19 files changed, 1835 insertions, 2003 deletions
diff --git a/src/_contexts.scss b/src/_contexts.scss
index 7ffbb4e..ed376a2 100644
--- a/src/_contexts.scss
+++ b/src/_contexts.scss
@@ -15,6 +15,9 @@
15/// @access public 15/// @access public
16//// 16////
17 17
18@use 'sass:list';
19@use 'sass:map';
20@use 'sass:meta';
18@use './functions'; 21@use './functions';
19 22
20/// 23///
@@ -34,7 +37,7 @@ $stacks: ();
34/// @throw If context stack already exists 37/// @throw If context stack already exists
35/// 38///
36@mixin create($stack-id) { 39@mixin create($stack-id) {
37 $noop: create($stack-id); 40 $noop: create($stack-id);
38} 41}
39 42
40/// 43///
@@ -43,13 +46,13 @@ $stacks: ();
43/// @param {string} $stack-id - ID of context stack 46/// @param {string} $stack-id - ID of context stack
44/// 47///
45@function create($stack-id) { 48@function create($stack-id) {
46 @if map-has-key($stacks, $stack-id) { 49 @if map.has-key($stacks, $stack-id) {
47 @error 'Context stack "#{inspect($stack-id)}" does not exist.'; 50 @error 'Context stack "#{inspect($stack-id)}" does not exist.';
48 } 51 }
49 52
50 $stacks: map-merge($stacks, ($stack-id: ())) !global; 53 $stacks: map.merge($stacks, ($stack-id: ())) !global;
51 54
52 @return null; 55 @return null;
53} 56}
54 57
55/// 58///
@@ -58,7 +61,7 @@ $stacks: ();
58/// @param {string} $stack-id - ID of context stack 61/// @param {string} $stack-id - ID of context stack
59/// 62///
60@mixin clear($stack-id) { 63@mixin clear($stack-id) {
61 $noop: clear($stack-id); 64 $noop: clear($stack-id);
62} 65}
63 66
64/// 67///
@@ -67,14 +70,14 @@ $stacks: ();
67/// @param {string} $stack-id - ID of context stack 70/// @param {string} $stack-id - ID of context stack
68/// 71///
69@function clear($stack-id) { 72@function clear($stack-id) {
70 @if not map-has-key($stacks, $stack-id) { 73 @if not map.has-key($stacks, $stack-id) {
71 @error 'Context stack "#{inspect($stack-id)}" does not exist.'; 74 @error 'Context stack "#{inspect($stack-id)}" does not exist.';
72 } 75 }
73 76
74 $context-stack: (); 77 $context-stack: ();
75 $stacks: map-merge($stacks, ($stack-id: $context-stack)) !global; 78 $stacks: map.merge($stacks, ($stack-id: $context-stack)) !global;
76 79
77 @return null; 80 @return null;
78} 81}
79 82
80/// 83///
@@ -83,7 +86,7 @@ $stacks: ();
83/// @param {string} $stack-id - ID of context stack 86/// @param {string} $stack-id - ID of context stack
84/// 87///
85@mixin delete($stack-id) { 88@mixin delete($stack-id) {
86 $noop: delete($stack-id); 89 $noop: delete($stack-id);
87} 90}
88 91
89/// 92///
@@ -92,13 +95,13 @@ $stacks: ();
92/// @param {string} $stack-id - ID of context stack 95/// @param {string} $stack-id - ID of context stack
93/// 96///
94@function delete($stack-id) { 97@function delete($stack-id) {
95 @if not map-has-key($stacks, $stack-id) { 98 @if not map.has-key($stacks, $stack-id) {
96 @error 'Context stack "#{inspect($stack-id)}" does not exist.'; 99 @error 'Context stack "#{inspect($stack-id)}" does not exist.';
97 } 100 }
98 101
99 $stacks: map-remove($stacks, $stack-id) !global; 102 $stacks: map.remove($stacks, $stack-id) !global;
100 103
101 @return null; 104 @return null;
102} 105}
103 106
104/// 107///
@@ -109,7 +112,7 @@ $stacks: ();
109/// @param {any} $data [()] - Data that belongs to the context 112/// @param {any} $data [()] - Data that belongs to the context
110/// 113///
111@mixin push($stack-id, $id, $data: ()) { 114@mixin push($stack-id, $id, $data: ()) {
112 $noop: push($stack-id, $id, $data); 115 $noop: push($stack-id, $id, $data);
113} 116}
114 117
115/// 118///
@@ -122,16 +125,16 @@ $stacks: ();
122/// @return {list} A list with two items: 1 = context id, 2 = context data 125/// @return {list} A list with two items: 1 = context id, 2 = context data
123/// 126///
124@function push($stack-id, $id, $data: ()) { 127@function push($stack-id, $id, $data: ()) {
125 @if not map-has-key($stacks, $stack-id) { 128 @if not map.has-key($stacks, $stack-id) {
126 @error 'Context stack "#{inspect($stack-id)}" does not exist.'; 129 @error 'Context stack "#{inspect($stack-id)}" does not exist.';
127 } 130 }
128 131
129 $context: $id $data; 132 $context: $id $data;
130 $context-stack: map-get($stacks, $stack-id); 133 $context-stack: map.get($stacks, $stack-id);
131 $context-stack: append($context-stack, $context); 134 $context-stack: list.append($context-stack, $context);
132 $stacks: map-merge($stacks, ($stack-id: $context-stack)) !global; 135 $stacks: map.merge($stacks, ($stack-id: $context-stack)) !global;
133 136
134 @return $context; 137 @return $context;
135} 138}
136 139
137/// 140///
@@ -142,7 +145,7 @@ $stacks: ();
142/// @throw If context stack doesn't exist 145/// @throw If context stack doesn't exist
143/// 146///
144@mixin pop($stack-id) { 147@mixin pop($stack-id) {
145 $noop: pop($stack-id); 148 $noop: pop($stack-id);
146} 149}
147 150
148/// 151///
@@ -153,27 +156,27 @@ $stacks: ();
153/// @return {list} A list with two items: 1 = context id, 2 = context data 156/// @return {list} A list with two items: 1 = context id, 2 = context data
154/// 157///
155@function pop($stack-id) { 158@function pop($stack-id) {
156 @if not map-has-key($stacks, $stack-id) { 159 @if not map.has-key($stacks, $stack-id) {
157 @error 'Context stack "#{inspect($stack-id)}" does not exist.'; 160 @error 'Context stack "#{inspect($stack-id)}" does not exist.';
158 } 161 }
159 162
160 $context-stack: map-get($stacks, $stack-id); 163 $context-stack: map.get($stacks, $stack-id);
161 164
162 @if length($context-stack) == 0 { 165 @if list.length($context-stack) == 0 {
163 @error 'Context stack "#{inspect($stack-id)}" is already empty.'; 166 @error 'Context stack "#{inspect($stack-id)}" is already empty.';
164 } 167 }
165 168
166 $popped-context: nth($context-stack, -1); 169 $popped-context: list.nth($context-stack, -1);
167 170
168 @if length($context-stack) == 1 { 171 @if list.length($context-stack) == 1 {
169 $context-stack: (); 172 $context-stack: ();
170 } @else { 173 } @else {
171 $context-stack: functions.list-slice($context-stack, 1, length($context-stack) - 1); 174 $context-stack: functions.list-slice($context-stack, 1, list.length($context-stack) - 1);
172 } 175 }
173 176
174 $stacks: map-merge($stacks, ($stack-id: $context-stack)) !global; 177 $stacks: map.merge($stacks, ($stack-id: $context-stack)) !global;
175 178
176 @return $popped-context; 179 @return $popped-context;
177} 180}
178 181
179/// 182///
@@ -186,15 +189,15 @@ $stacks: ();
186/// @throw If assertion fails 189/// @throw If assertion fails
187/// 190///
188@function assert-stack-must-contain($stack-id, $context-ids, $check-head-only: false) { 191@function assert-stack-must-contain($stack-id, $context-ids, $check-head-only: false) {
189 @if not contains($stack-id, $context-ids, $check-head-only) { 192 @if not contains($stack-id, $context-ids, $check-head-only) {
190 @error 'Must be called inside of contexts "#{inspect($context-ids)}".'; 193 @error 'Must be called inside of contexts "#{inspect($context-ids)}".';
191 } 194 }
192 195
193 @return null; 196 @return null;
194} 197}
195 198
196@mixin assert-stack-must-contain($stack-id, $context-ids, $check-head-only: false) { 199@mixin assert-stack-must-contain($stack-id, $context-ids, $check-head-only: false) {
197 $noop: assert-stack-must-contain($stack-id, $context-ids, $check-head-only); 200 $noop: assert-stack-must-contain($stack-id, $context-ids, $check-head-only);
198} 201}
199 202
200/// 203///
@@ -207,15 +210,15 @@ $stacks: ();
207/// @throw If assertion fails 210/// @throw If assertion fails
208/// 211///
209@function assert-stack-must-not-contain($stack-id, $context-ids, $check-head-only: false) { 212@function assert-stack-must-not-contain($stack-id, $context-ids, $check-head-only: false) {
210 @if contains($stack-id, $context-ids, $check-head-only) { 213 @if contains($stack-id, $context-ids, $check-head-only) {
211 @error 'Must not be called inside of contexts "#{inspect($context-ids)}".'; 214 @error 'Must not be called inside of contexts "#{inspect($context-ids)}".';
212 } 215 }
213 216
214 @return null; 217 @return null;
215} 218}
216 219
217@mixin assert-stack-must-not-contain($stack-id, $context-ids, $check-head-only: false) { 220@mixin assert-stack-must-not-contain($stack-id, $context-ids, $check-head-only: false) {
218 $noop: assert-stack-must-not-contain($stack-id, $context-ids, $check-head-only); 221 $noop: assert-stack-must-not-contain($stack-id, $context-ids, $check-head-only);
219} 222}
220 223
221/// 224///
@@ -228,29 +231,29 @@ $stacks: ();
228/// @return {bool} `true` if the context stack contains one of the context IDs, otherwise `false` 231/// @return {bool} `true` if the context stack contains one of the context IDs, otherwise `false`
229/// 232///
230@function contains($stack-id, $context-ids, $check-head-only: false) { 233@function contains($stack-id, $context-ids, $check-head-only: false) {
231 @if not map-has-key($stacks, $stack-id) { 234 @if not map.has-key($stacks, $stack-id) {
232 @error 'Context stack "#{inspect($stack-id)}" does not exist.'; 235 @error 'Context stack "#{inspect($stack-id)}" does not exist.';
233 } 236 }
234 237
235 $context-stack: map-get($stacks, $stack-id); 238 $context-stack: map.get($stacks, $stack-id);
236 239
237 @if length($context-stack) == 0 { 240 @if list.length($context-stack) == 0 {
238 @return false; 241 @return false;
239 } 242 }
240 243
241 $end-idx: if($check-head-only, length($context-stack), 1); 244 $end-idx: if($check-head-only, list.length($context-stack), 1);
242 245
243 @for $i from length($context-stack) through $end-idx { 246 @for $i from list.length($context-stack) through $end-idx {
244 $context: nth($context-stack, $i); 247 $context: list.nth($context-stack, $i);
245 248
246 @each $chk-context in $context-ids { 249 @each $chk-context in $context-ids {
247 @if nth($context, 1) == $chk-context { 250 @if list.nth($context, 1) == $chk-context {
248 @return true; 251 @return true;
249 } 252 }
250 } 253 }
251 } 254 }
252 255
253 @return false; 256 @return false;
254} 257}
255 258
256/// 259///
@@ -262,15 +265,15 @@ $stacks: ();
262/// @throw If assertion fails 265/// @throw If assertion fails
263/// 266///
264@function assert-stack-count($stack-id, $max-count) { 267@function assert-stack-count($stack-id, $max-count) {
265 @if count($stack-id) > $max-count { 268 @if count($stack-id) > $max-count {
266 @error 'Maximum context count "#{inspect($max-count)}" exceeded.'; 269 @error 'Maximum context count "#{inspect($max-count)}" exceeded.';
267 } 270 }
268 271
269 @return null; 272 @return null;
270} 273}
271 274
272@mixin assert-stack-count($stack-id, $max-count) { 275@mixin assert-stack-count($stack-id, $max-count) {
273 $noop: assert-stack-count($stack-id, $max-count); 276 $noop: assert-stack-count($stack-id, $max-count);
274} 277}
275 278
276/// 279///
@@ -281,13 +284,13 @@ $stacks: ();
281/// @return {number} The number of contexts 284/// @return {number} The number of contexts
282/// 285///
283@function count($stack-id) { 286@function count($stack-id) {
284 @if not map-has-key($stacks, $stack-id) { 287 @if not map.has-key($stacks, $stack-id) {
285 @error 'Context stack "#{inspect($stack-id)}" does not exist.'; 288 @error 'Context stack "#{inspect($stack-id)}" does not exist.';
286 } 289 }
287 290
288 $context-stack: map-get($stacks, $stack-id); 291 $context-stack: map.get($stacks, $stack-id);
289 292
290 @return length($context-stack); 293 @return list.length($context-stack);
291} 294}
292 295
293/// 296///
@@ -299,37 +302,37 @@ $stacks: ();
299/// @return {list} Null if no match was found, otherwise a list with two items: 1. context ID, 2. context data. 302/// @return {list} Null if no match was found, otherwise a list with two items: 1. context ID, 2. context data.
300/// 303///
301@function get($stack-id, $type-or-level: null) { 304@function get($stack-id, $type-or-level: null) {
302 @if not map-has-key($stacks, $stack-id) { 305 @if not map.has-key($stacks, $stack-id) {
303 @error 'Context stack "#{inspect($stack-id)}" does not exist.'; 306 @error 'Context stack "#{inspect($stack-id)}" does not exist.';
304 } 307 }
305 308
306 $context-stack: map-get($stacks, $stack-id); 309 $context-stack: map.get($stacks, $stack-id);
307 310
308 @if length($context-stack) == 0 { 311 @if list.length($context-stack) == 0 {
309 @return null; 312 @return null;
310 } 313 }
311 314
312 @if type-of($type-or-level) == number { 315 @if meta.type-of($type-or-level) == number {
313 $context: nth($context-stack, -$type-or-level); 316 $context: list.nth($context-stack, -$type-or-level);
314 317
315 @return $context; 318 @return $context;
316 } @else { 319 } @else {
317 @for $i from -1 through -(length($context-stack)) { 320 @for $i from -1 through -(list.length($context-stack)) {
318 $context: nth($context-stack, $i); 321 $context: list.nth($context-stack, $i);
319 322
320 @if type-of($type-or-level) == list { 323 @if meta.type-of($type-or-level) == list {
321 @for $j from 1 through length($type-or-level) { 324 @for $j from 1 through list.length($type-or-level) {
322 $ctx: nth($type-or-level, $j); 325 $ctx: list.nth($type-or-level, $j);
323 326
324 @if nth($context, 1) == $ctx { 327 @if list.nth($context, 1) == $ctx {
325 @return $context; 328 @return $context;
326 } 329 }
327 } 330 }
328 } @else if nth($context, 1) == $type-or-level { 331 } @else if list.nth($context, 1) == $type-or-level {
329 @return $context; 332 @return $context;
330 } 333 }
331 } 334 }
332 } 335 }
333 336
334 @return null; 337 @return null;
335} 338}
diff --git a/src/_easing.scss b/src/_easing.scss
index 8bcfd39..939eda2 100644
--- a/src/_easing.scss
+++ b/src/_easing.scss
@@ -10,6 +10,8 @@
10//// 10////
11 11
12@use 'sass:math'; 12@use 'sass:math';
13@use 'sass:map';
14@use 'sass:list';
13 15
14/// 16///
15/// @access private 17/// @access private
@@ -53,62 +55,62 @@ $cubic-bezier-subdiv-max-iters: 10 !default;
53/// @return {number} 55/// @return {number}
54/// 56///
55@function cubic-bezier($x1, $y1, $x2, $y2, $x) { 57@function cubic-bezier($x1, $y1, $x2, $y2, $x) {
56 // 58 //
57 // Cover simple cases 59 // Cover simple cases
58 // 60 //
59 61
60 @if ($x1 == $y1) and ($x2 == $y2) { 62 @if ($x1 == $y1) and ($x2 == $y2) {
61 @return $x; 63 @return $x;
62 } 64 }
63 @if $x == 0 { 65 @if $x == 0 {
64 @return 0; 66 @return 0;
65 } 67 }
66 @if $x == 1 { 68 @if $x == 1 {
67 @return 1; 69 @return 1;
68 } 70 }
69 71
70 // 72 //
71 // Generate samples 73 // Generate samples
72 // 74 //
73 75
74 $sample-pool-key: $x1 + '_' + $x2; 76 $sample-pool-key: $x1 + '_' + $x2;
75 77
76 @if not map-has-key($cubic-bezier-sample-pool, $sample-pool-key) { 78 @if not map.has-key($cubic-bezier-sample-pool, $sample-pool-key) {
77 $samples: (); 79 $samples: ();
78 80
79 @for $i from 0 through $cubic-bezier-sample-pool-size { 81 @for $i from 0 through $cubic-bezier-sample-pool-size {
80 $samples: append($samples, cubic-bezier-func($x1, $x2, math.div($i, $cubic-bezier-sample-pool-size))); 82 $samples: list.append($samples, cubic-bezier-func($x1, $x2, math.div($i, $cubic-bezier-sample-pool-size)));
81 } 83 }
82 84
83 $cubic-bezier-sample-pool: map-merge($cubic-bezier-sample-pool, ($sample-pool-key: $samples)) !global; 85 $cubic-bezier-sample-pool: map.merge($cubic-bezier-sample-pool, ($sample-pool-key: $samples)) !global;
84 } 86 }
85 87
86 // 88 //
87 // Calculate cubic bezier 89 // Calculate cubic bezier
88 // 90 //
89 91
90 @return cubic-bezier-func($y1, $y2, cubic-bezier-t-for-x($x1, $x2, $x)); 92 @return cubic-bezier-func($y1, $y2, cubic-bezier-t-for-x($x1, $x2, $x));
91} 93}
92 94
93/// 95///
94/// @access private 96/// @access private
95/// 97///
96@function cubic-bezier-func-a($p1, $p2) { 98@function cubic-bezier-func-a($p1, $p2) {
97 @return 1 - 3 * $p2 + 3 * $p1; 99 @return 1 - 3 * $p2 + 3 * $p1;
98} 100}
99 101
100/// 102///
101/// @access private 103/// @access private
102/// 104///
103@function cubic-bezier-func-b($p1, $p2) { 105@function cubic-bezier-func-b($p1, $p2) {
104 @return 3 * $p2 - 6 * $p1; 106 @return 3 * $p2 - 6 * $p1;
105} 107}
106 108
107/// 109///
108/// @access private 110/// @access private
109/// 111///
110@function cubic-bezier-func-c($p1) { 112@function cubic-bezier-func-c($p1) {
111 @return 3 * $p1; 113 @return 3 * $p1;
112} 114}
113 115
114/// 116///
@@ -117,7 +119,7 @@ $cubic-bezier-subdiv-max-iters: 10 !default;
117/// @access private 119/// @access private
118/// 120///
119@function cubic-bezier-func($p1, $p2, $t) { 121@function cubic-bezier-func($p1, $p2, $t) {
120 @return ((cubic-bezier-func-a($p1, $p2) * $t + cubic-bezier-func-b($p1, $p2)) * $t + cubic-bezier-func-c($p1)) * $t; 122 @return ((cubic-bezier-func-a($p1, $p2) * $t + cubic-bezier-func-b($p1, $p2)) * $t + cubic-bezier-func-c($p1)) * $t;
121} 123}
122 124
123/// 125///
@@ -126,7 +128,7 @@ $cubic-bezier-subdiv-max-iters: 10 !default;
126/// @access private 128/// @access private
127/// 129///
128@function cubic-bezier-func-slope($p1, $p2, $t) { 130@function cubic-bezier-func-slope($p1, $p2, $t) {
129 @return 3 * cubic-bezier-func-a($p1, $p2) * $t * $t + 2 * cubic-bezier-func-b($p1, $p2) * $t + cubic-bezier-func-c($p1); 131 @return 3 * cubic-bezier-func-a($p1, $p2) * $t * $t + 2 * cubic-bezier-func-b($p1, $p2) * $t + cubic-bezier-func-c($p1);
130} 132}
131 133
132/// 134///
@@ -135,18 +137,18 @@ $cubic-bezier-subdiv-max-iters: 10 !default;
135/// @access private 137/// @access private
136/// 138///
137@function cubic-bezier-newton-raphson($x1, $x2, $x, $t) { 139@function cubic-bezier-newton-raphson($x1, $x2, $x, $t) {
138 @for $i from 1 through $cubic-bezier-newton-iters { 140 @for $i from 1 through $cubic-bezier-newton-iters {
139 $cur-slope: cubic-bezier-func-slope($x1, $x2, $t); 141 $cur-slope: cubic-bezier-func-slope($x1, $x2, $t);
140 142
141 @if $cur-slope == 0 { 143 @if $cur-slope == 0 {
142 @return $t; 144 @return $t;
143 } 145 }
144 146
145 $cur-x: cubic-bezier-func($x1, $x2, $t) - $x; 147 $cur-x: cubic-bezier-func($x1, $x2, $t) - $x;
146 $t: $t - math.div($cur-x, $cur-slope); 148 $t: $t - math.div($cur-x, $cur-slope);
147 } 149 }
148 150
149 @return $t; 151 @return $t;
150} 152}
151 153
152/// 154///
@@ -155,26 +157,26 @@ $cubic-bezier-subdiv-max-iters: 10 !default;
155/// @access private 157/// @access private
156/// 158///
157@function cubic-bezier-binary-subdivide($x1, $x2, $x, $a, $b) { 159@function cubic-bezier-binary-subdivide($x1, $x2, $x, $a, $b) {
158 $cur-x: 0; 160 $cur-x: 0;
159 $cur-t: 0; 161 $cur-t: 0;
160 $i: 0; 162 $i: 0;
161 163
162 @while $i < $cubic-bezier-subdiv-max-iters { 164 @while $i < $cubic-bezier-subdiv-max-iters {
163 $cur-t: $a + ($b - $a) / 2; 165 $cur-t: $a + ($b - $a) / 2;
164 $cur-x: cubic-bezier-func($x1, $x2, $cur-t) - $x; 166 $cur-x: cubic-bezier-func($x1, $x2, $cur-t) - $x;
165 167
166 @if $cur-x > 0 { 168 @if $cur-x > 0 {
167 $b: $cur-t; 169 $b: $cur-t;
168 } @else { 170 } @else {
169 $a: $cur-t; 171 $a: $cur-t;
170 } 172 }
171 173
172 @if abs($cur-x) < $cubic-bezier-subdiv-precision { 174 @if math.abs($cur-x) < $cubic-bezier-subdiv-precision {
173 @return $cur-t; 175 @return $cur-t;
174 } 176 }
175 } 177 }
176 178
177 @return $cur-t; 179 @return $cur-t;
178} 180}
179 181
180/// 182///
@@ -183,30 +185,41 @@ $cubic-bezier-subdiv-max-iters: 10 !default;
183/// @access private 185/// @access private
184/// 186///
185@function cubic-bezier-t-for-x($x1, $x2, $x) { 187@function cubic-bezier-t-for-x($x1, $x2, $x) {
186 $sample-pool-key: $x1 + '_' + $x2; 188 $sample-pool-key: $x1 + '_' + $x2;
187 $samples: map-get($cubic-bezier-sample-pool, $sample-pool-key); 189 $samples: map.get($cubic-bezier-sample-pool, $sample-pool-key);
188 190
189 $intv-start: 0; 191 $intv-start: 0;
190 $cur-sample: 1; 192 $cur-sample: 1;
191 $last-sample: $cubic-bezier-sample-pool-size; 193 $last-sample: $cubic-bezier-sample-pool-size;
192 194
193 @while ($cur-sample != $last-sample) and (nth($samples, $cur-sample) <= $x) { 195 @while ($cur-sample != $last-sample) and (list.nth($samples, $cur-sample) <= $x) {
194 $intv-start: $intv-start + math.div(1, $cubic-bezier-sample-pool-size); 196 $intv-start: $intv-start + math.div(1, $cubic-bezier-sample-pool-size);
195 $cur-sample: $cur-sample + 1; 197 $cur-sample: $cur-sample + 1;
196 } 198 }
197 $cur-sample: $cur-sample - 1; 199 $cur-sample: $cur-sample - 1;
198 200
199 $dist: math.div($x - nth($samples, $cur-sample), nth($samples, $cur-sample + 1) - nth($samples, $cur-sample)); 201 $dist: math.div($x - list.nth($samples, $cur-sample), list.nth($samples, $cur-sample + 1) - list.nth($samples, $cur-sample));
200 $guess-t: $intv-start + math.div($dist, $cubic-bezier-sample-pool-size); 202 $guess-t: $intv-start + math.div($dist, $cubic-bezier-sample-pool-size);
201 203
202 $init-slope: cubic-bezier-func-slope($x1, $x2, $guess-t); 204 $init-slope: cubic-bezier-func-slope($x1, $x2, $guess-t);
203 @if $init-slope >= $cubic-bezier-newton-min-slope { 205 @if $init-slope >= $cubic-bezier-newton-min-slope {
204 @return cubic-bezier-newton-raphson($x1, $x2, $x, $guess-t); 206 @return cubic-bezier-newton-raphson($x1, $x2, $x, $guess-t);
205 } @else if $init-slope == 0 { 207 } @else if $init-slope == 0 {
206 @return $guess-t; 208 @return $guess-t;
207 } @else { 209 } @else {
208 @return cubic-bezier-binary-subdivide($x1, $x2, $x, $intv-start, $intv-start + 1 / $cubic-bezier-sample-pool-size); 210 @return cubic-bezier-binary-subdivide($x1, $x2, $x, $intv-start, $intv-start + 1 / $cubic-bezier-sample-pool-size);
209 } 211 }
212}
213
214///
215/// Linear easing function.
216///
217/// @param {number} $x - Progress between 0 and 1 inclusive
218///
219/// @return {number}
220///
221@function linear($x) {
222 @return $x;
210} 223}
211 224
212/// 225///
@@ -217,7 +230,7 @@ $cubic-bezier-subdiv-max-iters: 10 !default;
217/// @return {number} 230/// @return {number}
218/// 231///
219@function ease($x) { 232@function ease($x) {
220 @return cubic-bezier(.25, .1, .25, 1, $x); 233 @return cubic-bezier(.25, .1, .25, 1, $x);
221} 234}
222 235
223/// 236///
@@ -228,7 +241,7 @@ $cubic-bezier-subdiv-max-iters: 10 !default;
228/// @return {number} 241/// @return {number}
229/// 242///
230@function ease-in($x) { 243@function ease-in($x) {
231 @return cubic-bezier(.42, 0, 1, 1, $x); 244 @return cubic-bezier(.42, 0, 1, 1, $x);
232} 245}
233 246
234/// 247///
@@ -239,7 +252,7 @@ $cubic-bezier-subdiv-max-iters: 10 !default;
239/// @return {number} 252/// @return {number}
240/// 253///
241@function ease-out($x) { 254@function ease-out($x) {
242 @return cubic-bezier(0, 0, .58, 1, $x); 255 @return cubic-bezier(0, 0, .58, 1, $x);
243} 256}
244 257
245/// 258///
@@ -250,7 +263,7 @@ $cubic-bezier-subdiv-max-iters: 10 !default;
250/// @return {number} 263/// @return {number}
251/// 264///
252@function ease-in-out($x) { 265@function ease-in-out($x) {
253 @return cubic-bezier(.42, 0, .58, 1, $x); 266 @return cubic-bezier(.42, 0, .58, 1, $x);
254} 267}
255 268
256/// 269///
@@ -261,7 +274,7 @@ $cubic-bezier-subdiv-max-iters: 10 !default;
261/// @return {number} 274/// @return {number}
262/// 275///
263@function ease-in-sine($x) { 276@function ease-in-sine($x) {
264 @return cubic-bezier(.47, 0, .745, .715, $x); 277 @return cubic-bezier(.47, 0, .745, .715, $x);
265} 278}
266 279
267/// 280///
@@ -272,7 +285,7 @@ $cubic-bezier-subdiv-max-iters: 10 !default;
272/// @return {number} 285/// @return {number}
273/// 286///
274@function ease-out-sine($x) { 287@function ease-out-sine($x) {
275 @return cubic-bezier(.39, .575, .565, 1, $x); 288 @return cubic-bezier(.39, .575, .565, 1, $x);
276} 289}
277 290
278/// 291///
@@ -283,7 +296,7 @@ $cubic-bezier-subdiv-max-iters: 10 !default;
283/// @return {number} 296/// @return {number}
284/// 297///
285@function ease-in-out-sine($x) { 298@function ease-in-out-sine($x) {
286 @return cubic-bezier(.445, .05, .55, .95, $x); 299 @return cubic-bezier(.445, .05, .55, .95, $x);
287} 300}
288 301
289/// 302///
@@ -294,7 +307,7 @@ $cubic-bezier-subdiv-max-iters: 10 !default;
294/// @return {number} 307/// @return {number}
295/// 308///
296@function ease-in-quad($x) { 309@function ease-in-quad($x) {
297 @return cubic-bezier(.55, .085, .68, .53, $x); 310 @return cubic-bezier(.55, .085, .68, .53, $x);
298} 311}
299 312
300/// 313///
@@ -305,7 +318,7 @@ $cubic-bezier-subdiv-max-iters: 10 !default;
305/// @return {number} 318/// @return {number}
306/// 319///
307@function ease-out-quad($x) { 320@function ease-out-quad($x) {
308 @return cubic-bezier(.25, .46, .45, .94, $x); 321 @return cubic-bezier(.25, .46, .45, .94, $x);
309} 322}
310 323
311/// 324///
@@ -316,7 +329,7 @@ $cubic-bezier-subdiv-max-iters: 10 !default;
316/// @return {number} 329/// @return {number}
317/// 330///
318@function ease-in-out-quad($x) { 331@function ease-in-out-quad($x) {
319 @return cubic-bezier(.455, .03, .515, .955, $x); 332 @return cubic-bezier(.455, .03, .515, .955, $x);
320} 333}
321 334
322/// 335///
@@ -327,7 +340,7 @@ $cubic-bezier-subdiv-max-iters: 10 !default;
327/// @return {number} 340/// @return {number}
328/// 341///
329@function ease-in-cubic($x) { 342@function ease-in-cubic($x) {
330 @return cubic-bezier(.55, .055, .675, .19, $x); 343 @return cubic-bezier(.55, .055, .675, .19, $x);
331} 344}
332 345
333/// 346///
@@ -338,7 +351,7 @@ $cubic-bezier-subdiv-max-iters: 10 !default;
338/// @return {number} 351/// @return {number}
339/// 352///
340@function ease-out-cubic($x) { 353@function ease-out-cubic($x) {
341 @return cubic-bezier(.215, .61, .355, 1, $x); 354 @return cubic-bezier(.215, .61, .355, 1, $x);
342} 355}
343 356
344/// 357///
@@ -349,7 +362,7 @@ $cubic-bezier-subdiv-max-iters: 10 !default;
349/// @return {number} 362/// @return {number}
350/// 363///
351@function ease-in-out-cubic($x) { 364@function ease-in-out-cubic($x) {
352 @return cubic-bezier(.645, .045, .355, 1, $x); 365 @return cubic-bezier(.645, .045, .355, 1, $x);
353} 366}
354 367
355/// 368///
@@ -360,7 +373,7 @@ $cubic-bezier-subdiv-max-iters: 10 !default;
360/// @return {number} 373/// @return {number}
361/// 374///
362@function ease-in-quart($x) { 375@function ease-in-quart($x) {
363 @return cubic-bezier(.895, .03, .685, .22, $x); 376 @return cubic-bezier(.895, .03, .685, .22, $x);
364} 377}
365 378
366/// 379///
@@ -371,7 +384,7 @@ $cubic-bezier-subdiv-max-iters: 10 !default;
371/// @return {number} 384/// @return {number}
372/// 385///
373@function ease-out-quart($x) { 386@function ease-out-quart($x) {
374 @return cubic-bezier(.165, .84, .44, 1, $x); 387 @return cubic-bezier(.165, .84, .44, 1, $x);
375} 388}
376 389
377/// 390///
@@ -382,7 +395,7 @@ $cubic-bezier-subdiv-max-iters: 10 !default;
382/// @return {number} 395/// @return {number}
383/// 396///
384@function ease-in-out-quart($x) { 397@function ease-in-out-quart($x) {
385 @return cubic-bezier(.77, 0, .175, 1, $x); 398 @return cubic-bezier(.77, 0, .175, 1, $x);
386} 399}
387 400
388/// 401///
@@ -393,7 +406,7 @@ $cubic-bezier-subdiv-max-iters: 10 !default;
393/// @return {number} 406/// @return {number}
394/// 407///
395@function ease-in-quint($x) { 408@function ease-in-quint($x) {
396 @return cubic-bezier(.755, .05, .855, .06, $x); 409 @return cubic-bezier(.755, .05, .855, .06, $x);
397} 410}
398 411
399/// 412///
@@ -404,7 +417,7 @@ $cubic-bezier-subdiv-max-iters: 10 !default;
404/// @return {number} 417/// @return {number}
405/// 418///
406@function ease-out-quint($x) { 419@function ease-out-quint($x) {
407 @return cubic-bezier(.23, 1, .32, 1, $x); 420 @return cubic-bezier(.23, 1, .32, 1, $x);
408} 421}
409 422
410/// 423///
@@ -415,7 +428,7 @@ $cubic-bezier-subdiv-max-iters: 10 !default;
415/// @return {number} 428/// @return {number}
416/// 429///
417@function ease-in-out-quint($x) { 430@function ease-in-out-quint($x) {
418 @return cubic-bezier(.86, 0, .07, 1, $x); 431 @return cubic-bezier(.86, 0, .07, 1, $x);
419} 432}
420 433
421/// 434///
@@ -426,7 +439,7 @@ $cubic-bezier-subdiv-max-iters: 10 !default;
426/// @return {number} 439/// @return {number}
427/// 440///
428@function ease-in-expo($x) { 441@function ease-in-expo($x) {
429 @return cubic-bezier(.95, .05, .795, .035, $x); 442 @return cubic-bezier(.95, .05, .795, .035, $x);
430} 443}
431 444
432/// 445///
@@ -437,7 +450,7 @@ $cubic-bezier-subdiv-max-iters: 10 !default;
437/// @return {number} 450/// @return {number}
438/// 451///
439@function ease-out-expo($x) { 452@function ease-out-expo($x) {
440 @return cubic-bezier(.19, 1, .22, 1, $x); 453 @return cubic-bezier(.19, 1, .22, 1, $x);
441} 454}
442 455
443/// 456///
@@ -448,7 +461,7 @@ $cubic-bezier-subdiv-max-iters: 10 !default;
448/// @return {number} 461/// @return {number}
449/// 462///
450@function ease-in-out-expo($x) { 463@function ease-in-out-expo($x) {
451 @return cubic-bezier(1, 0, 0, 1, $x); 464 @return cubic-bezier(1, 0, 0, 1, $x);
452} 465}
453 466
454/// 467///
@@ -459,7 +472,7 @@ $cubic-bezier-subdiv-max-iters: 10 !default;
459/// @return {number} 472/// @return {number}
460/// 473///
461@function ease-in-circ($x) { 474@function ease-in-circ($x) {
462 @return cubic-bezier(.6, .04, .98, .335, $x); 475 @return cubic-bezier(.6, .04, .98, .335, $x);
463} 476}
464 477
465/// 478///
@@ -470,7 +483,7 @@ $cubic-bezier-subdiv-max-iters: 10 !default;
470/// @return {number} 483/// @return {number}
471/// 484///
472@function ease-out-circ($x) { 485@function ease-out-circ($x) {
473 @return cubic-bezier(.075, .82, .165, 1, $x); 486 @return cubic-bezier(.075, .82, .165, 1, $x);
474} 487}
475 488
476/// 489///
@@ -481,5 +494,5 @@ $cubic-bezier-subdiv-max-iters: 10 !default;
481/// @return {number} 494/// @return {number}
482/// 495///
483@function ease-in-out-circ($x) { 496@function ease-in-out-circ($x) {
484 @return cubic-bezier(.785, .135, .15, .86, $x); 497 @return cubic-bezier(.785, .135, .15, .86, $x);
485} 498}
diff --git a/src/_functions.scss b/src/_functions.scss
index 9dd14b1..74cc1b5 100644
--- a/src/_functions.scss
+++ b/src/_functions.scss
@@ -10,9 +10,32 @@
10//// 10////
11 11
12@use 'sass:map'; 12@use 'sass:map';
13@use 'sass:list';
13@use 'sass:math'; 14@use 'sass:math';
15@use 'sass:string';
16@use 'sass:meta';
14@use './vars'; 17@use './vars';
15 18
19$numbers: ('0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9);
20
21$units: (
22 'px': 1px,
23 'cm': 1cm,
24 'mm': 1mm,
25 '%': 1%,
26 'ch': 1ch,
27 'pc': 1pc,
28 'in': 1in,
29 'em': 1em,
30 'rem': 1rem,
31 'pt': 1pt,
32 'ex': 1ex,
33 'vw': 1vw,
34 'vh': 1vh,
35 'vmin': 1vmin,
36 'vmax': 1vmax
37);
38
16/// 39///
17/// Replace a substring with a new string. 40/// Replace a substring with a new string.
18/// 41///
@@ -23,13 +46,13 @@
23/// @return {string} A string with all instances of $search replaced with $replace 46/// @return {string} A string with all instances of $search replaced with $replace
24/// 47///
25@function str-replace($string, $search, $replace) { 48@function str-replace($string, $search, $replace) {
26 $index: str-index($string, $search); 49 $index: string.index($string, $search);
27 50
28 @if $index { 51 @if $index {
29 @return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace); 52 @return string.slice($string, 1, $index - 1) + $replace + str-replace(string.slice($string, $index + string.length($search)), $search, $replace);
30 } 53 }
31 54
32 @return $string; 55 @return $string;
33} 56}
34 57
35/// 58///
@@ -41,17 +64,17 @@
41/// @return {string} 64/// @return {string}
42/// 65///
43@function str-implode($list, $glue: '') { 66@function str-implode($list, $glue: '') {
44 $result: ''; 67 $result: '';
45 68
46 @each $item in $list { 69 @each $item in $list {
47 $result: $result + if(length($item) > 1, str-implode($item, $glue), $item); 70 $result: $result + if(list.length($item) > 1, str-implode($item, $glue), $item);
48 71
49 @if $item != nth($list, length($list)) { 72 @if $item != list.nth($list, list.length($list)) {
50 $result: $result + $glue; 73 $result: $result + $glue;
51 } 74 }
52 } 75 }
53 76
54 @return $result; 77 @return $result;
55} 78}
56 79
57/// 80///
@@ -63,18 +86,18 @@
63/// 86///
64/// @return {list} A slice of the list 87/// @return {list} A slice of the list
65/// 88///
66@function list-slice($list, $start: 1, $end: length($list)) { 89@function list-slice($list, $start: 1, $end: list.length($list)) {
67 $result: (); 90 $result: ();
68 91
69 @if $end >= $start { 92 @if $end >= $start {
70 @for $i from $start through $end { 93 @for $i from $start through $end {
71 @if $i != 0 { 94 @if $i != 0 {
72 $result: append($result, nth($list, $i), list-separator($list)); 95 $result: list.append($result, list.nth($list, $i), list.separator($list));
73 } 96 }
74 } 97 }
75 } 98 }
76 99
77 @return $result; 100 @return $result;
78} 101}
79 102
80/// 103///
@@ -86,15 +109,15 @@
86/// @return {list} A list with $value at the beginning, followed by the other items 109/// @return {list} A list with $value at the beginning, followed by the other items
87/// 110///
88@function list-prepend($list, $value) { 111@function list-prepend($list, $value) {
89 $result: append((), $value, list-separator($list)); 112 $result: list.append((), $value, list.separator($list));
90 113
91 @if length($list) > 0 { 114 @if list.length($list) > 0 {
92 @for $i from 1 through length($list) { 115 @for $i from 1 through list.length($list) {
93 $result: append($result, nth($list, $i), list-separator($list)); 116 $result: list.append($result, list.nth($list, $i), list.separator($list));
94 } 117 }
95 } 118 }
96 119
97 @return $result; 120 @return $result;
98} 121}
99 122
100/// 123///
@@ -105,15 +128,15 @@
105/// @return {list} Teh reversed list 128/// @return {list} Teh reversed list
106/// 129///
107@function list-reverse($list) { 130@function list-reverse($list) {
108 @if length($list) == 0 { 131 @if list.length($list) == 0 {
109 @return $list; 132 @return $list;
110 } 133 }
111 134
112 $result: (); 135 $result: ();
113 @for $i from length($list) * -1 through -1 { 136 @for $i from list.length($list) * -1 through -1 {
114 $result: append($result, nth($list, abs($i))); 137 $result: list.append($result, list.nth($list, math.abs($i)));
115 } 138 }
116 @return $result; 139 @return $result;
117} 140}
118 141
119/// 142///
@@ -126,52 +149,52 @@
126/// 149///
127/// @return {list} Sorted list 150/// @return {list} Sorted list
128/// 151///
129@function quicksort($l, $left: 1, $right: length($l)) { 152@function quicksort($l, $left: 1, $right: list.length($l)) {
130 @if $left < $right { 153 @if $left < $right {
131 $pvr: quicksort-partition($l, $left, $right); 154 $pvr: quicksort-partition($l, $left, $right);
132 $pivot: nth($pvr, 1); 155 $pivot: list.nth($pvr, 1);
133 $l: nth($pvr, 2); 156 $l: list.nth($pvr, 2);
134 $l: quicksort($l, $left, $pivot); 157 $l: quicksort($l, $left, $pivot);
135 $l: quicksort($l, $pivot + 1, $right); 158 $l: quicksort($l, $pivot + 1, $right);
136 } 159 }
137 160
138 @return $l; 161 @return $l;
139} 162}
140 163
141/// 164///
142/// @access private 165/// @access private
143/// 166///
144@function quicksort-partition($l, $left, $right) { 167@function quicksort-partition($l, $left, $right) {
145 $start: true; 168 $start: true;
146 $i: $left; 169 $i: $left;
147 $j: $right - 1; 170 $j: $right - 1;
148 $pivot: nth($l, $right); 171 $pivot: list.nth($l, $right);
149 172
150 @while ($i < $j) or $start { 173 @while ($i < $j) or $start {
151 @while (nth($l, $i) < $pivot) and ($i < $right - 1) { 174 @while (list.nth($l, $i) < $pivot) and ($i < $right - 1) {
152 $i: $i + 1; 175 $i: $i + 1;
153 } 176 }
154 177
155 @while (nth($l, $j)>= $pivot) and ($j > $left) { 178 @while (list.nth($l, $j)>= $pivot) and ($j > $left) {
156 $j: $j - 1; 179 $j: $j - 1;
157 } 180 }
158 181
159 @if $i < $j { 182 @if $i < $j {
160 $i-val: nth($l, $i); 183 $i-val: list.nth($l, $i);
161 $l: set-nth($l, $i, nth($l, $j)); 184 $l: list.set-nth($l, $i, list.nth($l, $j));
162 $l: set-nth($l, $j, $i-val); 185 $l: list.set-nth($l, $j, $i-val);
163 } 186 }
164 187
165 $start: false; 188 $start: false;
166 } 189 }
167 190
168 @if nth($l, $i) > $pivot { 191 @if list.nth($l, $i) > $pivot {
169 $i-val: nth($l, $i); 192 $i-val: list.nth($l, $i);
170 $l: set-nth($l, $i, nth($l, $right)); 193 $l: list.set-nth($l, $i, list.nth($l, $right));
171 $l: set-nth($l, $right, $i-val); 194 $l: list.set-nth($l, $right, $i-val);
172 } 195 }
173 196
174 @return $i $l; 197 @return $i $l;
175} 198}
176 199
177/// 200///
@@ -185,10 +208,10 @@
185/// @return {any} Either the value assigned to $key or $default 208/// @return {any} Either the value assigned to $key or $default
186/// 209///
187@function map-get-default($map, $key, $keys...) { 210@function map-get-default($map, $key, $keys...) {
188 $default: nth($keys, length($keys)); 211 $default: list.nth($keys, list.length($keys));
189 $keys: list-slice($keys, 1, length($keys) - 1); 212 $keys: list-slice($keys, 1, list.length($keys) - 1);
190 213
191 @return if(map-has-key($map, $key, $keys...), map-get($map, $key, $keys...), $default); 214 @return if(map.has-key($map, $key, $keys...), map.get($map, $key, $keys...), $default);
192} 215}
193 216
194/// 217///
@@ -199,29 +222,29 @@
199/// @return {string} 222/// @return {string}
200/// 223///
201@function map-print($map) { 224@function map-print($map) {
202 $output: ''; 225 $output: '';
203 226
204 @each $key, $value in $map { 227 @each $key, $value in $map {
205 $value-str: ''; 228 $value-str: '';
206 229
207 @if type-of($value) == map { 230 @if meta.type-of($value) == map {
208 $value-str: '[ ' + map-print($value) + ' ]'; 231 $value-str: '[ ' + map-print($value) + ' ]';
209 } @else if type-of($value) == list { 232 } @else if meta.type-of($value) == list {
210 $value-str: '[ ' + str-implode($value, ', ') + ' ]'; 233 $value-str: '[ ' + str-implode($value, ', ') + ' ]';
211 } @else if type-of($value) == string { 234 } @else if meta.type-of($value) == string {
212 $value-str: '\'' + $value + '\''; 235 $value-str: '\'' + $value + '\'';
213 } @else { 236 } @else {
214 $value-str: $value; 237 $value-str: $value;
215 } 238 }
216 239
217 @if $output == '' { 240 @if $output == '' {
218 $output: $key + ': ' + $value-str; 241 $output: $key + ': ' + $value-str;
219 } @else { 242 } @else {
220 $output: $output + ', ' + $key + ': ' + $value-str; 243 $output: $output + ', ' + $key + ': ' + $value-str;
221 } 244 }
222 } 245 }
223 246
224 @return $output; 247 @return $output;
225} 248}
226 249
227/// 250///
@@ -233,35 +256,35 @@
233/// @return {bool} `true` if the selector matches at least one suffix, otherwise `false`. 256/// @return {bool} `true` if the selector matches at least one suffix, otherwise `false`.
234/// 257///
235@function selector-suffix-match($selector, $suffixes) { 258@function selector-suffix-match($selector, $suffixes) {
236 $match: true; 259 $match: true;
237 260
238 @each $sel in $selector { 261 @each $sel in $selector {
239 @if $match { 262 @if $match {
240 $sel-match: false; 263 $sel-match: false;
241 264
242 @each $suffix in $suffixes { 265 @each $suffix in $suffixes {
243 @if not $sel-match { 266 @if not $sel-match {
244 $suf-match: true; 267 $suf-match: true;
245 268
246 @for $i from 1 through length($suffix) { 269 @for $i from 1 through list.length($suffix) {
247 @if $suf-match and (nth($sel, -$i) != nth($suffix, -$i)) { 270 @if $suf-match and (list.nth($sel, -$i) != list.nth($suffix, -$i)) {
248 $suf-match: false; 271 $suf-match: false;
249 } 272 }
250 } 273 }
251 274
252 @if $suf-match { 275 @if $suf-match {
253 $sel-match: true; 276 $sel-match: true;
254 } 277 }
255 } 278 }
256 } 279 }
257 280
258 @if not $sel-match { 281 @if not $sel-match {
259 $match: false; 282 $match: false;
260 } 283 }
261 } 284 }
262 } 285 }
263 286
264 @return $match; 287 @return $match;
265} 288}
266 289
267/// 290///
@@ -272,7 +295,7 @@
272/// @return {number} Unit-less variable 295/// @return {number} Unit-less variable
273/// 296///
274@function strip-unit($n) { 297@function strip-unit($n) {
275 @return math.div($n, $n * 0 + 1); 298 @return math.div($n, $n * 0 + 1);
276} 299}
277 300
278/// 301///
@@ -284,7 +307,62 @@
284/// @return {number} Pixel value converted to rem 307/// @return {number} Pixel value converted to rem
285/// 308///
286@function px-to-rem($size, $base: vars.$root-size) { 309@function px-to-rem($size, $base: vars.$root-size) {
287 @return math.div($size, $base) * 1rem; 310 @return math.div($size, $base) * 1rem;
311}
312
313///
314/// Casts a string into a number
315///
316/// @param {string|number} $value
317///
318/// @return {number}
319///
320@function to-number($value) {
321 @if meta.type-of($value) == 'number' {
322 @return $value;
323 }
324 @if meta.type-of($value) != 'string' {
325 @error 'Value for `to-number` should be a number or a string.';
326 }
327
328 $result: 0;
329 $digits: 0;
330 $minus: string.slice($value, 1, 1) == '-';
331
332 @for $i from if($minus, 2, 1) through string.length($value) {
333 $character: string.slice($value, $i, $i);
334
335 @if not list.index(map.keys($numbers), $character) and $character != '.' {
336 @return to-length(if($minus, -$result, $result), string.slice($value, $i));
337 }
338
339 @if $character == '.' {
340 $digits: 1;
341 } @else if $digits == 0 {
342 $result: $result * 10 + map.get($numbers, $character);
343 } @else {
344 $digits: $digits * 10;
345 $result: $result + math.div(map.get($numbers, $character), $digits);
346 }
347 }
348
349 @return if($minus, -$result, $result);
350}
351
352///
353/// Add $unit to $value
354///
355/// @param {number} $value - Value to add unit to
356/// @param {string} $unit - String representation of the unit
357///
358/// @return {number} $value expressed in $unit
359///
360@function to-length($value, $unit) {
361 @if not list.index(map.keys($units), $unit) {
362 @error 'Invalid unit `#{$unit}`.';
363 }
364
365 @return $value * map.get($units, $unit);
288} 366}
289 367
290/// 368///
@@ -293,5 +371,5 @@
293/// @content 371/// @content
294/// 372///
295@mixin execute { 373@mixin execute {
296 @content; 374 @content;
297} 375}
diff --git a/src/_gradients.scss b/src/_gradients.scss
index 6575482..345a9f1 100644
--- a/src/_gradients.scss
+++ b/src/_gradients.scss
@@ -15,8 +15,11 @@
15/// @access public 15/// @access public
16//// 16////
17 17
18@use 'sass:color';
19@use 'sass:list';
18@use 'sass:math'; 20@use 'sass:math';
19@use 'sass:meta'; 21@use 'sass:meta';
22@use 'sass:string';
20@use './functions'; 23@use './functions';
21@use './easing'; 24@use './easing';
22 25
@@ -135,144 +138,136 @@ $easing-gradient-steps: 10 !default;
135/// } 138/// }
136/// 139///
137@function easing-gradient($type, $dir, $stop, $stops...) { 140@function easing-gradient($type, $dir, $stop, $stops...) {
138 $pos-template: null; 141 $pos-template: null;
139 $stops: functions.list-prepend($stops, $stop); 142 $stops: functions.list-prepend($stops, $stop);
140 143
141 $last-positioned-stop: 1; 144 $last-positioned-stop: 1;
142 $generated-stops: (); 145 $generated-stops: ();
143 146
144 // 147 //
145 // Generate gradient 148 // Generate gradient
146 // 149 //
147 150
148 @for $i from 1 through length($stops) { 151 @for $i from 1 through list.length($stops) {
149 $stop: nth($stops, $i); 152 $stop: list.nth($stops, $i);
150 153
151 @if $i == 1 { 154 @if $i == 1 {
152 @if not easing-gradient-is-color-stop($stop) { 155 @if not easing-gradient-is-color-stop($stop) {
153 @error 'The first color stop argument must be a color stop.'; 156 @error 'The first color stop argument must be a color stop.';
154 } 157 }
155 158
156 @if type-of($stop) == color { 159 @if meta.type-of($stop) == color {
157 // 160 //
158 // The first color stop is unpositioned. The default position for the first 161 // The first color stop is unpositioned. The default position for the first
159 // color stop is 0, which is explicitly added for easier calculations. 162 // color stop is 0, which is explicitly added for easier calculations.
160 // 163 //
161 164
162 $stop: $stop 0; 165 $stop: $stop 0;
163 $stops: set-nth($stops, $i, $stop); 166 $stops: list.set-nth($stops, $i, $stop);
164 } 167 }
165 168
166 $generated-stops: append($generated-stops, functions.str-implode($stop, ' ')); 169 $generated-stops: list.append($generated-stops, functions.str-implode($stop, ' '));
167 } @else if easing-gradient-is-positioned-color-stop($stop) or ($i == length($stops)) { 170 } @else if easing-gradient-is-positioned-color-stop($stop) or ($i == list.length($stops)) {
168 @if not easing-gradient-is-color-stop($stop) { 171 @if not easing-gradient-is-color-stop($stop) {
169 @error 'The last color stop argument must be a color stop.'; 172 @error 'The last color stop argument must be a color stop.';
170 } 173 }
171 174
172 // 175 //
173 // Either the current stops list item is a positioned color stop, or the end of 176 // Either the current stops list item is a positioned color stop, or the end of
174 // the stops list has been reached. 177 // the stops list has been reached.
175 // 178 //
176 179
177 @if (type-of($stop) == color) and ($i == length($stops)) { 180 @if (meta.type-of($stop) == color) and ($i == list.length($stops)) {
178 // 181 //
179 // The current stop is an unpositioned color stop, which means this is the end 182 // The current stop is an unpositioned color stop, which means this is the end
180 // of the stops list. The default position for the last color stop is 100%, which 183 // of the stops list. The default position for the last color stop is 100%, which
181 // is explicitly added for easier calculations. 184 // is explicitly added for easier calculations.
182 // 185 //
183 186
184 $stop: $stop 100%; 187 $stop: $stop 100%;
185 $stops: set-nth($stops, $i, $stop); 188 $stops: list.set-nth($stops, $i, $stop);
186 } 189 }
187 190
188 // 191 //
189 // Now the current color stop is guaranteed to be a positioned color stop. 192 // Now the current color stop is guaranteed to be a positioned color stop.
190 // 193 //
191 194
192 @if $i > $last-positioned-stop + 1 { 195 @if $i > $last-positioned-stop + 1 {
193 // 196 //
194 // There is at least one stops list item (unpositioned color stop or easing function) 197 // There is at least one stops list item (unpositioned color stop or easing function)
195 // between the last positioned color stop and the current stops list item. Interpolate 198 // between the last positioned color stop and the current stops list item. Interpolate
196 // the positions of all stops list items that are color stops. 199 // the positions of all stops list items that are color stops.
197 // 200 //
198 201
199 $interpolated-stops: easing-gradient-interpolate-stop-positions( 202 $interpolated-stops: easing-gradient-interpolate-stop-positions(list.nth($stops, $last-positioned-stop),
200 nth($stops, $last-positioned-stop), 203 functions.list-slice($stops, $last-positioned-stop + 1, $i - 1),
201 functions.list-slice($stops, $last-positioned-stop + 1, $i - 1), 204 $stop);
202 $stop
203 );
204 205
205 $new-stops: join( 206 $new-stops: list.join(functions.list-slice($stops, 1, $last-positioned-stop),
206 functions.list-slice($stops, 1, $last-positioned-stop), 207 $interpolated-stops);
207 $interpolated-stops 208 $new-stops: list.join($new-stops,
208 ); 209 functions.list-slice($stops, $i));
209 $new-stops: join( 210 $stops: $new-stops;
210 $new-stops, 211 }
211 functions.list-slice($stops, $i)
212 );
213 $stops: $new-stops;
214 }
215 212
216 // 213 //
217 // Now all color stops between this one and the last positioned one have 214 // Now all color stops between this one and the last positioned one have
218 // interpolated positions. 215 // interpolated positions.
219 // Next task is to perform an easing transition between all color stops that 216 // Next task is to perform an easing transition between all color stops that
220 // have an easing function specified. The rest can be left alone since the 217 // have an easing function specified. The rest can be left alone since the
221 // browser will automatically apply a linear transition between them. 218 // browser will automatically apply a linear transition between them.
222 // 219 //
223 220
224 $j: $last-positioned-stop + 1; 221 $j: $last-positioned-stop + 1;
225 @while $j <= $i { 222 @while $j <= $i {
226 $easing: null; 223 $easing: null;
227 $prev-stop: nth($stops, $j - 1); 224 $prev-stop: list.nth($stops, $j - 1);
228 $next-stop: nth($stops, $j); 225 $next-stop: list.nth($stops, $j);
229 226
230 @if not easing-gradient-is-color-stop($next-stop) { 227 @if not easing-gradient-is-color-stop($next-stop) {
231 $j: $j + 1; 228 $j: $j + 1;
232 229
233 $easing: $next-stop; 230 $easing: $next-stop;
234 $next-stop: nth($stops, $j); 231 $next-stop: list.nth($stops, $j);
235 232
236 @if not easing-gradient-is-color-stop($next-stop) { 233 @if not easing-gradient-is-color-stop($next-stop) {
237 @error 'There can be at most one interpolation hint between to color stops.'; 234 @error 'There can be at most one interpolation hint between to color stops.';
238 } 235 }
239 } 236 }
240 237
241 @if $easing != null { 238 @if $easing != null {
242 @if type-of($easing) == number { 239 @if meta.type-of($easing) == number {
243 @error 'Midpoint shifts are not supported.'; 240 @error 'Midpoint shifts are not supported.';
244 } 241 }
245 242
246 $easing-func: null; 243 $easing-func: null;
247 $easing-args: (); 244 $easing-args: ();
248 245
249 @if type-of($easing) == list { 246 @if meta.type-of($easing) == list {
250 $easing-args: functions.list-slice($easing, 2); 247 $easing-args: functions.list-slice($easing, 2);
251 $easing: nth($easing, 1); 248 $easing: list.nth($easing, 1);
252 } 249 }
253 250
254 $generated-stops: join( 251 $generated-stops: list.join($generated-stops,
255 $generated-stops, 252 easing-gradient-ease-stops($prev-stop, $next-stop, $easing, $easing-args));
256 easing-gradient-ease-stops($prev-stop, $next-stop, $easing, $easing-args) 253 } @else {
257 ); 254 $generated-stops: list.append($generated-stops, functions.str-implode($next-stop, ' '));
258 } @else { 255 }
259 $generated-stops: append($generated-stops, functions.str-implode($next-stop, ' '));
260 }
261 256
262 $j: $j + 1; 257 $j: $j + 1;
263 } 258 }
264 259
265 $last-positioned-stop: $i; 260 $last-positioned-stop: $i;
266 } 261 }
267 } 262 }
268 263
269 @if $type == 'linear' { 264 @if $type == 'linear' {
270 @return linear-gradient($dir, unquote(functions.str-implode($generated-stops, ', '))); 265 @return linear-gradient($dir, string.unquote(functions.str-implode($generated-stops, ', ')));
271 } @else if $type == 'radial' { 266 } @else if $type == 'radial' {
272 @return radial-gradient($dir, unquote(functions.str-implode($generated-stops, ', '))); 267 @return radial-gradient($dir, string.unquote(functions.str-implode($generated-stops, ', ')));
273 } @else { 268 } @else {
274 @error 'Invalid gradient type: #{inspect($type)}.'; 269 @error 'Invalid gradient type: #{inspect($type)}.';
275 } 270 }
276} 271}
277 272
278/// 273///
@@ -281,7 +276,7 @@ $easing-gradient-steps: 10 !default;
281/// @see {function} easing-gradient 276/// @see {function} easing-gradient
282/// 277///
283@function easing-linear-gradient($dir, $stop, $stops...) { 278@function easing-linear-gradient($dir, $stop, $stops...) {
284 @return easing-gradient('linear', $dir, $stop, $stops...); 279 @return easing-gradient('linear', $dir, $stop, $stops...);
285} 280}
286 281
287/// 282///
@@ -290,7 +285,7 @@ $easing-gradient-steps: 10 !default;
290/// @see {function} easing-gradient 285/// @see {function} easing-gradient
291/// 286///
292@function easing-radial-gradient($dir, $stop, $stops...) { 287@function easing-radial-gradient($dir, $stop, $stops...) {
293 @return easing-gradient('radial', $dir, $stop, $stops...); 288 @return easing-gradient('radial', $dir, $stop, $stops...);
294} 289}
295 290
296/// 291///
@@ -299,24 +294,24 @@ $easing-gradient-steps: 10 !default;
299/// @access private 294/// @access private
300/// 295///
301@function easing-gradient-ease-stops($prev-stop, $next-stop, $easing, $easing-args: ()) { 296@function easing-gradient-ease-stops($prev-stop, $next-stop, $easing, $easing-args: ()) {
302 @if $easing == 'steps' { 297 @if $easing == 'steps' {
303 $steps: null; 298 $steps: null;
304 $jump: null; 299 $jump: null;
305 300
306 @if length($easing-args) > 1 { 301 @if list.length($easing-args) > 1 {
307 $steps: nth($easing-args, 1); 302 $steps: list.nth($easing-args, 1);
308 $jump: nth($easing-args, 2); 303 $jump: list.nth($easing-args, 2);
309 } @else { 304 } @else {
310 $steps: nth($easing-args, 1); 305 $steps: list.nth($easing-args, 1);
311 $jump: jump-end; 306 $jump: jump-end;
312 } 307 }
313 308
314 @return easing-gradient-steps-stops($prev-stop, $next-stop, $steps, $jump); 309 @return easing-gradient-steps-stops($prev-stop, $next-stop, $steps, $jump);
315 } @else { 310 } @else {
316 $easing-func: get-function($easing, $module: easing); 311 $easing-func: meta.get-function($easing, $module: easing);
317 312
318 @return easing-gradient-bezier-stops($prev-stop, $next-stop, $easing-func, $easing-args); 313 @return easing-gradient-bezier-stops($prev-stop, $next-stop, $easing-func, $easing-args);
319 } 314 }
320} 315}
321 316
322/// 317///
@@ -325,74 +320,74 @@ $easing-gradient-steps: 10 !default;
325/// @access private 320/// @access private
326/// 321///
327@function easing-gradient-bezier-stops($prev-stop, $next-stop, $easing-func, $easing-args: ()) { 322@function easing-gradient-bezier-stops($prev-stop, $next-stop, $easing-func, $easing-args: ()) {
328 $prev-stop-color: nth($prev-stop, 1); 323 $prev-stop-color: list.nth($prev-stop, 1);
329 $prev-stop-pos: nth($prev-stop, 2); 324 $prev-stop-pos: list.nth($prev-stop, 2);
330 $next-stop-color: nth($next-stop, 1); 325 $next-stop-color: list.nth($next-stop, 1);
331 $next-stop-pos: nth($next-stop, 2); 326 $next-stop-pos: list.nth($next-stop, 2);
332 327
333 $stops: (); 328 $stops: ();
334 329
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) { 330 @if ((meta.type-of($prev-stop-pos) == number) and (meta.type-of($next-stop-pos) == number) and (math.unit($prev-stop-pos) == math.unit($next-stop-pos))) or ($prev-stop-pos == 0) or ($next-stop-pos == 0) {
336 // 331 //
337 // The transition color stop positions can be statically calculated. 332 // The transition color stop positions can be statically calculated.
338 // 333 //
339 334
340 $distance: $next-stop-pos - $prev-stop-pos; 335 $distance: $next-stop-pos - $prev-stop-pos;
341 336
342 @for $i from 1 through $easing-gradient-steps { 337 @for $i from 1 through $easing-gradient-steps {
343 $perc: math.div($i, $easing-gradient-steps); 338 $perc: math.div($i, $easing-gradient-steps);
344 339
345 $color: null; 340 $color: null;
346 $pos: $prev-stop-pos + $perc * $distance; 341 $pos: $prev-stop-pos + $perc * $distance;
347 @if $perc == 1 { 342 @if $perc == 1 {
348 $color: $next-stop-color; 343 $color: $next-stop-color;
349 } @else { 344 } @else {
350 $color: mix($next-stop-color, $prev-stop-color, call($easing-func, append($easing-args, $perc)...) * 100%); 345 $color: color.mix($next-stop-color, $prev-stop-color, meta.call($easing-func, list.append($easing-args, $perc)...) * 100%);
351 } 346 }
352 347
353 $stops: append($stops, $color + ' ' + $pos); 348 $stops: list.append($stops, $color + ' ' + $pos);
354 } 349 }
355 } @else { 350 } @else {
356 // 351 //
357 // The transition color stop positions have to be dynamically calculated with the calc() function. 352 // The transition color stop positions have to be dynamically calculated with the calc() function.
358 // 353 //
359 354
360 @if type-of($prev-stop-pos) != number { 355 @if meta.type-of($prev-stop-pos) != number {
361 // must be calc() 356 // must be calc()
362 @if type-of($prev-stop-pos) != calculation { 357 @if meta.type-of($prev-stop-pos) != calculation {
363 @error 'Invalid color stop position: #{inspect($prev-stop-pos)}'; 358 @error 'Invalid color stop position: #{inspect($prev-stop-pos)}';
364 } 359 }
365 360
366 $prev-stop-pos: meta.calc-args($prev-stop-pos); 361 $prev-stop-pos: meta.calc-args($prev-stop-pos);
367 } 362 }
368 363
369 @if type-of($next-stop-pos) != number { 364 @if meta.type-of($next-stop-pos) != number {
370 // must be calc() 365 // must be calc()
371 @if type-of($next-stop-pos) != calculation { 366 @if meta.type-of($next-stop-pos) != calculation {
372 @error 'Invalid color stop position: #{inspect($next-stop-pos)}'; 367 @error 'Invalid color stop position: #{inspect($next-stop-pos)}';
373 } 368 }
374 369
375 $next-stop-pos: meta.calc-args($next-stop-pos); 370 $next-stop-pos: meta.calc-args($next-stop-pos);
376 } 371 }
377 372
378 @for $i from 1 through $easing-gradient-steps { 373 @for $i from 1 through $easing-gradient-steps {
379 $perc: math.div($i, $easing-gradient-steps); 374 $perc: math.div($i, $easing-gradient-steps);
380 375
381 $color: null; 376 $color: null;
382 $pos: null; 377 $pos: null;
383 @if $perc == 1 { 378 @if $perc == 1 {
384 $color: $next-stop-color; 379 $color: $next-stop-color;
385 $pos: calc(#{$next-stop-pos}); 380 $pos: calc(#{$next-stop-pos});
386 } @else { 381 } @else {
387 $color: mix($next-stop-color, $prev-stop-color, call($easing-func, append($easing-args, $perc)...) * 100%); 382 $color: color.mix($next-stop-color, $prev-stop-color, meta.call($easing-func, list.append($easing-args, $perc)...) * 100%);
388 $pos: calc(#{$prev-stop-pos} + (#{$next-stop-pos} - #{$prev-stop-pos}) * #{$perc}); 383 $pos: calc(#{$prev-stop-pos} + (#{$next-stop-pos} - #{$prev-stop-pos}) * #{$perc});
389 } 384 }
390 385
391 $stops: append($stops, $color + ' ' + $pos); 386 $stops: list.append($stops, $color + ' ' + $pos);
392 } 387 }
393 } 388 }
394 389
395 @return $stops; 390 @return $stops;
396} 391}
397 392
398/// 393///
@@ -401,110 +396,110 @@ $easing-gradient-steps: 10 !default;
401/// @access private 396/// @access private
402/// 397///
403@function easing-gradient-steps-stops($prev-stop, $next-stop, $steps, $jump: jump-end) { 398@function easing-gradient-steps-stops($prev-stop, $next-stop, $steps, $jump: jump-end) {
404 $prev-stop-color: nth($prev-stop, 1); 399 $prev-stop-color: list.nth($prev-stop, 1);
405 $prev-stop-pos: nth($prev-stop, 2); 400 $prev-stop-pos: list.nth($prev-stop, 2);
406 $next-stop-color: nth($next-stop, 1); 401 $next-stop-color: list.nth($next-stop, 1);
407 $next-stop-pos: nth($next-stop, 2); 402 $next-stop-pos: list.nth($next-stop, 2);
408 403
409 $stops: (); 404 $stops: ();
410 405
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) { 406 @if ((meta.type-of($prev-stop-pos) == number) and (meta.type-of($next-stop-pos) == number) and (math.unit($prev-stop-pos) == math.unit($next-stop-pos))) or ($prev-stop-pos == 0) or ($next-stop-pos == 0) {
412 // 407 //
413 // The transition color stop positions can be statically calculated. 408 // The transition color stop positions can be statically calculated.
414 // 409 //
415 410
416 $distance: $next-stop-pos - $prev-stop-pos; 411 $distance: $next-stop-pos - $prev-stop-pos;
417 412
418 @for $i from 1 through $steps { 413 @for $i from 1 through $steps {
419 $x1: math.div($i - 1, $steps); 414 $xx1: math.div($i - 1, $steps);
420 $x2: math.div($i, $steps); 415 $xx2: math.div($i, $steps);
421 $y: null; 416 $y: null;
422 417
423 @if $jump == jump-start { 418 @if $jump == jump-start {
424 $y: math.div($i, $steps); 419 $y: math.div($i, $steps);
425 } @else if $jump == jump-end { 420 } @else if $jump == jump-end {
426 $y: math.div($i - 1, $steps); 421 $y: math.div($i - 1, $steps);
427 } @else if $jump == jump-both { 422 } @else if $jump == jump-both {
428 $y: math.div($i, $steps + 1); 423 $y: math.div($i, $steps + 1);
429 } @else if $jump == jump-none { 424 } @else if $jump == jump-none {
430 $y: math.div($i - 1, $steps - 1); 425 $y: math.div($i - 1, $steps - 1);
431 } @else { 426 } @else {
432 @error 'Invalid $jump: #{inspect($jump)}'; 427 @error 'Invalid $jump: #{inspect($jump)}';
433 } 428 }
434 429
435 $color: null; 430 $color: null;
436 $pos1: if($x1 == 0, $prev-stop-pos, $prev-stop-pos + $x1 * $distance); 431 $pos1: if($xx1 == 0, $prev-stop-pos, $prev-stop-pos + $xx1 * $distance);
437 $pos2: if($x2 == 1, $next-stop-pos, $prev-stop-pos + $x2 * $distance); 432 $pos2: if($xx2 == 1, $next-stop-pos, $prev-stop-pos + $xx2 * $distance);
438 433
439 @if $y == 0 { 434 @if $y == 0 {
440 $color: $prev-stop-color; 435 $color: $prev-stop-color;
441 } @else if $y == 1 { 436 } @else if $y == 1 {
442 $color: $next-stop-color; 437 $color: $next-stop-color;
443 } @else { 438 } @else {
444 $color: mix($next-stop-color, $prev-stop-color, $y * 100%); 439 $color: color.mix($next-stop-color, $prev-stop-color, $y * 100%);
445 } 440 }
446 441
447 $stops: append($stops, $color + ' ' + $pos1); 442 $stops: list.append($stops, $color + ' ' + $pos1);
448 $stops: append($stops, $color + ' ' + $pos2); 443 $stops: list.append($stops, $color + ' ' + $pos2);
449 } 444 }
450 } @else { 445 } @else {
451 // 446 //
452 // The transition color stop positions have to be dynamically calculated with the calc() function. 447 // The transition color stop positions have to be dynamically calculated with the calc() function.
453 // 448 //
454 449
455 @if type-of($prev-stop-pos) != number { 450 @if meta.type-of($prev-stop-pos) != number {
456 // must be calc() 451 // must be calc()
457 @if type-of($prev-stop-pos) != calculation { 452 @if meta.type-of($prev-stop-pos) != calculation {
458 @error 'Invalid color stop position: #{inspect($prev-stop-pos)}'; 453 @error 'Invalid color stop position: #{inspect($prev-stop-pos)}';
459 } 454 }
460 455
461 $prev-stop-pos: meta.calc-args($prev-stop-pos); 456 $prev-stop-pos: meta.calc-args($prev-stop-pos);
462 } 457 }
463 458
464 @if type-of($next-stop-pos) != number { 459 @if meta.type-of($next-stop-pos) != number {
465 // must be calc() 460 // must be calc()
466 @if type-of($next-stop-pos) != calculation { 461 @if meta.type-of($next-stop-pos) != calculation {
467 @error 'Invalid color stop position: #{inspect($next-stop-pos)}'; 462 @error 'Invalid color stop position: #{inspect($next-stop-pos)}';
468 } 463 }
469 464
470 $next-stop-pos: meta.calc-args($next-stop-pos); 465 $next-stop-pos: meta.calc-args($next-stop-pos);
471 } 466 }
472 467
473 @for $i from 1 through $steps { 468 @for $i from 1 through $steps {
474 $x1: math.div($i - 1, $steps); 469 $xx1: math.div($i - 1, $steps);
475 $x2: math.div($i, $steps); 470 $xx2: math.div($i, $steps);
476 $y: null; 471 $y: null;
477 472
478 @if $jump == jump-start { 473 @if $jump == jump-start {
479 $y: math.div($i, $steps); 474 $y: math.div($i, $steps);
480 } @else if $jump == jump-end { 475 } @else if $jump == jump-end {
481 $y: math.div($i - 1, $steps); 476 $y: math.div($i - 1, $steps);
482 } @else if $jump == jump-both { 477 } @else if $jump == jump-both {
483 $y: math.div($i, $steps + 1); 478 $y: math.div($i, $steps + 1);
484 } @else if $jump == jump-none { 479 } @else if $jump == jump-none {
485 $y: math.div($i - 1, $steps - 1); 480 $y: math.div($i - 1, $steps - 1);
486 } @else { 481 } @else {
487 @error 'Invalid $jump: #{inspect($jump)}'; 482 @error 'Invalid $jump: #{inspect($jump)}';
488 } 483 }
489 484
490 $color: null; 485 $color: null;
491 $pos1: if($x1 == 0, $prev-stop-pos, calc(#{$prev-stop-pos} + (#{$next-stop-pos} - #{$prev-stop-pos}) * #{$x1})); 486 $pos1: if($xx1 == 0, $prev-stop-pos, calc(#{$prev-stop-pos} + (#{$next-stop-pos} - #{$prev-stop-pos}) * #{$xx1}));
492 $pos2: if($x2 == 1, $next-stop-pos, calc(#{$prev-stop-pos} + (#{$next-stop-pos} - #{$prev-stop-pos}) * #{$x2})); 487 $pos2: if($xx2 == 1, $next-stop-pos, calc(#{$prev-stop-pos} + (#{$next-stop-pos} - #{$prev-stop-pos}) * #{$xx2}));
493 488
494 @if $y == 0 { 489 @if $y == 0 {
495 $color: $prev-stop-color; 490 $color: $prev-stop-color;
496 } @else if $y == 1 { 491 } @else if $y == 1 {
497 $color: $next-stop-color; 492 $color: $next-stop-color;
498 } @else { 493 } @else {
499 $color: mix($next-stop-color, $prev-stop-color, $y * 100%); 494 $color: color.mix($next-stop-color, $prev-stop-color, $y * 100%);
500 } 495 }
501 496
502 $stops: append($stops, $color + ' ' + $pos1); 497 $stops: list.append($stops, $color + ' ' + $pos1);
503 $stops: append($stops, $color + ' ' + $pos2); 498 $stops: list.append($stops, $color + ' ' + $pos2);
504 } 499 }
505 } 500 }
506 501
507 @return $stops; 502 @return $stops;
508} 503}
509 504
510/// 505///
@@ -513,72 +508,72 @@ $easing-gradient-steps: 10 !default;
513/// @access private 508/// @access private
514/// 509///
515@function easing-gradient-interpolate-stop-positions($prev-stop, $stops, $next-stop) { 510@function easing-gradient-interpolate-stop-positions($prev-stop, $stops, $next-stop) {
516 $prev-stop-pos: nth($prev-stop, 2); 511 $prev-stop-pos: list.nth($prev-stop, 2);
517 $next-stop-pos: nth($next-stop, 2); 512 $next-stop-pos: list.nth($next-stop, 2);
518 513
519 $stops-num: 0; 514 $stops-num: 0;
520 @for $i from 1 through length($stops) { 515 @for $i from 1 through list.length($stops) {
521 $stop: nth($stops, $i); 516 $stop: list.nth($stops, $i);
522 @if easing-gradient-is-color-stop($stop) { 517 @if easing-gradient-is-color-stop($stop) {
523 $stops-num: $stops-num + 1; 518 $stops-num: $stops-num + 1;
524 } 519 }
525 } 520 }
526 521
527 $i: 1; 522 $i: 1;
528 $cur-stop-num: 1; 523 $cur-stop-num: 1;
529 524
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) { 525 @if ((meta.type-of($prev-stop-pos) == number) and (meta.type-of($next-stop-pos) == number) and (math.unit($prev-stop-pos) == math.unit($next-stop-pos))) or ($prev-stop-pos == 0) or ($next-stop-pos == 0) {
531 // 526 //
532 // The color stop positions can be statically calculated. 527 // The color stop positions can be statically calculated.
533 // 528 //
534 529
535 $distance: $next-stop-pos - $prev-stop-pos; 530 $distance: $next-stop-pos - $prev-stop-pos;
536 531
537 @for $i from 1 through length($stops) { 532 @for $i from 1 through list.length($stops) {
538 $stop: nth($stops, $i); 533 $stop: list.nth($stops, $i);
539 @if easing-gradient-is-color-stop($stop) { 534 @if easing-gradient-is-color-stop($stop) {
540 $pos: $prev-stop-pos + math.div($distance, $stops-num + 1) * $cur-stop-num; 535 $pos: $prev-stop-pos + math.div($distance, $stops-num + 1) * $cur-stop-num;
541 $stops: set-nth($stops, $i, $stop $pos); 536 $stops: list.set-nth($stops, $i, $stop $pos);
542 537
543 $cur-stop-num: $cur-stop-num + 1; 538 $cur-stop-num: $cur-stop-num + 1;
544 } 539 }
545 } 540 }
546 } @else { 541 } @else {
547 // 542 //
548 // The color stop positions have to be dynamically calculated with the calc() function. 543 // The color stop positions have to be dynamically calculated with the calc() function.
549 // 544 //
550 545
551 @if type-of($prev-stop-pos) != number { 546 @if meta.type-of($prev-stop-pos) != number {
552 // must be calc() 547 // must be calc()
553 @if type-of($prev-stop-pos) != calculation { 548 @if meta.type-of($prev-stop-pos) != calculation {
554 @error 'Invalid color stop position: #{inspect($prev-stop-pos)}'; 549 @error 'Invalid color stop position: #{inspect($prev-stop-pos)}';
555 } 550 }
556 551
557 $prev-stop-pos: meta.calc-args($prev-stop-pos); 552 $prev-stop-pos: meta.calc-args($prev-stop-pos);
558 } 553 }
559 554
560 @if type-of($next-stop-pos) != number { 555 @if meta.type-of($next-stop-pos) != number {
561 // must be calc() 556 // must be calc()
562 @if type-of($next-stop-pos) != calculation { 557 @if meta.type-of($next-stop-pos) != calculation {
563 @error 'Invalid color stop position: #{inspect($next-stop-pos)}'; 558 @error 'Invalid color stop position: #{inspect($next-stop-pos)}';
564 } 559 }
565 560
566 $next-stop-pos: meta.calc-args($next-stop-pos); 561 $next-stop-pos: meta.calc-args($next-stop-pos);
567 } 562 }
568 563
569 @for $i from 1 through length($stops) { 564 @for $i from 1 through list.length($stops) {
570 $stop: nth($stops, $i); 565 $stop: list.nth($stops, $i);
571 @if easing-gradient-is-color-stop($stop) { 566 @if easing-gradient-is-color-stop($stop) {
572 $perc: math.div($cur-stop-num, $stops-num + 1); 567 $perc: math.div($cur-stop-num, $stops-num + 1);
573 $pos: calc(#{$prev-stop-pos} + (#{$next-stop-pos} - #{$prev-stop-pos}) * #{$perc}); 568 $pos: calc(#{$prev-stop-pos} + (#{$next-stop-pos} - #{$prev-stop-pos}) * #{$perc});
574 $stops: set-nth($stops, $i, $stop $pos); 569 $stops: list.set-nth($stops, $i, $stop $pos);
575 570
576 $cur-stop-num: $cur-stop-num + 1; 571 $cur-stop-num: $cur-stop-num + 1;
577 } 572 }
578 } 573 }
579 } 574 }
580 575
581 @return $stops; 576 @return $stops;
582} 577}
583 578
584/// 579///
@@ -587,7 +582,7 @@ $easing-gradient-steps: 10 !default;
587/// @access private 582/// @access private
588/// 583///
589@function easing-gradient-is-color-stop($input) { 584@function easing-gradient-is-color-stop($input) {
590 @return (type-of($input) == color) or easing-gradient-is-positioned-color-stop($input); 585 @return (meta.type-of($input) == color) or easing-gradient-is-positioned-color-stop($input);
591} 586}
592 587
593/// 588///
@@ -596,5 +591,5 @@ $easing-gradient-steps: 10 !default;
596/// @access private 591/// @access private
597/// 592///
598@function easing-gradient-is-positioned-color-stop($input) { 593@function easing-gradient-is-positioned-color-stop($input) {
599 @return (type-of($input) == list) and (type-of(nth($input, 1)) == color); 594 @return (meta.type-of($input) == list) and (meta.type-of(list.nth($input, 1)) == color);
600} 595}
diff --git a/src/_harmony.scss b/src/_harmony.scss
index aaab726..c0cb772 100644
--- a/src/_harmony.scss
+++ b/src/_harmony.scss
@@ -8,7 +8,10 @@
8/// @access public 8/// @access public
9//// 9////
10 10
11@use 'sass:list';
12@use 'sass:map';
11@use 'sass:math'; 13@use 'sass:math';
14@use 'sass:meta';
12@use './functions'; 15@use './functions';
13@use './responsive'; 16@use './responsive';
14 17
@@ -26,35 +29,35 @@
26/// @return {number} 29/// @return {number}
27/// 30///
28@function modular-scale($times, $base, $ratio) { 31@function modular-scale($times, $base, $ratio) {
29 @if type-of($base) == number { 32 @if meta.type-of($base) == number {
30 @return $base * math.pow($ratio, $times); 33 @return $base * math.pow($ratio, $times);
31 } 34 }
32 35
33 $main-base: nth($base, 1); 36 $main-base: list.nth($base, 1);
34 $norm-bases: (); 37 $norm-bases: ();
35 38
36 @each $b in functions.list-slice($base, 2) { 39 @each $b in functions.list-slice($base, 2) {
37 @if $b > $main-base { 40 @if $b > $main-base {
38 @while $b > $main-base { 41 @while $b > $main-base {
39 $b: math.div($b, $ratio); 42 $b: math.div($b, $ratio);
40 } 43 }
41 $b: $b * $ratio; 44 $b: $b * $ratio;
42 } @else if $b < $main-base { 45 } @else if $b < $main-base {
43 @while $b < $main-base { 46 @while $b < $main-base {
44 $b: $b * $ratio; 47 $b: $b * $ratio;
45 } 48 }
46 } 49 }
47 50
48 $norm-bases: append($norm-bases, $b); 51 $norm-bases: list.append($norm-bases, $b);
49 } 52 }
50 53
51 $all-bases: append($norm-bases, $main-base); 54 $all-bases: list.append($norm-bases, $main-base);
52 $all-bases: functions.quicksort($all-bases); 55 $all-bases: functions.quicksort($all-bases);
53 56
54 $base-index: $times % length($all-bases) + 1; 57 $base-index: $times % list.length($all-bases) + 1;
55 $exp: math.floor(math.div($times, length($all-bases))); 58 $exp: math.floor(math.div($times, list.length($all-bases)));
56 59
57 @return nth($all-bases, $base-index) * math.pow($ratio, $exp); 60 @return list.nth($all-bases, $base-index) * math.pow($ratio, $exp);
58} 61}
59 62
60/// 63///
@@ -86,15 +89,13 @@
86/// } 89/// }
87/// 90///
88@mixin responsive-modular-scale($props, $times, $responsive-map, $fluid: true) { 91@mixin responsive-modular-scale($props, $times, $responsive-map, $fluid: true) {
89 $new-map: (); 92 $new-map: ();
90 93
91 @each $key, $value in $responsive-map { 94 @each $key, $value in $responsive-map {
92 $new-map: map-merge( 95 $new-map: map.merge($new-map, (
93 $new-map, ( 96 $key: modular-scale($times, $value...)
94 $key: modular-scale($times, $value...) 97 ));
95 ) 98 }
96 );
97 }
98 99
99 @include responsive.property($props, $new-map, $fluid); 100 @include responsive.property($props, $new-map, $fluid);
100} 101}
diff --git a/src/index.scss b/src/_iro-sass.scss
index cc9cda7..d1b8ee3 100644
--- a/src/index.scss
+++ b/src/_iro-sass.scss
@@ -1,9 +1,9 @@
1@forward 'bem' as bem-*; 1@forward 'vars' as vars-*;
2@forward 'functions' as fn-*;
2@forward 'contexts' as ctx-*; 3@forward 'contexts' as ctx-*;
4@forward 'props' as props-*;
5@forward 'bem' as bem-*;
3@forward 'easing' as easing-*; 6@forward 'easing' as easing-*;
4@forward 'functions' as fn-*;
5@forward 'gradients' as gradients-*; 7@forward 'gradients' as gradients-*;
6@forward 'harmony' as harmony-*;
7@forward 'props' as props-*;
8@forward 'responsive' as responsive-*; 8@forward 'responsive' as responsive-*;
9@forward 'vars' as vars-*; 9@forward 'harmony' as harmony-*;
diff --git a/src/_props.scss b/src/_props.scss
index 8d84aa1..17e9de7 100644
--- a/src/_props.scss
+++ b/src/_props.scss
@@ -1,423 +1,139 @@
1//// 1@use 'sass:list';
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@use 'sass:map'; 2@use 'sass:map';
14@use './functions'; 3@use 'sass:meta';
15@use './contexts';
16
17///
18/// The maximum depth of resolved iro-prop-ref() references.
19///
20/// @type number
21///
22$native-assign-max-depth: 2 !default;
23
24///
25/// Indicate if property names must start with two dashes (--).
26/// This is required if property trees are also used for native CSS custom properties.
27///
28/// @type bool
29///
30$enforce-double-dashes: true !default;
31
32///
33/// Default tree name to use if no name is specified.
34///
35/// @type string
36///
37$default-tree: 'default' !default;
38
39///
40/// List of all created property trees.
41///
42/// @type list
43///
44/// @access private
45///
46$trees: ();
47
48///
49/// Default context name used for the namespace context.
50///
51/// @type string
52///
53$namespace-context-id: 'namespace' !default;
54
55///
56/// Declare a namespace, meaning that all variables declared and accessed.
57///
58/// @param {string} $name - Name of the namespace
59///
60@mixin namespace($name) {
61 $key: '--#{$name}';
62
63 $ns-key: get-ns-key();
64
65 @if $ns-key != null {
66 $key: append($ns-key, $key);
67 } @else {
68 $key: ($key);
69 }
70
71 @include contexts.push($namespace-context-id, 'namespace', (
72 'name': $name,
73 'key': $key
74 ));
75
76 @content;
77
78 @include contexts.pop($namespace-context-id);
79}
80 4
81/// 5@function is-prop-ref($value) {
82/// Get the current namespace name. 6 @if meta.type-of($value) != 'list' {
83/// 7 @return false;
84@function namespace() { 8 }
85 $noop: contexts.assert-stack-must-contain($namespace-context-id, 'namespace'); 9 @if list.length($value) != 4 {
86 10 @return false;
87 $data: nth(contexts.get($namespace-context-id, 'namespace'), 2); 11 }
88 $name: map-get($data, 'name'); 12 @if list.nth($value, 1) != 'prop-ref' {
89 13 @return false;
90 @return $name; 14 }
15 @return true;
91} 16}
92 17
93/// 18@function def($name, $value: (), $metadata: ()) {
94/// Save a property tree. If a tree with the sane name already exists, the trees 19 @return ('prop-ref' $name $value $metadata);
95/// will be merged.
96///
97/// @param {map} $map - Map containing properties
98/// @param {string} $tree [$default-tree] - ID the map is saved as
99/// @param {bool} $merge [true] - If a tree named $tree already exists and this value is set to true, they will be merged. Otherwise an error will be emitted.
100///
101@mixin store($map, $tree: $default-tree, $merge: true, $global: false) {
102 $noop: store($map, $tree, $merge, $global);
103} 20}
104 21
105/// 22@function merge($ref, $value) {
106/// Save a property tree. 23 @if not is-prop-ref($ref) {
107/// 24 @return $ref;
108/// @param {map} $map - Map containing properties 25 }
109/// @param {string} $tree [$default-tree] - ID the map is saved as
110/// @param {bool} $merge [true] - If a tree named $tree already exists and this value is set to true, they will be merged. Otherwise an error will be emitted.
111///
112@function store($map, $tree: $default-tree, $merge: true, $global: false) {
113 $prop-map: null;
114
115 @if $enforce-double-dashes {
116 @if not validate($map) {
117 @error 'Property tree keys must start with two dashes (--). If you don\'t use property trees for native CSS custom properties, set $enforce-double-dashes to false.';
118 }
119 }
120
121 @if not $global {
122 $ns-key: get-ns-key();
123
124 @if $ns-key != null {
125 $map: ($ns-key: $map);
126 }
127 }
128
129 @if map-has-key($trees, $tree) {
130 @if $merge {
131 $map: map.deep-merge(map-get($trees, $tree), $map);
132 } @else {
133 @error 'Property tree #{inspect($tree)} does already exist.';
134 }
135 }
136
137 $trees: map-merge($trees, ($tree: $map)) !global;
138 26
139 @return null; 27 $v: list.nth($ref, 3);
28 $ref: list.set-nth($ref, 3, map.deep-merge($v, $value));
29 @return $ref;
140} 30}
141 31
142/// 32@function get-deep($name, $value, $key: null, $keys...) {
143/// Delete a property tree. 33 @if is-prop-ref($value) {
144/// 34 @return get($value, $key, $keys);
145/// @param {string} $tree [$default-tree] - ID of the tree to be deleted 35 }
146/// 36 @if meta.type-of($value) == 'map' and $key != null {
147@mixin clear($tree: $default-tree) { 37 @if meta.type-of($key) != 'string' {
148 $noop: clear($tree); 38 @error 'Expected string, got #{$key}';
39 }
40 @return get-deep(#{$name}#{$key}, map.get($value, $key), $keys...);
41 }
42 @return $name $value;
149} 43}
150 44
151/// 45@function map-to-vars($name, $map) {
152/// Delete a property tree. 46 @if meta.type-of($map) != 'map' {
153/// 47 @if meta.type-of($name) != 'string' {
154/// @param {string} $tree [$default-tree] - ID of the tree to be deleted 48 @error 'Expected variable name, got #{$name} instead';
155/// 49 }
156/// @throw If the property tree does not exist 50 @return var($name);
157/// 51 }
158@function clear($tree: $default-tree) {
159 @if not map-has-key($trees, $tree) {
160 @error 'Property tree "#{inspect($tree)}" does not exist.';
161 }
162
163 $trees: map-remove($trees, $tree) !global;
164
165 @return null;
166}
167
168///
169/// Access a whole property or a subsection (i.e. value) of it.
170///
171/// @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.
172/// @param {string} $tree [$default-tree] - ID of the property tree to use
173/// @param {any} $default [null] - Default value to return of no match was found. If null, this function will throw an error instead.
174///
175/// @return {any} Value assigned to property or $default
176///
177/// @throw If there was no match for $key and $default is null
178///
179@function get-static($key: (), $tree: $default-tree, $default: null, $global: false) {
180 @if not map-has-key($trees, $tree) {
181 @error 'Unknown tree "#{$tree}".';
182 }
183
184 $result: map-get($trees, $tree);
185
186 @if not $global {
187 $ns-key: get-ns-key();
188 52
189 @if $ns-key != null { 53 $out: ();
190 $orig-key: $key;
191 $key: $ns-key;
192 54
193 @if type-of($orig-key) == list { 55 @each $key, $value in $map {
194 @each $subkey in $orig-key { 56 $out: map.set($out, $key, map-to-vars(#{$name}#{$key}, $value));
195 $key: append($key, $subkey); 57 }
196 }
197 } @else {
198 $key: append($key, $orig-key);
199 }
200 }
201 }
202 58
203 @if type-of($key) == list { 59 @return $out;
204 $stop: false;
205
206 @each $k in $key {
207 @if not $stop and map-has-key($result, $k) {
208 $result: map-get($result, $k);
209
210 @if type-of($result) == list and nth($result, 1) == 'iro-prop-ref' {
211 @if length($result) == 2 {
212 $result: get-static($tree: nth($result, 2), $global: true);
213 } @else {
214 $result: get-static(nth($result, 3), nth($result, 2), $global: true);
215 }
216 }
217 } @else {
218 $stop: true;
219 }
220 }
221
222 @if $stop {
223 $result: null;
224 }
225 } @else {
226 $result: map-get($result, $key);
227
228 @if type-of($result) == list and nth($result, 1) == 'iro-prop-ref' {
229 @if length($result) == 2 {
230 $result: get-static($tree: nth($result, 2), $global: true);
231 } @else {
232 $result: get-static(nth($result, 3), nth($result, 2), $global: true);
233 }
234 }
235 }
236
237 @if $result == null {
238 @if $default == null {
239 @error '"#{$key}" is null.';
240 } @else {
241 @return $default;
242 }
243 }
244
245 @return $result;
246} 60}
247 61
248/// 62@function get($ref, $key: null, $keys...) {
249/// Generate a var() function call to get native CSS custom property. 63 @if not is-prop-ref($ref) {
250/// 64 @return $ref;
251/// @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. 65 }
252/// @param {string | null} $tree [null] - Optional tree to check if the property actually exists.
253/// @param {any} $default [null] - Default value to return of no match was found.
254///
255/// @return {string} var()
256///
257@function get($key, $tree: $default-tree, $default: null, $global: false) {
258 @if $tree != null {
259 $noop: get-static($key, $tree, $default, $global);
260 }
261
262 @if not $global {
263 $ns-key: get-ns-key();
264 66
265 @if $ns-key != null { 67 $name: list.nth($ref, 2);
266 $orig-key: $key; 68 $value: get(list.nth($ref, 3));
267 $key: $ns-key;
268 69
269 @if type-of($orig-key) == list { 70 @if meta.type-of($value) == 'map' {
270 @each $subkey in $orig-key { 71 $res: get-deep($name, $value, $key, $keys...);
271 $key: append($key, $subkey); 72 $name: list.nth($res, 1);
272 } 73 $value: list.nth($res, 2);
273 } @else { 74 } @else if meta.type-of($value) == 'list' {
274 $key: append($key, $orig-key); 75 $i: 1;
275 } 76 @each $item in $value {
276 } 77 $value: list.set-nth($value, $i, get($item));
277 } 78 $i: $i + 1;
79 }
80 }
278 81
279 $native-var: ''; 82 @return map-to-vars($name, $value);
280
281 @if type-of($key) == list {
282 @each $subkey in $key {
283 $native-var: $native-var + $subkey;
284 }
285 } @else {
286 $native-var: $key;
287 }
288
289 @if $default == null {
290 @return var(#{$native-var});
291 } @else {
292 @return var(#{$native-var}, #{$default});
293 }
294} 83}
295 84
296/// 85@mixin materialize-helper($name, $value) {
297/// Generate assignments for native CSS custom properties with the values from the specified tree. 86 @if meta.type-of($value) == 'map' {
298/// 87 @each $key, $value in $value {
299/// @param {string} $tree [$default-tree] - ID of the property tree to use 88 @include materialize-helper(#{$name}#{$key}, $value);
300/// @param {string} $root [()] - Sub-tree to use for assignment 89 }
301/// 90 } @else {
302@mixin assign($tree: $default-tree, $root: (), $skip: (), $prefix: '', $global: false) { 91 #{$name}: #{$value};
303 $map: get-static($root, $tree, $global: $global); 92 }
304 $map: map-remove($map, $skip...);
305
306 @if type-of($prefix) == list {
307 $prefix: functions.str-implode($prefix);
308 }
309
310 @if not $global {
311 $ns-key: get-ns-key();
312
313 @if $ns-key != null {
314 $prefix: $prefix + functions.str-implode($ns-key);
315 }
316 }
317
318 @include assign-internal($map, $prefix);
319} 93}
320 94
321/// 95@mixin materialize($ref, $match-meta: ()) {
322/// @access private 96 @if is-prop-ref($ref) {
323/// 97 $name: list.nth($ref, 2);
324@mixin assign-internal($map, $prefix: '', $ref-depth: $native-assign-max-depth) { 98 $value: get(list.nth($ref, 3));
325 @each $key, $value in $map { 99 $meta: get(list.nth($ref, 4));
326 $rd: $ref-depth;
327 @if type-of($value) == list and length($value) > 0 and nth($value, 1) == 'iro-prop-ref' {
328 @if $ref-depth != 0 {
329 $rd: $rd - 1;
330 @if length($value) == 2 {
331 $value: get-static($tree: nth($value, 2));
332 } @else {
333 $value: get-static(nth($value, 3), nth($value, 2));
334 }
335 } @else {
336 $value: null;
337 }
338 }
339 @if type-of($value) != map and $value != () {
340 #{$prefix + $key}: #{$value};
341 } @else {
342 @include assign-internal($value, $prefix + $key, $rd);
343 }
344 }
345}
346
347///
348/// Validate property names.
349///
350/// @access private
351///
352@function validate($map) {
353 @each $key, $value in $map {
354 @if str-index($key, '--') != 1 {
355 @return false;
356 }
357
358 @if type-of($value) == map {
359 @if not validate($value) {
360 @return false;
361 }
362 }
363 }
364
365 @return true;
366}
367
368///
369/// Generate a reference to another tree. Dereferencing is lazy, so you may specify a tree that hasn't been created yet.
370///
371/// @param {string} $tree [$default-tree] - ID of the property tree to use
372/// @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.
373///
374/// @return {list} A special list that let's Ignis know that this is a lazy value.
375///
376/// @throw If there was no match for $key and $default is null
377///
378@function ref($tree: $default-tree, $key: null, $global: false) {
379 @if not $global {
380 $ns-key: get-ns-key();
381 100
382 @if $ns-key != null { 101 $match: true;
383 $orig-key: $key; 102 @if meta.type-of($match-meta) == 'list' {
384 $key: $ns-key; 103 @each $item in $match-meta {
104 $match: $match and list.index($meta, $item) != null;
105 }
106 } @else if $match-meta == null and list.length($meta) == 0 {
107 $match: true;
108 } @else {
109 $match: list.index($meta, $match-meta) != null;
110 }
385 111
386 @if $orig-key != null { 112 @if $match {
387 @if type-of($orig-key) == list { 113 @include materialize-helper($name, $value);
388 @each $subkey in $orig-key { 114 }
389 $key: append($key, $subkey); 115 } @else if meta.type-of($ref) == 'list' {
390 } 116 @each $r in $ref {
391 } @else { 117 @if is-prop-ref($r) {
392 $key: append($key, $orig-key); 118 $name: list.nth($r, 2);
393 } 119 $value: get(list.nth($r, 3));
394 } 120 $meta: get(list.nth($r, 4));
395 }
396 }
397 121
398 @if $key == null { 122 $match: true;
399 @return ('iro-prop-ref' $tree); 123 @if meta.type-of($match-meta) == 'list' {
400 } @else { 124 @each $item in $match-meta {
401 @return ('iro-prop-ref' $tree $key); 125 $match: $match and list.index($meta, $item) != null;
402 } 126 }
127 } @else if $match-meta == null and list.length($meta) == 0 {
128 $match: true;
129 } @else {
130 $match: list.index($meta, $match-meta) != null;
131 }
132
133 @if $match {
134 @include materialize-helper($name, $value);
135 }
136 }
137 }
138 }
403} 139}
404
405///
406/// Get the current namespace key.
407///
408/// @access private
409///
410@function get-ns-key() {
411 $ctx: contexts.get($namespace-context-id, 'namespace');
412
413 @if $ctx == null {
414 @return null;
415 }
416
417 $data: nth($ctx, 2);
418 $key: map-get($data, 'key');
419
420 @return $key;
421}
422
423@include contexts.create($namespace-context-id);
diff --git a/src/_responsive.scss b/src/_responsive.scss
index 4d98638..f613a6d 100644
--- a/src/_responsive.scss
+++ b/src/_responsive.scss
@@ -17,6 +17,11 @@
17/// @access public 17/// @access public
18//// 18////
19 19
20@use 'sass:list';
21@use 'sass:map';
22@use 'sass:math';
23@use 'sass:meta';
24@use 'sass:string';
20@use './functions'; 25@use './functions';
21@use './contexts'; 26@use './contexts';
22 27
@@ -99,15 +104,15 @@ $named-viewports: () !default;
99/// } 104/// }
100/// 105///
101@mixin property($props, $responsive-map, $fluid: true, $vertical: false) { 106@mixin property($props, $responsive-map, $fluid: true, $vertical: false) {
102 @include env(map-keys($responsive-map), $fluid, $vertical) { 107 @include env(map.keys($responsive-map), $fluid, $vertical) {
103 @if type-of($props) == list { 108 @if meta.type-of($props) == list {
104 @each $prop in $props { 109 @each $prop in $props {
105 #{$prop}: set(map-values($responsive-map)); 110 #{$prop}: set(map.values($responsive-map));
106 } 111 }
107 } @else { 112 } @else {
108 #{$props}: set(map-values($responsive-map)); 113 #{$props}: set(map.values($responsive-map));
109 } 114 }
110 } 115 }
111} 116}
112 117
113/// 118///
@@ -150,153 +155,153 @@ $named-viewports: () !default;
150/// } 155/// }
151/// 156///
152@mixin env($viewports, $fluid: true, $vertical: false) { 157@mixin env($viewports, $fluid: true, $vertical: false) {
153 @if length($viewports) <= 1 { 158 @if list.length($viewports) <= 1 {
154 @error '$viewports must contain at least two viewports.'; 159 @error '$viewports must contain at least two viewports.';
155 } 160 }
156 161
157 $new-viewports: (); 162 $new-viewports: ();
158 163
159 @each $viewport in $viewports { 164 @each $viewport in $viewports {
160 @if map-has-key($named-viewports, $viewport) { 165 @if map.has-key($named-viewports, $viewport) {
161 $viewport: map-get($named-viewports, $viewport); 166 $viewport: map.get($named-viewports, $viewport);
162 } 167 }
163 168
164 @if (type-of($viewport) != number) or unitless($viewport) { 169 @if (meta.type-of($viewport) != number) or math.is-unitless($viewport) {
165 @error '$viewports contains invalid viewports.'; 170 @error '$viewports contains invalid viewports.';
166 } 171 }
167 172
168 $new-viewports: append($new-viewports, $viewport); 173 $new-viewports: list.append($new-viewports, $viewport);
169 } 174 }
170 175
171 $viewports: functions.quicksort($new-viewports); 176 $viewports: functions.quicksort($new-viewports);
172 177
173 @if $new-viewports != $viewports { 178 @if $new-viewports != $viewports {
174 @error '$viewports was not sorted in ascending order.'; 179 @error '$viewports was not sorted in ascending order.';
175 } 180 }
176 181
177 @if $fluid { 182 @if $fluid {
178 $first-vp: nth($viewports, 1); 183 $first-vp: list.nth($viewports, 1);
179 $last-vp: nth($viewports, length($viewports)); 184 $last-vp: list.nth($viewports, list.length($viewports));
180 185
181 @include contexts.push($context-id, 'env', ( 186 @include contexts.push($context-id, 'env', (
182 'viewports': $viewports, 187 'viewports': $viewports,
183 'mode': set, 188 'mode': set,
184 'index': 1, 189 'index': 1,
185 'fluid': $fluid, 190 'fluid': $fluid,
186 'vertical': $vertical, 191 'vertical': $vertical,
187 )); 192 ));
188 193
189 @content; 194 @content;
190 195
191 @include contexts.pop($context-id); 196 @include contexts.pop($context-id);
192 197
193 @for $i from 1 to length($viewports) { 198 @for $i from 1 to list.length($viewports) {
194 $prev-vp: nth($viewports, $i); 199 $prev-vp: list.nth($viewports, $i);
195 $next-vp: nth($viewports, $i + 1); 200 $next-vp: list.nth($viewports, $i + 1);
196 201
197 @if not $vertical { 202 @if not $vertical {
198 @media (min-width: $prev-vp) and (max-width: $next-vp) { 203 @media (min-width: $prev-vp) and (max-width: $next-vp) {
199 @include contexts.push($context-id, 'env', ( 204 @include contexts.push($context-id, 'env', (
200 'viewports': $viewports, 205 'viewports': $viewports,
201 'mode': transition, 206 'mode': transition,
202 'index': $i, 207 'index': $i,
203 'fluid': $fluid, 208 'fluid': $fluid,
204 'vertical': $vertical, 209 'vertical': $vertical,
205 )); 210 ));
206 211
207 @content; 212 @content;
208 213
209 @include contexts.pop($context-id); 214 @include contexts.pop($context-id);
210 } 215 }
211 } @else { 216 } @else {
212 @media (min-height: $prev-vp) and (max-height: $next-vp) { 217 @media (min-height: $prev-vp) and (max-height: $next-vp) {
213 @include contexts.push($context-id, 'env', ( 218 @include contexts.push($context-id, 'env', (
214 'viewports': $viewports, 219 'viewports': $viewports,
215 'mode': transition, 220 'mode': transition,
216 'index': $i, 221 'index': $i,
217 'fluid': $fluid, 222 'fluid': $fluid,
218 'vertical': $vertical, 223 'vertical': $vertical,
219 )); 224 ));
220 225
221 @content; 226 @content;
222 227
223 @include contexts.pop($context-id); 228 @include contexts.pop($context-id);
224 } 229 }
225 } 230 }
226 } 231 }
227 232
228 @if not $vertical { 233 @if not $vertical {
229 @media (min-width: $last-vp) { 234 @media (min-width: $last-vp) {
230 @include contexts.push($context-id, 'env', ( 235 @include contexts.push($context-id, 'env', (
231 'viewports': $viewports, 236 'viewports': $viewports,
232 'mode': set, 237 'mode': set,
233 'index': length($viewports), 238 'index': list.length($viewports),
234 'fluid': $fluid, 239 'fluid': $fluid,
235 'vertical': $vertical, 240 'vertical': $vertical,
236 )); 241 ));
237 242
238 @content; 243 @content;
239 244
240 @include contexts.pop($context-id); 245 @include contexts.pop($context-id);
241 } 246 }
242 } @else { 247 } @else {
243 @media (min-height: $last-vp) { 248 @media (min-height: $last-vp) {
244 @include contexts.push($context-id, 'env', ( 249 @include contexts.push($context-id, 'env', (
245 'viewports': $viewports, 250 'viewports': $viewports,
246 'mode': set, 251 'mode': set,
247 'index': length($viewports), 252 'index': list.length($viewports),
248 'fluid': $fluid, 253 'fluid': $fluid,
249 'vertical': $vertical, 254 'vertical': $vertical,
250 )); 255 ));
251 256
252 @content; 257 @content;
253 258
254 @include contexts.pop($context-id); 259 @include contexts.pop($context-id);
255 } 260 }
256 } 261 }
257 } @else { 262 } @else {
258 @include contexts.push($context-id, 'env', ( 263 @include contexts.push($context-id, 'env', (
259 'viewports': $viewports, 264 'viewports': $viewports,
260 'mode': set, 265 'mode': set,
261 'index': 1, 266 'index': 1,
262 'fluid': $fluid, 267 'fluid': $fluid,
263 'vertical': $vertical, 268 'vertical': $vertical,
264 )); 269 ));
265 270
266 @content; 271 @content;
267 272
268 @include contexts.pop($context-id); 273 @include contexts.pop($context-id);
269 274
270 @for $i from 2 through length($viewports) { 275 @for $i from 2 through list.length($viewports) {
271 $vp: nth($viewports, $i); 276 $vp: list.nth($viewports, $i);
272 277
273 @if not $vertical { 278 @if not $vertical {
274 @media (min-width: $vp) { 279 @media (min-width: $vp) {
275 @include contexts.push($context-id, 'env', ( 280 @include contexts.push($context-id, 'env', (
276 'viewports': $viewports, 281 'viewports': $viewports,
277 'mode': set, 282 'mode': set,
278 'index': $i 283 'index': $i
279 )); 284 ));
280 285
281 @content; 286 @content;
282 287
283 @include contexts.pop($context-id); 288 @include contexts.pop($context-id);
284 } 289 }
285 } @else { 290 } @else {
286 @media (min-height: $vp) { 291 @media (min-height: $vp) {
287 @include contexts.push($context-id, 'env', ( 292 @include contexts.push($context-id, 'env', (
288 'viewports': $viewports, 293 'viewports': $viewports,
289 'mode': set, 294 'mode': set,
290 'index': $i 295 'index': $i
291 )); 296 ));
292 297
293 @content; 298 @content;
294 299
295 @include contexts.pop($context-id); 300 @include contexts.pop($context-id);
296 } 301 }
297 } 302 }
298 } 303 }
299 } 304 }
300} 305}
301 306
302/// 307///
@@ -307,29 +312,29 @@ $named-viewports: () !default;
307/// @return {number|string} 312/// @return {number|string}
308/// 313///
309@function set($values, $without-calc: false) { 314@function set($values, $without-calc: false) {
310 $noop: contexts.assert-stack-must-contain($context-id, 'env'); 315 $noop: contexts.assert-stack-must-contain($context-id, 'env');
311 316
312 $data: nth(contexts.get($context-id, 'env'), 2); 317 $data: list.nth(contexts.get($context-id, 'env'), 2);
313 $viewports: map-get($data, 'viewports'); 318 $viewports: map.get($data, 'viewports');
314 $mode: map-get($data, 'mode'); 319 $mode: map.get($data, 'mode');
315 $fluid: map-get($data, 'fluid'); 320 $fluid: map.get($data, 'fluid');
316 $vertical: map-get($data, 'vertical'); 321 $vertical: map.get($data, 'vertical');
317 322
318 @if length($values) != length($viewports) { 323 @if list.length($values) != list.length($viewports) {
319 @error '$values must contain the same number of items as the responsive environment\'s $viewports.'; 324 @error '$values must contain the same number of items as the responsive environment\'s $viewports.';
320 } 325 }
321 326
322 @if $mode == set { 327 @if $mode == set {
323 @return nth($values, map-get($data, 'index')); 328 @return list.nth($values, map.get($data, 'index'));
324 } @else { 329 } @else {
325 $index: map-get($data, 'index'); 330 $index: map.get($data, 'index');
326 $prev-vp: nth($viewports, $index); 331 $prev-vp: list.nth($viewports, $index);
327 $next-vp: nth($viewports, $index + 1); 332 $next-vp: list.nth($viewports, $index + 1);
328 $prev-value: nth($values, $index); 333 $prev-value: list.nth($values, $index);
329 $next-value: nth($values, $index + 1); 334 $next-value: list.nth($values, $index + 1);
330 335
331 @return fluid-calc($prev-value, $next-value, $prev-vp, $next-vp, $vertical, $without-calc); 336 @return fluid-calc($prev-value, $next-value, $prev-vp, $next-vp, $vertical, $without-calc);
332 } 337 }
333} 338}
334 339
335/// 340///
@@ -345,62 +350,62 @@ $named-viewports: () !default;
345/// @access private 350/// @access private
346/// 351///
347@function fluid-calc($min-value, $max-value, $min-viewport, $max-viewport, $vertical: false, $without-calc: false) { 352@function fluid-calc($min-value, $max-value, $min-viewport, $max-viewport, $vertical: false, $without-calc: false) {
348 $value-unit: unit($min-value); 353 $value-unit: math.unit($min-value);
349 $max-value-unit: unit($max-value); 354 $max-value-unit: math.unit($max-value);
350 $viewport-unit: unit($min-viewport); 355 $viewport-unit: math.unit($min-viewport);
351 $max-viewport-unit: unit($max-viewport); 356 $max-viewport-unit: math.unit($max-viewport);
352 357
353 @if $min-value == 0 { 358 @if $min-value == 0 {
354 $value-unit: $max-value-unit; 359 $value-unit: $max-value-unit;
355 } 360 }
356 @if $max-value == 0 { 361 @if $max-value == 0 {
357 $max-value-unit: $value-unit; 362 $max-value-unit: $value-unit;
358 } 363 }
359 @if $min-viewport == 0 { 364 @if $min-viewport == 0 {
360 $viewport-unit: $max-viewport-unit; 365 $viewport-unit: $max-viewport-unit;
361 } 366 }
362 @if $max-viewport == 0 { 367 @if $max-viewport == 0 {
363 $max-viewport-unit: $viewport-unit; 368 $max-viewport-unit: $viewport-unit;
364 } 369 }
365 370
366 @if ($value-unit != $max-value-unit) or ($viewport-unit != $max-viewport-unit) { 371 @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.'; 372 @error 'Units of $min-value and $max-value, $min-viewport and $max-viewport must match.';
368 } 373 }
369 374
370 @if ($value-unit == rem) and ($viewport-unit == px) { 375 @if ($value-unit == rem) and ($viewport-unit == px) {
371 $min-viewport: functions.px-to-rem($min-viewport); 376 $min-viewport: functions.px-to-rem($min-viewport);
372 $max-viewport: functions.px-to-rem($max-viewport); 377 $max-viewport: functions.px-to-rem($max-viewport);
373 $viewport-unit: rem; 378 $viewport-unit: rem;
374 } @else if ($value-unit == px) and ($viewport-unit == rem) { 379 } @else if ($value-unit == px) and ($viewport-unit == rem) {
375 $min-value: functions.px-to-rem($min-value); 380 $min-value: functions.px-to-rem($min-value);
376 $max-value: functions.px-to-rem($max-value); 381 $max-value: functions.px-to-rem($max-value);
377 $value-unit: rem; 382 $value-unit: rem;
378 } 383 }
379 384
380 @if $value-unit != $viewport-unit { 385 @if $value-unit != $viewport-unit {
381 @error 'This combination of units is not supported.'; 386 @error 'This combination of units is not supported.';
382 } 387 }
383 388
384 $value-diff: functions.strip-unit($max-value - $min-value); 389 $value-diff: functions.strip-unit($max-value - $min-value);
385 $viewport-diff: functions.strip-unit($max-viewport - $min-viewport); 390 $viewport-diff: functions.strip-unit($max-viewport - $min-viewport);
386 391
387 $calc: ''; 392 $calc: '';
388 393
389 @if $min-value != 0 { 394 @if $min-value != 0 {
390 $calc: '#{$min-value} + '; 395 $calc: '#{$min-value} + ';
391 } 396 }
392 397
393 @if not $vertical { 398 @if not $vertical {
394 $calc: unquote('#{$calc}#{$value-diff} * (100vw - #{$min-viewport}) / #{$viewport-diff}'); 399 $calc: string.unquote('#{$calc}#{$value-diff} * (100vw - #{$min-viewport}) / #{$viewport-diff}');
395 } @else { 400 } @else {
396 $calc: unquote('#{$calc}#{$value-diff} * (100vh - #{$min-viewport}) / #{$viewport-diff}'); 401 $calc: string.unquote('#{$calc}#{$value-diff} * (100vh - #{$min-viewport}) / #{$viewport-diff}');
397 } 402 }
398 403
399 @if $without-calc { 404 @if $without-calc {
400 @return $calc; 405 @return $calc;
401 } @else { 406 } @else {
402 @return calc(#{$calc}); 407 @return calc(#{$calc});
403 } 408 }
404} 409}
405 410
406@include contexts.create($context-id); 411@include contexts.create($context-id);
diff --git a/src/bem/_block.scss b/src/bem/_block.scss
index 49af04b..a4b2a47 100644
--- a/src/bem/_block.scss
+++ b/src/bem/_block.scss
@@ -4,6 +4,10 @@
4/// @access public 4/// @access public
5//// 5////
6 6
7@use 'sass:list';
8@use 'sass:map';
9@use 'sass:meta';
10@use 'sass:selector';
7@use './validators'; 11@use './validators';
8@use './vars'; 12@use './vars';
9@use './functions' as bemfunctions; 13@use './functions' as bemfunctions;
@@ -37,28 +41,34 @@
37/// } 41/// }
38/// 42///
39@mixin block($name, $type: null) { 43@mixin block($name, $type: null) {
40 $result: block($name, $type); 44 $result: block($name, $type);
41 $selector: nth($result, 1); 45 $selector: list.nth($result, 1);
42 $context: nth($result, 2); 46 $context: list.nth($result, 2);
43 47
44 @include validators.validate( 48 @include validators.validate(
45 'block', 49 'block',
46 (name: $name, type: $type), 50 (name: $name, type: $type),
47 $selector, 51 $selector,
48 $context 52 $context
49 ); 53 );
50 54
51 @if $type != null { 55 @if $type != null {
52 vars.$blocks: append(vars.$blocks, $name + '_' + $type); 56 vars.$blocks: list.append(vars.$blocks, $name + '_' + $type);
53 } @else { 57 } @else {
54 vars.$blocks: append(vars.$blocks, $name); 58 vars.$blocks: list.append(vars.$blocks, $name);
55 } 59 }
56 60
57 @include contexts.push(vars.$context-id, $context...); 61 @include contexts.push(vars.$context-id, $context...);
58 @at-root #{$selector} { 62 @at-root #{$selector} {
59 @content; 63 @if $type != null {
60 } 64 @layer #{$type} {
61 @include contexts.pop(vars.$context-id); 65 @content;
66 }
67 } @else {
68 @content;
69 }
70 }
71 @include contexts.pop(vars.$context-id);
62} 72}
63 73
64/// 74///
@@ -69,47 +79,47 @@
69/// @see {mixin} block 79/// @see {mixin} block
70/// 80///
71@function block($name, $type: null) { 81@function block($name, $type: null) {
72 // 82 //
73 // Possible outcomes: 83 // Possible outcomes:
74 // - ({b,e,m,s}) block 84 // - ({b,e,m,s}) block
75 // 85 //
76 86
77 $noop: contexts.assert-stack-count(vars.$context-id, vars.$max-depth); 87 $noop: contexts.assert-stack-count(vars.$context-id, vars.$max-depth);
78 88
79 $selector: null; 89 $selector: null;
80 $base-selector: null; 90 $base-selector: null;
81 91
82 @if $type != null { 92 @if $type != null {
83 $namespace: map-get(vars.$namespaces, $type); 93 $namespace: map.get(vars.$namespaces, $type);
84 94
85 @if not $namespace { 95 @if not $namespace {
86 @error '"#{$type}" is not a valid type.'; 96 @error '"#{$type}" is not a valid type.';
87 } 97 }
88 98
89 $base-selector: selector-parse('.' + $namespace + '-' + $name); 99 $base-selector: selector.parse('.' + $namespace + '-' + $name);
90 100
91 @if $type != 'theme' or & { 101 @if $type != 'theme' or & {
92 $selector: $base-selector; 102 $selector: $base-selector;
93 } @else if not & { 103 } @else if not & {
94 $selector: bemfunctions.theme-selector($name); 104 $selector: bemfunctions.theme-selector($name);
95 } 105 }
96 } @else { 106 } @else {
97 $base-selector: selector-parse('.' + $name); 107 $base-selector: selector.parse('.' + $name);
98 $selector: $base-selector; 108 $selector: $base-selector;
99 } 109 }
100 110
101 @if & { 111 @if & {
102 $selector: selector-nest(&, $selector); 112 $selector: selector.nest(&, $selector);
103 } 113 }
104 114
105 $context: 'block', ( 115 $context: 'block', (
106 'name': $name, 116 'name': $name,
107 'type': $type, 117 'type': $type,
108 'selector': $selector, 118 'selector': $selector,
109 'base-selector': $base-selector 119 'base-selector': $base-selector
110 ); 120 );
111 121
112 @return $selector $context; 122 @return $selector $context;
113} 123}
114 124
115/// 125///
@@ -120,9 +130,9 @@
120/// @content 130/// @content
121/// 131///
122@mixin object($name) { 132@mixin object($name) {
123 @include block($name, 'object') { 133 @include block($name, 'object') {
124 @content; 134 @content;
125 } 135 }
126} 136}
127 137
128/// 138///
@@ -133,7 +143,7 @@
133/// @see {mixin} object 143/// @see {mixin} object
134/// 144///
135@function object($name) { 145@function object($name) {
136 @return block($name, 'object'); 146 @return block($name, 'object');
137} 147}
138 148
139/// 149///
@@ -144,9 +154,9 @@
144/// @content 154/// @content
145/// 155///
146@mixin component($name) { 156@mixin component($name) {
147 @include block($name, 'component') { 157 @include block($name, 'component') {
148 @content; 158 @content;
149 } 159 }
150} 160}
151 161
152/// 162///
@@ -157,7 +167,7 @@
157/// @see {mixin} component 167/// @see {mixin} component
158/// 168///
159@function component($name) { 169@function component($name) {
160 @return block($name, 'component'); 170 @return block($name, 'component');
161} 171}
162 172
163/// 173///
@@ -168,9 +178,9 @@
168/// @content 178/// @content
169/// 179///
170@mixin layout($name) { 180@mixin layout($name) {
171 @include block($name, 'layout') { 181 @include block($name, 'layout') {
172 @content; 182 @content;
173 } 183 }
174} 184}
175 185
176/// 186///
@@ -181,7 +191,7 @@
181/// @see {mixin} layout 191/// @see {mixin} layout
182/// 192///
183@function layout($name) { 193@function layout($name) {
184 @return block($name, 'layout'); 194 @return block($name, 'layout');
185} 195}
186 196
187/// 197///
@@ -192,9 +202,9 @@
192/// @content 202/// @content
193/// 203///
194@mixin utility($name) { 204@mixin utility($name) {
195 @include block($name, 'utility') { 205 @include block($name, 'utility') {
196 @content; 206 @content;
197 } 207 }
198} 208}
199 209
200/// 210///
@@ -205,7 +215,7 @@
205/// @see {mixin} utility 215/// @see {mixin} utility
206/// 216///
207@function utility($name) { 217@function utility($name) {
208 @return block($name, 'utility'); 218 @return block($name, 'utility');
209} 219}
210 220
211/// 221///
@@ -216,9 +226,9 @@
216/// @content 226/// @content
217/// 227///
218@mixin scope($name) { 228@mixin scope($name) {
219 @include block($name, 'scope') { 229 @include block($name, 'scope') {
220 @content; 230 @content;
221 } 231 }
222} 232}
223 233
224/// 234///
@@ -229,7 +239,7 @@
229/// @see {mixin} scope 239/// @see {mixin} scope
230/// 240///
231@function scope($name) { 241@function scope($name) {
232 @return block($name, 'scope'); 242 @return block($name, 'scope');
233} 243}
234 244
235/// 245///
@@ -240,9 +250,9 @@
240/// @content 250/// @content
241/// 251///
242@mixin theme($name) { 252@mixin theme($name) {
243 @include block($name, 'theme') { 253 @include block($name, 'theme') {
244 @content; 254 @content;
245 } 255 }
246} 256}
247 257
248/// 258///
@@ -253,7 +263,7 @@
253/// @see {mixin} theme 263/// @see {mixin} theme
254/// 264///
255@function theme($name) { 265@function theme($name) {
256 @return block($name, 'theme'); 266 @return block($name, 'theme');
257} 267}
258 268
259/// 269///
@@ -264,9 +274,9 @@
264/// @content 274/// @content
265/// 275///
266@mixin js($name) { 276@mixin js($name) {
267 @include block($name, 'js') { 277 @include block($name, 'js') {
268 @content; 278 @content;
269 } 279 }
270} 280}
271 281
272/// 282///
@@ -277,7 +287,7 @@
277/// @see {mixin} js 287/// @see {mixin} js
278/// 288///
279@function js($name) { 289@function js($name) {
280 @return block($name, 'js'); 290 @return block($name, 'js');
281} 291}
282 292
283/// 293///
@@ -288,9 +298,9 @@
288/// @content 298/// @content
289/// 299///
290@mixin qa($name) { 300@mixin qa($name) {
291 @include block($name, 'qa') { 301 @include block($name, 'qa') {
292 @content; 302 @content;
293 } 303 }
294} 304}
295 305
296/// 306///
@@ -301,7 +311,7 @@
301/// @see {mixin} qa 311/// @see {mixin} qa
302/// 312///
303@function qa($name) { 313@function qa($name) {
304 @return block($name, 'qa'); 314 @return block($name, 'qa');
305} 315}
306 316
307/// 317///
@@ -312,9 +322,9 @@
312/// @content 322/// @content
313/// 323///
314@mixin hack($name) { 324@mixin hack($name) {
315 @include block($name, 'hack') { 325 @include block($name, 'hack') {
316 @content; 326 @content;
317 } 327 }
318} 328}
319 329
320/// 330///
@@ -325,7 +335,7 @@
325/// @see {mixin} hack 335/// @see {mixin} hack
326/// 336///
327@function hack($name) { 337@function hack($name) {
328 @return block($name, 'hack'); 338 @return block($name, 'hack');
329} 339}
330 340
331/// 341///
@@ -377,22 +387,22 @@
377/// // Compilation will fail because c-someBlock is defined after c-anotherBlock__elem 387/// // Compilation will fail because c-someBlock is defined after c-anotherBlock__elem
378/// 388///
379@mixin composed-of($block, $blocks...) { 389@mixin composed-of($block, $blocks...) {
380 @each $block in functions.list-prepend($blocks, $block) { 390 @each $block in functions.list-prepend($blocks, $block) {
381 @if type-of($block) == string { 391 @if meta.type-of($block) == string {
382 @if not index(vars.$blocks, $block) { 392 @if not list.index(vars.$blocks, $block) {
383 @error 'Block "#{$block}" does not exist.'; 393 @error 'Block "#{$block}" does not exist.';
384 } 394 }
385 } @else { 395 } @else {
386 $name: nth($block, 1); 396 $name: list.nth($block, 1);
387 $type: nth($block, 2); 397 $type: list.nth($block, 2);
388 398
389 @if not map-get(vars.$namespaces, $type) { 399 @if not map.get(vars.$namespaces, $type) {
390 @error '"#{$type}" is not a valid type.'; 400 @error '"#{$type}" is not a valid type.';
391 } 401 }
392 402
393 @if not index(vars.$blocks, $name + '_' + $type) { 403 @if not list.index(vars.$blocks, $name + '_' + $type) {
394 @error 'Block "#{$name}" does not exist.'; 404 @error 'Block "#{$name}" does not exist.';
395 } 405 }
396 } 406 }
397 } 407 }
398} 408}
diff --git a/src/bem/_debug.scss b/src/bem/_debug.scss
index 8ea0f05..b1f20a7 100644
--- a/src/bem/_debug.scss
+++ b/src/bem/_debug.scss
@@ -4,15 +4,16 @@
4/// @access public 4/// @access public
5//// 5////
6 6
7@use 'sass:map';
7@use './vars'; 8@use './vars';
8 9
9@if vars.$debug { 10@if vars.$debug {
10 @each $type, $color in vars.$debug-colors { 11 @each $type, $color in vars.$debug-colors {
11 $namespace: map-get(vars.$namespaces, $type); 12 $namespace: map.get(vars.$namespaces, $type);
12 13
13 [class^='#{$namespace}-'], 14 [class^='#{$namespace}-'],
14 [class*=' #{$namespace}-'] { 15 [class*=' #{$namespace}-'] {
15 outline: 5px solid $color; 16 outline: 5px solid $color;
16 } 17 }
17 } 18 }
18} 19}
diff --git a/src/bem/_element.scss b/src/bem/_element.scss
index 64862b0..9f108fe 100644
--- a/src/bem/_element.scss
+++ b/src/bem/_element.scss
@@ -4,6 +4,11 @@
4/// @access public 4/// @access public
5//// 5////
6 6
7@use 'sass:list';
8@use 'sass:map';
9@use 'sass:meta';
10@use 'sass:selector';
11@use 'sass:string';
7@use './validators'; 12@use './validators';
8@use './vars'; 13@use './vars';
9@use '../functions'; 14@use '../functions';
@@ -92,22 +97,22 @@
92/// } 97/// }
93/// 98///
94@mixin elem($name, $names...) { 99@mixin elem($name, $names...) {
95 $result: elem($name, $names...); 100 $result: elem($name, $names...);
96 $selector: nth($result, 1); 101 $selector: list.nth($result, 1);
97 $context: nth($result, 2); 102 $context: list.nth($result, 2);
98 103
99 @include validators.validate( 104 @include validators.validate(
100 'element', 105 'element',
101 (name: $name, names: $names), 106 (name: $name, names: $names),
102 $selector, 107 $selector,
103 $context 108 $context
104 ); 109 );
105 110
106 @include contexts.push(vars.$context-id, $context...); 111 @include contexts.push(vars.$context-id, $context...);
107 @at-root #{$selector} { 112 @at-root #{$selector} {
108 @content; 113 @content;
109 } 114 }
110 @include contexts.pop(vars.$context-id); 115 @include contexts.pop(vars.$context-id);
111} 116}
112 117
113/// 118///
@@ -118,99 +123,93 @@
118/// @see {mixin} element 123/// @see {mixin} element
119/// 124///
120@function elem($name, $names...) { 125@function elem($name, $names...) {
121 $noop: contexts.assert-stack-count(vars.$context-id, vars.$max-depth); 126 $noop: contexts.assert-stack-count(vars.$context-id, vars.$max-depth);
122 $noop: contexts.assert-stack-must-contain(vars.$context-id, 'block'); 127 $noop: contexts.assert-stack-must-contain(vars.$context-id, 'block');
123 128
124 $parent-context: contexts.get(vars.$context-id, 'block' 'element'); 129 $parent-context: contexts.get(vars.$context-id, 'block' 'element');
125 130
126 $selector: (); 131 $selector: ();
127 $parts-data: (); 132 $parts-data: ();
128 133
129 @if nth($parent-context, 1) == 'element' { 134 @if list.nth($parent-context, 1) == 'element' {
130 @if vars.$element-nesting-policy == 'disallow' { 135 @if vars.$element-nesting-policy == 'disallow' {
131 @error 'Element nesting is forbidden.'; 136 @error 'Element nesting is forbidden.';
132 } 137 }
133 138
134 @if vars.$element-nesting-policy == 'append' { 139 @if vars.$element-nesting-policy == 'append' {
135 $element-selector: map-get(nth($parent-context, 2), 'selector'); 140 $element-selector: map.get(list.nth($parent-context, 2), 'selector');
136 141
137 @if not functions.selector-suffix-match(&, $element-selector) { 142 @if not functions.selector-suffix-match(&, $element-selector) {
138 @error 'A nested element must be an immediate children of the parent element.'; 143 @error 'A nested element must be an immediate children of the parent element.';
139 } 144 }
140 145
141 // 146 //
142 // Possible outcomes: 147 // Possible outcomes:
143 // - {e}__element 148 // - {e}__element
144 // - [manual selector] {e}__element 149 // - [manual selector] {e}__element
145 // 150 //
146 151
147 @each $name in join($name, $names) { 152 @each $name in list.join($name, $names) {
148 $sel: selector-append(&, vars.$element-separator + $name); 153 $sel: selector.append(&, vars.$element-separator + $name);
149 $selector: join($selector, $sel, comma); 154 $selector: list.join($selector, $sel, comma);
150 $parts-data: append( 155 $parts-data: list.append($parts-data, (
151 $parts-data, ( 156 'name': $name,
152 'name': $name, 157 'selector': $sel
153 'selector': $sel 158 ));
154 ) 159 }
155 ); 160 }
156 }
157 }
158 161
159 $parent-context: contexts.get(vars.$context-id, 'block'); 162 $parent-context: contexts.get(vars.$context-id, 'block');
160 } 163 }
161 164
162 @if length($selector) == 0 { 165 @if list.length($selector) == 0 {
163 $parent-selector: map-get(nth($parent-context, 2), 'selector'); 166 $parent-selector: map.get(list.nth($parent-context, 2), 'selector');
164 167
165 @if functions.selector-suffix-match(&, $parent-selector) { 168 @if functions.selector-suffix-match(&, $parent-selector) {
166 // 169 //
167 // Possible outcomes: 170 // Possible outcomes:
168 // - {b}__element 171 // - {b}__element
169 // - [manual selector] {b}__element 172 // - [manual selector] {b}__element
170 // 173 //
171 174
172 @each $name in join($name, $names) { 175 @each $name in list.join($name, $names) {
173 $sel: selector-append(&, vars.$element-separator + $name); 176 $sel: selector.append(&, vars.$element-separator + $name);
174 $selector: join($selector, $sel, comma); 177 $selector: list.join($selector, $sel, comma);
175 $parts-data: append( 178 $parts-data: list.append($parts-data, (
176 $parts-data, ( 179 'name': $name,
177 'name': $name, 180 'selector': $sel
178 'selector': $sel 181 ));
179 ) 182 }
180 ); 183 } @else {
181 } 184 //
182 } @else { 185 // Possible outcomes:
183 // 186 // - {b} [manual selector] {b}__element
184 // Possible outcomes: 187 // - {e,m,s} ([manual selector]) {b}__element
185 // - {b} [manual selector] {b}__element 188 //
186 // - {e,m,s} ([manual selector]) {b}__element
187 //
188 189
189 @if nth($parent-context, 1) != 'block' { 190 @if list.nth($parent-context, 1) != 'block' {
190 $parent-context: contexts.get(vars.$context-id, 'block'); 191 $parent-context: contexts.get(vars.$context-id, 'block');
191 } 192 }
192 193
193 $block-base-selector: map-get(nth($parent-context, 2), 'base-selector'); 194 $block-base-selector: map.get(list.nth($parent-context, 2), 'base-selector');
194 195
195 @each $name in join($name, $names) { 196 @each $name in list.join($name, $names) {
196 $sel: selector-nest(&, selector-append($block-base-selector, vars.$element-separator + $name)); 197 $sel: selector.nest(&, selector.append($block-base-selector, vars.$element-separator + $name));
197 $selector: join($selector, $sel, comma); 198 $selector: list.join($selector, $sel, comma);
198 $parts-data: append( 199 $parts-data: list.append($parts-data, (
199 $parts-data, ( 200 'name': $name,
200 'name': $name, 201 'selector': $sel
201 'selector': $sel 202 ));
202 ) 203 }
203 ); 204 }
204 } 205 }
205 }
206 }
207 206
208 $context: 'element', ( 207 $context: 'element', (
209 'parts': $parts-data, 208 'parts': $parts-data,
210 'selector': $selector 209 'selector': $selector
211 ); 210 );
212 211
213 @return $selector $context; 212 @return $selector $context;
214} 213}
215 214
216/// 215///
@@ -291,22 +290,22 @@
291/// } 290/// }
292/// 291///
293@mixin related-elem($sign, $name, $names...) { 292@mixin related-elem($sign, $name, $names...) {
294 $result: related-elem($sign, $name, $names...); 293 $result: related-elem($sign, $name, $names...);
295 $selector: nth($result, 1); 294 $selector: list.nth($result, 1);
296 $context: nth($result, 2); 295 $context: list.nth($result, 2);
297 296
298 @include validators.validate( 297 @include validators.validate(
299 'related-element', 298 'related-element',
300 (sign: $sign, name: $name, names: $names), 299 (sign: $sign, name: $name, names: $names),
301 $selector, 300 $selector,
302 $context 301 $context
303 ); 302 );
304 303
305 @include contexts.push(vars.$context-id, $context...); 304 @include contexts.push(vars.$context-id, $context...);
306 @at-root #{$selector} { 305 @at-root #{$selector} {
307 @content; 306 @content;
308 } 307 }
309 @include contexts.pop(vars.$context-id); 308 @include contexts.pop(vars.$context-id);
310} 309}
311 310
312/// 311///
@@ -318,44 +317,42 @@
318/// @see {mixin} related-element 317/// @see {mixin} related-element
319/// 318///
320@function related-elem($sign, $name, $names...) { 319@function related-elem($sign, $name, $names...) {
321 // 320 //
322 // Generating this selector is simple: Take the latest block context, use it 321 // Generating this selector is simple: Take the latest block context, use it
323 // to generate the element part, and insert it at the end of the current selector. 322 // to generate the element part, and insert it at the end of the current selector.
324 // Possible outcomes: 323 // Possible outcomes:
325 // - {e} ({m,s}) ([manual selector]) + {e} 324 // - {e} ({m,s}) ([manual selector]) + {e}
326 // - {e} ({m,s}) ([manual selector]) ~ {e} 325 // - {e} ({m,s}) ([manual selector]) ~ {e}
327 // 326 //
328 327
329 $noop: contexts.assert-stack-count(vars.$context-id, vars.$max-depth); 328 $noop: contexts.assert-stack-count(vars.$context-id, vars.$max-depth);
330 $noop: contexts.assert-stack-must-contain(vars.$context-id, 'element'); 329 $noop: contexts.assert-stack-must-contain(vars.$context-id, 'element');
331 330
332 @if $sign != '+' and $sign != '~' { 331 @if $sign != '+' and $sign != '~' {
333 @error 'Invalid relationship sign #{inspect($sign)}.'; 332 @error 'Invalid relationship sign #{inspect($sign)}.';
334 } 333 }
335 334
336 $block-context: contexts.get(vars.$context-id, 'block'); 335 $block-context: contexts.get(vars.$context-id, 'block');
337 $block-base-selector: map-get(nth($block-context, 2), 'base-selector'); 336 $block-base-selector: map.get(list.nth($block-context, 2), 'base-selector');
338 337
339 $selector: (); 338 $selector: ();
340 $parts-data: (); 339 $parts-data: ();
341 340
342 @each $name in join($name, $names) { 341 @each $name in list.join($name, $names) {
343 $sel: selector-nest(&, $sign, selector-append($block-base-selector, vars.$element-separator + $name)); 342 $sel: selector.nest(&, $sign, selector.append($block-base-selector, vars.$element-separator + $name));
344 $selector: join($selector, $sel, comma); 343 $selector: list.join($selector, $sel, comma);
345 $parts-data: append( 344 $parts-data: list.append($parts-data, (
346 $parts-data, ( 345 'name': $name,
347 'name': $name, 346 'selector': $sel
348 'selector': $sel 347 ));
349 ) 348 }
350 );
351 }
352 349
353 $context: 'element', ( 350 $context: 'element', (
354 'parts': $parts-data, 351 'parts': $parts-data,
355 'selector': $selector 352 'selector': $selector
356 ); 353 );
357 354
358 @return $selector $context; 355 @return $selector $context;
359} 356}
360 357
361/// 358///
@@ -369,9 +366,9 @@
369/// @content 366/// @content
370/// 367///
371@mixin sibling-elem($name, $names...) { 368@mixin sibling-elem($name, $names...) {
372 @include related-elem('~', $name, $names...) { 369 @include related-elem('~', $name, $names...) {
373 @content; 370 @content;
374 } 371 }
375} 372}
376 373
377/// 374///
@@ -383,7 +380,7 @@
383/// @see {mixin} sibling-element 380/// @see {mixin} sibling-element
384/// 381///
385@function sibling-elem($name, $names...) { 382@function sibling-elem($name, $names...) {
386 @return related-elem('~', $name, $names...); 383 @return related-elem('~', $name, $names...);
387} 384}
388 385
389/// 386///
@@ -397,9 +394,9 @@
397/// @content 394/// @content
398/// 395///
399@mixin next-elem($name, $names...) { 396@mixin next-elem($name, $names...) {
400 @include related-elem('+', $name, $names...) { 397 @include related-elem('+', $name, $names...) {
401 @content; 398 @content;
402 } 399 }
403} 400}
404 401
405/// 402///
@@ -411,7 +408,7 @@
411/// @see {mixin} next-element 408/// @see {mixin} next-element
412/// 409///
413@function next-elem($name, $names...) { 410@function next-elem($name, $names...) {
414 @return related-elem('+', $name, $names...); 411 @return related-elem('+', $name, $names...);
415} 412}
416 413
417/// 414///
@@ -467,22 +464,22 @@
467/// } 464/// }
468/// 465///
469@mixin related-twin-elem($sign) { 466@mixin related-twin-elem($sign) {
470 $result: related-twin-elem($sign); 467 $result: related-twin-elem($sign);
471 $selector: nth($result, 1); 468 $selector: list.nth($result, 1);
472 $context: nth($result, 2); 469 $context: list.nth($result, 2);
473 470
474 @include validators.validate( 471 @include validators.validate(
475 'next-twin-elem', 472 'next-twin-elem',
476 (), 473 (),
477 $selector, 474 $selector,
478 $context 475 $context
479 ); 476 );
480 477
481 @include contexts.push(vars.$context-id, $context...); 478 @include contexts.push(vars.$context-id, $context...);
482 @at-root #{$selector} { 479 @at-root #{$selector} {
483 @content; 480 @content;
484 } 481 }
485 @include contexts.pop(vars.$context-id); 482 @include contexts.pop(vars.$context-id);
486} 483}
487 484
488/// 485///
@@ -494,96 +491,94 @@
494/// @see {mixin} next-twin-elem 491/// @see {mixin} next-twin-elem
495/// 492///
496@function related-twin-elem($sign) { 493@function related-twin-elem($sign) {
497 $noop: contexts.assert-stack-count(vars.$context-id, vars.$max-depth); 494 $noop: contexts.assert-stack-count(vars.$context-id, vars.$max-depth);
498 $noop: contexts.assert-stack-must-contain(vars.$context-id, 'element'); 495 $noop: contexts.assert-stack-must-contain(vars.$context-id, 'element');
499 496
500 $element-context: contexts.get(vars.$context-id, 'element'); 497 $element-context: contexts.get(vars.$context-id, 'element');
501 $element-selector: map-get(nth($element-context, 2), 'selector'); 498 $element-selector: map.get(list.nth($element-context, 2), 'selector');
502 499
503 $block-context: contexts.get(vars.$context-id, 'block'); 500 $block-context: contexts.get(vars.$context-id, 'block');
504 $block-base-selector: map-get(nth($block-context, 2), 'base-selector'); 501 $block-base-selector: map.get(list.nth($block-context, 2), 'base-selector');
505 502
506 $selector: (); 503 $selector: ();
507 $parts-data: (); 504 $parts-data: ();
508 505
509 // 506 //
510 // To determine the twin for each element, iterate the sub-selectors from the current selector 507 // To determine the twin for each element, iterate the sub-selectors from the current selector
511 // and check if it contains the currently inspected element. This has to be done with string 508 // and check if it contains the currently inspected element. This has to be done with string
512 // comparison since none of Sass selector functions is of use here. 509 // comparison since none of Sass selector functions is of use here.
513 // Finally, the current twin will be appended to the extracted sub-selector as a successor 510 // Finally, the current twin will be appended to the extracted sub-selector as a successor
514 // element. 511 // element.
515 // 512 //
516 @each $part-data in map-get(nth($element-context, 2), 'parts') { 513 @each $part-data in map.get(list.nth($element-context, 2), 'parts') {
517 $part-selector: map-get($part-data, 'selector'); 514 $part-selector: map.get($part-data, 'selector');
518 $part-name: map-get($part-data, 'name'); 515 $part-name: map.get($part-data, 'name');
519 516
520 $sel: (); 517 $sel: ();
521 @if functions.selector-suffix-match(&, $element-selector) { 518 @if functions.selector-suffix-match(&, $element-selector) {
522 // 519 //
523 // This mixin is included in the selector the last element mixin created. 520 // This mixin is included in the selector the last element mixin created.
524 // Possible outcomes: 521 // Possible outcomes:
525 // - {e} + {e} 522 // - {e} + {e}
526 // - [manual selector] {e} + {e} 523 // - [manual selector] {e} + {e}
527 // 524 //
528 525
529 @each $s in & { 526 @each $s in & {
530 @each $ps in $part-selector { 527 @each $ps in $part-selector {
531 @if nth($s, -1) == nth($ps, -1) { 528 @if list.nth($s, -1) == list.nth($ps, -1) {
532 $sel-ent: selector-nest($s, $sign, selector-append($block-base-selector, vars.$element-separator + $part-name)); 529 $sel-ent: selector.nest($s, $sign, selector.append($block-base-selector, vars.$element-separator + $part-name));
533 $sel: join($sel, $sel-ent, comma); 530 $sel: list.join($sel, $sel-ent, comma);
534 } 531 }
535 } 532 }
536 } 533 }
537 } @else { 534 } @else {
538 // 535 //
539 // This mixin is NOT included in the selector the last element mixin created. 536 // This mixin is NOT included in the selector the last element mixin created.
540 // Possible outcomes: 537 // Possible outcomes:
541 // - {e} {m,s} + {e} 538 // - {e} {m,s} + {e}
542 // - {e} [manual selector] + {e} 539 // - {e} [manual selector] + {e}
543 // - {e} {m,s} [manual selector] + {e} 540 // - {e} {m,s} [manual selector] + {e}
544 // 541 //
545 542
546 @each $s in & { 543 @each $s in & {
547 @each $ps in $part-selector { 544 @each $ps in $part-selector {
548 @if str-index(inspect($s), inspect($ps)) { 545 @if string.index(meta.inspect($s), meta.inspect($ps)) {
549 $char-index: str-length(inspect($ps)) + 1; 546 $char-index: string.length(meta.inspect($ps)) + 1;
550 $match: index(' ' ':' ',', str-slice(inspect($s), $char-index, $char-index)) != null; 547 $match: list.index(' ' ':' ',', string.slice(meta.inspect($s), $char-index, $char-index)) != null;
551 548
552 @if not $match { 549 @if not $match {
553 @each $separator in vars.$element-separator vars.$modifier-separator vars.$suffix-separator { 550 @each $separator in vars.$element-separator vars.$modifier-separator vars.$suffix-separator {
554 @if str-slice(inspect($s), $char-index, $char-index + str-length($separator) - 1) == $separator { 551 @if string.slice(meta.inspect($s), $char-index, $char-index + string.length($separator) - 1) == $separator {
555 $match: true; 552 $match: true;
556 } 553 }
557 } 554 }
558 } 555 }
559 556
560 @if $match { 557 @if $match {
561 $sel-ent: selector-nest($s, '+', selector-append($block-base-selector, vars.$element-separator + $part-name)); 558 $sel-ent: selector.nest($s, '+', selector.append($block-base-selector, vars.$element-separator + $part-name));
562 $sel: join($sel, $sel-ent, comma); 559 $sel: list.join($sel, $sel-ent, comma);
563 } 560 }
564 } 561 }
565 } 562 }
566 } 563 }
567 } 564 }
568 @if length($sel) != length($part-selector) { 565 @if list.length($sel) != list.length($part-selector) {
569 @error 'Could not generate twin element selector.'; 566 @error 'Could not generate twin element selector.';
570 } 567 }
571 568
572 $selector: join($selector, $sel, comma); 569 $selector: list.join($selector, $sel, comma);
573 $parts-data: append( 570 $parts-data: list.append($parts-data, (
574 $parts-data, ( 571 'name': $part-name,
575 'name': $part-name, 572 'selector': $sel
576 'selector': $sel 573 ));
577 ) 574 }
578 );
579 }
580 575
581 $context: 'element', ( 576 $context: 'element', (
582 'parts': $parts-data, 577 'parts': $parts-data,
583 'selector': $selector 578 'selector': $selector
584 ); 579 );
585 580
586 @return $selector $context; 581 @return $selector $context;
587} 582}
588 583
589/// 584///
@@ -594,9 +589,9 @@
594/// @content 589/// @content
595/// 590///
596@mixin sibling-twin-element { 591@mixin sibling-twin-element {
597 @include related-twin-elem('~') { 592 @include related-twin-elem('~') {
598 @content; 593 @content;
599 } 594 }
600} 595}
601 596
602/// 597///
@@ -608,7 +603,7 @@
608/// @see {mixin} sibling-twin-element 603/// @see {mixin} sibling-twin-element
609/// 604///
610@function sibling-twin-elem() { 605@function sibling-twin-elem() {
611 @return related-twin-elem('~'); 606 @return related-twin-elem('~');
612} 607}
613 608
614/// 609///
@@ -619,9 +614,9 @@
619/// @content 614/// @content
620/// 615///
621@mixin next-twin-elem { 616@mixin next-twin-elem {
622 @include related-twin-elem('+') { 617 @include related-twin-elem('+') {
623 @content; 618 @content;
624 } 619 }
625} 620}
626 621
627/// 622///
@@ -633,5 +628,5 @@
633/// @see {mixin} next-twin-elem 628/// @see {mixin} next-twin-elem
634/// 629///
635@function next-twin-elem() { 630@function next-twin-elem() {
636 @return related-twin-elem('+'); 631 @return related-twin-elem('+');
637} 632}
diff --git a/src/bem/_functions.scss b/src/bem/_functions.scss
index b7bd5ec..7f52b93 100644
--- a/src/bem/_functions.scss
+++ b/src/bem/_functions.scss
@@ -4,25 +4,28 @@
4/// @access public 4/// @access public
5//// 5////
6 6
7@use 'sass:list';
8@use 'sass:map';
9@use 'sass:selector';
7@use './vars'; 10@use './vars';
8 11
9/// 12///
10/// @access private 13/// @access private
11/// 14///
12@function theme-selector($name, $names...) { 15@function theme-selector($name, $names...) {
13 $namespace: map-get(vars.$namespaces, 'theme'); 16 $namespace: map.get(vars.$namespaces, 'theme');
14 $selector: null; 17 $selector: null;
15 18
16 @each $name in join($name, $names) { 19 @each $name in list.join($name, $names) {
17 $sel: '.' + $namespace + '-' + $name; 20 $sel: '.' + $namespace + '-' + $name;
18 21
19 @if $selector == null { 22 @if $selector == null {
20 $selector: join(selector-parse($sel), selector-parse('[class*=\' t-\'] ' + $sel), comma); 23 $selector: list.join(selector.parse($sel), selector.parse('[class*=\' t-\'] ' + $sel), comma);
21 $selector: join($selector, selector-parse('[class^=\'t-\'] ' + $sel), comma); 24 $selector: list.join($selector, selector.parse('[class^=\'t-\'] ' + $sel), comma);
22 } @else { 25 } @else {
23 $selector: selector-nest($selector, $sel); 26 $selector: selector.nest($selector, $sel);
24 } 27 }
25 } 28 }
26 29
27 @return $selector; 30 @return $selector;
28} 31}
diff --git a/src/bem/_modifier.scss b/src/bem/_modifier.scss
index 07267fe..10e2826 100644
--- a/src/bem/_modifier.scss
+++ b/src/bem/_modifier.scss
@@ -4,6 +4,11 @@
4/// @access public 4/// @access public
5//// 5////
6 6
7@use 'sass:list';
8@use 'sass:map';
9@use 'sass:meta';
10@use 'sass:selector';
11@use 'sass:string';
7@use './validators'; 12@use './validators';
8@use './vars'; 13@use './vars';
9@use '../functions'; 14@use '../functions';
@@ -111,22 +116,22 @@
111/// } 116/// }
112/// 117///
113@mixin modifier($name, $names...) { 118@mixin modifier($name, $names...) {
114 $result: modifier($name, $names...); 119 $result: modifier($name, $names...);
115 $selector: nth($result, 1); 120 $selector: list.nth($result, 1);
116 $context: nth($result, 2); 121 $context: list.nth($result, 2);
117 122
118 @include validators.validate( 123 @include validators.validate(
119 'modifier', 124 'modifier',
120 (name: $name, names: $names), 125 (name: $name, names: $names),
121 $selector, 126 $selector,
122 $context 127 $context
123 ); 128 );
124 129
125 @include contexts.push(vars.$context-id, $context...); 130 @include contexts.push(vars.$context-id, $context...);
126 @at-root #{$selector} { 131 @at-root #{$selector} {
127 @content; 132 @content;
128 } 133 }
129 @include contexts.pop(vars.$context-id); 134 @include contexts.pop(vars.$context-id);
130} 135}
131 136
132/// 137///
@@ -137,121 +142,115 @@
137/// @see {mixin} modifier 142/// @see {mixin} modifier
138/// 143///
139@function modifier($name, $names...) { 144@function modifier($name, $names...) {
140 $noop: contexts.assert-stack-count(vars.$context-id, vars.$max-depth); 145 $noop: contexts.assert-stack-count(vars.$context-id, vars.$max-depth);
141 $noop: contexts.assert-stack-must-contain(vars.$context-id, 'block'); 146 $noop: contexts.assert-stack-must-contain(vars.$context-id, 'block');
142 147
143 $parent-context: contexts.get(vars.$context-id, 'block' 'element' 'modifier' 'suffix' 'state'); 148 $parent-context: contexts.get(vars.$context-id, 'block' 'element' 'modifier' 'suffix' 'state');
144 $parent-selector: map-get(nth($parent-context, 2), 'selector'); 149 $parent-selector: map.get(list.nth($parent-context, 2), 'selector');
145 $selector: (); 150 $selector: ();
146 $parts-data: (); 151 $parts-data: ();
147 152
148 @if not functions.selector-suffix-match(&, $parent-selector) { 153 @if not functions.selector-suffix-match(&, $parent-selector) {
149 // 154 //
150 // The current selector doesn't match the parent selector. 155 // The current selector doesn't match the parent selector.
151 // The user manually added a selector between parent context and this modifier call. 156 // The user manually added a selector between parent context and this modifier call.
152 // This case is forbidden because any outcome semantically wouldn't make sense: 157 // This case is forbidden because any outcome semantically wouldn't make sense:
153 // - {b,e,m,s} [manual selector] {b,e,m,s}--modifier 158 // - {b,e,m,s} [manual selector] {b,e,m,s}--modifier
154 // - {b,e,m,s}--modifier [manual selector] 159 // - {b,e,m,s}--modifier [manual selector]
155 // The first case would make the modifier behave like an element. 160 // The first case would make the modifier behave like an element.
156 // The second case is unintuitive, the code would be more clear by nesting the manual 161 // The second case is unintuitive, the code would be more clear by nesting the manual
157 // selector in the modifier instead. 162 // selector in the modifier instead.
158 // 163 //
159 164
160 @error 'A modifier must be an immediate child of the parent context'; 165 @error 'A modifier must be an immediate child of the parent context';
161 } 166 }
162 167
163 @each $name in functions.list-prepend($names, $name) { 168 @each $name in functions.list-prepend($names, $name) {
164 $extend: false; 169 $extend: false;
165 @if type-of($name) == list { 170 @if meta.type-of($name) == list {
166 $extend: nth($name, 2); 171 $extend: list.nth($name, 2);
167 $name: nth($name, 1); 172 $name: list.nth($name, 1);
168 } 173 }
169 174
170 @if index('block' 'element', nth($parent-context, 1)) or $extend == true { 175 @if list.index('block' 'element', list.nth($parent-context, 1)) or $extend == true {
171 // 176 //
172 // Either the parent context is block or element, or a modifier or suffix 177 // Either the parent context is block or element, or a modifier or suffix
173 // is to be extended. The modifier part can simply be appended. 178 // is to be extended. The modifier part can simply be appended.
174 // Possible outcomes: 179 // Possible outcomes:
175 // - {b,e,m,s}--modifier 180 // - {b,e,m,s}--modifier
176 // 181 //
177 182
178 $sel: selector-append(&, vars.$modifier-separator + $name); 183 $sel: selector.append(&, vars.$modifier-separator + $name);
179 $selector: join($selector, $sel, comma); 184 $selector: list.join($selector, $sel, comma);
180 $parts-data: append( 185 $parts-data: list.append($parts-data, (
181 $parts-data, ( 186 'name': $name,
182 'name': $name, 187 'selector': $sel
183 'selector': $sel 188 ));
184 ) 189 } @else {
185 ); 190 //
186 } @else { 191 // Parent context is modifier, suffix or state and $extend is false.
187 // 192 //
188 // Parent context is modifier, suffix or state and $extend is false.
189 //
190 193
191 $be-context: contexts.get(vars.$context-id, 'block' 'element'); 194 $be-context: contexts.get(vars.$context-id, 'block' 'element');
192 195
193 @if nth($be-context, 1) == 'element' { 196 @if list.nth($be-context, 1) == 'element' {
194 // 197 //
195 // Latest context is element. Since element contexts can consist of multiple single 198 // Latest context is element. Since element contexts can consist of multiple single
196 // elements, inspect all elements and append its selector with the suffix "--$name". 199 // elements, inspect all elements and append its selector with the suffix "--$name".
197 // This has to be done with string comparison since none of Sass selector functions 200 // This has to be done with string comparison since none of Sass selector functions
198 // is of use here. 201 // is of use here.
199 // Possible outcomes: 202 // Possible outcomes:
200 // - {m,s}.{e}--modifier 203 // - {m,s}.{e}--modifier
201 // 204 //
202 205
203 $nsel: (); 206 $nsel: ();
204 207
205 @each $elem-part-data in map-get(nth($be-context, 2), 'parts') { 208 @each $elem-part-data in map.get(list.nth($be-context, 2), 'parts') {
206 $elem-part-selector: map-get($elem-part-data, 'selector'); 209 $elem-part-selector: map.get($elem-part-data, 'selector');
207 210
208 $sel: (); 211 $sel: ();
209 @each $s in & { 212 @each $s in & {
210 @each $ps in $elem-part-selector { 213 @each $ps in $elem-part-selector {
211 @if str-index(inspect($s), inspect($ps) + vars.$modifier-separator) or str-index(inspect($s), inspect($ps) + vars.$suffix-separator) { 214 @if string.index(meta.inspect($s), meta.inspect($ps) + vars.$modifier-separator) or string.index(meta.inspect($s), meta.inspect($ps) + vars.$suffix-separator) {
212 $sel: join($sel, selector-unify($s, selector-append($ps, vars.$modifier-separator + $name)), comma); 215 $sel: list.join($sel, selector.unify($s, selector.append($ps, vars.$modifier-separator + $name)), comma);
213 } 216 }
214 } 217 }
215 } 218 }
216 @if length($sel) == 0 { 219 @if list.length($sel) == 0 {
217 @error 'Could not generate modifier selector.'; 220 @error 'Could not generate modifier selector.';
218 } 221 }
219 222
220 $nsel: join($nsel, $sel, comma); 223 $nsel: list.join($nsel, $sel, comma);
221 } 224 }
222 225
223 $selector: join($selector, $nsel, comma); 226 $selector: list.join($selector, $nsel, comma);
224 $parts-data: append( 227 $parts-data: list.append($parts-data, (
225 $parts-data, ( 228 'name': $name,
226 'name': $name, 229 'selector': $nsel
227 'selector': $nsel 230 ));
228 ) 231 } @else {
229 ); 232 //
230 } @else { 233 // Latest context is block. Just append the modifier part.
231 // 234 // Possible outcomes:
232 // Latest context is block. Just append the modifier part. 235 // - {m,s}.{b}--modifier
233 // Possible outcomes: 236 //
234 // - {m,s}.{b}--modifier
235 //
236 237
237 $block-base-selector: map-get(nth($be-context, 2), 'base-selector'); 238 $block-base-selector: map.get(list.nth($be-context, 2), 'base-selector');
238 239
239 $sel: selector-append(&, $block-base-selector, vars.$modifier-separator + $name); 240 $sel: selector.append(&, $block-base-selector, vars.$modifier-separator + $name);
240 $selector: join($selector, $sel, comma); 241 $selector: list.join($selector, $sel, comma);
241 $parts-data: append( 242 $parts-data: list.append($parts-data, (
242 $parts-data, ( 243 'name': $name,
243 'name': $name, 244 'selector': $sel
244 'selector': $sel 245 ));
245 ) 246 }
246 ); 247 }
247 } 248 }
248 }
249 }
250 249
251 $context: 'modifier', ( 250 $context: 'modifier', (
252 'parts': $parts-data, 251 'parts': $parts-data,
253 'selector': $selector 252 'selector': $selector
254 ); 253 );
255 254
256 @return $selector $context; 255 @return $selector $context;
257} 256}
diff --git a/src/bem/_multi.scss b/src/bem/_multi.scss
index 1de5cdc..c0beeeb 100644
--- a/src/bem/_multi.scss
+++ b/src/bem/_multi.scss
@@ -4,6 +4,10 @@
4/// @access public 4/// @access public
5//// 5////
6 6
7@use 'sass:list';
8@use 'sass:meta';
9@use 'sass:selector';
10@use 'sass:string';
7@use '../functions'; 11@use '../functions';
8@use '../contexts'; 12@use '../contexts';
9@use './block'; 13@use './block';
@@ -81,67 +85,67 @@
81/// } 85/// }
82/// 86///
83@mixin multi($first, $others...) { 87@mixin multi($first, $others...) {
84 @include contexts.assert-stack-count(vars.$context-id, vars.$max-depth); 88 @include contexts.assert-stack-count(vars.$context-id, vars.$max-depth);
85 89
86 @each $entity in functions.list-prepend($others, $first) { 90 @each $entity in functions.list-prepend($others, $first) {
87 $is-manual-selector: false; 91 $is-manual-selector: false;
88 92
89 @if type-of($entity) == string { 93 @if meta.type-of($entity) == string {
90 @if find-bem-function($entity) == null { 94 @if find-bem-function($entity) == null {
91 $is-manual-selector: true; 95 $is-manual-selector: true;
92 } 96 }
93 } 97 }
94 98
95 @if $is-manual-selector { 99 @if $is-manual-selector {
96 $sel: if(&, selector-nest(&, $entity), selector-parse($entity)); 100 $sel: if(&, selector.nest(&, $entity), selector.parse($entity));
97 101
98 @at-root #{$sel} { 102 @at-root #{$sel} {
99 @content; 103 @content;
100 } 104 }
101 } @else { 105 } @else {
102 $entity-func-id: null; 106 $entity-func-id: null;
103 107
104 @if type-of($entity) == list { 108 @if meta.type-of($entity) == list {
105 $entity-func-id: nth($entity, 1); 109 $entity-func-id: list.nth($entity, 1);
106 $entity: functions.list-slice($entity, 2); 110 $entity: functions.list-slice($entity, 2);
107 } @else { 111 } @else {
108 $entity-func-id: $entity; 112 $entity-func-id: $entity;
109 $entity: (); 113 $entity: ();
110 } 114 }
111 115
112 @if str-slice($entity-func-id, str-length($entity-func-id)) == ':' { 116 @if string.slice($entity-func-id, string.length($entity-func-id)) == ':' {
113 $entity-func-id: unquote(str-slice($entity-func-id, 1, str-length($entity-func-id) - 1)); 117 $entity-func-id: string.unquote(string.slice($entity-func-id, 1, string.length($entity-func-id) - 1));
114 } 118 }
115 119
116 $sel-func: find-bem-function($entity-func-id); 120 $sel-func: find-bem-function($entity-func-id);
117 121
118 @if $sel-func == null { 122 @if $sel-func == null {
119 @error 'Function "#{inspect($entity-func-id)}" was not found.'; 123 @error 'Function "#{inspect($entity-func-id)}" was not found.';
120 } 124 }
121 125
122 $entity-result: call($sel-func, $entity...); 126 $entity-result: meta.call($sel-func, $entity...);
123 $entity-result-selector: nth($entity-result, 1); 127 $entity-result-selector: list.nth($entity-result, 1);
124 $entity-result-context: nth($entity-result, 2); 128 $entity-result-context: list.nth($entity-result, 2);
125 129
126 @if $entity-result-context != null { 130 @if $entity-result-context != null {
127 @include contexts.push(vars.$context-id, $entity-result-context...); 131 @include contexts.push(vars.$context-id, $entity-result-context...);
128 } 132 }
129 @at-root #{$entity-result-selector} { 133 @at-root #{$entity-result-selector} {
130 @content; 134 @content;
131 } 135 }
132 @if $entity-result-context != null { 136 @if $entity-result-context != null {
133 @include contexts.pop(vars.$context-id); 137 @include contexts.pop(vars.$context-id);
134 } 138 }
135 } 139 }
136 } 140 }
137} 141}
138 142
139@function find-bem-function($name) { 143@function find-bem-function($name) {
140 @each $module in (block element modifier state suffix theme) { 144 @each $module in (block element modifier state suffix theme) {
141 @if function-exists($name, $module) { 145 @if meta.function-exists($name, $module) {
142 @return get-function($name, $module: $module); 146 @return meta.get-function($name, $module: $module);
143 } 147 }
144 } 148 }
145 149
146 @return null; 150 @return null;
147} 151}
diff --git a/src/bem/_state.scss b/src/bem/_state.scss
index 41bacee..bd0efb1 100644
--- a/src/bem/_state.scss
+++ b/src/bem/_state.scss
@@ -4,6 +4,8 @@
4/// @access public 4/// @access public
5//// 5////
6 6
7@use 'sass:list';
8@use 'sass:selector';
7@use './validators'; 9@use './validators';
8@use './vars'; 10@use './vars';
9@use '../contexts'; 11@use '../contexts';
@@ -56,22 +58,22 @@
56/// } 58/// }
57/// 59///
58@mixin state($prefix, $state, $states...) { 60@mixin state($prefix, $state, $states...) {
59 $result: state($prefix, $state, $states...); 61 $result: state($prefix, $state, $states...);
60 $selector: nth($result, 1); 62 $selector: list.nth($result, 1);
61 $context: nth($result, 2); 63 $context: list.nth($result, 2);
62 64
63 @include validators.validate( 65 @include validators.validate(
64 'state', 66 'state',
65 (prefix: $prefix, state: $state, states: $states), 67 (prefix: $prefix, state: $state, states: $states),
66 $selector, 68 $selector,
67 $context 69 $context
68 ); 70 );
69 71
70 @include contexts.push(vars.$context-id, $context...); 72 @include contexts.push(vars.$context-id, $context...);
71 @at-root #{$selector} { 73 @at-root #{$selector} {
72 @content; 74 @content;
73 } 75 }
74 @include contexts.pop(vars.$context-id); 76 @include contexts.pop(vars.$context-id);
75} 77}
76 78
77/// 79///
@@ -82,29 +84,27 @@
82/// @see {mixin} has 84/// @see {mixin} has
83/// 85///
84@function state($prefix, $state, $states...) { 86@function state($prefix, $state, $states...) {
85 $selector: (); 87 $selector: ();
86 $parts-data: (); 88 $parts-data: ();
87 89
88 @each $state in join($state, $states) { 90 @each $state in list.join($state, $states) {
89 $sel: selector-parse('.#{$prefix}-#{$state}'); 91 $sel: selector.parse('.#{$prefix}-#{$state}');
90 @if & { 92 @if & {
91 $sel: selector-append(&, $sel); 93 $sel: selector.append(&, $sel);
92 } 94 }
93 $selector: join($selector, $sel, comma); 95 $selector: list.join($selector, $sel, comma);
94 $parts-data: append( 96 $parts-data: list.append($parts-data, (
95 $parts-data, ( 97 'name': $state,
96 'name': $state, 98 'selector': $sel
97 'selector': $sel 99 ));
98 ) 100 }
99 );
100 }
101 101
102 $context: 'state', ( 102 $context: 'state', (
103 'parts': $parts-data, 103 'parts': $parts-data,
104 'selector': $selector 104 'selector': $selector
105 ); 105 );
106 106
107 @return $selector $context; 107 @return $selector $context;
108} 108}
109 109
110/// 110///
@@ -113,9 +113,9 @@
113/// It's a shorthand for state('is', $state, $states...). 113/// It's a shorthand for state('is', $state, $states...).
114/// 114///
115@mixin is($state, $states...) { 115@mixin is($state, $states...) {
116 @include state('is', $state, $states...) { 116 @include state('is', $state, $states...) {
117 @content; 117 @content;
118 } 118 }
119} 119}
120 120
121/// 121///
@@ -126,7 +126,7 @@
126/// @see {mixin} is 126/// @see {mixin} is
127/// 127///
128@function is($state, $states...) { 128@function is($state, $states...) {
129 @return state('is', $state, $states...); 129 @return state('is', $state, $states...);
130} 130}
131 131
132/// 132///
@@ -135,9 +135,9 @@
135/// It's a shorthand for state('has', $state, $states...). 135/// It's a shorthand for state('has', $state, $states...).
136/// 136///
137@mixin has($state, $states...) { 137@mixin has($state, $states...) {
138 @include state('has', $state, $states...) { 138 @include state('has', $state, $states...) {
139 @content; 139 @content;
140 } 140 }
141} 141}
142 142
143/// 143///
@@ -148,5 +148,5 @@
148/// @see {mixin} has 148/// @see {mixin} has
149/// 149///
150@function has($state, $states...) { 150@function has($state, $states...) {
151 @return state('has', $state, $states...); 151 @return state('has', $state, $states...);
152} 152}
diff --git a/src/bem/_suffix.scss b/src/bem/_suffix.scss
index 2ddb54d..93e4066 100644
--- a/src/bem/_suffix.scss
+++ b/src/bem/_suffix.scss
@@ -4,6 +4,9 @@
4/// @access public 4/// @access public
5//// 5////
6 6
7@use 'sass:list';
8@use 'sass:map';
9@use 'sass:selector';
7@use './validators'; 10@use './validators';
8@use './vars'; 11@use './vars';
9@use '../functions'; 12@use '../functions';
@@ -54,22 +57,22 @@
54/// } 57/// }
55/// 58///
56@mixin suffix($name) { 59@mixin suffix($name) {
57 $result: suffix($name); 60 $result: suffix($name);
58 $selector: nth($result, 1); 61 $selector: list.nth($result, 1);
59 $context: nth($result, 2); 62 $context: list.nth($result, 2);
60 63
61 @include validators.validate( 64 @include validators.validate(
62 'suffix', 65 'suffix',
63 (name: $name), 66 (name: $name),
64 $selector, 67 $selector,
65 $context 68 $context
66 ); 69 );
67 70
68 @include contexts.push(vars.$context-id, $context...); 71 @include contexts.push(vars.$context-id, $context...);
69 @at-root #{$selector} { 72 @at-root #{$selector} {
70 @content; 73 @content;
71 } 74 }
72 @include contexts.pop(vars.$context-id); 75 @include contexts.pop(vars.$context-id);
73} 76}
74 77
75/// 78///
@@ -80,44 +83,44 @@
80/// @see {mixin} suffix 83/// @see {mixin} suffix
81/// 84///
82@function suffix($name) { 85@function suffix($name) {
83 // 86 //
84 // Suffixes can be used on block, element and modifier. 87 // Suffixes can be used on block, element and modifier.
85 // 88 //
86 89
87 $noop: contexts.assert-stack-count(vars.$context-id, vars.$max-depth); 90 $noop: contexts.assert-stack-count(vars.$context-id, vars.$max-depth);
88 $noop: contexts.assert-stack-must-contain(vars.$context-id, 'block'); 91 $noop: contexts.assert-stack-must-contain(vars.$context-id, 'block');
89 $noop: contexts.assert-stack-must-not-contain(vars.$context-id, 'suffix'); 92 $noop: contexts.assert-stack-must-not-contain(vars.$context-id, 'suffix');
90 93
91 $parent-context: contexts.get(vars.$context-id, 'block' 'element' 'modifier'); 94 $parent-context: contexts.get(vars.$context-id, 'block' 'element' 'modifier');
92 $parent-selector: map-get(nth($parent-context, 2), 'selector'); 95 $parent-selector: map.get(list.nth($parent-context, 2), 'selector');
93 96
94 @if not functions.selector-suffix-match(&, $parent-selector) { 97 @if not functions.selector-suffix-match(&, $parent-selector) {
95 // 98 //
96 // The current selector doesn't match the parent selector. 99 // The current selector doesn't match the parent selector.
97 // The user manually added a selector between parent context and this suffix call. 100 // The user manually added a selector between parent context and this suffix call.
98 // This case is forbidden because any outcome semantically wouldn't make sense: 101 // This case is forbidden because any outcome semantically wouldn't make sense:
99 // - {b,e,m} [manual selector] {b,e,m}@suffix 102 // - {b,e,m} [manual selector] {b,e,m}@suffix
100 // - {b,e,m}@suffix [manual selector] 103 // - {b,e,m}@suffix [manual selector]
101 // The first case would make the modifier behave like an element. 104 // The first case would make the modifier behave like an element.
102 // The second case is unintuitive, the code would be more clear by nesting the manual 105 // The second case is unintuitive, the code would be more clear by nesting the manual
103 // selector in the suffix instead. 106 // selector in the suffix instead.
104 // 107 //
105 108
106 @error 'A suffix must be an immediate child of the parent context'; 109 @error 'A suffix must be an immediate child of the parent context';
107 } 110 }
108 111
109 // 112 //
110 // The suffix part can simply be appended. 113 // The suffix part can simply be appended.
111 // Possible outcomes: 114 // Possible outcomes:
112 // - {b,e,m}@suffix 115 // - {b,e,m}@suffix
113 // 116 //
114 117
115 $selector: selector-append(&, vars.$suffix-separator + $name); 118 $selector: selector.append(&, vars.$suffix-separator + $name);
116 119
117 $context: 'suffix', ( 120 $context: 'suffix', (
118 'name': $name, 121 'name': $name,
119 'selector': $selector 122 'selector': $selector
120 ); 123 );
121 124
122 @return $selector $context; 125 @return $selector $context;
123} 126}
diff --git a/src/bem/_theme.scss b/src/bem/_theme.scss
index ff1ba49..535cc81 100644
--- a/src/bem/_theme.scss
+++ b/src/bem/_theme.scss
@@ -4,6 +4,9 @@
4/// @access public 4/// @access public
5//// 5////
6 6
7@use 'sass:list';
8@use 'sass:map';
9@use 'sass:selector';
7@use './functions'; 10@use './functions';
8@use './validators'; 11@use './validators';
9@use './vars'; 12@use './vars';
@@ -18,22 +21,22 @@
18/// @content 21/// @content
19/// 22///
20@mixin at-theme($name, $names...) { 23@mixin at-theme($name, $names...) {
21 $result: at-theme($name, $names...); 24 $result: at-theme($name, $names...);
22 $selector: nth($result, 1); 25 $selector: list.nth($result, 1);
23 $context: nth($result, 2); 26 $context: list.nth($result, 2);
24 27
25 @include validators.validate( 28 @include validators.validate(
26 'at-theme', 29 'at-theme',
27 (name: $name, names: $names), 30 (name: $name, names: $names),
28 $selector, 31 $selector,
29 $context 32 $context
30 ); 33 );
31 34
32 @include contexts.push(vars.$context-id, $context...); 35 @include contexts.push(vars.$context-id, $context...);
33 @at-root #{$selector} { 36 @at-root #{$selector} {
34 @content; 37 @content;
35 } 38 }
36 @include contexts.pop(vars.$context-id); 39 @include contexts.pop(vars.$context-id);
37} 40}
38 41
39/// 42///
@@ -45,22 +48,22 @@
45/// @see {mixin} at-theme 48/// @see {mixin} at-theme
46/// 49///
47@function at-theme($name, $names...) { 50@function at-theme($name, $names...) {
48 $noop: contexts.assert-stack-must-contain(vars.$context-id, 'block'); 51 $noop: contexts.assert-stack-must-contain(vars.$context-id, 'block');
49 52
50 $parent-context: contexts.get(vars.$context-id, 'block'); 53 $parent-context: contexts.get(vars.$context-id, 'block');
51 $parent-selector: map-get(nth($parent-context, 2), 'selector'); 54 $parent-selector: map.get(list.nth($parent-context, 2), 'selector');
52 55
53 //@if not functions.selector-suffix-match(&, $parent-selector) { 56 //@if not functions.selector-suffix-match(&, $parent-selector) {
54 // @error 'An at-theme rule must be an immediate child of a block'; 57 // @error 'An at-theme rule must be an immediate child of a block';
55 //} 58 //}
56 59
57 $selector: functions.theme-selector($name, $names...); 60 $selector: functions.theme-selector($name, $names...);
58 $selector: selector-nest($selector, &); 61 $selector: selector.nest($selector, &);
59 62
60 $context: 'at-theme', ( 63 $context: 'at-theme', (
61 'name': join($name, $names), 64 'name': list.join($name, $names),
62 'selector': $selector 65 'selector': $selector
63 ); 66 );
64 67
65 @return $selector $context; 68 @return $selector $context;
66} 69}
diff --git a/src/bem/_validators.scss b/src/bem/_validators.scss
index 042e15e..bc3c9b7 100644
--- a/src/bem/_validators.scss
+++ b/src/bem/_validators.scss
@@ -16,6 +16,9 @@
16/// @access public 16/// @access public
17//// 17////
18 18
19@use 'sass:list';
20@use 'sass:map';
21@use 'sass:meta';
19@use './vars'; 22@use './vars';
20@use '../functions'; 23@use '../functions';
21@use '../contexts'; 24@use '../contexts';
@@ -46,7 +49,7 @@ $validators: ();
46/// @param {string} $func-names - Other function names. 49/// @param {string} $func-names - Other function names.
47/// 50///
48@mixin add($func-name, $func-names...) { 51@mixin add($func-name, $func-names...) {
49 $noop: add($func-name, $func-names...); 52 $noop: add($func-name, $func-names...);
50} 53}
51 54
52/// 55///
@@ -55,11 +58,11 @@ $validators: ();
55/// @see {mixin} add 58/// @see {mixin} add
56/// 59///
57@function add($func-name, $func-names...) { 60@function add($func-name, $func-names...) {
58 @each $fn-name in join($func-name, $func-names) { 61 @each $fn-name in list.join($func-name, $func-names) {
59 $fn: get-function($fn-name); 62 $fn: meta.get-function($fn-name);
60 $validators: map-merge($validators, ($fn-name: $fn)); 63 $validators: map.merge($validators, ($fn-name: $fn));
61 } 64 }
62 @return null; 65 @return null;
63} 66}
64 67
65/// 68///
@@ -69,7 +72,7 @@ $validators: ();
69/// @param {string} $func-names - Other function names. 72/// @param {string} $func-names - Other function names.
70/// 73///
71@mixin remove($func-name, $func-names...) { 74@mixin remove($func-name, $func-names...) {
72 $noop: remove($func-name, $func-names...); 75 $noop: remove($func-name, $func-names...);
73} 76}
74 77
75/// 78///
@@ -78,20 +81,20 @@ $validators: ();
78/// @see {mixin} remove 81/// @see {mixin} remove
79/// 82///
80@function remove($func-name, $func-names...) { 83@function remove($func-name, $func-names...) {
81 $validators: map-remove($validators, $func-name, $func-names...); 84 $validators: map.remove($validators, $func-name, $func-names...);
82 @return null; 85 @return null;
83} 86}
84 87
85/// 88///
86/// @access private 89/// @access private
87/// 90///
88@mixin validate($type, $args, $selector, $context) { 91@mixin validate($type, $args, $selector, $context) {
89 @each $id, $fn in $validators { 92 @each $id, $fn in $validators {
90 $result: call($fn, $type, $args, $selector, $context); 93 $result: meta.call($fn, $type, $args, $selector, $context);
91 @if not nth($result, 1) { 94 @if not list.nth($result, 1) {
92 @error 'A BEM validator rejected this mixin usage due to the following reason: #{nth($result, 2)}'; 95 @error 'A BEM validator rejected this mixin usage due to the following reason: #{nth($result, 2)}';
93 } 96 }
94 } 97 }
95} 98}
96 99
97// 100//
@@ -105,76 +108,76 @@ $validators: ();
105/// namespace used. 108/// namespace used.
106/// 109///
107@function enforce-namespace-order($type, $args, $selector, $context) { 110@function enforce-namespace-order($type, $args, $selector, $context) {
108 @if not global-variable-exists(namespace-order, vars) { 111 @if not meta.global-variable-exists(namespace-order, vars) {
109 vars.$namespace-order: map-keys(vars.$namespaces); 112 vars.$namespace-order: map.keys(vars.$namespaces);
110 } 113 }
111 @if not global-variable-exists(cur-namespace-index, vars) { 114 @if not meta.global-variable-exists(cur-namespace-index, vars) {
112 vars.$cur-namespace-index: 1; 115 vars.$cur-namespace-index: 1;
113 } 116 }
114 117
115 @if $type == 'block' { 118 @if $type == 'block' {
116 $block-type: map-get($args, 'type'); 119 $block-type: map.get($args, 'type');
117 120
118 @if $block-type != null { 121 @if $block-type != null {
119 $ns-index: index(vars.$namespace-order, $block-type); 122 $ns-index: list.index(vars.$namespace-order, $block-type);
120 123
121 @if $ns-index != null { 124 @if $ns-index != null {
122 @if $ns-index < vars.$cur-namespace-index { 125 @if $ns-index < vars.$cur-namespace-index {
123 @return false 'Namespace "#{$block-type}" comes before current namespace #{nth(vars.$namespace-order, vars.$cur-namespace-index)}'; 126 @return false 'Namespace "#{$block-type}" comes before current namespace #{nth(vars.$namespace-order, vars.$cur-namespace-index)}';
124 } 127 }
125 128
126 vars.$cur-namespace-index: $ns-index; 129 vars.$cur-namespace-index: $ns-index;
127 } 130 }
128 } 131 }
129 } 132 }
130 133
131 @return true ''; 134 @return true '';
132} 135}
133 136
134/// 137///
135/// A validator that makes all BEM entities immutable. 138/// A validator that makes all BEM entities immutable.
136/// 139///
137@function immutable-entities($type, $args, $selector, $context) { 140@function immutable-entities($type, $args, $selector, $context) {
138 @if not global-variable-exists(generated-selectors, vars) { 141 @if not meta.global-variable-exists(generated-selectors, vars) {
139 vars.$generated-selectors: (); 142 vars.$generated-selectors: ();
140 } 143 }
141 144
142 $block-name: null; 145 $block-name: null;
143 $block-type: null; 146 $block-type: null;
144 $block-id: null; 147 $block-id: null;
145 148
146 @if $type == 'block' { 149 @if $type == 'block' {
147 $block-name: map-get($args, 'name'); 150 $block-name: map.get($args, 'name');
148 $block-type: map-get($args, 'type'); 151 $block-type: map.get($args, 'type');
149 } @else { 152 } @else {
150 $block-context: contexts.get(vars.$context-id, 'block'); 153 $block-context: contexts.get(vars.$context-id, 'block');
151 $block-name: map-get(nth($block-context, 2), 'name'); 154 $block-name: map.get(list.nth($block-context, 2), 'name');
152 $block-type: map-get(nth($block-context, 2), 'type'); 155 $block-type: map.get(list.nth($block-context, 2), 'type');
153 } 156 }
154 157
155 @if $block-type != null { 158 @if $block-type != null {
156 $block-id: $block-name + '_' + $block-type; 159 $block-id: $block-name + '_' + $block-type;
157 } @else { 160 } @else {
158 $block-id: $block-name; 161 $block-id: $block-name;
159 } 162 }
160 163
161 @if $type == 'block' { 164 @if $type == 'block' {
162 @if map-has-key(vars.$generated-selectors, $block-id) { 165 @if map.has-key(vars.$generated-selectors, $block-id) {
163 @return false 'Entity "#{$type}" with arguments [ #{functions.map-print($args)} ] was already defined.'; 166 @return false 'Entity "#{$type}" with arguments [ #{functions.map-print($args)} ] was already defined.';
164 } 167 }
165 168
166 vars.$generated-selectors: map-merge(vars.$generated-selectors, ($block-id: ())); 169 vars.$generated-selectors: map.merge(vars.$generated-selectors, ($block-id: ()));
167 } @else { 170 } @else {
168 $selectors: map-get(vars.$generated-selectors, $block-id); 171 $selectors: map.get(vars.$generated-selectors, $block-id);
169 172
170 @if index($selectors, $selector) { 173 @if list.index($selectors, $selector) {
171 @return false 'Entity "#{$type}" with arguments [ #{functions.map-print($args)} ] was already defined.'; 174 @return false 'Entity "#{$type}" with arguments [ #{functions.map-print($args)} ] was already defined.';
172 } 175 }
173 176
174 $selectors: append($selectors, $selector); 177 $selectors: list.append($selectors, $selector);
175 178
176 vars.$generated-selectors: map-merge(vars.$generated-selectors, ($block-id: $selectors)); 179 vars.$generated-selectors: map.merge(vars.$generated-selectors, ($block-id: $selectors));
177 } 180 }
178 181
179 @return true ''; 182 @return true '';
180} 183}
diff --git a/src/bem/_vars.scss b/src/bem/_vars.scss
index 3d0f92a..823bf0a 100644
--- a/src/bem/_vars.scss
+++ b/src/bem/_vars.scss
@@ -41,15 +41,15 @@ $suffix-separator: '\\@' !default;
41/// @type map 41/// @type map
42/// 42///
43$namespaces: ( 43$namespaces: (
44 object: 'o', 44 object: 'o',
45 component: 'c', 45 component: 'c',
46 layout: 'l', 46 layout: 'l',
47 scope: 's', 47 scope: 's',
48 theme: 't', 48 theme: 't',
49 utility: 'u', 49 utility: 'u',
50 js: 'js', 50 js: 'js',
51 qa: 'qa', 51 qa: 'qa',
52 hack: '_' 52 hack: '_'
53) !default; 53) !default;
54 54
55/// 55///
@@ -100,9 +100,9 @@ $debug: false !default;
100/// @type map 100/// @type map
101/// 101///
102$debug-colors: ( 102$debug-colors: (
103 object: #ffa500, 103 object: #ffa500,
104 component: #00f, 104 component: #00f,
105 layout: #ff0, 105 layout: #ff0,
106 utility: #008000, 106 utility: #008000,
107 hack: #f00 107 hack: #f00
108) !default; 108) !default;