diff options
Diffstat (limited to 'src/_contexts.scss')
-rw-r--r-- | src/_contexts.scss | 315 |
1 files changed, 315 insertions, 0 deletions
diff --git a/src/_contexts.scss b/src/_contexts.scss new file mode 100644 index 0000000..556fde3 --- /dev/null +++ b/src/_contexts.scss | |||
@@ -0,0 +1,315 @@ | |||
1 | //// | ||
2 | /// Context handling. | ||
3 | /// | ||
4 | /// Contexts allow you to pass data between mixins and let you enforce a certain nesting order. | ||
5 | /// It's an essential part for the BEM-related mixins. | ||
6 | /// | ||
7 | /// If you want to create a new context, the easiest pattern is to create a new mixin and wrap | ||
8 | /// the @content between a pair of iro-context-push and iro-context-pop. | ||
9 | /// From within the @content, you can access the context's data with iro-context-get. | ||
10 | /// To make the compilation fail if a certain nesting order is violated, use | ||
11 | /// iro-context-assert-stack-must-contain and iro-context-assert-stack-must-not-contain. | ||
12 | /// | ||
13 | /// @group Contexts | ||
14 | /// | ||
15 | /// @access public | ||
16 | //// | ||
17 | |||
18 | /// | ||
19 | /// Map of all context stacks. | ||
20 | /// | ||
21 | /// @type map | ||
22 | /// | ||
23 | /// @access private | ||
24 | /// | ||
25 | $iro-context-stacks: (); | ||
26 | |||
27 | /// | ||
28 | /// Create a new context stack. | ||
29 | /// | ||
30 | /// @param {string} $stack-id - ID of context stack | ||
31 | /// | ||
32 | /// @throw If context stack already exists | ||
33 | /// | ||
34 | @mixin iro-context-stack-create($stack-id) { | ||
35 | $noop: iro-context-stack-create($stack-id); | ||
36 | } | ||
37 | |||
38 | /// | ||
39 | /// Create a new context stack. | ||
40 | /// | ||
41 | /// @param {string} $stack-id - ID of context stack | ||
42 | /// | ||
43 | @function iro-context-stack-create($stack-id) { | ||
44 | @if map-has-key($iro-context-stacks, $stack-id) { | ||
45 | @error 'Context stack "#{inspect($stack-id)}" does not exist.'; | ||
46 | } | ||
47 | |||
48 | $iro-context-stacks: map-merge($iro-context-stacks, ($stack-id: ())) !global; | ||
49 | |||
50 | @return null; | ||
51 | } | ||
52 | |||
53 | /// | ||
54 | /// Clear a context stack. | ||
55 | /// | ||
56 | /// @param {string} $stack-id - ID of context stack | ||
57 | /// | ||
58 | @mixin iro-context-stack-clear($stack-id) { | ||
59 | $noop: iro-context-stack-clear($stack-id); | ||
60 | } | ||
61 | |||
62 | /// | ||
63 | /// Clear a context stack. | ||
64 | /// | ||
65 | /// @param {string} $stack-id - ID of context stack | ||
66 | /// | ||
67 | @function iro-context-stack-clear($stack-id) { | ||
68 | @if not map-has-key($iro-context-stacks, $stack-id) { | ||
69 | @error 'Context stack "#{inspect($stack-id)}" does not exist.'; | ||
70 | } | ||
71 | |||
72 | $context-stack: (); | ||
73 | $iro-context-stacks: map-merge($iro-context-stacks, ($stack-id: $context-stack)) !global; | ||
74 | |||
75 | @return null; | ||
76 | } | ||
77 | |||
78 | /// | ||
79 | /// Delete a context stack. | ||
80 | /// | ||
81 | /// @param {string} $stack-id - ID of context stack | ||
82 | /// | ||
83 | @mixin iro-context-stack-delete($stack-id) { | ||
84 | $noop: iro-context-stack-delete($stack-id); | ||
85 | } | ||
86 | |||
87 | /// | ||
88 | /// Delete a context stack. | ||
89 | /// | ||
90 | /// @param {string} $stack-id - ID of context stack | ||
91 | /// | ||
92 | @function iro-context-stack-delete($stack-id) { | ||
93 | @if not map-has-key($iro-context-stacks, $stack-id) { | ||
94 | @error 'Context stack "#{inspect($stack-id)}" does not exist.'; | ||
95 | } | ||
96 | |||
97 | $iro-context-stacks: map-remove($iro-context-stacks, $stack-id) !global; | ||
98 | |||
99 | @return null; | ||
100 | } | ||
101 | |||
102 | /// | ||
103 | /// Push a new context to a context stack. | ||
104 | /// | ||
105 | /// @param {string} $stack-id - ID of context stack to use | ||
106 | /// @param {string} $id - ID of new context | ||
107 | /// @param {any} $data [()] - Data that belongs to the context | ||
108 | /// | ||
109 | @mixin iro-context-push($stack-id, $id, $data: ()) { | ||
110 | $noop: iro-context-push($stack-id, $id, $data); | ||
111 | } | ||
112 | |||
113 | /// | ||
114 | /// Push a new context to a context stack. | ||
115 | /// | ||
116 | /// @param {string} $stack-id - ID of context stack to use | ||
117 | /// @param {string} $id - ID of new context | ||
118 | /// @param {any} $data [()] - Data that belongs to the context | ||
119 | /// | ||
120 | /// @return {list} A list with two items: 1 = context id, 2 = context data | ||
121 | /// | ||
122 | @function iro-context-push($stack-id, $id, $data: ()) { | ||
123 | @if not map-has-key($iro-context-stacks, $stack-id) { | ||
124 | @error 'Context stack "#{inspect($stack-id)}" does not exist.'; | ||
125 | } | ||
126 | |||
127 | $context: $id $data; | ||
128 | $context-stack: map-get($iro-context-stacks, $stack-id); | ||
129 | $context-stack: append($context-stack, $context); | ||
130 | $iro-context-stacks: map-merge($iro-context-stacks, ($stack-id: $context-stack)) !global; | ||
131 | |||
132 | @return $context; | ||
133 | } | ||
134 | |||
135 | /// | ||
136 | /// Pop the latest context from a context stack. | ||
137 | /// | ||
138 | /// @param {string} $stack-id - ID of context stack to use | ||
139 | /// | ||
140 | /// @throw If context stack doesn't exist | ||
141 | /// | ||
142 | @mixin iro-context-pop($stack-id) { | ||
143 | $noop: iro-context-pop($stack-id); | ||
144 | } | ||
145 | |||
146 | /// | ||
147 | /// Pop the latest context from a context stack. | ||
148 | /// | ||
149 | /// @param {string} $stack-id - ID of context stack to use | ||
150 | /// | ||
151 | /// @return {list} A list with two items: 1 = context id, 2 = context data | ||
152 | /// | ||
153 | @function iro-context-pop($stack-id) { | ||
154 | @if not map-has-key($iro-context-stacks, $stack-id) { | ||
155 | @error 'Context stack "#{inspect($stack-id)}" does not exist.'; | ||
156 | } | ||
157 | |||
158 | $context-stack: map-get($iro-context-stacks, $stack-id); | ||
159 | |||
160 | @if length($context-stack) == 0 { | ||
161 | @error 'Context stack "#{inspect($stack-id)}" is already empty.'; | ||
162 | } | ||
163 | |||
164 | $popped-context: nth($context-stack, -1); | ||
165 | |||
166 | @if length($context-stack) == 1 { | ||
167 | $context-stack: (); | ||
168 | } @else { | ||
169 | $context-stack: iro-list-slice($context-stack, 1, length($context-stack) - 1); | ||
170 | } | ||
171 | |||
172 | $iro-context-stacks: map-merge($iro-context-stacks, ($stack-id: $context-stack)) !global; | ||
173 | |||
174 | @return $popped-context; | ||
175 | } | ||
176 | |||
177 | /// | ||
178 | /// Assert that a context stack must contain one of the given context IDs. | ||
179 | /// | ||
180 | /// @param {string} $stack-id - ID of context stack to use | ||
181 | /// @param {list} $context-ids - Context IDs | ||
182 | /// @param {bool} $check-head-only [false] - If false, all items will be checked. If true, only the head will be checked. | ||
183 | /// | ||
184 | /// @throw If assertion fails | ||
185 | /// | ||
186 | @mixin iro-context-assert-stack-must-contain($stack-id, $context-ids, $check-head-only: false) { | ||
187 | @if not iro-context-stack-contains($stack-id, $context-ids, $check-head-only) { | ||
188 | @error 'Must be called inside of contexts "#{inspect($context-ids)}".'; | ||
189 | } | ||
190 | } | ||
191 | |||
192 | /// | ||
193 | /// Assert that a context stack must not contain any of the given context IDs. | ||
194 | /// | ||
195 | /// @param {string} $stack-id - ID of context stack to use | ||
196 | /// @param {list} $context-ids - Context IDs | ||
197 | /// @param {bool} $check-head-only [false] - If false, all items will be checked. If true, only the head will be checked. | ||
198 | /// | ||
199 | /// @throw If assertion fails | ||
200 | /// | ||
201 | @mixin iro-context-assert-stack-must-not-contain($stack-id, $context-ids, $check-head-only: false) { | ||
202 | @if iro-context-stack-contains($stack-id, $context-ids, $check-head-only) { | ||
203 | @error 'Must not be called inside of contexts "#{inspect($context-ids)}".'; | ||
204 | } | ||
205 | } | ||
206 | |||
207 | /// | ||
208 | /// Check if a context stack contains one of the given context IDs. | ||
209 | /// | ||
210 | /// @param {string} $stack-id - ID of context stack to use | ||
211 | /// @param {list} $context-ids - Context IDs | ||
212 | /// @param {bool} $check-head-only [false] - If false, all items will be checked. If true, only the head will be checked. | ||
213 | /// | ||
214 | /// @return {bool} `true` if the context stack contains one of the context IDs, otherwise `false` | ||
215 | /// | ||
216 | @function iro-context-stack-contains($stack-id, $context-ids, $check-head-only: false) { | ||
217 | @if not map-has-key($iro-context-stacks, $stack-id) { | ||
218 | @error 'Context stack "#{inspect($stack-id)}" does not exist.'; | ||
219 | } | ||
220 | |||
221 | $context-stack: map-get($iro-context-stacks, $stack-id); | ||
222 | |||
223 | @if length($context-stack) == 0 { | ||
224 | @return false; | ||
225 | } | ||
226 | |||
227 | $end-idx: if($check-head-only, length($context-stack), 1); | ||
228 | |||
229 | @for $i from length($context-stack) through $end-idx { | ||
230 | $context: nth($context-stack, $i); | ||
231 | |||
232 | @each $chk-context in $context-ids { | ||
233 | @if nth($context, 1) == $chk-context { | ||
234 | @return true; | ||
235 | } | ||
236 | } | ||
237 | } | ||
238 | |||
239 | @return false; | ||
240 | } | ||
241 | |||
242 | /// | ||
243 | /// Assert that a context stack must contain a number of contexts smaller than $max-count. | ||
244 | /// | ||
245 | /// @param {string} $stack-id - ID of context stack to use | ||
246 | /// @param {number} $max-count - Maximum number ofg contexts in context stack | ||
247 | /// | ||
248 | /// @throw If assertion fails | ||
249 | /// | ||
250 | @mixin iro-context-assert-stack-count($stack-id, $max-count) { | ||
251 | @if iro-context-stack-count($stack-id) > $max-count { | ||
252 | @error 'Maximum context count "#{inspect($max-count)}" exceeded.'; | ||
253 | } | ||
254 | } | ||
255 | |||
256 | /// | ||
257 | /// Get the number of contexts from a context stack. | ||
258 | /// | ||
259 | /// @param {string} $stack-id - ID of context stack to use | ||
260 | /// | ||
261 | /// @return {number} The number of contexts | ||
262 | /// | ||
263 | @function iro-context-stack-count($stack-id) { | ||
264 | @if not map-has-key($iro-context-stacks, $stack-id) { | ||
265 | @error 'Context stack "#{inspect($stack-id)}" does not exist.'; | ||
266 | } | ||
267 | |||
268 | $context-stack: map-get($iro-context-stacks, $stack-id); | ||
269 | |||
270 | @return length($context-stack); | ||
271 | } | ||
272 | |||
273 | /// | ||
274 | /// Get a specific context from the stack. | ||
275 | /// | ||
276 | /// @param {string} $stack-id - ID of context stack to use | ||
277 | /// @param {number | string | list} $type-or-level - If this is a number (!= 0), the nth context from the head will be returned. If it is a string, the first context with a matching ID will be returned. If it is a list, the first context that matches one of the IDs in the list will be returned. | ||
278 | /// | ||
279 | /// @return {list} Null if no match was found, otherwise a list with two items: 1. context ID, 2. context data. | ||
280 | /// | ||
281 | @function iro-context-get($stack-id, $type-or-level: null) { | ||
282 | @if not map-has-key($iro-context-stacks, $stack-id) { | ||
283 | @error 'Context stack "#{inspect($stack-id)}" does not exist.'; | ||
284 | } | ||
285 | |||
286 | $context-stack: map-get($iro-context-stacks, $stack-id); | ||
287 | |||
288 | @if length($context-stack) == 0 { | ||
289 | @return null; | ||
290 | } | ||
291 | |||
292 | @if type-of($type-or-level) == number { | ||
293 | $context: nth($context-stack, -$type-or-level); | ||
294 | |||
295 | @return $context; | ||
296 | } @else { | ||
297 | @for $i from -1 through -(length($context-stack)) { | ||
298 | $context: nth($context-stack, $i); | ||
299 | |||
300 | @if type-of($type-or-level) == list { | ||
301 | @for $j from 1 through length($type-or-level) { | ||
302 | $ctx: nth($type-or-level, $j); | ||
303 | |||
304 | @if nth($context, 1) == $ctx { | ||
305 | @return $context; | ||
306 | } | ||
307 | } | ||
308 | } @else if nth($context, 1) == $type-or-level { | ||
309 | @return $context; | ||
310 | } | ||
311 | } | ||
312 | } | ||
313 | |||
314 | @return null; | ||
315 | } | ||