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 | } | ||
