//// /// Property trees. /// /// Property trees allow you to organize properties in a tree structure (internally nested maps). /// The intended use is to store all your properties at the beginning and for the rest of the /// stylesheet you just get them. /// /// @group Property trees /// /// @access public //// /// /// The maximum depth of resolved iro-prop-ref() references. /// /// @type number /// $iro-props-native-assing-max-depth: 2 !default; /// /// Indicate if property names must start with two dashes (--). /// This is required if property trees are also used for native CSS custom properties. /// /// @type bool /// $iro-props-enforce-double-dashes: true !default; /// /// Default tree name to use if no name is specified. /// /// @type string /// $iro-props-default-tree: 'default' !default; /// /// List of all created property trees. /// /// @type list /// /// @access private /// $iro-props-trees: (); /// /// Save a property tree. If a tree with the sane name already exists, the trees /// will be merged. /// /// @param {map} $map - Map containing properties /// @param {string} $tree [$iro-props-default-tree] - ID the map is saved as /// @param {bool} $merge [false] - If a tree named $tree already exists and this value is set to true, they will be merged. Otherwise an error will be emitted. /// @mixin iro-props-save($map, $tree: $iro-props-default-tree, $merge: false) { $noop: iro-props-save($map, $tree, $merge); } /// /// Save a property tree. /// /// @param {map} $map - Map containing properties /// @param {string} $tree [$iro-props-default-tree] - ID the map is saved as /// @param {bool} $merge [false] - If a tree named $tree already exists and this value is set to true, they will be merged. Otherwise an error will be emitted. /// @function iro-props-save($map, $tree: $iro-props-default-tree, $merge: false) { $prop-map: null; @if $iro-props-enforce-double-dashes { @if not iro-props-validate($map) { @error 'Property tree keys must start with two dashes (--). If you don\'t use property trees for native CSS custom properties, set $iro-props-enforce-double-dashes to false.'; } } @if map-has-key($iro-props-trees, $tree) { @if $merge { $map: iro-map-merge-recursive(map-get($iro-props-trees, $tree), $map); } @else { @error 'Property tree #{inspect($tree)} does already exist.'; } } $iro-props-trees: map-merge($iro-props-trees, ($tree: $map)) !global; @return null; } /// /// Delete a property tree. /// /// @param {string} $tree [$iro-props-default-tree] - ID of the tree to be deleted /// @mixin iro-props-delete($tree: $iro-props-default-tree) { $noop: iro-props-delete($tree); } /// /// Unset a property tree. /// /// @param {string} $tree [$iro-props-default-tree] - ID of the tree to be deleted /// /// @throw If the property tree does not exist /// @function iro-props-delete($tree: $iro-props-default-tree) { @if not map-has-key($iro-props-trees, $tree) { @error 'Property tree "#{inspect($tree)}" does not exist.'; } $iro-props-trees: map-remove($iro-props-trees, $tree) !global; @return null; } /// /// Access a whole property or a subsection (i.e. value) of it. /// /// @param {string | list} $key [null] - Key of the property to read. If this is a list of keys, the map will be traversed in that order. /// @param {string} $tree [$iro-props-default-tree] - ID of the property tree to use /// @param {any} $default [null] - Default value to return of no match was found. If null, this function will throw an error instead. /// /// @return {any} Value assigned to property or $default /// /// @throw If there was no match for $key and $default is null /// @function iro-props-get($key: (), $tree: $iro-props-default-tree, $default: null) { @if not map-has-key($iro-props-trees, $tree) { @error 'Unknown tree "#{$tree}".'; } $result: map-get($iro-props-trees, $tree); @if type-of($key) == list { $stop: false; @each $k in $key { @if map-has-key($result, $k) and not $stop { $result: map-get($result, $k); @if type-of($result) == list and nth($result, 1) == 'iro-prop-ref' { @if length($result) == 2 { $result: iro-props-get($tree: nth($result, 2)); } @else { $result: iro-props-get(nth($result, 3), nth($result, 2)); } } } @else { $stop: true; } } @if $stop { $result: null; } } @else { $result: map-get($result, $key); @if type-of($result) == list and nth($result, 1) == 'iro-prop-ref' { @if length($result) == 2 { $result: iro-props-get($tree: nth($result, 2)); } @else { $result: iro-props-get(nth($result, 3), nth($result, 2)); } } } @if $result == null { @if $default == null { @error '"#{$key}" is null.'; } @else { @return $default; } } @return $result; } /// /// Generate a var() function call to get native CSS custom property. /// /// @param {string | list} $key - Key of the property to read. If this is a list of keys, the map will be traversed in that order. /// @param {string | null} $tree [null] - Optional tree to check if the property actually exists. /// @param {any} $default [null] - Default value to return of no match was found. /// /// @return {string} var() /// @function iro-props-get-native($key, $tree: null, $default: null) { @if $tree != null { $noop: iro-props-get($key, $tree, $default); } $native-var: ''; @if type-of($key) == list { @each $subkey in $key { $native-var: $native-var + $subkey; } } @else { $native-var: $key; } @if $default == null { @return var(#{$native-var}); } @else { @return var(#{$native-var}, #{$default}); } } /// /// Generate assignments for native CSS custom properties with the values from the specified tree. /// /// @param {string} $tree [$iro-props-default-tree] - ID of the property tree to use /// @param {string} $root [()] - Sub-tree to use for assignment /// @mixin iro-props-assign-native($tree: $iro-props-default-tree, $root: (), $skip: (), $prefix: '') { $map: iro-props-get($root, $tree); $map: map-remove($map, $skip...); @if type-of($prefix) == list { $prefix: iro-str-implode($prefix) } @include iro-props-assign-native-internal($map, $prefix); } /// /// @access private /// @mixin iro-props-assign-native-internal($map, $prefix: '', $ref-depth: $iro-props-native-assing-max-depth) { @each $key, $value in $map { $rd: $ref-depth; @if type-of($value) == list and nth($value, 1) == 'iro-prop-ref' { @if $ref-depth != 0 { $rd: $rd - 1; @if length($value) == 2 { $value: iro-props-get($tree: nth($value, 2)); } @else { $value: iro-props-get(nth($value, 3), nth($value, 2)); } } @else { $value: null; } } @if type-of($value) != map { #{$prefix + $key}: #{$value}; } @else { @include iro-props-assign-native-internal($value, $prefix + $key, $rd); } } } /// /// Validate property names. /// /// @access private /// @function iro-props-validate($map) { @each $key, $value in $map { @if str-index($key, '--') != 1 { @return false; } @if type-of($value) == map { @if not iro-props-validate($value) { @return false; } } } @return true; } /// /// Generate a reference to another tree. Dereferencing is lazy, so you may specify a tree that hasn't been created yet. /// /// @param {string} $tree [$iro-props-default-tree] - ID of the property tree to use /// @param {string | list} $key - Key of the property to read. If this is a list of keys, the map will be traversed in that order. /// /// @return {list} A special list that let's Ignis know that this is a lazy value. /// /// @throw If there was no match for $key and $default is null /// @function iro-props-ref($tree: $iro-props-default-tree, $key: null) { @if $key == null { @return ('iro-prop-ref' $tree); } @else { @return ('iro-prop-ref' $tree $key); } }