Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add API for custom component theming with M3 #27617

Merged
merged 8 commits into from
Aug 14, 2023
122 changes: 105 additions & 17 deletions src/material-experimental/theming/_config-validation.scss
Original file line number Diff line number Diff line change
@@ -1,30 +1,82 @@
@use 'sass:list';
@use 'sass:map';
@use 'sass:meta';
@use 'sass:string';
@use '@angular/material' as mat;
@use './m3-palettes';

/// Creates an error message by finding `$config` in the existing message and appending a suffix to
/// it.
/// @param {List|String} $err The error message.
/// @param {String} $suffix The suffix to add.
/// @return {List|String} The updated error message.
@function _create-dollar-config-error-message($err, $suffix) {
@if meta.type-of($err) == 'list' {
@for $i from 1 through list.length($err) {
$err: list.set-nth($err, $i,
_create-dollar-config-error-message(list.nth($err, $i), $suffix));
}
}
@else if meta.type-of($err) == 'string' {
$start: string.index($err, '$config');
@if $start {
$err: string.insert($err, $suffix, $start + 7);
}
}
@return $err;
}

/// Validates that the given object is an M3 palette.
/// @param {*} $palette The object to test
/// @return {Boolean|null} null if it is a valid M3 palette, else true.
@function _validate-palette($palette) {
@if not meta.type-of($palette) == 'map' {
@return true;
}
$keys: map.keys($palette);
$expected-keys: map.keys(m3-palettes.$red-palette);
@if mat.private-validate-allowed-values($keys, $expected-keys...) or
mat.private-validate-required-values($keys, $expected-keys...) {
@return true;
}
$nv-keys: map.keys(map.get($palette, neutral-variant));
$expected-nv-keys: map.keys(map.get(m3-palettes.$red-palette, neutral-variant));
@if mat.private-validate-allowed-values($nv-keys, $expected-nv-keys...) or
mat.private-validate-required-values($nv-keys, $expected-nv-keys...) {
@return true;
}
@return null;
}

