1 | //
|
2 | // Copyright 2020 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 | @use 'sass:string';
|
27 | @use './css';
|
28 | @use './gss';
|
29 |
|
30 | /// Configuration for custom properties.
|
31 | /// @see {mixin} configure
|
32 | $_config: (
|
33 | emit-custom-properties: true,
|
34 | emit-fallback-values: true,
|
35 | emit-fallback-vars: true,
|
36 | );
|
37 |
|
38 | /// Configure options for custom properties. The configuration will be applied
|
39 | /// within the scope of the mixin's content and reset when the mixin scope ends.
|
40 | ///
|
41 | /// @example - scss
|
42 | /// @include configure($emit-fallback-values: false) {
|
43 | /// // No fallback values will be emitted within this mixin scope
|
44 | /// @include another-mixin();
|
45 | /// }
|
46 | ///
|
47 | /// All parameters must be provided as argument lists.
|
48 | ///
|
49 | /// @link https://sass-lang.com/documentation/values/lists#argument-lists
|
50 | ///
|
51 | /// @param {Bool} $emit-custom-properties [true] - Enable or disable all
|
52 | /// custom property emission.
|
53 | /// @param {Bool} $emit-fallback-values [true] - Enable or disable emission of
|
54 | /// fallback CSS static values. This does not include dynamic `var()`
|
55 | /// fallback values.
|
56 | /// @param {Bool} $emit-fallback-vars [true] - Enable or disable emission of
|
57 | /// fallback `var()` values.
|
58 | @mixin configure($config...) {
|
59 | @if not meta.content-exists() {
|
60 | @error 'content is required for configure()';
|
61 | }
|
62 |
|
63 | $config: meta.keywords($config);
|
64 | @each $key, $value in $config {
|
65 | @if $value == null {
|
66 | $config: map.remove($config, $key);
|
67 | }
|
68 | }
|
69 |
|
70 | @if list.length($config) == 0 {
|
71 | @content;
|
72 | } @else {
|
73 | $previous: $_config;
|
74 | // Use !global to avoid shadowing
|
75 | // https://sass-lang.com/documentation/variables#shadowing
|
76 | $_config: map.merge($_config, $config) !global;
|
77 | @content;
|
78 | $_config: $previous !global;
|
79 | }
|
80 | }
|
81 |
|
82 | /// Returns true if the parameter is a custom property Map.
|
83 | ///
|
84 | /// @param {*} $value - the value to test.
|
85 | /// @return true if the value is a custom property Map, or false if not.
|
86 | @function is-custom-prop($value) {
|
87 | @return meta.type-of($value) == 'map' and map.has-key($value, 'varname');
|
88 | }
|
89 |
|
90 | /// Indicates whether or not a value is a custom property var() string.
|
91 | ///
|
92 | /// @example - scss
|
93 | /// $is-prop-string: is-custom-prop-string('var(--foo)'); // true
|
94 | ///
|
95 | /// @param {*} $value - The value to test.
|
96 | /// @return {Bool} True if the value is a custom property var() string, or
|
97 | /// false if not.
|
98 | @function is-custom-prop-string($value) {
|
99 | @return meta.type-of($value) == 'string' and string.slice($value, 1, 4) ==
|
100 | 'var(';
|
101 | }
|
102 |
|
103 | /// Returns true if $prop1's varname and fallback values are deeply equal to
|
104 | /// $prop2's varname and fallback values.
|
105 | ///
|
106 | /// @param {Map} $prop1 - the first value to test.
|
107 | /// @param {Map} $prop2 - the second value to test.
|
108 | /// @return true if both properties are deeply equal
|
109 | @function are-equal($prop1, $prop2) {
|
110 | @return create-var($prop1) == create-var($prop2);
|
111 | }
|
112 |
|
113 | /// Creates a custom property Map.
|
114 | ///
|
115 | /// @param {String} $varname - the custom property name.
|
116 | /// @param {String | Number | Map} - the fallback value (may be another custom
|
117 | /// property Map). May be null.
|
118 | /// @return a custom property Map.
|
119 | @function create($varname, $fallback: null) {
|
120 | @if string.slice($varname, 1, 2) != '--' {
|
121 | $varname: create-varname($varname);
|
122 | }
|
123 |
|
124 | @return (varname: $varname, fallback: $fallback);
|
125 | }
|
126 |
|
127 | /// Global prefix for all custom properties.
|
128 | $_varname-prefix: 'mdc';
|
129 |
|
130 | /// Create a custom property variable name with the global prefix.
|
131 | ///
|
132 | /// @example - scss
|
133 | /// $varname: create-varname(foo); // --mdc-foo
|
134 | ///
|
135 | /// @param {String} $name - The name of the custom property.
|
136 | /// @return {String} The full CSS custom property variable name.
|
137 | @function create-varname($name) {
|
138 | @return --#{$_varname-prefix}-#{$name};
|
139 | }
|
140 |
|
141 | /// Returns the custom property variable name of a custom property Map.
|
142 | ///
|
143 | /// @param {Map} $custom-prop - a custom property Map.
|
144 | /// @return the custom property variable name defined by the Map.
|
145 | @function get-varname($custom-prop) {
|
146 | @return map.get($custom-prop, 'varname');
|
147 | }
|
148 |
|
149 | /// Returns the fallback value of a custom property Map. May be null if the
|
150 | /// custom property does not have a fallback.
|
151 | ///
|
152 | /// @param {Map} $custom-prop - a custom property Map.
|
153 | /// @param {Bool} $shallow - if true, return the first fallback value, which
|
154 | /// may be another custom property Map. Defaults to false, which will return
|
155 | /// the deep final fallback value.
|
156 | /// @return the fallback value of a custom property Map. May be null.
|
157 | @function get-fallback($custom-prop, $shallow: false) {
|
158 | $fallback: map.get($custom-prop, 'fallback');
|
159 | @if is-custom-prop($fallback) and not $shallow {
|
160 | @return get-fallback($fallback);
|
161 | }
|
162 |
|
163 | @return $fallback;
|
164 | }
|
165 |
|
166 | /// Creates a new custom property Map and returns it with the specified new
|
167 | /// fallback value.
|
168 | ///
|
169 | /// @param {Map} $custom-prop - the custom property Map to copy.
|
170 | /// @param {String | Number | Map} $new-fallback - the new fallback value of the
|
171 | /// custom property Map. May be null.
|
172 | /// @param {Bool} $shallow - if true, set the first fallback value. Defaults to
|
173 | /// false, which will set the deep final fallback value.
|
174 | /// @return a new custom property Map with the new fallback value.
|
175 | @function set-fallback($custom-prop, $new-fallback, $shallow: false) {
|
176 | $varname: get-varname($custom-prop);
|
177 | $first-fallback: get-fallback($custom-prop, $shallow: true);
|
178 |
|
179 | @if is-custom-prop($first-fallback) and not $shallow {
|
180 | // The first fallback is a custom property and $shallow is false. Deeply
|
181 | // set the fallback value of the custom property and get the new custom
|
182 | // property Map returned.
|
183 | $new-fallback: set-fallback($first-fallback, $new-fallback);
|
184 | }
|
185 |
|
186 | @return create($varname, $new-fallback);
|
187 | }
|
188 |
|
189 | /// Creates and returns a CSS `var()` function value represented by the provided
|
190 | /// custom property Map.
|
191 | ///
|
192 | /// If custom properties are disabled, this function will return the custom
|
193 | /// property's fallback value instead, which may be `null` and result in an
|
194 | /// empty declaration.
|
195 | ///
|
196 | /// @param {Map} $custom-prop - A custom property Map.
|
197 | /// @return {*} A CSS value, typically a `var()` function, representing the
|
198 | /// custom property. The returned value may change depending on the current
|
199 | /// configuration options for custom properties and whether or not a
|
200 | /// fallback value is present in the custom property Map.
|
201 | @function create-var($custom-prop) {
|
202 | @if not map.get($_config, emit-custom-properties) {
|
203 | // If configured not to emit custom properties and a request is made for a
|
204 | // custom prop's CSS value, return its fallback value. If this is null, it
|
205 | // will result in an empty declaration.
|
206 | @return get-fallback($custom-prop);
|
207 | }
|
208 |
|
209 | $varname: get-varname($custom-prop);
|
210 | $fallback: get-fallback($custom-prop, $shallow: true);
|
211 |
|
212 | $emit-fallback-vars: map.get($_config, emit-fallback-vars);
|
213 | $fallback-is-prop: is-custom-prop($fallback);
|
214 | @if $fallback-is-prop and $emit-fallback-vars {
|
215 | @return var($varname, create-var($fallback));
|
216 | }
|
217 |
|
218 | $emit-fallback-values: map.get($_config, emit-fallback-values);
|
219 | @if $fallback and not $fallback-is-prop and $emit-fallback-values {
|
220 | @return var($varname, $fallback);
|
221 | }
|
222 |
|
223 | @return var($varname);
|
224 | }
|
225 |
|
226 | /// Retrieves the CSS declaration value for a custom property Map. This is
|
227 | /// typically a `var()` function.
|
228 | ///
|
229 | /// If custom properties are disabled, the custom property's fallback value
|
230 | /// will be returned instead. If the fallback value is `null`, an error will
|
231 | /// be thrown.
|
232 | ///
|
233 | /// @param {Map} $custom-prop - The custom property Map to retrieve a
|
234 | /// declaration value for.
|
235 | /// @return {*} The CSS declaration value.
|
236 | @function get-declaration-value($custom-prop) {
|
237 | $emit-custom-properties: map.get($_config, emit-custom-properties);
|
238 | $fallback: get-fallback($custom-prop);
|
239 | @if not $emit-custom-properties and not $fallback {
|
240 | @error 'Custom properties are disabled and #{get-varname($custom-prop)} does not have a fallback value.';
|
241 | }
|
242 |
|
243 | @if not $emit-custom-properties {
|
244 | @return $fallback;
|
245 | }
|
246 |
|
247 | @return create-var($custom-prop);
|
248 | }
|
249 |
|
250 | /// Retrieves the CSS fallback declaration value for a custom property Map.
|
251 | /// This is typically a static CSS value if a custom property has a fallback, or
|
252 | /// null if it does not.
|
253 | ///
|
254 | /// This function will always return `null` if custom properties or fallback
|
255 | /// values are disabled.
|
256 | ///
|
257 | /// @param {Map} $custom-prop - The custom property Map to retrieve a fallback
|
258 | /// declaration value for.
|
259 | /// @return {String | null} The CSS fallback declaration value.
|
260 | @function get-declaration-fallback($custom-prop) {
|
261 | $emit-custom-properties: map.get($_config, emit-custom-properties);
|
262 | $emit-fallback-values: map.get($_config, emit-fallback-values);
|
263 | @if not $emit-custom-properties or not $emit-fallback-values {
|
264 | @return null;
|
265 | }
|
266 |
|
267 | @return get-fallback($custom-prop);
|
268 | }
|
269 |
|
270 | /// Emits a CSS declaration for a custom property. A custom property may either
|
271 | /// be provided as the value to a CSS property string to be emitted as a var()
|
272 | /// function, or as a standalone value to be emitted as a custom property
|
273 | /// declaration.
|
274 | ///
|
275 | /// @example - scss
|
276 | /// @include declaration(color, create(--foo, teal));
|
277 | /// // color: var(--foo, teal);
|
278 | /// @include declaration(create(--foo, teal));
|
279 | /// // --foo: teal;
|
280 | ///
|
281 | /// Standalone custom properties must have a fallback value to emit as a CSS
|
282 | /// declaration.
|
283 | ///
|
284 | /// The declaration emitted for a custom property may change or be ignored
|
285 | /// based on the current configuration for custom properties.
|
286 | ///
|
287 | /// @see {mixin} css.declaration
|
288 | /// @see {mixin} configuration
|
289 | ///
|
290 | /// @param {String} $property - The CSS property of the declaration or the
|
291 | /// custom property Map to emit.
|
292 | /// @param {Map} $custom-prop - A custom property Map for the property's value.
|
293 | /// Optional if $property is the custom property Map to emit.
|
294 | /// @param {Map} $gss - An optional Map of GSS annotations to add.
|
295 | /// @param {Bool} $important - If true, add `!important` to the declaration.
|
296 | @mixin declaration($property, $custom-prop: null, $gss: (), $important: false) {
|
297 | @if $property {
|
298 | $value: null;
|
299 | $fallback-value: null;
|
300 | @if is-custom-prop($property) {
|
301 | @if map.get($_config, emit-custom-properties) {
|
302 | $custom-prop: $property;
|
303 | $property: get-varname($custom-prop);
|
304 | $value: get-fallback($custom-prop, $shallow: true);
|
305 | @if is-custom-prop($value) {
|
306 | $value: create-var($value);
|
307 | }
|
308 | }
|
309 | } @else {
|
310 | @if not is-custom-prop($custom-prop) {
|
311 | @error "Invalid custom property: #{$custom-prop}. Must be a Map with 'varname' and 'fallback'.";
|
312 | }
|
313 |
|
314 | $value: get-declaration-value($custom-prop);
|
315 | $fallback-value: get-declaration-fallback($custom-prop);
|
316 | }
|
317 |
|
318 | @include css.declaration(
|
319 | $property,
|
320 | $value,
|
321 | $fallback-value: $fallback-value,
|
322 | $gss: $gss,
|
323 | $important: $important
|
324 | );
|
325 | }
|
326 | }
|