From 9aed5dc56689f30327013eb383880f7f74f34392 Mon Sep 17 00:00:00 2001 From: Jackie Chu Date: Wed, 8 Jun 2022 08:46:42 +0200 Subject: [PATCH] feat(material/core): use strong focus indicators in high contrast mode Reuses the strong focus indicators styling for high-contrast mode users. --- src/dev-app/theme.scss | 14 ++- .../mdc-button/button-high-contrast.scss | 6 -- .../mdc-button/button.scss | 26 +++++ .../mdc-checkbox/checkbox.scss | 18 ++-- src/material-experimental/mdc-chips/chip.scss | 30 +++++- .../mdc-core/option/option.scss | 24 +---- .../mdc-helpers/_focus-indicators-theme.scss | 23 ++--- .../mdc-helpers/_focus-indicators.scss | 96 +------------------ src/material-experimental/mdc-list/list.scss | 6 ++ src/material-experimental/mdc-menu/menu.scss | 8 +- .../mdc-radio/radio.scss | 23 ++--- .../mdc-slide-toggle/slide-toggle.scss | 18 ++-- .../mdc-slider/slider.scss | 11 +++ .../mdc-tabs/_tabs-common.scss | 9 -- .../mdc-tabs/tab-header.scss | 6 ++ .../mdc-tabs/tab-nav-bar/tab-link.scss | 6 ++ src/material/_index.scss | 1 + src/material/button-toggle/button-toggle.scss | 9 -- src/material/button/button.scss | 28 ++++-- src/material/checkbox/checkbox.scss | 12 +-- src/material/chips/chips.scss | 15 ++- src/material/core/_core.scss | 21 +--- .../_focus-indicators-theme.scss | 15 ++- .../focus-indicators/_focus-indicators.scss | 87 +---------------- src/material/core/option/option.scss | 21 ++-- .../core/style/_focus-indicators.scss | 63 ++++++++++++ src/material/datepicker/calendar-body.scss | 11 --- src/material/datepicker/calendar.scss | 16 +++- .../expansion/expansion-panel-header.scss | 16 ---- src/material/list/list.scss | 6 +- src/material/menu/menu.scss | 8 +- src/material/radio/radio.scss | 15 ++- src/material/slide-toggle/slide-toggle.scss | 14 ++- src/material/sort/BUILD.bazel | 1 + src/material/sort/sort-header.scss | 11 +++ src/material/stepper/step-header.scss | 9 +- src/material/tabs/_tabs-common.scss | 6 -- src/material/tabs/tab-header.scss | 6 ++ .../tabs/tab-nav-bar/tab-nav-bar.scss | 6 ++ 39 files changed, 312 insertions(+), 409 deletions(-) create mode 100644 src/material/core/style/_focus-indicators.scss diff --git a/src/dev-app/theme.scss b/src/dev-app/theme.scss index f4d362b3054f..1ab706e393c4 100644 --- a/src/dev-app/theme.scss +++ b/src/dev-app/theme.scss @@ -33,12 +33,9 @@ $candy-app-theme: mat.define-light-theme(( .demo-strong-focus { // Include base styles for strong focus indicators. - @include mat.strong-focus-indicators(); - @include experimental.mdc-strong-focus-indicators(); - - // Include the default theme for focus indicators. - @include mat.strong-focus-indicators-theme($candy-app-theme); - @include experimental.mdc-strong-focus-indicators-theme($candy-app-theme); + $indicators-config: (border-color: mat.get-color-from-palette($candy-app-primary)); + @include mat.strong-focus-indicators($indicators-config); + @include experimental.mdc-strong-focus-indicators($indicators-config); } // Include the alternative theme styles inside of a block with a CSS class. You can make this @@ -60,8 +57,9 @@ $candy-app-theme: mat.define-light-theme(( // Include the dark theme colors for focus indicators. &.demo-strong-focus { - @include mat.strong-focus-indicators-color($dark-colors); - @include experimental.mdc-strong-focus-indicators-color($dark-colors); + $indicators-config: (border-color: mat.get-color-from-palette($dark-primary)); + @include mat.strong-focus-indicators($indicators-config); + @include experimental.mdc-strong-focus-indicators($indicators-config); } } diff --git a/src/material-experimental/mdc-button/button-high-contrast.scss b/src/material-experimental/mdc-button/button-high-contrast.scss index eec802bca506..dffac1830765 100644 --- a/src/material-experimental/mdc-button/button-high-contrast.scss +++ b/src/material-experimental/mdc-button/button-high-contrast.scss @@ -11,9 +11,3 @@ outline: solid 1px; } } - -@include cdk.high-contrast(active, off) { - .mat-mdc-button-base:focus { - outline: solid 3px; - } -} diff --git a/src/material-experimental/mdc-button/button.scss b/src/material-experimental/mdc-button/button.scss index 6e2618cfe584..cfcd1e58854e 100644 --- a/src/material-experimental/mdc-button/button.scss +++ b/src/material-experimental/mdc-button/button.scss @@ -107,3 +107,29 @@ right: $offset; border-width: $offset; } + +// For the button element, default inset/offset values are necessary to ensure that +// the focus indicator is sufficiently contrastive and renders appropriately. +.mat-mdc-unelevated-button, +.mat-mdc-raised-button, +.mdc-fab { + .mat-mdc-focus-indicator::before { + $default-border-width: mat.$focus-indicators-private-default-border-width; + $border-width: var(--mat-mdc-focus-indicator-border-width, #{$default-border-width}); + $offset: calc(#{$border-width} + 2px); + margin: calc(#{$offset} * -1); + } +} + +.mat-mdc-outlined-button .mat-mdc-focus-indicator::before { + $default-border-width: mat.$focus-indicators-private-default-border-width; + $border-width: var(--mat-mdc-focus-indicator-border-width, #{$default-border-width}); + $offset: calc(#{$border-width} + 3px); + margin: calc(#{$offset} * -1); +} + +// For buttons, render the focus indicator when the parent +// button is focused. +.mat-mdc-button-base:focus .mat-mdc-focus-indicator::before { + content: ''; +} diff --git a/src/material-experimental/mdc-checkbox/checkbox.scss b/src/material-experimental/mdc-checkbox/checkbox.scss index a2bbcee30802..eb9978e3f192 100644 --- a/src/material-experimental/mdc-checkbox/checkbox.scss +++ b/src/material-experimental/mdc-checkbox/checkbox.scss @@ -1,4 +1,3 @@ -@use '@angular/cdk'; @use '@angular/material' as mat; @use '@material/checkbox/checkbox' as mdc-checkbox; @use '@material/checkbox/checkbox-theme' as mdc-checkbox-theme; @@ -49,11 +48,6 @@ .mdc-checkbox__native-control:not([disabled]):focus ~ .mdc-checkbox__ripple { opacity: map.get(mdc-ripple.$dark-ink-opacities, hover) + map.get(mdc-ripple.$dark-ink-opacities, focus); - - @include cdk.high-contrast(active, off) { - outline: solid 3px; - opacity: 1; - } } } @@ -125,3 +119,15 @@ $set-width: true, $query: mdc-helpers.$mat-base-styles-query); } + +// Checkbox components have to set `border-radius: 50%` in order to support density scaling +// which will clip a square focus indicator so we have to turn it into a circle. +.mat-mdc-checkbox-ripple.mat-mdc-focus-indicator::before { + border-radius: 50%; +} + +// For checkboxes 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 { + content: ''; +} diff --git a/src/material-experimental/mdc-chips/chip.scss b/src/material-experimental/mdc-chips/chip.scss index 99cad10cc9be..35529f263116 100644 --- a/src/material-experimental/mdc-chips/chip.scss +++ b/src/material-experimental/mdc-chips/chip.scss @@ -40,11 +40,6 @@ @include cdk.high-contrast(active, off) { outline: solid 1px; - &.cdk-focused { - // Use 2px here since the dotted outline is a little thinner. - outline: dotted 2px; - } - .mdc-evolution-chip__checkmark-path { // SVG colors won't be changed in high contrast mode and since the checkmark is white // by default, it'll blend in with the background in black-on-white mode. Override the @@ -215,3 +210,28 @@ outline-width: 3px; } } + +// For the chip element, default inset/offset values are necessary to ensure that +// the focus indicator is sufficiently contrastive and renders appropriately. +.mat-mdc-chip-action-label .mat-mdc-focus-indicator::before { + $default-border-width: mat.$focus-indicators-private-default-border-width; + $border-width: var(--mat-mdc-focus-indicator-border-width, #{$default-border-width}); + $offset: calc(#{$border-width} + 2px); + margin: calc(#{$offset} * -1); +} + +.mat-mdc-focus-indicator.mat-mdc-chip-remove::before { + $default-border-width: mat.$focus-indicators-private-default-border-width; + margin: var(--mat-mdc-focus-indicator-border-width, #{$default-border-width}); +} + +// MDC sets a padding a on the chip button which stretches out the focus indicator. +.mat-mdc-focus-indicator.mat-mdc-chip-remove::before { + left: 8px; + right: 8px; +} + +// In the chips the individual actions have focus so we target a different element. +.mat-mdc-chip-action:focus .mat-mdc-focus-indicator::before { + content: ''; +} diff --git a/src/material-experimental/mdc-core/option/option.scss b/src/material-experimental/mdc-core/option/option.scss index 228c91258a5e..701a32012dbc 100644 --- a/src/material-experimental/mdc-core/option/option.scss +++ b/src/material-experimental/mdc-core/option/option.scss @@ -1,4 +1,3 @@ -@use '@angular/cdk'; @use '@angular/material' as mat; @use '@material/list/evolution-mixins' as mdc-list-mixins; @use '@material/list/evolution-variables' as mdc-list-variables; @@ -91,24 +90,7 @@ } } -.mat-mdc-option-active { - @include cdk.high-contrast(active, off) { - // A pseudo element is used here, because the active indication gets moved between options - // and we don't want the border from below to shift the layout around as it's added and removed. - &::before { - content: ''; - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - pointer-events: none; - - // We use a border here, rather than an outline, because the outline will be cut off - // by the `overflow: hidden` on the panel wrapping the options, whereas a border - // will push the element inwards. This could be done using `outline-offset: -1px`, - // however the property isn't supported on IE11. - border: solid 1px currentColor; - } - } +// For options, render the focus indicator when the class .mat-mdc-option-active is present. +.mat-mdc-focus-indicator.mat-mdc-option-active::before { + content: ''; } diff --git a/src/material-experimental/mdc-helpers/_focus-indicators-theme.scss b/src/material-experimental/mdc-helpers/_focus-indicators-theme.scss index 00035bde271e..d31cb5491363 100644 --- a/src/material-experimental/mdc-helpers/_focus-indicators-theme.scss +++ b/src/material-experimental/mdc-helpers/_focus-indicators-theme.scss @@ -1,34 +1,29 @@ @use '@angular/material' as mat; @use 'sass:map'; @use 'sass:meta'; - -@mixin _border-color($color) { - .mat-mdc-focus-indicator::before { - border-color: $color; - } -} +@use '../theming/theming'; // stylelint-disable-next-line material/theme-mixin-api @mixin color($config-or-theme-or-color) { @if meta.type-of($config-or-theme-or-color) == 'color' { - @include _border-color($config-or-theme-or-color); + @include mat.strong-focus-indicators((border-color: $config-or-theme-or-color)); } @else { - $config: mat.get-color-config($config-or-theme-or-color); - $border-color: mat.get-color-from-palette(map.get($config, primary)); - @include _border-color($border-color); + $config: theming.get-color-config($config-or-theme-or-color); + $border-color: theming.get-color-from-palette(map.get($config, primary)); + @include mat.strong-focus-indicators((border-color: $border-color)); } } // stylelint-disable-next-line material/theme-mixin-api @mixin theme($theme-or-color-config-or-color) { @if meta.type-of($theme-or-color-config-or-color) == 'color' { - @include _border-color($theme-or-color-config-or-color); + @include mat.strong-focus-indicators((border-color: $theme-or-color-config-or-color)); } @else { - $theme: mat.private-legacy-get-theme($theme-or-color-config-or-color); - @include mat.private-check-duplicate-theme-styles($theme, 'mat-mdc-focus-indicators') { - $color: mat.get-color-config($theme); + $theme: theming.private-legacy-get-theme($theme-or-color-config-or-color); + @include theming.private-check-duplicate-theme-styles($theme, 'mat-mdc-focus-indicators') { + $color: theming.get-color-config($theme); @if $color != null { @include color($color); } diff --git a/src/material-experimental/mdc-helpers/_focus-indicators.scss b/src/material-experimental/mdc-helpers/_focus-indicators.scss index 6c2273d95a67..4b4f13408c35 100644 --- a/src/material-experimental/mdc-helpers/_focus-indicators.scss +++ b/src/material-experimental/mdc-helpers/_focus-indicators.scss @@ -1,104 +1,14 @@ -@use '@angular/material' as mat; @use 'sass:map'; +@use '@angular/material' as mat; -/// Mixin that turns on strong focus indicators. -/// -/// @example -/// .my-app { -/// @include mat-mdc-strong-focus-indicators($config); -/// } @mixin strong-focus-indicators($config: ()) { // Default focus indicator config. $default-config: ( - border-style: solid, - border-width: 3px, - border-radius: 4px, + border-color: black, ); // Merge default config with user config. $config: map.merge($default-config, $config); - $border-style: map.get($config, border-style); - $border-width: map.get($config, border-width); - $border-radius: map.get($config, border-radius); - - // Base styles for focus indicators. - .mat-mdc-focus-indicator::before { - @include mat.private-fill(); - box-sizing: border-box; - pointer-events: none; - border: $border-width $border-style transparent; - border-radius: $border-radius; - - .cdk-high-contrast-active & { - display: 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-unelevated-button .mat-mdc-focus-indicator::before, - .mat-mdc-raised-button .mat-mdc-focus-indicator::before, - .mdc-fab .mat-mdc-focus-indicator::before, - .mat-mdc-chip-action-label .mat-mdc-focus-indicator::before { - margin: -($border-width + 2px); - } - - .mat-mdc-outlined-button .mat-mdc-focus-indicator::before { - margin: -($border-width + 3px); - } - - .mat-mdc-focus-indicator.mat-mdc-chip-remove::before { - margin: -$border-width; - } - - // MDC sets a padding a on the button which stretches out the focus indicator. - .mat-mdc-focus-indicator.mat-mdc-chip-remove::before { - left: 8px; - right: 8px; - } - - .mat-mdc-focus-indicator.mat-mdc-tab::before, - .mat-mdc-focus-indicator.mat-mdc-tab-link::before { - margin: 5px; - } - - // These components have to set `border-radius: 50%` in order to support density scaling - // which will clip a square focus indicator so we have to turn it into a circle. - .mat-mdc-checkbox-ripple.mat-mdc-focus-indicator::before, - .mat-radio-ripple.mat-mdc-focus-indicator::before, - .mat-mdc-slider .mat-mdc-focus-indicator::before, - .mat-mdc-slide-toggle .mat-mdc-focus-indicator::before { - border-radius: 50%; - } - - // Render the focus indicator on focus. Defining a pseudo element's - // content will cause it to render. - - // For checkboxes, radios 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, - .mat-mdc-radio-button.cdk-focused .mat-mdc-focus-indicator::before, - - // In the chips the individual actions have focus so we target a different element. - .mat-mdc-chip-action:focus .mat-mdc-focus-indicator::before, - - // For buttons and list items, render the focus indicator when the parent - // button or list item is focused. - .mat-mdc-button-base:focus .mat-mdc-focus-indicator::before, - .mat-mdc-list-item:focus > .mat-mdc-focus-indicator::before, - - // For options, render the focus indicator when the class .mat-mdc-option-active is present. - .mat-mdc-focus-indicator.mat-mdc-option-active::before, - - // In the MDC slider the focus indicator is inside the thumb. - .mdc-slider__thumb--focused .mat-mdc-focus-indicator::before, - // For all other components, render the focus indicator on focus. - .mat-mdc-focus-indicator:focus::before { - content: ''; - } + @include mat.focus-indicators-private-private-customize-focus-indicators($config, 'mat-mdc'); } diff --git a/src/material-experimental/mdc-list/list.scss b/src/material-experimental/mdc-list/list.scss index 9f15b503d6de..14c9a1931960 100644 --- a/src/material-experimental/mdc-list/list.scss +++ b/src/material-experimental/mdc-list/list.scss @@ -104,3 +104,9 @@ mat-action-list button { border: 0; } } + +// For list items, render the focus indicator when the parent +// listem item is focused. +.mat-mdc-list-item:focus > .mat-mdc-focus-indicator::before { + content: ''; +} diff --git a/src/material-experimental/mdc-menu/menu.scss b/src/material-experimental/mdc-menu/menu.scss index 59ed142be4f0..51e2cbe0b002 100644 --- a/src/material-experimental/mdc-menu/menu.scss +++ b/src/material-experimental/mdc-menu/menu.scss @@ -84,7 +84,7 @@ mat-menu { // from closing, but clicks on child nodes still propagate which is inconsistent (see #16694). // In order to keep the behavior consistent and prevent the menu from closing, we add an overlay // on top of the content that will catch all the clicks while disabled. - &::before { + &::after { display: block; position: absolute; content: ''; @@ -124,12 +124,6 @@ mat-menu { // We need to move the item 1px down, because Firefox seems to have // an issue rendering the top part of the outline (see #21524). margin-top: $outline-width; - - &.cdk-program-focused, - &.cdk-keyboard-focused, - &-highlighted { - outline: dotted $outline-width; - } } } diff --git a/src/material-experimental/mdc-radio/radio.scss b/src/material-experimental/mdc-radio/radio.scss index 43fefa470d1d..782b5e10d5be 100644 --- a/src/material-experimental/mdc-radio/radio.scss +++ b/src/material-experimental/mdc-radio/radio.scss @@ -1,5 +1,4 @@ @use 'sass:map'; -@use '@angular/cdk'; @use '@angular/material' as mat; @use '@material/radio/radio' as mdc-radio; @use '@material/radio/radio-theme' as mdc-radio-theme; @@ -105,16 +104,14 @@ } } -// Note that this creates a square box around the circle, however it's consistent with -// how IE/Edge treat native radio buttons in high contrast mode. We can't turn the border -// into a dotted one, because it's too thick which causes the circles to look off. -@include cdk.high-contrast(active, 'off') { - .mat-mdc-radio-button:not(.mat-radio-disabled) { - &.cdk-keyboard-focused, - &.cdk-program-focused { - .mat-radio-ripple { - outline: solid 3px; - } - } - } +// Radio components have to set `border-radius: 50%` in order to support density scaling +// which will clip a square focus indicator so we have to turn it into a circle. +.mat-radio-ripple.mat-mdc-focus-indicator::before { + border-radius: 50%; +} + +// For radios render the focus indicator when we know +// the hidden input is focused (slightly different for each control). +.mat-mdc-radio-button.cdk-focused .mat-mdc-focus-indicator::before { + content: ''; } diff --git a/src/material-experimental/mdc-slide-toggle/slide-toggle.scss b/src/material-experimental/mdc-slide-toggle/slide-toggle.scss index 9d8530606795..ef9927e2e626 100644 --- a/src/material-experimental/mdc-slide-toggle/slide-toggle.scss +++ b/src/material-experimental/mdc-slide-toggle/slide-toggle.scss @@ -1,6 +1,4 @@ @use 'sass:map'; -@use 'sass:math'; -@use '@angular/cdk'; @use '@angular/material' as mat; @use '@material/switch/switch' as mdc-switch; @use '@material/switch/switch-theme' as mdc-switch-theme; @@ -65,12 +63,14 @@ } } +// Slide-toggle components have to set `border-radius: 50%` in order to support density scaling +// which will clip a square focus indicator so we have to turn it into a circle. +.mat-mdc-slide-toggle .mat-mdc-focus-indicator::before { + border-radius: 50%; +} -@include cdk.high-contrast(active, off) { - .mat-mdc-slide-toggle-focused .mdc-switch__track { - // Usually 1px would be enough, but MDC reduces the opacity on the - // element so we need to make this a bit more prominent. - outline: solid 2px; - outline-offset: math.div(map.get(mdc-switch-theme.$light-theme, track-height), 2); - } +// For slide-toggles render the focus indicator when we know +// the hidden input is focused (slightly different for each control). +.mat-mdc-slide-toggle-focused .mat-mdc-focus-indicator::before { + content: ''; } diff --git a/src/material-experimental/mdc-slider/slider.scss b/src/material-experimental/mdc-slider/slider.scss index 2362f4df2a36..5770a9ce6d87 100644 --- a/src/material-experimental/mdc-slider/slider.scss +++ b/src/material-experimental/mdc-slider/slider.scss @@ -35,3 +35,14 @@ $mat-slider-horizontal-margin: 8px !default; } } } + +// Slider components have to set `border-radius: 50%` in order to support density scaling +// which will clip a square focus indicator so we have to turn it into a circle. +.mat-mdc-slider .mat-mdc-focus-indicator::before { + border-radius: 50%; +} + +// In the MDC slider the focus indicator is inside the thumb. +.mdc-slider__thumb--focused .mat-mdc-focus-indicator::before { + content: ''; +} diff --git a/src/material-experimental/mdc-tabs/_tabs-common.scss b/src/material-experimental/mdc-tabs/_tabs-common.scss index c11ebcd864f3..a0ce244f99d3 100644 --- a/src/material-experimental/mdc-tabs/_tabs-common.scss +++ b/src/material-experimental/mdc-tabs/_tabs-common.scss @@ -1,4 +1,3 @@ -@use '@angular/cdk'; @use '@angular/material' as mat; @use '@material/ripple' as mdc-ripple; @use '@material/tab' as mdc-tab; @@ -69,14 +68,6 @@ $mat-tab-animation-duration: 500ms !default; .mdc-tab__ripple::before { opacity: map.get(mdc-ripple.$dark-ink-opacities, focus); } - - // The usual focus indication is color-based so it won't show up in - // high contrast mode. Add an outline which is a bit more visible. - @include cdk.high-contrast { - $outline-width: 2px; - outline: dotted $outline-width; - outline-offset: -$outline-width; // Not supported on IE, but looks better everywhere else. - } } .mat-ripple-element { diff --git a/src/material-experimental/mdc-tabs/tab-header.scss b/src/material-experimental/mdc-tabs/tab-header.scss index 6e98db2fe11c..cfc394d03a7a 100644 --- a/src/material-experimental/mdc-tabs/tab-header.scss +++ b/src/material-experimental/mdc-tabs/tab-header.scss @@ -9,3 +9,9 @@ .mat-mdc-tab-labels { @include tabs-common.paginated-tab-header-item-wrapper('.mat-mdc-tab-header'); } + +// For the tab element, default inset/offset values are necessary to ensure that +// the focus indicator is sufficiently contrastive and renders appropriately. +.mat-mdc-focus-indicator.mat-mdc-tab::before { + margin: 5px; +} diff --git a/src/material-experimental/mdc-tabs/tab-nav-bar/tab-link.scss b/src/material-experimental/mdc-tabs/tab-nav-bar/tab-link.scss index 9a90a0be5d0e..2972e831c3e0 100644 --- a/src/material-experimental/mdc-tabs/tab-nav-bar/tab-link.scss +++ b/src/material-experimental/mdc-tabs/tab-nav-bar/tab-link.scss @@ -27,3 +27,9 @@ min-width: 72px; } } + +// For the tab-link element, default inset/offset values are necessary to ensure that +// the focus indicator is sufficiently contrastive and renders appropriately. +.mat-mdc-focus-indicator.mat-mdc-tab-link::before { + margin: 5px; +} diff --git a/src/material/_index.scss b/src/material/_index.scss index d850e2aed93b..8219c737103f 100644 --- a/src/material/_index.scss +++ b/src/material/_index.scss @@ -32,6 +32,7 @@ @forward './table/table-flex-styles' show private-table-flex-styles; @forward './core/style/menu-common' as private-menu-common-*; @forward './core/style/button-common' as private-button-common-*; +@forward './core/style/focus-indicators' as focus-indicators-private-*; // Structural @forward './core/core' show core; diff --git a/src/material/button-toggle/button-toggle.scss b/src/material/button-toggle/button-toggle.scss index 02de7b704b04..f02c7df0b929 100644 --- a/src/material/button-toggle/button-toggle.scss +++ b/src/material/button-toggle/button-toggle.scss @@ -59,11 +59,6 @@ $legacy-border-radius: 2px !default; &.cdk-keyboard-focused { .mat-button-toggle-focus-overlay { opacity: 1; - - // In high contrast mode `opacity: 1` will show the overlay as solid so we fall back 0.5. - @include cdk.high-contrast(active, off) { - opacity: 0.5; - } } } } @@ -80,10 +75,6 @@ $legacy-border-radius: 2px !default; // TODO(paul): support `program` as well. See https://github.com/angular/components/issues/9889 &.cdk-keyboard-focused:not(.mat-button-toggle-disabled) .mat-button-toggle-focus-overlay { opacity: 0.12; - - @include cdk.high-contrast(active, off) { - opacity: 0.5; - } } // On touch devices the hover state will linger on the element after the user has tapped. diff --git a/src/material/button/button.scss b/src/material/button/button.scss index 935d98dc8771..815678e4e083 100644 --- a/src/material/button/button.scss +++ b/src/material/button/button.scss @@ -2,6 +2,7 @@ @use './button-base'; @use '../core/style/layout-common'; +@use '../core/style/focus-indicators'; // TODO(jelbourn): Measure perf benefits for translate3d and will-change. // TODO(jelbourn): Figure out if anchor hover underline actually happens in any browser. @@ -155,16 +156,31 @@ } } +// For the button element, default inset/offset values are necessary to ensure that +// the focus indicator is sufficiently contrastive and renders appropriately. +.mat-flat-button, +.mat-raised-button, +.mat-fab, +.mat-mini-fab { + &.mat-focus-indicator::before { + $border-width: + var(--mat-focus-indicator-border-width, #{focus-indicators.$default-border-width}); + $offset: calc(#{$border-width} + 2px); + margin: calc(#{$offset} * -1); + } +} + +.mat-focus-indicator.mat-stroked-button::before { + $border-width: + var(--mat-focus-indicator-border-width, #{focus-indicators.$default-border-width}); + $offset: calc(#{$border-width} + 3px); + margin: calc(#{$offset} * -1); +} + // Add an outline to make buttons more visible in high contrast mode. Stroked buttons // don't need a special look in high-contrast mode, because those already have an outline. @include cdk.high-contrast(active, off) { .mat-button, .mat-flat-button, .mat-raised-button, .mat-icon-button, .mat-fab, .mat-mini-fab { outline: solid 1px; } - - .mat-button-base { - &.cdk-keyboard-focused, &.cdk-program-focused { - outline: solid 3px; - } - } } diff --git a/src/material/checkbox/checkbox.scss b/src/material/checkbox/checkbox.scss index 84bf4d9673f6..82766addb146 100644 --- a/src/material/checkbox/checkbox.scss +++ b/src/material/checkbox/checkbox.scss @@ -204,12 +204,6 @@ $_mark-stroke-size: math.div(2, 15) * checkbox-common.$size !default; z-index: 1; pointer-events: none; } - - &.cdk-keyboard-focused .mat-checkbox-ripple { - @include cdk.high-contrast(active, off) { - outline: solid 3px; - } - } } .mat-checkbox-layout { @@ -518,3 +512,9 @@ $_mark-stroke-size: math.div(2, 15) * checkbox-common.$size !default; bottom: 0; left: 50%; } + +// Checkboxes render focus indicators when the +// associated visually-hidden input is focused. +.mat-checkbox-input:focus ~ .mat-focus-indicator::before { + content: ''; +} diff --git a/src/material/chips/chips.scss b/src/material/chips/chips.scss index 767f93b5969d..1d5993778400 100644 --- a/src/material/chips/chips.scss +++ b/src/material/chips/chips.scss @@ -4,6 +4,7 @@ @use '../core/style/elevation'; @use '../core/style/layout-common'; @use '../core/style/private'; +@use '../core/style/focus-indicators'; $chip-min-height: 32px; $chip-vertical-padding: 7px; @@ -92,11 +93,6 @@ $chip-remove-size: 18px; @include cdk.high-contrast(active, off) { outline: solid 1px; - &:focus { - // Use 2px here since the dotted outline is a little thinner. - outline: dotted 2px; - } - // Seleted state is shown using a background color which isn't visible in high contrast mode. &.mat-chip-selected { outline-width: 3px; @@ -242,3 +238,12 @@ input.mat-chip-input { margin: $chip-input-margin; flex: 1 0 $chip-input-width; } + +// For the chip element, default inset/offset values are necessary to ensure that +// the focus indicator is sufficiently contrastive and renders appropriately. +.mat-focus-indicator.mat-chip::before { + $border-width: + var(--mat-focus-indicator-border-width, #{focus-indicators.$default-border-width}); + $offset: calc(#{$border-width} + 2px); + margin: calc(#{$offset} * -1); +} diff --git a/src/material/core/_core.scss b/src/material/core/_core.scss index 47ae88b6cccf..f15f55e7c2ac 100644 --- a/src/material/core/_core.scss +++ b/src/material/core/_core.scss @@ -2,7 +2,7 @@ // Core styles that can be used to apply material design treatments to any element. @use './ripple/ripple'; -@use './focus-indicators/focus-indicators'; +@use './style/focus-indicators'; @use './typography/all-typography'; // Mixin that renders all of the core styles that are not theme-dependent. @@ -13,21 +13,6 @@ @include cdk.overlay(); @include cdk.text-field-autosize(); @include cdk.text-field-autofill(); - - @include focus-indicators.private-strong-focus-indicators-positioning(); - @include _mdc-core(); -} - -// Mixin that renders all of the core MDC styles. Private mixin included with `mat-core`. -@mixin _mdc-core() { - @include _mdc-strong-focus-indicators-positioning(); -} - -// Mixin that ensures focus indicator host elements are positioned so that the focus indicator -// pseudo element within is positioned relative to the host. Private mixin included within -// `_mat-mdc-core`. -@mixin _mdc-strong-focus-indicators-positioning() { - .mat-mdc-focus-indicator { - position: relative; - } + @include focus-indicators.private-structural-styling('mat'); + @include focus-indicators.private-structural-styling('mat-mdc'); } diff --git a/src/material/core/focus-indicators/_focus-indicators-theme.scss b/src/material/core/focus-indicators/_focus-indicators-theme.scss index 05f87401e4a9..d75a70ddf9cd 100644 --- a/src/material/core/focus-indicators/_focus-indicators-theme.scss +++ b/src/material/core/focus-indicators/_focus-indicators-theme.scss @@ -1,29 +1,26 @@ @use 'sass:map'; @use 'sass:meta'; @use '../theming/theming'; - -@mixin _border-color($color) { - .mat-focus-indicator::before { - border-color: $color; - } -} +@use './focus-indicators'; // stylelint-disable-next-line material/theme-mixin-api @mixin color($config-or-theme-or-color) { @if meta.type-of($config-or-theme-or-color) == 'color' { - @include _border-color($config-or-theme-or-color); + @include focus-indicators.strong-focus-indicators((border-color: $config-or-theme-or-color)); } @else { $config: theming.get-color-config($config-or-theme-or-color); $border-color: theming.get-color-from-palette(map.get($config, primary)); - @include _border-color($border-color); + @include focus-indicators.strong-focus-indicators((border-color: $border-color)); } } // stylelint-disable-next-line material/theme-mixin-api @mixin theme($theme-or-color-config-or-color) { @if meta.type-of($theme-or-color-config-or-color) == 'color' { - @include _border-color($theme-or-color-config-or-color); + @include focus-indicators.strong-focus-indicators(( + border-color: $theme-or-color-config-or-color + )); } @else { $theme: theming.private-legacy-get-theme($theme-or-color-config-or-color); diff --git a/src/material/core/focus-indicators/_focus-indicators.scss b/src/material/core/focus-indicators/_focus-indicators.scss index 0062400610b0..6c3f553eec3e 100644 --- a/src/material/core/focus-indicators/_focus-indicators.scss +++ b/src/material/core/focus-indicators/_focus-indicators.scss @@ -1,95 +1,14 @@ @use 'sass:map'; -@use '../style/layout-common'; +@use '../style/focus-indicators'; -/// Mixin that turns on strong focus indicators. -/// -/// @example -/// .my-app { -/// @include mat-strong-focus-indicators($config); -/// } @mixin strong-focus-indicators($config: ()) { // Default focus indicator config. $default-config: ( - border-style: solid, - border-width: 3px, - border-radius: 4px, + border-color: black, ); // Merge default config with user config. $config: map.merge($default-config, $config); - $border-style: map.get($config, border-style); - $border-width: map.get($config, border-width); - $border-radius: map.get($config, border-radius); - // Base styles for focus indicators. - .mat-focus-indicator::before { - @include layout-common.fill(); - box-sizing: border-box; - pointer-events: none; - border: $border-width $border-style transparent; - border-radius: $border-radius; - - .cdk-high-contrast-active & { - display: 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-focus-indicator.mat-flat-button::before, - .mat-focus-indicator.mat-raised-button::before, - .mat-focus-indicator.mat-fab::before, - .mat-focus-indicator.mat-mini-fab::before, - .mat-focus-indicator.mat-chip::before, - .mat-focus-indicator.mat-sort-header-container::before { - margin: -($border-width + 2px); - } - - .mat-focus-indicator.mat-stroked-button::before, - .mat-focus-indicator.mat-calendar-body-cell-content::before { - margin: -($border-width + 3px); - } - - .mat-focus-indicator.mat-tab-link::before, - .mat-focus-indicator.mat-tab-label::before { - margin: 5px; - } - - // Render the focus indicator on focus. Defining a pseudo element's - // content will cause it to render. - - // Checkboxes, radios, and slide toggles render focus indicators when the - // associated visually-hidden input is focused. - .mat-checkbox-input:focus ~ .mat-focus-indicator::before, - .mat-radio-input:focus ~ .mat-focus-indicator::before, - .mat-slide-toggle-input:focus ~ .mat-slide-toggle-thumb-container .mat-focus-indicator::before, - - // For options, render the focus indicator when the class .mat-active - // is present. - .mat-focus-indicator.mat-option.mat-active::before, - - // For calendar cells, render the focus indicator when the parent cell is - // focused. - .mat-calendar-body-cell:focus .mat-focus-indicator::before, - - // Stepper headers have the focus indicator as a descendant, - // because `::before` is used for other styling. - .mat-step-header:focus .mat-focus-indicator::before, - - // For all other components, render the focus indicator on focus. - .mat-focus-indicator:focus::before { - content: ''; - } -} - -// Mixin that ensures focus indicator host elements are positioned so that the focus indicator -// pseudo element within is positioned relative to the host. Private mixin included within -// `mat-core`. -@mixin private-strong-focus-indicators-positioning() { - .mat-focus-indicator { - position: relative; - } + @include focus-indicators.private-customize-focus-indicators($config, 'mat'); } diff --git a/src/material/core/option/option.scss b/src/material/core/option/option.scss index b1f77c8a55f0..1dc3cab55b7c 100644 --- a/src/material/core/option/option.scss +++ b/src/material/core/option/option.scss @@ -32,21 +32,6 @@ } @include cdk.high-contrast(active, off) { - $high-contrast-border-width: 1px; - - // Add a margin to offset the border that we're adding to active option, in order - // to avoid the options shifting as the user is moving through the list. - margin: 0 $high-contrast-border-width; - - &.mat-active { - // We use a border here, rather than an outline, because the outline will be cut off - // by the `overflow: hidden` on the panel wrapping the options, whereas a border - // will push the element inwards. This could be done using `outline-offset: -1px`, - // however the property isn't supported on IE11. - border: solid $high-contrast-border-width currentColor; - margin: 0; - } - // Fade out the option when it is disabled so that it can be distinguished from the enabled // options. Note that ideally we'd use `color: GreyText` here which is what the browser uses // for disabled buttons, but we can't because Firefox doesn't recognize it. @@ -87,3 +72,9 @@ margin-right: 0; } } + +// For options, render the focus indicator when the class .mat-active +// is present. +.mat-focus-indicator.mat-option.mat-active::before { + content: ''; +} diff --git a/src/material/core/style/_focus-indicators.scss b/src/material/core/style/_focus-indicators.scss new file mode 100644 index 000000000000..278fc5088cd5 --- /dev/null +++ b/src/material/core/style/_focus-indicators.scss @@ -0,0 +1,63 @@ +@use 'sass:map'; +@use '../style/layout-common'; + +// Private sass variables that will be used as reference throughout component stylesheets. +$default-border-width: 3px; +$default-border-style: solid; +$default-border-color: transparent; +$default-border-radius: 4px; + +// Mixin that renders the focus indicator structural styles. +@mixin private-structural-styling($prefix) { + .#{$prefix}-focus-indicator { + position: relative; + + &::before { + @include layout-common.fill(); + box-sizing: border-box; + pointer-events: none; + border: var( + --#{$prefix}-focus-indicator-border-width, + #{$default-border-width} + ) + var( + --#{$prefix}-focus-indicator-border-style, + #{$default-border-style} + ) + var( + --#{$prefix}-focus-indicator-border-color, + #{$default-border-color} + ); + border-radius: var( + --#{$prefix}-focus-indicator-border-radius, + #{$default-border-radius} + ); + } + + // By default, render the focus indicator when the focus indicator host element takes focus. + // Defining a pseudo element's content will cause it to render. + &:focus::before { + content: ''; + } + } +} + +// Mixin that handles null values and defines CSS variables accordingly. +@mixin _create-variable($name, $value) { + @if ($value) { + --#{$name}: #{$value}; + } +} + +// Mixin that dedups CSS variables for the strong-focus-indicators mixin. +@mixin private-customize-focus-indicators($config, $prefix) { + $border-style: map.get($config, border-style); + $border-width: map.get($config, border-width); + $border-radius: map.get($config, border-radius); + $border-color: map.get($config, border-color); + + @include _create-variable('#{$prefix}-focus-indicator-border-style', $border-style); + @include _create-variable('#{$prefix}-focus-indicator-border-width', $border-width); + @include _create-variable('#{$prefix}-focus-indicator-border-radius', $border-radius); + @include _create-variable('#{$prefix}-focus-indicator-border-color', $border-color); +} diff --git a/src/material/datepicker/calendar-body.scss b/src/material/datepicker/calendar-body.scss index 95344b44a2cd..9dce40071d02 100644 --- a/src/material/datepicker/calendar-body.scss +++ b/src/material/datepicker/calendar-body.scss @@ -219,17 +219,6 @@ $calendar-range-end-body-cell-size: outline: dotted 1px; } - .cdk-keyboard-focused .mat-calendar-body-active, - .cdk-program-focused .mat-calendar-body-active { - & > .mat-calendar-body-cell-content { - outline: dotted 2px; - - &.mat-calendar-body-selected { - outline: solid 3px; - } - } - } - // These backgrounds need to be removed, because they'll block the date ranges. .mat-calendar-body-cell::before, .mat-calendar-body-cell::after, diff --git a/src/material/datepicker/calendar.scss b/src/material/datepicker/calendar.scss index 1cfbeb91f25f..38fdc5025a9f 100644 --- a/src/material/datepicker/calendar.scss +++ b/src/material/datepicker/calendar.scss @@ -1,6 +1,7 @@ @use '@angular/cdk'; @use '../core/style/layout-common'; +@use '../core/style/focus-indicators'; $calendar-padding: 8px !default; $calendar-header-divider-width: 1px !default; @@ -20,7 +21,6 @@ $calendar-prev-next-icon-margin: 15.5px; $calendar-prev-icon-transform: translateX(2px) rotate(-45deg); $calendar-next-icon-transform: translateX(-2px) rotate(45deg); - .mat-calendar { display: block; } @@ -126,3 +126,17 @@ $calendar-next-icon-transform: translateX(-2px) rotate(45deg); } } +// For the calendar element, default inset/offset values are necessary to ensure that +// the focus indicator is sufficiently contrastive and renders appropriately. +.mat-focus-indicator.mat-calendar-body-cell-content::before { + $border-width: + var(--mat-focus-indicator-border-width, #{focus-indicators.$default-border-width}); + $offset: calc(#{$border-width} + 3px); + margin: calc(#{$offset} * -1); +} + +// For calendar cells, render the focus indicator when the parent cell is +// focused. +.mat-calendar-body-cell:focus .mat-focus-indicator::before { + content: ''; +} diff --git a/src/material/expansion/expansion-panel-header.scss b/src/material/expansion/expansion-panel-header.scss index f1a925be43c1..cb54ff9c4352 100644 --- a/src/material/expansion/expansion-panel-header.scss +++ b/src/material/expansion/expansion-panel-header.scss @@ -1,8 +1,5 @@ @use '@angular/cdk'; - -@use '../core/style/layout-common'; @use './expansion-variables'; -@use './expansion-mixins'; .mat-expansion-panel-header { display: flex; @@ -81,19 +78,6 @@ } @include cdk.high-contrast(active, off) { - @include expansion-mixins.private-expansion-focus { - &::before { - // These styles are identical to the ones generated for all - // `.mat-focus-indicator` when strong focus indication is enabled. - // We have to repeat them, because strong focus is opt-in. - @include layout-common.fill(); - box-sizing: border-box; - pointer-events: none; - border: 3px solid; - border-radius: 4px; - content: ''; - } - } .mat-expansion-panel-content { border-top: 1px solid; border-top-left-radius: 0; diff --git a/src/material/list/list.scss b/src/material/list/list.scss index 5e408c9461b1..2da7b4fd2fb5 100644 --- a/src/material/list/list.scss +++ b/src/material/list/list.scss @@ -312,14 +312,10 @@ mat-action-list { } @include cdk.high-contrast(active, off) { - .mat-selection-list:focus { - outline-style: dotted; - } - .mat-list-option, .mat-nav-list .mat-list-item, mat-action-list .mat-list-item { - &:hover, &:focus { + &:hover { outline: dotted 1px; z-index: 1; } diff --git a/src/material/menu/menu.scss b/src/material/menu/menu.scss index e50c44af2196..844223c7fd1c 100644 --- a/src/material/menu/menu.scss +++ b/src/material/menu/menu.scss @@ -58,7 +58,7 @@ mat-menu { // from closing, but clicks on child nodes still propagate which is inconsistent (see #16694). // In order to keep the behavior consistent and prevent the menu from closing, we add an overlay // on top of the content that will catch all the clicks while disabled. - &[disabled]::before { + &[disabled]::after { display: block; position: absolute; content: ''; @@ -74,12 +74,6 @@ mat-menu { // We need to move the item 1px down, because Firefox seems to have // an issue rendering the top part of the outline (see #21524). margin-top: $outline-width; - - &.cdk-program-focused, - &.cdk-keyboard-focused, - &-highlighted { - outline: dotted $outline-width; - } } } diff --git a/src/material/radio/radio.scss b/src/material/radio/radio.scss index 0ede9a0d1beb..59b8414df1e5 100644 --- a/src/material/radio/radio.scss +++ b/src/material/radio/radio.scss @@ -210,16 +210,13 @@ $ripple-radius: 20px; z-index: -1; } -@include cdk.high-contrast(active, off) { - .mat-radio-button:not(.mat-radio-disabled) { - &.cdk-keyboard-focused, - &.cdk-program-focused { - .mat-radio-ripple { - outline: solid 3px; - } - } - } +// Radio renders focus indicators when the +// associated visually-hidden input is focused. +.mat-radio-input:focus ~ .mat-focus-indicator::before { + content: ''; +} +@include cdk.high-contrast(active, off) { .mat-radio-disabled { opacity: 0.5; } diff --git a/src/material/slide-toggle/slide-toggle.scss b/src/material/slide-toggle/slide-toggle.scss index 5db6d8450b5a..e420cb134285 100644 --- a/src/material/slide-toggle/slide-toggle.scss +++ b/src/material/slide-toggle/slide-toggle.scss @@ -221,18 +221,16 @@ $bar-track-width: $bar-width - $thumb-size; } } +// Slide-toggles render focus indicators when the +// associated visually-hidden input is focused. +.mat-slide-toggle-input:focus ~ .mat-slide-toggle-thumb-container .mat-focus-indicator::before { + content: ''; +} + // Custom styling to make the slide-toggle usable in high contrast mode. @include cdk.high-contrast(active, off) { .mat-slide-toggle-thumb, .mat-slide-toggle-bar { border: 1px solid; } - - // As a focus indication in high contrast mode, we add a dotted outline to the slide-toggle - // bar. Since the bar element does not have any padding, we need to specify an outline offset - // because otherwise the opaque thumb element will hide the outline. - .mat-slide-toggle.cdk-keyboard-focused .mat-slide-toggle-bar { - outline: 2px dotted; - outline-offset: math.div($height - $bar-height, 2); - } } diff --git a/src/material/sort/BUILD.bazel b/src/material/sort/BUILD.bazel index 57b722595313..1986448be175 100644 --- a/src/material/sort/BUILD.bazel +++ b/src/material/sort/BUILD.bazel @@ -40,6 +40,7 @@ sass_binary( src = "sort-header.scss", deps = [ "//src/cdk:sass_lib", + "//src/material/core:core_scss_lib", ], ) diff --git a/src/material/sort/sort-header.scss b/src/material/sort/sort-header.scss index d0de2114915b..e35e68845c8c 100644 --- a/src/material/sort/sort-header.scss +++ b/src/material/sort/sort-header.scss @@ -1,5 +1,7 @@ @use '@angular/cdk'; +@use '../core/style/focus-indicators'; + $header-arrow-margin: 6px; $header-arrow-container-size: 12px; $header-arrow-stem-size: 10px; @@ -125,3 +127,12 @@ $header-arrow-hint-opacity: 0.38; transform-origin: left; right: 0; } + +// For the sort-header element, default inset/offset values are necessary to ensure that +// the focus indicator is sufficiently contrastive and renders appropriately. +.mat-focus-indicator.mat-sort-header-container::before { + $border-width: + var(--mat-focus-indicator-border-width, #{focus-indicators.$default-border-width}); + $offset: calc(#{$border-width} + 2px); + margin: calc(#{$offset} * -1); +} diff --git a/src/material/stepper/step-header.scss b/src/material/stepper/step-header.scss index 54d675a1c1e7..c8e18ea9b66c 100644 --- a/src/material/stepper/step-header.scss +++ b/src/material/stepper/step-header.scss @@ -13,9 +13,6 @@ @include cdk.high-contrast(active, off) { outline: solid 1px; - &.cdk-keyboard-focused, &.cdk-program-focused { - outline: solid 3px; - } &[aria-selected='true'] { .mat-step-label { @@ -82,3 +79,9 @@ @include layout-common.fill; pointer-events: none; } + +// Stepper headers have the focus indicator as a descendant, +// because `::before` is used for other styling. +.mat-step-header:focus .mat-focus-indicator::before { + content: ''; +} diff --git a/src/material/tabs/_tabs-common.scss b/src/material/tabs/_tabs-common.scss index ace08a99be75..1283611f4ae5 100644 --- a/src/material/tabs/_tabs-common.scss +++ b/src/material/tabs/_tabs-common.scss @@ -27,12 +27,6 @@ $tab-animation-duration: 500ms !default; &:not(.mat-tab-disabled) { opacity: 1; } - - @include cdk.high-contrast(active, off) { - $outline-width: 2px; - outline: dotted $outline-width; - outline-offset: -$outline-width; // Not supported on IE, but looks better everywhere else. - } } &.mat-tab-disabled { diff --git a/src/material/tabs/tab-header.scss b/src/material/tabs/tab-header.scss index 1cb81c98363d..55223237bcd9 100644 --- a/src/material/tabs/tab-header.scss +++ b/src/material/tabs/tab-header.scss @@ -26,6 +26,12 @@ position: relative; } +// For the tab element, default inset/offset values are necessary to ensure that +// the focus indicator is sufficiently contrastive and renders appropriately. +.mat-focus-indicator.mat-tab-label::before { + margin: 5px; +} + @media (variables.$xsmall) { .mat-tab-label { min-width: 72px; diff --git a/src/material/tabs/tab-nav-bar/tab-nav-bar.scss b/src/material/tabs/tab-nav-bar/tab-nav-bar.scss index cc7365ab3264..56d2fb6bcdea 100644 --- a/src/material/tabs/tab-nav-bar/tab-nav-bar.scss +++ b/src/material/tabs/tab-nav-bar/tab-nav-bar.scss @@ -38,6 +38,12 @@ } } +// For the tab element, default inset/offset values are necessary to ensure that +// the focus indicator is sufficiently contrastive and renders appropriately. +.mat-focus-indicator.mat-tab-link::before { + margin: 5px; +} + @media (variables.$xsmall) { .mat-tab-link { min-width: 72px;