/// Validates a theme config.
/// @param {Map} $config The config to test.
/// @return {List} null if no error, else the error message
@function validate-theme-config($config) {
$err: mat.private-validate-type($config, 'map', 'null');
@if $err {
@return (#{'$config'} #{'should be a color configuration object. Got:'} $config);
@return (#{'$config should be a configuration object. Got:'} $config);
}
$err: mat.private-validate-allowed-values(map.keys($config or ()), color, typography, density);
$allowed: (color, typography, density);
$err: mat.private-validate-allowed-values(map.keys($config or ()), $allowed...);
@if $err {
@return (#{'$config'} #{'has unexpected properties:'} $err);
@return (
#{'$config has unexpected properties. Valid properties are'}
#{'#{$allowed}.'}
#{'Found:'}
$err
);
}
$err: validate-color-config(map.get($config, color));
@if $err {
@return list.set-nth($err, 1, #{'#{list.nth($err, 1)}.color'});
@return _create-dollar-config-error-message($err, '.color');
}
$err: validate-typography-config(map.get($config, typography));
@if $err {
@return list.set-nth($err, 1, #{'#{list.nth($err, 1)}.typography'});
@return _create-dollar-config-error-message($err, '.typography');
}
$err: validate-density-config(map.get($config, density));
@if $err {
@return list.set-nth($err, 1, #{'#{list.nth($err, 1)}.density'});
@return _create-dollar-config-error-message($err, '.density');
}
@return null;
}
Expand All @@ -35,12 +87,35 @@
@function validate-color-config($config) {
$err: mat.private-validate-type($config, 'map', 'null');
@if $err {
@return (#{'$config'} #{'should be a color configuration object. Got:'} $config);
@return (#{'$config should be a color configuration object. Got:'} $config);
}
$err: mat.private-validate-allowed-values(
map.keys($config or ()), theme-type, primary, secondary, tertiary);
$allowed: (theme-type, primary, secondary, tertiary);
$err: mat.private-validate-allowed-values(map.keys($config or ()), $allowed...);
@if $err {
@return (#{'$config'} #{'has unexpected properties:'} $err);
@return (
#{'$config has unexpected properties. Valid properties are'}
#{'#{$allowed}.'}
#{'Found:'}
$err
);
}
@if $config and map.has-key($config, theme-type) and
not list.index((light, dark), map.get($config, theme-type)) {
@return (
#{'Expected $config.theme-type to be one of: light, dark. Got:'}
map.get($config, theme-type)
);
}
@each $palette in (primary, secondary, tertiary) {
@if $config and map.has-key($config, $palette) {
$err: _validate-palette(map.get($config, $palette));
@if $err {
@return (
#{'Expected $config.#{$palette} to be a valid M3 palette. Got:'}
map.get($config, $palette)
);
}
}
}
@return null;
}
Expand All @@ -51,13 +126,17 @@
@function validate-typography-config($config) {
$err: mat.private-validate-type($config, 'map', 'null');
@if $err {
@return (#{'$config'} #{'should be a typography configuration object. Got:'} $config);
@return (#{'$config should be a typography configuration object. Got:'} $config);
}
$err: mat.private-validate-allowed-values(
map.keys($config or ()), brand-family, plain-family, bold-weight, medium-weight,
regular-weight);
$allowed: (brand-family, plain-family, bold-weight, medium-weight, regular-weight);
$err: mat.private-validate-allowed-values(map.keys($config or ()), $allowed...);
@if $err {
@return (#{'$config'} #{'has unexpected properties:'} $err);
@return (
#{'$config has unexpected properties. Valid properties are'}
#{'#{$allowed}.'}
#{'Found:'}
$err
);
}
@return null;
}
Expand All @@ -68,11 +147,20 @@
@function validate-density-config($config) {
$err: mat.private-validate-type($config, 'map', 'null');
@if $err {
@return (#{'$config'} #{'should be a density configuration object. Got:'} $config);
@return (#{'$config should be a density configuration object. Got:'} $config);
}
$err: mat.private-validate-allowed-values(map.keys($config or ()), scale);
@if $err {
@return (#{'$config'} #{'has unexpected properties:'} $err);
@return (#{'$config has unexpected properties. Valid properties are: scale. Found:'} $err);
}
@if $config and map.has-key($config, scale) {
$allowed-scales: (0, -1, -2, -3, -4 -5, minimum, maximum);
@if mat.private-validate-allowed-values(map.get($config, scale), $allowed-scales...) {
@return (
#{'Expected $config.scale to be one of: #{$allowed-scales}. Got:'}
map.get($config, scale)
);
}
}
@return null;
}
10 changes: 9 additions & 1 deletion src/material-experimental/theming/_definition.scss
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ $theme-version: 1;
$internals: (
theme-version: $theme-version,
theme-type: $type,
palettes: (
primary: map.remove($primary, neutral-variant),
secondary: map.remove($secondary, neutral-variant),
tertiary: map.remove($tertiary, neutral-variant),
neutral: m3-palettes.$neutral-palette,
neutral-variant: map.get($primary, neutral-variant),
error: m3-palettes.$red-palette
),
color-tokens: m3-tokens.generate-color-tokens($type, $primary, $secondary, $tertiary,
m3-palettes.$neutral-palette, m3-palettes.$red-palette)
)
Expand All @@ -61,7 +69,7 @@ $theme-version: 1;
@error $err;
}

$plain: map.get($config, plain-family) or Roboto, sans-serif;
$plain: map.get($config, plain-family) or (Roboto, sans-serif);
$brand: map.get($config, brand-family) or $plain;
$bold: map.get($config, bold-weight) or 700;
$medium: map.get($config, medium-weight) or 500;
Expand Down
1 change: 1 addition & 0 deletions src/material-experimental/theming/_m3-palettes.scss
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ $yellow-palette: (
90: #e6e3d1,
95: #f4f1df,
99: #fffbff,
100: #fff,
),
);

Expand Down
5 changes: 5 additions & 0 deletions src/material/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,8 @@
private-typography-config-level-from-mdc, private-if-touch-targets-unsupported,
$private-mdc-base-styles-query, $private-mdc-base-styles-without-animation-query,
$private-mdc-theme-styles-query, $private-mdc-typography-styles-query;

// New theming APIs, currently in development:
@forward './core/theming/inspection' as private-* show private-get-theme-version,
private-get-theme-type, private-get-theme-color, private-get-theme-typography,
private-get-theme-density, private-theme-has;
14 changes: 14 additions & 0 deletions src/material/core/style/_validation.scss
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,17 @@
}
@return if(list.length($invalid) > 0, $invalid, null);
}

/// Validates that a list contains all values from the required list of values.
/// @param {List} $list The list to test
/// @param {List} $required The required values
/// @return {List} null if no error, else the list of missing values.
@function validate-required-values($list, $required...) {
$invalid: ();
@each $element in $required {
@if not list.index($list, $element) {
$invalid: list.append($invalid, $element);
}
}
@return if(list.length($invalid) > 0, $invalid, null);
}
Loading
Loading