Skip to content

Commit ce79a2f

Browse files
committed
refactor(material/core): move M2-specific theming APIs into a separate directory
Moves all of the M2-specific public theming APIs into a separate directory so they're easier to distinguish and so they don't interfere when we move the M3 APIs from experimental. This shouldn't affect users since namespacing already happened as a part of angular#28892.
1 parent 4ba4689 commit ce79a2f

28 files changed

+585
-574
lines changed

.github/CODEOWNERS

+1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
/src/material/core/style/** @andrewseguin
6363
/src/material/core/testing/** @crisbeto
6464
/src/material/core/theming/** @andrewseguin @jelbourn
65+
/src/material/core/m2/** @andrewseguin @jelbourn
6566
/src/material/core/tokens/** @mmalerba
6667
/src/material/core/typography/** @crisbeto
6768
/src/material/core/util/** @andrewseguin

src/material/_index.scss

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
$theme-legacy-inspection-api-compatibility;
1010
@forward './core/theming/theming' as private-* show private-clamp-density;
1111
@forward './core/typography/typography' show typography-hierarchy;
12+
@forward './core/typography/typography-utils' show font-shorthand;
1213
@forward './core/tokens/m2' show m2-tokens-from-theme;
1314

1415
// Private/Internal

src/material/core/_m2.scss src/material/core/m2/_index.scss

+5-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// M2-specific theming APIs which are separated out into this file so they
22
// can be renamed conditionally depending on whether we're in 1P or 3P.
3-
@forward './theming/theming' show
3+
@forward './theming' show
44
define-light-theme,
55
define-dark-theme,
66
define-palette,
@@ -10,7 +10,7 @@
1010
get-typography-config,
1111
get-density-config;
1212

13-
@forward './theming/palette' show
13+
@forward './palette' show
1414
$red-palette,
1515
$pink-palette,
1616
$indigo-palette,
@@ -37,18 +37,16 @@
3737
$light-theme-foreground-palette,
3838
$dark-theme-foreground-palette;
3939

40-
41-
@forward './typography/typography' show
40+
@forward './typography' show
4241
define-typography-level,
4342
define-rem-typography-config,
4443
define-typography-config,
4544
define-legacy-typography-config;
4645

47-
@forward './typography/typography-utils' show
46+
@forward './typography-utils' show
4847
typography-level,
4948
font-size,
5049
line-height,
5150
font-weight,
5251
letter-spacing,
53-
font-family,
54-
font-shorthand;
52+
font-family;
File renamed without changes.

src/material/core/m2/_theming.scss

+295
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
@use 'sass:list';
2+
@use 'sass:map';
3+
@use 'sass:meta';
4+
@use '../theming/theming';
5+
@use './palette';
6+
7+
/// Extracts a color from a palette or throws an error if it doesn't exist.
8+
/// @param {Map} $palette The palette from which to extract a color.
9+
/// @param {String | Number} $hue The hue for which to get the color.
10+
@function _get-color-from-palette($palette, $hue) {
11+
@if map.has-key($palette, $hue) {
12+
@return map.get($palette, $hue);
13+
}
14+
15+
@error 'Hue "' + $hue + '" does not exist in palette. Available hues are: ' + map.keys($palette);
16+
}
17+
18+
/// For a given hue in a palette, return the contrast color from the map of contrast palettes.
19+
/// @param {Map} $palette The palette from which to extract a color.
20+
/// @param {String | Number} $hue The hue for which to get a contrast color.
21+
/// @returns {Color} The contrast color for the given palette and hue.
22+
@function get-contrast-color-from-palette($palette, $hue) {
23+
@return map.get(map.get($palette, contrast), $hue);
24+
}
25+
26+
27+
/// Creates a map of hues to colors for a theme. This is used to define a theme palette in terms
28+
/// of the Material Design hues.
29+
/// @param {Map} $base-palette Map of hue keys to color values for the basis for this palette.
30+
/// @param {String | Number} $default Default hue for this palette.
31+
/// @param {String | Number} $lighter "lighter" hue for this palette.
32+
/// @param {String | Number} $darker "darker" hue for this palette.
33+
/// @param {String | Number} $text "text" hue for this palette.
34+
/// @returns {Map} A complete Angular Material theming palette.
35+
@function define-palette($base-palette, $default: 500, $lighter: 100, $darker: 700,
36+
$text: $default) {
37+
$result: map.merge($base-palette, (
38+
default: _get-color-from-palette($base-palette, $default),
39+
lighter: _get-color-from-palette($base-palette, $lighter),
40+
darker: _get-color-from-palette($base-palette, $darker),
41+
text: _get-color-from-palette($base-palette, $text),
42+
default-contrast: get-contrast-color-from-palette($base-palette, $default),
43+
lighter-contrast: get-contrast-color-from-palette($base-palette, $lighter),
44+
darker-contrast: get-contrast-color-from-palette($base-palette, $darker)
45+
));
46+
47+
// For each hue in the palette, add a "-contrast" color to the map.
48+
@each $hue, $color in $base-palette {
49+
$result: map.merge($result, (
50+
'#{$hue}-contrast': get-contrast-color-from-palette($base-palette, $hue)
51+
));
52+
}
53+
54+
@return $result;
55+
}
56+
57+
58+
/// Gets a color from a theme palette (the output of mat-palette).
59+
/// The hue can be one of the standard values (500, A400, etc.), one of the three preconfigured
60+
/// hues (default, lighter, darker), or any of the aforementioned suffixed with "-contrast".
61+
///
62+
/// @param {Map} $palette The palette from which to extract a color.
63+
/// @param {String | Number} $hue The hue from the palette to use. If this is a value between 0
64+
// and 1, it will be treated as opacity.
65+
/// @param {Number} $opacity The alpha channel value for the color.
66+
/// @returns {Color} The color for the given palette, hue, and opacity.
67+
@function get-color-from-palette($palette, $hue: default, $opacity: null) {
68+
// If hueKey is a number between zero and one, then it actually contains an
69+
// opacity value, so recall this function with the default hue and that given opacity.
70+
@if meta.type-of($hue) == number and $hue >= 0 and $hue <= 1 {
71+
@return get-color-from-palette($palette, default, $hue);
72+
}
73+
74+
// We cast the $hue to a string, because some hues starting with a number, like `700-contrast`,
75+
// might be inferred as numbers by Sass. Casting them to string fixes the map lookup.
76+
$color: if(map.has-key($palette, $hue), map.get($palette, $hue), map.get($palette, $hue + ''));
77+
78+
@if (meta.type-of($color) != color) {
79+
// If the $color resolved to something different from a color (e.g. a CSS variable),
80+
// we can't apply the opacity anyway so we return the value as is, otherwise Sass can
81+
// throw an error or output something invalid.
82+
@return $color;
83+
}
84+
85+
@return rgba($color, if($opacity == null, opacity($color), $opacity));
86+
}
87+
88+
// Validates the specified theme by ensuring that the optional color config defines
89+
// a primary, accent and warn palette. Returns the theme if no failures were found.
90+
@function _mat-validate-theme($theme) {
91+
@if map.get($theme, color) {
92+
$color: map.get($theme, color);
93+
@if not map.get($color, primary) {
94+
@error 'Theme does not define a valid "primary" palette.';
95+
}
96+
@else if not map.get($color, accent) {
97+
@error 'Theme does not define a valid "accent" palette.';
98+
}
99+
@else if not map.get($color, warn) {
100+
@error 'Theme does not define a valid "warn" palette.';
101+
}
102+
}
103+
@return $theme;
104+
}
105+
106+
// Creates a light-themed color configuration from the specified
107+
// primary, accent and warn palettes.
108+
@function _mat-create-light-color-config($primary, $accent, $warn: null) {
109+
@return (
110+
primary: $primary,
111+
accent: $accent,
112+
warn: if($warn != null, $warn, define-palette(palette.$red-palette)),
113+
is-dark: false,
114+
foreground: palette.$light-theme-foreground-palette,
115+
background: palette.$light-theme-background-palette,
116+
);
117+
}
118+
119+
// Creates a dark-themed color configuration from the specified
120+
// primary, accent and warn palettes.
121+
@function _mat-create-dark-color-config($primary, $accent, $warn: null) {
122+
@return (
123+
primary: $primary,
124+
accent: $accent,
125+
warn: if($warn != null, $warn, define-palette(palette.$red-palette)),
126+
is-dark: true,
127+
foreground: palette.$dark-theme-foreground-palette,
128+
background: palette.$dark-theme-background-palette,
129+
);
130+
}
131+
132+
// TODO: Remove legacy API and rename `$primary` below to `$config`. Currently it cannot be renamed
133+
// as it would break existing apps that set the parameter by name.
134+
135+
/// Creates a container object for a light theme to be given to individual component theme mixins.
136+
/// @param {Map} $primary The theme configuration object.
137+
/// @returns {Map} A complete Angular Material theme map.
138+
@function define-light-theme($primary, $accent: null, $warn: define-palette(palette.$red-palette)) {
139+
// This function creates a container object for the individual component theme mixins. Consumers
140+
// can construct such an object by calling this function, or by building the object manually.
141+
// There are two possible ways to invoke this function in order to create such an object:
142+
//
143+
// (1) Passing in a map that holds optional configurations for individual parts of the
144+
// theming system. For `color` configurations, the function only expects the palettes
145+
// for `primary` and `accent` (and optionally `warn`). The function will expand the
146+
// shorthand into an actual configuration that can be consumed in `-color` mixins.
147+
// (2) Legacy pattern: Passing in the palettes as parameters. This is not as flexible
148+
// as passing in a configuration map because only the `color` system can be configured.
149+
//
150+
// If the legacy pattern is used, we generate a container object only with a light-themed
151+
// configuration for the `color` theming part.
152+
@if $accent != null {
153+
@warn theming.$private-legacy-theme-warning;
154+
$theme: _mat-validate-theme((
155+
_is-legacy-theme: true,
156+
color: _mat-create-light-color-config($primary, $accent, $warn),
157+
));
158+
159+
@return _internalize-theme(theming.private-create-backwards-compatibility-theme($theme));
160+
}
161+
// If the map pattern is used (1), we just pass-through the configurations for individual
162+
// parts of the theming system, but update the `color` configuration if set. As explained
163+
// above, the color shorthand will be expanded to an actual light-themed color configuration.
164+
$result: $primary;
165+
@if map.get($primary, color) {
166+
$color-settings: map.get($primary, color);
167+
$primary: map.get($color-settings, primary);
168+
$accent: map.get($color-settings, accent);
169+
$warn: map.get($color-settings, warn);
170+
$result: map.merge($result, (color: _mat-create-light-color-config($primary, $accent, $warn)));
171+
}
172+
@return _internalize-theme(
173+
theming.private-create-backwards-compatibility-theme(_mat-validate-theme($result)));
174+
}
175+
176+
// TODO: Remove legacy API and rename below `$primary` to `$config`. Currently it cannot be renamed
177+
// as it would break existing apps that set the parameter by name.
178+
179+
/// Creates a container object for a dark theme to be given to individual component theme mixins.
180+
/// @param {Map} $primary The theme configuration object.
181+
/// @returns {Map} A complete Angular Material theme map.
182+
@function define-dark-theme($primary, $accent: null, $warn: define-palette(palette.$red-palette)) {
183+
// This function creates a container object for the individual component theme mixins. Consumers
184+
// can construct such an object by calling this function, or by building the object manually.
185+
// There are two possible ways to invoke this function in order to create such an object:
186+
//
187+
// (1) Passing in a map that holds optional configurations for individual parts of the
188+
// theming system. For `color` configurations, the function only expects the palettes
189+
// for `primary` and `accent` (and optionally `warn`). The function will expand the
190+
// shorthand into an actual configuration that can be consumed in `-color` mixins.
191+
// (2) Legacy pattern: Passing in the palettes as parameters. This is not as flexible
192+
// as passing in a configuration map because only the `color` system can be configured.
193+
//
194+
// If the legacy pattern is used, we generate a container object only with a dark-themed
195+
// configuration for the `color` theming part.
196+
@if $accent != null {
197+
@warn theming.$private-legacy-theme-warning;
198+
$theme: _mat-validate-theme((
199+
_is-legacy-theme: true,
200+
color: _mat-create-dark-color-config($primary, $accent, $warn),
201+
));
202+
@return _internalize-theme(theming.private-create-backwards-compatibility-theme($theme));
203+
}
204+
// If the map pattern is used (1), we just pass-through the configurations for individual
205+
// parts of the theming system, but update the `color` configuration if set. As explained
206+
// above, the color shorthand will be expanded to an actual dark-themed color configuration.
207+
$result: $primary;
208+
@if map.get($primary, color) {
209+
$color-settings: map.get($primary, color);
210+
$primary: map.get($color-settings, primary);
211+
$accent: map.get($color-settings, accent);
212+
$warn: map.get($color-settings, warn);
213+
$result: map.merge($result, (color: _mat-create-dark-color-config($primary, $accent, $warn)));
214+
}
215+
@return _internalize-theme(
216+
theming.private-create-backwards-compatibility-theme(_mat-validate-theme($result)));
217+
}
218+
219+
/// Gets the color configuration from the given theme or configuration.
220+
/// @param {Map} $theme The theme map returned from `define-light-theme` or `define-dark-theme`.
221+
/// @param {Map} $default The default value returned if the given `$theme` does not include a
222+
/// `color` configuration.
223+
/// @returns {Map} Color configuration for a theme.
224+
@function get-color-config($theme, $default: null) {
225+
@return theming.private-get-color-config($theme, $default);
226+
}
227+
228+
/// Gets the density configuration from the given theme or configuration.
229+
/// @param {Map} $theme-or-config The theme map returned from `define-light-theme` or
230+
/// `define-dark-theme`.
231+
/// @param {Map} $default The default value returned if the given `$theme` does not include a
232+
/// `density` configuration.
233+
/// @returns {Map} Density configuration for a theme.
234+
@function get-density-config($theme-or-config, $default: 0) {
235+
@return theming.private-get-density-config($theme-or-config, $default);
236+
}
237+
238+
/// Gets the typography configuration from the given theme or configuration.
239+
/// For backwards compatibility, typography is not included by default.
240+
/// @param {Map} $theme-or-config The theme map returned from `define-light-theme` or
241+
/// `define-dark-theme`.
242+
/// @param {Map} $default The default value returned if the given `$theme` does not include a
243+
/// `typography` configuration.
244+
/// @returns {Map} Typography configuration for a theme.
245+
@function get-typography-config($theme-or-config, $default: null) {
246+
@return theming.private-get-typography-config($theme-or-config, $default);
247+
}
248+
249+
/// Copies the given theme object and nests it within itself under a secret key and replaces the
250+
/// original map keys with error values. This allows the inspection API which is aware of the secret
251+
/// key to access the real values, but attempts to directly access the map will result in errors.
252+
/// @param {Map} $theme The theme map.
253+
@function _internalize-theme($theme) {
254+
@if map.has-key($theme, theming.$private-internal-name) {
255+
@return $theme;
256+
}
257+
$internalized-theme: (
258+
theming.$private-internal-name: (
259+
theme-version: 0,
260+
m2-config: $theme
261+
)
262+
);
263+
@if (theming.$theme-legacy-inspection-api-compatibility) {
264+
@return map.merge($theme, $internalized-theme);
265+
}
266+
$error-theme:
267+
_replace-values-with-errors($theme, 'Theme may only be accessed via theme inspection API');
268+
@return map.merge($error-theme, $internalized-theme);
269+
}
270+
271+
/// Replaces concrete CSS values with errors in a theme object.
272+
/// Errors are represented as a map `(ERROR: <message>)`. Because maps are not valid CSS values,
273+
/// the Sass will not compile if the user tries to use any of the error theme values in their CSS.
274+
/// Users will see a message about `(ERROR: <message>)` not being a valid CSS value. Using the
275+
/// message, that winds up getting shown, we can help explain to users why they're getting the
276+
/// error.
277+
/// @param {*} $value The theme value to replace with errors.
278+
/// @param {String} $message The error message to sow users.
279+
/// @return {Map} A version of $value where concrete CSS values have been replaced with errors
280+
@function _replace-values-with-errors($value, $message) {
281+
$value-type: meta.type-of($value);
282+
@if $value-type == 'map' {
283+
@each $k, $v in $value {
284+
$value: map.set($value, $k, _replace-values-with-errors($v, $message));
285+
}
286+
@return $value;
287+
}
288+
@else if $value-type == 'list' and list.length($value) > 0 {
289+
@for $i from 1 through list.length() {
290+
$value: list.set-nth($value, $i, _replace-values-with-errors(list.nth($value, $i), $message));
291+
}
292+
@return $value;
293+
}
294+
@return (ERROR: $message);
295+
}

src/material/core/typography/_property-getters.scss src/material/core/m2/_typography-utils.scss

+14
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,20 @@
22
@use 'sass:meta';
33
@use 'sass:string';
44

5+
/// Emits CSS styles for the given typography level.
6+
/// @param {Map} $config A typography config.
7+
/// @param {Map} $level A typography level.
8+
@mixin typography-level($config, $level) {
9+
// we deliberately do not use the font shorthand here because it overrides
10+
// certain font properties that can't be configured in the current typography
11+
// config, e.g. the font-variant-caps or font-feature-settings property
12+
font-size: font-size($config, $level);
13+
font-weight: font-weight($config, $level);
14+
line-height: line-height($config, $level);
15+
font-family: font-family($config, $level);
16+
letter-spacing: letter-spacing($config, $level);
17+
}
18+
519
// Utility for fetching a nested value from a typography config.
620
@function _mat-get-type-value($config, $level, $name) {
721
@if meta.type-of($config) != 'map' {

0 commit comments

Comments
 (0)