diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 7b7715893236..862dd89182e3 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -177,6 +177,7 @@ /src/dev-app/mdc-list/** @mmalerba /src/dev-app/mdc-menu/** @crisbeto /src/dev-app/mdc-progress-bar/** @crisbeto +/src/dev-app/mdc-progress-spinner/** @annieyw @mmalerba /src/dev-app/mdc-radio/** @mmalerba /src/dev-app/mdc-snack-bar/** @andrewseguin /src/dev-app/mdc-sidenav/** @crisbeto @@ -236,6 +237,7 @@ /src/e2e-app/mdc-input/** @devversion /src/e2e-app/mdc-menu/** @crisbeto /src/e2e-app/mdc-progress-bar/** @crisbeto +/src/e2e-app/mdc-progress-spinner/** @annieyw @mmalerba /src/e2e-app/mdc-radio/** @mmalerba /src/e2e-app/mdc-slider/** @andrewseguin /src/e2e-app/mdc-slide-toggle/** @crisbeto diff --git a/rollup-globals.bzl b/rollup-globals.bzl index 71cd302e5584..ca76ac87d5be 100644 --- a/rollup-globals.bzl +++ b/rollup-globals.bzl @@ -41,6 +41,7 @@ ROLLUP_GLOBALS = { "@material/auto-init": "mdc.autoInit", "@material/base": "mdc.base", "@material/checkbox": "mdc.checkbox", + "@material/circular-progress": "mdc.circularProgress", "@material/chips": "mdc.chips", "@material/dialog": "mdc.dialog", "@material/dom": "mdc.dom", diff --git a/src/dev-app/BUILD.bazel b/src/dev-app/BUILD.bazel index 31b817794910..52c3217bb4a5 100644 --- a/src/dev-app/BUILD.bazel +++ b/src/dev-app/BUILD.bazel @@ -54,6 +54,7 @@ ng_module( "//src/dev-app/mdc-list", "//src/dev-app/mdc-menu", "//src/dev-app/mdc-progress-bar", + "//src/dev-app/mdc-progress-spinner", "//src/dev-app/mdc-radio", "//src/dev-app/mdc-sidenav", "//src/dev-app/mdc-slide-toggle", @@ -130,6 +131,7 @@ filegroup( "@npm//:node_modules/@material/base/dist/mdc.base.js", "@npm//:node_modules/@material/checkbox/dist/mdc.checkbox.js", "@npm//:node_modules/@material/chips/dist/mdc.chips.js", + "@npm//:node_modules/@material/circular-progress/dist/mdc.circularProgress.js", "@npm//:node_modules/@material/data-table/dist/mdc.dataTable.js", "@npm//:node_modules/@material/dialog/dist/mdc.dialog.js", "@npm//:node_modules/@material/dom/dist/mdc.dom.js", diff --git a/src/dev-app/dev-app/dev-app-layout.ts b/src/dev-app/dev-app/dev-app-layout.ts index daf5413d0b9a..7c96d56ec0ef 100644 --- a/src/dev-app/dev-app/dev-app-layout.ts +++ b/src/dev-app/dev-app/dev-app-layout.ts @@ -83,6 +83,7 @@ export class DevAppLayout { {name: 'MDC Menu', route: '/mdc-menu'}, {name: 'MDC Radio', route: '/mdc-radio'}, {name: 'MDC Progress Bar', route: '/mdc-progress-bar'}, + {name: 'MDC Progress Spinner', route: '/mdc-progress-spinner'}, {name: 'MDC Tabs', route: '/mdc-tabs'}, {name: 'MDC Sidenav', route: '/mdc-sidenav'}, {name: 'MDC Slide Toggle', route: '/mdc-slide-toggle'}, diff --git a/src/dev-app/dev-app/routes.ts b/src/dev-app/dev-app/routes.ts index 8d0d05679590..89f1d59a161b 100644 --- a/src/dev-app/dev-app/routes.ts +++ b/src/dev-app/dev-app/routes.ts @@ -83,6 +83,11 @@ export const DEV_APP_ROUTES: Routes = [ {path: 'mdc-input', loadChildren: 'mdc-input/mdc-input-demo-module#MdcInputDemoModule'}, {path: 'mdc-list', loadChildren: 'mdc-list/mdc-list-demo-module#MdcListDemoModule'}, {path: 'mdc-menu', loadChildren: 'mdc-menu/mdc-menu-demo-module#MdcMenuDemoModule'}, + { + path: 'mdc-progress-spinner', + loadChildren: + 'mdc-progress-spinner/mdc-progress-spinner-demo-module#MdcProgressSpinnerDemoModule' + }, {path: 'mdc-radio', loadChildren: 'mdc-radio/mdc-radio-demo-module#MdcRadioDemoModule'}, {path: 'mdc-sidenav', loadChildren: 'mdc-sidenav/mdc-sidenav-demo-module#MdcSidenavDemoModule'}, { diff --git a/src/dev-app/mdc-progress-spinner/BUILD.bazel b/src/dev-app/mdc-progress-spinner/BUILD.bazel new file mode 100644 index 000000000000..851ca3cd6941 --- /dev/null +++ b/src/dev-app/mdc-progress-spinner/BUILD.bazel @@ -0,0 +1,25 @@ +load("//tools:defaults.bzl", "ng_module", "sass_binary") + +package(default_visibility = ["//visibility:public"]) + +ng_module( + name = "mdc-progress-spinner", + srcs = glob(["**/*.ts"]), + assets = [ + "mdc-progress-spinner-demo.html", + ":mdc_progress_spinner_demo_scss", + ], + deps = [ + "//src/material-experimental/mdc-progress-spinner", + "//src/material/button", + "//src/material/button-toggle", + "//src/material/checkbox", + "@npm//@angular/forms", + "@npm//@angular/router", + ], +) + +sass_binary( + name = "mdc_progress_spinner_demo_scss", + src = "mdc-progress-spinner-demo.scss", +) diff --git a/src/dev-app/mdc-progress-spinner/mdc-progress-spinner-demo-module.ts b/src/dev-app/mdc-progress-spinner/mdc-progress-spinner-demo-module.ts new file mode 100644 index 000000000000..351381e161a0 --- /dev/null +++ b/src/dev-app/mdc-progress-spinner/mdc-progress-spinner-demo-module.ts @@ -0,0 +1,29 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {NgModule} from '@angular/core'; +import {MatProgressSpinnerModule} from '@angular/material-experimental/mdc-progress-spinner'; +import {RouterModule} from '@angular/router'; +import {MdcProgressSpinnerDemo} from './mdc-progress-spinner-demo'; +import {MatButtonModule} from '@angular/material/button'; +import {MatCheckboxModule} from '@angular/material/checkbox'; +import {MatButtonToggleModule} from '@angular/material/button-toggle'; +import {FormsModule} from '@angular/forms'; + +@NgModule({ + imports: [ + MatButtonModule, + MatCheckboxModule, + MatButtonToggleModule, + FormsModule, + MatProgressSpinnerModule, + RouterModule.forChild([{path: '', component: MdcProgressSpinnerDemo}]), + ], + declarations: [MdcProgressSpinnerDemo], +}) +export class MdcProgressSpinnerDemoModule {} diff --git a/src/dev-app/mdc-progress-spinner/mdc-progress-spinner-demo.html b/src/dev-app/mdc-progress-spinner/mdc-progress-spinner-demo.html new file mode 100644 index 000000000000..9d2f05fc1f53 --- /dev/null +++ b/src/dev-app/mdc-progress-spinner/mdc-progress-spinner-demo.html @@ -0,0 +1,32 @@ +

