| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222 |
- //
- // Copyright 2019 Google Inc.
- //
- // Permission is hereby granted, free of charge, to any person obtaining a copy
- // of this software and associated documentation files (the "Software"), to deal
- // in the Software without restriction, including without limitation the rights
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- // copies of the Software, and to permit persons to whom the Software is
- // furnished to do so, subject to the following conditions:
- //
- // The above copyright notice and this permission notice shall be included in
- // all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- // THE SOFTWARE.
- //
- @use 'sass:list';
- @use 'sass:map';
- @use 'sass:meta';
- // ==Terminology==
- // Feature:
- // A simple string (e.g. `color`) representing a cross-cutting feature in
- // Material.
- // Feature query:
- // A structure that represents a query for a feature or combination of features. This may be
- // either a feature or a map containing `op` and `queries` fields. A single feature represents a
- // simple query for just that feature. A map represents a complex query made up of an operator,
- // `op`, applied to a list of sub-queries, `queries`.
- // (e.g. `color`, `(op: any, queries: (color, typography))`).
- // Feature target:
- // A map that contains the feature being targeted as well as the current feature query. This is
- // the structure that is intended to be passed to the `@mdc-feature-targets` mixin.
- // (e.g. `(target: color, query: (op: any, queries: (color, typography))`).
- //
- // Public
- //
- $all-features: (structure, color, typography, animation);
- $all-query-operators: (any, all, without);
- // Creates a feature target from the given feature query and targeted feature.
- @function create-target($feature-query, $targeted-feature) {
- $feature-target: (
- query: $feature-query,
- target: $targeted-feature,
- );
- $valid: verify-target_($feature-target);
- @return $feature-target;
- }
- // Parses a list of feature targets to produce a map containing the feature query and list of
- // available features.
- @function parse-targets($feature-targets) {
- $valid: verify-target_($feature-targets...);
- $available-features: ();
- @each $target in $feature-targets {
- $available-features: list.append(
- $available-features,
- map.get($target, target)
- );
- }
- @return (
- available: $available-features,
- query: map.get(list.nth($feature-targets, 1), query)
- );
- }
- // Creates a feature query that is satisfied iff all of its sub-queries are satisfied.
- @function all($feature-queries...) {
- $valid: verify-query_($feature-queries...);
- @return (op: all, queries: $feature-queries);
- }
- // Creates a feature query that is satisfied iff any of its sub-queries are satisfied.
- @function any($feature-queries...) {
- $valid: verify-query_($feature-queries...);
- @return (op: any, queries: $feature-queries);
- }
- // Creates a feature query that is satisfied iff its sub-query is not satisfied.
- @function without($feature-query) {
- $valid: verify-query_($feature-query);
- // NOTE: we need to use `append`, just putting parens around a single value doesn't make it a list in Sass.
- @return (op: without, queries: list.append((), $feature-query));
- }
- //
- // Package-internal
- //
- // Verifies that the given feature targets are valid, throws an error otherwise.
- @function verify-target_($feature-targets...) {
- @each $target in $feature-targets {
- @if meta.type-of($target) != map {
- @error "Invalid feature target: '#{$target}'. Must be a map.";
- }
- $targeted-feature: map.get($target, target);
- $feature-query: map.get($target, query);
- $valid: verify-feature_($targeted-feature) and
- verify-query_($feature-query);
- }
- @return true;
- }
- // Checks whether the given feature query is satisfied by the given list of available features.
- @function is-query-satisfied_($feature-query, $available-features) {
- $valid: verify-query_($feature-query);
- $valid: verify-feature_($available-features...);
- @if meta.type-of($feature-query) == map {
- $op: map.get($feature-query, op);
- $sub-queries: map.get($feature-query, queries);
- @if $op == without {
- @return not
- is-query-satisfied_(list.nth($sub-queries, 1), $available-features);
- }
- @if $op == any {
- @each $sub-query in $sub-queries {
- @if is-query-satisfied_($sub-query, $available-features) {
- @return true;
- }
- }
- @return false;
- }
- @if $op == all {
- @each $sub-query in $sub-queries {
- @if not is-query-satisfied_($sub-query, $available-features) {
- @return false;
- }
- }
- @return true;
- }
- }
- @return list-contains_($available-features, $feature-query);
- }
- //
- // Private
- //
- // Verifies that the given feature(s) are valid, throws an error otherwise.
- @function verify-feature_($features...) {
- @each $feature in $features {
- @if not list-contains_($all-features, $feature) {
- @error "Invalid feature: '#{$feature}'. Valid features are: #{$all-features}.";
- }
- }
- @return true;
- }
- // Verifies that the given feature queries are valid, throws an error otherwise.
- @function verify-query_($feature-queries...) {
- @each $query in $feature-queries {
- @if meta.type-of($query) == map {
- $op: map.get($query, op);
- $sub-queries: map.get($query, queries);
- $valid: verify-query_($sub-queries...);
- @if not list-contains_($all-query-operators, $op) {
- @error "Invalid feature query operator: '#{$op}'. " +
- "Valid operators are: #{$all-query-operators}";
- }
- } @else {
- $valid: verify-feature_($query);
- }
- }
- @return true;
- }
- // Checks whether the given list contains the given item.
- @function list-contains_($list, $item) {
- @return list.index($list, $item) != null;
- }
- // Tracks whether the current context is inside a `mdc-feature-targets` mixin.
- $targets-context_: false;
- // Mixin that annotates the contained styles as applying to specific cross-cutting features
- // indicated by the given list of feature targets.
- @mixin targets($feature-targets...) {
- // Prevent accidental nesting of this mixin, which could lead to unexpected results.
- @if $targets-context_ {
- @error "mdc-feature-targets must not be used inside of another mdc-feature-targets block";
- }
- $targets-context_: true !global;
- $parsed-targets: parse-targets($feature-targets);
- @if is-query-satisfied_(
- map.get($parsed-targets, query),
- map.get($parsed-targets, available)
- )
- {
- @content;
- }
- $targets-context_: false !global;
- }
|