UNPKG

7.04 kBSCSSView Raw
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
23@use 'sass:list';
24@use 'sass:map';
25@use 'sass:meta';
26
27// ==Terminology==
28// Feature:
29// A simple string (e.g. `color`) representing a cross-cutting feature in
30// Material.
31// Feature query:
32// A structure that represents a query for a feature or combination of features. This may be
33// either a feature or a map containing `op` and `queries` fields. A single feature represents a
34// simple query for just that feature. A map represents a complex query made up of an operator,
35// `op`, applied to a list of sub-queries, `queries`.
36// (e.g. `color`, `(op: any, queries: (color, typography))`).
37// Feature target:
38// A map that contains the feature being targeted as well as the current feature query. This is
39// the structure that is intended to be passed to the `@mdc-feature-targets` mixin.
40// (e.g. `(target: color, query: (op: any, queries: (color, typography))`).
41
42//
43// Public
44//
45
46$all-features: (structure, color, typography, animation);
47$all-query-operators: (any, all, without);
48
49// Creates a feature target from the given feature query and targeted feature.
50@function create-target($feature-query, $targeted-feature) {
51 $feature-target: (
52 query: $feature-query,
53 target: $targeted-feature,
54 );
55 $valid: verify-target_($feature-target);
56
57 @return $feature-target;
58}
59
60// Parses a list of feature targets to produce a map containing the feature query and list of
61// available features.
62@function parse-targets($feature-targets) {
63 $valid: verify-target_($feature-targets...);
64 $available-features: ();
65
66 @each $target in $feature-targets {
67 $available-features: list.append(
68 $available-features,
69 map.get($target, target)
70 );
71 }
72
73 @return (
74 available: $available-features,
75 query: map.get(list.nth($feature-targets, 1), query)
76 );
77}
78
79// Creates a feature query that is satisfied iff all of its sub-queries are satisfied.
80@function all($feature-queries...) {
81 $valid: verify-query_($feature-queries...);
82
83 @return (op: all, queries: $feature-queries);
84}
85
86// Creates a feature query that is satisfied iff any of its sub-queries are satisfied.
87@function any($feature-queries...) {
88 $valid: verify-query_($feature-queries...);
89
90 @return (op: any, queries: $feature-queries);
91}
92
93// Creates a feature query that is satisfied iff its sub-query is not satisfied.
94@function without($feature-query) {
95 $valid: verify-query_($feature-query);
96
97 // NOTE: we need to use `append`, just putting parens around a single value doesn't make it a list in Sass.
98 @return (op: without, queries: list.append((), $feature-query));
99}
100
101//
102// Package-internal
103//
104
105// Verifies that the given feature targets are valid, throws an error otherwise.
106@function verify-target_($feature-targets...) {
107 @each $target in $feature-targets {
108 @if meta.type-of($target) != map {
109 @error "Invalid feature target: '#{$target}'. Must be a map.";
110 }
111
112 $targeted-feature: map.get($target, target);
113 $feature-query: map.get($target, query);
114 $valid: verify-feature_($targeted-feature) and
115 verify-query_($feature-query);
116 }
117
118 @return true;
119}
120
121// Checks whether the given feature query is satisfied by the given list of available features.
122@function is-query-satisfied_($feature-query, $available-features) {
123 $valid: verify-query_($feature-query);
124 $valid: verify-feature_($available-features...);
125
126 @if meta.type-of($feature-query) == map {
127 $op: map.get($feature-query, op);
128 $sub-queries: map.get($feature-query, queries);
129
130 @if $op == without {
131 @return not
132 is-query-satisfied_(list.nth($sub-queries, 1), $available-features);
133 }
134
135 @if $op == any {
136 @each $sub-query in $sub-queries {
137 @if is-query-satisfied_($sub-query, $available-features) {
138 @return true;
139 }
140 }
141
142 @return false;
143 }
144
145 @if $op == all {
146 @each $sub-query in $sub-queries {
147 @if not is-query-satisfied_($sub-query, $available-features) {
148 @return false;
149 }
150 }
151
152 @return true;
153 }
154 }
155
156 @return list-contains_($available-features, $feature-query);
157}
158
159//
160// Private
161//
162
163// Verifies that the given feature(s) are valid, throws an error otherwise.
164@function verify-feature_($features...) {
165 @each $feature in $features {
166 @if not list-contains_($all-features, $feature) {
167 @error "Invalid feature: '#{$feature}'. Valid features are: #{$all-features}.";
168 }
169 }
170
171 @return true;
172}
173
174// Verifies that the given feature queries are valid, throws an error otherwise.
175@function verify-query_($feature-queries...) {
176 @each $query in $feature-queries {
177 @if meta.type-of($query) == map {
178 $op: map.get($query, op);
179 $sub-queries: map.get($query, queries);
180 $valid: verify-query_($sub-queries...);
181
182 @if not list-contains_($all-query-operators, $op) {
183 @error "Invalid feature query operator: '#{$op}'. " +
184 "Valid operators are: #{$all-query-operators}";
185 }
186 } @else {
187 $valid: verify-feature_($query);
188 }
189 }
190
191 @return true;
192}
193
194// Checks whether the given list contains the given item.
195@function list-contains_($list, $item) {
196 @return list.index($list, $item) != null;
197}
198
199// Tracks whether the current context is inside a `mdc-feature-targets` mixin.
200$targets-context_: false;
201
202// Mixin that annotates the contained styles as applying to specific cross-cutting features
203// indicated by the given list of feature targets.
204@mixin targets($feature-targets...) {
205 // Prevent accidental nesting of this mixin, which could lead to unexpected results.
206 @if $targets-context_ {
207 @error "mdc-feature-targets must not be used inside of another mdc-feature-targets block";
208 }
209
210 $targets-context_: true !global;
211 $parsed-targets: parse-targets($feature-targets);
212
213 @if is-query-satisfied_(
214 map.get($parsed-targets, query),
215 map.get($parsed-targets, available)
216 )
217 {
218 @content;
219 }
220
221 $targets-context_: false !global;
222}