//// /// Context handling. /// /// Contexts allow you to pass data between mixins and let you enforce a certain nesting order. /// It's an essential part for the BEM-related mixins. /// /// If you want to create a new context, the easiest pattern is to create a new mixin and wrap /// the @content between a pair of push and pop. /// From within the @content, you can access the context's data with get. /// To make the compilation fail if a certain nesting order is violated, use /// assert-stack-must-contain and assert-stack-must-not-contain. /// /// @group Contexts /// /// @access public //// @use './functions'; /// /// Map of all context stacks. /// /// @type map /// /// @access private /// $stacks: (); /// /// Create a new context stack. /// /// @param {string} $stack-id - ID of context stack /// /// @throw If context stack already exists /// @mixin create($stack-id) { $noop: create($stack-id); } /// /// Create a new context stack. /// /// @param {string} $stack-id - ID of context stack /// @function create($stack-id) { @if map-has-key($stacks, $stack-id) { @error 'Context stack "#{inspect($stack-id)}" does not exist.'; } $stacks: map-merge($stacks, ($stack-id: ())) !global; @return null; } /// /// Clear a context stack. /// /// @param {string} $stack-id - ID of context stack /// @mixin clear($stack-id) { $noop: clear($stack-id); } /// /// Clear a context stack. /// /// @param {string} $stack-id - ID of context stack /// @function clear($stack-id) { @if not map-has-key($stacks, $stack-id) { @error 'Context stack "#{inspect($stack-id)}" does not exist.'; } $context-stack: (); $stacks: map-merge($stacks, ($stack-id: $context-stack)) !global; @return null; } /// /// Delete a context stack. /// /// @param {string} $stack-id - ID of context stack /// @mixin delete($stack-id) { $noop: delete($stack-id); } /// /// Delete a context stack. /// /// @param {string} $stack-id - ID of context stack /// @function delete($stack-id) { @if not map-has-key($stacks, $stack-id) { @error 'Context stack "#{inspect($stack-id)}" does not exist.'; } $stacks: map-remove($stacks, $stack-id) !global; @return null; } /// /// Push a new context to a context stack. /// /// @param {string} $stack-id - ID of context stack to use /// @param {string} $id - ID of new context /// @param {any} $data [()] - Data that belongs to the context /// @mixin push($stack-id, $id, $data: ()) { $noop: push($stack-id, $id, $data); } /// /// Push a new context to a context stack. /// /// @param {string} $stack-id - ID of context stack to use /// @param {string} $id - ID of new context /// @param {any} $data [()] - Data that belongs to the context /// /// @return {list} A list with two items: 1 = context id, 2 = context data /// @function push($stack-id, $id, $data: ()) { @if not map-has-key($stacks, $stack-id) { @error 'Context stack "#{inspect($stack-id)}" does not exist.'; } $context: $id $data; $context-stack: map-get($stacks, $stack-id); $context-stack: append($context-stack, $context); $stacks: map-merge($stacks, ($stack-id: $context-stack)) !global; @return $context; } /// /// Pop the latest context from a context stack. /// /// @param {string} $stack-id - ID of context stack to use /// /// @throw If context stack doesn't exist /// @mixin pop($stack-id) { $noop: pop($stack-id); } /// /// Pop the latest context from a context stack. /// /// @param {string} $stack-id - ID of context stack to use /// /// @return {list} A list with two items: 1 = context id, 2 = context data /// @function pop($stack-id) { @if not map-has-key($stacks, $stack-id) { @error 'Context stack "#{inspect($stack-id)}" does not exist.'; } $context-stack: map-get($stacks, $stack-id); @if length($context-stack) == 0 { @error 'Context stack "#{inspect($stack-id)}" is already empty.'; } $popped-context: nth($context-stack, -1); @if length($context-stack) == 1 { $context-stack: (); } @else { $context-stack: functions.list-slice($context-stack, 1, length($context-stack) - 1); } $stacks: map-merge($stacks, ($stack-id: $context-stack)) !global; @return $popped-context; } /// /// Assert that a context stack must contain one of the given context IDs. /// /// @param {string} $stack-id - ID of context stack to use /// @param {list} $context-ids - Context IDs /// @param {bool} $check-head-only [false] - If false, all items will be checked. If true, only the head will be checked. /// /// @throw If assertion fails /// @function assert-stack-must-contain($stack-id, $context-ids, $check-head-only: false) { @if not contains($stack-id, $context-ids, $check-head-only) { @error 'Must be called inside of contexts "#{inspect($context-ids)}".'; } @return null; } @mixin assert-stack-must-contain($stack-id, $context-ids, $check-head-only: false) { $noop: assert-stack-must-contain($stack-id, $context-ids, $check-head-only); } /// /// Assert that a context stack must not contain any of the given context IDs. /// /// @param {string} $stack-id - ID of context stack to use /// @param {list} $context-ids - Context IDs /// @param {bool} $check-head-only [false] - If false, all items will be checked. If true, only the head will be checked. /// /// @throw If assertion fails /// @function assert-stack-must-not-contain($stack-id, $context-ids, $check-head-only: false) { @if contains($stack-id, $context-ids, $check-head-only) { @error 'Must not be called inside of contexts "#{inspect($context-ids)}".'; } @return null; } @mixin assert-stack-must-not-contain($stack-id, $context-ids, $check-head-only: false) { $noop: assert-stack-must-not-contain($stack-id, $context-ids, $check-head-only); } /// /// Check if a context stack contains one of the given context IDs. /// /// @param {string} $stack-id - ID of context stack to use /// @param {list} $context-ids - Context IDs /// @param {bool} $check-head-only [false] - If false, all items will be checked. If true, only the head will be checked. /// /// @return {bool} `true` if the context stack contains one of the context IDs, otherwise `false` /// @function contains($stack-id, $context-ids, $check-head-only: false) { @if not map-has-key($stacks, $stack-id) { @error 'Context stack "#{inspect($stack-id)}" does not exist.'; } $context-stack: map-get($stacks, $stack-id); @if length($context-stack) == 0 { @return false; } $end-idx: if($check-head-only, length($context-stack), 1); @for $i from length($context-stack) through $end-idx { $context: nth($context-stack, $i); @each $chk-context in $context-ids { @if nth($context, 1) == $chk-context { @return true; } } } @return false; } /// /// Assert that a context stack must contain a number of contexts smaller than $max-count. /// /// @param {string} $stack-id - ID of context stack to use /// @param {number} $max-count - Maximum number ofg contexts in context stack /// /// @throw If assertion fails /// @function assert-stack-count($stack-id, $max-count) { @if count($stack-id) > $max-count { @error 'Maximum context count "#{inspect($max-count)}" exceeded.'; } @return null; } @mixin assert-stack-count($stack-id, $max-count) { $noop: assert-stack-count($stack-id, $max-count); } /// /// Get the number of contexts from a context stack. /// /// @param {string} $stack-id - ID of context stack to use /// /// @return {number} The number of contexts /// @function count($stack-id) { @if not map-has-key($stacks, $stack-id) { @error 'Context stack "#{inspect($stack-id)}" does not exist.'; } $context-stack: map-get($stacks, $stack-id); @return length($context-stack); } /// /// Get a specific context from the stack. /// /// @param {string} $stack-id - ID of context stack to use /// @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. /// /// @return {list} Null if no match was found, otherwise a list with two items: 1. context ID, 2. context data. /// @function get($stack-id, $type-or-level: null) { @if not map-has-key($stacks, $stack-id) { @error 'Context stack "#{inspect($stack-id)}" does not exist.'; } $context-stack: map-get($stacks, $stack-id); @if length($context-stack) == 0 { @return null; } @if type-of($type-or-level) == number { $context: nth($context-stack, -$type-or-level); @return $context; } @else { @for $i from -1 through -(length($context-stack)) { $context: nth($context-stack, $i); @if type-of($type-or-level) == list { @for $j from 1 through length($type-or-level) { $ctx: nth($type-or-level, $j); @if nth($context, 1) == $ctx { @return $context; } } } @else if nth($context, 1) == $type-or-level { @return $context; } } } @return null; }