diff --git a/src/dev-app/theme.scss b/src/dev-app/theme.scss index dec5dd5d0ac3..2a01adba5396 100644 --- a/src/dev-app/theme.scss +++ b/src/dev-app/theme.scss @@ -1,5 +1,6 @@ @import '../material/core/theming/all-theme'; @import '../material/core/focus-indicator/focus-indicator'; +@import '../material-experimental/mdc-helpers/mdc-helpers'; @import '../material-experimental/mdc-slider/mdc-slider'; @import '../material-experimental/mdc-theming/all-theme'; @import '../material-experimental/mdc-typography/all-typography'; @@ -17,6 +18,7 @@ // Include base styles for strong focus indicators. .demo-strong-focus { @include mat-strong-focus-indicators(); + @include mat-mdc-strong-focus-indicators(); } // Define the default theme (same as the example above). @@ -39,6 +41,7 @@ $dark-theme: mat-dark-theme($dark-primary, $dark-accent, $dark-warn); // Include the default theme for focus indicators. .demo-strong-focus { @include mat-strong-focus-indicators-theme($candy-app-theme); + @include mat-mdc-strong-focus-indicators-theme($candy-app-theme); } // 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); // Include the dark theme for focus indicators. .demo-unicorn-dark-theme.demo-strong-focus { @include mat-strong-focus-indicators-theme($dark-theme); + @include mat-mdc-strong-focus-indicators-theme($dark-theme); } diff --git a/src/material-experimental/mdc-button/button-base.ts b/src/material-experimental/mdc-button/button-base.ts index 2b16d20e53b9..b1968daf8bb1 100644 --- a/src/material-experimental/mdc-button/button-base.ts +++ b/src/material-experimental/mdc-button/button-base.ts @@ -35,6 +35,7 @@ export const MAT_BUTTON_HOST = { // an unthemed version. If color is undefined, apply a CSS class that makes it easy to // select and style this "theme". '[class.mat-unthemed]': '!color', + 'class': 'mat-mdc-focus-indicator', }; /** List of classes to add to buttons instances based on host attribute selector. */ @@ -147,6 +148,7 @@ export const MAT_ANCHOR_HOST = { // an unthemed version. If color is undefined, apply a CSS class that makes it easy to // select and style this "theme". '[class.mat-unthemed]': '!color', + 'class': 'mat-mdc-focus-indicator', }; /** diff --git a/src/material-experimental/mdc-button/button.spec.ts b/src/material-experimental/mdc-button/button.spec.ts index e18a27ae72d4..da0daeea423d 100644 --- a/src/material-experimental/mdc-button/button.spec.ts +++ b/src/material-experimental/mdc-button/button.spec.ts @@ -257,6 +257,15 @@ describe('MDC-based MatButton', () => { ); }); }); + + it('should have a focus indicator', () => { + const fixture = TestBed.createComponent(TestApp); + const buttonNativeElements = + [...fixture.debugElement.nativeElement.querySelectorAll('a, button')]; + + expect(buttonNativeElements + .every(element => element.classList.contains('mat-mdc-focus-indicator'))).toBe(true); + }); }); /** Test component that contains an MatButton. */ diff --git a/src/material-experimental/mdc-button/fab.scss b/src/material-experimental/mdc-button/fab.scss index a23a4d05aa04..f059b0882f46 100644 --- a/src/material-experimental/mdc-button/fab.scss +++ b/src/material-experimental/mdc-button/fab.scss @@ -9,9 +9,13 @@ .mat-mdc-fab, .mat-mdc-mini-fab { @include _mat-button-interactive(); @include _mat-button-disabled(); -} -.mat-mdc-fab, .mat-mdc-mini-fab { + // MDC adds some styles to fab and mini-fab that conflict with some of our focus indicator + // styles and don't actually do anything. This undoes those conflicting styles. + &:not(.mdc-ripple-upgraded):focus::before { + background: transparent; + opacity: 1; + } } // MDC expects the fab icon to contain this HTML content: diff --git a/src/material-experimental/mdc-button/icon-button.scss b/src/material-experimental/mdc-button/icon-button.scss index ac18a6c91571..c16dbe908a40 100644 --- a/src/material-experimental/mdc-button/icon-button.scss +++ b/src/material-experimental/mdc-button/icon-button.scss @@ -13,4 +13,16 @@ border-radius: 50%; @include _mat-button-disabled(); + + // MDC adds some styles to icon buttons that conflict with some of our focus indicator styles + // and don't actually do anything. This undoes those conflicting styles. + &.mat-unthemed, + &.mat-primary, + &.mat-accent, + &.mat-warn { + &:not(.mdc-ripple-upgraded):focus::before { + background: transparent; + opacity: 1; + } + } } diff --git a/src/material-experimental/mdc-checkbox/checkbox.html b/src/material-experimental/mdc-checkbox/checkbox.html index 28f63297fa18..20b55ada8721 100644 --- a/src/material-experimental/mdc-checkbox/checkbox.html +++ b/src/material-experimental/mdc-checkbox/checkbox.html @@ -27,7 +27,7 @@
-
{ expect(inputElement.indeterminate).toBe(true, 'indeterminate should not change'); })); }); + + it('should have a focus indicator', () => { + const checkboxRippleNativeElement = + checkboxNativeElement.querySelector('.mat-mdc-checkbox-ripple')!; + + expect(checkboxRippleNativeElement.classList.contains('mat-mdc-focus-indicator')).toBe(true); + }); }); describe('with change event and no initial value', () => { diff --git a/src/material-experimental/mdc-helpers/_mdc-helpers.scss b/src/material-experimental/mdc-helpers/_mdc-helpers.scss index d84a77dbfdcf..632edcc5a65c 100644 --- a/src/material-experimental/mdc-helpers/_mdc-helpers.scss +++ b/src/material-experimental/mdc-helpers/_mdc-helpers.scss @@ -6,6 +6,7 @@ @import '@material/theme/functions.import'; @import '@material/theme/variables.import'; @import '@material/typography/variables.import'; +@import '../../material/core/style/layout-common'; @import '../../material/core/theming/theming'; @import '../../material/core/typography/typography'; @@ -211,3 +212,74 @@ $mat-typography-level-mappings: ( // Reset the original values. $mdc-typography-styles: $orig-mdc-typography-styles !global; } + +/// Mixin that turns on strong focus indicators. +/// +/// @example +/// .my-app { +/// @include mat-mdc-strong-focus-indicators(); +/// } +@mixin mat-mdc-strong-focus-indicators() { + // Base styles for the focus indicators. + .mat-mdc-focus-indicator::before { + @include mat-fill(); + + border-radius: $mat-focus-indicator-border-radius; + border: $mat-focus-indicator-border-width $mat-focus-indicator-border-style transparent; + box-sizing: border-box; + pointer-events: none; + } + + // By default, all focus indicators are flush with the bounding box of their + // host element. For particular elements (listed below), default inset/offset + // values are necessary to ensure that the focus indicator is sufficiently + // contrastive and renders appropriately. + + .mat-mdc-focus-indicator.mdc-button::before, + .mat-mdc-focus-indicator.mdc-fab::before, + .mat-mdc-focus-indicator.mdc-icon-button::before { + margin: $mat-focus-indicator-border-width * -2; + } + + .mat-mdc-focus-indicator.mat-mdc-tab::before, + .mat-mdc-focus-indicator.mat-mdc-tab-link::before { + margin: $mat-focus-indicator-border-width * 2; + } + + // Render the focus indicator on focus. Defining a pseudo element's + // content will cause it to render. + + // For checkboxes and slide toggles, render the focus indicator when we know the hidden input + // is focused (slightly different for each control). + .mdc-checkbox__native-control:focus ~ .mat-mdc-focus-indicator::before, + .mat-mdc-slide-toggle-focused .mat-mdc-focus-indicator::before, + + // For all other components, render the focus indicator on focus. + .mat-mdc-focus-indicator:focus::before { + content: ''; + } +} + +/// Mixin that sets the color of the focus indicators. +/// +/// @param {color|map} $themeOrMap +/// If theme, focus indicators are set to the primary color of the theme. If +/// color, focus indicators are set to that color. +/// +/// @example +/// .demo-dark-theme { +/// @include mat-mdc-strong-focus-indicators-theme($darkThemeMap); +/// } +/// +/// @example +/// .demo-red-theme { +/// @include mat-mdc-strong-focus-indicators-theme(#F00); +/// } +@mixin mat-mdc-strong-focus-indicators-theme($themeOrColor) { + .mat-mdc-focus-indicator::before { + border-color: if( + type-of($themeOrColor) == 'map', + mat-color(map_get($themeOrColor, primary)), + $themeOrColor); + } +} diff --git a/src/material-experimental/mdc-menu/menu-item.ts b/src/material-experimental/mdc-menu/menu-item.ts index 16358c8dff8e..fb6a9ceac883 100644 --- a/src/material-experimental/mdc-menu/menu-item.ts +++ b/src/material-experimental/mdc-menu/menu-item.ts @@ -18,10 +18,11 @@ import {MatMenuItem as BaseMatMenuItem} from '@angular/material/menu'; inputs: ['disabled', 'disableRipple'], host: { '[attr.role]': 'role', - // The MatMenuItem parent class adds `mat-menu-item` to the CSS classlist, but this should - // not be added for this MDC equivalent menu item. + // The MatMenuItem parent class adds `mat-menu-item` and `mat-focus-indicator` to the CSS + // classlist, but these should not be added for this MDC equivalent menu item. '[class.mat-menu-item]': 'false', - 'class': 'mat-mdc-menu-item', + '[class.mat-focus-indicator]': 'false', + 'class': 'mat-mdc-menu-item mat-mdc-focus-indicator', '[class.mat-mdc-menu-item-highlighted]': '_highlighted', '[class.mat-mdc-menu-item-submenu-trigger]': '_triggersSubmenu', '[attr.tabindex]': '_getTabIndex()', diff --git a/src/material-experimental/mdc-menu/menu.spec.ts b/src/material-experimental/mdc-menu/menu.spec.ts index 83dfee6e0bbb..7d59a65e539e 100644 --- a/src/material-experimental/mdc-menu/menu.spec.ts +++ b/src/material-experimental/mdc-menu/menu.spec.ts @@ -2160,6 +2160,18 @@ describe('MDC-based MatMenu', () => { }); + it('should have a focus indicator', () => { + const fixture = createComponent(SimpleMenu, [], [FakeIcon]); + fixture.detectChanges(); + fixture.componentInstance.trigger.openMenu(); + fixture.detectChanges(); + const menuItemNativeElements = + Array.from(overlayContainerElement.querySelectorAll('.mat-mdc-menu-item')); + + expect(menuItemNativeElements + .every(element => element.classList.contains('mat-mdc-focus-indicator'))).toBe(true); + }); + }); describe('MatMenu default overrides', () => { diff --git a/src/material-experimental/mdc-slide-toggle/slide-toggle.html b/src/material-experimental/mdc-slide-toggle/slide-toggle.html index 823510286e97..d99596f79a4e 100644 --- a/src/material-experimental/mdc-slide-toggle/slide-toggle.html +++ b/src/material-experimental/mdc-slide-toggle/slide-toggle.html @@ -3,28 +3,29 @@
-
-
- +
+ +
diff --git a/src/material-experimental/mdc-slide-toggle/slide-toggle.scss b/src/material-experimental/mdc-slide-toggle/slide-toggle.scss index 8d4f828bea6b..6c2eda116902 100644 --- a/src/material-experimental/mdc-slide-toggle/slide-toggle.scss +++ b/src/material-experimental/mdc-slide-toggle/slide-toggle.scss @@ -16,6 +16,11 @@ // The ripple needs extra specificity so the base ripple styling doesn't override its `position`. .mat-mdc-slide-toggle-ripple, .mdc-switch__thumb-underlay::before { @include mat-fill; + + // Disable pointer events for the ripple container so that it doesn't eat the mouse events meant + // for the input. Pointer events can be safely disabled because the ripple trigger element is + // the host element. + pointer-events: none; } // The MDC switch styles related to the hover state are intertwined with the MDC ripple styles. diff --git a/src/material-experimental/mdc-slide-toggle/slide-toggle.spec.ts b/src/material-experimental/mdc-slide-toggle/slide-toggle.spec.ts index 4107a43eaf43..3e2272ee991f 100644 --- a/src/material-experimental/mdc-slide-toggle/slide-toggle.spec.ts +++ b/src/material-experimental/mdc-slide-toggle/slide-toggle.spec.ts @@ -297,6 +297,14 @@ describe('MDC-based MatSlideToggle without forms', () => { expect(slideToggleElement.querySelectorAll(rippleSelector).length).toBe(0); }); + + it('should have a focus indicator', () => { + const slideToggleRippleNativeElement = + slideToggleElement.querySelector('.mat-mdc-slide-toggle-ripple')!; + + expect(slideToggleRippleNativeElement.classList.contains('mat-mdc-focus-indicator')) + .toBe(true); + }); }); describe('custom template', () => { diff --git a/src/material-experimental/mdc-slider/slider.spec.ts b/src/material-experimental/mdc-slider/slider.spec.ts index d3b7d579e782..e046c86a5612 100644 --- a/src/material-experimental/mdc-slider/slider.spec.ts +++ b/src/material-experimental/mdc-slider/slider.spec.ts @@ -127,6 +127,10 @@ describe('MDC-based MatSlider', () => { expect(sliderInstance.value).toBe(100); }); + it('should have a focus indicator', () => { + expect(sliderNativeElement.classList.contains('mat-mdc-focus-indicator')).toBe(true); + }); + }); describe('disabled slider', () => { diff --git a/src/material-experimental/mdc-slider/slider.ts b/src/material-experimental/mdc-slider/slider.ts index a5b5f2126dcf..5e594e8546b8 100644 --- a/src/material-experimental/mdc-slider/slider.ts +++ b/src/material-experimental/mdc-slider/slider.ts @@ -81,7 +81,7 @@ export class MatSliderChange { templateUrl: 'slider.html', styleUrls: ['slider.css'], host: { - 'class': 'mat-mdc-slider mdc-slider', + 'class': 'mat-mdc-slider mdc-slider mat-mdc-focus-indicator', 'role': 'slider', 'aria-orientation': 'horizontal', // The tabindex if the slider turns disabled is managed by the MDC foundation which diff --git a/src/material-experimental/mdc-tabs/tab-group.html b/src/material-experimental/mdc-tabs/tab-group.html index a89da9fb8108..e7369ef899fd 100644 --- a/src/material-experimental/mdc-tabs/tab-group.html +++ b/src/material-experimental/mdc-tabs/tab-group.html @@ -4,7 +4,7 @@ (indexFocused)="_focusChanged($event)" (selectFocusedIndex)="selectedIndex = $event"> -