Skip to content

Commit 214499e

Browse files
committed
add validation to extend-theme
1 parent 55e5c65 commit 214499e

File tree

3 files changed

+212
-23
lines changed

3 files changed

+212
-23
lines changed

src/material/checkbox/_checkbox-theme.scss

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,15 @@
5151
&.mat-primary {
5252
@include token-utils.create-token-values(
5353
tokens-mdc-checkbox.$prefix,
54-
tokens-mdc-checkbox.get-color-tokens($theme, primary));
54+
tokens-mdc-checkbox.get-color-tokens($theme, primary)
55+
);
5556
}
5657

5758
&.mat-warn {
5859
@include token-utils.create-token-values(
5960
tokens-mdc-checkbox.$prefix,
60-
tokens-mdc-checkbox.get-color-tokens($theme, warn));
61+
tokens-mdc-checkbox.get-color-tokens($theme, warn)
62+
);
6163
}
6264
}
6365
}
@@ -123,7 +125,7 @@
123125
/// @param {Map} $theme The material theme for an application.
124126
/// @param {Map} $overrides The token values to override in the theme.
125127
@function extend-theme($theme, $overrides: ()) {
126-
@return token-utils.extend-theme($theme, checkbox, $overrides);
128+
@return token-utils.extend-theme($theme, ((mdc, checkbox), (mat, checkbox)), $overrides);
127129
}
128130

129131
/// Outputs all (base, color, typography, and density) theme styles for the mat-checkbox.

src/material/core/theming/tests/m3-theme.spec.ts

Lines changed: 151 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import {parse, Rule} from 'postcss';
2-
import {compileString} from 'sass';
31
import {runfiles} from '@bazel/runfiles';
42
import * as path from 'path';
3+
import {parse, Rule} from 'postcss';
4+
import {compileString} from 'sass';
55

6-
import {createLocalAngularPackageImporter} from '../../../../../tools/sass/local-sass-importer';
76
import {pathToFileURL} from 'url';
7+
import {createLocalAngularPackageImporter} from '../../../../../tools/sass/local-sass-importer';
88

