Skip to content

Commit b730ba8

Browse files
crisbetommalerba
authored andcommitted
prototype(slide-toggle): create prototype menu based on MDC Web (#15893)
Creates a prototype of `mat-slide-toggle` that uses MDC web.
1 parent fde980c commit b730ba8

18 files changed

+1485
-21
lines changed
Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,75 @@
1-
// TODO: copy tests from existing mat-slide-toggle, update as necessary to fix.
1+
import {browser, element, by, Key} from 'protractor';
2+
import {expectToExist} from '../util/index';
3+
4+
5+
describe('slide-toggle', () => {
6+
const getInput = () => element(by.css('#normal-slide-toggle input'));
7+
const getNormalToggle = () => element(by.css('#normal-slide-toggle'));
8+
9+
beforeEach(async () => await browser.get('mdc-slide-toggle'));
10+
11+
it('should render a slide-toggle', async () => {
12+
await expectToExist('mat-slide-toggle');
13+
});
14+
15+
it('should change the checked state on click', async () => {
16+
const inputEl = getInput();
17+
18+
expect(await inputEl.getAttribute('checked'))
19+
.toBeFalsy('Expect slide-toggle to be unchecked');
20+
21+
await getNormalToggle().click();
22+
23+
expect(await inputEl.getAttribute('checked'))
24+
.toBeTruthy('Expect slide-toggle to be checked');
25+
});
26+
27+
it('should change the checked state on click', async () => {
28+
const inputEl = getInput();
29+
30+
expect(await inputEl.getAttribute('checked'))
31+
.toBeFalsy('Expect slide-toggle to be unchecked');
32+
33+
await getNormalToggle().click();
34+
35+
expect(await inputEl.getAttribute('checked'))
36+
.toBeTruthy('Expect slide-toggle to be checked');
37+
});
38+
39+
it('should not change the checked state on click when disabled', async () => {
40+
const inputEl = getInput();
41+
42+
expect(await inputEl.getAttribute('checked'))
43+
.toBeFalsy('Expect slide-toggle to be unchecked');
44+
45+
await element(by.css('#disabled-slide-toggle')).click();
46+
47+
expect(await inputEl.getAttribute('checked'))
48+
.toBeFalsy('Expect slide-toggle to be unchecked');
49+
});
50+
51+
it('should move the thumb on state change', async () => {
52+
const slideToggleEl = getNormalToggle();
53+
const thumbEl = element(by.css('#normal-slide-toggle .mdc-switch__thumb-underlay'));
54+
const previousPosition = await thumbEl.getLocation();
55+
56+
await slideToggleEl.click();
57+
58+
const position = await thumbEl.getLocation();
59+
60+
expect(position.x).not.toBe(previousPosition.x);
61+
});
62+
63+
it('should toggle the slide-toggle on space key', async () => {
64+
const inputEl = getInput();
65+
66+
expect(await inputEl.getAttribute('checked'))
67+
.toBeFalsy('Expect slide-toggle to be unchecked');
68+
69+
await inputEl.sendKeys(Key.SPACE);
70+
71+
expect(await inputEl.getAttribute('checked'))
72+
.toBeTruthy('Expect slide-toggle to be checked');
73+
});
74+
75+
});

src/dev-app/mdc-slide-toggle/mdc-slide-toggle-demo-module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@
99
import {NgModule} from '@angular/core';
1010
import {MatSlideToggleModule} from '@angular/material-experimental/mdc-slide-toggle';
1111
import {RouterModule} from '@angular/router';
12+
import {FormsModule} from '@angular/forms';
1213
import {MdcSlideToggleDemo} from './mdc-slide-toggle-demo';
1314

1415
@NgModule({
1516
imports: [
1617
MatSlideToggleModule,
1718
RouterModule.forChild([{path: '', component: MdcSlideToggleDemo}]),
19+
FormsModule,
1820
],
1921
declarations: [MdcSlideToggleDemo],
2022
})
Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,29 @@
1-
<!-- TODO: copy in demo template from existing mat-slide-toggle demo. -->
2-
Not yet implemented.
1+
<div class="demo-slide-toggle">
2+
3+
<mat-slide-toggle color="primary" [(ngModel)]="firstToggle">
4+
Default Slide Toggle
5+
</mat-slide-toggle>
6+
7+
<mat-slide-toggle [(ngModel)]="firstToggle" disabled>
8+
Disabled Slide Toggle
9+
</mat-slide-toggle>
10+
11+
<mat-slide-toggle [disabled]="firstToggle">
12+
Disable Bound
13+
</mat-slide-toggle>
14+
15+
<p>Example where the slide toggle is required inside of a form.</p>
16+
17+
<form #form="ngForm" (ngSubmit)="onFormSubmit()" ngNativeValidate>
18+
19+
<mat-slide-toggle name="slideToggle" required ngModel>
20+
Slide Toggle
21+
</mat-slide-toggle>
22+
23+
<p>
24+
<button mat-raised-button type="submit">Submit Form</button>
25+
</p>
26+
27+
</form>
28+
29+
</div>
Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,9 @@
1-
// TODO: copy in demo styles from existing mat-slide-toggle demo.
1+
.demo-slide-toggle {
2+
display: flex;
3+
flex-direction: column;
4+
align-items: flex-start;
5+
6+
mat-slide-toggle {
7+
margin: 6px 0;
8+
}
9+
}

src/dev-app/mdc-slide-toggle/mdc-slide-toggle-demo.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,9 @@ import {Component} from '@angular/core';
1515
styleUrls: ['mdc-slide-toggle-demo.css'],
1616
})
1717
export class MdcSlideToggleDemo {
18+
firstToggle: boolean;
19+
20+
onFormSubmit() {
21+
alert(`You submitted the form.`);
22+
}
1823
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
<!-- TODO: copy implementation from existing mat-slide-toggle e2e page. -->
1+
<mat-slide-toggle id="normal-slide-toggle">Slide Toggle</mat-slide-toggle>
2+
<mat-slide-toggle id="disabled-slide-toggle" disabled>Disabled Slide Toggle</mat-slide-toggle>

src/e2e-app/mdc-slide-toggle/mdc-slide-toggle-e2e.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,4 @@ import {Component} from '@angular/core';
1313
selector: 'mdc-slide-toggle-e2e',
1414
templateUrl: 'mdc-slide-toggle-e2e.html',
1515
})
16-
export class MdcSlideToggleE2e {
17-
// TODO: copy implementation from existing mat-slide-toggle e2e page.
18-
}
16+
export class MdcSlideToggleE2e {}

