//// /// 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: (); /// /// Default context name used for the namespace context. /// /// @type string /// $iro-props-namespace-context-id: 'namespace' !default; /// /// Declare a namespace, meaning that all variables declared and accessed. /// /// @param {string} $name - Name of the namespace /// @mixin iro-props-namespace($name) { $key: '--#{$name}'; $ns-key: iro-props-get-ns-key(); @if $ns-key != null { $key: append($ns-key, $key); } @else { $key: ($key); } @include iro-context-push($iro-props-namespace-context-id, 'namespace', ( 'name': $name, 'key': $key )); @content; @include iro-context-pop($iro-props-namespace-context-id); } /// /// Get the current namespace name. /// @function iro-props-namespace() { $noop: iro-context-assert-stack-must-contain($iro-props-namespace-context-id, 'namespace'); $data: nth(iro-context-get($iro-props-namespace-context-id, 'namespace'), 2); $name: map-get($data, 'name'); @return $name; } /// /// 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 [true] - 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-store($map, $tree: $iro-props-default-tree, $merge: true, $global: false) { $noop: iro-props-store($map, $tree, $merge, $global); } /// /// 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 [true] - 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-store($map, $tree: $iro-props-default-tree, $merge: true, $global: 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 not $global { $ns-key: iro-props-get-ns-key(); @if $ns-key != null { $map: ($ns-key: $map) } } @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-clear($tree: $iro-props-default-tree) { $noop: iro-props-clear($tree); } /// /// Delete 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-clear($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-static($key: (), $tree: $iro-props-default-tree, $default: null, $global: false) { @if not map-has-key($iro-props-trees, $tree) { @error 'Unknown tree "#{$tree}".'; } $result: map-get($iro-props-trees, $tree); @if not $global { $ns-key: iro-props-get-ns-key(); @if $ns-key != null { $orig-key: $key; $key: $ns-key; @if type-of($orig-key) == list { @each $subkey in $orig-key { $key: append($key, $subkey); } } @else { $key: append($key, $orig-key); } } } @if type-of($key) == list { $stop: false; @each $k in $key { @if not $stop and map-has-key($result, $k) { $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-static($tree: nth($result, 2), $global: true); } @else { $result: iro-props-get-static(nth($result, 3), nth($result, 2), $global: true); } } } @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-static($tree: nth($result, 2), $global: true); } @else { $result: iro-props-get-static(nth($result, 3), nth($result, 2), $global: true); } } } @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($key, $tree: $iro-props-default-tree, $default: null, $global: false) { @if $tree != null { $noop: iro-props-get-static($key, $tree, $default, $global); } @if not $global { $ns-key: iro-props-get-ns-key(); @if $ns-key != null { $orig-key: $key; $key: $ns-key; @if type-of($orig-key) == list { @each $subkey in $orig-key { $key: append($key, $subkey); } } @else { $key: append($key, $orig-key); } } } $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($tree: $iro-props-default-tree, $root: (), $skip: (), $prefix: '', $global: false) { $map: iro-props-get-static($root, $tree); $map: map-remove($map, $skip...); @if type-of($prefix) == list { $prefix: iro-str-implode($prefix) } @if not $global { $ns-key: iro-props-get-ns-key(); @if $ns-key != null { $prefix: $prefix + iro-str-implode($ns-key); } } @include iro-props-assign-internal($map, $prefix); } /// /// @access private /// @mixin iro-props-assign-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 length($list) > 0 and nth($value, 1) == 'iro-prop-ref' { @if $ref-depth != 0 { $rd: $rd - 1; @if length($value) == 2 { $value: iro-props-get-static($tree: nth($value, 2)); } @else { $value: iro-props-get-static(nth($value, 3), nth($value, 2)); } } @else { $value: null; } } @if type-of($value) != map { #{$prefix + $key}: #{$value}; } @else { @include iro-props-assign-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, $global: false) { @if not $global { $ns-key: iro-props-get-ns-key(); @if $ns-key != null { $orig-key: $key; $key: $ns-key; @if $orig-key != null { @if type-of($orig-key) == list { @each $subkey in $orig-key { $key: append($key, $subkey); } } @else { $key: append($key, $orig-key); } } } } @if $key == null { @return ('iro-prop-ref' $tree); } @else { @return ('iro-prop-ref' $tree $key); } } /// /// Get the current namespace key. /// /// @access private /// @function iro-props-get-ns-key() { $ctx: iro-context-get($iro-props-namespace-context-id, 'namespace'); @if $ctx == null { @return null; } $data: nth($ctx, 2); $key: map-get($data, 'key'); @return $key; } @include iro-context-stack-create($iro-props-namespace-context-id);