99
// Note: For Windows compatibility, we need to resolve the directory paths through runfiles
1010
// which are guaranteed to reside in the source tree.
@@ -128,4 +128,152 @@ describe('M3 theme', () => {
128128
/Calls to Angular Material theme mixins with an M3 theme must be wrapped in a selector/,
129129
);
130130
});
131+
132+
describe('theme extension API', () => {
133+
it('should allow overriding token value', () => {
134+
const css = transpile(`
135+
@use '../../tokens/token-utils';
136+
137+
$theme: token-utils.extend-theme($theme, ((mdc, checkbox), (mat, checkbox)), (
138+
selected-checkmark-color: magenta
139+
));
140+
141+
html {
142+
@include mat.checkbox-theme($theme);
143+
}
144+
`);
145+
146+
expect(css).toContain('--mdc-checkbox-selected-checkmark-color: magenta');
147+
});
148+
149+
it('should not override token value for other color variant', () => {
150+
const css = transpile(`
151+
@use '../../tokens/token-utils';
152+
153+
$theme: token-utils.extend-theme($theme, ((mdc, checkbox), (mat, checkbox)), (
154+
selected-checkmark-color: magenta
155+
));
156+
157+
html {
158+
@include mat.checkbox-theme($theme, $color-variant: secondary);
159+
}
160+
`);
161+
162+
expect(css).not.toContain('--mdc-checkbox-selected-checkmark-color: magenta');
163+
});
164+
165+
it('should allow overriding specific color variant separately', () => {
166+
const css = transpile(`
167+
@use '../../tokens/token-utils';
168+
169+
$theme: token-utils.extend-theme($theme, ((mdc, checkbox), (mat, checkbox)), (
170+
selected-checkmark-color: magenta,
171+
tertiary: (
172+
selected-checkmark-color: cyan,
173+
),
174+
));
175+
176+
html {
177+
@include mat.checkbox-theme($theme);
178+
}
179+
180+
.tertiary {
181+
@include mat.checkbox-color($theme, $color-variant: tertiary);
182+
}
183+
`);
184+
185+
expect(css).toContain('--mdc-checkbox-selected-checkmark-color: magenta');
186+
expect(css).toContain('--mdc-checkbox-selected-checkmark-color: cyan');
187+
});
188+
});
189+
190+
it('should error if used on M2 theme', () => {
191+
expect(() =>
192+
transpile(`
193+
@use '../../tokens/token-utils';
194+
195+
$theme: mat.m2-define-light-theme(mat.$m2-red-palette, mat.$m2-red-palette);
196+
197+
$theme: token-utils.extend-theme($theme, ((mdc, checkbox), (mat, checkbox)), (
198+
selected-checkmark-color: magenta
199+
));
200+
201+
html {
202+
@include mat.checkbox-theme($theme);
203+
}
204+
`),
205+
).toThrowError(/The `extend-theme` functions are only supported for M3 themes/);
206+
});
207+
208+
it('should error on invalid namespace', () => {
209+
expect(() =>
210+
transpile(`
211+
@use '../../tokens/token-utils';
212+
213+
$theme: token-utils.extend-theme($theme, ((mdc, checkbox), (mat, checkbocks)), (
214+
selected-checkmark-color: magenta
215+
));
216+
217+
html {
218+
@include mat.checkbox-theme($theme);
219+
}
220+
`),
221+
).toThrowError(
222+
/Error extending theme: Theme does not have tokes for namespace `\(mat, checkbocks\)`/,
223+
);
224+
});
225+
226+
it('should error on ambiguous shorthand token name', () => {
227+
expect(() =>
228+
transpile(`
229+
@use '../../tokens/token-utils';
230+
231+
$theme: token-utils.extend-theme($theme, ((mdc, checkbox), (mdc, radio)), (
232+
selected-checkmark-color: magenta
233+
));
234+
235+
html {
236+
@include mat.checkbox-theme($theme);
237+
}
238+
`),
239+
).toThrowError(
240+
/Error extending theme: Ambiguous token name `.*` exists in multiple namespaces: `\(mdc, checkbox\)` and `\(mdc, radio\)`/,
241+
);
242+
});
243+
244+
it('should error on unknown variant', () => {
245+
expect(() =>
246+
transpile(`
247+
@use '../../tokens/token-utils';
248+
249+
$theme: token-utils.extend-theme($theme, ((mdc, checkbox), (mat, checkbox)), (
250+
accent: (
251+
selected-checkmark-color: magenta
252+
)
253+
));
254+
255+
html {
256+
@include mat.checkbox-theme($theme);
257+
}
258+
`),
259+
).toThrowError(
260+
/Error extending theme: Unrecognized color variant `accent`. Allowed variants are: primary, secondary, tertiary, error, surface/,
261+
);
262+
});
263+
264+
it('should error on unknown token', () => {
265+
expect(() =>
266+
transpile(`
267+
@use '../../tokens/token-utils';
268+
269+
$theme: token-utils.extend-theme($theme, ((mdc, checkbox), (mat, checkbox)), (
270+
fake-token: red
271+
));
272+
273+
html {
274+
@include mat.checkbox-theme($theme);
275+
}
276+
`),
277+
).toThrowError(/Error extending theme: Unrecognized token `fake-token`. Allowed tokens are: /);
278+
});
131279
});

src/material/core/tokens/_token-utils.scss

Lines changed: 56 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
@use '../m2/palette' as m2-palette;
1111
@use '../m2/theming' as m2-theming;
1212
@use '../m2/typography' as m2-typography;
13+
@use '../theming/inspection';
1314

