_functions.scss 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. //
  2. // Copyright 2019 Google Inc.
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to deal
  6. // in the Software without restriction, including without limitation the rights
  7. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. // copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20. // THE SOFTWARE.
  21. //
  22. @use "sass:list";
  23. @use "sass:map";
  24. @use "sass:meta";
  25. @use "./variables";
  26. // ==Terminology==
  27. // Feature:
  28. // A simple string (e.g. `color`) representing a cross-cutting feature in
  29. // Material.
  30. // Feature query:
  31. // A structure that represents a query for a feature or combination of features. This may be
  32. // either a feature or a map containing `op` and `queries` fields. A single feature represents a
  33. // simple query for just that feature. A map represents a complex query made up of an operator,
  34. // `op`, applied to a list of sub-queries, `queries`.
  35. // (e.g. `color`, `(op: any, queries: (color, typography))`).
  36. // Feature target:
  37. // A map that contains the feature being targeted as well as the current feature query. This is
  38. // the structure that is intended to be passed to the `@mdc-feature-targets` mixin.
  39. // (e.g. `(target: color, query: (op: any, queries: (color, typography))`).
  40. //
  41. // Public
  42. //
  43. // Creates a feature target from the given feature query and targeted feature.
  44. @function create-target($feature-query, $targeted-feature) {
  45. $feature-target: (query: $feature-query, target: $targeted-feature);
  46. $valid: verify-target_($feature-target);
  47. @return $feature-target;
  48. }
  49. // Parses a list of feature targets to produce a map containing the feature query and list of
  50. // available features.
  51. @function parse-targets($feature-targets) {
  52. $valid: verify-target_($feature-targets...);
  53. $available-features: ();
  54. @each $target in $feature-targets {
  55. $available-features: list.append($available-features, map.get($target, target));
  56. }
  57. @return (
  58. available: $available-features,
  59. query: map.get(list.nth($feature-targets, 1), query)
  60. );
  61. }
  62. // Creates a feature query that is satisfied iff all of its sub-queries are satisfied.
  63. @function all($feature-queries...) {
  64. $valid: verify-query_($feature-queries...);
  65. @return (
  66. op: all,
  67. queries: $feature-queries
  68. );
  69. }
  70. // Creates a feature query that is satisfied iff any of its sub-queries are satisfied.
  71. @function any($feature-queries...) {
  72. $valid: verify-query_($feature-queries...);
  73. @return (
  74. op: any,
  75. queries: $feature-queries
  76. );
  77. }
  78. // Creates a feature query that is satisfied iff its sub-query is not satisfied.
  79. @function without($feature-query) {
  80. $valid: verify-query_($feature-query);
  81. @return (
  82. op: without,
  83. // NOTE: we need to use `append`, just putting parens around a single value doesn't make it a list in Sass.
  84. queries: list.append((), $feature-query)
  85. );
  86. }
  87. //
  88. // Package-internal
  89. //
  90. // Verifies that the given feature targets are valid, throws an error otherwise.
  91. @function verify-target_($feature-targets...) {
  92. @each $target in $feature-targets {
  93. @if meta.type-of($target) != map {
  94. @error "Invalid feature target: '#{$target}'. Must be a map.";
  95. }
  96. $targeted-feature: map.get($target, target);
  97. $feature-query: map.get($target, query);
  98. $valid: verify-feature_($targeted-feature) and verify-query_($feature-query);
  99. }
  100. @return true;
  101. }
  102. // Checks whether the given feature query is satisfied by the given list of available features.
  103. @function is-query-satisfied_($feature-query, $available-features) {
  104. $valid: verify-query_($feature-query);
  105. $valid: verify-feature_($available-features...);
  106. @if meta.type-of($feature-query) == map {
  107. $op: map.get($feature-query, op);
  108. $sub-queries: map.get($feature-query, queries);
  109. @if $op == without {
  110. @return not is-query-satisfied_(list.nth($sub-queries, 1), $available-features);
  111. }
  112. @if $op == any {
  113. @each $sub-query in $sub-queries {
  114. @if is-query-satisfied_($sub-query, $available-features) {
  115. @return true;
  116. }
  117. }
  118. @return false;
  119. }
  120. @if $op == all {
  121. @each $sub-query in $sub-queries {
  122. @if not is-query-satisfied_($sub-query, $available-features) {
  123. @return false;
  124. }
  125. }
  126. @return true;
  127. }
  128. }
  129. @return list-contains_($available-features, $feature-query);
  130. }
  131. //
  132. // Private
  133. //
  134. // Verifies that the given feature(s) are valid, throws an error otherwise.
  135. @function verify-feature_($features...) {
  136. @each $feature in $features {
  137. @if not list-contains_(variables.$all-features, $feature) {
  138. @error "Invalid feature: '#{$feature}'. Valid features are: #{variables.$all-features}.";
  139. }
  140. }
  141. @return true;
  142. }
  143. // Verifies that the given feature queries are valid, throws an error otherwise.
  144. @function verify-query_($feature-queries...) {
  145. @each $query in $feature-queries {
  146. @if meta.type-of($query) == map {
  147. $op: map.get($query, op);
  148. $sub-queries: map.get($query, queries);
  149. $valid: verify-query_($sub-queries...);
  150. @if not list-contains_(variables.$all-query-operators, $op) {
  151. @error "Invalid feature query operator: '#{$op}'. " +
  152. "Valid operators are: #{variables.$all-query-operators}";
  153. }
  154. } @else {
  155. $valid: verify-feature_($query);
  156. }
  157. }
  158. @return true;
  159. }
  160. // Checks whether the given list contains the given item.
  161. @function list-contains_($list, $item) {
  162. @return list.index($list, $item) != null;
  163. }