//// /// Validators are custom functions that will be called before a BEM entity is created. /// They check if the current mixin usage is valid or not and thus they are a flexible way to /// let you implement your own rules. /// /// Validator functions receive the following information: /// - BEM entity type /// - Arguments passed to the mixin /// - The generated selector /// - The generated context, if any /// /// Additionally, the context stack used by the BEM system can be examined. /// /// @group BEM /// /// @access public //// /// /// A list of validator functions. /// /// @type list /// /// @access private /// $iro-bem-validators: (); /// /// Register one or multiple validator functions. /// /// A validator function is a function that accepts 4 arguments: /// 1. BEM entity type (string) /// 2. Arguments passed to the mixin (map) /// 3. The generated selector (selector) /// 4. The generated context (list, may be null) /// /// The function must return a list with two items: /// 1. `true` if the mixin usage is valid, otherwise `false`, /// 2. a string with a rejection reason (empty if the usage is valid). /// /// @param {string} $func-name - First function name. /// @param {string} $func-names - Other function names. /// @mixin iro-bem-add-validator($func-name, $func-names...) { $noop: iro-bem-add-validator($func-name, $func-names...); } /// /// Register one or multiple validator functions. Check the respective mixin documentation for more information. /// /// @see {mixin} iro-bem-add-validator /// @function iro-bem-add-validator($func-name, $func-names...) { @each $fn-name in join($func-name, $func-names) { $fn: get-function($fn-name); $iro-bem-validators: map-merge($iro-bem-validators, ($fn-name: $fn)) !global; } @return null; } /// /// Unregister one or multiple validator functions. /// /// @param {string} $func-name - First function name. /// @param {string} $func-names - Other function names. /// @mixin iro-bem-remove-validator($func-name, $func-names...) { $noop: iro-bem-remove-validator($func-name, $func-names...); } /// /// Unregister one or multiple validator functions. Check the respective mixin documentation for more information. /// /// @see {mixin} iro-bem-remove-validator /// @function iro-bem-remove-validator($func-name, $func-names...) { $iro-bem-validators: map-remove($iro-bem-validators, $func-name, $func-names...) !global; @return null; } /// /// @access private /// @mixin iro-bem-validate($type, $args, $selector, $context) { @each $id, $fn in $iro-bem-validators { $result: call($fn, $type, $args, $selector, $context); @if not nth($result, 1) { @error 'A BEM validator rejected this mixin usage due to the following reason: #{nth($result, 2)}'; } } } // // --------------------------------------------------------------------------------------------------------- // Built-in validators // --------------------------------------------------------------------------------------------------------- // /// /// A validator that makes sure blocks are declared in the right order, determined by the /// namespace used. /// @function iro-bem-validator--enforce-namespace-order($type, $args, $selector, $context) { @if not global-variable-exists(iro-bem-namespace-order) { $iro-bem-namespace-order: map-keys($iro-bem-namespaces) !global; } @if not global-variable-exists(iro-bem-cur-namespace-index) { $iro-bem-cur-namespace-index: 1 !global; } @if $type == 'block' { $block-type: map-get($args, 'type'); @if $block-type != null { $ns-index: index($iro-bem-namespace-order, $block-type); @if $ns-index != null { @if $ns-index < $iro-bem-cur-namespace-index { @return false 'Namespace "#{$block-type}" comes before current namespace #{nth($iro-bem-namespace-order, $iro-bem-cur-namespace-index)}'; } $iro-bem-cur-namespace-index: $ns-index !global; } } } @return true ''; } /// /// A validator that makes all BEM entities immutable. /// @function iro-bem-validator--immutable-entities($type, $args, $selector, $context) { @if not global-variable-exists(iro-bem-generated-selectors) { $iro-bem-generated-selectors: () !global; } $block-name: null; $block-type: null; $block-id: null; @if $type == 'block' { $block-name: map-get($args, 'name'); $block-type: map-get($args, 'type'); } @else { $block-context: iro-context-get($iro-bem-context-id, 'block'); $block-name: map-get(nth($block-context, 2), 'name'); $block-type: map-get(nth($block-context, 2), 'type'); } @if $block-type != null { $block-id: $block-name + '_' + $block-type; } @else { $block-id: $block-name; } @if $type == 'block' { @if map-has-key($iro-bem-generated-selectors, $block-id) { @return false 'Entity "#{$type}" with arguments [ #{iro-map-print($args)} ] was already defined.'; } $iro-bem-generated-selectors: map-merge($iro-bem-generated-selectors, ($block-id: ())) !global; } @else { $selectors: map-get($iro-bem-generated-selectors, $block-id); @if index($selectors, $selector) { @return false 'Entity "#{$type}" with arguments [ #{iro-map-print($args)} ] was already defined.'; } $selectors: append($selectors, $selector); $iro-bem-generated-selectors: map-merge($iro-bem-generated-selectors, ($block-id: $selectors)) !global; } @return true ''; }