1415
// Indicates whether we're building internally. Used for backwards compatibility.
1516
$private-is-internal-build: false;
@@ -424,29 +425,67 @@ $_component-prefix: null;
424425
/// Returns a theme config with the given tokens overridden.
425426
/// @param {Map} $theme The material theme for an application.
426427
/// @param {Map} $overrides The token values to override in the theme.
427-
@function extend-theme($theme, $component, $overrides: ()) {
428+
@function extend-theme($theme, $extend-namespaces, $overrides: ()) {
428429
$internals: _mat-theming-internals-do-not-access;
430+
$systems: (color-tokens, typography-tokens, density-tokens, base-tokens);
431+
$variants: (primary, secondary, tertiary, error, surface);
429432

430-
@each $system in (color, typography, density, base) {
431-
$system-name: $system + '-tokens';
432-
$namespaces: map.get($theme, $internals, $system-name);
433-
@each $namespace, $tokens in $namespaces {
434-
@if (list.nth($namespace, 2) == $component) {
435-
$namespace-overrides: if(
436-
list.length($namespace) == 3,
437-
map.get($overrides, list.nth($namespace, 3)),
438-
$overrides
439-
);
440-
441-
@each $name, $value in $tokens {
442-
$token: map.get($namespace-overrides, $name);
443-
@if $token != null {
444-
$theme: map.set($theme, $internals, $system-name, $namespace, $name, $token);
445-
}
433+
@if (inspection.get-theme-version($theme) < 1) {
434+
@error #{'The `extend-theme` functions are only supported for M3 themes'};
435+
}
436+
437+
// Determine which system and namespace each shorthand token belongs to.
438+
$seen-tokens: ();
439+
@each $namespace in $extend-namespaces {
440+
@each $system in $systems {
441+
$tokens: map.get($theme, $internals, $system, $namespace);
442+
@if $tokens == null {
443+
@error #{'Error extending theme: Theme does not have tokes for namespace `('}#{$namespace}#{
444+
')`'};
445+
}
446+
@each $name, $value in $tokens {
447+
@if map.has-key($seen-tokens, $name) {
448+
@error #{'Error extending theme: Ambiguous token name `'}#{$name}#{
449+
'` exists in multiple namespaces: `('}#{list.nth(map.get($seen-tokens, $name), 1)}#{
450+
')` and `('}#{$namespace}#{')`'};
446451
}
452+
$seen-tokens: map.set($seen-tokens, $name, ($namespace, $system));
447453
}
448454
}
449455
}
450456

457+
// Update internal tokens based on given overrides.
458+
@each $token, $value in $overrides {
459+
@if (meta.type-of($value) == 'map') {
460+
$variant: $token;
461+
$variant-overrides: $value;
462+
@if (list.index($variants, $variant) == null) {
463+
@error #{'Error extending theme: Unrecognized color variant `'}#{$variant}#{
464+
'`. Allowed variants are: '}#{$variants};
465+
}
466+
@each $token, $value in $variant-overrides {
467+
$theme: _update-token($theme, $seen-tokens, $token, $value, $variant);
468+
}
469+
} @else {
470+
$theme: _update-token($theme, $seen-tokens, $token, $value);
471+
}
472+
}
473+
451474
@return $theme;
452475
}
476+
477+
// Update the given token in the given theme.
478+
@function _update-token($theme, $seen-tokens, $token, $value, $variant: null) {
479+
$internals: _mat-theming-internals-do-not-access;
480+
$token-info: map.get($seen-tokens, $token);
481+
@if $token-info == null {
482+
@error #{'Error extending theme: Unrecognized token `'}#{$token}#{'`. Allowed tokens are: '}#{
483+
map.keys($seen-tokens)};
484+
}
485+
$namespace: list.nth($token-info, 1);
486+
@if ($variant != null) {
487+
$namespace: list.append($namespace, $variant);
488+
}
489+
$system: list.nth($token-info, 2);
490+
@return map.set($theme, $internals, $system, $namespace, $token, $value);
491+
}

0 commit comments

Comments
 (0)