_theme.scss 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. //
  2. // Copyright 2017 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 '@material/feature-targeting/feature-targeting';
  26. @use './css';
  27. @use './custom-properties';
  28. @use './gss';
  29. @use './keys';
  30. @use './replace';
  31. @use './theme-color';
  32. @mixin core-styles($query: feature-targeting.all()) {
  33. $feat-color: feature-targeting.create-target($query, color);
  34. :root {
  35. @include feature-targeting.targets($feat-color) {
  36. @each $style in theme-color.get-theme-keys() {
  37. @include custom-properties.declaration(
  38. keys.create-custom-property($style)
  39. );
  40. }
  41. }
  42. }
  43. @each $style in theme-color.get-theme-keys() {
  44. @if $style != 'background' and $style != 'surface' {
  45. .mdc-theme--#{$style} {
  46. @include feature-targeting.targets($feat-color) {
  47. @include property(color, $style, $important: true);
  48. }
  49. }
  50. } @else {
  51. .mdc-theme--#{$style} {
  52. @include feature-targeting.targets($feat-color) {
  53. @include property(background-color, $style);
  54. }
  55. }
  56. }
  57. }
  58. // CSS rules for using primary and secondary (plus light/dark variants) as background colors.
  59. @each $style in ('primary', 'secondary') {
  60. .mdc-theme--#{$style}-bg {
  61. @include feature-targeting.targets($feat-color) {
  62. @include property(background-color, $style, $important: true);
  63. }
  64. }
  65. }
  66. }
  67. /// Applies a dynamic value to the specified property. This mixin should be used
  68. /// in theme style mixins when setting properties.
  69. ///
  70. /// The value may be any of the following:
  71. /// - a standard CSS value
  72. /// - a custom property Map, e.g. (varname: --mdc-foo, fallback: blue)
  73. /// - a Material theme key String, e.g. 'primary', 'on-primary'
  74. ///
  75. /// @example
  76. /// @include theme.property(color, teal);
  77. /// @include theme.property(color, custom-properties.create(foo, blue));
  78. /// @include theme.property(color, primary);
  79. ///
  80. /// A `$replace` Map parameter may be provided to replace key/value pairs for
  81. /// string values. This can be used to substitute parameters in complex string
  82. /// values such as `calc()` with custom properties.
  83. ///
  84. /// @example
  85. /// @include theme.property(
  86. /// width,
  87. /// calc(foo + bar),
  88. /// $replace: (foo: custom-properties.create(foo), bar: 8px)
  89. /// );
  90. ///
  91. /// Note: Material theme key Strings (e.g. `primary`) are not supported as
  92. /// replacement values.
  93. ///
  94. /// A CSS custom property declaration may be emitted by providing a custom
  95. /// property Map to `$property`. The fallback value (or `$value` if provided)
  96. /// will be used as the declaration value.
  97. ///
  98. /// @example - scss
  99. /// .foo {
  100. /// @include theme.property(custom-properties.create(foo, teal));
  101. /// @include theme.property(custom-properties.create(bar, teal), blue);
  102. /// }
  103. ///
  104. /// @example - css
  105. /// .foo {
  106. /// --mdc-foo: teal;
  107. /// --mdc-bar: blue;
  108. /// }
  109. ///
  110. /// @param {String | Map} $property - The name of the CSS property. May also be
  111. /// a custom property Map to emit a custom propery declaration.
  112. /// @param {String | Number | Color | List | Map} $value - The property's value.
  113. /// This parameter may be omitted if `$property` is a custom property Map.
  114. /// @param {Map} $gss - Optional Map of GSS annotations to set.
  115. /// @param {Map} $replace - An optional Map of replacement key/value pairs if
  116. /// the `$value` is a string.
  117. /// @param {Bool} $important - Set to true to add an `!important` rule. Defaults
  118. /// to false.
  119. @mixin property(
  120. $property,
  121. $value: null,
  122. $gss: (),
  123. $replace: null,
  124. $important: false
  125. ) {
  126. @if custom-properties.is-custom-prop($property) {
  127. // $property is a custom property Map
  128. // --mdc-foo: value;
  129. @if $value {
  130. $property: custom-properties.set-fallback(
  131. $property,
  132. $value,
  133. $shallow: true
  134. );
  135. }
  136. @include custom-properties.declaration(
  137. $property,
  138. $gss: $gss,
  139. $important: $important
  140. );
  141. } @else if custom-properties.is-custom-prop($value) {
  142. // $value is a custom property Map
  143. // property: var(--mdc-foo, fallback);
  144. @include custom-properties.declaration(
  145. $property,
  146. $value,
  147. $gss: $gss,
  148. $important: $important
  149. );
  150. } @else if keys.is-key($value) {
  151. // $value is a key String
  152. // property: key;
  153. $custom-prop: keys.create-custom-property($value);
  154. @if theme-color.is-theme-key($value) {
  155. // Determine if we need to use a compile-time updated value to support
  156. // Angular.
  157. $key: $value;
  158. // (changed: Bool, value: *)
  159. $result: theme-color.deprecated-get-global-theme-key-value-if-changed(
  160. $key
  161. );
  162. @if map.get($result, changed) {
  163. // $mdc-theme-property-values was changed at compile time. Use the
  164. // global value instead. Otherwise if it was not changed, continue
  165. // using the key store normally.
  166. $custom-prop: keys.create-custom-property($key);
  167. $custom-prop: custom-properties.set-fallback(
  168. $custom-prop,
  169. map.get($result, value)
  170. );
  171. }
  172. }
  173. @include custom-properties.declaration(
  174. $property,
  175. $custom-prop,
  176. $gss: $gss,
  177. $important: $important
  178. );
  179. } @else {
  180. // $value is a standard CSS value
  181. // property: value;
  182. $fallback: null;
  183. @if $replace {
  184. // If any replacements are null, treat the entire value as null (do not
  185. // emit anything).
  186. @each $name, $replacement in $replace {
  187. @if $replacement == null {
  188. $value: null;
  189. }
  190. }
  191. }
  192. @if $replace and $value {
  193. @if meta.type-of($replace) != 'map' {
  194. @error 'mdc-theme: Invalid replacement #{$replace}. Must be a Map.';
  195. }
  196. $replace-map-fallback: ();
  197. $replace-map-value: ();
  198. $needs-fallback: false;
  199. @each $name, $replacement in $replace {
  200. @if custom-properties.is-custom-prop($replacement) {
  201. $replace-value: custom-properties.get-declaration-value($replacement);
  202. $replace-fallback: custom-properties.get-declaration-fallback(
  203. $replacement
  204. );
  205. @if $replace-fallback {
  206. $needs-fallback: true;
  207. }
  208. $replace-map-value: map.set(
  209. $replace-map-value,
  210. $name,
  211. $replace-value
  212. );
  213. $replace-map-fallback: map.set(
  214. $replace-map-fallback,
  215. $name,
  216. $replace-fallback
  217. );
  218. } @else {
  219. $replace-map-value: map.set($replace-map-value, $name, $replacement);
  220. $replace-map-fallback: map.set(
  221. $replace-map-fallback,
  222. $name,
  223. $replacement
  224. );
  225. }
  226. }
  227. @if meta.type-of($value) == 'string' {
  228. @if $needs-fallback {
  229. $fallback: replace.replace-string($value, $replace-map-fallback);
  230. }
  231. $value: replace.replace-string($value, $replace-map-value);
  232. } @else if meta.type-of($value) == 'list' {
  233. @if $needs-fallback {
  234. $fallback: replace.replace-list($value, $replace-map-fallback);
  235. }
  236. $value: replace.replace-list($value, $replace-map-value);
  237. } @else {
  238. @error 'mdc-theme: Invalid replacement value #{$value}. $replace may only be used with string or list values.';
  239. }
  240. }
  241. @include css.declaration(
  242. $property,
  243. $value,
  244. $fallback-value: $fallback,
  245. $gss: $gss,
  246. $important: $important
  247. );
  248. }
  249. }
  250. // @deprecated use the `property()` mixin instead
  251. @mixin prop($property, $style, $important: false) {
  252. @include property($property, $style, $important: $important);
  253. }
  254. /// Validates theme configuration keys by comparing it with original theme
  255. /// configuration, also validates theme values to see if it has any unsupported
  256. /// value formats.
  257. ///
  258. /// Use this in internal `theme()` mixins to validate library-provided
  259. /// `$theme` maps and ensure that all tokens are correct and present.
  260. ///
  261. /// @example
  262. /// @mixin theme($theme) {
  263. /// @include theme.validate-theme($light-theme, $theme);
  264. /// ...
  265. /// }
  266. ///
  267. /// @see validate-theme-styles to validate only theme keys.
  268. ///
  269. /// @param {Map} $origin-theme - Original theme configuration in Sass map format
  270. /// that has all supported keys.
  271. /// @param {Map} $custom-theme - Provided theme configuration in Sass map format
  272. /// that should be validated against `$origin-theme`.
  273. @mixin validate-theme($origin-theme, $custom-theme, $test-only: false) {
  274. @include validate-theme-styles(
  275. $origin-theme,
  276. $custom-theme,
  277. $test-only: $test-only
  278. );
  279. @include _validate-theme-values($custom-theme, $test-only: $test-only);
  280. }
  281. /// Validates theme configuration keys by comparing it with original theme
  282. /// configuration.
  283. ///
  284. /// Use this in internal `theme-styles()` mixins to validate library-provided
  285. /// `$theme` maps and ensure that all tokens are correct and present.
  286. ///
  287. /// @example
  288. /// @mixin theme-styles($theme) {
  289. /// @include theme.validate-theme-styles($light-theme, $theme);
  290. /// ...
  291. /// }
  292. ///
  293. /// @see validate-theme to validate both theme keys and theme values.
  294. ///
  295. /// @param {Map} $origin-theme - Original theme configuration in Sass map format
  296. /// that has all supported keys.
  297. /// @param {Map} $custom-theme - Provided theme configuration in Sass map format
  298. /// that should be validated against `$origin-theme`.
  299. @mixin validate-theme-styles($origin-theme, $custom-theme, $test-only: false) {
  300. $origin-keys: map.keys($origin-theme);
  301. $unsupported-keys: ();
  302. @each $key, $value in $custom-theme {
  303. @if (not list.index($origin-keys, $key)) {
  304. $unsupported-keys: list.append(
  305. $unsupported-keys,
  306. $key,
  307. $separator: comma
  308. );
  309. }
  310. }
  311. @if list.length($unsupported-keys) > 0 {
  312. $error-message: 'Unsupported keys found: #{$unsupported-keys}. Expected one of: #{$origin-keys}.';
  313. @if $test-only {
  314. content: $error-message;
  315. } @else {
  316. @error $error-message;
  317. }
  318. }
  319. }
  320. /// Validates theme configuration values to see if it has any unsupported value
  321. /// formats.
  322. /// @see Use `validate-theme()` to validate both theme keys and theme values.
  323. /// @param {Map} $custom-theme - Provided theme configuration in Sass map format
  324. /// that needs to be validated.
  325. @mixin _validate-theme-values($custom-theme, $test-only: false) {
  326. $unsupported-custom-prop-keys: ();
  327. @each $key, $value in $custom-theme {
  328. @if custom-properties.is-custom-prop($value) {
  329. $unsupported-custom-prop-keys: list.append(
  330. $unsupported-custom-prop-keys,
  331. $key,
  332. $separator: comma
  333. );
  334. }
  335. }
  336. @if list.length($unsupported-custom-prop-keys) > 0 {
  337. $error-message: 'Custom properties are not supported for theme map keys: #{$unsupported-custom-prop-keys}';
  338. @if $test-only {
  339. content: $error-message;
  340. } @else {
  341. @error $error-message;
  342. }
  343. }
  344. }