Skip to content

Commit 41fa20f

Browse files
zelliottmmalerba
authored andcommitted
feat(material-experimental/*): Add focus indicators to all MDC except mdc-chips. (#18175)
1 parent cd9818a commit 41fa20f

File tree

21 files changed

+200
-41
lines changed

21 files changed

+200
-41
lines changed

src/dev-app/theme.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
@import '../material/core/theming/all-theme';
22
@import '../material/core/focus-indicator/focus-indicator';
3+
@import '../material-experimental/mdc-helpers/mdc-helpers';
34
@import '../material-experimental/mdc-slider/mdc-slider';
45
@import '../material-experimental/mdc-theming/all-theme';
56
@import '../material-experimental/mdc-typography/all-typography';
@@ -17,6 +18,7 @@
1718
// Include base styles for strong focus indicators.
1819
.demo-strong-focus {
1920
@include mat-strong-focus-indicators();
21+
@include mat-mdc-strong-focus-indicators();
2022
}
2123

2224
// Define the default theme (same as the example above).
@@ -39,6 +41,7 @@ $dark-theme: mat-dark-theme($dark-primary, $dark-accent, $dark-warn);
3941
// Include the default theme for focus indicators.
4042
.demo-strong-focus {
4143
@include mat-strong-focus-indicators-theme($candy-app-theme);
44+
@include mat-mdc-strong-focus-indicators-theme($candy-app-theme);
4245
}
4346

4447
// Include the alternative theme styles inside of a block with a CSS class. You can make this
@@ -55,4 +58,5 @@ $dark-theme: mat-dark-theme($dark-primary, $dark-accent, $dark-warn);
5558
// Include the dark theme for focus indicators.
5659
.demo-unicorn-dark-theme.demo-strong-focus {
5760
@include mat-strong-focus-indicators-theme($dark-theme);
61+
@include mat-mdc-strong-focus-indicators-theme($dark-theme);
5862
}

src/material-experimental/mdc-button/button-base.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export const MAT_BUTTON_HOST = {
3535
// an unthemed version. If color is undefined, apply a CSS class that makes it easy to
3636
// select and style this "theme".
3737
'[class.mat-unthemed]': '!color',
38+
'class': 'mat-mdc-focus-indicator',
3839
};
3940

4041
/** List of classes to add to buttons instances based on host attribute selector. */
@@ -147,6 +148,7 @@ export const MAT_ANCHOR_HOST = {
147148
// an unthemed version. If color is undefined, apply a CSS class that makes it easy to
148149
// select and style this "theme".
149150
'[class.mat-unthemed]': '!color',
151+
'class': 'mat-mdc-focus-indicator',
150152
};
151153

152154
/**

src/material-experimental/mdc-button/button.spec.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,15 @@ describe('MDC-based MatButton', () => {
257257
);
258258
});
259259
});
260+
261+
it('should have a focus indicator', () => {
262+
const fixture = TestBed.createComponent(TestApp);
263+
const buttonNativeElements =
264+
[...fixture.debugElement.nativeElement.querySelectorAll('a, button')];
265+
266+
expect(buttonNativeElements
267+
.every(element => element.classList.contains('mat-mdc-focus-indicator'))).toBe(true);
268+
});
260269
});
261270

262271
/** Test component that contains an MatButton. */

src/material-experimental/mdc-button/fab.scss

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,13 @@
99
.mat-mdc-fab, .mat-mdc-mini-fab {
1010
@include _mat-button-interactive();
1111
@include _mat-button-disabled();
12-
}
1312

14-
.mat-mdc-fab, .mat-mdc-mini-fab {
13+
// MDC adds some styles to fab and mini-fab that conflict with some of our focus indicator
14+
// styles and don't actually do anything. This undoes those conflicting styles.
15+
&:not(.mdc-ripple-upgraded):focus::before {
16+
background: transparent;
17+
opacity: 1;
18+
}
1519
}
1620

1721
// MDC expects the fab icon to contain this HTML content:

src/material-experimental/mdc-button/icon-button.scss

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,16 @@
1313
border-radius: 50%;
1414

1515
@include _mat-button-disabled();
16+
17+
// MDC adds some styles to icon buttons that conflict with some of our focus indicator styles
18+
// and don't actually do anything. This undoes those conflicting styles.
19+
&.mat-unthemed,
20+
&.mat-primary,
21+
&.mat-accent,
22+
&.mat-warn {
23+
&:not(.mdc-ripple-upgraded):focus::before {
24+
background: transparent;
25+
opacity: 1;
26+
}
27+
}
1628
}

