_custom-properties.scss 11 KB

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