//// /// Various functions. /// /// This file contains various and mostly unrelated functions. Some of which /// are used in this framework, while others are just there and may be used. /// /// @group Functions /// /// @access public //// @use 'sass:map'; @use 'sass:list'; @use 'sass:math'; @use 'sass:string'; @use 'sass:meta'; @use './vars'; $numbers: ('0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9); $units: ( 'px': 1px, 'cm': 1cm, 'mm': 1mm, '%': 1%, 'ch': 1ch, 'pc': 1pc, 'in': 1in, 'em': 1em, 'rem': 1rem, 'pt': 1pt, 'ex': 1ex, 'vw': 1vw, 'vh': 1vh, 'vmin': 1vmin, 'vmax': 1vmax ); /// /// Replace a substring with a new string. /// /// @param {string} $string - String to search /// @param {string} $search - Substring that gets replaced /// @param {string} $replace - String that replaces $search /// /// @return {string} A string with all instances of $search replaced with $replace /// @function str-replace($string, $search, $replace) { $index: string.index($string, $search); @if $index { @return string.slice($string, 1, $index - 1) + $replace + str-replace(string.slice($string, $index + string.length($search)), $search, $replace); } @return $string; } /// /// Concatenate all items from $list. /// /// @param {list} $list /// @param {number} $glue - Delimiter /// /// @return {string} /// @function str-implode($list, $glue: '') { $result: ''; @each $item in $list { $result: $result + if(list.length($item) > 1, str-implode($item, $glue), $item); @if $item != list.nth($list, list.length($list)) { $result: $result + $glue; } } @return $result; } /// /// Extract a subset from the given list. /// /// @param {list} $list /// @param {number} $start [1] - Indices before this value will be discarded /// @param {number} $end [length($list)] - Indices starting after this value will be discarded /// /// @return {list} A slice of the list /// @function list-slice($list, $start: 1, $end: list.length($list)) { $result: (); @if $end >= $start { @for $i from $start through $end { @if $i != 0 { $result: list.append($result, list.nth($list, $i), list.separator($list)); } } } @return $result; } /// /// Add a new item to the beginning of a list. /// /// @param {list} $list /// @param {number} $value /// /// @return {list} A list with $value at the beginning, followed by the other items /// @function list-prepend($list, $value) { $result: list.append((), $value, list.separator($list)); @if list.length($list) > 0 { @for $i from 1 through list.length($list) { $result: list.append($result, list.nth($list, $i), list.separator($list)); } } @return $result; } /// /// Reverse the order of items in a list. /// /// @param {list} $list /// /// @return {list} Teh reversed list /// @function list-reverse($list) { @if list.length($list) == 0 { @return $list; } $result: (); @for $i from list.length($list) * -1 through -1 { $result: list.append($result, list.nth($list, math.abs($i))); } @return $result; } /// /// Sort numeric items in a list. /// /// The implementation is based on the algorithm on the German Wikipedia article /// about quicksort: https://de.wikipedia.org/wiki/Quicksort#Pseudocode /// /// @param {list} $l /// /// @return {list} Sorted list /// @function quicksort($l, $left: 1, $right: list.length($l)) { @if $left < $right { $pvr: quicksort-partition($l, $left, $right); $pivot: list.nth($pvr, 1); $l: list.nth($pvr, 2); $l: quicksort($l, $left, $pivot); $l: quicksort($l, $pivot + 1, $right); } @return $l; } /// /// @access private /// @function quicksort-partition($l, $left, $right) { $start: true; $i: $left; $j: $right - 1; $pivot: list.nth($l, $right); @while ($i < $j) or $start { @while (list.nth($l, $i) < $pivot) and ($i < $right - 1) { $i: $i + 1; } @while (list.nth($l, $j)>= $pivot) and ($j > $left) { $j: $j - 1; } @if $i < $j { $i-val: list.nth($l, $i); $l: list.set-nth($l, $i, list.nth($l, $j)); $l: list.set-nth($l, $j, $i-val); } $start: false; } @if list.nth($l, $i) > $pivot { $i-val: list.nth($l, $i); $l: list.set-nth($l, $i, list.nth($l, $right)); $l: list.set-nth($l, $right, $i-val); } @return $i $l; } /// /// Try to get the value for the given key from the given map. If it doesn't contain that key, /// return the provided default value instead. /// /// @param {map} $map /// @param {string} $key /// @param {any} $default /// /// @return {any} Either the value assigned to $key or $default /// @function map-get-default($map, $key, $keys...) { $default: list.nth($keys, list.length($keys)); $keys: list-slice($keys, 1, list.length($keys) - 1); @return if(map.has-key($map, $key, $keys...), map.get($map, $key, $keys...), $default); } /// /// Get the contents of a map as a user-friendly string representation. /// /// @param {map} $map /// /// @return {string} /// @function map-print($map) { $output: ''; @each $key, $value in $map { $value-str: ''; @if meta.type-of($value) == map { $value-str: '[ ' + map-print($value) + ' ]'; } @else if meta.type-of($value) == list { $value-str: '[ ' + str-implode($value, ', ') + ' ]'; } @else if meta.type-of($value) == string { $value-str: '\'' + $value + '\''; } @else { $value-str: $value; } @if $output == '' { $output: $key + ': ' + $value-str; } @else { $output: $output + ', ' + $key + ': ' + $value-str; } } @return $output; } /// /// Check if the given selector ends with one of the provided suffixes. /// /// @param {selector} $selector /// @param {selector} $suffixes /// /// @return {bool} `true` if the selector matches at least one suffix, otherwise `false`. /// @function selector-suffix-match($selector, $suffixes) { $match: true; @each $sel in $selector { @if $match { $sel-match: false; @each $suffix in $suffixes { @if not $sel-match { $suf-match: true; @for $i from 1 through list.length($suffix) { @if $suf-match and (list.nth($sel, -$i) != list.nth($suffix, -$i)) { $suf-match: false; } } @if $suf-match { $sel-match: true; } } } @if not $sel-match { $match: false; } } } @return $match; } /// /// Remove the unit from any variable. /// /// @param {any} $n /// /// @return {number} Unit-less variable /// @function strip-unit($n) { @return math.div($n, $n * 0 + 1); } /// /// Convert a pixel value to a rem value. /// /// @param {number} $size - Pixel value to convert /// @param {number} $base [vars.$root-size] - Reference base font size used for conversion /// /// @return {number} Pixel value converted to rem /// @function px-to-rem($size, $base: vars.$root-size) { @return math.div($size, $base) * 1rem; } /// /// Casts a string into a number /// /// @param {string|number} $value /// /// @return {number} /// @function to-number($value) { @if meta.type-of($value) == 'number' { @return $value; } @if meta.type-of($value) != 'string' { @error 'Value for `to-number` should be a number or a string.'; } $result: 0; $digits: 0; $minus: string.slice($value, 1, 1) == '-'; @for $i from if($minus, 2, 1) through string.length($value) { $character: string.slice($value, $i, $i); @if not list.index(map.keys($numbers), $character) and $character != '.' { @return to-length(if($minus, -$result, $result), string.slice($value, $i)) } @if $character == '.' { $digits: 1; } @else if $digits == 0 { $result: $result * 10 + map.get($numbers, $character); } @else { $digits: $digits * 10; $result: $result + math.div(map.get($numbers, $character), $digits); } } @return if($minus, -$result, $result); } /// /// Add $unit to $value /// /// @param {number} $value - Value to add unit to /// @param {string} $unit - String representation of the unit /// /// @return {number} $value expressed in $unit /// @function to-length($value, $unit) { @if not list.index(map.keys($units), $unit) { @error 'Invalid unit `#{$unit}`.'; } @return $value * map.get($units, $unit); } /// /// A mixin with the sole purpose of letting you use temporary variables without polluting the global namespace. /// /// @content /// @mixin execute { @content; }