Determinate

+ +
+

Value: {{progressValue}}

+ + + Is determinate +
+ +
+ + + +
+ +

Indeterminate

+ + + Primary Color + Accent Color + Warn Color + + +
+ + + +
+ diff --git a/src/dev-app/mdc-progress-spinner/mdc-progress-spinner-demo.scss b/src/dev-app/mdc-progress-spinner/mdc-progress-spinner-demo.scss new file mode 100644 index 000000000000..d0dec7c4d5ab --- /dev/null +++ b/src/dev-app/mdc-progress-spinner/mdc-progress-spinner-demo.scss @@ -0,0 +1,12 @@ +.demo-progress-spinner { + width: 100%; + + .mat-mdc-progress-spinner, + .mat-mdc-spinner { + display: inline-block; + } +} + +.demo-progress-spinner-controls { + margin: 10px 0; +} diff --git a/src/dev-app/mdc-progress-spinner/mdc-progress-spinner-demo.ts b/src/dev-app/mdc-progress-spinner/mdc-progress-spinner-demo.ts new file mode 100644 index 000000000000..e5177f137285 --- /dev/null +++ b/src/dev-app/mdc-progress-spinner/mdc-progress-spinner-demo.ts @@ -0,0 +1,26 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {Component} from '@angular/core'; +import {ThemePalette} from '@angular/material/core'; + + +@Component({ + selector: 'mdc-progress-spinner-demo', + templateUrl: 'mdc-progress-spinner-demo.html', + styleUrls: ['mdc-progress-spinner-demo.css'], +}) +export class MdcProgressSpinnerDemo { + progressValue = 60; + color: ThemePalette = 'primary'; + isDeterminate = true; + + step(val: number) { + this.progressValue = Math.max(0, Math.min(100, val + this.progressValue)); + } +} diff --git a/src/e2e-app/BUILD.bazel b/src/e2e-app/BUILD.bazel index 454af893fda4..8b6285cb62cf 100644 --- a/src/e2e-app/BUILD.bazel +++ b/src/e2e-app/BUILD.bazel @@ -40,6 +40,7 @@ ng_module( "//src/material-experimental/mdc-input", "//src/material-experimental/mdc-menu", "//src/material-experimental/mdc-progress-bar", + "//src/material-experimental/mdc-progress-spinner", "//src/material-experimental/mdc-radio", "//src/material-experimental/mdc-slide-toggle", "//src/material-experimental/mdc-slider", diff --git a/src/e2e-app/devserver-configure.js b/src/e2e-app/devserver-configure.js index 9906a958623d..4b5b619a7d11 100644 --- a/src/e2e-app/devserver-configure.js +++ b/src/e2e-app/devserver-configure.js @@ -13,6 +13,7 @@ require.config({ '@material/base': '@material/base/dist/mdc.base', '@material/checkbox': '@material/checkbox/dist/mdc.checkbox', '@material/chips': '@material/chips/dist/mdc.chips', + '@material/circular-progress': '@material/circular-progress/dist/mdc.circularProgress', '@material/dialog': '@material/dialog/dist/mdc.dialog', '@material/dom': '@material/dom/dist/mdc.dom', '@material/drawer': '@material/drawer/dist/mdc.drawer', diff --git a/src/e2e-app/e2e-app/e2e-app-layout.html b/src/e2e-app/e2e-app/e2e-app-layout.html index 36147be3f023..1cedee067943 100644 --- a/src/e2e-app/e2e-app/e2e-app-layout.html +++ b/src/e2e-app/e2e-app/e2e-app-layout.html @@ -34,6 +34,7 @@ MDC Table MDC Tabs MDC Progress bar + MDC Progress spinner
diff --git a/src/e2e-app/e2e-app/routes.ts b/src/e2e-app/e2e-app/routes.ts index a88f9fb64b84..e699e23e181d 100644 --- a/src/e2e-app/e2e-app/routes.ts +++ b/src/e2e-app/e2e-app/routes.ts @@ -23,6 +23,7 @@ import {MdcSliderE2e} from '../mdc-slider/mdc-slider-e2e'; import {MdcTableE2e} from '../mdc-table/mdc-table-e2e'; import {MdcTabsE2e} from '../mdc-tabs/mdc-tabs-e2e'; import {MdcProgressBarE2E} from '../mdc-progress-bar/mdc-progress-bar-e2e'; +import {MdcProgressSpinnerE2e} from '../mdc-progress-spinner/mdc-progress-spinner-e2e'; import {MenuE2E} from '../menu/menu-e2e'; import {ProgressBarE2E} from '../progress-bar/progress-bar-e2e'; import {ProgressSpinnerE2E} from '../progress-spinner/progress-spinner-e2e'; @@ -61,6 +62,7 @@ export const E2E_APP_ROUTES: Routes = [ {path: 'mdc-tabs', component: MdcTabsE2e}, {path: 'mdc-table', component: MdcTableE2e}, {path: 'mdc-progress-bar', component: MdcProgressBarE2E}, + {path: 'mdc-progress-spinner', component: MdcProgressSpinnerE2e}, {path: 'menu', component: MenuE2E}, {path: 'progress-bar', component: ProgressBarE2E}, {path: 'progress-spinner', component: ProgressSpinnerE2E}, diff --git a/src/e2e-app/main-module.ts b/src/e2e-app/main-module.ts index b2bb0cc28d35..30a8e3e36b5f 100644 --- a/src/e2e-app/main-module.ts +++ b/src/e2e-app/main-module.ts @@ -40,6 +40,7 @@ import {TabsE2eModule} from './tabs/tabs-e2e-module'; import {ToolbarE2eModule} from './toolbar/toolbar-e2e-module'; import {VirtualScrollE2eModule} from './virtual-scroll/virtual-scroll-e2e-module'; import {MdcProgressBarE2eModule} from './mdc-progress-bar/mdc-progress-bar-e2e-module'; +import {MdcProgressSpinnerE2eModule} from './mdc-progress-spinner/mdc-progress-spinner-module'; @NgModule({ imports: [ @@ -72,6 +73,7 @@ import {MdcProgressBarE2eModule} from './mdc-progress-bar/mdc-progress-bar-e2e-m MdcTableE2eModule, MdcTabsE2eModule, MdcProgressBarE2eModule, + MdcProgressSpinnerE2eModule, MenuE2eModule, ProgressBarE2eModule, ProgressSpinnerE2eModule, diff --git a/src/e2e-app/mdc-progress-spinner/mdc-progress-spinner-e2e.html b/src/e2e-app/mdc-progress-spinner/mdc-progress-spinner-e2e.html new file mode 100644 index 000000000000..e2672d5bd949 --- /dev/null +++ b/src/e2e-app/mdc-progress-spinner/mdc-progress-spinner-e2e.html @@ -0,0 +1,3 @@ + + + diff --git a/src/e2e-app/mdc-progress-spinner/mdc-progress-spinner-e2e.ts b/src/e2e-app/mdc-progress-spinner/mdc-progress-spinner-e2e.ts new file mode 100644 index 000000000000..ad20e871b974 --- /dev/null +++ b/src/e2e-app/mdc-progress-spinner/mdc-progress-spinner-e2e.ts @@ -0,0 +1,19 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {Component} from '@angular/core'; + +@Component({ + selector: 'mdc-progress-spinner-e2e', + templateUrl: 'mdc-progress-spinner-e2e.html' +}) +export class MdcProgressSpinnerE2e { + value = 65; + diameter = 37; + strokeWidth = 6; +} diff --git a/src/e2e-app/mdc-progress-spinner/mdc-progress-spinner-module.ts b/src/e2e-app/mdc-progress-spinner/mdc-progress-spinner-module.ts new file mode 100644 index 000000000000..78da71c0da20 --- /dev/null +++ b/src/e2e-app/mdc-progress-spinner/mdc-progress-spinner-module.ts @@ -0,0 +1,17 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {NgModule} from '@angular/core'; +import {MatProgressSpinnerModule} from '@angular/material-experimental/mdc-progress-spinner'; +import {MdcProgressSpinnerE2e} from './mdc-progress-spinner-e2e'; + +@NgModule({ + imports: [MatProgressSpinnerModule], + declarations: [MdcProgressSpinnerE2e] +}) +export class MdcProgressSpinnerE2eModule {} diff --git a/src/material-experimental/config.bzl b/src/material-experimental/config.bzl index d94533d7e327..65b7ce391822 100644 --- a/src/material-experimental/config.bzl +++ b/src/material-experimental/config.bzl @@ -17,6 +17,8 @@ entryPoints = [ "mdc-menu/testing", "mdc-progress-bar", "mdc-progress-bar/testing", + "mdc-progress-spinner", + "mdc-progress-spinner/testing", "mdc-radio", "mdc-select", "mdc-sidenav", diff --git a/src/material-experimental/mdc-progress-spinner/BUILD.bazel b/src/material-experimental/mdc-progress-spinner/BUILD.bazel new file mode 100644 index 000000000000..73419190adc9 --- /dev/null +++ b/src/material-experimental/mdc-progress-spinner/BUILD.bazel @@ -0,0 +1,95 @@ +load("//src/e2e-app:test_suite.bzl", "e2e_test_suite") +load( + "//tools:defaults.bzl", + "ng_e2e_test_library", + "ng_module", + "ng_test_library", + "ng_web_test_suite", + "sass_binary", + "sass_library", +) + +package(default_visibility = ["//visibility:public"]) + +ng_module( + name = "mdc-progress-spinner", + srcs = glob( + ["**/*.ts"], + exclude = [ + "**/*.spec.ts", + ], + ), + assets = [":progress_spinner_scss"] + glob(["**/*.html"]), + module_name = "@angular/material-experimental/mdc-progress-spinner", + deps = [ + "//src/cdk/platform", + "//src/material/core", + "//src/material/progress-spinner", + "@npm//@angular/common", + "@npm//@angular/core", + "@npm//@material/circular-progress", + ], +) + +sass_library( + name = "mdc_progress_spinner_scss_lib", + srcs = glob(["**/_*.scss"]), + deps = [ + "//src/material-experimental/mdc-helpers:mdc_helpers_scss_lib", + "//src/material-experimental/mdc-helpers:mdc_scss_deps_lib", + ], +) + +sass_binary( + name = "progress_spinner_scss", + src = "progress-spinner.scss", + include_paths = [ + "external/npm/node_modules", + ], + deps = [ + "//src/material-experimental/mdc-helpers:mdc_helpers_scss_lib", + "//src/material-experimental/mdc-helpers:mdc_scss_deps_lib", + ], +) + +ng_test_library( + name = "progress_spinner_tests_lib", + srcs = glob( + ["**/*.spec.ts"], + exclude = ["**/*.e2e.spec.ts"], + ), + deps = [ + ":mdc-progress-spinner", + "//src/cdk/platform", + "//src/material/progress-spinner", + "@npm//@angular/common", + "@npm//@angular/platform-browser", + ], +) + +ng_web_test_suite( + name = "unit_tests", + static_files = [ + "@npm//:node_modules/@material/circular-progress/dist/mdc.circularProgress.js", + ], + deps = [ + ":progress_spinner_tests_lib", + "//src/material-experimental:mdc_require_config.js", + ], +) + +ng_e2e_test_library( + name = "e2e_test_sources", + srcs = glob(["**/*.e2e.spec.ts"]), + deps = [ + "//src/cdk/testing/private/e2e", + ], +) + +e2e_test_suite( + name = "e2e_tests", + deps = [ + ":e2e_test_sources", + "//src/cdk/testing/private/e2e", + ], +) diff --git a/src/material-experimental/mdc-progress-spinner/README.md b/src/material-experimental/mdc-progress-spinner/README.md new file mode 100644 index 000000000000..3c70c4e81c91 --- /dev/null +++ b/src/material-experimental/mdc-progress-spinner/README.md @@ -0,0 +1,88 @@ +This is prototype of an alternate version of `` built on top of +[MDC Web](https://github.com/material-components/material-components-web). It demonstrates how +Angular Material could use MDC Web under the hood while still exposing the same API Angular users as +the existing ``. This component is experimental and should not be used in +production. + +## How to use +Assuming your application is already up and running using Angular Material, you can add this +component by following these steps: + +1. Install Angular Material Experimental & MDC WEB: + + ```bash + npm i material-components-web @angular/material-experimental + ``` + +2. In your `angular.json`, make sure `node_modules/` is listed as a Sass include path. This is + needed for the Sass compiler to be able to find the MDC Web Sass files. + + ```json + ... + "styles": [ + "src/styles.scss" + ], + "stylePreprocessorOptions": { + "includePaths": [ + "node_modules/" + ] + }, + ... + ``` + +3. Import the experimental `MatProgressSpinnerModule` and add it to the module that declares your + component: + + ```ts + import {MatProgressSpinnerModule} from '@angular/material-experimental/mdc-progress-spinner'; + + @NgModule({ + declarations: [MyComponent], + imports: [MatProgressSpinnerModule], + }) + export class MyModule {} + ``` + +4. Add use `` in your component's template, just like you would the normal + ``: + + ```html + + ``` + +5. Add the theme and typography mixins to your Sass. (There is currently no pre-built CSS option for + the experimental ``): + + ```scss + @import '~@angular/material/theming'; + @import '~@angular/material-experimental/mdc-progress-spinner'; + + $my-primary: mat-palette($mat-indigo); + $my-accent: mat-palette($mat-pink, A200, A100, A400); + $my-theme: mat-light-theme(( + color: ( + primary: $my-primary, + accent: $my-accent + ) + )); + + @include mat-mdc-progress-spinner-theme($my-theme); + @include mat-mdc-progress-spinner-typography(); + ``` + +## Replacing the standard progress spinner in an existing app +Because the experimental API mirrors the API for the standard progress spinner, it can easily be swapped +in by just changing the import paths. There is currently no schematic for this, but you can run the +following string replace across your TypeScript files: + +```bash +grep -lr --include="*.ts" --exclude-dir="node_modules" \ + --exclude="*.d.ts" "['\"]@angular/material/progress-spinner['\"]" | xargs sed -i \ + "s/['\"]@angular\/material\/progress-spinner['\"]/'@angular\/material-experimental\/mdc-progress-spinner'/g" +``` + +CSS styles and tests that depend on implementation details of mat-progress-spinner (such as getting +elements from the template by class name) will need to be manually updated. + +There are some small visual differences between this progress and the standard `mat-progress-spinner`. +This progress spinner has slightly different animation timings and easing curves. diff --git a/src/material-experimental/mdc-progress-spinner/_progress-spinner-theme.scss b/src/material-experimental/mdc-progress-spinner/_progress-spinner-theme.scss new file mode 100644 index 000000000000..8dd80b3b71a4 --- /dev/null +++ b/src/material-experimental/mdc-progress-spinner/_progress-spinner-theme.scss @@ -0,0 +1,46 @@ +@import '@material/circular-progress/mixins.import'; +@import '../mdc-helpers/mdc-helpers'; + +@mixin _mat-mdc-progress-spinner-color($color) { + @include mdc-circular-progress-color($color, $query: $mat-theme-styles-query); +} + +@mixin mat-mdc-progress-spinner-color($config-or-theme) { + $config: mat-get-color-config($config-or-theme); + @include mat-using-mdc-theme($config) { + .mat-mdc-progress-spinner { + @include _mat-mdc-progress-spinner-color(primary); + + &.mat-accent { + @include _mat-mdc-progress-spinner-color(secondary); + } + + &.mat-warn { + @include _mat-mdc-progress-spinner-color(error); + } + } + } +} + +@mixin mat-mdc-progress-spinner-typography($config-or-theme) {} + +@mixin mat-mdc-progress-spinner-density($config-or-theme) {} + +@mixin mat-mdc-progress-spinner-theme($theme-or-color-config) { + $theme: _mat-legacy-get-theme($theme-or-color-config); + @include _mat-check-duplicate-theme-styles($theme, 'mat-mdc-progress-spinner') { + $color: mat-get-color-config($theme); + $density: mat-get-density-config($theme); + $typography: mat-get-typography-config($theme); + + @if $color != null { + @include mat-mdc-progress-spinner-color($color); + } + @if $density != null { + @include mat-mdc-progress-spinner-density($density); + } + @if $typography != null { + @include mat-mdc-progress-spinner-typography($typography); + } + } +} diff --git a/src/material-experimental/mdc-progress-spinner/index.ts b/src/material-experimental/mdc-progress-spinner/index.ts new file mode 100644 index 000000000000..676ca90f1ffa --- /dev/null +++ b/src/material-experimental/mdc-progress-spinner/index.ts @@ -0,0 +1,9 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +export * from './public-api'; diff --git a/src/material-experimental/mdc-progress-spinner/module.ts b/src/material-experimental/mdc-progress-spinner/module.ts new file mode 100644 index 000000000000..b7cfff51bc78 --- /dev/null +++ b/src/material-experimental/mdc-progress-spinner/module.ts @@ -0,0 +1,20 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {NgModule} from '@angular/core'; +import {MatCommonModule} from '@angular/material/core'; +import {MatProgressSpinner, MatSpinner} from './progress-spinner'; +import {CommonModule} from '@angular/common'; + +@NgModule({ + imports: [CommonModule], + exports: [MatProgressSpinner, MatSpinner, MatCommonModule], + declarations: [MatProgressSpinner, MatSpinner], +}) +export class MatProgressSpinnerModule { +} diff --git a/src/material-experimental/mdc-progress-spinner/progress-spinner.e2e.spec.ts b/src/material-experimental/mdc-progress-spinner/progress-spinner.e2e.spec.ts new file mode 100644 index 000000000000..e7347d20a384 --- /dev/null +++ b/src/material-experimental/mdc-progress-spinner/progress-spinner.e2e.spec.ts @@ -0,0 +1,18 @@ +import {browser, by, element} from 'protractor'; + +describe('MDC-based progress-spinner', () => { + beforeEach(async () => await browser.get('/mdc-progress-spinner')); + + it('should render a determinate progress spinner', async () => { + expect(await element(by.css('mat-progress-spinner')).isPresent()).toBe(true); + }); + + it('should render an indeterminate progress spinner', async () => { + expect(await element(by.css('mat-progress-spinner[mode="indeterminate"]')).isPresent()) + .toBe(true); + }); + + it('should render a spinner', async () => { + expect(await element(by.css('mat-spinner')).isPresent()).toBe(true); + }); +}); diff --git a/src/material-experimental/mdc-progress-spinner/progress-spinner.html b/src/material-experimental/mdc-progress-spinner/progress-spinner.html new file mode 100644 index 000000000000..ec5cf42f6dc3 --- /dev/null +++ b/src/material-experimental/mdc-progress-spinner/progress-spinner.html @@ -0,0 +1,36 @@ + + + + + + +
+ + + +
+ +
+
+
+ +
+
+ +
+
+ +
+
+
diff --git a/src/material-experimental/mdc-progress-spinner/progress-spinner.scss b/src/material-experimental/mdc-progress-spinner/progress-spinner.scss new file mode 100644 index 000000000000..610abd1ec850 --- /dev/null +++ b/src/material-experimental/mdc-progress-spinner/progress-spinner.scss @@ -0,0 +1,9 @@ +@import '@material/circular-progress/mixins.import'; +@import '../mdc-helpers/mdc-helpers'; + + +@include mdc-circular-progress-core-styles($query: $mat-base-styles-without-animation-query); + +:not(._mat-animation-noopable) { + @include mdc-circular-progress-core-styles($query: animation); +} diff --git a/src/material-experimental/mdc-progress-spinner/progress-spinner.spec.ts b/src/material-experimental/mdc-progress-spinner/progress-spinner.spec.ts new file mode 100644 index 000000000000..46516577c9d1 --- /dev/null +++ b/src/material-experimental/mdc-progress-spinner/progress-spinner.spec.ts @@ -0,0 +1,385 @@ +import {async, TestBed} from '@angular/core/testing'; +import { + MatProgressSpinner, + MatProgressSpinnerModule +} from '@angular/material-experimental/mdc-progress-spinner'; +import {CommonModule} from '@angular/common'; +import {By} from '@angular/platform-browser'; +import {MAT_PROGRESS_SPINNER_DEFAULT_OPTIONS} from '@angular/material/progress-spinner'; +import {Component, ElementRef, ViewChild, ViewEncapsulation} from '@angular/core'; + +describe('MDC-based MatProgressSpinner', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [MatProgressSpinnerModule, CommonModule], + declarations: [ + BasicProgressSpinner, + IndeterminateProgressSpinner, + ProgressSpinnerWithValueAndBoundMode, + ProgressSpinnerWithColor, + ProgressSpinnerCustomStrokeWidth, + ProgressSpinnerCustomDiameter, + SpinnerWithColor, + ProgressSpinnerWithStringValues, + IndeterminateSpinnerInShadowDom, + IndeterminateSpinnerInShadowDomWithNgIf, + ], + }).compileComponents(); + })); + + it('should apply a mode of "determinate" if no mode is provided.', () => { + let fixture = TestBed.createComponent(BasicProgressSpinner); + fixture.detectChanges(); + + let progressElement = fixture.debugElement.query(By.css('mat-progress-spinner'))!; + expect(progressElement.componentInstance.mode).toBe('determinate'); + }); + + it('should not modify the mode if a valid mode is provided.', () => { + let fixture = TestBed.createComponent(IndeterminateProgressSpinner); + fixture.detectChanges(); + + let progressElement = fixture.debugElement.query(By.css('mat-progress-spinner'))!; + expect(progressElement.componentInstance.mode).toBe('indeterminate'); + }); + + it('should define a default value of zero for the value attribute', () => { + let fixture = TestBed.createComponent(BasicProgressSpinner); + fixture.detectChanges(); + + let progressElement = fixture.debugElement.query(By.css('mat-progress-spinner'))!; + expect(progressElement.componentInstance.value).toBe(0); + }); + + it('should set the value to 0 when the mode is set to indeterminate', () => { + let fixture = TestBed.createComponent(ProgressSpinnerWithValueAndBoundMode); + let progressElement = fixture.debugElement.query(By.css('mat-progress-spinner'))!; + fixture.componentInstance.mode = 'determinate'; + fixture.detectChanges(); + + expect(progressElement.componentInstance.value).toBe(50); + fixture.componentInstance.mode = 'indeterminate'; + fixture.detectChanges(); + expect(progressElement.componentInstance.value).toBe(0); + }); + + it('should retain the value if it updates while indeterminate', () => { + let fixture = TestBed.createComponent(ProgressSpinnerWithValueAndBoundMode); + let progressElement = fixture.debugElement.query(By.css('mat-progress-spinner'))!; + + fixture.componentInstance.mode = 'determinate'; + fixture.detectChanges(); + expect(progressElement.componentInstance.value).toBe(50); + + fixture.componentInstance.mode = 'indeterminate'; + fixture.detectChanges(); + expect(progressElement.componentInstance.value).toBe(0); + + fixture.componentInstance.value = 75; + fixture.detectChanges(); + expect(progressElement.componentInstance.value).toBe(0); + + fixture.componentInstance.mode = 'determinate'; + fixture.detectChanges(); + expect(progressElement.componentInstance.value).toBe(75); + }); + + it('should clamp the value of the progress between 0 and 100', () => { + let fixture = TestBed.createComponent(BasicProgressSpinner); + fixture.detectChanges(); + + let progressElement = fixture.debugElement.query(By.css('mat-progress-spinner'))!; + let progressComponent = progressElement.componentInstance; + + progressComponent.value = 50; + expect(progressComponent.value).toBe(50); + + progressComponent.value = 0; + expect(progressComponent.value).toBe(0); + + progressComponent.value = 100; + expect(progressComponent.value).toBe(100); + + progressComponent.value = 999; + expect(progressComponent.value).toBe(100); + + progressComponent.value = -10; + expect(progressComponent.value).toBe(0); + }); + + it('should default to a stroke width that is 10% of the diameter', () => { + const fixture = TestBed.createComponent(ProgressSpinnerCustomDiameter); + const spinner = fixture.debugElement.query(By.directive(MatProgressSpinner))!; + + fixture.componentInstance.diameter = 67; + fixture.detectChanges(); + + expect(spinner.componentInstance.strokeWidth).toBe(6.7); + }); + + it('should allow a custom diameter', () => { + const fixture = TestBed.createComponent(ProgressSpinnerCustomDiameter); + const spinner = fixture.debugElement.query(By.css('mat-progress-spinner'))!.nativeElement; + const svgElement = fixture.nativeElement.querySelector('svg'); + + fixture.componentInstance.diameter = 32; + fixture.detectChanges(); + + expect(parseInt(spinner.style.width)) + .toBe(32, 'Expected the custom diameter to be applied to the host element width.'); + expect(parseInt(spinner.style.height)) + .toBe(32, 'Expected the custom diameter to be applied to the host element height.'); + expect(parseInt(svgElement.clientWidth)) + .toBe(32, 'Expected the custom diameter to be applied to the svg element width.'); + expect(parseInt(svgElement.clientHeight)) + .toBe(32, 'Expected the custom diameter to be applied to the svg element height.'); + expect(svgElement.getAttribute('viewBox')) + .toBe('0 0 25.2 25.2', 'Expected the custom diameter to be applied to the svg viewBox.'); + }); + + it('should allow a custom stroke width', () => { + const fixture = TestBed.createComponent(ProgressSpinnerCustomStrokeWidth); + + fixture.componentInstance.strokeWidth = 40; + fixture.detectChanges(); + + const circleElement = fixture.nativeElement.querySelector('circle'); + const svgElement = fixture.nativeElement.querySelector('svg'); + + expect(parseInt(circleElement.style.strokeWidth)).toBe(40, 'Expected the custom stroke ' + + 'width to be applied to the circle element as a percentage of the element size.'); + expect(svgElement.getAttribute('viewBox')) + .toBe('0 0 130 130', 'Expected the viewBox to be adjusted based on the stroke width.'); + }); + + it('should expand the host element if the stroke width is greater than the default', () => { + const fixture = TestBed.createComponent(ProgressSpinnerCustomStrokeWidth); + const element = fixture.debugElement.nativeElement.querySelector('.mat-mdc-progress-spinner'); + + fixture.componentInstance.strokeWidth = 40; + fixture.detectChanges(); + + expect(element.style.width).toBe('100px'); + expect(element.style.height).toBe('100px'); + }); + + it('should not collapse the host element if the stroke width is less than the default', () => { + const fixture = TestBed.createComponent(ProgressSpinnerCustomStrokeWidth); + const element = fixture.debugElement.nativeElement.querySelector('.mat-mdc-progress-spinner'); + + fixture.componentInstance.strokeWidth = 5; + fixture.detectChanges(); + + expect(element.style.width).toBe('100px'); + expect(element.style.height).toBe('100px'); + }); + + it('should set the color class on the mat-spinner', () => { + let fixture = TestBed.createComponent(SpinnerWithColor); + fixture.detectChanges(); + + let progressElement = fixture.debugElement.query(By.css('mat-spinner'))!; + + expect(progressElement.nativeElement.classList).toContain('mat-primary'); + + fixture.componentInstance.color = 'accent'; + fixture.detectChanges(); + + expect(progressElement.nativeElement.classList).toContain('mat-accent'); + expect(progressElement.nativeElement.classList).not.toContain('mat-primary'); + }); + + it('should set the color class on the mat-progress-spinner', () => { + let fixture = TestBed.createComponent(ProgressSpinnerWithColor); + fixture.detectChanges(); + + let progressElement = fixture.debugElement.query(By.css('mat-progress-spinner'))!; + + expect(progressElement.nativeElement.classList).toContain('mat-primary'); + + fixture.componentInstance.color = 'accent'; + fixture.detectChanges(); + + expect(progressElement.nativeElement.classList).toContain('mat-accent'); + expect(progressElement.nativeElement.classList).not.toContain('mat-primary'); + }); + + it('should remove the underlying SVG element from the tab order explicitly', () => { + const fixture = TestBed.createComponent(BasicProgressSpinner); + + fixture.detectChanges(); + + expect(fixture.nativeElement.querySelector('svg').getAttribute('focusable')).toBe('false'); + }); + + it('should handle the number inputs being passed in as strings', () => { + const fixture = TestBed.createComponent(ProgressSpinnerWithStringValues); + const spinner = fixture.debugElement.query(By.directive(MatProgressSpinner))!; + const svgElement = spinner.nativeElement.querySelector('svg'); + + fixture.detectChanges(); + + expect(spinner.componentInstance.diameter).toBe(37); + expect(spinner.componentInstance.strokeWidth).toBe(11); + expect(spinner.componentInstance.value).toBe(25); + + expect(spinner.nativeElement.style.width).toBe('37px'); + expect(spinner.nativeElement.style.height).toBe('37px'); + + expect(svgElement.clientWidth).toBe(37); + expect(svgElement.clientHeight).toBe(37); + expect(svgElement.getAttribute('viewBox')).toBe('0 0 38 38'); + }); + + it('should update the element size when changed dynamically', () => { + let fixture = TestBed.createComponent(BasicProgressSpinner); + let spinner = fixture.debugElement.query(By.directive(MatProgressSpinner))!; + spinner.componentInstance.diameter = 32; + fixture.detectChanges(); + expect(spinner.nativeElement.style.width).toBe('32px'); + expect(spinner.nativeElement.style.height).toBe('32px'); + }); + + it('should be able to set a default diameter', () => { + TestBed + .resetTestingModule() + .configureTestingModule({ + imports: [MatProgressSpinnerModule], + declarations: [BasicProgressSpinner], + providers: [{ + provide: MAT_PROGRESS_SPINNER_DEFAULT_OPTIONS, + useValue: {diameter: 23} + }] + }) + .compileComponents(); + + const fixture = TestBed.createComponent(BasicProgressSpinner); + fixture.detectChanges(); + + const progressElement = fixture.debugElement.query(By.css('mat-progress-spinner'))!; + expect(progressElement.componentInstance.diameter).toBe(23); + }); + + it('should be able to set a default stroke width', () => { + TestBed + .resetTestingModule() + .configureTestingModule({ + imports: [MatProgressSpinnerModule], + declarations: [BasicProgressSpinner], + providers: [{ + provide: MAT_PROGRESS_SPINNER_DEFAULT_OPTIONS, + useValue: {strokeWidth: 7} + }] + }) + .compileComponents(); + + const fixture = TestBed.createComponent(BasicProgressSpinner); + fixture.detectChanges(); + + const progressElement = fixture.debugElement.query(By.css('mat-progress-spinner'))!; + expect(progressElement.componentInstance.strokeWidth).toBe(7); + }); + + it('should set `aria-valuenow` to the current value in determinate mode', () => { + const fixture = TestBed.createComponent(ProgressSpinnerWithValueAndBoundMode); + const progressElement = fixture.debugElement.query(By.css('mat-progress-spinner'))!; + fixture.componentInstance.mode = 'determinate'; + fixture.componentInstance.value = 37; + fixture.detectChanges(); + + expect(progressElement.nativeElement.getAttribute('aria-valuenow')).toBe('0.37'); + }); + + it('should clear `aria-valuenow` in indeterminate mode', () => { + const fixture = TestBed.createComponent(ProgressSpinnerWithValueAndBoundMode); + const progressElement = fixture.debugElement.query(By.css('mat-progress-spinner'))!; + fixture.componentInstance.mode = 'determinate'; + fixture.componentInstance.value = 89; + fixture.detectChanges(); + + expect(progressElement.nativeElement.hasAttribute('aria-valuenow')).toBe(true); + + fixture.componentInstance.mode = 'indeterminate'; + fixture.detectChanges(); + + expect(progressElement.nativeElement.hasAttribute('aria-valuenow')).toBe(false); + }); +}); + + +@Component({template: ''}) +class BasicProgressSpinner { +} + +@Component({template: ''}) +class ProgressSpinnerCustomStrokeWidth { + strokeWidth: number; +} + +@Component({template: ''}) +class ProgressSpinnerCustomDiameter { + diameter: number; +} + +@Component({template: ''}) +class IndeterminateProgressSpinner { +} + +@Component({ + template: '' +}) +class ProgressSpinnerWithValueAndBoundMode { + mode = 'indeterminate'; + value = 50; +} + +@Component({ + template: ` + ` +}) +class SpinnerWithColor { + color: string = 'primary'; +} + +@Component({ + template: ` + ` +}) +class ProgressSpinnerWithColor { + color: string = 'primary'; +} + +@Component({ + template: ` + + ` +}) +class ProgressSpinnerWithStringValues { +} + + +@Component({ + template: ` + + `, + encapsulation: ViewEncapsulation.ShadowDom, +}) +class IndeterminateSpinnerInShadowDom { + diameter: number; +} + +@Component({ + template: ` +
+ +
+ `, + encapsulation: ViewEncapsulation.ShadowDom, +}) +class IndeterminateSpinnerInShadowDomWithNgIf { + @ViewChild(MatProgressSpinner, {read: ElementRef}) + spinner: ElementRef; + + diameter: number; +} + diff --git a/src/material-experimental/mdc-progress-spinner/progress-spinner.ts b/src/material-experimental/mdc-progress-spinner/progress-spinner.ts new file mode 100644 index 000000000000..aeef9a5f4676 --- /dev/null +++ b/src/material-experimental/mdc-progress-spinner/progress-spinner.ts @@ -0,0 +1,242 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { + AfterViewInit, + ChangeDetectionStrategy, + Component, + ElementRef, + Inject, + Input, + OnDestroy, + Optional, + ViewChild, + ViewEncapsulation +} from '@angular/core'; +import { + MDCCircularProgressAdapter, + MDCCircularProgressFoundation +} from '@material/circular-progress'; +import {CanColor, CanColorCtor, mixinColor} from '@angular/material/core'; +import {ANIMATION_MODULE_TYPE} from '@angular/platform-browser/animations'; +import { + MAT_PROGRESS_SPINNER_DEFAULT_OPTIONS, + MatProgressSpinnerDefaultOptions +} from '@angular/material/progress-spinner'; +import {coerceNumberProperty, NumberInput} from '@angular/cdk/coercion'; + +// Boilerplate for applying mixins to MatProgressBar. +class MatProgressSpinnerBase { + constructor(public _elementRef: ElementRef) { + } +} + +const _MatProgressSpinnerMixinBase: CanColorCtor & typeof MatProgressSpinnerBase = + mixinColor(MatProgressSpinnerBase, 'primary'); + +/** Possible mode for a progress spinner. */ +export type ProgressSpinnerMode = 'determinate' | 'indeterminate'; + +/** + * Base reference size of the spinner. + */ +const BASE_SIZE = 100; + +/** + * Base reference stroke width of the spinner. + */ +const BASE_STROKE_WIDTH = 10; + +@Component({ + selector: 'mat-progress-spinner, mat-spinner', + exportAs: 'matProgressSpinner', + host: { + 'role': 'progressbar', + 'class': 'mat-mdc-progress-spinner mdc-circular-progress', + '[class._mat-animation-noopable]': `_noopAnimations`, + '[style.width.px]': 'diameter', + '[style.height.px]': 'diameter', + '[attr.aria-valuemin]': '0', + '[attr.aria-valuemax]': '100', + '[attr.aria-valuenow]': 'mode === "determinate" ? value : null', + '[attr.mode]': 'mode', + }, + inputs: ['color'], + templateUrl: 'progress-spinner.html', + styleUrls: ['progress-spinner.css'], + changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None, +}) +export class MatProgressSpinner extends _MatProgressSpinnerMixinBase implements AfterViewInit, + OnDestroy, CanColor { + + /** Whether the _mat-animation-noopable class should be applied, disabling animations. */ + _noopAnimations: boolean; + + /** Implements all of the logic of the MDC circular progress. */ + _foundation: MDCCircularProgressFoundation; + + /** The element of the determinate spinner. */ + @ViewChild('determinateSpinner') _determinateCircle: ElementRef; + + /** Adapter used by MDC to interact with the DOM. */ + // TODO: switch to class when MDC removes object spread in foundation + // https://github.com/material-components/material-components-web/pull/6256 + private _adapter: MDCCircularProgressAdapter = { + addClass: (className: string) => this._elementRef.nativeElement.classList.add(className), + hasClass: (className: string) => this._elementRef.nativeElement.classList.contains(className), + removeClass: (className: string) => this._elementRef.nativeElement.classList.remove(className), + removeAttribute: (name: string) => this._elementRef.nativeElement.removeAttribute(name), + setAttribute: (name: string, value: string) => + this._elementRef.nativeElement.setAttribute(name, value), + getDeterminateCircleAttribute: (attributeName: string) => + this._determinateCircle.nativeElement.getAttribute(attributeName), + setDeterminateCircleAttribute: (attributeName: string, value: string) => + this._determinateCircle.nativeElement.setAttribute(attributeName, value), + }; + + constructor(public _elementRef: ElementRef, + @Optional() @Inject(ANIMATION_MODULE_TYPE) animationMode: string, + @Inject(MAT_PROGRESS_SPINNER_DEFAULT_OPTIONS) + defaults?: MatProgressSpinnerDefaultOptions) { + super(_elementRef); + this._noopAnimations = animationMode === 'NoopAnimations' && + (!!defaults && !defaults._forceAnimations); + + if (defaults) { + if (defaults.diameter) { + this.diameter = defaults.diameter; + } + + if (defaults.strokeWidth) { + this.strokeWidth = defaults.strokeWidth; + } + } + } + + private _mode: ProgressSpinnerMode = this._elementRef.nativeElement.nodeName.toLowerCase() === + 'mat-spinner' ? 'indeterminate' : 'determinate'; + + /** + * Mode of the progress bar. + * + * Input must be one of these values: determinate, indeterminate, buffer, query, defaults to + * 'determinate'. + * Mirrored to mode attribute. + */ + @Input() + get mode(): ProgressSpinnerMode { return this._mode; } + + set mode(value: ProgressSpinnerMode) { + this._mode = value; + this._syncFoundation(); + } + + private _value = 0; + + /** Value of the progress bar. Defaults to zero. Mirrored to aria-valuenow. */ + @Input() + get value(): number { + return this.mode === 'determinate' ? this._value : 0; + } + + set value(v: number) { + this._value = Math.max(0, Math.min(100, coerceNumberProperty(v))); + this._syncFoundation(); + } + + private _diameter = BASE_SIZE; + + /** The diameter of the progress spinner (will set width and height of svg). */ + @Input() + get diameter(): number { + return this._diameter; + } + + set diameter(size: number) { + this._diameter = coerceNumberProperty(size); + this._syncFoundation(); + } + + private _strokeWidth: number; + + /** Stroke width of the progress spinner. */ + @Input() + get strokeWidth(): number { + return this._strokeWidth ?? this.diameter / 10; + } + + set strokeWidth(value: number) { + this._strokeWidth = coerceNumberProperty(value); + } + + /** The radius of the spinner, adjusted for stroke width. */ + _circleRadius(): number { + return (this.diameter - BASE_STROKE_WIDTH) / 2; + } + + /** The view box of the spinner's svg element. */ + _viewBox() { + const viewBox = this._circleRadius() * 2 + this.strokeWidth; + return `0 0 ${viewBox} ${viewBox}`; + } + + /** The stroke circumference of the svg circle. */ + _strokeCircumference(): number { + return 2 * Math.PI * this._circleRadius(); + } + + /** The dash offset of the svg circle. */ + _strokeDashOffset() { + if (this.mode === 'determinate') { + return this._strokeCircumference() * (100 - this._value) / 100; + } + return null; + } + + /** Stroke width of the circle in percent. */ + _circleStrokeWidth() { + return this.strokeWidth / this.diameter * 100; + } + + ngAfterViewInit() { + this._foundation = new MDCCircularProgressFoundation(this._adapter); + this._foundation.init(); + this._syncFoundation(); + } + + ngOnDestroy() { + if (this._foundation) { + this._foundation.destroy(); + } + } + + /** Syncs the state of the progress spinner with the MDC foundation. */ + private _syncFoundation() { + const foundation = this._foundation; + + if (foundation) { + const mode = this.mode; + foundation.setProgress(this.value / 100); + foundation.setDeterminate(mode === 'determinate'); + } + } + + static ngAcceptInputType_diameter: NumberInput; + static ngAcceptInputType_strokeWidth: NumberInput; + static ngAcceptInputType_value: NumberInput; +} + +/** + * `` component. + * + * This is a component definition to be used as a convenience reference to create an + * indeterminate `` instance. + */ +// tslint:disable-next-line:variable-name +export const MatSpinner = MatProgressSpinner; diff --git a/src/material-experimental/mdc-progress-spinner/public-api.ts b/src/material-experimental/mdc-progress-spinner/public-api.ts new file mode 100644 index 000000000000..6aa2230d1349 --- /dev/null +++ b/src/material-experimental/mdc-progress-spinner/public-api.ts @@ -0,0 +1,10 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +export * from './progress-spinner'; +export * from './module'; diff --git a/src/material-experimental/mdc-progress-spinner/testing/BUILD.bazel b/src/material-experimental/mdc-progress-spinner/testing/BUILD.bazel new file mode 100644 index 000000000000..d878e9403268 --- /dev/null +++ b/src/material-experimental/mdc-progress-spinner/testing/BUILD.bazel @@ -0,0 +1,38 @@ +load("//tools:defaults.bzl", "ng_module", "ng_test_library", "ng_web_test_suite") + +package(default_visibility = ["//visibility:public"]) + +ng_module( + name = "testing", + srcs = glob( + ["**/*.ts"], + exclude = ["**/*.spec.ts"], + ), + module_name = "@angular/material-experimental/mdc-progress-spinner/testing", + deps = [ + "//src/cdk/coercion", + "//src/cdk/testing", + "//src/material/progress-spinner/testing", + ], +) + +ng_test_library( + name = "unit_tests_lib", + srcs = glob(["**/*.spec.ts"]), + deps = [ + ":testing", + "//src/material-experimental/mdc-progress-spinner", + "//src/material/progress-spinner/testing:harness_tests_lib", + ], +) + +ng_web_test_suite( + name = "unit_tests", + static_files = [ + "@npm//:node_modules/@material/circular-progress/dist/mdc.circularProgress.js", + ], + deps = [ + ":unit_tests_lib", + "//src/material-experimental:mdc_require_config.js", + ], +) diff --git a/src/material-experimental/mdc-progress-spinner/testing/index.ts b/src/material-experimental/mdc-progress-spinner/testing/index.ts new file mode 100644 index 000000000000..676ca90f1ffa --- /dev/null +++ b/src/material-experimental/mdc-progress-spinner/testing/index.ts @@ -0,0 +1,9 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +export * from './public-api'; diff --git a/src/material-experimental/mdc-progress-spinner/testing/progress-spinner-harness.spec.ts b/src/material-experimental/mdc-progress-spinner/testing/progress-spinner-harness.spec.ts new file mode 100644 index 000000000000..ea15d90dadbc --- /dev/null +++ b/src/material-experimental/mdc-progress-spinner/testing/progress-spinner-harness.spec.ts @@ -0,0 +1,7 @@ +import {runHarnessTests} from '@angular/material/progress-spinner/testing/shared.spec'; +import {MatProgressSpinnerHarness} from './progress-spinner-harness'; +import {MatProgressSpinnerModule} from '../index'; + +describe('MDC-based MatProgressSpinnerHarness', () => { + runHarnessTests(MatProgressSpinnerModule, MatProgressSpinnerHarness); +}); diff --git a/src/material-experimental/mdc-progress-spinner/testing/progress-spinner-harness.ts b/src/material-experimental/mdc-progress-spinner/testing/progress-spinner-harness.ts new file mode 100644 index 000000000000..1ab7e56ad0fb --- /dev/null +++ b/src/material-experimental/mdc-progress-spinner/testing/progress-spinner-harness.ts @@ -0,0 +1,42 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {coerceNumberProperty} from '@angular/cdk/coercion'; +import {ComponentHarness, HarnessPredicate} from '@angular/cdk/testing'; +import {ProgressSpinnerMode} from '@angular/material/progress-spinner'; +import {ProgressSpinnerHarnessFilters} from '@angular/material/progress-spinner/testing'; + +/** Harness for interacting with a MDC based mat-progress-spinner in tests. */ +export class MatProgressSpinnerHarness extends ComponentHarness { + /** The selector for the host element of a `MatProgressSpinner` instance. */ + static hostSelector = 'mat-progress-spinner,mat-spinner'; + + /** + * Gets a `HarnessPredicate` that can be used to search for a `MatProgressSpinnerHarness` that + * meets certain criteria. + * @param options Options for filtering which progress spinner instances are considered a match. + * @return a `HarnessPredicate` configured with the given options. + */ + static with(options: ProgressSpinnerHarnessFilters = {}): + HarnessPredicate { + return new HarnessPredicate(MatProgressSpinnerHarness, options); + } + + /** Gets the progress spinner's value. */ + async getValue(): Promise { + const host = await this.host(); + const ariaValue = await host.getAttribute('aria-valuenow'); + return ariaValue ? coerceNumberProperty(ariaValue) : null; + } + + /** Gets the progress spinner's mode. */ + async getMode(): Promise { + const modeAttr = (await this.host()).getAttribute('mode'); + return await modeAttr as ProgressSpinnerMode; + } +} diff --git a/src/material-experimental/mdc-progress-spinner/testing/public-api.ts b/src/material-experimental/mdc-progress-spinner/testing/public-api.ts new file mode 100644 index 000000000000..77f91ded9e77 --- /dev/null +++ b/src/material-experimental/mdc-progress-spinner/testing/public-api.ts @@ -0,0 +1,9 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +export * from './progress-spinner-harness'; diff --git a/src/material-experimental/mdc-theming/BUILD.bazel b/src/material-experimental/mdc-theming/BUILD.bazel index 87ff3709bbf5..095b88a2f07f 100644 --- a/src/material-experimental/mdc-theming/BUILD.bazel +++ b/src/material-experimental/mdc-theming/BUILD.bazel @@ -25,6 +25,7 @@ sass_library( "//src/material-experimental/mdc-list:mdc_list_scss_lib", "//src/material-experimental/mdc-menu:mdc_menu_scss_lib", "//src/material-experimental/mdc-progress-bar:mdc_progress_bar_scss_lib", + "//src/material-experimental/mdc-progress-spinner:mdc_progress_spinner_scss_lib", "//src/material-experimental/mdc-radio:mdc_radio_scss_lib", "//src/material-experimental/mdc-slide-toggle:mdc_slide_toggle_scss_lib", "//src/material-experimental/mdc-slider:mdc_slider_scss_lib", diff --git a/src/material-experimental/mdc_require_config.js b/src/material-experimental/mdc_require_config.js index bff597ceb6a1..f86b99b8cef0 100644 --- a/src/material-experimental/mdc_require_config.js +++ b/src/material-experimental/mdc_require_config.js @@ -7,6 +7,7 @@ require.config({ '@material/base': '/base/npm/node_modules/@material/base/dist/mdc.base', '@material/checkbox': '/base/npm/node_modules/@material/checkbox/dist/mdc.checkbox', '@material/chips': '/base/npm/node_modules/@material/chips/dist/mdc.chips', + '@material/circular-progress': '/base/npm/node_modules/@material/circular-progress/dist/mdc.circularProgress', '@material/dialog': '/base/npm/node_modules/@material/dialog/dist/mdc.dialog', '@material/dom': '/base/npm/node_modules/@material/dom/dist/mdc.dom', '@material/drawer': '/base/npm/node_modules/@material/drawer/dist/mdc.drawer', diff --git a/src/universal-app/kitchen-sink-mdc/kitchen-sink-mdc.html b/src/universal-app/kitchen-sink-mdc/kitchen-sink-mdc.html index 9af4d662b63b..42abd2cfd384 100644 --- a/src/universal-app/kitchen-sink-mdc/kitchen-sink-mdc.html +++ b/src/universal-app/kitchen-sink-mdc/kitchen-sink-mdc.html @@ -195,3 +195,9 @@

Prefix and Suffix

Name + +

MDC Progress spinner

+ + + + diff --git a/src/universal-app/kitchen-sink-mdc/kitchen-sink-mdc.ts b/src/universal-app/kitchen-sink-mdc/kitchen-sink-mdc.ts index 7c3273c7e847..c36edb3b970b 100644 --- a/src/universal-app/kitchen-sink-mdc/kitchen-sink-mdc.ts +++ b/src/universal-app/kitchen-sink-mdc/kitchen-sink-mdc.ts @@ -14,6 +14,7 @@ import {MatTabsModule} from '@angular/material-experimental/mdc-tabs'; import {MatTableModule} from '@angular/material-experimental/mdc-table'; import {MatIconModule} from '@angular/material/icon'; import {MatSnackBarModule, MatSnackBar} from '@angular/material-experimental/mdc-snack-bar'; +import {MatProgressSpinnerModule} from '@angular/material-experimental/mdc-progress-spinner'; @Component({ selector: 'kitchen-sink-mdc', @@ -39,6 +40,7 @@ export class KitchenSinkMdc { MatTableModule, MatProgressBarModule, MatSnackBarModule, + MatProgressSpinnerModule, ], declarations: [KitchenSinkMdc], exports: [KitchenSinkMdc], diff --git a/tools/system-config-tmpl.js b/tools/system-config-tmpl.js index 6e8cdae10997..ca531ffe7962 100644 --- a/tools/system-config-tmpl.js +++ b/tools/system-config-tmpl.js @@ -42,6 +42,7 @@ var pathMapping = { '@material/base': 'node:@material/base/dist/mdc.base.js', '@material/checkbox': 'node:@material/checkbox/dist/mdc.checkbox.js', '@material/chips': 'node:@material/chips/dist/mdc.chips.js', + '@material/circular-progress': 'node:@material/circular-progress/dist/mdc.circularProgress.js', '@material/dialog': 'node:@material/dialog/dist/mdc.dialog.js', '@material/dom': 'node:@material/dom/dist/mdc.dom.js', '@material/drawer': 'node:@material/drawer/dist/mdc.drawer.js',