src/material-experimental/mdc-slide-toggle/BUILD.bazel

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ ng_module(
1010
module_name = "@angular/material-experimental/mdc-slide-toggle",
1111
assets = [":slide_toggle_scss"] + glob(["**/*.html"]),
1212
deps = [
13+
"@npm//@angular/common",
14+
"@npm//@angular/core",
15+
"@npm//@angular/forms",
16+
"@npm//@angular/animations",
1317
"@npm//material-components-web",
1418
] + CDK_TARGETS + MATERIAL_TARGETS,
1519
)
@@ -20,10 +24,18 @@ sass_library(
2024
deps = [
2125
"//src/material/core:core_scss_lib",
2226
"//src/material-experimental/mdc-helpers:mdc_helpers_scss_lib",
27+
"//src/material-experimental/mdc-helpers:mdc_scss_deps_lib",
2328
],
2429
)
2530

2631
sass_binary(
2732
name = "slide_toggle_scss",
2833
src = "slide-toggle.scss",
34+
include_paths = [
35+
"external/npm/node_modules",
36+
],
37+
deps = [
38+
":slide_toggle_scss_lib",
39+
"//src/material-experimental/mdc-helpers:mdc_scss_deps_lib",
40+
]
2941
)
Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,93 @@
1-
This is a placeholder for the MDC-based implementation of slide toggle.
1+
This is prototype of an alternate version of `<mat-slide-toggle>` built on top of
2+
[MDC Web](https://github.com/material-components/material-components-web). It demonstrates how
3+
Angular Material could use MDC Web under the hood while still exposing the same API Angular users as
4+
the existing `<mat-slide-toggle>`. This component is experimental and should not be used in production.
5+
6+
## How to use
7+
Assuming your application is already up and running using Angular Material, you can add this
8+
component by following these steps:
9+
10+
1. Install Angular Material Experimental & MDC WEB:
11+
12+
```bash
13+
npm i material-components-web @angular/material-experimental
14+
```
15+
16+
2. In your `angular.json`, make sure `node_modules/` is listed as a Sass include path. This is
17+
needed for the Sass compiler to be able to find the MDC Web Sass files.
18+
19+
```json
20+
...
21+
"styles": [
22+
"src/styles.scss"
23+
],
24+
"stylePreprocessorOptions": {
25+
"includePaths": [
26+
"node_modules/"
27+
]
28+
},
29+
...
30+
```
31+
32+
3. Import the experimental `MatSlideToggleModule` and add it to the module that declares your
33+
component:
34+
35+
```ts
36+
import {MatSlideToggleModule} from '@angular/material-experimental/mdc-slide-toggle';
37+
38+
@NgModule({
39+
declarations: [MyComponent],
40+
imports: [MatSlideToggleModule],
41+
})
42+
export class MyModule {}
43+
```
44+
45+
4. Add use `<mat-slide-toggle>` in your component's template, just like you would the normal
46+
`<mat-slide-toggle>`:
47+
48+
```html
49+
<mat-slide-toggle [checked]="isChecked">Toggle me</mat-slide-toggle>
50+
```
51+
52+
5. Add the theme and typography mixins to your Sass. (There is currently no pre-built CSS option for
53+
the experimental `<mat-slide-toggle>`):
54+
55+
```scss
56+
@import '~@angular/material/theming';
57+
@import '~@angular/material-experimental/mdc-slide-toggle';
58+
59+
$my-primary: mat-palette($mat-indigo);
60+
$my-accent: mat-palette($mat-pink, A200, A100, A400);
61+
$my-theme: mat-light-theme($my-primary, $my-accent);
62+
63+
@include mat-slide-toggle-theme-mdc($my-theme);
64+
@include mat-slide-toggle-typography-mdc();
65+
```
66+
67+
## API differences
68+
The experimental slide toggle API closely matches the
69+
[API of the standard slide toggle](https://material.angular.io/components/slide-toggle/api).
70+
`@angular/material-experimental/mdc-slide-toggle` exports symbols with the same name and public
71+
interface as all of the symbols found under `@angular/material/slide-toggle`, except for the
72+
following differences:
73+
74+
* The MDC-based `mat-slide-toggle` drops the dependency on Hammer.js and as a result doesn't support
75+
dragging gestures.
76+
* As a result of dragging gestures not being supported, the `dragChange` event won't emit.
77+
78+
## Replacing the standard slide toggle in an existing app
79+
Because the experimental API mirrors the API for the standard slide toggle, it can easily be swapped
80+
in by just changing the import paths. There is currently no schematic for this, but you can run the
81+
following string replace across your TypeScript files:
82+
83+
```bash
84+
grep -lr --include="*.ts" --exclude-dir="node_modules" \
85+
--exclude="*.d.ts" "['\"]@angular/material/slide-toggle['\"]" | xargs sed -i \
86+
"s/['\"]@angular\/material\/slide-toggle['\"]/'@angular\/material-experimental\/mdc-slide-toggle'/g"
87+
```
88+
89+
CSS styles and tests that depend on implementation details of mat-slide-toggle (such as getting
90+
elements from the template by class name) will need to be manually updated.
91+
92+
There are some small visual differences between this slide and the standard mat-slide. This
93+
slide has a slightly larger ripple and different spacing between the label and the toggle.
Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,70 @@
1+
@import '@material/switch/mixins';
2+
@import '@material/switch/variables';
3+
@import '@material/form-field/mixins';
4+
@import '@material/ripple/variables';
5+
@import '@material/theme/functions';
16
@import '../mdc-helpers/mdc-helpers';
27

38
@mixin mat-slide-toggle-theme-mdc($theme) {
9+
$primary: mat-color(map-get($theme, primary));
10+
$accent: mat-color(map-get($theme, accent));
11+
$warn: mat-color(map-get($theme, warn));
12+
13+
// Save original values of MDC global variables. We need to save these so we can restore the
14+
// variables to their original values and prevent unintended side effects from using this mixin.
15+
$orig-mdc-switch-baseline-theme-color: $mdc-switch-baseline-theme-color;
16+
417
@include mat-using-mdc-theme($theme) {
5-
// TODO: MDC theme styles here.
18+
$mdc-switch-baseline-theme-color: primary !global;
19+
20+
@include mdc-switch-without-ripple($query: $mat-theme-styles-query);
21+
@include mdc-form-field-core-styles($query: $mat-theme-styles-query);
22+
23+
// MDC's switch doesn't support a `color` property. We add support
24+
// for it by adding a CSS class for accent and warn style.
25+
.mat-mdc-slide-toggle {
26+
.mdc-switch__thumb-underlay::before, .mat-ripple-element {
27+
background: $mdc-switch-toggled-off-ripple-color;
28+
}
29+
30+
&.mat-accent {
31+
$mdc-switch-baseline-theme-color: secondary !global;
32+
@include mdc-switch-without-ripple($query: $mat-theme-styles-query);
33+
}
34+
35+
&.mat-warn {
36+
$mdc-switch-baseline-theme-color: error !global;
37+
@include mdc-switch-without-ripple($query: $mat-theme-styles-query);
38+
}
39+
}
40+
41+
// The ripple color matches the palette only when it's checked.
42+
.mat-mdc-slide-toggle-checked {
43+
.mdc-switch__thumb-underlay::before, .mat-ripple-element {
44+
background: $primary;
45+
}
46+
47+
&.mat-accent {
48+
.mdc-switch__thumb-underlay::before, .mat-ripple-element {
49+
background: $accent;
50+
}
51+
}
52+
53+
&.mat-warn {
54+
.mdc-switch__thumb-underlay::before, .mat-ripple-element {
55+
background: $warn;
56+
}
57+
}
58+
}
659
}
60+
61+
// Restore original values of MDC global variables.
62+
$mdc-switch-baseline-theme-color: $orig-mdc-switch-baseline-theme-color !global;
763
}
864

965
@mixin mat-slide-toggle-typography-mdc($config) {
1066
@include mat-using-mdc-typography($config) {
11-
// TODO: MDC typography styles here.
67+
@include mdc-switch-without-ripple($query: $mat-typography-styles-query);
68+
@include mdc-form-field-core-styles($query: $mat-typography-styles-query);
1269
}
1370
}

0 commit comments

Comments
 (0)