src/material-experimental/mdc-checkbox/checkbox.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
</svg>
2828
<div class="mdc-checkbox__mixedmark"></div>
2929
</div>
30-
<div class="mat-mdc-checkbox-ripple" mat-ripple
30+
<div class="mat-mdc-checkbox-ripple mat-mdc-focus-indicator" mat-ripple
3131
[matRippleTrigger]="checkbox"
3232
[matRippleDisabled]="disableRipple || disabled"
3333
[matRippleCentered]="true"

src/material-experimental/mdc-checkbox/checkbox.spec.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,13 @@ describe('MDC-based MatCheckbox', () => {
588588
expect(inputElement.indeterminate).toBe(true, 'indeterminate should not change');
589589
}));
590590
});
591+
592+
it('should have a focus indicator', () => {
593+
const checkboxRippleNativeElement =
594+
checkboxNativeElement.querySelector('.mat-mdc-checkbox-ripple')!;
595+
596+
expect(checkboxRippleNativeElement.classList.contains('mat-mdc-focus-indicator')).toBe(true);
597+
});
591598
});
592599

593600
describe('with change event and no initial value', () => {

src/material-experimental/mdc-helpers/_mdc-helpers.scss

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
@import '@material/theme/functions.import';
77
@import '@material/theme/variables.import';
88
@import '@material/typography/variables.import';
9+
@import '../../material/core/style/layout-common';
910
@import '../../material/core/theming/theming';
1011
@import '../../material/core/typography/typography';
1112

@@ -211,3 +212,74 @@ $mat-typography-level-mappings: (
211212
// Reset the original values.
212213
$mdc-typography-styles: $orig-mdc-typography-styles !global;
213214
}
215+
216+
/// Mixin that turns on strong focus indicators.
217+
///
218+
/// @example
219+
/// .my-app {
220+
/// @include mat-mdc-strong-focus-indicators();
221+
/// }
222+
@mixin mat-mdc-strong-focus-indicators() {
223+
// Base styles for the focus indicators.
224+
.mat-mdc-focus-indicator::before {
225+
@include mat-fill();
226+
227+
border-radius: $mat-focus-indicator-border-radius;
228+
border: $mat-focus-indicator-border-width $mat-focus-indicator-border-style transparent;
229+
box-sizing: border-box;
230+
pointer-events: none;
231+
}
232+
233+
// By default, all focus indicators are flush with the bounding box of their
234+
// host element. For particular elements (listed below), default inset/offset
235+
// values are necessary to ensure that the focus indicator is sufficiently
236+
// contrastive and renders appropriately.
237+
238+
.mat-mdc-focus-indicator.mdc-button::before,
239+
.mat-mdc-focus-indicator.mdc-fab::before,
240+
.mat-mdc-focus-indicator.mdc-icon-button::before {
241+
margin: $mat-focus-indicator-border-width * -2;
242+
}
243+
244+
.mat-mdc-focus-indicator.mat-mdc-tab::before,
245+
.mat-mdc-focus-indicator.mat-mdc-tab-link::before {
246+
margin: $mat-focus-indicator-border-width * 2;
247+
}
248+
249+
// Render the focus indicator on focus. Defining a pseudo element's
250+
// content will cause it to render.
251+
252+
// For checkboxes and slide toggles, render the focus indicator when we know the hidden input
253+
// is focused (slightly different for each control).
254+
.mdc-checkbox__native-control:focus ~ .mat-mdc-focus-indicator::before,
255+
.mat-mdc-slide-toggle-focused .mat-mdc-focus-indicator::before,
256+
257+
// For all other components, render the focus indicator on focus.
258+
.mat-mdc-focus-indicator:focus::before {
259+
content: '';
260+
}
261+
}
262+
263+
/// Mixin that sets the color of the focus indicators.
264+
///
265+
/// @param {color|map} $themeOrMap
266+
/// If theme, focus indicators are set to the primary color of the theme. If
267+
/// color, focus indicators are set to that color.
268+
///
269+
/// @example
270+
/// .demo-dark-theme {
271+
/// @include mat-mdc-strong-focus-indicators-theme($darkThemeMap);
272+
/// }
273+
///
274+
/// @example
275+
/// .demo-red-theme {
276+
/// @include mat-mdc-strong-focus-indicators-theme(#F00);
277+
/// }
278+
@mixin mat-mdc-strong-focus-indicators-theme($themeOrColor) {
279+
.mat-mdc-focus-indicator::before {
280+
border-color: if(
281+
type-of($themeOrColor) == 'map',
282+
mat-color(map_get($themeOrColor, primary)),
283+
$themeOrColor);
284+
}
285+
}

src/material-experimental/mdc-menu/menu-item.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@ import {MatMenuItem as BaseMatMenuItem} from '@angular/material/menu';
1818
inputs: ['disabled', 'disableRipple'],
1919
host: {
2020
'[attr.role]': 'role',
21-
// The MatMenuItem parent class adds `mat-menu-item` to the CSS classlist, but this should
22-
// not be added for this MDC equivalent menu item.
21+
// The MatMenuItem parent class adds `mat-menu-item` and `mat-focus-indicator` to the CSS
22+
// classlist, but these should not be added for this MDC equivalent menu item.
2323
'[class.mat-menu-item]': 'false',
24-
'class': 'mat-mdc-menu-item',
24+
'[class.mat-focus-indicator]': 'false',
25+
'class': 'mat-mdc-menu-item mat-mdc-focus-indicator',
2526
'[class.mat-mdc-menu-item-highlighted]': '_highlighted',
2627
'[class.mat-mdc-menu-item-submenu-trigger]': '_triggersSubmenu',
2728
'[attr.tabindex]': '_getTabIndex()',

src/material-experimental/mdc-menu/menu.spec.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2160,6 +2160,18 @@ describe('MDC-based MatMenu', () => {
21602160

21612161
});
21622162

2163+
it('should have a focus indicator', () => {
2164+
const fixture = createComponent(SimpleMenu, [], [FakeIcon]);
2165+
fixture.detectChanges();
2166+
fixture.componentInstance.trigger.openMenu();
2167+
fixture.detectChanges();
2168+
const menuItemNativeElements =
2169+
Array.from(overlayContainerElement.querySelectorAll('.mat-mdc-menu-item'));
2170+
2171+
expect(menuItemNativeElements
2172+
.every(element => element.classList.contains('mat-mdc-focus-indicator'))).toBe(true);
2173+
});
2174+
21632175
});
21642176

21652177
describe('MatMenu default overrides', () => {

src/material-experimental/mdc-slide-toggle/slide-toggle.html

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,29 @@
33
<div class="mdc-switch" #switch>
44
<div class="mdc-switch__track"></div>
55
<div class="mdc-switch__thumb-underlay">
6-
<div class="mat-mdc-slide-toggle-ripple" mat-ripple
6+
<div class="mat-mdc-slide-toggle-ripple mat-mdc-focus-indicator" mat-ripple
77
[matRippleTrigger]="switch"
88
[matRippleDisabled]="disableRipple || disabled"
99
[matRippleCentered]="true"
1010
[matRippleRadius]="24"
1111
[matRippleAnimation]="_rippleAnimation"></div>
12-
<div class="mdc-switch__thumb"></div>
13-
<input #input class="mdc-switch__native-control" type="checkbox"
14-
role="switch"
15-
[id]="inputId"
16-
[required]="required"
17-
[tabIndex]="tabIndex"
18-
[checked]="checked"
19-
[disabled]="disabled"
20-
[attr.name]="name"
21-
[attr.aria-checked]="checked.toString()"
22-
[attr.aria-label]="ariaLabel"
23-
[attr.aria-labelledby]="ariaLabelledby"
24-
(change)="_onChangeEvent($event)"
25-
(click)="_onInputClick($event)"
26-
(blur)="_onBlur()"
27-
(focus)="_focused = true">
12+
<div class="mdc-switch__thumb">
13+
<input #input class="mdc-switch__native-control" type="checkbox"
14+
role="switch"
15+
[id]="inputId"
16+
[required]="required"
17+
[tabIndex]="tabIndex"
18+
[checked]="checked"
19+
[disabled]="disabled"
20+
[attr.name]="name"
21+
[attr.aria-checked]="checked.toString()"
22+
[attr.aria-label]="ariaLabel"
23+
[attr.aria-labelledby]="ariaLabelledby"
24+
(change)="_onChangeEvent($event)"
25+
(click)="_onInputClick($event)"
26+
(blur)="_onBlur()"
27+
(focus)="_focused = true">
28+
</div>
2829
</div>
2930
</div>
3031

src/material-experimental/mdc-slide-toggle/slide-toggle.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@
1616
// The ripple needs extra specificity so the base ripple styling doesn't override its `position`.
1717
.mat-mdc-slide-toggle-ripple, .mdc-switch__thumb-underlay::before {
1818
@include mat-fill;
19+
20+
// Disable pointer events for the ripple container so that it doesn't eat the mouse events meant
21+
// for the input. Pointer events can be safely disabled because the ripple trigger element is
22+
// the host element.
23+
pointer-events: none;
1924
}
2025

2126
// The MDC switch styles related to the hover state are intertwined with the MDC ripple styles.

src/material-experimental/mdc-slide-toggle/slide-toggle.spec.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,14 @@ describe('MDC-based MatSlideToggle without forms', () => {
297297

298298
expect(slideToggleElement.querySelectorAll(rippleSelector).length).toBe(0);
299299
});
300+
301+
it('should have a focus indicator', () => {
302+
const slideToggleRippleNativeElement =
303+
slideToggleElement.querySelector('.mat-mdc-slide-toggle-ripple')!;
304+
305+
expect(slideToggleRippleNativeElement.classList.contains('mat-mdc-focus-indicator'))
306+
.toBe(true);
307+
});
300308
});
301309

302310
describe('custom template', () => {

src/material-experimental/mdc-slider/slider.spec.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,10 @@ describe('MDC-based MatSlider', () => {
127127
expect(sliderInstance.value).toBe(100);
128128
});
129129

130+
it('should have a focus indicator', () => {
131+
expect(sliderNativeElement.classList.contains('mat-mdc-focus-indicator')).toBe(true);
132+
});
133+
130134
});
131135

132136
describe('disabled slider', () => {

src/material-experimental/mdc-slider/slider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ export class MatSliderChange {
8181
templateUrl: 'slider.html',
8282
styleUrls: ['slider.css'],
8383
host: {
84-
'class': 'mat-mdc-slider mdc-slider',
84+
'class': 'mat-mdc-slider mdc-slider mat-mdc-focus-indicator',
8585
'role': 'slider',
8686
'aria-orientation': 'horizontal',
8787
// The tabindex if the slider turns disabled is managed by the MDC foundation which

src/material-experimental/mdc-tabs/tab-group.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
(indexFocused)="_focusChanged($event)"
55
(selectFocusedIndex)="selectedIndex = $event">
66

7-
<div class="mdc-tab mat-mdc-tab"
7+
<div class="mdc-tab mat-mdc-tab mat-mdc-focus-indicator"
88
#tabNode
99
role="tab"
1010
matTabLabelWrapper

src/material-experimental/mdc-tabs/tab-group.spec.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,14 @@ describe('MDC-based MatTabGroup', () => {
292292
subscription.unsubscribe();
293293
});
294294

295+
it('should have a focus indicator', () => {
296+
const tabLabelNativeElements =
297+
[...fixture.debugElement.nativeElement.querySelectorAll('.mat-mdc-tab')];
298+
299+
expect(tabLabelNativeElements.every(el => el.classList.contains('mat-mdc-focus-indicator')))
300+
.toBe(true);
301+
});
302+
295303
});
296304

297305
describe('aria labelling', () => {

src/material-experimental/mdc-tabs/tab-nav-bar/tab-nav-bar.spec.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,14 @@ describe('MDC-based MatTabNavBar', () => {
322322
expect(fixture.componentInstance.tabLinks.toArray().every(tabLink => tabLink.rippleDisabled))
323323
.toBe(true, 'Expected every tab link to have ripples disabled');
324324
});
325+
326+
it('should have a focus indicator', () => {
327+
const tabLinkNativeElements =
328+
[...fixture.debugElement.nativeElement.querySelectorAll('.mat-mdc-tab-link')];
329+
330+
expect(tabLinkNativeElements
331+
.every(element => element.classList.contains('mat-mdc-focus-indicator'))).toBe(true);
332+
});
325333
});
326334

327335
describe('with the ink bar fit to content', () => {

src/material-experimental/mdc-tabs/tab-nav-bar/tab-nav-bar.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ export class MatTabNav extends _MatTabNavBase implements AfterContentInit {
118118
templateUrl: 'tab-link.html',
119119
styleUrls: ['tab-link.css'],
120120
host: {
121-
'class': 'mdc-tab mat-mdc-tab-link',
121+
'class': 'mdc-tab mat-mdc-tab-link mat-mdc-focus-indicator',
122122
'[attr.aria-current]': 'active ? "page" : null',
123123
'[attr.aria-disabled]': 'disabled',
124124
'[attr.tabIndex]': 'tabIndex',

src/material/button/button.spec.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -273,9 +273,11 @@ describe('MatButton', () => {
273273

274274
it('should have a focus indicator', () => {
275275
const fixture = TestBed.createComponent(TestApp);
276-
const buttonNativeElement = fixture.debugElement.nativeElement.querySelector('button');
276+
const buttonNativeElements =
277+
[...fixture.debugElement.nativeElement.querySelectorAll('a, button')];
277278

278-
expect(buttonNativeElement.classList.contains('mat-focus-indicator')).toBe(true);
279+
expect(buttonNativeElements.every(element => element.classList.contains('mat-focus-indicator')))
280+
.toBe(true);
279281
});
280282
});
281283

0 commit comments

Comments
 (0)