From d07f664450ddaaebb44127a4bd057763d13d3f82 Mon Sep 17 00:00:00 2001 From: Feuerfuchs Date: Sun, 1 Nov 2020 20:55:14 +0100 Subject: Init --- src/_contexts.scss | 315 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 315 insertions(+) create mode 100644 src/_contexts.scss (limited to 'src/_contexts.scss') 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 @@ +//// +/// 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 iro-context-push and iro-context-pop. +/// From within the @content, you can access the context's data with iro-context-get. +/// To make the compilation fail if a certain nesting order is violated, use +/// iro-context-assert-stack-must-contain and iro-context-assert-stack-must-not-contain. +/// +/// @group Contexts +/// +/// @access public +//// + +/// +/// Map of all context stacks. +/// +/// @type map +/// +/// @access private +/// +$iro-context-stacks: (); + +/// +/// Create a new context stack. +/// +/// @param {string} $stack-id - ID of context stack +/// +/// @throw If context stack already exists +/// +@mixin iro-context-stack-create($stack-id) { + $noop: iro-context-stack-create($stack-id); +} + +/// +/// Create a new context stack. +/// +/// @param {string} $stack-id - ID of context stack +/// +@function iro-context-stack-create($stack-id) { + @if map-has-key($iro-context-stacks, $stack-id) { + @error 'Context stack "#{inspect($stack-id)}" does not exist.'; + } + + $iro-context-stacks: map-merge($iro-context-stacks, ($stack-id: ())) !global; + + @return null; +} + +/// +/// Clear a context stack. +/// +/// @param {string} $stack-id - ID of context stack +/// +@mixin iro-context-stack-clear($stack-id) { + $noop: iro-context-stack-clear($stack-id); +} + +/// +/// Clear a context stack. +/// +/// @param {string} $stack-id - ID of context stack +/// +@function iro-context-stack-clear($stack-id) { + @if not map-has-key($iro-context-stacks, $stack-id) { + @error 'Context stack "#{inspect($stack-id)}" does not exist.'; + } + + $context-stack: (); + $iro-context-stacks: map-merge($iro-context-stacks, ($stack-id: $context-stack)) !global; + + @return null; +} + +/// +/// Delete a context stack. +/// +/// @param {string} $stack-id - ID of context stack +/// +@mixin iro-context-stack-delete($stack-id) { + $noop: iro-context-stack-delete($stack-id); +} + +/// +/// Delete a context stack. +/// +/// @param {string} $stack-id - ID of context stack +/// +@function iro-context-stack-delete($stack-id) { + @if not map-has-key($iro-context-stacks, $stack-id) { + @error 'Context stack "#{inspect($stack-id)}" does not exist.'; + } + + $iro-context-stacks: map-remove($iro-context-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 iro-context-push($stack-id, $id, $data: ()) { + $noop: iro-context-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 iro-context-push($stack-id, $id, $data: ()) { + @if not map-has-key($iro-context-stacks, $stack-id) { + @error 'Context stack "#{inspect($stack-id)}" does not exist.'; + } + + $context: $id $data; + $context-stack: map-get($iro-context-stacks, $stack-id); + $context-stack: append($context-stack, $context); + $iro-context-stacks: map-merge($iro-context-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 iro-context-pop($stack-id) { + $noop: iro-context-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 iro-context-pop($stack-id) { + @if not map-has-key($iro-context-stacks, $stack-id) { + @error 'Context stack "#{inspect($stack-id)}" does not exist.'; + } + + $context-stack: map-get($iro-context-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: iro-list-slice($context-stack, 1, length($context-stack) - 1); + } + + $iro-context-stacks: map-merge($iro-context-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 +/// +@mixin iro-context-assert-stack-must-contain($stack-id, $context-ids, $check-head-only: false) { + @if not iro-context-stack-contains($stack-id, $context-ids, $check-head-only) { + @error 'Must be called inside of contexts "#{inspect($context-ids)}".'; + } +} + +/// +/// 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 +/// +@mixin iro-context-assert-stack-must-not-contain($stack-id, $context-ids, $check-head-only: false) { + @if iro-context-stack-contains($stack-id, $context-ids, $check-head-only) { + @error 'Must not be called inside of contexts "#{inspect($context-ids)}".'; + } +} + +/// +/// 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 iro-context-stack-contains($stack-id, $context-ids, $check-head-only: false) { + @if not map-has-key($iro-context-stacks, $stack-id) { + @error 'Context stack "#{inspect($stack-id)}" does not exist.'; + } + + $context-stack: map-get($iro-context-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 +/// +@mixin iro-context-assert-stack-count($stack-id, $max-count) { + @if iro-context-stack-count($stack-id) > $max-count { + @error 'Maximum context count "#{inspect($max-count)}" exceeded.'; + } +} + +/// +/// 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 iro-context-stack-count($stack-id) { + @if not map-has-key($iro-context-stacks, $stack-id) { + @error 'Context stack "#{inspect($stack-id)}" does not exist.'; + } + + $context-stack: map-get($iro-context-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 iro-context-get($stack-id, $type-or-level: null) { + @if not map-has-key($iro-context-stacks, $stack-id) { + @error 'Context stack "#{inspect($stack-id)}" does not exist.'; + } + + $context-stack: map-get($iro-context-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; +} -- cgit v1.3.1