diff --git a/scripts/check-mdc-tests-config.ts b/scripts/check-mdc-tests-config.ts
index fefb4efe8b83..fb702020cea8 100644
--- a/scripts/check-mdc-tests-config.ts
+++ b/scripts/check-mdc-tests-config.ts
@@ -175,6 +175,9 @@ export const config = {
'should re-add margin if label is added asynchronously',
'should properly update margin if label content is projected',
+ // The MDC slide toggle uses a `button` which isn't able to block form submission.
+ 'should prevent the form from submit when being required',
+
// TODO: the focus origin functionality has to be implemeted for the MDC slide toggle.
'should not change focus origin if origin not specified'
],
diff --git a/src/e2e-app/protractor.conf.js b/src/e2e-app/protractor.conf.js
index 17317b8708c3..7b4e269f53d5 100644
--- a/src/e2e-app/protractor.conf.js
+++ b/src/e2e-app/protractor.conf.js
@@ -24,7 +24,10 @@ exports.config = {
{id: 'region', enabled: false},
// Don't require at least one `
` since we don't have any content.
- {id: 'page-has-heading-one', enabled: false}
+ {id: 'page-has-heading-one', enabled: false},
+
+ // Axe incorrectly picks up that `aria-required` is not allowed on the MDC slide toggle.
+ {id: 'aria-allowed-attr', selector: '*:not(.mdc-switch)'}
]
}
],
diff --git a/src/material-experimental/mdc-helpers/_focus-indicators.scss b/src/material-experimental/mdc-helpers/_focus-indicators.scss
index 20c39f28d3c4..4189d3486265 100644
--- a/src/material-experimental/mdc-helpers/_focus-indicators.scss
+++ b/src/material-experimental/mdc-helpers/_focus-indicators.scss
@@ -60,7 +60,8 @@
// 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-slider .mat-mdc-focus-indicator::before,
+ .mat-mdc-slide-toggle .mat-mdc-focus-indicator::before {
border-radius: 50%;
}
diff --git a/src/material-experimental/mdc-slide-toggle/_slide-toggle-theme.scss b/src/material-experimental/mdc-slide-toggle/_slide-toggle-theme.scss
index fbb9e67bfe59..7b4a9eb3daff 100644
--- a/src/material-experimental/mdc-slide-toggle/_slide-toggle-theme.scss
+++ b/src/material-experimental/mdc-slide-toggle/_slide-toggle-theme.scss
@@ -1,103 +1,121 @@
-@use '@material/theme/theme-color' as mdc-theme-color;
-@use '@material/switch/deprecated' as mdc-switch with ($deprecated-suffix: '');
-@use '@material/form-field' as mdc-form-field;
@use 'sass:map';
+@use 'sass:color';
+@use '@material/switch/switch-theme' as mdc-switch-theme;
+@use '@material/theme/color-palette' as mdc-color-palette;
+@use '@material/form-field' as mdc-form-field;
@use '../mdc-helpers/mdc-helpers';
@use '../../material/core/typography/typography';
@use '../../material/core/theming/theming';
+@use '../../material/core/theming/palette';
+
+// Generates all color mapping for the properties that only change based on the theme.
+@function _get-theme-base-map($is-dark) {
+ $on-surface: if($is-dark, mdc-color-palette.$grey-100, mdc-color-palette.$grey-800);
+ $hairline: if($is-dark, mdc-color-palette.$grey-500, mdc-color-palette.$grey-300);
+ $on-surface-variant: if($is-dark, mdc-color-palette.$grey-200, mdc-color-palette.$grey-700);
+ $on-surface-state-content: if($is-dark, mdc-color-palette.$grey-50, mdc-color-palette.$grey-900);
+ $disabled-handle-color: mdc-color-palette.$grey-800;
+ $selected-icon-color: mdc-color-palette.$grey-100;
+ $icon-color: if($is-dark, mdc-color-palette.$grey-800, mdc-color-palette.$grey-100);
+
+ @return (
+ disabled-selected-handle-color: $disabled-handle-color,
+ disabled-unselected-handle-color: $disabled-handle-color,
+
+ disabled-selected-track-color: $on-surface,
+ disabled-unselected-track-color: $on-surface,
+ unselected-focus-state-layer-color: $on-surface,
+ unselected-pressed-state-layer-color: $on-surface,
+ unselected-hover-state-layer-color: $on-surface,
+
+ unselected-focus-track-color: $hairline,
+ unselected-hover-track-color: $hairline,
+ unselected-pressed-track-color: $hairline,
+ unselected-track-color: $hairline,
+
+ unselected-focus-handle-color: $on-surface-state-content,
+ unselected-hover-handle-color: $on-surface-state-content,
+ unselected-pressed-handle-color: $on-surface-state-content,
+
+ handle-surface-color: surface,
+ unselected-handle-color: $on-surface-variant,
+
+ selected-icon-color: $selected-icon-color,
+ disabled-selected-icon-color: $icon-color,
+ disabled-unselected-icon-color: $icon-color,
+ unselected-icon-color: $icon-color,
+ );
+}
+// Generates the mapping for the properties that change based on the slide toggle color.
+@function _get-theme-color-map($color-palette) {
+ $state-content: color.scale($color-palette, $blackness: 50%);
+ $inverse: color.scale($color-palette, $lightness: 75%);
+
+ @return (
+ selected-focus-state-layer-color: $color-palette,
+ selected-handle-color: $color-palette,
+ selected-hover-state-layer-color: $color-palette,
+ selected-pressed-state-layer-color: $color-palette,
+
+ selected-focus-handle-color: $state-content,
+ selected-hover-handle-color: $state-content,
+ selected-pressed-handle-color: $state-content,
+
+ selected-focus-track-color: $inverse,
+ selected-hover-track-color: $inverse,
+ selected-pressed-track-color: $inverse,
+ selected-track-color: $inverse,
+ );
+}
@mixin color($config-or-theme) {
$config: theming.get-color-config($config-or-theme);
$primary: theming.get-color-from-palette(map.get($config, primary));
$accent: theming.get-color-from-palette(map.get($config, accent));
$warn: theming.get-color-from-palette(map.get($config, warn));
-
- // Save original values of MDC global variables. We need to save these so we can restore the
- // variables to their original values and prevent unintended side effects from using this mixin.
- $orig-baseline-theme-color: mdc-switch.$baseline-theme-color;
- $orig-toggled-off-thumb-color: mdc-switch.$toggled-off-thumb-color;
- $orig-toggled-off-track-color: mdc-switch.$toggled-off-track-color;
- $orig-disabled-thumb-color: mdc-switch.$disabled-thumb-color;
- $orig-disabled-track-color: mdc-switch.$disabled-track-color;
+ $is-dark: map.get($config, is-dark);
@include mdc-helpers.mat-using-mdc-theme($config) {
- mdc-switch.$baseline-theme-color: primary;
- mdc-switch.$toggled-off-thumb-color: mdc-theme-color.prop-value(surface);
- mdc-switch.$toggled-off-track-color: mdc-theme-color.prop-value(on-surface);
- mdc-switch.$disabled-thumb-color: mdc-theme-color.prop-value(surface);
- mdc-switch.$disabled-track-color: mdc-theme-color.prop-value(on-surface);
-
// MDC's switch doesn't support a `color` property. We add support
// for it by adding a CSS class for accent and warn style.
.mat-mdc-slide-toggle {
@include mdc-form-field.core-styles($query: mdc-helpers.$mat-theme-styles-query);
-
- .mdc-switch__thumb-underlay::after, .mat-ripple-element {
- background: mdc-switch.$toggled-off-ripple-color;
- }
+ @include mdc-switch-theme.theme(_get-theme-base-map($is-dark));
&.mat-primary {
- @include mdc-switch.without-ripple($query: mdc-helpers.$mat-theme-styles-query);
+ @include mdc-switch-theme.theme(_get-theme-color-map($primary));
}
&.mat-accent {
- mdc-switch.$baseline-theme-color: secondary;
- @include mdc-switch.without-ripple($query: mdc-helpers.$mat-theme-styles-query);
+ @include mdc-switch-theme.theme(_get-theme-color-map($accent));
}
&.mat-warn {
- mdc-switch.$baseline-theme-color: error;
- @include mdc-switch.without-ripple($query: mdc-helpers.$mat-theme-styles-query);
- }
- }
-
- // The ripple color matches the palette only when it's checked.
- .mat-mdc-slide-toggle-checked {
- .mdc-switch__thumb-underlay::after, .mat-ripple-element {
- background: $primary;
- }
-
- &.mat-accent {
- .mdc-switch__thumb-underlay::after, .mat-ripple-element {
- background: $accent;
- }
- }
-
- &.mat-warn {
- .mdc-switch__thumb-underlay::after, .mat-ripple-element {
- background: $warn;
- }
+ @include mdc-switch-theme.theme(_get-theme-color-map($warn));
}
}
}
-
- // Restore original values of MDC global variables.
- mdc-switch.$baseline-theme-color: $orig-baseline-theme-color;
- mdc-switch.$toggled-off-thumb-color: $orig-toggled-off-thumb-color;
- mdc-switch.$toggled-off-track-color: $orig-toggled-off-track-color;
- mdc-switch.$disabled-thumb-color: $orig-disabled-thumb-color;
- mdc-switch.$disabled-track-color: $orig-disabled-track-color;
}
@mixin typography($config-or-theme) {
$config: typography.private-typography-to-2018-config(
theming.get-typography-config($config-or-theme));
@include mdc-helpers.mat-using-mdc-typography($config) {
- @include mdc-switch.without-ripple($query: mdc-helpers.$mat-typography-styles-query);
@include mdc-form-field.core-styles($query: mdc-helpers.$mat-typography-styles-query);
}
}
@mixin density($config-or-theme) {
$density-scale: theming.get-density-config($config-or-theme);
- .mat-mdc-slide-toggle .mdc-switch {
- @include mdc-switch.density($density-scale, $query: mdc-helpers.$mat-base-styles-query);
+ .mat-mdc-slide-toggle {
+ @include mdc-switch-theme.theme(mdc-switch-theme.density($density-scale));
}
}
@mixin theme($theme-or-color-config) {
$theme: theming.private-legacy-get-theme($theme-or-color-config);
+
@include theming.private-check-duplicate-theme-styles($theme, 'mat-mdc-slide-toggle') {
$color: theming.get-color-config($theme);
$density: theming.get-density-config($theme);
diff --git a/src/material-experimental/mdc-slide-toggle/slide-toggle.e2e.spec.ts b/src/material-experimental/mdc-slide-toggle/slide-toggle.e2e.spec.ts
index bba931a5d706..0979f9429846 100644
--- a/src/material-experimental/mdc-slide-toggle/slide-toggle.e2e.spec.ts
+++ b/src/material-experimental/mdc-slide-toggle/slide-toggle.e2e.spec.ts
@@ -3,7 +3,7 @@ import {expectToExist} from '../../cdk/testing/private/e2e';
describe('MDC-based slide-toggle', () => {
- const getInput = () => element(by.css('#normal-slide-toggle input'));
+ const getButton = () => element(by.css('#normal-slide-toggle button'));
const getNormalToggle = () => element(by.css('#normal-slide-toggle'));
beforeEach(async () => await browser.get('mdc-slide-toggle'));
@@ -13,44 +13,44 @@ describe('MDC-based slide-toggle', () => {
});
it('should change the checked state on click', async () => {
- const inputEl = getInput();
+ const buttonEl = getButton();
- expect(await inputEl.getAttribute('checked'))
- .toBeFalsy('Expect slide-toggle to be unchecked');
+ expect(await buttonEl.getAttribute('aria-checked'))
+ .toBe('false', 'Expect slide-toggle to be unchecked');
await getNormalToggle().click();
- expect(await inputEl.getAttribute('checked'))
- .toBeTruthy('Expect slide-toggle to be checked');
+ expect(await buttonEl.getAttribute('aria-checked'))
+ .toBe('true', 'Expect slide-toggle to be checked');
});
it('should change the checked state on click', async () => {
- const inputEl = getInput();
+ const buttonEl = getButton();
- expect(await inputEl.getAttribute('checked'))
- .toBeFalsy('Expect slide-toggle to be unchecked');
+ expect(await buttonEl.getAttribute('aria-checked'))
+ .toBe('false', 'Expect slide-toggle to be unchecked');
await getNormalToggle().click();
- expect(await inputEl.getAttribute('checked'))
- .toBeTruthy('Expect slide-toggle to be checked');
+ expect(await buttonEl.getAttribute('aria-checked'))
+ .toBe('true', 'Expect slide-toggle to be checked');
});
it('should not change the checked state on click when disabled', async () => {
- const inputEl = getInput();
+ const buttonEl = getButton();
- expect(await inputEl.getAttribute('checked'))
- .toBeFalsy('Expect slide-toggle to be unchecked');
+ expect(await buttonEl.getAttribute('aria-checked'))
+ .toBe('false', 'Expect slide-toggle to be unchecked');
await element(by.css('#disabled-slide-toggle')).click();
- expect(await inputEl.getAttribute('checked'))
- .toBeFalsy('Expect slide-toggle to be unchecked');
+ expect(await buttonEl.getAttribute('aria-checked'))
+ .toBe('false', 'Expect slide-toggle to be unchecked');
});
it('should move the thumb on state change', async () => {
const slideToggleEl = getNormalToggle();
- const thumbEl = element(by.css('#normal-slide-toggle .mdc-switch__thumb-underlay'));
+ const thumbEl = element(by.css('#normal-slide-toggle .mdc-switch__handle'));
const previousPosition = await thumbEl.getLocation();
await slideToggleEl.click();
@@ -61,15 +61,15 @@ describe('MDC-based slide-toggle', () => {
});
it('should toggle the slide-toggle on space key', async () => {
- const inputEl = getInput();
+ const buttonEl = getButton();
- expect(await inputEl.getAttribute('checked'))
- .toBeFalsy('Expect slide-toggle to be unchecked');
+ expect(await buttonEl.getAttribute('aria-checked'))
+ .toBe('false', 'Expect slide-toggle to be unchecked');
- await inputEl.sendKeys(Key.SPACE);
+ await buttonEl.sendKeys(Key.SPACE);
- expect(await inputEl.getAttribute('checked'))
- .toBeTruthy('Expect slide-toggle to be checked');
+ expect(await buttonEl.getAttribute('aria-checked'))
+ .toBe('true', 'Expect slide-toggle to be checked');
});
});
diff --git a/src/material-experimental/mdc-slide-toggle/slide-toggle.html b/src/material-experimental/mdc-slide-toggle/slide-toggle.html
index 2e3a95febad8..2c92c287e977 100644
--- a/src/material-experimental/mdc-slide-toggle/slide-toggle.html
+++ b/src/material-experimental/mdc-slide-toggle/slide-toggle.html
@@ -1,33 +1,51 @@