Skip to content

Commit 812f1f2

Browse files
committed
fix(theming): fix broken sass expressions nested in theme classes
* Fixes certain theme selectors being broken due to uses of the `&` operator at the end of the selector. * Adds a custom Stylelint rule to catch future improper uses of the ampersand inside themes. Relates to #3928. Fixes #4077.
1 parent 11b97aa commit 812f1f2

File tree

7 files changed

+118
-78
lines changed

7 files changed

+118
-78
lines changed

src/lib/input/_input-theme.scss

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,19 @@
2222

2323
.mat-input-placeholder {
2424
color: $input-placeholder-color;
25+
}
2526

26-
// :focus is applied to the input, but we apply mat-focused to the other elements
27-
// that need to listen to it.
28-
.mat-focused & {
29-
color: $input-floating-placeholder-color;
27+
// :focus is applied to the input, but we apply mat-focused to the other elements
28+
// that need to listen to it.
29+
.mat-focused .mat-input-placeholder {
30+
color: $input-floating-placeholder-color;
3031

31-
&.mat-accent {
32-
color: $input-underline-color-accent;
33-
}
34-
&.mat-warn {
35-
color: $input-underline-color-warn;
36-
}
32+
&.mat-accent {
33+
color: $input-underline-color-accent;
34+
}
35+
36+
&.mat-warn {
37+
color: $input-underline-color-warn;
3738
}
3839
}
3940

src/lib/radio/_radio-theme.scss

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,28 +11,26 @@
1111

1212
.mat-radio-outer-circle {
1313
border-color: mat-color($foreground, secondary-text);
14+
}
1415

15-
.mat-radio-checked & {
16-
border-color: mat-color($accent);
17-
}
16+
.mat-radio-checked .mat-radio-outer-circle {
17+
border-color: mat-color($accent);
18+
}
1819

19-
.mat-radio-disabled & {
20-
border-color: mat-color($foreground, disabled);
21-
}
20+
.mat-radio-disabled .mat-radio-outer-circle {
21+
border-color: mat-color($foreground, disabled);
2222
}
2323

2424
.mat-radio-inner-circle {
2525
background-color: mat-color($accent);
26-
27-
.mat-radio-disabled & {
28-
background-color: mat-color($foreground, disabled);
29-
}
3026
}
3127

3228
.mat-radio-ripple .mat-ripple-element {
3329
background-color: mat-color($accent, 0.26);
30+
}
3431

35-
.mat-radio-disabled & {
32+
.mat-radio-disabled {
33+
.mat-radio-ripple .mat-ripple-element, .mat-radio-inner-circle {
3634
background-color: mat-color($foreground, disabled);
3735
}
3836
}

src/lib/select/_select-theme.scss

Lines changed: 22 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,39 @@
11
@import '../core/theming/palette';
22
@import '../core/theming/theming';
33

4+
@mixin _mat-select-inner-content-theme($palette) {
5+
$color: mat-color($palette);
6+
7+
.mat-select-trigger, .mat-select-arrow {
8+
color: $color;
9+
}
10+
11+
.mat-select-underline {
12+
background-color: $color;
13+
}
14+
}
15+
416
@mixin mat-select-theme($theme) {
517
$foreground: map-get($theme, foreground);
618
$background: map-get($theme, background);
719
$primary: map-get($theme, primary);
820
$warn: map-get($theme, warn);
921

10-
.mat-select-trigger {
22+
.mat-select-trigger,
23+
.mat-select-arrow {
1124
color: mat-color($foreground, hint-text);
12-
13-
.mat-select:focus:not(.mat-select-disabled) & {
14-
color: mat-color($primary);
15-
}
16-
17-
.mat-select:not(:focus).ng-invalid.ng-touched:not(.mat-select-disabled) & {
18-
color: mat-color($warn);
19-
}
2025
}
2126

2227
.mat-select-underline {
2328
background-color: mat-color($foreground, divider);
24-
25-
.mat-select:focus:not(.mat-select-disabled) & {
26-
background-color: mat-color($primary);
27-
}
28-
29-
.mat-select:not(:focus).ng-invalid.ng-touched:not(.mat-select-disabled) & {
30-
background-color: mat-color($warn);
31-
}
3229
}
3330

34-
.mat-select-arrow {
35-
color: mat-color($foreground, hint-text);
36-
37-
.mat-select:focus:not(.mat-select-disabled) & {
38-
color: mat-color($primary);
39-
}
31+
.mat-select:focus:not(.mat-select-disabled) {
32+
@include _mat-select-inner-content-theme($primary);
33+
}
4034

41-
.mat-select:not(:focus).ng-invalid.ng-touched:not(.mat-select-disabled) & {
42-
color: mat-color($warn);
43-
}
35+
.mat-select:not(:focus).ng-invalid.ng-touched:not(.mat-select-disabled) {
36+
@include _mat-select-inner-content-theme($warn);
4437
}
4538

4639
.mat-select-content, .mat-select-panel-done-animating {
@@ -49,9 +42,9 @@
4942

5043
.mat-select-value {
5144
color: mat-color($foreground, text);
45+
}
5246

53-
.mat-select-disabled & {
54-
color: mat-color($foreground, hint-text);
55-
}
47+
.mat-select-disabled .mat-select-value {
48+
color: mat-color($foreground, hint-text);
5649
}
5750
}

src/lib/slider/_slider-theme.scss

Lines changed: 19 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
@import '../core/theming/palette';
22
@import '../core/theming/theming';
33

4+
@mixin _mat-slider-inner-content-theme($palette) {
5+
.mat-slider-track-fill,
6+
.mat-slider-thumb,
7+
.mat-slider-thumb-label {
8+
background-color: mat-color($palette);
9+
}
10+
11+
.mat-slider-thumb-label-text {
12+
color: mat-color($palette, default-contrast);
13+
}
14+
}
415

516
@mixin mat-slider-theme($theme) {
617
$primary: map-get($theme, primary);
@@ -19,40 +30,22 @@
1930
background-color: $mat-slider-off-color;
2031
}
2132

22-
.mat-slider-track-fill,
23-
.mat-slider-thumb,
24-
.mat-slider-thumb-label {
25-
.mat-primary & {
26-
background-color: mat-color($primary);
27-
}
33+
.mat-primary {
34+
@include _mat-slider-inner-content-theme($primary);
35+
}
2836

29-
.mat-accent & {
30-
background-color: mat-color($accent);
31-
}
37+
.mat-accent {
38+
@include _mat-slider-inner-content-theme($accent);
39+
}
3240

33-
.mat-warn & {
34-
background-color: mat-color($warn);
35-
}
41+
.mat-warn {
42+
@include _mat-slider-inner-content-theme($warn);
3643
}
3744

3845
.mat-slider-focus-ring {
3946
background-color: $mat-slider-focus-ring-color;
4047
}
4148

42-
.mat-slider-thumb-label-text {
43-
.mat-primary & {
44-
color: mat-color($primary, default-contrast);
45-
}
46-
47-
.mat-accent & {
48-
color: mat-color($accent, default-contrast);
49-
}
50-
51-
.mat-warn & {
52-
color: mat-color($warn, default-contrast);
53-
}
54-
}
55-
5649
.mat-slider:hover,
5750
.cdk-focused {
5851
.mat-slider-track-background {

src/lib/tabs/_tabs-theme.scss

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@
1212
.mat-tab-nav-bar,
1313
.mat-tab-header {
1414
border-bottom: $header-border;
15+
}
1516

16-
.mat-tab-group-inverted-header & {
17+
.mat-tab-group-inverted-header {
18+
.mat-tab-nav-bar,
19+
.mat-tab-header {
1720
border-top: $header-border;
1821
border-bottom: none;
1922
}

stylelint-config.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
{
22
"plugins": [
3-
"./tools/stylelint/no-prefixes/no-prefixes.js"
3+
"./tools/stylelint/no-prefixes/no-prefixes.js",
4+
"./tools/stylelint/selector-nested-pattern-scoped/index.js"
45
],
56
"rules": {
67
"material/no-prefixes": [["last 2 versions", "not ie <= 10", "not ie_mob <= 10"]],
8+
"material/selector-nested-pattern-scoped": [".*[^&]$", {
9+
"message": "The & operator is not allowed at the end of theme selectors.",
10+
"filePattern": "-theme\\.scss$"
11+
}],
12+
713
"color-hex-case": "lower",
814
"color-no-invalid-hex": true,
915

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
const stylelint = require('stylelint');
2+
const path = require('path');
3+
const isStandardSyntaxRule = require('stylelint/lib/utils/isStandardSyntaxRule');
4+
const isStandardSyntaxSelector = require('stylelint/lib/utils/isStandardSyntaxSelector');
5+
6+
const ruleName = 'material/selector-nested-pattern-scoped';
7+
const messages = stylelint.utils.ruleMessages(ruleName, {
8+
expected: selector => `Expected nested selector '${selector}' to match specified pattern`,
9+
});
10+
11+
/**
12+
* Re-implementation of the `selector-nested-pattern` Stylelint rule, allowing us
13+
* to scope it to a particular set of files via the custom `filePattern` option. The
14+
* primary use-case is to be able to apply the rule only to theme files.
15+
*
16+
* Reference: https://stylelint.io/user-guide/rules/selector-nested-pattern/
17+
* Source: https://github.com/stylelint/stylelint/blob/master/lib/rules/selector-nested-pattern/
18+
*/
19+
const plugin = stylelint.createPlugin(ruleName, (pattern, options) => {
20+
return (root, result) => {
21+
const selectorPattern = new RegExp(pattern);
22+
const filePattern = new RegExp(options.filePattern);
23+
const fileName = path.basename(root.source.input.file);
24+
25+
if (!filePattern.test(fileName)) return;
26+
27+
root.walkRules(rule => {
28+
if (rule.parent.type === 'rule' &&
29+
isStandardSyntaxRule(rule) &&
30+
isStandardSyntaxSelector(rule.selector) &&
31+
!selectorPattern.test(rule.selector)) {
32+
33+
stylelint.utils.report({
34+
result,
35+
ruleName,
36+
message: messages.expected(rule.selector),
37+
node: rule
38+
});
39+
}
40+
});
41+
};
42+
});
43+
44+
plugin.ruleName = ruleName;
45+
plugin.messages = messages;
46+
module.exports = plugin;

0 commit comments

Comments
 (0)